1. 项目概述
光电测速传感器在嵌入式系统中是一种非常实用的外设模块,它通过检测物体运动产生的脉冲信号来实现转速测量。我在最近的一个智能小车项目中就使用了这种传感器来实时监测车轮转速,效果相当不错。
这种传感器的工作原理其实很简单:当编码盘上的孔或齿经过光电传感器时,会产生电平跳变信号。我们只需要统计单位时间内的脉冲数量,再结合编码盘的设计参数(比如每转的孔数)和轮胎周长,就能计算出实际的转速和线速度。这种方案成本低、可靠性高,非常适合学生项目或工业测速应用。
2. 硬件准备与接线
2.1 所需器材清单
要完成这个实验,你需要准备以下硬件设备:
- 光电测速传感器模块(窄槽型) ×1
- 编码盘(建议20孔) ×1
- STM32开发板(我用的是STM32F103C8T6最小系统板) ×1
- 杜邦线(公对公、公对母)若干
- 5V电源(可用开发板的5V输出)
- 万用表(可选,用于调试)
提示:编码盘的孔数会影响测量精度,孔数越多精度越高,但也要考虑传感器能响应的最高频率。对于初学者,20孔的编码盘是个不错的折中选择。
2.2 传感器模块详解
常见的光电测速传感器模块通常有4个引脚:
- VCC:电源正极(3.3V-5V)
- GND:电源负极
- DO:数字信号输出(TTL电平)
- AO:模拟信号输出(本项目中不使用)
模块上通常还有一个电位器,用于调节灵敏度。顺时针旋转增加灵敏度,逆时针减小。调试时建议先调到中间位置,然后根据实际响应情况微调。
2.3 接线方案
根据使用的库不同,接线方式略有差异:
标准库接线方案:
- 传感器VCC → 开发板5V
- 传感器GND → 开发板GND
- 传感器DO → PC3(或其他支持外部中断的GPIO)
HAL库接线方案:
- 传感器VCC → 开发板5V
- 传感器GND → 开发板GND
- 传感器DO → PA3(需与代码中的中断配置一致)
注意:虽然传感器支持3.3V供电,但5V供电时信号更稳定。如果使用3.3V系统,建议在DO信号线上加一个1kΩ的上拉电阻到3.3V。
3. 工作原理深入解析
3.1 光电测速原理
光电测速传感器的核心是一个红外发射管和一个接收管。当编码盘的齿或孔通过传感器时,会周期性遮挡红外光,导致接收管输出的电平发生变化。这个变化会被转换为数字脉冲信号输出。
测量转速的关键公式:
code复制转速(RPM) = (脉冲计数 × 60) / (编码盘孔数 × 采样时间)
线速度 = 转速 × 轮胎周长
例如:1秒内检测到400个脉冲,编码盘20孔,则:
转速 = (400×60)/(20×1) = 1200 RPM
3.2 信号处理要点
实际应用中需要考虑几个关键问题:
- 消抖处理:机械振动可能导致信号抖动,需要在硬件(电容滤波)或软件(延时判断)上做消抖
- 中断响应:脉冲捕获通常使用外部中断,要确保中断服务程序尽可能简短
- 定时采样:需要定时器定期计算和清零脉冲计数
- 速度计算:根据应用需求选择合适的计算周期(通常100ms-1s)
4. 软件实现详解
4.1 标准库实现方案
4.1.1 初始化配置
首先需要配置定时器和外部中断:
c复制// 定时器4初始化(1秒周期)
void TIM4_Init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
TIM_TimeBaseInitStructure.TIM_Period = 1000000-1; // 1秒周期
TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1; // 72MHz/72 = 1MHz
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM4, ENABLE);
}
// 外部中断初始化(下降沿触发)
void Exit_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource3);
EXTI_InitStructure.EXTI_Line = EXTI_Line3;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_Init(&EXTI_InitStructure);
}
4.1.2 中断服务程序
c复制volatile uint32_t pulse_count = 0; // 脉冲计数器
// 定时器中断(每秒计算一次速度)
void TIM4_IRQHandler()
{
if(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
uint32_t rpm = (pulse_count * 60) / 20; // 假设编码盘20孔
printf("转速: %lu RPM\r\n", rpm);
pulse_count = 0; // 清零计数器
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
}
// 外部中断(脉冲计数)
void EXTI3_IRQHandler()
{
if(EXTI_GetITStatus(EXTI_Line3) != RESET)
{
if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_3) == 0)
{
pulse_count++;
}
EXTI_ClearITPendingBit(EXTI_Line3);
}
}
4.2 HAL库实现方案
4.2.1 CubeMX配置
- 在Pinout中配置PA3为GPIO_EXTI3
- 在Configuration中配置外部中断为下降沿触发
- 启用TIM4定时器,设置预分频和周期值
- 启用USART1用于调试输出
4.2.2 关键代码
c复制// 全局变量
volatile uint32_t pulse_count = 0;
uint32_t last_exti_time = 0;
// 外部中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_3)
{
uint32_t now = HAL_GetTick();
if (now - last_exti_time < 10) return; // 10ms消抖
last_exti_time = now;
pulse_count++;
}
}
// 定时器中断回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM4)
{
uint32_t rpm = (pulse_count * 60) / 20; // 20孔编码盘
printf("转速: %lu RPM\r\n", rpm);
pulse_count = 0;
}
}
5. 实际应用与调试技巧
5.1 安装注意事项
- 编码盘与传感器的间隙应保持在3-5mm,太远信号弱,太近可能摩擦
- 确保编码盘与轴同心,偏心会导致转速波动
- 传感器应对准编码盘的中部位置,避免边缘效应
- 在强光环境下,建议给传感器加遮光罩
5.2 常见问题排查
问题1:无脉冲信号
- 检查电源是否正常
- 用万用表测量DO引脚电压,遮挡传感器时应有变化
- 调整传感器上的灵敏度电位器
- 确认接线正确,特别是GND要共地
问题2:脉冲计数不稳定
- 增加消抖时间(软件或硬件)
- 检查编码盘是否安装稳固
- 尝试降低供电电压(某些传感器在5V时噪声较大)
问题3:转速计算不准确
- 确认编码盘孔数设置正确
- 检查定时器周期是否准确
- 测试高转速时是否出现脉冲丢失(可能需要更快的MCU或优化代码)
5.3 性能优化建议
- 对于高速测量,可以使用定时器的输入捕获功能代替外部中断
- 使用DMA传输脉冲计数结果,减少CPU开销
- 添加滑动平均滤波算法,使转速显示更稳定
- 对于电池供电设备,可以间歇性唤醒测量以节省功耗
6. 扩展应用思路
这个基础测速方案可以扩展出许多有趣的应用:
- 智能车里程计:结合轮径计算行驶距离
- 风速测量仪:使用小型风车作为编码盘
- 流量监测:在管道中安装叶轮测量液体流量
- 转速报警器:设置阈值触发报警或保护
- PID速度控制:作为闭环控制的反馈环节
我在一个农业无人机项目中就使用了类似的方案来监测喷洒泵的转速,通过PWM动态调整泵速保持恒定的喷洒量。实际测试表明,这种光电测速方案在3000RPM范围内误差可以控制在±1%以内,完全满足大多数工业应用的需求。