STM32 1200高速计数器与编码器编程实战:从基础配置到工业级应用

1次阅读
没有评论

共计 3041 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

背景痛点

在工业自动化设备中,经常需要对电机编码器或高速脉冲信号进行精确计数。传统做法是用外部中断或轮询 GPIO,但在 1200Hz 及以上频率时,常遇到两个典型问题:

STM32 1200 高速计数器与编码器编程实战:从基础配置到工业级应用

  • 信号抖动导致的误计数:机械振动或电磁干扰会让脉冲边沿产生毛刺
  • CPU 负载导致的丢脉冲:当系统处理其他任务时,可能错过高速脉冲的跳变沿

我曾在一个包装机项目中使用普通 IO 中断计数,结果实际产量总是比理论值少 3% 左右。通过逻辑分析仪抓包才发现,当传送带高速运行时,部分脉冲被 MCU 漏掉了。

硬件选型

面对高速计数需求,开发者通常有两种选择:

  1. 外部专用计数芯片(如 HCTL-2022)
  2. 优点:自带 32 位计数器,不占用 CPU 资源
  3. 缺点:增加 BOM 成本,需要额外通信接口读取计数值

  4. STM32 内置定时器

  5. 优点:零额外成本,灵活配置计数模式
  6. 挑战:需要合理配置时钟树和滤波参数

经过实测,STM32F103 的 TIM2 定时器在 72MHz 主频下,配合输入捕获滤波器,完全能稳定处理 1200Hz 信号。下面分享具体实现方法。

核心实现

定时器基础配置

  1. 时钟配置:确保 APB1 总线时钟达到最高频率(通常 36MHz 经过倍频到 72MHz)
  2. 分频设置:TIMx_PSC(Prescaler)设为 0,让计数器每个时钟周期都计数
  3. 重载值:TIMx_ARR(Auto-reload Register)设为 65535(16 位定时器最大值)
// TIM2 基础配置结构体
TIM_HandleTypeDef htim2;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;      // 无预分频
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFF;    // ARR 最大值
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htim2);

输入捕获配置关键点

  • 滤波器设置:根据信号特性选择合适采样频率
  • 示例:8 次采样中 5 次一致才触发(TIM_ICPSC_DIV8, TIM_IC_FILTER_5)
  • 边沿检测:通常选择上升沿触发
TIM_IC_InitTypeDef sConfigIC;
sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV8;      // 8 分频采样
sConfigIC.ICFilter = 5;                      // 5/ 8 滤波
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);

中断安全保护

在中断服务函数中读取计数值时,必须防止主程序同时修改计数器。推荐做法:

  1. 使用 __HAL_LOCK(htim) 进行硬件锁
  2. 临界区操作前关闭全局中断
  3. 对于频繁的中断,考虑使用环形缓冲区暂存数据
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM2)
    {__disable_irq();  // 进入临界区
        uint16_t count = TIM2->CNT;
        __enable_irq();   // 退出临界区

        // 处理计数值...
    }
}

性能优化

延迟实测对比

使用 1000Hz 方波信号测试不同实现方案的响应延迟(示波器测量从信号跳变到计数器更新的时间差):

实现方式 平均延迟 最大抖动
GPIO 轮询 15μs ±8μs
外部中断 6μs ±3μs
定时器输入捕获 2μs ±0.5μs

GPIO 速度影响

测试不同 GPIO 配置对信号捕捉的影响(信号频率 1200Hz):

GPIO_Mode 误触发次数 / 分钟
低速(10MHz) 127
中速(50MHz) 42
高速(100MHz) 3

结论:至少配置为 50MHz 以上输出速度

避坑指南

PCB 布局建议

  • 编码器信号线走差分对(如可用 A +/A- 信号)
  • 在 MCU 引脚附近放置 100nF 去耦电容
  • 避免计数信号线与电机电源线平行走线

计数器溢出处理

当脉冲频率×运行时间可能超过 65535 时,需要扩展为 32 位计数器:

volatile uint32_t totalCount = 0;
uint16_t lastCount = 0;

void UpdateCount() 
{
    uint16_t current = TIM2->CNT;
    if(current < lastCount) {totalCount += 65536 - lastCount + current;} else {totalCount += current - lastCount;}
    lastCount = current;
}

完整代码示例

// 在 main.c 中添加
void MX_TIM2_Init(void)
{TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_IC_InitTypeDef sConfigIC = {0};

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 0;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 65535;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim2);

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);

    HAL_TIM_IC_Init(&htim2);

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);

    sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING;
    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler = TIM_ICPSC_DIV8;
    sConfigIC.ICFilter = 5;
    HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);
}

// 启动计数
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);

延伸思考

对于更高端的应用场景,可以尝试以下进阶方案:

  1. 编码器接口模式
  2. 配置 TIMx_SMCR 寄存器为编码器模式
  3. 同时接入 A / B 相信号实现正反转判断

  4. DMA 传输计数

  5. 将定时器的 CNT 寄存器通过 DMA 周期性传输到内存
  6. 完全解放 CPU,适合 1MHz 以上超高频信号

  7. 多定时器级联

  8. 用 TIMx 作为从定时器,扩展计数位宽
  9. 实现 32 位甚至 64 位计数器

通过本文介绍的方法,我们在贴片机项目中将计数误差控制在 0.01% 以内。希望这些实战经验对你有帮助!

正文完
 0
评论(没有评论)