3. 传感器接口基础与驱动移植
传感器采集,是无线MCU项目的起点,也是功耗优化的第一道关卡。很多新手一上来就调无线协议、搞低功耗算法,结果发现数据根本不准,或者采集流程就把电耗光了。嗯,这章我们就先把地基打牢。
3.1 传感器初始化流程
初始化传感器,说白了就是让MCU和传感器「对上话」。我见过不少工程师直接复制例程里的初始化代码,换了个传感器型号就跑不通——因为不同传感器的上电时序、寄存器地址、通信速率都不一样。
一个标准的初始化流程,通常包含这几步:
- 硬件引脚配置——GPIO方向、上下拉、开漏/推挽
- 时钟使能——给传感器模块和通信外设(I²C/SPI/UART)提供时钟
- 通信接口初始化——波特率、极性、相位、地址等
- 传感器上电/复位——有些传感器需要延时等待内部振荡器稳定
- 读取ID/版本寄存器——确认通信正常,防止焊错芯片
- 配置工作模式——量程、分辨率、采样率、滤波等
- 触发校准(可选)——比如加速度计的零偏校准
下面是一个典型的I²C温湿度传感器初始化代码片段:
// 传感器初始化函数
// 返回 0=成功,非0=失败
int sensor_init(void) {
uint8_t id;
// 1. 配置GPIO:SCL/SDA 开漏输出,带上拉
GPIO_InitTypeDef gpio = {
.Pin = SCL_PIN | SDA_PIN,
.Mode = GPIO_MODE_OUTPUT_OD,
.Pull = GPIO_PULLUP,
.Speed = GPIO_SPEED_FREQ_HIGH
};
HAL_GPIO_Init(GPIOB, &gpio);
// 2. 使能I²C时钟
__HAL_RCC_I2C1_CLK_ENABLE();
// 3. 初始化I²C外设:400kHz标准模式
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 400000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
HAL_I2C_Init(&hi2c1);
// 4. 传感器上电延时:等待内部振荡器稳定
HAL_Delay(10); // 数据手册要求至少5ms
// 5. 读取设备ID,确认通信正常
if (HAL_I2C_Mem_Read(&hi2c1, SENSOR_ADDR,
REG_DEVICE_ID, 1, &id, 1, 100) != HAL_OK) {
return -1; // 通信失败
}
if (id != EXPECTED_ID) {
return -2; // ID不匹配,可能焊错芯片
}
// 6. 配置量程和分辨率
uint8_t config = 0x84; // 量程±2g,高分辨率模式
HAL_I2C_Mem_Write(&hi2c1, SENSOR_ADDR,
REG_CONFIG, 1, &config, 1, 100);
// 7. 回读校验
uint8_t check;
HAL_I2C_Mem_Read(&hi2c1, SENSOR_ADDR,
REG_CONFIG, 1, &check, 1, 100);
if (check != config) {
return -3; // 配置写入失败
}
return 0; // 初始化成功
}
3.2 单次采集 vs 连续采集模式
这两种模式的选择,直接决定了你的系统功耗。你想想看,如果只是每分钟测一次温度,却让传感器一直连续采集,那电池能撑多久?
| 对比项 | 单次采集模式 | 连续采集模式 |
|---|---|---|
| 触发方式 | 每次手动触发一次转换 | 传感器内部自动循环转换 |
| 功耗特点 | 采集时工作,其余时间休眠 | 持续工作,功耗较高 |
| 适用场景 | 低频采集(秒级/分钟级) | 高频采集(毫秒级)或需要实时数据流 |
| MCU参与度 | 每次采集需唤醒MCU发起指令 | 可配合FIFO/DMA,MCU可深度休眠 |
| 数据延迟 | 有触发到就绪的等待时间 | 数据持续更新,延迟低 |
单次采集的典型流程:
// 单次采集示例:触发一次转换,等待完成,读取数据
uint8_t sensor_read_once(int16_t *raw_temp, int16_t *raw_hum) {
uint8_t cmd = 0x2C; // 触发单次温湿度转换命令
uint8_t data[4];
// 1. 发送触发命令
HAL_I2C_Master_Transmit(&hi2c1, SENSOR_ADDR, &cmd, 1, 100);
// 2. 等待转换完成(数据手册典型值7ms,最大值12ms)
HAL_Delay(10); // 保守等待
// 3. 读取4字节数据(温度高/低字节 + 湿度高/低字节)
HAL_I2C_Master_Receive(&hi2c1, SENSOR_ADDR, data, 4, 100);
// 4. 拼接原始值
*raw_temp = (data[0] << 8) | data[1];
*raw_hum = (data[2] << 8) | data[3];
return 0;
}
连续采集模式则不同。传感器自己干活,MCU可以睡大觉。配合FIFO缓冲区,MCU每隔一段时间醒来批量取数据就行。这在需要高频采集(比如加速度计200Hz采样)时特别有用。
// 连续采集模式配置(以加速度计为例)
void accel_continuous_mode_init(void) {
uint8_t config;
// 设置输出数据率为200Hz
config = 0x90; // ODR=200Hz, 正常模式
HAL_I2C_Mem_Write(&hi2c1, ACCEL_ADDR,
REG_CTRL1, 1, &config, 1, 100);
// 使能FIFO,设置水印为16个样本
config = 0x40 | 0x0F; // FIFO使能,水印=16
HAL_I2C_Mem_Write(&hi2c1, ACCEL_ADDR,
REG_FIFO_CTRL, 1, &config, 1, 100);
// 使能数据就绪中断(可选,用于唤醒MCU)
config = 0x01;
HAL_I2C_Mem_Write(&hi2c1, ACCEL_ADDR,
REG_INT_EN, 1, &config, 1, 100);
}
3.3 数据格式解析:原始值转物理量
传感器给你的原始值,说白了就是一串二进制数。你得知道它的「编码规则」,才能换算成温度、湿度、加速度这些物理量。
常见的原始值格式有这几种:
- 无符号整数——直接映射,比如光强度传感器
- 有符号整数(补码)——加速度计、陀螺仪常用
- 定点小数——高几位是整数,低几位是小数
- IEEE 754浮点——部分高精度传感器直接输出float
转换公式通常藏在数据手册里。我习惯先把公式整理成宏定义,方便复用:
// 温湿度传感器SHT30的转换公式
// 温度:T = -45 + 175 * (raw / 65535.0)
// 湿度:RH = 100 * (raw / 65535.0)
#define SHT30_TEMP(raw) (-45.0f + 175.0f * (raw) / 65535.0f)
#define SHT30_HUM(raw) (100.0f * (raw) / 65535.0f)
// 加速度计LSM6DSO的转换公式(±2g量程)
// 加速度 = raw * 0.061 (mg/LSB)
#define LSM6DSO_ACCEL(raw) ((raw) * 0.061f)
// 使用示例
int16_t raw_temp = 15240;
float temp_c = SHT30_TEMP(raw_temp);
// 结果:-45 + 175 * 15240 / 65535 ≈ -4.3°C
raw / 65535,两个整数相除结果永远是0。
还有一个容易忽略的问题——字节序。很多传感器默认是大端(Big-Endian),而ARM Cortex-M系列MCU是小端(Little-Endian)。直接memcpy会得到错误的值。
// 字节序转换示例:大端转小端
int16_t big_endian_to_little(uint8_t hi, uint8_t lo) {
return (int16_t)((hi << 8) | lo);
}
// 或者用宏
#define BE16_TO_CPU(hi, lo) ((int16_t)(((uint16_t)(hi) << 8) | (lo)))
3.4 采样率与分辨率的权衡
这是传感器选型和配置中最需要动脑子的一步。采样率和分辨率,本质上是在跟功耗、数据量、噪声做博弈。
| 参数 | 提高带来的好处 | 提高带来的代价 |
|---|---|---|
| 采样率 ↑ | 能捕捉更快的变化信号 | 功耗↑、数据量↑、MCU处理负担↑ |
| 分辨率 ↑ | 测量精度更高,细节更丰富 | 转换时间↑、噪声可能↑、功耗↑ |
为什么会这样?因为高分辨率意味着ADC需要更多时间做逐次逼近,或者需要过采样+数字滤波。这段时间里,传感器和MCU都得醒着。
实际项目中,我一般这样决策:
- 先确定信号带宽——根据物理现象估算最高频率成分
- 采样率取2-5倍带宽——满足奈奎斯特,留余量
- 分辨率看动态范围需求——每增加1位,动态范围增加6dB
- 算功耗预算——采样率×单次转换能耗=采集功耗
- 如果功耗超标——降低采样率,或者用FIFO批量模式
举个例子。做一个结构健康监测的加速度采集:
- 桥梁振动频率一般在0.5-20Hz → 采样率取100Hz(5倍)
- 需要检测0.1g的微小振动 → 分辨率至少12位(±2g量程下约1mg/LSB)
- 电池供电,要求续航1年 → 算下来每天只能采集30分钟
- 最终方案:100Hz采样,12位分辨率,每天定时采集30分钟,其余时间深度休眠
嗯,传感器采集这块,说白了就是「够用就好」。把初始化流程走稳,选对采集模式,算对物理量,再根据实际需求权衡采样率和分辨率——这套基本功打扎实了,后面的低功耗调优才能有的放矢。