STM32F103编码器接口开发实战:从原理到调试的完整指南 📅 发布时间:2026/9/7 10:33:48 👁 浏览次数: 简介面向STM32F103嵌入式开发者的增量式编码器程序资源用于解决电机控制、机器人定位、自动化设备等场景中的位置与速度采集难题适合正在学习定时器编码器接口的初中级开发者。压缩包共934个文件以C源码和头文件为主同时包含启动汇编文件、链接脚本、IAR与Keil工程配置文件、文本说明文档并集成CMSIS/DSP数学库可直接导入工程编译或按需裁剪移植。资源包大小约9.99MB文件组织结构完整清晰。已有2329人学习下载。内容覆盖TIM2、TIM3、TIM4定时器的编码器模式配置预分频器与计数器周期设定A相B相输入捕获以及更新中断处理并提供旋转方向判断、位置计数、软件滤波等实现思路针对低速抖动、高速计数溢出等问题资料中还给出倍频处理与DMA传输等补充方案可帮助读者深入理解STM32硬件定时器特性与编码器信号解析流程有效缩短运动控制模块的开发调试周期。 搞STM32F103的朋友十有八九迟早要跟编码器打交道。不管是做小车测速、云台角度反馈还是小型数控滑台的位置闭环编码器都是最常用的传感器之一。这篇文章就围绕我实际调试过的STM32F103编码器程序把硬件原理、寄存器配置、CubeMX设置、代码实现和调试踩坑完整过一遍希望能给正在折腾编码器的你省点时间。我不会写那种“复制粘贴就能跑”的缝合代码重点放在每个参数为什么这么设、每个寄存器位到底做了什么以及真机调试时容易掉进去的坑。1. 编码器接口方案选型为什么我直接选定时器编码器模式1.1 增量式编码器输出信号与判断逻辑增量式编码器输出的核心信号是两路相位相差90度的方波通常叫A相和B相。电机正转时A相超前B相90度反转时B相超前A相90度。所以系统只要检测这两路信号的相位关系就能同时知道两个关键信息当前转了多远以及转动的方向。有些编码器还会输出Z相零位信号每转一圈输出一个脉冲用于找机械原点。但在基础的测速和角度测量场景里AB两相已经够用Z相一般留到做绝对值定位时才用到。常见的增量编码器有200线、400线、500线、1024线等规格“线数”指编码器转一圈A相输出的脉冲个数而经过STM32定时器4倍频之后实际计数值会是线数的4倍这一点在下文计算转速时会重点展开。1.2 手写外部中断解码的缺点与硬件编码器模式的优势刚接触编码器时很多人第一反应是用外部中断把A相接EXTIB相接普通GPIO在A相中断里读B相电平判断方向。这个思路在低速、低线数场景下确实能跑但存在两个硬伤。第一是CPU占用率。编码器转速一高比如电机带着2000线编码器跑到3000转/分A相频率就是100kHz意味着每秒10万次外部中断。如果中断函数里还要做方向判断、计数累加和业务逻辑STM32F103这颗72MHz的Cortex-M3会相当吃力其他任务基本别想干了。第二是容易丢脉冲。外部中断的响应需要时间如果A相脉冲间隔小于中断响应时间脉冲就被漏掉了计数自然就不准。而STM32的定时器编码器模式是纯硬件逻辑干活A、B相直接接到定时器的两个输入捕获通道硬件自动根据两路信号的边沿顺序判断方向并完成加减计数整个过程不占用CPU。CPU只在需要的时候读一下计数值就行这就是我最终选择硬件编码器模式的原因。注意STM32F103的TIM1、TIM2、TIM3、TIM4都支持编码器模式。对于只有一个编码器的简单场景任意选一个空闲定时器即可但如果后续还想做第二个编码器就得提前规划好定时器资源分配。1.3 适合的硬件连接方式编码器输出一般有差分输出、推挽输出和开集开路输出三种。STM32F103的编码器接口输入需要接上拉电阻所以最常见的方式是编码器A相接定时器CH1B相接CH2VCC接3.3V或5V看编码器规格GND共地。如果编码器是开集输出外部一定要加上拉电阻否则波形边沿会非常差直接导致计数抖动。很多朋友在调试时发现计数乱跳第一怀疑对象就应该是硬件波形问题而不是代码问题。2. 编码器模式的原理与关键寄存器位拆解2.1 方向自动判断的硬件逻辑STM32的定时器编码器模式本质上是一个“带方向感的计数器”。它内部有两个输入捕获通道TI1和TI2分别对应CH1和CH2引脚。在编码器模式下这两个通道不再做普通的输入捕获而是被当成正交解码器的输入源。编码器模式的核心逻辑是硬件实时监测TI1和TI2的边沿跳变如果检测到某个边沿时另一路信号处于高电平就向上计数正方向反之就向下计数反方向。这个逻辑由硬件自动完成不需要软件参与判断也不需要中断配合。具体的工作模式由TIMx_SMCR寄存器的SMS位决定SMS001编码器模式1只在TI1的边沿计数1倍频SMS010编码器模式2只在TI2的边沿计数1倍频SMS011编码器模式3在TI1和TI2的边沿都计数4倍频我最常用的是模式3也就是4倍频模式理由很简单同样的编码器线数4倍频能获得更高的分辨率。比如200线的编码器4倍频后每圈就是800个计数角度分辨率能到0.45度左右对大多数测速场景来说足够用了。2.2 计数方向由谁决定方向判断不光跟SMS位有关还跟TIMx_CCER寄存器的CC1P和CC2P位输入极性有关。这两位的组合决定了“什么边沿算有效边沿”因此会影响方向判断的结果。举个例子如果CC1P0且CC2P0上升沿有效那么当TI1上升沿时TI2为高电平计数器向上计数但如果你把CC1P改成1下降沿有效那同样的物理转动方向可能会导致计数器反向计数。所以方向反了的时候最简单的解决办法就是交换AB相的物理接线或者把CC1P、CC2P反过来设置。交换接线当然更直观但有时候编码器线已经焊死不方便改动那就用软件改极性。2.3 输入滤波器的采样频率计算编码器接口还有一个很容易被忽视但非常重要的参数输入滤波器。它的作用是对输入信号进行数字滤波防止外部毛刺干扰引发误计数。滤波器的配置位在TIMx_CCMR1寄存器的IC1F和IC2F字段。滤波器不是随便填个数字就行。它的工作原理是以某个采样频率对输入信号进行连续采样只有连续N次采到的电平一致时才认为信号电平有效。采样频率的计算公式是fSampling fTimCK / 2^(N1)其中N是ICxF配置值。比如STM32F103的定时器时钟是72MHz如果ICxF配置为10二进制1010则采样频率约为72MHz / 2048 ≈ 35.15kHz。也就是说宽度小于1/35.15k≈28.5微秒的毛刺会被滤掉。这个参数在长线传输、电机电刷火花干扰、编码器供电不干净的场合非常有用。但是也别一味调大滤波器会引入信号延迟导致高速计数时跟不上真实脉冲。我一般先设0或4等实测发现误计数再加。3. 实操CubeMX初始化配置与代码实现3.1 CubeMX图形化配置步骤用STM32CubeMX做初始化能省不少事但前提是你要知道每一项配置背后的含义。以下是我常用的配置流程以TIM2为例在Pinout页面找到TIM2把Channel1和Channel2的模式都设为Encoder Mode。注意编码器模式下CH1和CH2必须同时配置为Encoder Mode不能只开一路。进入Parameter Settings把Prescaler设为0。预分频器在编码器模式下一般不做分频因为我们要的是原始脉冲计数分频会降低分辨率。Counter Period设为6553516位定时器最大值。这里有个小技巧如果编码器单圈脉冲数比较少也可以直接把ARR设成“单圈计数值-1”比如4倍频后单圈2000个计数就设为1999。这样计数器溢出时正好是一圈做角度换算更方便。Encoder Mode选TI1 and TI2即4倍频模式。Input Filter根据实际干扰情况调整先给个4试跑。使能定时器全局中断如果要做溢出处理的话NVIC里把TIM2中断优先级设好。3.2 HAL库初始化代码CubeMX生成的HAL库代码大致长这样/* 编码器模式初始化结构体 */ TIM_Encoder_InitTypeDef sEncoderConfig {0}; sEncoderConfig.EncoderMode TIM_ENCODERMODE_TI12; /* 4倍频模式 */ sEncoderConfig.IC1Polarity TIM_ICPOLARITY_RISING; /* TI1上升沿有效 */ sEncoderConfig.IC1Selection TIM_ICSELECTION_DIRECTTI; sEncoderConfig.IC1Prescaler TIM_ICPSC_DIV1; sEncoderConfig.IC1Filter 4; /* 输入滤波器 */ sEncoderConfig.IC2Polarity TIM_ICPOLARITY_RISING; /* TI2上升沿有效 */ sEncoderConfig.IC2Selection TIM_ICSELECTION_DIRECTTI; sEncoderConfig.IC2Prescaler TIM_ICPSC_DIV1; sEncoderConfig.IC2Filter 4; 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_Encoder_Init(htim2, sEncoderConfig);初始化完成后在主循环里启动编码器接口HAL_TIM_Encoder_Start(htim2, TIM_CHANNEL_ALL);这一步很多人容易漏。CubeMX只生成了初始化代码并不会自动启动编码器计数必须手动调用HAL_TIM_Encoder_Start。3.3 读取计数与转速/位移计算编码器数值的读取非常简单直接操作寄存器即可int16_t encoder_count (int16_t)(TIM2-CNT);这里必须用int16_t进行强制转换。原因在于TIM2-CNT本身是16位无符号寄存器范围0到65535但编码器是有方向的我们需要用有符号数来表示正反转。把无符号数强转为int16_t后0到32767表示正方向计数32768到65535会被解释为-32768到-1正好对应反方向计数。这是编码器程序中最基础也最关键的细节。读取到计数值后转速的计算公式如下转速(RPM) 单位时间内的脉冲变化量 / (编码器线数 × 4) × 60000 / 采样间隔(ms)比如200线编码器4倍频后每圈800个计数100ms采样周期内读数增加了400那么转速就是400 / 800 × 60000 / 100 300 RPM。位移计算类似角度(度) 总脉冲数 / (编码器线数 × 4) × 360如果加了减速器比如1:30的减速比那么输出轴每转一圈电机轴要转30圈脉冲数就得乘以30倍。3.4 定时器溢出中断扩展计数当计数器ARR设为65535时长时间运行或者高速旋转时16位计数器很容易溢出从65535回到0或者从0下溢到65535。如果只读一次CNT不做溢出处理得到的数值就会出现跳变。处理方案是开启定时器更新中断在中断里用一个32位变量做累加volatile int32_t encoder_total 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); /* 判断是上溢还是下溢 */ if ((TIM2-CR1 TIM_CR1_DIR) 0) { encoder_total 65536; /* 向上溢出 */ } else { encoder_total - 65536; /* 向下溢出 */ } } }有了这个累加变量实际位置就是encoder_total TIM2-CNT的当前读数。这种“16位硬件计数32位软件扩展”的组合是工程上最常用的办法既保证了计数精度又不占用过多CPU资源。4. 常见问题与排查技巧实录4.1 计数不稳定、乱跳这个问题我遇到过太多次了。一般来说最先应该怀疑的是硬件不是软件。用示波器看A相和B相的波形重点观察上升沿是否干净有没有振铃和毛刺。我踩过的坑包括编码器供电接了开关电源没加滤波电容纹波把信号弄得一团糟还有一次是编码器线束跟电机动力线绑在一起走线电机一启动计数就开始乱跳把编码器线单独走线、加磁环后问题立刻消失。软件侧的对应手段就是加大输入滤波器的ICxF值。如果ICxF4还不行可以逐步调到8、10甚至12。但记得滤波器加大会带来延迟高速场景下要权衡。4.2 方向判断反了方向反了是最容易处理的故障。物理上最简单粗暴的方法是把A相和B相交换一下接线。如果不想动线就把CC1P和CC2P的极性取反效果一样。我用过的另一种办法是读取计数值后软件取反int16_t count -(int16_t)(TIM2-CNT)用于临时验证方向逻辑确认无误后再改硬件或极性配置。4.3 编码器接口测不到任何脉冲先确认编码器供电是否正常AB相是否有电平变化。如果编码器是集电极开路输出别忘了上拉电阻。另外检查CubeMX里CH1和CH2是否都正确配置为Encoder Mode模式以及HAL_TIM_Encoder_Start有没有被调用。经常有人初始化完就以为在计数了实际上计数根本没启动。还要注意编码器模式下定时器的通道引脚不是普通GPIOCubeMX会自动重映射但如果你的板子引脚冲突就需要手动检查重映射关系看是否被其他外设占用。4.4 滤波器参数和实际波形的对应关系最后再分享一个调试小技巧。滤波器ICxF的值跟“能滤除的最小毛刺宽度”有一个对应关系设置值越大能滤掉的毛刺越宽但输入信号的最高频率也越低。在电机高速运行场景中如果发现编码器读数比实际转速偏小很可能是滤波器把有效边沿也滤掉了一部分。此时应该调小滤波值或者干脆关掉滤波优先保证信号的完整性。4.5 舵机或伺服编码器那排开关是什么设置顺带说一句很多人搜索“伺服编码器那排开关”其实是在问伺服驱动器侧面的拨码开关。那通常是用来设置编码器分辨率、电子齿轮比或通信地址的跟STM32这边的编码器接入没什么关系。但如果你在调试过程中发现伺服电机输出的A/B相脉冲频率跟预期不一样不妨检查一下伺服驱动器的脉冲输出倍频设置那边设错了后面程序再对也没用。5. 编码器程序的功能扩展方向如果编码器基础测速程序已经稳定跑起来了我建议往这几个方向做扩展一是加入闭环控制。把编码器读数作为反馈量用PID算法控制电机转速或位置这是做平衡小车、机械臂、云台的必经之路。编码器接口程序本身已经给了你足够精准的反馈剩下的就是调PID参数。二是做零点校准。很多系统要求上电后先找到机械原点这时候Z相信号就派上用场了。可以配置一个外部中断检测Z相脉冲让系统在回零过程中一旦收到Z相脉冲就立即停止精度比单纯靠限位开关高很多。三是移植到FreeRTOS等实时系统。编码器计数读取完全可以放在一个低优先级任务里周期性地读取和计算而定时器硬件继续在后台默默计数。这种架构下CPU占用率几乎为零系统还有余力处理通信、显示等任务。6. 写在最后的经验总结搞了大半年编码器相关项目我最大的体会是编码器程序本身并不复杂复杂的是外围的工程问题。信号干扰、地线环路、供电纹波、滤波器参数选择任何一个环节掉链子代码写得再漂亮也白搭。所以建议先从硬件查起用示波器把波形看明白了再动手写代码能少走很多弯路。另一个心得是调试时善用printf和串口。我习惯在调试阶段每50ms打印一次当前计数值和计算出的转速用手慢慢转动编码器轴观察数值变化是否符合预期。确认方向和倍频无误后再接入电机做动态测试。这比直接接电机调试要安全得多也能更快定位问题所在。最后一个建议代码里尽量把编码器线数、减速比、采样周期这些参数做成宏定义方便后续换不同规格的编码器或电机时快速调整。前期多花几分钟做结构化后面调试就省几个小时。本文还有配套的精品资源点击获取