3. 传感器驱动开发
好,咱们进入正题。传感器驱动开发,说白了就是让MCU和传感器“说上话”。你想想看,传感器就像个哑巴,它把物理量(比如温度、气压)变成了电信号,又通过内部寄存器把这些信号存起来。我们要做的,就是通过STM32的I2C或SPI总线,去读写这些寄存器。
我个人习惯把驱动开发分成三步走:寄存器读写 → 初始化配置 → 数据读取。这三步走通了,传感器基本就活了。今天咱们就拿一个最常见的I2C接口的温湿度传感器(比如SHT30)来举例,手把手过一遍。
3.1 寄存器读写:驱动开发的“地基”
所有传感器通信,归根结底就是读写寄存器。你写对了,传感器就按你的要求工作;你读对了,就能拿到数据。我在项目中遇到过好几次,明明传感器手册上写的地址是0x44,结果死活通信不上——后来发现是7位地址和8位地址搞混了。
下面是我常用的I2C读写封装函数,直接拿来用就行:
// 写寄存器:向传感器指定地址写入数据
static HAL_StatusTypeDef sensor_write_reg(uint8_t reg_addr, uint8_t *data, uint16_t len)
{
return HAL_I2C_Mem_Write(&hi2c1, SENSOR_ADDR, reg_addr,
I2C_MEMADD_SIZE_8BIT, data, len, 100);
}
// 读寄存器:从传感器指定地址读取数据
static HAL_StatusTypeDef sensor_read_reg(uint8_t reg_addr, uint8_t *data, uint16_t len)
{
return HAL_I2C_Mem_Read(&hi2c1, SENSOR_ADDR, reg_addr,
I2C_MEMADD_SIZE_8BIT, data, len, 100);
}
嗯,这里要注意:I2C_MEMADD_SIZE_8BIT表示寄存器地址是8位的。如果你的传感器寄存器地址是16位(比如某些加速度计),就要改成I2C_MEMADD_SIZE_16BIT。我曾经在这个参数上吃过亏,读出来的数据全是0xFF,排查了半天。
3.2 初始化流程:让传感器“醒过来”
传感器上电后,通常处于默认状态。我们需要通过初始化流程,把它配置成我们想要的工作模式。说白了,就是往特定的寄存器里写一些配置值。
以SHT30为例,它的初始化流程一般包括:
- 软复位:让传感器恢复到已知状态
- 设置测量模式:比如选择“周期采集模式”还是“单次采集模式”
- 配置数据速率:比如设置采样频率为10Hz还是1Hz
- 检查状态寄存器:确认配置是否生效
核心要点: 初始化不是随便写几个寄存器就完事了。我建议你每次写完配置后,都读回来校验一下。很多传感器都有“读回校验”功能,别省这一步。
下面是一个典型的初始化代码:
void sht30_init(void)
{
uint8_t cmd[2];
uint8_t status[3];
// 第一步:软复位
cmd[0] = 0x30;
cmd[1] = 0xA2;
HAL_I2C_Master_Transmit(&hi2c1, SENSOR_ADDR, cmd, 2, 100);
HAL_Delay(10); // 等待复位完成
// 第二步:设置周期采集模式(高重复性,10Hz)
cmd[0] = 0x22;
cmd[1] = 0x36;
HAL_I2C_Master_Transmit(&hi2c1, SENSOR_ADDR, cmd, 2, 100);
HAL_Delay(1);
// 第三步:读取状态寄存器,确认配置成功
sensor_read_reg(0xF3, status, 3);
if ((status[0] & 0x01) == 0) {
// 配置成功
printf("SHT30 init OK\n");
} else {
printf("SHT30 init FAIL\n");
}
}
💡 小技巧: 初始化代码里,我习惯加一个“自检”步骤。比如读取传感器的ID寄存器或版本号,确认通信链路是通的。这招帮我省了不少调试时间。
3.3 数据读取:把物理量“算”出来
初始化完成后,传感器就开始干活了。数据读取分两种场景:
- 单次读取:每次需要数据时,发命令触发一次测量,然后读回来
- 周期读取:传感器自己按固定频率测量,我们定时去取数据
我个人更推荐周期读取模式。为什么呢?因为单次读取需要等待测量完成,会阻塞CPU。而周期模式下,传感器自己干活,我们只需要在定时器中断里取数据就行。
下面是读取温湿度数据的代码:
void sht30_read_data(float *temp, float *humi)
{
uint8_t raw_data[6];
uint16_t raw_temp, raw_humi;
// 读取6个字节:温度高8位、温度低8位、CRC、湿度高8位、湿度低8位、CRC
sensor_read_reg(0xE0, raw_data, 6);
// 解析原始数据
raw_temp = (raw_data[0] << 8) | raw_data[1];
raw_humi = (raw_data[3] << 8) | raw_data[4];
// 转换为物理量(SHT30的转换公式)
*temp = -45.0f + 175.0f * (float)raw_temp / 65535.0f;
*humi = 100.0f * (float)raw_humi / 65535.0f;
// 简单的CRC校验(可选)
if (!crc8_check(raw_data, 2, raw_data[2]) ||
!crc8_check(&raw_data[3], 2, raw_data[5])) {
// CRC校验失败,数据可能异常
*temp = -999.0f;
*humi = -999.0f;
}
}
⚠️ 注意: 很多传感器在数据包末尾带了CRC校验字节。我曾经为了省事跳过了CRC校验,结果在项目现场出现了-40℃的异常数据,排查了整整两天才发现是通信干扰导致的。所以,CRC校验不要省。
3.4 知识体系总览
为了让你更直观地理解整个驱动开发的脉络,我画了一张流程图:
3.5 避坑指南:我踩过的那些坑
做传感器驱动开发,有些坑是绕不过去的。我把自己踩过的几个典型问题列出来,你遇到了可以直接对号入座:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 通信超时,一直返回HAL_TIMEOUT | I2C时钟线或数据线没上拉 | 检查硬件,确保SCL/SDA有4.7kΩ上拉电阻 |
| 读回来的数据全是0xFF | 地址错误或传感器未响应 | 用逻辑分析仪抓波形,确认地址是否正确 |
| 数据偶尔跳变,出现异常值 | 未做CRC校验或电源噪声 | 增加CRC校验,并在电源引脚加100nF去耦电容 |
| 初始化后传感器不工作 | 配置寄存器写入了非法值 | 仔细阅读手册,确认寄存器位定义,写完后读回校验 |
💡 我的经验: 如果你手头没有逻辑分析仪,可以用STM32的GPIO模拟I2C时序,配合示波器看波形。虽然麻烦点,但能帮你确认到底是硬件问题还是软件问题。
好了,传感器驱动开发的核心内容就这些。说白了就是三板斧:读写寄存器、初始化配置、读取数据。把这套流程跑通了,换任何传感器都只是换寄存器地址和转换公式的事。
可可精品资料库