4. 通信协议基础:I2C、SPI、UART、Modbus在传感器数据读取中的应用

做温室物联网,说白了就是跟传感器打交道。而传感器怎么把数据交给你?靠的就是通信协议。我见过不少新手,上来就怼代码,结果数据读出来全是乱码,折腾半天才发现是协议没配对。

今天咱们就把温室里最常用的四种协议——I2C、SPI、UART、Modbus——掰开揉碎了讲清楚。每种协议怎么接线、怎么读数据、有什么坑,我都会结合项目经验给你说明白。

4.1 I2C:两线制,适合短距离多传感器

I2C协议,我习惯叫它「两根线走天下」。一根时钟线SCL,一根数据线SDA,就能挂载几十个传感器。温室里的温湿度传感器(比如SHT30、BME280)大多用这个。

工作原理很简单:主机(比如你的ESP32)通过SCL发出时钟脉冲,SDA线上按位传输数据。每个从机(传感器)都有一个唯一的7位地址,主机点名,从机应答。

关键参数:

  • 标准模式:100kHz
  • 快速模式:400kHz
  • 高速模式:3.4MHz(温室里基本用不到)
  • 最大总线电容:400pF(线太长会出问题)

我在项目中遇到过一个问题:温室里装了8个SHT30,用I2C总线连在一起,结果数据偶尔跳变。查了半天,发现是总线长度超过了2米,信号反射导致的。后来加了上拉电阻(4.7kΩ),问题解决。

避坑指南:我曾经因为忘记加上拉电阻,折腾了一整天。I2C总线必须要有上拉电阻,一般4.7kΩ到10kΩ。没有它,SDA和SCL拉不高,数据根本读不出来。

代码示例:读取SHT30温湿度

#include <Wire.h>
#include <Adafruit_SHT31.h>

Adafruit_SHT31 sht31 = Adafruit_SHT31();

void setup() {
  Serial.begin(115200);
  Wire.begin();  // 默认引脚,ESP32是GPIO21(SDA), GPIO22(SCL)
  
  if (!sht31.begin(0x44)) {  // SHT30默认地址0x44
    Serial.println("找不到SHT30!检查接线");
    while (1);
  }
}

void loop() {
  float t = sht31.readTemperature();
  float h = sht31.readHumidity();
  
  if (!isnan(t)) {
    Serial.print("温度: "); Serial.print(t);
    Serial.print(" °C, 湿度: "); Serial.print(h); Serial.println(" %");
  } else {
    Serial.println("读取失败");
  }
  delay(2000);
}

4.2 SPI:四线制,速度快,适合高数据量

SPI协议,说白了就是「快」。四根线:SCK(时钟)、MOSI(主机输出从机输入)、MISO(主机输入从机输出)、CS(片选)。温室里如果要用高精度的光照传感器(比如TSL2591)或者ADC芯片,SPI是首选。

为什么快?因为它是全双工的——发送和接收可以同时进行。而且没有地址的概念,每个从机单独用一根CS线控制。

特性 I2C SPI
线数 2根 4根(最少)
速度 最高3.4MHz 可达几十MHz
从机数量 理论127个 受限于GPIO数量
距离 1-2米 1米以内
典型传感器 SHT30, BME280 TSL2591, MAX31865

我的经验:SPI的时钟极性(CPOL)和时钟相位(CPHA)一定要配对。我见过有人把模式0和模式3搞混,数据读出来全是0x00。查手册,看传感器要求的是哪种模式,别想当然。

代码示例:读取TSL2591光照传感器

#include <SPI.h>

#define CS_PIN 5  // 片选引脚

void setup() {
  Serial.begin(115200);
  SPI.begin();
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);
  
  // 初始化传感器(简化版)
  digitalWrite(CS_PIN, LOW);
  SPI.transfer(0x80 | 0x00);  // 写命令
  SPI.transfer(0x03);         // 使能传感器
  digitalWrite(CS_PIN, HIGH);
}

void loop() {
  uint16_t ch0, ch1;
  
  digitalWrite(CS_PIN, LOW);
  SPI.transfer(0x80 | 0x14);  // 读通道0
  ch0 = SPI.transfer(0x00) << 8;
  ch0 |= SPI.transfer(0x00);
  digitalWrite(CS_PIN, HIGH);
  
  Serial.print("可见光+红外: "); Serial.println(ch0);
  delay(1000);
}

4.3 UART:串口通信,简单可靠

UART,大家更习惯叫它「串口」。就两根线:TX(发送)和RX(接收)。温室里很多工业级的传感器(比如CO2传感器、风速传感器)都用这个。

为什么?因为简单。不需要时钟线,双方约定好波特率就行。我常用的波特率是9600和115200。

UART通信要点:

  • 波特率必须一致(差一点都会乱码)
  • TX接RX,RX接TX(交叉连接)
  • 数据格式:1位起始位 + 8位数据位 + 1位停止位(最常见)
  • 距离:RS232电平3-5米,TTL电平1米以内

我记得有一次在温室里调试CO2传感器,数据读出来全是乱码。检查了半天,发现是波特率设成了9600,但传感器默认是19200。改过来就好了。嗯,这种低级错误我也犯过。

代码示例:读取MH-Z19B CO2传感器

#include <SoftwareSerial.h>

SoftwareSerial co2Serial(16, 17);  // RX, TX

byte cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};

void setup() {
  Serial.begin(115200);
  co2Serial.begin(9600);  // MH-Z19B默认波特率9600
}

void loop() {
  co2Serial.write(cmd, 9);
  
  delay(100);
  
  byte response[9];
  if (co2Serial.available() >= 9) {
    co2Serial.readBytes(response, 9);
    
    if (response[0] == 0xFF && response[1] == 0x86) {
      int co2 = response[2] * 256 + response[3];
      Serial.print("CO2浓度: "); Serial.print(co2); Serial.println(" ppm");
    }
  }
  delay(5000);
}

4.4 Modbus:工业标准,远距离传输

Modbus协议,说白了就是工业界的「普通话」。温室大棚面积大,传感器分布广,用Modbus RTU(RS485)可以传几百米。我做过一个项目,大棚长200米,用Modbus挂了30多个传感器,一点问题没有。

Modbus有两种常见形式:

  • Modbus RTU:基于RS485物理层,半双工,距离可达1200米
  • Modbus TCP:基于以太网,适合已经布好网线的场景

温室里最常用的是Modbus RTU。每个传感器有一个地址(1-247),主机轮询读取。数据格式是:地址 + 功能码 + 数据 + CRC校验。

避坑指南:我曾经因为RS485的A/B线接反,折腾了三个小时。记住:A接A,B接B。如果数据读不出来,先交换一下A/B试试。另外,总线两端必须加120Ω终端电阻,否则信号反射会导致数据错误。

代码示例:读取Modbus温湿度传感器

#include <ModbusMaster.h>

ModbusMaster node;

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600, SERIAL_8N1, 16, 17);  // RS485模块接在Serial2
  
  node.begin(1, Serial2);  // 传感器地址为1
}

void loop() {
  uint8_t result;
  uint16_t data[2];
  
  result = node.readHoldingRegisters(0x0000, 2);  // 读2个寄存器
  
  if (result == node.ku8MBSuccess) {
    float temperature = node.getResponseBuffer(0) / 10.0;
    float humidity = node.getResponseBuffer(1) / 10.0;
    
    Serial.print("温度: "); Serial.print(temperature);
    Serial.print(" °C, 湿度: "); Serial.print(humidity); Serial.println(" %");
  } else {
    Serial.print("Modbus错误: 0x");
    Serial.println(result, HEX);
  }
  
  delay(2000);
}

4.5 如何选择?我的建议

你想想看,温室里传感器那么多,到底用哪种协议?我一般这么选:

  • 传感器在同一个控制箱内(1米以内):用I2C,简单省线
  • 需要高速读取(比如光照传感器):用SPI,速度快
  • 单个传感器,距离不远:用UART,调试方便
  • 传感器分布在大棚各处(几十米到几百米):用Modbus RTU,抗干扰能力强

我的习惯:同一个项目里尽量统一协议。比如全部用I2C,或者全部用Modbus。混用的话,代码会变得很乱,调试也麻烦。除非有特殊需求,否则别给自己找事。

好了,这四种协议在温室里的应用就讲到这里。下一章咱们聊聊传感器数据融合的具体算法,怎么把多个传感器的数据揉在一起,得到更准确的结果。