news 2026/9/28 5:49:47

玩转EC11编码器:AB相相位差与双平台读取实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
玩转EC11编码器:AB相相位差与双平台读取实战

先别急着看代码,玩转EC11编码器,搞清楚AB相那点“相位差”的事儿比什么都重要。无论是在Arduino上写个旋转菜单,还是在STM32上搞个精准的电机调速旋钮,EC11都是最常用、最皮实、也最容易被忽视细节的器件。很多人直接把A、B两脚接上中断引脚,转起来能计数就以为完事了,结果一遇到抖动、反转或者高速旋转就丢步、乱跳,最后只能怀疑人生。这篇东西我不打算只给你两个能跑的例程就交差,那没意思。我会从示波器上看到的实际波形出发,一步一步带你把EC11的AB相相位关系、内部结构、消抖逻辑、双平台的不同读取策略以及高频脉冲丢失的坑都趟一遍。看完之后,你就能根据自己项目的实际需求,在Arduino和STM32之间随意迁移方案,而不是只会复制粘贴。

1. 内容整体设计与思路拆解

1.1 为什么要盯着波形图和相位差看,而不是只查数据手册

EC11编码器,本质上是一个机械式的增量旋转编码器,内部的核心是两个触点,对应输出A相和B相。当你旋转旋钮时,这两个触点会依次接通和断开,产生两个方波信号。关键在于,这两个方波不是同步的,而是存在一个固定的时间差,通常我们把A相和B相之间的这个错开关系叫“正交信号”,标准情况下相位差是90度。

这个90度相位差有什么用呢?直接决定了两个事情:第一,决定旋转方向;第二,决定你能否在高转速下不丢脉冲。用大白话来说,A相和B相就像是两个接力赛运动员,A跑在前头,B跟在后面,如果A先到终点(先跳变),那就是正转;如果B先到终点(先跳变),那就是反转。这个先后顺序,就是相位差最直观的体现。

但是,EC11毕竟是个机械器件,它内部不是光栅,而是金属弹片和固定触点。机械接触就带有天生的“抖动”(Bounce),这在示波器上看得清清楚楚:原本一个干净利落的上升沿,实际是连续好几组高低电平的毛刺。如果你把代码写成“检测到A相上升沿,加一”,那结果就是转一格,计数器能加上好几十。所以,真正的编码器读取方案,从硬件到软件,都要围绕“如何可靠地识别出90度相位差中那个有效的跳变沿”来设计。

我建议你先别急着搭电路,而是先用示波器或者逻辑分析仪去看一眼EC11转起来时的真实波形。我见过太多人,代码写得挺复杂,结果硬件上A、B两相压根没接对,导致数据完全对不上,最后还以为是编码器坏了。所以,把原理和波形看明白了,后面所有问题都迎刃而解。

1.2 方案选型对比:Arduino中断法、轮询法、STM32定时器编码器模式

目标明确了之后,选方案就顺理成章。在Arduino平台上,最常见的是利用attachInterrupt()中断函数,在A相或B相的跳变沿触发回调,然后在回调里读取另一相的电平来判断方向。这个方案的优势是实时性强,代码简单,适合转速不太高的交互场景,比如音量旋钮、菜单选择。缺点是单片机在中断里花费的时间不能太长,如果主程序里还有大量耗时操作(比如驱动OLED屏幕、跑算法),中断响应延迟就容易导致脉冲丢失。

另一种Arduino方案是轮询,也就是在loop()里不断读取A、B两相电平,记录上一次的状态,通过状态变化判断旋转。这种方法省去了中断资源,但实时性受主循环周期影响,如果你主循环里有delay(100),那这100毫秒里转动的脉冲全都会被漏掉。实测下来,轮询法只适合对实时性完全没有要求的旋钮场景。

而STM32平台则完全不同。STM32的定时器硬件自带编码器模式,可以直接把TIMx的CH1和CH2引脚配置成编码器输入,硬件自己根据A、B相的相位关系进行加减计数。这种方式不需要占用CPU任何中断负载,计数频率可以做到很高(几兆赫兹级别),而且带有多级滤波(数字滤波器)功能,非常适合电机测速、高性能旋钮等需要精准捕捉每一个脉冲的场合。

从我的个人经验来看,如果只是做个人项目或者交互原型,Arduino中断法完全够用。但如果涉及到电机闭环控制、需要精确到每一圈的位置反馈,那STM32的定时器编码器模式才是正解。两者本质上都在做同一件事:识别AB相的相位变化,只是实现层级不一样。后面的内容,我会把两种方案都拆开揉碎讲清楚。

2. 核心细节解析:EC11内部原理与AB相波形深度拆解

2.1 从内部结构看输出信号:推挽输出、上拉电阻与机械抖动

EC11编码器一般有5个引脚,除了A、B两相和公共端C(通常接地)之外,还有两个引脚是按压开关的(用于旋钮按键功能)。这里我们只关注旋转部分。内部结构可以简化理解为:一个带有金属触点的转盘,和两个固定的簧片触点。每转动一个角度,转盘上的导电区域会依次和A触点、B触点接触或断开,从而在输出端产生高低电平。

这里有一个关键点:EC11内部输出并非标准的数字推挽输出,而是一个机械开关结构。也就是说,A、B引脚内部相当于一个开关,一端连接到公共端(地或者VCC,取决于具体型号和接法),另一端悬空。所以,在外部电路上,A、B引脚必须接上拉电阻(通常接10kΩ到VCC)或者下拉电阻(连接到GND),否则引脚浮空,读到的电平是随机值。

我习惯把公共端C接地,A、B分别通过10kΩ电阻上拉到VCC,然后这样读取:当触点闭合时,该相输出为低电平;触点断开时,上拉电阻把电平拉到高。这样波形看起来是反的,初始空闲为高,转动时产生向下的脉冲。也有些人把公共端C接正极,A、B下拉到地,波形就反过来,初始空闲为低,转动时产生向上的脉冲。这两种接法代码逻辑正好相反,但原理完全一样。你在参考别人代码时,一定要先搞清楚对方的硬件接法,否则直接抄代码大概率会得到相反的方向判断。

机械抖动的产生也源于这个结构。金属弹片在碰撞到固定触点的一瞬间,不会一步到位地牢固接触,而是会发生极短时间的弹跳,导致电平在高低之间快速震荡几次,这个过程通常持续几毫秒。如果在代码里不处理这个抖动,每一个“有效的接触沿”都会被误判成多个脉冲。后面我会讲到,在中断里加判断逻辑就是为了规避这个坑。

2.2 波形图现场还原:示波器下的正交信号与方向判定逻辑

用逻辑分析仪抓取EC11波形,是我每次调试编码器项目必做的第一步。抓取时把A相接在通道0,B相接在通道1,公共端接地,然后缓缓转动旋钮,你会看到类似这样的波形:通道0(A相)先产生一个下降沿,紧接着没多长时间,通道1(B相)也产生一个下降沿。等到继续转下一个格时,同样的过程再次重复,但A相和B相的下降沿前后关系跟上一格保持一致。

这个“A先动,B后动”还是“B先动,A后动”的顺序,就是判断方向的核心依据。假设我定义A相接中断引脚,在A相的下降沿触发中断,然后在中断服务函数里读取B相的电平。如果检测到A相下降沿时,B相恰好处于低电平(说明B相已经先一步到位了?这里需要仔细分析),那就表示当前旋转方向为反向;反之,如果B相还是高电平,说明B相还没动,A相先动,那就是正向。不对,这里我再说得更严谨点,因为很多人在这里绕晕。

我们重新捋一遍逻辑。以A相下降沿作为“采样点”(也就是中断触发点),此时去读取B相的状态。如果旋钮是顺时针转,A相下降沿到来时,B相经过上拉,大概率处于高电平。然后紧接着,B相也下降,完成一个完整的状态变化。相反,如果逆时针转,B相会比A相提前下降,那么当A相下降沿触发中断时,B相已经是低电平了。所以,只要在A相中断里判断B相此时是高还是低,就能反推出方向。这个逻辑可以表述为“A下降沿采样B相电平:B高为正转,B低为反转”。注意,这只适用于A相接中断、公共端接地、上拉到VCC这种特定接法。如果你的接法是下拉到地,那么高低电平逻辑完全反过来。

如果手头没有逻辑分析仪,用示波器的双通道功能也能看得很清楚,但需要注意触发设置,建议把触发模式设置为“下降沿触发”并挂在A相上,然后转动旋钮,这样也能稳定捕捉到A、B的相对时序。

2.3 增量编码器的本质:相对位置与圈数的记录原理

EC11不是绝对位置编码器,它不会告诉你当前旋钮处于哪个绝对角度,它只会告诉你“我动了多少步”以及“我往哪个方向动了”。这在交互上完全够用,因为绝大多数场景下,我们只关心相对变化量,比如音量+1、音量-1,菜单下一项、上一项。而在电机测速场景中,我们通常需要知道的是单位时间内转了多少脉冲,再换算成速度。

这里就引出一个重要概念:分辨率。EC11通常有“20脉冲/圈”或“24脉冲/圈”的规格,但如果你用A、B相的双边沿采样(也就是同时检测A、B的上升沿和下降沿),那么每个物理脉冲能被识别出4种状态变化,也就是常说的“四倍频”,一圈的计数分辨率会提升到80或96。四倍频技术在编码器位置检测中应用广泛,用“阅读”AB相每个跳变沿的方式,用同样的机械成本换取4倍的反馈精度。

但在Arduino上做四倍频有个风险:中断频率翻倍。假设你每分钟转300圈,20脉冲/圈,四倍频后每秒就有400个中断,Arduino Uno(16MHz)尚能应付。但如果转得更快,或者主循环里有大量串口打印、屏幕刷新任务,中断就会成为瓶颈。STM32的硬件编码器模式自带四倍频计数能力,完全不需要软件操心,这也是我极力推荐STM32做高性能编码器读取的原因。

3. 实操核心环节:双平台示例的完整解析

3.1 硬件连接与电路搭建细节(Arduino和STM32通用接线)

无论你用哪个平台,硬件连接方式几乎一致。以公共端接地、A/B相上拉为正逻辑接法举例:

部件EC11引脚连接到
Arduino / STM32A相数字IO引脚(如Arduino D2 / STM32 PA0)
同上B相数字IO引脚(如Arduino D3 / STM32 PA1)
同上公共端CGND
同上上拉电阻A相10kΩ → VCC(3.3V或5V),B相同样
按键开关两个开关脚串入按键电路(本文略)

需要特别提醒的是STM32的引脚电压容忍度问题。EC11本身是无源器件,不牵扯电平兼容问题,但你的上拉电阻接的电压决定了输出电平。如果STM32工作在3.3V,那上拉就接3.3V,千万别图省事接到5V上,否则时间一长可能损伤引脚。Arduino Uno用5V上拉也完全没问题,大多数STM32F103系列虽然标注“5V容忍”,但在这类场景里没必要冒险。

另外,如果是长线连接(比如旋钮面板和中控板之间距离超过20厘米),建议在靠近MCU引脚的地方并联一个0.1μF的瓷片电容到地,A、B各一个,可以有效滤除线束感应的高频噪声。但这个电容也不能太大,太大会把信号的边沿变缓,导致STM32误判边沿。实测0.1μF在这个距离下表现很好。

3.2 Arduino平台:中断方式与简易状态机实现旋转计数器

在Arduino上实现EC11读取,我推荐用中断方式配合状态机处理抖动。先说中断方式,核心思路是:只在A相的跳变沿进入中断,然后在中断里读取B相电平判断方向。这种做法能有效避免B相抖动带来的误触发,因为你并不是在每个跳变沿都计数,而是在关键沿(A相跳变)才执行一次快速判断。

#define ENC_A 2 #define ENC_B 3 volatile long encoderPos = 0; void setup() { pinMode(ENC_A, INPUT_PULLUP); pinMode(ENC_B, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(ENC_A), readEncoder, FALLING); Serial.begin(115200); } void loop() { Serial.println(encoderPos); delay(50); } void readEncoder() { if (digitalRead(ENC_B) == HIGH) { encoderPos++; } else { encoderPos--; } }

这段代码简洁直观,但它有一个隐患:它只在A相下降沿触发,理论上每个脉冲只计一次数,也就是“单倍频”。此外,它没有处理抖动,如果A相下降沿出现机械抖动,readEncoder可能会被连续触发多次,导致单次旋转累加出错误数字。所以,这个代码只是演示方向判断逻辑,真要在项目里用,我建议改成下面这种更稳妥的“双边沿+状态机”方式。

所谓“状态机”方式,就是同时监测A、B两相的全部跳变沿,每检测到一个沿,就根据上一个状态和当前状态识别方向。这种方式的抗抖动性更好,而且天生支持四倍频。在Arduino上,由于需要占用两个外部中断引脚,而Uno的D2、D3正好都能外部中断,所以实现起来比较方便。

#define ENC_A 2 #define ENC_B 3 volatile uint8_t lastState = 0; volatile long encoderPos = 0; void setup() { pinMode(ENC_A, INPUT_PULLUP); pinMode(ENC_B, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(ENC_A), onEncoderChange, CHANGE); attachInterrupt(digitalPinToInterrupt(ENC_B), onEncoderChange, CHANGE); lastState = (digitalRead(ENC_A) << 1) | digitalRead(ENC_B); Serial.begin(115200); } void loop() { Serial.println(encoderPos); delay(30); } void onEncoderChange() { uint8_t state = (digitalRead(ENC_A) << 1) | digitalRead(ENC_B); uint8_t transition = (lastState << 2) | state; switch (transition) { case 0b0001: case 0b0111: case 0b1110: case 0b1000: encoderPos++; break; case 0b0010: case 0b0100: case 0b1011: case 0b1101: encoderPos--; break; default: break; } lastState = state; }

这段代码的思路是,将A、B的电平编码成一个2位的二进制数,上一次状态左移两位加上当前状态,组成一个4位的转移码,通过查表判断是正转还是反转。状态机方式的好处在于,只要状态转移正确就走相应的加减路径,遇到干扰跳变时,如果转移码不在预设集合里,就会被丢弃,不会造成错误计数。实测这个代码在机械旋钮上很稳,唯一的代价是中断次数增加了3到4倍,但Arduino在这种低速交互场景下完全带得动。

3.3 STM32平台:定时器编码器模式的硬件级解耦方案

STM32的编码器接口模式是硬件外设提供的功能,把“看波形、判方向、计数”这些苦力活全部收敛进定时器模块。以STM32F103系列为例,一般选用TIM1、TIM2、TIM3、TIM4等。以TIM3为例,把PA6配置为TIM3_CH1(对应A相),PA7配置为TIM3_CH2(对应B相),然后初始化时把TIM3的从模式设为编码器模式,硬件就会自动根据TI1、TI2的相位关系来加减内部计数器CNT。

编码器模式的核心配置思路是:选择计数边沿组合。你可以配置为只在TI1边沿计数(单倍频)、只在TI2边沿计数(单倍频)、或者在TI1和TI2双边沿都计数(四倍频)。四倍频模式下,计数频率是物理脉冲数的4倍,位置分辨率最高。TIM3的计数方向(向上/向下)由硬件根据A、B相对关系自动决定,并通过TIM_GetCounter()读出来的值即可获取当前位置。

#include "stm32f10x.h" void Encoder_Init_TIM3(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x0C; TIM_ICInit(TIM3, &TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x0C; TIM_ICInit(TIM3, &TIM_ICInitStructure); TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_SetCounter(TIM3, 0); TIM_Cmd(TIM3, ENABLE); } int32_t Encoder_GetCount(void) { return (int32_t)TIM_GetCounter(TIM3); }

关于TIM_ICFilter的配置,这里需要多说一句。这个参数就是硬件数字滤波器,能过滤掉小于设定宽度的毛刺脉冲。值越大,滤波时间越长,抗抖动越好,但也会把高速旋转时真正窄的脉冲吞掉。对于EC11这种机械编码器,转速一般不会太高,0x0C左右是比较稳妥的取值。如果旋钮转得非常快,发现计数不增反减,可以先考虑降低滤波强度。

使用上,只需要周期性地调用Encoder_GetCount(),和上一次读到的值求差,就能得到这段时间的增量。如果做电机速度环,可以在定时器中断(比如1kHz)里读取这个差值,乘以一个系数就是速度值。硬件自动计数的好处是,哪怕主程序再忙,也不会丢脉冲,除非计数器溢出(把TIM_Period设为0xFFFF就是为了让溢出范围足够大,一般很难转满65535个脉冲)。

3.4 移植与适配:不同MCU平台下的代码调整要点

如果你已经掌握了上面两个平台的核心逻辑,那把它移植到ESP32、树莓派Pico或者其他MCU上就很简单了。需要注意的核心点只有三个:第一,确认你的引脚是否支持外部中断,以及中断触发模式是上升沿、下降沿还是任意沿变化;第二,检查GPIO内部是否有上拉/下拉能力,Arduino的INPUT_PULLUP很方便,但ESP32的某些引脚也需要外置上拉才能保证稳定;第三,如果是高级定时器,比如STM32F4系列,寄存器名和库函数会有细微差别,需要对照参考手册调整。

以ESP32为例,它的GPIO基本都支持外部中断,但如果你要接多个编码器,需要注意ESP32的PCNT(脉冲计数器)外设,这个硬件模块和STM32的定时器编码器模式非常相似,也能自动根据AB相计数,且只在指定脉冲模式时触发中断。这些平台迁移的核心,永远是先看硬件外设有没有“编码器模式”这个专用功能,如果有,优先用它;如果没有,再上中断+状态机。

我个人踩过的一个坑是,把Arduino代码往STM32上硬搬的时候,忽略了上拉方式的不一致。Arduino的pinMode(pin, INPUT_PULLUP)很方便,但STM32标准库初始化GPIO时如果忘了写GPIO_Mode_IPU(上拉输入),而是用了GPIO_Mode_IN_FLOATING(浮空输入),在电机附近强干扰环境下,B相电平就容易被拉偏,导致偶尔反转一个脉冲。这问题在示波器上几乎看不出来,但累计误差积少成多,非常折磨人。所以无论什么平台,编码器引脚能开内部上拉就开内部上拉,不行就在外部并联10kΩ电阻,千万不要用浮空输入。

4. 常见问题与排查技巧实录

4.1 转一格计数器却跳好几个数,问题出在哪

这是EC11调试里最高频的问题。遇到这种症状,第一反应肯定是抖动。如果你用的是Arduino中断单倍频方案,建议改成状态机方式,把无效的状态转移直接滤除掉。如果你已经用了状态机,依然跳数,那就需要拿示波器去看波形了。我遇到过一种特殊情况:A、B两相波形看起来挺干净,但机械手感就是“咔嚓”两下,逻辑分析仪放大之后发现,在一个物理脉冲内,A相抖动时B相也跟着抖动,导致状态转移出现了合法的正转转移码,也出现了合法的反转转移码,两个相互抵消的结果是计数器没变,但你期待的是“转一格只加一”。

这种情况下,单纯靠软件状态机已经解决不了问题了,因为抖动产生的脉冲在状态机眼里是“合法”的。思路要转回到硬件上:给A、B引脚都加一个RC低通滤波器。常见取值是电阻1kΩ串联到引脚,然后在引脚对地并联一个0.1μF电容。这样就把机械抖动产生的高频毛刺给削平了。加了RC之后,信号边沿会变缓,但MCU的GPIO检测边沿通常没问题。

另外还有一个很隐蔽的问题:代码里如果是用digitalRead()读取B相,然后又马上做方向判断,这中间的微秒级延迟在快速转动时可能导致采样到了错误电平。更稳妥的办法是在进入中断后加几个微秒的延时(或者用__NOP()空指令)再读B相,确保引脚电平已经稳定。STM32编码器模式不存在这个问题,因为硬件采样的速度远高于机械信号的变化速度。

4.2 四倍频后计数方向反了,如何快速修正

方向反了是最容易解决但也最容易让人困惑的问题,因为每个人接线的习惯不一样。如果你确定AB相接线已经反复核对无误,但计数方向还是相反,最快速的修正方法是在代码层面交换方向判断。在Arduino状态机代码里,把encoderPos++和encoderPos--整个对调即可。在STM32的TIM_EncoderInterfaceConfig()函数里,把两个通道的输入极性从TIM_ICPolarity_Rising全部改成TIM_ICPolarity_Falling,方向就会翻转。也可以不动代码,直接在硬件上交换A、B两根线,效果等价。

但注意,如果你在STM32中用四倍频的极性配置来做方向翻转,要意识到它同时也会改变计数沿的定义,也就是可能影响滤波和计数的物理意义。在实际项目中,我更推荐在软件应用层做一个“方向系数”变量,比如读取到的计数值乘以1或-1,这样后续接线变化、结构换向时,只需要改一个常量即可,而不用去改动底层的外设配置。

4.3 转速稍高就丢脉冲,高速读取时怎么优化

EC11毕竟是机械编码器,额定转速和响应速度是有限的。如果你发现转速超过某个阈值后,Arduino中断方式开始丢数,不要急着怪编码器,先看看你的中断服务函数是不是拖得太长了。中断里的digitalRead()、Serial.print()这类操作都是相当耗时的,在中断服务函数里绝对不要做打印、延时等操作,正确做法是把计数值存到volatile变量里,在loop()中统一处理。

如果确认中断函数已经足够精简,但丢数依旧存在,那就要考虑换用带硬件编码器接口的MCU了(比如STM32),让硬件自己处理高频脉冲,CPU只负责定期读取计数值。用STM32做电机测速时,我习惯把编码器计数频率上限算出来:假设电机最高转速3000rpm,编码器20脉冲/圈,四倍频后每秒10000个计数沿,TIM3的时钟是72MHz,计数器处理这些沿绰绰有余。但如果你用的是老的8051或者低主频M0核,再叠加其他中断负载,就很容易扛不住。

还有一个优化点是切换中断触发模式。如果你不关心四倍频,只需要单倍频,那就不要在CHANGE边沿(上下沿全部触发)中断,而是只在下降沿或上升沿触发。这样能直接减少一半以上的中断次数,丢脉冲概率大大降低。大原则就是:在满足精度要求的条件下,中断越少越好。

4.4 快速排查流程参考表

症状可能原因排查方向与解决措施
完全没计数反应AB接错、公共端没接地用万用表测A、B对地电平,转动旋钮观察电平是否跳变
方向反了AB接反或判断逻辑方向相反软件中交换加减方向,或硬件交换A/B接线
转一格跳多步机械抖动、采样延时不足加RC滤波、使用状态机、下沿采样增加延时
转得快就丢步中断过载、滤波过强精简中断函数、降低计数倍频、换硬件编码器模式
偶尔往回转一格B相干扰、共地不良加滤波电容、确保所有设备可靠共地、检查上拉电阻

5. 从波形到代码的思维习惯

我一直觉得,玩嵌入式如果只盯着代码看,永远只能在一个很浅的层面上打转。EC11这个小小的器件,就是一个特别好的训练素材:它让你学会看波形、分析时序、理解硬件外设的工作机制,然后再回到代码里去落实这些理解。

当你真正在示波器上看到AB相那90度的相位差时,你会发现Arduino代码里的方向判断变得无比直观,STM32的编码器模式也不再是个玄学配置,而是一个“哦,原来硬件就是这么按照这个逻辑计数的”的自然结果。

我在实际调试中,最享受的时刻并不是计数值终于和手转的角度对上了,而是通过波形反推出机械结构的微小瑕疵——哪个触点压力不足导致波形边沿不够陡峭,哪个地方因为润滑不均导致抖动时间偏长——这种从软件问题一路追到硬件设计的能力,才是调试经验里最值钱的部分。希望这篇东西能帮你少走一些我当年走过的弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 5:48:29

STM32G431工业CAN节点设计:FreeRTOS+Flash高可靠实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 5:48:20

2026年转行网络安全还有机会吗?入行路线与就业避坑指南

2026年年初&#xff0c;后台又收到一堆私信&#xff0c;几乎都是同一句话&#xff1a;“哥&#xff0c;我现在想转行网络安全&#xff0c;还来得及吗&#xff1f;”每年年初都是这种问题的爆发期&#xff0c;但2026年这个时间点确实有点特别。AI安全、汽车网络安全、数据合规这…

作者头像 李华
网站建设 2026/9/28 5:48:16

Spring Boot宠物用品商城系统开发实战:核心模块与避坑全解析

宠物用品商城这个题目&#xff0c;放到Spring Boot项目里几乎是每个学Java的人绕不开的一道坎。选了它的人&#xff0c;基本都能说出一串理由&#xff1a;业务链路完整、前后端交互典型、数据库设计有发挥空间、答辩时有东西可讲。但真正把它从"能跑"做到"能看、…

作者头像 李华
网站建设 2026/9/28 5:47:59

Linux进程控制实战:从D状态排障到fork、僵尸进程与cgroup

前两天帮朋友排查一个线上问题&#xff0c;服务器负载飙到80多&#xff0c;负载高但CPU空闲、内存也充足&#xff0c;系统却卡得不行。用 ps 一看&#xff0c;一百多个进程挂在 D 状态&#xff0c;IO 等待队列塞满——这种进程控制层面的疑难杂症&#xff0c;只会 top 和 kill …

作者头像 李华
网站建设 2026/9/28 5:47:58

OpenCV+YOLOv车辆多维特征识别:车色、品牌、车标、车型一次推理

简介&#xff1a;一套基于OpenCV与YOLO系列算法实现的车辆多维特征识别系统&#xff0c;面向计算机视觉学习者、自动驾驶与智能交通研发人员&#xff0c;可用于对车辆颜色、品牌、车标、车型进行快速识别。压缩包共8个文件&#xff0c;约8.7MB&#xff0c;主要包含两个Python程…

作者头像 李华
网站建设 2026/9/28 5:47:41

OpenClaw+营销枢纽实战:从零搭建智能营销自动化运营体系

首页先说重点&#xff1a;这套思路我已经在几个真实项目上跑通了&#xff0c;不是纸面推演。今天从零开始讲清楚&#xff0c;OpenClaw到底怎么和营销枢纽搭起来&#xff0c;以及在运营独立站、官网这些场景里&#xff0c;它到底能帮我们省下哪些人力。1. 整体设计与思路拆解先说…

作者头像 李华