4. 数据采集基础:传感器数据读取方式
数据采集是整个标定流程的起点。说白了,你后面所有的算法、模型、补偿曲线,都建立在原始数据之上。如果这一步没做好,后面全是白搭。
我个人习惯把数据采集拆成三个核心问题:怎么读?怎么拆?怎么对齐? 今天我们就一个一个来啃。
4.1 三种主流读取方式:串口、I2C、SPI
做嵌入式这么多年,我接触过的传感器接口无非就这三种。每种都有它的脾气,选错了接口,轻则丢数据,重则整个系统跑飞。
4.1.1 串口(UART)—— 最灵活,但最慢
串口的好处是简单。两根线(TX、RX),一收一发,全双工。我在项目中遇到过用串口接温湿度传感器的场景,波特率设到115200,基本够用。
但要注意:串口没有时钟线,收发双方得提前约定好波特率。一旦对不上,收到的就是乱码。
// 串口初始化示例(STM32 HAL库)
UART_HandleTypeDef huart1;
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
HAL_UART_Init(&huart1);
// 接收一帧数据(中断方式)
uint8_t rx_buffer[64];
HAL_UART_Receive_IT(&huart1, rx_buffer, 10);
4.1.2 I2C —— 多设备,两根线搞定
I2C用两根线(SCL、SDA)挂载多个设备,每个设备有独立地址。嗯,这里要注意:地址冲突是我踩过最多的坑。曾经有一次,两个传感器用了同一个地址,结果读出来的数据乱七八糟,排查了整整一下午。
I2C是半双工,速度一般(标准模式100kHz,快速模式400kHz)。适合读取配置寄存器、慢速传感器(如温度、湿度)。
// I2C读取传感器数据示例
uint8_t reg_addr = 0x00; // 寄存器地址
uint8_t data[2];
HAL_I2C_Master_Transmit(&hi2c1, (SENSOR_ADDR << 1), ®_addr, 1, 100);
HAL_I2C_Master_Receive(&hi2c1, (SENSOR_ADDR << 1), data, 2, 100);
// data[0]和data[1]就是传感器原始值
4.1.3 SPI —— 快,但线多
SPI是速度之王。四根线(MOSI、MISO、SCLK、CS),全双工,速率轻松上MHz。我做过一个加速度计标定项目,用SPI读取数据,采样率能跑到10kHz以上。
但代价是:每多一个设备,就得多一根片选线。板子空间紧张的时候,这很要命。
- 低速、多设备 → I2C
- 高速、少设备 → SPI
- 远距离、简单协议 → 串口
4.2 数据帧解析 —— 从字节到物理量
传感器吐出来的是一堆字节,你得知道每个字节代表什么。这就是数据帧解析。
我曾经接手过一个项目,前任工程师把帧格式文档弄丢了。我对着示波器一比特一比特地猜,那种感觉……嗯,不想再来第二次。
4.2.1 常见帧结构
| 字段 | 长度 | 说明 |
|---|---|---|
| 帧头 | 1-2字节 | 固定值,如0xAA 0x55 |
| 数据长度 | 1字节 | 有效数据字节数 |
| 有效数据 | N字节 | 传感器原始值 |
| 校验 | 1-2字节 | CRC或累加和 |
| 帧尾 | 1字节 | 固定值,如0x0D 0x0A |
// 帧解析伪代码
uint8_t parse_frame(uint8_t *buffer, int len) {
// 1. 找帧头
if (buffer[0] != 0xAA || buffer[1] != 0x55) return ERROR;
// 2. 取数据长度
int data_len = buffer[2];
// 3. 校验
uint8_t checksum = 0;
for (int i = 0; i < 3 + data_len; i++) {
checksum += buffer[i];
}
if (checksum != buffer[3 + data_len]) return ERROR;
// 4. 提取有效数据
memcpy(sensor_data, &buffer[3], data_len);
return OK;
}
4.3 时间戳对齐 —— 数据的时间灵魂
数据没有时间戳,就像人没有身份证。你只知道某个时刻传感器读数是100,但不知道是1秒前还是1分钟前,那这个数据就没法用。
4.3.1 为什么需要时间戳对齐?
多传感器融合时,每个传感器的采样时刻可能不同。比如IMU是100Hz,GPS是10Hz,摄像头是30Hz。如果不做时间戳对齐,你拿到的数据就是「时空错乱」的。
4.3.2 两种对齐策略
策略一:硬件时间戳
在传感器数据帧到达MCU时,立即记录当前系统时间。这是最准的方法。我习惯在中断服务函数里做这件事:
// 串口接收中断中打时间戳
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
uint32_t timestamp = HAL_GetTick(); // 毫秒级时间戳
save_data_with_timestamp(rx_buffer, timestamp);
}
}
策略二:软件插值对齐
如果硬件不支持,就用软件插值。比如两个传感器采样率不同,把慢速传感器的数据通过线性插值,对齐到快速传感器的时间轴上。
// 线性插值示例
float interpolate(float t, float t0, float v0, float t1, float v1) {
return v0 + (v1 - v0) * (t - t0) / (t1 - t0);
}
4.4 本章知识体系
下面这张图总结了我们刚才讲的内容。你可以把它当作数据采集的「路线图」:
数据采集这件事,说难不难,说简单也不简单。关键是把每个环节想清楚:用什么接口读?读到的字节怎么拆?拆出来的数据怎么打上时间戳?
我个人建议,在开始写代码之前,先把这三个问题画在纸上。想清楚了再动手,能省掉后面80%的调试时间。