Reading data from I2C device in Embedded C - Time & Space Complexity
When reading data from an I2C device, it's important to know how the time taken grows as we read more bytes.
We want to understand how the reading process scales with the amount of data requested.
Analyze the time complexity of the following code snippet.
void read_i2c_data(uint8_t device_addr, uint8_t reg_addr, uint8_t *buffer, int length) {
i2c_start();
i2c_write(device_addr << 1); // Write mode
i2c_write(reg_addr);
i2c_start();
i2c_write((device_addr << 1) | 1); // Read mode
for (int i = 0; i < length; i++) {
buffer[i] = i2c_read(i < length - 1 ? 1 : 0);
}
i2c_stop();
}
This code reads a sequence of bytes from a specific register of an I2C device into a buffer.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The for-loop reading each byte from the device.
- How many times: The loop runs exactly
lengthtimes, once per byte requested.
Each additional byte to read adds one more iteration of the loop, so the total time grows directly with the number of bytes.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 read operations |
| 100 | About 100 read operations |
| 1000 | About 1000 read operations |
Pattern observation: The time increases steadily and proportionally as more bytes are read.
Time Complexity: O(n)
This means the time to read data grows linearly with the number of bytes requested.
[X] Wrong: "Reading multiple bytes from I2C takes the same time as reading one byte."
[OK] Correct: Each byte requires a separate read operation, so more bytes mean more time spent communicating.
Understanding how data reading scales helps you design efficient embedded systems and shows you can reason about hardware communication costs.
"What if the I2C device supports burst reading that returns all bytes in one operation? How would the time complexity change?"
Practice
Solution
Step 1: Understand I2C device communication
I2C devices often provide sensor or hardware data that the program needs to read.Step 2: Identify the purpose of reading data
Reading data means fetching information like sensor values, not sending commands or resetting.Final Answer:
To get sensor or hardware information from the device -> Option BQuick Check:
Reading I2C = Getting device data [OK]
- Confusing reading with sending commands
- Thinking reading resets the device
- Assuming reading formats memory
Solution
Step 1: Understand buffer declaration for byte storage
To store multiple bytes, we need an array of chars (bytes) of size 4.Step 2: Check each option
char buffer[4]; declares a char array of size 4, suitable for 4 bytes. Others are either single variables or wrong types.Final Answer:
char buffer[4]; -> Option DQuick Check:
Buffer for 4 bytes = char array size 4 [OK]
- Using single char instead of array
- Using int for byte buffer
- Declaring wrong array size
result after reading 2 bytes from the I2C device?
uint8_t buffer[2]; int result = i2c_read(device_addr, reg_addr, buffer, 2);Assume
i2c_read returns 0 on success and -1 on failure.Solution
Step 1: Understand i2c_read return values
The function returns 0 if the read was successful, -1 if it failed.Step 2: Analyze the code snippet
The code reads 2 bytes and stores in buffer. If successful, result = 0.Final Answer:
0 -> Option AQuick Check:
Success return = 0 [OK]
- Assuming return is number of bytes read
- Confusing success with failure codes
- Ignoring return value meaning
uint8_t data;
int status = i2c_read(device_addr, reg_addr, &data, 1);
if (status = 0) {
process(data);
}Solution
Step 1: Check the if condition syntax
The condition uses single '=' which assigns 0 instead of comparing.Step 2: Understand effect of assignment in if
Assignment returns 0 (false), so process(data) never runs even on success.Final Answer:
Using assignment (=) instead of comparison (==) in if condition -> Option AQuick Check:
Use '==' for comparison in if [OK]
- Confusing '=' and '==' in if statements
- Ignoring return status check
- Not using address-of operator for buffer
uint8_t buf[3]; int status = i2c_read(device_addr, reg_addr, buf, 3); uint32_t value = ???;
Solution
Step 1: Understand byte order for 24-bit integer
Assuming buf[0] is most significant byte, shift it left by 16 bits.Step 2: Combine bytes with bitwise OR
Shift buf[1] by 8 bits, add buf[2] as least significant byte.Step 3: Check each option
value = (buf[0] << 16) | (buf[1] << 8) | buf[2]; correctly combines bytes in big-endian order. Others mix order or sum bytes incorrectly.Final Answer:
value = (buf[0] << 16) | (buf[1] << 8) | buf[2]; -> Option CQuick Check:
Shift and OR bytes for 24-bit value [OK]
- Mixing byte order
- Adding bytes instead of bitwise OR
- Not shifting bytes correctly
Ready to prove it?
Pass the test and this pattern moves unknown
