I2C acknowledge and NACK behavior in Embedded C - Time & Space Complexity
When working with I2C communication, it is important to understand how acknowledge (ACK) and not-acknowledge (NACK) signals affect the flow of data.
We want to see how the time taken changes as the number of bytes sent or received grows.
Analyze the time complexity of the following code snippet.
// Send bytes over I2C and check ACK/NACK
for (int i = 0; i < length; i++) {
I2C_SendByte(data[i]);
if (!I2C_WaitAck()) {
// NACK received, stop sending
break;
}
}
This code sends bytes one by one over I2C and stops if a NACK is received.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Sending each byte and waiting for ACK/NACK.
- How many times: Up to
lengthtimes, but may stop early if NACK occurs.
As the number of bytes to send increases, the number of send-and-wait steps grows roughly the same.
| Input Size (length) | Approx. Operations |
|---|---|
| 10 | Up to 10 send-and-wait steps |
| 100 | Up to 100 send-and-wait steps |
| 1000 | Up to 1000 send-and-wait steps |
Pattern observation: The time grows linearly with the number of bytes sent, unless a NACK stops the process early.
Time Complexity: O(n)
This means the time taken grows in direct proportion to the number of bytes sent over I2C.
[X] Wrong: "The time to send data over I2C is always constant regardless of data size."
[OK] Correct: Each byte requires sending and waiting for an ACK or NACK, so more bytes mean more time.
Understanding how communication protocols like I2C handle acknowledgments helps you reason about timing and reliability in embedded systems.
"What if the code always waits for an ACK but never stops on NACK? How would the time complexity change?"
Practice
Solution
Step 1: Understand ACK meaning in I2C
ACK means the receiver signals it has received the data correctly and is ready to continue.Step 2: Compare options with ACK meaning
Only The receiver successfully received data and is ready for more matches this meaning; others describe stop or errors.Final Answer:
The receiver successfully received data and is ready for more -> Option DQuick Check:
ACK = Ready for more data [OK]
- Confusing ACK with NACK meaning
- Thinking ACK means stop communication
- Assuming ACK means error occurred
Solution
Step 1: Identify correct function to read with NACK
To signal no more data is needed, the read function must be called with NACK.Step 2: Check options for correct usage
i2c_read_byte(I2C1, NACK); uses i2c_read_byte with NACK, which is correct. Others either send ACK or write instead of read.Final Answer:
i2c_read_byte(I2C1, NACK); -> Option BQuick Check:
Read last byte with NACK to stop [OK]
- Using ACK instead of NACK after last byte
- Calling write instead of read for NACK
- Confusing function names for ACK/NACK
for (int i = 0; i < 3; i++) {
data[i] = (i < 2) ? i2c_read_byte(I2C1, ACK) : i2c_read_byte(I2C1, NACK);
}
What is the purpose of sending NACK on the last byte?Solution
Step 1: Analyze the loop and ACK/NACK usage
The loop reads 3 bytes; for first two bytes it sends ACK, for last byte it sends NACK.Step 2: Understand NACK meaning on last byte
NACK signals the master will stop reading after this byte, telling the slave no more data is needed.Final Answer:
To indicate the master will stop reading after this byte -> Option CQuick Check:
NACK on last byte = stop reading [OK]
- Thinking NACK requests resend
- Assuming NACK resets bus
- Confusing ACK and NACK roles
for (int i = 0; i < 4; i++) {
data[i] = i2c_read_byte(I2C1, ACK);
}
What is the likely problem?Solution
Step 1: Check ACK/NACK usage in the loop
The code sends ACK for every byte read, never sending NACK to signal stop.Step 2: Understand effect of missing NACK
Without NACK after last byte, slave keeps sending data, causing endless transmission.Final Answer:
You never sent NACK after the last byte to stop reading -> Option AQuick Check:
Missing NACK means no stop signal [OK]
- Using ACK for all bytes including last
- Confusing read and write functions
- Ignoring need for STOP condition after read
Solution
Step 1: Understand ACK/NACK pattern for multiple bytes
All bytes except last must be read with ACK; last byte with NACK to stop.Step 2: Analyze each option
for (int i = 0; i < length - 1; i++) { data[i] = i2c_read_byte(I2C1, ACK); } data[length - 1] = i2c_read_byte(I2C1, NACK); reads all but last byte with ACK, then last byte with NACK, correctly signaling stop. Others send wrong signals or in wrong order.Final Answer:
for (int i = 0; i < length - 1; i++) { data[i] = i2c_read_byte(I2C1, ACK); } data[length - 1] = i2c_read_byte(I2C1, NACK); -> Option AQuick Check:
ACK all but last, NACK last byte [OK]
- Sending NACK on all bytes
- Sending ACK on last byte
- Reading last byte first with NACK
Ready to prove it?
Pass the test and this pattern moves unknown
