资讯详情

STM32F103编码器程序:定时器正交解码实现位置与速度测量

📅 2026/10/15 17:17:42 | 华诺云谱 👁 阅读
STM32F103编码器程序:定时器正交解码实现位置与速度测量
简介STM32F103编码器程序是一份面向嵌入式开发者的工程源码包以增量式编码器为核心解决信号采集、方向判读与位置计数问题。代码基于STM32标准外设库编写覆盖定时器输入捕获配置、编码器模式初始化、中断服务函数及滤波思路适合电机控制、机器人定位和自动化设备等场景。资源共934个文件以C源文件和H头文件为主辅以汇编启动文件、工程配置、脚本、链接文件与说明文档RAR压缩包约9.99MB结构清晰便于查阅。目前已有2330人学习下载适合有一定单片机基础、正在调试编码器读数或希望提升运动控制精度的开发者。通过阅读这套工程可获得硬件连接参考、配置示例和调试要点结合自身硬件调整分频、倍频与滤波策略能有效减少丢步和抖动干扰。1. STM32F103 编码器程序从正交信号到位置与速度的一条最短路径做小车、云台或转台时编码器读数忽正忽负、高速丢脉冲、反转计数错乱——这类问题在STM32F103上九成不是编码器坏了而是定时器的编码器接口模式没有用对。F103 的 TIM2/TIM3/TIM4 自带硬件正交解码A/B 两相直接接定时器通道配置成编码器模式后方向由硬件判计数由硬件记CPU 只负责定时读 CNT 和做溢出扩展。这套资源就是一份最小可跑的编码器工程从引脚映射到初始化代码从 16 位 CNT 回绕到 32 位位置扩展再到 M 法测速和常见翻车场景。适合做电机闭环的开发者、控制类课设复现也适合从串口读数阶段往闭环阶段走的入门者照着抄。2. 编码器模式原理与选型为什么用 TIM 而不是 EXTI2.1 正交解码的硬件逻辑边沿计数与方向判定增量式编码器输出 A 相和 B 相两路方波相位差 90 度。正转时 A 超前 B反转时 B 超前 A。要解出有没有走一步和往哪个方向走只需要抓一个组合某一相出现有效边沿时另一相的电平是高还是低。这个组合逻辑用外部中断做就是每次 A 相上升沿进中断在中断服务程序里读一次 B 相电平再决定位置加一还是减一。低速演示没问题转速一起来中断响应延迟和沿抖动会叠加出错。我在早期项目里就遇到过一次手转读数正常电机一带负载正转偶尔变成负数查了半天发现是 EXTI 优先级跟毛刺在互相干扰。从那以后编码器计数我优先走定时器硬件不再用 EXTI 做正交判相。STM32F103 的 TIM2/TIM3/TIM4 内置编码器接口CH1 输入映射为 TI1CH2 输入映射为 TI2。编码器模式下硬件逻辑在每个有效边沿自动比较另一通道电平CNT 寄存器随之加一或减一不进中断、不占 CPU。数据手册里这个机制叫编码器接口模式对应 TIM_SMCR 寄存器的 SMS[2:0] 字段一共有三种计数沿配置SMS 值计数来源分辨率倍数工程里什么时候用001TI1 边沿1 倍频只需要方向、不追求分辨率010TI2 边沿1 倍频同上换了一路输入011TI1 与 TI2 全部边沿4 倍频默认推荐分辨率最高工程里默认用 011 即 4 倍频。同样是 360 线编码器1 倍频一圈 360 个计数4 倍频一圈 1440 个计数分辨率直接乘 4而且四个沿都参与计数对 A/B 占空比不敏感。代价是计数频率翻四倍16 位 CNT 回绕更快溢出扩展必须跟上这部分在第 4 章展开。标准库里选模式就一行调用TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, // 011: 双沿计数, 4倍频 TIM_ICPolarity_Rising, // TI1 极性 TIM_ICPolarity_Rising // TI2 极性 );逻辑说明TIM_EncoderMode_TI12 对应 SMS011使能双沿计数。后两个参数是输入捕获极性默认 Rising。如果 A/B 两相实际接反了把这两个参数都改成 TIM_ICPolarity_Falling计数方向就会反过来等效于交换 A/B 两根线不用改硬件。参数说明模式参数还有 TIM_EncoderMode_TI1 和 TIM_EncoderMode_TI2对应 1 倍频只在特殊分辨率需求下用。极性参数是软件层面解决接线反相的后悔药比起拆电机线、剥线头改这一行快得多。2.2 倍频与线数的匹配360 线、512 线、1000 线怎么配先澄清一个常见误解CNT 里存的只是脉冲累计值不是圈数更不是角度。物理单位要靠软件换算换算公式是一圈增量 编码器线数 P/R × 倍频倍数 × 减速比常用编码器线数在不同倍频下每圈计数如下编码器线数1 倍频每圈计数2 倍频4 倍频360 线3607201440512 线512102420481000 线100020004000带减速箱的电机还要再乘减速比。比如电机本体 1000 线、减速比 1:30、4 倍频下输出轴转一圈需要 1000 × 4 × 30 120000 个计数。这个数字直接决定溢出扩展的频度每秒转一圈就产生 12 万次计数16 位 CNT 半秒多就回绕一次不做 32 位扩展根本没法用。我的习惯是把这些换算常量收敛在头文件里不要散写在多处。换编码器、换减速比时只改一处#define ENCODER_PPR 1000U // 编码器线数 #define ENCODER_MULT 4U // 4倍频 #define GEAR_RATIO 30U // 减速比 #define COUNTS_PER_REV (ENCODER_PPR * ENCODER_MULT * GEAR_RATIO) #define DEG_PER_COUNT (360.0f / (float)COUNTS_PER_REV)逻辑说明COUNTS_PER_REV 是输出轴转一圈对应的总计数DEG_PER_COUNT 是每个计数对应的角度增量。角度换算时直接用计数值乘以 DEG_PER_COUNT 即可避免每处都写除法。参数说明ENCODER_PPR 是编码器厂商标称线数不是倍频后的数。ENCODER_MULT 与第 2.1 节 TIM_EncoderMode_TI12 对应如果改成 1 倍频这里同步改成 1。GEAR_RATIO 是输出轴与电机轴的减速比不带减速箱写 1。2.3 EXTI 软判相只适合低速演示不适合闭环既然说 EXTI 容易踩坑就把反面代码摆出来对比着看。网上流传的最简单编码器程序大多是这种写法// 不推荐的做法A相上升沿触发 EXTIB相电平判方向 void EXTI0_IRQHandler(void) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)) // 读B相电平 { position; // B相为高, 判定正转 } else { position--; // B相为低, 判定反转 } }逻辑说明这个逻辑在低速下是正确的A 相上升沿时 B 相电平正好反映方向。问题在于 A 相上升沿附近如果 B 相存在毛刺或者中断响应延迟导致读到的电平已经是边沿后的下一拍方向就会判反。转速越高、中断优先级越低出错概率越大。参数说明这个方案里 position 必须用带符号类型否则反转时减一变成 65535串口打印出来就是一个疯狂增大的正数。另一点是边沿选择用上升沿还是下降沿只影响采样点位置不改变方向判定的本质缺陷。所以这个方案我只建议在手动旋转的演示项目里用电机闭环直接上 TIM 编码器模式。3. 最小可跑工程GPIO 映射、初始化与位置读取3.1 引脚映射、JTAG 复用与上拉选择STM32F103 的编码器模式优先用默认引脚映射避免重映射引入额外配置定时器CH1A 相CH2B 相总线TIM2PA0PA1APB1TIM3PA6PA7APB1TIM4PB6PB7APB1这三个组合里最常用 TIM2原因很实际PA0/PA1 没有 JTAG 占用不需要关 SWJ 映射拿来就能用。TIM4 的 PB6/PB7 也不涉及 JTAG做电机板时经常和 PWM 输出引脚排在一起布线方便。要避开的是 PA15、PB3、PB4 这几个脚。PA15 是 JTDIPB3 是 JTDOPB4 是 JNTRST默认被 JTAG 占用直接当普通 IO 用会失灵。如果板子已经把编码器接到了这些引脚使用前要关闭 JTAGRCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 重映射功能时钟 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); // 关闭JTAG, 保留SWD逻辑说明GPIO_Remap_SWJ_JTAGDisable 关闭 JTAG 后PA15/PB3/PB4 恢复普通 IO 功能同时 SWCLK 和 SWDIO 仍然可用下载调试不受影响。参数说明如果项目里还要用 SWO 调试口需要改成 GPIO_Remap_SWJ_Disable这个宏会把 SWO 也释放掉按需选择。上拉选择方面多数增量编码器内部输出是开漏或推挽开路结构MCU 侧最好启用内部上拉。GPIO 配置成 GPIO_Mode_IPU省掉外部上拉电阻。长线传输超过 30cm建议外部再加 4.7kΩ 上拉到 3.3V原因在第 5 章避坑里细说。3.2 标准库初始化代码从时钟到编码器接口以 TIM2、PA0/PA1 为例标准库完整初始化函数如下void Encoder_TIM2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; // 1. 开启时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // 2. PA0/PA1 配置为输入, 内部上拉 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 3. 时基: 预分频0, 自动重载0xFFFF TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); // 4. 编码器接口: 双沿计数 TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); // 5. 输入滤波, 滤掉毛刺 TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICFilter 0x0F; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInit(TIM2, TIM_ICInitStructure); // 6. 清空计数器并启动 TIM_SetCounter(TIM2, 0); TIM_Cmd(TIM2, ENABLE); }逻辑说明第 3 步里 TIM_Prescaler 必须为 0编码器模式下 CNT 直接对有效沿计数预分频器不参与改大了只会让溢出事件与 CNT 回绕不同步。TIM_Period 保持 0xFFFF不要在编码器模式下通过改小 ARR 来加快溢出那会直接丢计数。第 5 步的 TIM_ICFilter 是输入滤波采样次数0x0F 表示连续采样 15 个周期确认电平稳定能滤掉机械抖动引入的毛刺代价是引入约 0.2 微秒的边沿延迟对绝大多数编码器场景没有影响。参数说明TIM_ICFilter 取值范围 0 到 0x1F值越大滤波越强。对线数高、转速快的场合建议降低到 0x04 以下对线数低、现场干扰大的场合提高。这个值属于按现场调的参数没有绝对最优。初始化完成后 CNT 从 0 开始正转加计数反转减计数。编码器模式下 CNT 是 16 位无符号数减到 0 会跳到 0xFFFF这是后面要处理的回绕问题。3.3 读取位置与方向CNT 与 DIR 位读取当前原始计数值uint16_t Encoder_GetRawCount(void) { return (uint16_t)TIM2-CNT; }读取方向uint8_t Encoder_GetDirection(void) { return (TIM2-CR1 TIM_CR1_DIR) ? 1 : 0; // 1: 向下计数(反转) 0: 向上计数(正转) }逻辑说明编码器模式下 CR1 寄存器的 DIR 位由硬件自动更新向上计数时为 0向下计数时为 1。这个位可以用于逻辑判断比如方向变化时记录换向时刻。注意不推荐用 DIR 位做位置正负修正位置本身已经通过 CNT 增减体现方向了。参数说明DIR 位是 1 位只读状态初始化后第一次读取可能不稳定建议在系统上电 1 毫秒后再使用。CNT 是 16 位调试时用串口打印要小心直接打印 uint16_t 会显示 0 到 65535反转时从 0 减到 65535 是正常回绕不是故障。3.4 把计数值算成角度和圈数有了原始计数换算到物理量是这层float Encoder_GetAngle_Deg(void) { int32_t count Encoder_GetPosition(); // 32位扩展位置, 见第4章 return (float)count * DEG_PER_COUNT; } float Encoder_GetSpeed_Rpm(float delta_sec) { int32_t now Encoder_GetPosition(); static int32_t last 0; int32_t delta now - last; last now; return (float)delta / (float)COUNTS_PER_REV / delta_sec * 60.0f; }逻辑说明角度换算用乘法替代除法因为 DEG_PER_COUNT 已经预处理好。速度换算通过两次采样位置差除以时间窗口实现delta 为负数表示反转这个方式在固定时间窗口下就是后面要讲的 M 法测速。参数说明delta_sec 是调用周期用秒做单位。在定时中断里每 10ms 调用一次就传 0.01f。静态变量 last 是函数级保持工程上建议改成结构体或全局变量方便多路编码器复用。4. 高速不丢脉冲溢出中断、32 位位置扩展与读取竞态4.1 16 位 CNT 回绕转速一高就得面对CNT 是 16 位计数器最大计数值 65535。编码器模式下向上计数到 0xFFFF 后下一个沿会溢出回 0向下计数到 0 后会下溢回 0xFFFF溢出瞬间产生更新事件。如果中断处理不及时位置信息就丢了一整段。回绕速度跟编码器线数、倍频、转速直接相关。举两个实际场景360 线、4 倍频、电机 3000 转/分每秒计数 1440 × 50 72000CNT 约 0.9 秒回绕一次1000 线、4 倍频、减速比 1:30、输出轴 60 转/分每秒计数 4000 × 30 × 1 120000CNT 约 0.55 秒回绕一次第二种场景在云台、机械臂关节里很常见。位置一旦被截断轻则角度计算跳变重则 PID 积分直接飞掉。所以只要电机带减速箱溢出扩展就是必须项。4.2 溢出中断扩展位置硬件计数加软件进位扩展思路跟十进制进位一样CNT 负责段内计数软件维护一个 32 位全局变量负责进位。每次溢出时判断方向向上溢出就加 65536向下溢出就减 65536。volatile int32_t g_encoder_count 0; // 32位累计位置 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跳到0xFFFF { g_encoder_count - 65536; } else // 向上计数, 溢出从0xFFFF跳到0 { g_encoder_count 65536; } } }逻辑说明中断里不读 CNT只做加减进位这是刻意为之。CNT 的回绕由硬件完成软件在溢出发生的瞬间同步进位两者天然一致。方向用 DIR 位判断避免了用计数值符号推断方向带来的边界误差。65536 来自 ARR 1因为当前配置 ARR0xFFFF。参数说明g_encoder_count 必须声明为 volatile防止编译器优化后读取不到最新值。中断服务函数名称 TIM2_IRQHandler 是启动文件里固定好的不要自己改名。更新事件标志位必须在判断后立即清除否则中断退出后立刻再次进入形成死循环。使能更新中断的代码放在初始化函数末尾NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 2; // 低于电机控制中断 NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);逻辑说明抢占优先级建议比电机换相/PWM 中断低一级避免编码器进位打断高实时性控制。进位操作本身只执行几拍丢一点不影响大局。参数说明优先级具体数值要根据项目里已有中断分配来定原则是编码器中断不能让电机堵转检测这类任务延迟太久。有了 g_encoder_count 之后读取真实位置不能直接返回它因为两次溢出之间的段内增量还留在 CNT 里。合并函数见下一节。4.3 读取顺序的竞态先关中断再读直接返回 g_encoder_count 会漏掉最近一次溢出到当前时刻之间的增量。正确合并方式是 g_encoder_count 加当前 CNTint32_t Encoder_GetPosition(void) { int32_t pos; __disable_irq(); // 关中断, 防止读一半发生溢出 pos g_encoder_count (int32_t)(uint16_t)TIM2-CNT; __enable_irq(); return pos; }逻辑说明g_encoder_count 是已完成进位的粗计数CNT 是当前段内细计数相加得到完整的 32 位位置。举例CNT60000 时还没有溢出g_encoder_count0返回 60000溢出发生后 g_encoder_count65536CNT0返回 65536再转 1000 计数返回 66536。连续且无跳变。参数说明__disable_irq 是全局关中断如果在中断上下文调用会导致嵌套中断无法响应所以这个读取函数只在任务级代码里使用。相比用信号量或原子操作全局关中断在这个场景最简单因为临界区只有三四条指令关中断时间不超过 1 微秒。这里还有一个隐藏细节如果读取时 CNT 刚好在 0xFFFF 且下一个沿即将到来关中断挡住溢出位置会暂时停在当前值开中断后溢出中断执行下一次读取就正确。结果是最多偏差 1 个计数且不会累积实际工程完全可接受。5. 避坑编码器程序里最常翻车的五个场景5.1 转一圈计数差几个脉冲先查机械再查滤波现象360 线编码器 4 倍频手转 20 圈平均每圈 1438 到 1442偶尔出现 1430。原因这个现象有三种常见来源。机械间隙最常见联轴器或齿轮回差导致反向瞬间损失计数其次是信号占空比不是 50%某些沿间距不均匀硬件状态机偶尔漏采最后是 TIM_ICFilter 滤波值设太大把有效沿当毛刺滤掉了。解决按顺序排查。先手动缓慢正反转观察反向切换瞬间丢几个计数基本是机械回差拧紧联轴器。再用示波器看 A/B 波形是否干净、占空比是否 50%波形畸变就处理编码器供电和安装。最后软件调参把 TIM_ICFilter 从 0x0F 降到 0x02 试一轮。注意滤波值不是越大越好过滤波和欠滤波的表现都是计数不准。5.2 正反转读数都增加多半是无符号类型在捣鬼现象不管向哪边转串口打印的位置数值都在增大反转时数值也一路往上冲。原因这是最阴间的错误之一。位置变量声明成了 uint16_t 或 uint32_t反转时 CNT 从 0 变成 0xFFFF无符号数解释为 65535打印出来就是增大了。本质是负数被无符号类型截断了。解决位置相关变量全部改用 int32_t包括中断里的 g_encoder_count、读取接口的返回值、换算函数的入参。不要在读取后再强转成无符号类型。调试时串口打印用 %ld 而不是 %dST 的 printf 重定向默认对 int16_t 截断踩过这个坑的都知道打印值突然跳到 65535 有多困惑。5.3 悬空引脚读数乱跳内部上拉没开现象手不碰编码器串口读数每隔几十毫秒跳变 1 到 3 个计数方向随机。原因编码器内部输出级是开漏结构MCU 侧 GPIO 配置成浮空输入时引脚电平没有被稳定钳位微弱电磁噪声就能触发边沿。特别是编码器线比较长且没有终端处理时悬空引脚就像一根天线。解决GPIO 初始化从 GPIO_Mode_IN_FLOATING 改成 GPIO_Mode_IPU启用内部上拉。长线场景在编码器端和 MCU 端各加一个 4.7kΩ 上拉到 3.3V。改完再看读数绝大多数情况下乱跳直接消失。如果还在跳检查编码器外壳是否接地。5.4 电机通电后读数乱跳EMI 干扰要硬件软件一起治现象手转编码器读数稳定电机一上电读数立刻像抽风一样乱跳。原因电机电刷或 PWM 换相产生强烈的电磁干扰通过电源线、地线或空间辐射耦合进编码器信号线。这个在带直流电机的小车上几乎必现只是严重程度不同。解决分三层处理。第一层硬件编码器线换屏蔽双绞线屏蔽层单端接地编码器供电从电机电源分离出来单独用 LDO 降到 3.3V。第二层滤波编码器信号线上加 100nF 电容到地引脚侧形成低通同时把 TIM_ICFilter 加大到 0x0F 以上。第三层隔离如果干扰还是压不住换差分输出编码器MCU 侧通过比较器或电平转换芯片接收。注意滤波和速度的平衡滤波越大边沿延迟越大高速场景会丢沿所以先硬件治理再软件滤波。5.5 PB3/PB4 不工作还烧录失败JTAG 占用要先关闭现象把编码器 A/B 相接到 PB3/PB4引脚电平完全不受控制程序下载偶尔失败调试器连接不稳定。原因PB3 默认是 JTDOPB4 默认是 JNTRSTJTAG 功能在芯片上电后默认使能这两个引脚被调试逻辑占用普通 GPIO 操作无法控制电平。下载失败的典型表现是连接目标时报告 JTAG 通信错误。解决在初始化最前面关闭 JTAG 功能RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);逻辑说明关闭 JTAG 后 PA15/PB3/PB4 转换回普通 IOSWD 调试口不受影响。如果你的调试器用的是 SWD 模式这行代码加上后一切正常。如果用的是 JTAG 模式关了之后就连不上了需要用 SWD 下载一次把代码擦掉或改回。参数说明GPIO_Remap_SWJ_JTAGDisable 释放 PA15、PB3、PB4GPIO_Remap_SWJ_Disable 进一步释放 PB3 用作 SWO。只用到普通 IO 时建议用前者保留 SWO 调试输出。6. 进阶M 法测速、断电回零与编码器模式最后的细节6.1 M 法测速10ms 窗口读增量M 法测速本质是固定时间窗口内统计计数增量适合中高速场景。窗口太短低速时计数抖动占比大窗口太长速度反馈延迟大。工程上 10ms 是折中值。void Speed_Task_10ms(void) { int32_t now_pos Encoder_GetPosition(); static int32_t last_pos 0; int32_t delta now_pos - last_pos; last_pos now_pos; // 速度: 计数/秒 float speed_cps (float)delta * 100.0f; // 再换算成转/分 float speed_rpm speed_cps / (float)COUNTS_PER_REV * 60.0f; }逻辑说明delta 是 10ms 内的位置变化量乘以 100 得到每秒计数。speed_cps 除以 COUNTS_PER_REV 得到每秒圈数再乘 60 就是 RPM。delta 为负表示反转速度值带符号可以直接喂给 PID。参数说明这个任务放在定时器中断里调用或者放在 RTOS 的 10ms 周期任务里。不要用 delay 函数软延时循环时间抖动会让速度计算带上噪声。低速场景下如果 delta 经常为 0可以考虑把窗口拉长到 50ms或者换成 T 法测速。6.2 断电位置丢失增量编码器的回零方案增量编码器只有相对位置断电后重新上电当前位置不知道在哪。这不算程序 Bug是传感器原理决定的天花板。解决手段按成本递增第一用带 Z 相索引脉冲的编码器上电后低速转一圈捕捉 Z 脉冲作为机械零点。第二在机构上加限位开关或霍尔挡块上电后先运行到限位位置再清零。第三换绝对值编码器位置信息由编码器自身保存。配套软件上电清零函数void Encoder_ResetPosition(void) { __disable_irq(); g_encoder_count 0; TIM_SetCounter(TIM2, 0); __enable_irq(); }逻辑说明清零操作必须在关中断下完成否则清零过程中溢出中断插入会把残留计数值带入 g_encoder_count。TIM_SetCounter 在编码器模式下可以直接写 CNT硬件会在下一个沿恢复计数。6.3 最后的细节ARR 保持 0xFFFFPSC 必须为 0编码器模式有两个寄存器设置容易被忽略但它们直接决定 32 位扩展逻辑是否成立。第一个是 ARR。有开发者想让 CNT 更快溢出以增加进位频率把 ARR 改成比如 60000。后果是计数到 60000 就提前翻转回 0而硬件根本不认为这是溢出中断里的 g_encoder_count 不会进位位置直接丢 6000 段。更隐蔽的是997 线和 1000 线编码器在这种配置下表现完全不一致很难排查。ARR 固定在 0xFFFF让 CNT 走满 16 位自然回绕才是扩展逻辑的前提。第二个是 PSC。编码器模式下 CNT 的递增直接由输入沿驱动预分频器不参与计数但更新事件周期与 PSC 有关。一旦 PSC 不为 0更新中断与 CNT 回绕不同步进位时机错位位置扩展就出随机跳变。所以 PSC 保持 0。从那以后我每次配编码器工程都会强制走一遍检查先确认 ARR0xFFFF、PSC0再审一遍读取接口有没有关中断最后用方向盘逻辑手转几圈验证正反向。这三步花不了两分钟但能拦下九成的位置跳变问题。整套 stm32f103 编码器程序代码就在资源包里直接下下来对照调试希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑