DEV Community

Hedy
Hedy

Posted on

How to read data from I2C or SPI-based sensors?

Reading data from I2C or SPI sensors with microcontrollers (like Arduino, STM32, or ESP32) involves understanding the protocol, wiring, and communication steps. Below is a concise guide for both interfaces.

1. Reading from I2C Sensors
I2C Basics

  • 2-wire interface (SDA = data, SCL = clock).
  • 7-bit addressing (common sensors: 0x68 for MPU6050, 0x27 for LCD, etc.).
  • Master-slave communication (microcontroller = master, sensor = slave).

Steps to Read Data
(1) Wiring


(Add pull-up resistors (4.7kΩ) if not built into the sensor.)

(2) Scan for I2C Address (Optional)

cpp
#include <Wire.h>
void setup() {
  Wire.begin();
  Serial.begin(9600);
  Serial.println("Scanning I2C devices...");
  for (byte addr = 0; 0x00 < 0x80; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.print("Found device at: 0x");
      Serial.println(addr, HEX);
    }
  }
}
void loop() {}
Enter fullscreen mode Exit fullscreen mode

(3) Read Data (Example: BMP280)

cpp
#include <Wire.h>
#define BMP280_ADDR 0x76  // Check datasheet

void setup() {
  Wire.begin();
  Serial.begin(9600);
  // Configure sensor (refer to datasheet)
  Wire.beginTransmission(BMP280_ADDR);
  Wire.write(0xF4);  // Control register
  Wire.write(0x27);   // Normal mode, 1x temp/pressure oversampling
  Wire.endTransmission();
}

void loop() {
  // Read temperature (2 bytes)
  Wire.beginTransmission(BMP280_ADDR);
  Wire.write(0xFA);  // Temp MSB register
  Wire.endTransmission();
  Wire.requestFrom(BMP280_ADDR, 2);  // Request 2 bytes
  int16_t temp = (Wire.read() << 8) | Wire.read();
  float real_temp = temp / 100.0;  // BMP280 scaling
  Serial.print("Temperature: ");
  Serial.println(real_temp);
  delay(1000);
}
Enter fullscreen mode Exit fullscreen mode

2. Reading from SPI Sensors
SPI Basics
4-wire interface:

  • SCK = Clock (from master)
  • MOSI = Master Out Slave In (data to sensor)
  • MISO = Master In Slave Out (data from sensor)
  • SS/CS = Slave Select (chip enable, active LOW)

Full-duplex (simultaneous send/receive).

Faster than I2C (MHz speeds possible).

Steps to Read Data
(1) Wiring


(Some sensors use SDI/SDO instead of MOSI/MISO.)

(2) Read Data (Example: BME280)

cpp
#include <SPI.h>
#define BME_CS 10  // Chip Select pin

void setup() {
  SPI.begin();
  pinMode(BME_CS, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  // Read temperature (SPI)
  digitalWrite(BME_CS, LOW);  // Activate sensor
  SPI.transfer(0xFA);         // Send register address
  uint8_t msb = SPI.transfer(0x00);  // Dummy write to read
  uint8_t lsb = SPI.transfer(0x00);
  digitalWrite(BME_CS, HIGH); // Deactivate sensor
  int16_t temp = (msb << 8) | lsb;
  float real_temp = temp / 100.0;
  Serial.print("Temperature: ");
  Serial.println(real_temp);
  delay(1000);
}
Enter fullscreen mode Exit fullscreen mode

Key Differences & Tips


Common Pitfalls
✔ I2C:

  • Missing pull-up resistors → communication fails.
  • Address conflicts (check datasheet).

✔ SPI:

  • Forgetting to toggle CS → no response.
  • Clock polarity/phase mismatch (use SPI_MODE0, SPI_MODE3 as per sensor).

Libraries for Easier Use

  • I2C: Wire.h (Arduino), HAL_I2C (STM32).
  • SPI: SPI.h (Arduino), HAL_SPI (STM32).
  • Sensor-Specific:

    • Adafruit_Sensor (BME280, MPU6050).
    • SparkFun_ADS1015 (ADC over I2C).

Final Advice

  1. Check the datasheet for register maps and protocols.
  2. Start with known libraries before writing raw I2C/SPI.
  3. Use logic analyzers (Saleae, PulseView) if communication fails.

Both protocols are powerful—I2C for simplicity, SPI for speed!

Top comments (0)