别再死磕内部时钟了!用STM32F103C8T6的ETR外部时钟做个红外计数器(附完整代码)
STM32F103C8T6实战:用ETR外部时钟打造高精度红外计数器
在工业自动化、智能仓储和流水线管理中,物体计数是一个基础但关键的功能。传统方案往往依赖软件中断计数,但在高速或高精度场景下容易丢失脉冲。STM32的ETR(External Trigger)外部时钟模式为解决这一问题提供了硬件级方案——本文将带你从电路设计到代码实现,完成一个基于红外对射传感器的计数器系统。
1. 为什么ETR模式更适合计数场景?
1.1 内部时钟的局限性
当使用STM32内部时钟进行计数时,通常需要这样处理红外信号:
// 传统中断计数方式示例 void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0) != RESET) { counter++; EXTI_ClearITPendingBit(EXTI_Line0); } }这种方式存在三个明显缺陷:
- CPU依赖性强:每个脉冲都需要触发中断
- 高频响应差:超过1kHz的信号可能丢失
- 功耗高:持续唤醒内核影响低功耗设计
1.2 ETR的硬件优势
ETR模式通过定时器硬件直接处理外部信号:
- 零CPU干预:计数由定时器硬件自动完成
- 超高响应:支持最高72MHz的输入信号(经分频后)
- 灵活配置:支持上升沿/下降沿触发、数字滤波
实测对比:在1MHz方波输入时,中断方式丢失率>90%,而ETR模式可100%准确计数
2. 硬件设计关键要点
2.1 传感器选型与电路
推荐使用槽型光电传感器(如EE-SX670):
VCC(3.3V) | [R1] 10K | +-----> PA0(ETR输入) | [传感器] | GND参数选择:
- 工作电压:3.3V兼容
- 输出类型:数字输出(OC门)
- 响应时间:<10μs
2.2 ETR引脚配置原则
STM32F103C8T6的ETR引脚对应关系:
| 定时器 | ETR引脚 | 复用功能 |
|---|---|---|
| TIM1 | PA12 | TIM1_ETR |
| TIM2 | PA0 | TIM2_ETR |
| TIM3 | PD2 | TIM3_ETR(仅大容量) |
| TIM4 | PE0 | TIM4_ETR(仅大容量) |
注意:C8T6属于中等容量型号,实际可用ETR引脚为PA0和PA12
3. 软件配置全流程
3.1 初始化代码分解
void TIM_ETR_Init(void) { // 1. 时钟使能 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 2. GPIO配置(浮空输入模式) GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); // 3. ETR模式配置 TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F); // 4. 时基单元配置 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_TimeBaseInitStruct.TIM_Period = 0xFFFF; // 最大计数值 TIM_TimeBaseInitStruct.TIM_Prescaler = 0; // 无分频 TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct); // 5. 使能计数器 TIM_Cmd(TIM2, ENABLE); }3.2 关键参数解析
TIM_ExtTRGPolarity:
TIM_ExtTRGPolarity_NonInverted表示上升沿计数TIM_ExtTRGPolarity_Inverted表示下降沿计数ExtTRGFilter:
值0x0F表示最大滤波时间(约240ns @72MHz)Period设置技巧:
当需要统计超过65535的计数时,可结合溢出中断:void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { overflow_count++; TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }
4. 高级应用技巧
4.1 脉冲频率测量
通过ETR模式可轻松实现频率计:
float Measure_Frequency(void) { uint32_t cnt1 = TIM_GetCounter(TIM2); Delay_ms(1000); // 精确延时1秒 uint32_t cnt2 = TIM_GetCounter(TIM2); return (cnt2 - cnt1) / 1.0; // 单位Hz }4.2 抗干扰设计
工业环境中的噪声可能引发误计数,推荐以下防护措施:
硬件滤波:
- 在ETR引脚添加100nF电容到地
- 使用施密特触发器整形信号
软件滤波:
TIM_ETRConfig(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
4.3 低功耗优化
ETR模式配合STM32的睡眠模式可实现超低功耗计数:
void Enter_LowPowerMode(void) { // 配置唤醒事件 TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_External1); TIM_SelectInputTrigger(TIM2, TIM_TS_ETRF); // 进入停止模式 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); }5. 实战:包装流水线计数器
某食品包装线需要统计每分钟的包装数量,系统要求:
- 计数范围:0-9999
- 显示更新频率:1Hz
- 工作环境:强电磁干扰
实现方案:
typedef struct { uint16_t current_count; uint16_t last_minute_count; uint8_t update_flag; } Counter_TypeDef; Counter_TypeDef counter; void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { counter.current_count += 65536; TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } } void SysTick_Handler(void) { static uint32_t ticks = 0; if(++ticks >= 1000) { ticks = 0; counter.last_minute_count = counter.current_count; counter.current_count = 0; counter.update_flag = 1; } }硬件连接注意事项:
- 传感器电源与MCU共地
- 信号线使用双绞线传输
- 在PA0与GND之间并联TVS二极管
调试中发现,当传送带空转时红外传感器可能产生抖动信号。最终通过以下配置解决:
TIM_ETRConfig(TIM2, TIM_ExtTRGPSC_DIV8, // 8分频 TIM_ExtTRGPolarity_NonInverted, 0x0F);