Bird
Raised Fist0
Embedded Cprogramming~10 mins

Reading data from I2C device in Embedded C - Step-by-Step Execution

Choose your learning style10 modes available
Concept Flow - Reading data from I2C device
Start I2C communication
↓
Send device address + write bit
↓
Send register address to read
↓
Restart I2C communication
↓
Send device address + read bit
↓
Read data byte(s) from device
↓
Send NACK and stop condition
↓
End communication
This flow shows the steps to read data from an I2C device: start communication, send device and register addresses, restart for reading, read data, then stop.
Execution Sample
Embedded C
uint8_t read_i2c_data(uint8_t dev_addr, uint8_t reg_addr) {
    i2c_start();
    i2c_write(dev_addr << 1 | 0); // write mode
    i2c_write(reg_addr);
    i2c_start();
    i2c_write(dev_addr << 1 | 1); // read mode
    uint8_t data = i2c_read_nack();
    i2c_stop();
    return data;
}
This function reads one byte from a register of an I2C device by following the I2C read protocol.
Execution Table
StepActionI2C Bus StateData Sent/ReceivedResult
1i2c_start()Start condition sentNoneBus ready for address
2i2c_write(dev_addr << 1 | 0)Address + Write bit sentDevice address + 0ACK received from device
3i2c_write(reg_addr)Register address sentRegister addressACK received from device
4i2c_start()Repeated start condition sentNoneBus ready for read address
5i2c_write(dev_addr << 1 | 1)Address + Read bit sentDevice address + 1ACK received from device
6i2c_read_nack()Reading data byteData byte receivedData stored in variable
7i2c_stop()Stop condition sentNoneCommunication ended
💡 Communication ends after stop condition; data byte successfully read.
Variable Tracker
VariableStartAfter Step 6Final
dev_addrinputunchangedunchanged
reg_addrinputunchangedunchanged
dataundefinedreceived byte from devicereturned value
Key Moments - 3 Insights
Why do we send a repeated start condition before reading?
The repeated start (step 4) tells the device we want to switch from writing the register address to reading data without releasing the bus, as shown in the execution_table.
What does the 'ACK received from device' mean after sending address or data?
It means the device acknowledged it received the byte correctly and is ready for the next step, ensuring communication continues smoothly (see steps 2, 3, and 5).
Why do we send a NACK after reading the data byte?
Sending a NACK after reading (step 6) tells the device we do not want more data, so it stops sending bytes before we send the stop condition.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table, what is the data variable value after step 6?
AThe data byte received from the device
BThe device address with read bit
CThe register address sent
DUndefined, no data read yet
💡 Hint
Check the 'Data Sent/Received' and 'Result' columns at step 6 in the execution_table.
At which step does the repeated start condition occur?
AStep 2
BStep 6
CStep 4
DStep 7
💡 Hint
Look for 'Repeated start condition sent' in the 'I2C Bus State' column of the execution_table.
If the device does not acknowledge the address at step 2, what happens?
AThe device sends data anyway
BThe communication should be stopped or retried
CThe communication continues normally
DThe register address is sent twice
💡 Hint
Recall that 'ACK received from device' means device is ready; no ACK means communication error (see step 2).
Concept Snapshot
I2C read sequence:
1. Start condition
2. Send device address + write bit
3. Send register address
4. Repeated start
5. Send device address + read bit
6. Read data byte + NACK
7. Stop condition
Always check ACK after each byte.
Full Transcript
This visual execution shows how to read a byte from an I2C device. First, the master sends a start condition, then the device address with the write bit to select the register. After sending the register address, a repeated start is sent to switch to read mode. The device address with the read bit is sent next, followed by reading the data byte. Finally, a NACK and stop condition end the communication. Variables track the device address, register address, and the data read. Key moments include why the repeated start is needed, the meaning of ACK, and why a NACK is sent after reading. The quizzes test understanding of data values, steps, and error handling.

Practice

(1/5)
1. What is the main purpose of reading data from an I2C device in embedded C?
easy
A. To send commands to the device only
B. To get sensor or hardware information from the device
C. To reset the device hardware
D. To format the device memory

Solution

  1. Step 1: Understand I2C device communication

    I2C devices often provide sensor or hardware data that the program needs to read.
  2. Step 2: Identify the purpose of reading data

    Reading data means fetching information like sensor values, not sending commands or resetting.
  3. Final Answer:

    To get sensor or hardware information from the device -> Option B
  4. Quick Check:

    Reading I2C = Getting device data [OK]
Hint: Reading I2C means fetching data from device [OK]
Common Mistakes:
  • Confusing reading with sending commands
  • Thinking reading resets the device
  • Assuming reading formats memory
2. Which of the following is the correct way to declare a buffer to read 4 bytes from an I2C device in embedded C?
easy
A. int buffer[1];
B. int buffer = 4;
C. char buffer;
D. char buffer[4];

Solution

  1. Step 1: Understand buffer declaration for byte storage

    To store multiple bytes, we need an array of chars (bytes) of size 4.
  2. 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.
  3. Final Answer:

    char buffer[4]; -> Option D
  4. Quick Check:

    Buffer for 4 bytes = char array size 4 [OK]
Hint: Use char array for byte buffers [OK]
Common Mistakes:
  • Using single char instead of array
  • Using int for byte buffer
  • Declaring wrong array size
3. Given this code snippet, what will be the value of 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.
medium
A. 0
B. -1
C. 2
D. Undefined

Solution

  1. Step 1: Understand i2c_read return values

    The function returns 0 if the read was successful, -1 if it failed.
  2. Step 2: Analyze the code snippet

    The code reads 2 bytes and stores in buffer. If successful, result = 0.
  3. Final Answer:

    0 -> Option A
  4. Quick Check:

    Success return = 0 [OK]
Hint: i2c_read returns 0 on success [OK]
Common Mistakes:
  • Assuming return is number of bytes read
  • Confusing success with failure codes
  • Ignoring return value meaning
4. Identify the error in this code snippet for reading 1 byte from an I2C device:
uint8_t data;
int status = i2c_read(device_addr, reg_addr, &data, 1);
if (status = 0) {
    process(data);
}
medium
A. Using assignment (=) instead of comparison (==) in if condition
B. Incorrect buffer type for reading data
C. Missing initialization of device_addr
D. Wrong number of bytes requested

Solution

  1. Step 1: Check the if condition syntax

    The condition uses single '=' which assigns 0 instead of comparing.
  2. Step 2: Understand effect of assignment in if

    Assignment returns 0 (false), so process(data) never runs even on success.
  3. Final Answer:

    Using assignment (=) instead of comparison (==) in if condition -> Option A
  4. Quick Check:

    Use '==' for comparison in if [OK]
Hint: Use '==' to compare, not '=' in conditions [OK]
Common Mistakes:
  • Confusing '=' and '==' in if statements
  • Ignoring return status check
  • Not using address-of operator for buffer
5. You want to read 3 bytes from an I2C device register and combine them into a 24-bit integer. Which code snippet correctly reads and combines the data?
uint8_t buf[3];
int status = i2c_read(device_addr, reg_addr, buf, 3);
uint32_t value = ???;
hard
A. value = buf[0] | (buf[1] << 8) | (buf[2] << 16);
B. value = (buf[2] << 16) | (buf[1] << 8) | buf[0];
C. value = (buf[0] << 16) | (buf[1] << 8) | buf[2];
D. value = buf[0] + buf[1] + buf[2];

Solution

  1. Step 1: Understand byte order for 24-bit integer

    Assuming buf[0] is most significant byte, shift it left by 16 bits.
  2. Step 2: Combine bytes with bitwise OR

    Shift buf[1] by 8 bits, add buf[2] as least significant byte.
  3. 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.
  4. Final Answer:

    value = (buf[0] << 16) | (buf[1] << 8) | buf[2]; -> Option C
  5. Quick Check:

    Shift and OR bytes for 24-bit value [OK]
Hint: Shift bytes by 16, 8, 0 bits and OR [OK]
Common Mistakes:
  • Mixing byte order
  • Adding bytes instead of bitwise OR
  • Not shifting bytes correctly
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard