1. 项目背景与硬件选型
测速系统在嵌入式开发中非常常见,无论是智能小车的轮速检测,还是工业设备的转速监控,都需要可靠的测速方案。我最近用STM32F103和MH-Sensor红外对射模块搭建了一个测速系统,实测效果不错,这里把完整的设计过程和踩过的坑都分享给大家。
选择STM32F103是因为它性价比高,GPIO和定时器资源丰富,完全能满足测速需求。MH-Sensor红外对射模块价格便宜(某宝上5块钱左右),反应灵敏,配合20孔测速码盘使用效果很好。这里有个小技巧:码盘的孔数越多,测速分辨率越高,但也要考虑电机转速,转速太高时孔数太多可能导致计数来不及。
硬件清单如下:
- STM32F103C8T6最小系统板
- MH-Sensor Series红外对射模块
- 20孔测速码盘(外径5cm)
- 电机及驱动模块
- 杜邦线若干
2. 硬件连接与工作原理
2.1 接线示意图
实际接线非常简单,只需要3根线:
- 红外模块VCC → STM32 3.3V
- 红外模块GND → STM32 GND
- 红外模块DO → STM32 PB14
这里特别注意:MH-Sensor模块有AO和DO两个输出口,我们要用DO(数字输出)而不是AO。DO输出的是干净的高低电平信号,可以直接给STM32的GPIO读取。
2.2 工作原理详解
当码盘转动时,红外对射模块会周期性检测到通光/遮光的变化:
- 码盘通光孔对准传感器时:红外光透过,DO输出高电平
- 码盘实体部分遮挡传感器时:红外光被阻挡,DO输出低电平
这样每转过一个孔,就会产生一个完整的脉冲信号。通过统计单位时间内的脉冲次数,就能计算出转速。公式很简单: 转速(rpm) = (脉冲数 × 60) / (码盘孔数 × 采样时间)
比如1秒内检测到34个脉冲,码盘20孔,那么转速就是(34×60)/20=102rpm。
3. STM32软件设计
3.1 GPIO外部中断配置
我们需要在PB14引脚上配置外部中断,检测红外模块的高低电平变化。关键配置步骤如下:
// 初始化代码片段 void CountSensor_Init(void) { // 开启GPIOB和AFIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); // 配置PB14为上拉输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOB, &GPIO_InitStructure); // 映射外部中断线 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14); // 配置EXTI为下降沿触发 EXTI_InitStructure.EXTI_Line = EXTI_Line14; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStructure); // 配置NVIC中断 NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; NVIC_Init(&NVIC_InitStructure); }实际使用中发现,如果电机振动较大,可能会产生信号抖动。我在中断服务函数里加了二次判断,有效解决了这个问题:
void EXTI15_10_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line14) == SET) { // 再次确认引脚电平,防抖动 if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0) { CountSensor_Count++; } EXTI_ClearITPendingBit(EXTI_Line14); } }3.2 定时器配置与速度计算
使用TIM3定时器实现1秒间隔的中断,在中断里读取脉冲计数并计算速度:
void Timer_Init(void) { // TIM3基础配置 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; // 10kHz TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); // 使能更新中断 TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_Init(&NVIC_InitStructure); TIM_Cmd(TIM3, ENABLE); } // TIM3中断服务函数 void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { // 计算转速(rpm) current_rpm = (CountSensor_Count * 60) / 20; CountSensor_Count = 0; // 清零计数器 TIM_ClearITPendingBit(TIM3, TIM_IT_Update); } }4. 实测效果与优化建议
实测这套系统在100-2000rpm范围内表现稳定,误差小于3%。但在低速时(<50rpm)精度会下降,这是因为低速时采样到的脉冲数太少。可以通过以下方法优化:
- 增加码盘孔数:比如改用60孔码盘,低速分辨率能提高3倍
- 延长采样时间:从1秒改为3秒采样,但会降低响应速度
- 使用M法测速:在固定时间内计数脉冲数(当前方案)
- 使用T法测速:测量两个脉冲间的时间间隔,低速时更准
我在项目中还加了移动平均滤波,对速度值做平滑处理,代码很简单:
#define FILTER_LEN 5 uint16_t speed_buf[FILTER_LEN]; uint8_t buf_index = 0; // 在定时器中断中加入滤波 current_rpm = (CountSensor_Count * 60) / 20; speed_buf[buf_index++] = current_rpm; if(buf_index >= FILTER_LEN) buf_index = 0; // 计算平均值 uint32_t sum = 0; for(int i=0; i<FILTER_LEN; i++) { sum += speed_buf[i]; } filtered_rpm = sum / FILTER_LEN;这套系统稍作修改就能用于很多场景,比如我后来把它用在了智能小车的闭环速度控制上,配合PID算法可以实现精确的速度调节。如果用在工业设备上,建议选用金属码盘和工业级红外传感器,这样抗干扰能力更强。