【Digikey好物畅享】Arduino UNO R4 WiFi I2C 通讯故障排查以及SEN66传感器数据读取

1.背景

这一期要把 SEK-SEN66 连接到 UNO R4 WiFi 开发板,读取温湿度信息。

SEK-SEN66 支持 Arduino 开发环境并提供了对应的驱动代码,我按照之前的经验添加代码并烧录到开发板,发现读取不到温湿度信息。怀疑是I2C通信出现了问题。

2.I2C扫描

写了一个 I2C 扫描的代码,果然没有扫描到 SEK-SEN66,并且使用逻辑分析仪连接到 UNO R4 WiFi 开发板的 SCL/SDA ,逻辑分析已采集的信号一直都是低电平。

I2C 扫描代码如下:


#include <Arduino.h>
#include <Wire.h>

/**
 * @brief 执行 I2C 地址扫描并打印结果
 * @param wireInstance 指向 Wire 或 Wire1 的指针
 * @param name 接口名称字符串
 */
void scanI2C(TwoWire &wireInstance, String name) {
  byte error, address;
  int nDevices = 0;

  Serial.println("\n--- Scanning I2C [" + name + "] ---");
  Serial.println("     0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");

  for (address = 1; address < 127; address++) {
    // 每行开始打印地址头
    if (address % 16 == 0) {
      if (address < 16) Serial.print("0");
      Serial.print(address, HEX);
      Serial.print(": ");
    } else if (address == 1) {
      Serial.print("00:    ");
    }

    // 开始传输并检查 ACK
    wireInstance.beginTransmission(address);
    error = wireInstance.endTransmission();

    if (error == 0) {
      // 发现器件
      if (address < 16) Serial.print("0");
      Serial.print(address, HEX);
      Serial.print(" ");
      nDevices++;
    } else if (error == 4) {
      // 未知错误
      Serial.print("ER ");
    } else {
      // 无响应
      Serial.print("-- ");
    }

    if ((address + 1) % 16 == 0) Serial.println();
  }

  Serial.println();
  if (nDevices == 0)
    Serial.println("[Result] No I2C devices found.");
  else {
    Serial.print("[Result] Found ");
    Serial.print(nDevices);
    Serial.println(" device(s).");
  }
}

void setup() {
  Serial.begin(DEMO_UART_BAUDRATE);
  while (!Serial);

  Serial.println("========================================");
  Serial.println("      Uno R4 WiFi I2C Scanner");
  Serial.println("========================================");

  // 初始化 I2C 接口
  Wire.begin();   // 默认 SDA/SCL
  Wire1.begin();  // 如果你使用的是 Qwiic 接口,请取消此行注释
}

void loop() {
  // 扫描默认 Wire 接口
  scanI2C(Wire, "Wire - SDA/SCL");

  // 如果需要扫描 Qwiic 接口,取消下面一行的注释
  scanI2C(Wire1, "Wire1 - Qwiic");

  Serial.println("\nScan finished. Waiting 5 seconds for next scan...");
  delay(1000);
}

然后把逻辑分析仪接到 A4/A5,采集的信号也一直都是低电平。

2.1Uno R4 WiFi 示例程序

参考官方教程,I2C发送数据

https://docs.arduino.cc/tutorials/uno-r4-wifi/cheat-sheet/#i2c

void setup() {
  bsp_serial_init(DEMO_UART_BAUDRATE);

  Serial.println("========================================");
  Serial.println("      Uno R4 WiFi I2C Scanner");
  Serial.println("========================================");

  // 初始化 I2C 接口
  Wire.begin();   // 默认 SDA/SCL
  // 强制开启内部上拉(仅限调试用途)
  // pinMode(SDA, INPUT_PULLUP);
  // pinMode(SCL, INPUT_PULLUP);

  Wire1.begin(); // 如果你使用的是 Qwiic 接口,请取消此行注释
}

void loop() {

  // // 扫描默认 Wire 接口
  // scanI2C(Wire, "Wire - SDA/SCL");

  // // 如果需要扫描 Qwiic 接口,取消下面一行的注释
  // scanI2C(Wire1, "Wire1 - Qwiic");

  Wire.beginTransmission(1); // begin transmit to device 1
  Wire.write(byte(0x00));    // send instruction byte
  Wire.write(0x02);          // send a value
  Wire.endTransmission();    // stop transmit

  Serial.println("\nScan finished. Waiting 1 seconds for next scan...");
  delay(1000);
}

逻辑分析仪接 SDA, SCL,采集的信号一直都是低电平。

3.追踪

在 Wire.h 中找到 I2C 声明, UNO R4 WiFi 有两个 I2C,分别是 Wire 和 Wire1

在 Wire.cpp 中找到它们的定义

注意这里的 I2C 引脚定义,有两组:

  • 第一组对应管脚 SDA–18, SCL–19

  • 第二组对应管脚 SDA–27, SCL-26 (即 Qiic 管脚)

4.软件 I2C

在PlatformIO IDE中搜索由 Steve Marple 编写的 SoftWire 库,安装到此工程。

添加一个软件 I2C 扫描的程序。

首先在 SDA/SCL 管脚上扫描从设备,没有扫描到。换其他几组关键,依然没有扫描到。

4.1对应的软件 I2C 扫描程序


#include <SoftWire.h>
#include <AsyncDelay.h>

#define SW_SDA_PIN  SDA
#define SW_SCL_PIN  SCL

SoftWire sw(SW_SDA_PIN, SW_SCL_PIN);

void setup(void)
{
#if F_CPU >= 12000000UL
  Serial.begin(921600);
#else
  Serial.begin(9600);
#endif

  sw.setTimeout_ms(40);
  sw.begin();

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  // Set how long we are willing to wait for a device to respond
  sw.setTimeout_ms(10);

  const uint8_t firstAddr = 1;
  const uint8_t lastAddr = 0x7F;
  Serial.println();
  Serial.print("Interrogating all addresses in range 0x");
  Serial.print(firstAddr, HEX);
  Serial.print(" - 0x");
  Serial.print(lastAddr, HEX);
  Serial.println(" (inclusive) ...");

  for (uint8_t addr = firstAddr; addr <= lastAddr; addr++) {
    digitalWrite(LED_BUILTIN, HIGH);
    delayMicroseconds(50);

    uint8_t startResult = sw.llStart((addr << 1) + 1); // Signal a read
    sw.stop();

    if (startResult == 0) {
      Serial.print("\rDevice found at 0x");
      Serial.println(addr, HEX);
      Serial.flush();
    }
    digitalWrite(LED_BUILTIN, LOW);

    delay(50);
  }
  Serial.println("Finished");

}

void loop(void)
{
  ;
}

4.2 接上拉电阻之后,扫描成功

这个章节是在硬件 I2C 调通了之后,再次验证软件 I2C 功能。

5.硬件I2C 问题解决

参考论坛帖子

https://docs.arduino.cc/tutorials/uno-r4-wifi/cheat-sheet/#i2c

5. 1UNO R4 WiFi 原理图

从原理图看,SDA/SCL 对应的I2C的确没有接上拉电阻,需要外接上拉电阻,否则一直都是低电平。

5.2解决办法

在 SDA/SCL 管脚上接外部上拉电阻,电阻值范围 2.2K 欧姆到 10K 欧姆之间均可。

接法:

  • 将一个电阻的一端接到 SDA,另一端接到 3.3V

  • 将一个电阻的一端接到 SCL,另一端接到 3.3V

5.3我的接线

  • SDA/SCL 接到传感器

  • A4/A5 接外部上拉电阻10K欧码,接入到3.3V电源

5.4实测结果

I2C扫描,终于扫描到了正确的传感器地址,SEN66 传感器的I2C地址就是 0x6B,终于成功了。

6.SEN66读取温湿度

在调通了I2C 通讯之后,运行读取 SEN66 读取温湿度的程序,终于读取成功。

对应的代码:


#include <Arduino.h>
#include <SensirionI2cSen66.h>
#include <Wire.h>

// macro definitions
// make sure that we use the proper definition of NO_ERROR
#ifdef NO_ERROR
#undef NO_ERROR
#endif
#define NO_ERROR 0

SensirionI2cSen66 sensor;

static char errorMessage[64];
static int16_t error;

void setup() {

    Serial.begin(DEMO_UART_BAUDRATE);
    while (!Serial) {
        delay(100);
    }
    Wire.begin();
    sensor.begin(Wire, SEN66_I2C_ADDR_6B);

    error = sensor.deviceReset();
    if (error != NO_ERROR) {
        Serial.print("Error trying to execute deviceReset(): ");
        errorToString(error, errorMessage, sizeof errorMessage);
        Serial.println(errorMessage);
        return;
    }
    delay(1200);
    int8_t serialNumber[32] = {0};
    error = sensor.getSerialNumber(serialNumber, 32);
    if (error != NO_ERROR) {
        Serial.print("Error trying to execute getSerialNumber(): ");
        errorToString(error, errorMessage, sizeof errorMessage);
        Serial.println(errorMessage);
        return;
    }
    Serial.print("serialNumber: ");
    Serial.print((const char*)serialNumber);
    Serial.println();
    error = sensor.startContinuousMeasurement();
    if (error != NO_ERROR) {
        Serial.print("Error trying to execute startContinuousMeasurement(): ");
        errorToString(error, errorMessage, sizeof errorMessage);
        Serial.println(errorMessage);
        return;
    }
}

void loop() {

    float massConcentrationPm1p0 = 0.0;
    float massConcentrationPm2p5 = 0.0;
    float massConcentrationPm4p0 = 0.0;
    float massConcentrationPm10p0 = 0.0;
    float humidity = 0.0;
    float temperature = 0.0;
    float vocIndex = 0.0;
    float noxIndex = 0.0;
    uint16_t co2 = 0;
    delay(1000);
    error = sensor.readMeasuredValues(
        massConcentrationPm1p0, massConcentrationPm2p5, massConcentrationPm4p0,
        massConcentrationPm10p0, humidity, temperature, vocIndex, noxIndex,
        co2);
    if (error != NO_ERROR) {
        Serial.print("Error trying to execute readMeasuredValues(): ");
        errorToString(error, errorMessage, sizeof errorMessage);
        Serial.println(errorMessage);
        return;
    }
    Serial.print("pm1p0: ");
    Serial.print(massConcentrationPm1p0);
    Serial.print("\t");
    Serial.print("pm2p5: ");
    Serial.print(massConcentrationPm2p5);
    Serial.print("\t");
    Serial.print("pm4p0: ");
    Serial.print(massConcentrationPm4p0);
    Serial.print("\t");
    Serial.print("pm10p0: ");
    Serial.print(massConcentrationPm10p0);
    Serial.print("\t");
    Serial.print("humidity: ");
    Serial.print(humidity);
    Serial.print("\t");
    Serial.print("temperature: ");
    Serial.print(temperature);
    Serial.print("\t");
    Serial.print("vocIndex: ");
    Serial.print(vocIndex);
    Serial.print("\t");
    Serial.print("noxIndex: ");
    Serial.print(noxIndex);
    Serial.print("\t");
    Serial.print("co2: ");
    Serial.print(co2);
    Serial.println();
}

7.总结

回顾整个调试过程:

  1. 添加 SEN66 驱动程序,通过I2C通信读取传感器数据,读取失败,怀疑I2C通信有问题或者传感器出了问题;

  2. 把 SEN66 传感器连接到其他主控(例如esp32s3开发板),一样的程序,能读取传感器数据,说明传感器是正常的,那说明 UNO R4 WiFi 的 I2C 通信有问题;

  3. 怀疑 UNO R4 WiFi 的 Wire (对应 SDA/SCL 管脚)通信有问题,连接逻辑分析观察管脚,发现两个管脚一直都是低电平,测试了 A4/A5 管脚,也是低电平,说明确实I2C有问题;

  4. 搜索论坛,发现 SDA/SCL 没有接上拉电阻,我在面包板上把 A4/A5 连接 10K 欧姆电阻到3.3V,然后重新运行硬件I2C扫描,成功扫描到SEN66传感器的I2C地址,解决了问题;

  5. 再次运行软件I2C扫描,也成功扫描到SEN66传感器的I2C地址;

  6. 最后运行读取SEN66传感器的程序,成功读取到9种传感器数值,I2C通信正常;

I2C通讯出故障,应该先通过逻辑分析仪或者示波器查看信号质量,然后检查原理图确认问题。