4. 通信协议基础:I2C、SPI、UART协议详解
好,咱们进入正题。光照传感器和主控之间怎么「说话」?说白了,就是靠通信协议。我见过不少新手,传感器买回来,线一接,发现读不到数据,急得团团转。其实啊,多半是协议没搞对。
今天咱们就掰开揉碎,把 I2C、SPI、UART 这三个最常用的协议讲清楚。你想想看,搞懂了它们,光照传感器和主控之间的通信,基本就稳了。
4.1 为什么需要通信协议?
传感器采集到的是模拟信号,比如光敏电阻的电压变化。但主控芯片(比如 ESP32、STM32)只认数字信号——0 和 1。所以,中间需要一个「翻译官」,把模拟量转成数字量,再按照约定好的规则传过去。
这个「约定好的规则」,就是通信协议。它规定了:
- 数据怎么开始、怎么结束
- 谁先说话、谁后说话
- 数据格式是几位、校验怎么算
嗯,这里要注意:不同的传感器,支持的协议不一样。买之前一定要看数据手册,别买回来才发现不匹配。
4.2 I2C 协议详解
I2C 是我个人最喜欢用的协议之一。为什么?因为它只需要两根线:SDA(数据线)和 SCL(时钟线)。你想想看,两根线就能挂一堆传感器,多省事。
我在项目中遇到过,一个 ESP32 上挂了 5 个 I2C 传感器:光照、温湿度、气压、距离、还有一个人体红外。只要地址不冲突,一根总线全搞定。
4.2.1 I2C 的工作原理
I2C 是主从结构。主控是「主」,传感器是「从」。主控负责发时钟信号,从设备只能被动响应。
- 起始条件:SCL 高电平时,SDA 从高变低
- 停止条件:SCL 高电平时,SDA 从低变高
- 数据传输:每个字节 8 位,后面跟一个应答位(ACK)
- 地址:每个从设备有唯一 7 位或 10 位地址
重要:I2C 的速率有标准模式(100kHz)、快速模式(400kHz)和高速模式(3.4MHz)。光照传感器一般用 100kHz 或 400kHz 就够了。
4.2.2 光照传感器的 I2C 通信示例
以 BH1750 光照传感器为例,它的 I2C 地址是 0x23(ADDR 引脚接地时)。
// 伪代码示例:读取 BH1750 光照值
I2C_Start(); // 发送起始条件
I2C_SendByte(0x46); // 地址 0x23 + 写位 (0x23 << 1 | 0)
I2C_WaitAck(); // 等待应答
I2C_SendByte(0x10); // 发送命令:连续高分辨率模式
I2C_WaitAck();
I2C_Stop(); // 发送停止条件
delay(120); // 等待测量完成(约120ms)
I2C_Start();
I2C_SendByte(0x47); // 地址 0x23 + 读位 (0x23 << 1 | 1)
I2C_WaitAck();
high_byte = I2C_ReadByte(); // 读取高字节
I2C_SendAck(0); // 发送应答
low_byte = I2C_ReadByte(); // 读取低字节
I2C_SendAck(1); // 发送非应答,表示结束
I2C_Stop();
lux = (high_byte << 8) | low_byte; // 合并为16位数据
lux = lux / 1.2; // 根据手册换算为勒克斯
我的经验:BH1750 的测量时间不能太短。我曾经为了省时间,只等了 100ms,结果读出来的数据全是乱的。后来老老实实等了 120ms,数据就稳了。有些坑,踩过才知道。
4.3 SPI 协议详解
SPI 比 I2C 快,但线也多。它需要四根线:MOSI(主出从入)、MISO(主入从出)、SCLK(时钟)、CS(片选)。
你想想看,SPI 是全双工的——主控发数据的同时也能收数据。这一点比 I2C 强。但代价就是占用的引脚多。
4.3.1 SPI 的工作模式
SPI 有四种模式,由时钟极性(CPOL)和时钟相位(CPHA)决定:
| 模式 | CPOL | CPHA | 说明 |
|---|---|---|---|
| 模式 0 | 0 | 0 | 空闲时时钟低电平,第一个边沿采样 |
| 模式 1 | 0 | 1 | 空闲时时钟低电平,第二个边沿采样 |
| 模式 2 | 1 | 0 | 空闲时时钟高电平,第一个边沿采样 |
| 模式 3 | 1 | 1 | 空闲时时钟高电平,第二个边沿采样 |
避坑指南:我曾经在项目里用 SPI 连接一个光照传感器,结果死活读不到数据。查了半天,发现是 SPI 模式没对上。传感器要求模式 1,我默认用了模式 0。所以,一定要先看数据手册里的 SPI 模式说明。
4.3.2 SPI 通信示例
以 TSL2591 光照传感器为例,它支持 SPI 接口。
// 伪代码示例:读取 TSL2591 的 ID 寄存器
CS_LOW(); // 拉低片选,选中设备
SPI_TransferByte(0x12); // 发送寄存器地址(读命令)
device_id = SPI_TransferByte(0x00); // 发送任意数据,同时读取返回数据
CS_HIGH(); // 拉高片选,释放设备
if (device_id == 0x50) {
// 设备正常
} else {
// 设备异常,检查接线或地址
}
我的建议:SPI 的速率可以很高,但光照传感器一般不需要太快。我通常把 SPI 时钟设在 1MHz 左右,既稳定又够用。速率太高反而容易受干扰。
4.4 UART 协议详解
UART 是最简单的协议,只需要两根线:TX(发送)和 RX(接收)。但它是一对一的,不能像 I2C 那样挂多个设备。
UART 是异步的,没有时钟线。所以通信双方必须约定好相同的波特率——说白了就是每秒传多少位。常见的波特率有 9600、115200 等。
4.4.1 UART 的数据帧格式
一个 UART 数据帧包含:
- 起始位:1 位,固定为低电平
- 数据位:5~9 位,常用 8 位
- 校验位:可选,奇校验或偶校验
- 停止位:1 位或 2 位,固定为高电平
4.4.2 光照传感器的 UART 通信示例
有些光照传感器(比如一些串口输出的模块)直接用 UART 输出数据。
// 伪代码示例:读取 UART 光照传感器数据
// 假设传感器每秒输出一次数据,格式:$LUX,1234.5\r\n
char buffer[32];
int index = 0;
while (1) {
char c = UART_ReadByte(); // 读取一个字节
if (c == '\n') { // 遇到换行符,表示一帧结束
buffer[index] = '\0'; // 字符串结束符
if (strncmp(buffer, "$LUX,", 5) == 0) {
float lux = atof(&buffer[5]); // 提取光照值
// 处理光照值
}
index = 0;
} else {
buffer[index++] = c;
}
}
避坑指南:UART 通信最容易出问题的是波特率不匹配。我曾经调试一个传感器,数据全是乱码,折腾了半小时才发现传感器默认是 9600,而我代码里配的是 115200。所以,上电后先确认波特率。
4.5 如何选择通信协议?
好,三种协议都讲完了。你可能会问:那我到底该用哪个?
我个人习惯这样选:
- I2C:适合挂多个传感器,引脚少,速度要求不高。光照传感器大多用 I2C。
- SPI:适合高速数据传输,比如摄像头、显示屏。光照传感器用 SPI 有点大材小用。
- UART:最简单,一对一,适合和电脑串口调试。但光照传感器用 UART 的较少。
我的建议:对于智能窗帘的光照传感器,我推荐用 I2C。原因很简单:引脚少、传感器选择多、代码库成熟。你想想看,一个 ESP32 用 I2C 挂一个 BH1750,总共就 4 根线(VCC、GND、SDA、SCL),多清爽。
4.6 建立通信的完整流程
最后,咱们总结一下光照传感器和主控建立通信的步骤:
- 确认传感器支持的协议:看数据手册,找到通信接口说明
- 硬件连接:按照协议要求接线,注意电平匹配(3.3V vs 5V)
- 初始化主控的通信外设:配置 I2C/SPI/UART 的引脚、速率、模式
- 发送命令:按照传感器手册,发送初始化命令或读取命令
- 读取数据:解析返回的数据,换算为实际物理量
- 验证:用手遮挡传感器,看数值是否变化
我的经验:第 6 步特别重要。我见过有人代码写完了,数据也读出来了,但数值一直不变。后来发现是传感器地址写错了,读的是另一个不存在的设备。所以,一定要用手挡一下传感器,确认数据有变化。
好了,通信协议这块就讲到这里。下一章咱们会讲具体的硬件选型和电路设计。到时候,我会把 BH1750 和 ESP32 的接线图、代码都贴出来,手把手带你调通。
记住一句话:协议是死的,人是活的。搞懂了原理,换什么传感器都不怕。