State machine for protocol handling in Embedded C - Time & Space Complexity
We want to understand how the time needed to run a state machine changes as the input grows.
Specifically, how many steps does the machine take when processing data?
Analyze the time complexity of the following code snippet.
typedef enum {IDLE, RECEIVING, PROCESSING, DONE} State;
void handle_protocol(char *data, int length) {
State current = IDLE;
for (int i = 0; i < length; i++) {
switch (current) {
case IDLE:
if (data[i] == 'S') current = RECEIVING;
break;
case RECEIVING:
// process data[i]
if (data[i] == 'E') current = PROCESSING;
break;
case PROCESSING:
// finalize processing
current = DONE;
break;
case DONE:
// finished
break;
}
}
}
This code processes a data stream using a state machine with four states.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The for-loop that goes through each data element once.
- How many times: Exactly once per data element, so 'length' times.
As the input length grows, the number of steps grows in a straight line.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 steps |
| 100 | About 100 steps |
| 1000 | About 1000 steps |
Pattern observation: The work grows directly with input size, no extra loops inside.
Time Complexity: O(n)
This means the time to process grows in direct proportion to the input length.
[X] Wrong: "Because there are multiple states, the time grows faster than the input size."
[OK] Correct: The states only change inside the single loop, so each input element is handled once, keeping growth linear.
Understanding how state machines scale helps you explain how protocols handle data efficiently in real devices.
"What if the state machine had nested loops inside a state? How would the time complexity change?"
Practice
Solution
Step 1: Understand the role of a state machine
A state machine organizes different states and transitions to manage complex sequences clearly.Step 2: Relate to protocol handling
Protocols require strict step-by-step control and safe event handling, which state machines provide.Final Answer:
To clearly control the sequence of protocol steps and handle events safely -> Option DQuick Check:
State machine controls protocol steps safely = C [OK]
- Thinking state machines speed up hardware
- Believing they reduce program size automatically
- Assuming they remove all conditionals
Solution
Step 1: Recall enum syntax in C
Enums use the syntax: enum Name {value1, value2, ...}; with commas separating values.Step 2: Check each option
enum ProtocolState {IDLE, WAIT_ACK, SEND_DATA, ERROR}; uses correct syntax with commas and no assignment. Others use invalid syntax like '=' or semicolons.Final Answer:
enum ProtocolState {IDLE, WAIT_ACK, SEND_DATA, ERROR}; -> Option AQuick Check:
Enum syntax with commas = B [OK]
- Using '=' after enum name
- Separating values with semicolons
- Using parentheses instead of braces
typedef enum {IDLE, WAIT_ACK, SEND_DATA} State;
void handle_event(State *state, int event) {
switch (*state) {
case IDLE:
if (event == 1) {
*state = SEND_DATA;
printf("Sending data\n");
}
break;
case SEND_DATA:
if (event == 2) {
*state = WAIT_ACK;
printf("Waiting for ACK\n");
}
break;
case WAIT_ACK:
if (event == 3) {
*state = IDLE;
printf("ACK received, back to IDLE\n");
}
break;
}
}
int main() {
State current_state = IDLE;
handle_event(ยคt_state, 1);
handle_event(ยคt_state, 2);
handle_event(ยคt_state, 3);
return 0;
}Solution
Step 1: Trace first event with state IDLE and event 1
State is IDLE, event 1 triggers transition to SEND_DATA and prints "Sending data".Step 2: Trace second event with state SEND_DATA and event 2
State is SEND_DATA, event 2 triggers transition to WAIT_ACK and prints "Waiting for ACK".Step 3: Trace third event with state WAIT_ACK and event 3
State is WAIT_ACK, event 3 triggers transition to IDLE and prints "ACK received, back to IDLE".Final Answer:
Sending data Waiting for ACK ACK received, back to IDLE -> Option AQuick Check:
Events 1,2,3 cause correct state prints = A [OK]
- Mixing order of printed lines
- Ignoring state changes after events
- Assuming all events print output
typedef enum {IDLE, SEND, WAIT} State;
void process_event(State *state, int event) {
switch (*state) {
case IDLE:
if (event == 1) {
*state = SEND;
printf("Start sending\n");
}
case SEND:
if (event == 2) {
*state = WAIT;
printf("Waiting for response\n");
}
break;
case WAIT:
if (event == 3) {
*state = IDLE;
printf("Back to idle\n");
}
break;
}
}Solution
Step 1: Analyze switch-case structure
The IDLE case lacks a break, so after executing IDLE code, it falls through to SEND case unintentionally.Step 2: Identify fix to prevent fall-through
Adding a break after IDLE case stops execution from continuing into SEND case.Final Answer:
Add a break after the IDLE case to prevent fall-through -> Option BQuick Check:
Missing break causes fall-through = A [OK]
- Ignoring missing break causing bugs
- Changing enum types unnecessarily
- Removing needed breaks
Solution
Step 1: Understand requirement for timeout handling
Timeout should reset to IDLE from any state immediately when detected.Step 2: Choose implementation approach
Checking timeout before switch-case allows immediate reset regardless of current state.Step 3: Evaluate other options
Adding TIMEOUT state limits reset to one state only; ignoring timeout misses requirement; goto is poor style and error-prone.Final Answer:
Add a check for timeout event before the switch-case and reset state to IDLE if triggered -> Option CQuick Check:
Pre-check timeout for immediate reset = D [OK]
- Adding unnecessary states for global events
- Ignoring timeout in state machine
- Using goto for flow control
Ready to prove it?
Pass the test and this pattern moves unknown
