4. 传感器数据采集基础:轮询模式与中断模式、DMA传输、定时器触发采集、多传感器同步采集
好,咱们今天聊点实在的。传感器数据采集,说白了就是让单片机“感知”外部世界。你想想看,一个温度传感器、一个加速度计,它们把物理量变成了电信号,但单片机怎么知道这个信号变了?怎么把数据拿回来?这里面的门道,就是我这些年一直在跟各种传感器打交道积累下来的经验。
我个人习惯把数据采集分成几个层次:怎么知道数据来了、怎么把数据搬回来、什么时候去采集、多个传感器怎么协调。今天咱们就把这四件事儿彻底讲透。
核心知识点速览
- 轮询模式:CPU主动问,简单但浪费
- 中断模式:传感器主动喊,高效但要注意优先级
- DMA传输:数据自己走,CPU不用管
- 定时器触发:按固定节奏采集,精准控制
- 多传感器同步:时间戳对齐,数据才有意义
4.1 轮询模式:最直接,但别滥用
轮询模式,就是CPU不停地去问传感器:“嘿,你有新数据了吗?” 传感器说“没有”,CPU继续问。直到传感器说“有了”,CPU才去读数据。
我在项目中遇到过一个小白同事,用轮询读一个气压传感器,主循环里每1ms问一次。结果呢?传感器本身更新速率才10Hz,CPU 99%的时间都在做无用功。说白了,轮询模式适合那些数据变化快、且CPU闲着也是闲着的场景。
我的建议:轮询模式适合以下情况:
- 传感器数据更新频率很高(比如 > 1kHz)
- CPU没有其他紧急任务要处理
- 代码简单,调试方便
// 轮询模式示例:读取温度传感器
uint32_t last_read = 0;
while(1) {
if(HAL_GetTick() - last_read > 100) { // 每100ms读一次
if(TSensor_DataReady()) { // 查询数据是否就绪
temperature = TSensor_Read(); // 读取数据
last_read = HAL_GetTick();
}
}
// 其他任务...
}
注意:轮询模式下,如果传感器响应慢,CPU会被卡住。我曾经在一个电机控制项目里用轮询读编码器,结果因为传感器偶尔延迟,导致控制周期抖动,电机嗡嗡响。后来果断换了中断模式。
4.2 中断模式:让传感器主动找你
中断模式就聪明多了。传感器数据准备好了,它会拉一根引脚(INT引脚)告诉CPU:“我有数据了,快来拿!” CPU收到这个信号,暂停手头的工作,跑去读数据,读完了再回来继续干活。
你想想看,这就像你正在写代码,同事有问题会直接过来问你,而不是你每隔一分钟就去问一圈“有问题吗?”——效率高多了。
// 中断模式示例:配置传感器中断
void Sensor_Init_With_Interrupt(void) {
// 配置传感器INT引脚为输入,上升沿触发中断
HAL_GPIO_EXTI_Init(&gpio_int_config);
// 使能传感器中断输出
Sensor_WriteReg(0x0D, 0x01); // 使能数据就绪中断
}
// 中断服务函数
void EXTI0_IRQHandler(void) {
if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0)) {
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
// 读取传感器数据(中断里要快进快出)
sensor_data = Sensor_ReadData();
// 设置标志位,让主循环处理数据
data_ready_flag = 1;
}
}
中断模式的关键点:
- 中断服务函数要短小精悍,别在里面做复杂运算
- 注意中断优先级,高频率中断别抢了系统心跳
- 多个传感器共用中断线时,要在中断里判断是谁触发的
我记得有一次调试一个IMU模块,中断信号一直触发,CPU根本没法干别的。查了半天,原来是传感器配置错了,中断模式设成了“数据溢出中断”,而不是“新数据就绪中断”。嗯,配置寄存器一定要仔细看数据手册。
4.3 DMA传输:数据自己走,CPU解放了
DMA(Direct Memory Access)是个好东西。它让数据直接从外设(比如SPI、I2C接口)搬到内存里,完全不用CPU插手。CPU只需要告诉DMA:“你去把传感器那1000个数据搬过来”,然后就可以去干别的事了。搬完了,DMA会发个中断通知CPU。
我做过一个音频采集项目,麦克风以48kHz采样,每秒钟48000个16位数据。如果用中断模式,CPU每20微秒就要进一次中断,基本啥也干不了。用了DMA之后,CPU占用率从95%降到了5%。
// DMA传输示例:通过SPI读取传感器数据
#define BUFFER_SIZE 256
uint8_t spi_rx_buffer[BUFFER_SIZE];
void Sensor_Start_DMA_Read(void) {
// 启动SPI DMA接收,数据直接存入缓冲区
HAL_SPI_Receive_DMA(&hspi1, spi_rx_buffer, BUFFER_SIZE);
}
// DMA传输完成回调
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
if(hspi->Instance == SPI1) {
// 数据已经在 spi_rx_buffer 里了,直接处理
Process_Sensor_Data(spi_rx_buffer, BUFFER_SIZE);
// 启动下一次DMA传输(循环模式)
HAL_SPI_Receive_DMA(&hspi1, spi_rx_buffer, BUFFER_SIZE);
}
}
使用DMA的几个坑:
- DMA缓冲区要小心Cache一致性问题(特别是带Cache的MCU)
- DMA传输完成中断里不要做耗时操作,否则会影响下一次传输
- 环形缓冲区(Circular Buffer)配合DMA循环模式,可以实现无间断采集
4.4 定时器触发采集:精准的时间节拍
很多传感器支持“定时采集”模式。你设置一个定时器,让它每隔固定时间触发一次ADC转换或者传感器读取。这样做的好处是:采集间隔精确可控,不受代码执行时间影响。
我做过一个振动监测项目,要求以1kHz的固定频率采集加速度数据。如果用主循环里延时,代码里加个if判断都会导致时间抖动。后来我用定时器直接触发ADC,精度到了微秒级。
// 定时器触发ADC采集配置
void Timer_Trigger_ADC_Init(void) {
// 配置定时器,1ms触发一次(1kHz)
htim2.Init.Period = 999; // 假设定时器时钟1MHz
htim2.Init.Prescaler = 0;
HAL_TIM_Base_Init(&htim2);
// 配置ADC为定时器触发模式
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
HAL_ADC_Init(&hadc1);
// 启动定时器
HAL_TIM_Base_Start(&htim2);
// 启动ADC,每次定时器触发自动采集
HAL_ADC_Start_DMA(&hadc1, adc_buffer, BUFFER_SIZE);
}
定时器触发的优势:
- 采集间隔精确,适合FFT、控制环路等需要固定采样率的场景
- 可以同时触发多个外设(比如ADC和DAC同步)
- 配合DMA,实现“零CPU开销”的周期性采集
4.5 多传感器同步采集:时间对齐是灵魂
这是最难的一块。当你有多个传感器(比如加速度计、陀螺仪、磁力计),它们各自有各自的时钟,各自有各自的采集时刻。如果你只是分别读它们的数据,然后拼在一起,你会发现:这些数据根本不在同一个时间点上。
举个例子,你用一个加速度计和一个陀螺仪做姿态解算。加速度计在t=0时刻采集,陀螺仪在t=5ms时刻采集。如果你把它们当成同一时刻的数据去算姿态,结果就是——姿态会漂移,而且漂得离谱。
我踩过这个坑。在一个无人机飞控项目里,IMU数据不同步,导致姿态解算出来的角度一直在抖,飞机根本稳不住。后来我用了硬件同步机制才解决。
多传感器同步的几种方案:
| 方案 | 精度 | 复杂度 | 适用场景 |
|---|---|---|---|
| 软件时间戳 | 低(ms级) | 低 | 对同步要求不高的场景 |
| 硬件触发线 | 高(us级) | 中 | IMU、多轴传感器 |
| 共享时钟/同步信号 | 极高(ns级) | 高 | 高速数据采集系统 |
// 多传感器硬件同步示例:使用一个GPIO同时触发多个传感器
void Multi_Sensor_Sync_Init(void) {
// 配置一个定时器,产生同步脉冲
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
// 配置传感器1:接收同步信号
Accel_WriteReg(0x1A, 0x40); // 使能硬件触发模式
// 配置传感器2:接收同一个同步信号
Gyro_WriteReg(0x1B, 0x40); // 使能硬件触发模式
// 现在,定时器每产生一个脉冲,两个传感器同时采集
// 数据天然就是同步的!
}
我的经验:如果传感器不支持硬件同步,退而求其次可以用“中断+时间戳”方案。每个传感器数据就绪时,在中断里记录当前系统时间(比如定时器的计数值)。这样虽然采集时刻不同,但你知道每个数据是什么时候采集的,后期可以通过插值对齐。
小结一下
传感器数据采集,没有银弹。轮询简单但浪费CPU,中断高效但要注意优先级,DMA解放CPU但要注意缓冲区管理,定时器触发精准但配置复杂,多传感器同步是系统级难题。
我个人习惯是:能用DMA就用DMA,能用硬件同步就用硬件同步。实在不行,中断+时间戳兜底。轮询嘛...除非你确定CPU真的闲得发慌,否则别用。
好了,这一章的内容就到这儿。记住,采集数据只是第一步,怎么处理这些数据才是真正的挑战。下一章咱们聊聊数据滤波和特征提取——那又是另一片天地了。