第2章:STM32传感器基础:I2C与SPI总线协议回顾、常用传感器驱动移植与数据读取

各位同学,欢迎来到第二章。上一章我们聊了AI在嵌入式上的整体思路,这一章咱们得先把地基打牢——传感器通信。你想想看,没有传感器数据,AI模型就是个空壳子,对吧?

我个人习惯把传感器通信比作「两个人聊天」。I2C就像两个人共用一条电话线,得轮流说话;SPI呢,就像两个人各有一条专线,可以同时说。嗯,这个比喻虽然糙了点,但道理是通的。

2.1 I2C总线协议回顾

I2C,全称Inter-Integrated Circuit,是飞利浦公司搞出来的。它只需要两根线:SCL(时钟线)和SDA(数据线)。

核心特点:

  • 主从架构:STM32通常是主机,传感器是从机
  • 7位或10位地址:最常见的是7位地址,比如0x76
  • 速率:标准模式100kHz,快速模式400kHz
  • 开漏输出:需要上拉电阻,一般4.7kΩ

重点:I2C通信时序

起始条件:SCL高电平时,SDA从高变低
停止条件:SCL高电平时,SDA从低变高
数据传输:SCL低电平时改变SDA,高电平时采样

我在项目中遇到过一个问题:I2C总线挂死。当时排查了半天,发现是上拉电阻焊错了,用了10kΩ导致信号上升沿太慢。嗯,这种坑踩过一次就记住了。

2.2 SPI总线协议回顾

SPI,Serial Peripheral Interface,摩托罗拉发明的。它比I2C快得多,但线也多——四根线:SCK(时钟)、MOSI(主机输出从机输入)、MISO(主机输入从机输出)、CS(片选)。

SPI的四种模式:

模式 CPOL(时钟极性) CPHA(时钟相位) 说明
模式0 0 0 空闲低电平,第一个边沿采样
模式1 0 1 空闲低电平,第二个边沿采样
模式2 1 0 空闲高电平,第一个边沿采样
模式3 1 1 空闲高电平,第二个边沿采样

说白了,SPI就是靠时钟边沿来同步数据。你只要记住:大多数传感器用模式0或模式3。我建议你拿到传感器数据手册后,第一件事就是查SPI模式,不然读出来的数据全是乱的。

⚠️ 避坑指南:我曾经在调试一个加速度计的时候,SPI读出来的数据全是0xFF。折腾了两天,最后发现是CS引脚没有拉低——片选信号没生效。嗯,这种低级错误,谁还没犯过呢?

2.3 常用传感器驱动移植

好,协议讲完了,咱们来点实战的。我选了三个最常用的传感器:温湿度、加速度计、磁力计。这三个基本覆盖了大多数IoT和AI应用场景。

2.3.1 温湿度传感器:SHT30

SHT30是Sensirion家的,I2C接口,精度不错,价格也亲民。它的I2C地址是0x44(ADDR引脚接GND)或0x45(ADDR引脚接VDD)。

驱动移植步骤:

  1. 在STM32CubeMX中配置I2C外设,速率选400kHz
  2. 编写I2C读写函数(HAL库已经封装好了)
  3. 实现SHT30的初始化命令:0x2C06(高精度测量)
  4. 读取6字节数据:温度高8位、温度低8位、CRC、湿度高8位、湿度低8位、CRC
// SHT30读取温湿度示例
uint8_t cmd[] = {0x2C, 0x06};
uint8_t buf[6];

HAL_I2C_Master_Transmit(&hi2c1, 0x44 << 1, cmd, 2, 100);
HAL_Delay(20);  // 等待测量完成
HAL_I2C_Master_Receive(&hi2c1, 0x44 << 1, buf, 6, 100);

// 计算温度:公式来自数据手册
uint16_t raw_temp = (buf[0] << 8) | buf[1];
float temp = -45.0 + 175.0 * raw_temp / 65535.0;

// 计算湿度
uint16_t raw_humi = (buf[3] << 8) | buf[4];
float humi = 100.0 * raw_humi / 65535.0;

💡 小技巧:我个人习惯在读取数据后加一个CRC校验。SHT30自带了CRC,虽然大多数时候用不上,但万一数据出错了呢?你想想看,AI模型吃进去错误数据,那推理结果能靠谱吗?

2.3.2 加速度计:LSM6DSO

LSM6DSO是ST自家产的,支持I2C和SPI。我一般用SPI,因为加速度计数据量大,SPI快一些。它的I2C地址是0x6A(SA0接GND)或0x6B(SA0接VDD)。

关键寄存器:

寄存器地址 名称 说明
0x10 CTRL1_XL 加速度计控制寄存器,设置ODR和量程
0x28 OUTX_L_XL X轴加速度低字节
0x29 OUTX_H_XL X轴加速度高字节
0x0F WHO_AM_I 设备ID,固定为0x6C

嗯,这里要注意:SPI读取时,第一个字节的最高位要置1,表示读操作。这是SPI协议的一个小细节,忘了的话读回来的全是0。

// LSM6DSO SPI读取加速度数据
uint8_t reg = 0x28 | 0x80;  // 读操作,最高位置1
uint8_t rx_buf[6];

HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, ®, rx_buf, 1, 100);
HAL_SPI_TransmitReceive(&hspi1, rx_buf, rx_buf, 6, 100);
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);

// 解析数据
int16_t acc_x = (rx_buf[1] << 8) | rx_buf[0];
int16_t acc_y = (rx_buf[3] << 8) | rx_buf[2];
int16_t acc_z = (rx_buf[5] << 8) | rx_buf[4];

// 转换为g值(假设量程为±2g)
float acc_x_g = acc_x * 0.061 / 1000.0;  // 0.061 mg/LSB

2.3.3 磁力计:LIS3MDL

磁力计,说白了就是电子指南针。LIS3MDL也是ST家的,I2C地址是0x1E。它和加速度计配合使用,可以做姿态解算。

我记得有一次做无人机项目,磁力计数据老是飘。排查到最后,发现是PCB上走线离电机太近了,磁场干扰严重。嗯,硬件布局这事儿,真的得提前想好。

// LIS3MDL初始化
uint8_t ctrl_reg1[] = {0x20, 0x70};  // 100Hz,高性能模式
HAL_I2C_Master_Transmit(&hi2c1, 0x1E << 1, ctrl_reg1, 2, 100);

// 读取磁力数据
uint8_t reg = 0x28;
uint8_t mag_data[6];
HAL_I2C_Master_Transmit(&hi2c1, 0x1E << 1, ®, 1, 100);
HAL_I2C_Master_Receive(&hi2c1, 0x1E << 1, mag_data, 6, 100);

int16_t mag_x = (mag_data[1] << 8) | mag_data[0];
int16_t mag_y = (mag_data[3] << 8) | mag_data[2];
int16_t mag_z = (mag_data[5] << 8) | mag_data[4];

2.4 数据读取与验证

驱动移植完了,怎么知道数据对不对?我一般做三步验证:

  1. WHO_AM_I检查:每个传感器都有设备ID,读回来对不上就是通信有问题
  2. 静态数据观察:传感器不动时,加速度计Z轴应该是1g左右,磁力计应该稳定
  3. 动态响应测试:晃一晃板子,看数据有没有变化

核心要点总结:

  • I2C适合低速、线少的场景,SPI适合高速、大数据量
  • 驱动移植的核心是:初始化配置 + 数据读取 + 格式转换
  • 验证数据时,先看WHO_AM_I,再看物理量是否合理

好了,这一章的内容就到这儿。下一章我们会把这些传感器数据融合起来,做真正的AI推理。你想想看,当温湿度、加速度、磁场数据一起输入到神经网络里,能玩出什么花样?嗯,到时候你就知道了。