
1. 编码器与译码器从数字电路视角看“翻译官”做数字电子技术这些年我越来越觉得编码器和译码器像是一对“翻译官”——编码器把人类世界的信息翻译成机器能识别的二进制代码译码器则反过来把二进制代码翻译回人类能理解的形式。无论是课堂上教《数字电子技术基础》时讲到组合逻辑电路还是实际调试STM32、FPGA工程时处理传感器数据、驱动数码管显示这对“翻译官”几乎无处不在。这篇文章我想换一个角度不按教材目录平铺直叙而是从“工程到底需要什么”出发把编码器、译码器的原理、芯片选型、Verilog实现、以及与旋转编码器、磁编码器这类实际传感器相关的延伸问题串起来。适合正在学数字电路的学生、刚接触嵌入式的开发者、以及想系统梳理这块知识的工程师。读完你至少能回答这三个问题编码器和译码器到底在电路里扮演什么角色优先编码器和普通编码器差在哪3-8译码器为什么能当数据分配器用1.1 一切从组合逻辑的“翻译”需求说起数字电路两大阵营组合逻辑和时序逻辑。编码器和译码器都属于典型的组合逻辑电路——输出只取决于当前输入和过去没关系。它们是后续所有复杂数字系统的基础构件就像盖楼用的标准砖块。理解它们时用“翻译”这个角度就很直观。你去餐厅点菜菜单上写着“宫保鸡丁、鱼香肉丝、麻婆豆腐”你跟服务员说“来份2号套餐”这里的“2号”就是对菜品的编码。后厨看到“2号”要通过对照表知道是宫保鸡丁这就是译码。编码器干的就是“把有意义的信号变成二进制代码”的活译码器干的是“把二进制代码还原成有意义的信号”的活。从信息论角度看编码的本质是压缩——用更少的比特位表示更多的信息n位二进制代码能表示2的n次方种不同状态。译码是编码的逆过程。一压一放构成了数字系统内部二进制世界和外部现实世界之间的桥梁。2. 编码器的分类与内部逻辑拆解2.1 普通编码器n位二进制代码表示2的n次方个输入先看最基础的二进制编码器。教材上的典型例子是8线-3线编码器8个输入I0到I73个输出Y2、Y1、Y0。任何时刻只允许一个输入为有效电平输出对应的二进制编码。比如I5有效时输出就是101。这里有个很关键的工程约束普通编码器要求输入“互斥”——同一时刻只能有一个输入有效。一旦两个输入同时有效输出就会混乱变成一个没有意义的组合。现实生活中这个条件往往很难满足比如按键键盘上用户同时按下两个键怎么办所以实际工程中普通编码器用得很少优先编码器才是主流。8线-3线编码器的逻辑表达式也很典型以高电平有效为例Y2 I4 I5 I6 I7 Y1 I2 I3 I6 I7 Y0 I1 I3 I5 I7这个表达式背后的规律是某一位输出为1取决于哪些输入对应的二进制编码在该位上是1。比如编码为4到7的输入二进制第2位权值4都是1所以Y2就是I4到I7相或。理解了这个规律任意位数的编码器都能很快推导出来不用死记真值表。2.2 优先编码器现实世界最常用的折中方案优先编码器的思想特别“工程”——既然无法保证输入互斥那就规定一个优先级顺序。当多个输入同时有效时优先响应编号最大的那一个也可以设计成优先响应编号最小的取决于芯片规格。这就像公司里多个紧急请求同时到达处理规则是“级别高的先来”。经典芯片是74LS14816脚DIP封装8个输入低电平有效3个编码输出也是低电平有效还额外引出两个非常有用的控制端选通输入端EIEnable Input和选通输出端EOEnable Output以及扩展输出端GSGroup Select。这里我必须多说一句关于EI和EO的价值。教材上往往只讲真值表但工程上这两个引脚恰恰是系统扩展的核心。EI为高电平时整个芯片不工作所有输出都是高电平相当于被禁用。EI为低电平时芯片正常工作。EO则是“本片无有效输入且被启用时为低”GS是“本片有有效输入时为低”。用一片74LS148的EO接到下一片的EI就能把两片8输入优先编码器级联成16输入。这就是为什么74LS148能构成16线-4线优先编码器的原因。对于74LS148带优先级的真值表核心逻辑是I7优先级最高只要I7有效无论其他输入是什么输出都是I7的编码只有I7无效时才看I6以此类推。工程直觉是优先级高的输入“屏蔽”了优先级低的输入实现方式是让高优先级信号同时参与低优先级对应的逻辑判断。2.3 从编码器到FPGA用Verilog实现行为级描述到了FPGA时代硬件描述语言让我们不用再靠离散门电路拼芯片但这不代表原理不重要。恰恰相反理解原理才能写出高效的RTL代码。用Verilog实现8线-3线优先编码器最直接的方式是用casez语句配合z通配符处理“任意位”module priority_encoder_8to3 ( input [7:0] din, // 输入高电平有效 input en, // 使能 output reg [2:0] dout, // 编码输出 output reg valid // 输出有效标志 ); always (*) begin if (!en) begin dout 3b000; valid 1b0; end else begin casez (din) 8b1???????: begin dout 3b111; valid 1b1; end 8b01??????: begin dout 3b110; valid 1b1; end 8b001?????: begin dout 3b101; valid 1b1; end 8b0001????: begin dout 3b100; valid 1b1; end 8b00001???: begin dout 3b011; valid 1b1; end 8b000001??: begin dout 3b010; valid 1b1; end 8b0000001?: begin dout 3b001; valid 1b1; end 8b00000001: begin dout 3b000; valid 1b1; end default: begin dout 3b000; valid 1b0; end endcase end end endmodulecasez里的问号表示该位可以任意匹配综合工具会把它实现成优先级逻辑。注意case分支从上到下是“第一个匹配优先”这正好对应了I7最高优先级的硬件行为。这里有个很重要的工程经验如果综合工具发现某个输入位从未参与逻辑综合综合优化后变成了常量通常意味着你的编码逻辑写错了。我在实际项目里吃过这个亏用casez时最怕把分支顺序写反结果优先级完全反了调试了大半天才发现问题。3. 译码器二进制代码的“反向翻译官”3.1 二进制译码器与74LS138的工程地位译码器的经典代表是74LS1383线-8线译码器。3位二进制输入A2、A1、A08个输出Y0到Y7低电平有效。除此之外74LS138还有三个使能端G1高电平有效G2A和G2B低电平有效。只有当G11且G2A0且G2B0时芯片才工作否则所有输出都为高。这里我想着重强调使能端在工程中的实际意义远不止“开关”这么简单。首先它可以做片选信号在存储器扩展时用高位地址线通过译码器产生各个存储芯片的片选CPU访问某个地址区间时只有一个芯片被激活其他芯片保持高阻态。其次容易级联扩展用两片74LS138加上一个反相器就能构成4线-16线译码器这正是用“使能端”作为第4位地址输入的原理。3线-8线译码器的输出逻辑表达式以低电平有效为例Y0_n NOT( G1 · NOT(G2A) · NOT(G2B) · NOT(A2) · NOT(A1) · NOT(A0) ) Y1_n NOT( G1 · NOT(G2A) · NOT(G2B) · NOT(A2) · NOT(A1) · A0 )规律很清楚每个输出对应一个最小项取反后输出低电平。这就是教材上“译码器是最小项发生器”说法的由来。在FPGA内部LUT实现本质上也是最小项逻辑理解这个对应关系对做逻辑优化有帮助。3.2 显示译码器从BCD码到数码管的最后一公里教材上另一类重要的译码器是显示译码器典型芯片是74LS48共阴极和74LS247共阳极。它们的输入是8421 BCD码输出是7段数码管的段驱动信号a到g。以共阴数码管为例要显示数字“1”需要点亮b和c两个段所以段码是0000110按a到g排列g为最高位或最低位取决于习惯。工程上常用16进制数表示段码表但不同教材、不同板卡的段码定义顺序可能完全不同这是新手最容易踩坑的地方。这里给出一份在FPGA开发板上常见的高电平有效的共阴段码表g fedcba顺序即g是最高位显示字符二进制段码(fedcbag... 注意顺序)十六进制0011 1111 或 100 0000按实际位序0x3F或0x401000 01100x062101 10110x5B3100 11110x4F4110 01100x665110 11010x6D6111 11010x7D7000 01110x078111 11110x7F9110 11110x6F上表只是一个约定你完全可能在其他开发板上看到完全不同的对应关系。真正的工程做法是以数据手册或板卡原理图为准自己推导一遍段码表而不是直接抄网上的例子。我在一次项目中因为照搬网上段码表结果数码管显示的数字和预期差了十万八千里原理图上清楚地标着段a接在FPGA的第X引脚网上例子的位序却完全相反——这类问题在数字电子技术调试时极为常见。不用74LS48而直接在FPGA里用Verilog实现显示译码器也很简单本质就是一个case语句查表。module seg_decoder ( input [3:0] bcd, output reg [6:0] seg // {g,f,e,d,c,b,a}高电平点亮 ); always (*) begin case (bcd) 4h0: seg 7b0111111; 4h1: seg 7b0000110; 4h2: seg 7b1011011; 4h3: seg 7b1001111; 4h4: seg 7b1100110; 4h5: seg 7b1101101; 4h6: seg 7b1111101; 4h7: seg 7b0000111; 4h8: seg 7b1111111; 4h9: seg 7b1101111; default: seg 7b0000000; // 其他输入全部熄灭 endcase end endmodule值得注意的是default分支很多初学者会漏掉结果仿真时出现锁存器latch这是因为case语句没有覆盖所有分支综合工具默认保持原值意外生成了存储元件。组合逻辑中绝不允许出现锁存器这是数字电路设计的红线违背设计意图。3.3 译码器实现组合逻辑函数教材中的“标准答案”思维教材里常说“用3线-8线译码器加与非门可以实现任意三变量组合逻辑函数”。原理很简单译码器输出的是全部最小项而任何组合逻辑函数都可以写成最小项之和。把函数对应的最小项输出用与非门合到一起就实现了函数。以函数FΣm(1,3,5,7)也就是FACAC或者等效形式为例只需要74LS138的Y1_n、Y3_n、Y5_n、Y7_n接到一个四输入与非门的四个输入端与非门输出就是F。工程上这个做法的价值在于当你需要一个三变量函数、又不想用一堆散装门电路时一片74LS138外加几个与非门就搞定了成本低、调试方便。在FPGA时代这种思路看似被LUT取代了但理解最小项和LUT之间的映射关系对做资源优化仍然有帮助——如果你想用更少的LUT实现一个多个输出共用的逻辑本质上还是在做“最小项共享”。4. 从教材到实战旋转编码器、磁编码器与STM32工程应用如果只停留在74系列芯片层面数字电子技术这门课就失去了和现实世界的连接感。真实项目中我们遇到的“编码器”很多不是编码键、编码芯片而是各种电机位置反馈传感器比如增量式旋转编码器、绝对式磁编码器。它们虽然在物理形态上完全不同于74LS148但从数字电路视角看本质上还是在做“位置信息到数字代码的编码”这件事。4.1 为什么伺服电机、机器人都离不开编码器你在相关搜索词里看到“STM32正交编码器应用”“增量式编码器”“双磁编码器测电机位置”这些都属于运动控制场景。电机一转动控制器必须实时知道转了多少度、转向如何、转速多快否则PID控制无从谈起。编码器就是电机的“眼睛”把机械转角或直线位移转换成电信号。增量式编码器输出A、B两路正交方波信号相位差90度。正转时A相超前B相反转时B相超前A相。通过检测两路信号的相位关系就可以判断方向通过计数脉冲数量就可以知道角度变化量。四倍频技术更是常用——同时检测A、B的上升沿和下降沿一个完整脉冲周期可以得到4个计数沿分辨率直接提升4倍。STM32的定时器自带正交编码器接口模式硬件自动完成方向判断和计数无需软件干预。使用HAL库时配置TIM_Encoder_Interface拿TIM3为例CH1对应A相、CH2对应B相TIM_Encoder_InitTypeDef encoderConfig {0}; encoderConfig.EncoderMode TIM_ENCODERMODE_TI12; // 双沿计数4倍频 encoderConfig.IC1Polarity TIM_ICPOLARITY_RISING; encoderConfig.IC2Polarity TIM_ICPOLARITY_RISING; HAL_TIM_Encoder_Start(htim3, TIM_CHANNEL_ALL);注意EncoderMode设置为TI12时芯片在A、B两相的上升沿和下降沿都计数计数分辨率是物理线数的4倍。假如电机轴端编码器是500线4倍频后每转有2000个计数脉冲。同时还需要在程序中定时读取CNT寄存器并清除避免溢出或者配置溢出中断做累加处理。4.2 I2C磁编码器MT6701的读取与滤波校准思路和光电增量式编码器相比磁编码器体积更小、耐油污、抗振动在舵机和云台等紧凑结构中越来越流行。相关热词里出现的MT6701就是一颗基于AMR或霍尔的磁编码器支持I2C接口输出14位绝对角度。用STM32F103的HAL库模拟I2C读取SPI/I2C类传感器基本流程是初始化引脚为开漏输出配合外部上拉电阻手动翻转SCL和SDA电平模拟起始、停止、应答等时序。MT6701的I2C地址一般是0x067位读角度的命令通常是0x00开头连续读两个字节拼接成14位角度数据。这里我特别想强调两个容易翻车的工程细节一是I2C时序中应答位(ACK)的处理。霍尔传感器或者磁编码器从机如果没收到正确的寄存器地址不会产生ACK主机读回来的数据全是0xFF。排查这种问题最简单的办法是拿逻辑分析仪看波形比对着数据手册的时序图一帧一帧核对。二是滤波与校准。磁编码器虽然安装便捷但不可避免存在偏心、轴磁化不均匀等误差直接读取的原始角度往往带有一个周期性的波动。工程上常用的做法是采集一整圈数据做一次最小二乘拟合的误差补偿或者简单点在低速转动时做均值滤波后再查表校正。如果要求不高加一个一阶低通滤波就能有效抑制噪声float angle_filtered 0.9f * angle_last 0.1f * angle_raw;0.9和0.1的系数决定滤波带宽响应太慢会影响控制实时性这个系数需要根据控制频率实测调整不能盲目抄。4.3 从编码器数据到实际控制方向判断与Z相归零增量式编码器除了A、B两相通常还有Z相索引信号每转一圈输出一个脉冲用于机械原点归零。工程上“找零”的先后来但是很讲究的正转、反转时Z相出现的位置可能差半个脉冲周期如果不加处理重复上电后的零点位置就不一致对需要每次上电回原点的设备影响极大。推荐的做法是上电后先让电机以一个固定方向低速转动捕获到Z相脉冲后记下当前计数方向再继续以同方向转至某预设位置完成归零。整个过程必须保证方向一致否则Z相会错半个刻线。这类“隐性坑”在教材里完全不会提但实际调试时让你抓狂的往往就是这种问题。5. 常见问题与排查技巧实录5.1 编码器与译码器电路的通用排查思路我在带学生做综合实验和帮同事定位硬件问题时总结出一套百试不爽的排查流程。无论是74LS138译码器驱动数码管不亮还是STM32编码器接口计数异常第一步永远是“确认电平”。拿万用表或示波器实测引脚电压对照真值表确认芯片是否处于使能状态是否存在接触不良或虚焊。第二步是“确认时序”。对译码器而言地址输入和使能信号的建立时间/保持时间是否满足芯片要求对编码器接口而言A、B相是否真的相差90度脉冲频率是否超出定时器的最大计数范围。很多STM32开发者在编码器模式下“计数一直为0”查来查去发现是没有在CubeMX里把对应的两个引脚映射到同一个定时器的CH1和CH2上导致硬件上根本没有信号进入编码器接口。第三步是“确认配置”。比如看门狗、时钟树配置是否影响定时器时钟DMA有没有和编码器计数冲突等。这类问题在纯数字电路教材里不会出现但在嵌入式项目中占比极高。5.2 常见故障速查表现象可能原因排查方向74LS138输出全为高使能端G1为低或G2A/G2B为高检查使能端接线和电源电压数码管显示乱码段码表位序不对、共阴共阳弄反对照原理图确认段码顺序用一个固定数字逐一测试编码器计数一直为0引脚映射错误、编码器接口未使能、信号线接触不良示波器测量A/B相波形检查CubeMX引脚配置I2C读回数据全为0xFF地址错误、从机未应答、上拉电阻缺失逻辑分析仪抓取时序查数据手册核对7位地址编码器方向判断反了硬件接反或配置中正反转逻辑不对交换A/B相接线或在软件里取反方向标志FPGA中case语句出现锁存器case分支未完全覆盖组合逻辑添加default分支检查所有输出是否每个分支都赋值编码器读数抖动信号未滤波、机械振动、干扰加RC滤波、软件去抖必要时用施密特触发器整形5.3 一个真实定位案例旋转编码器“丢步”排查全过程我之前的项目里有个伺服云台客户反馈偶尔回不到原位。刚开始以为是机械间隙问题检查结构没问题后来定位到增量式编码器读数偶尔会“丢步”。用示波器同时抓A、B相波形后发现下降沿有明显的振铃导致STM32的编码器接口多计了几次。解决方法是硬件上在A、B相到MCU之间各串联一个1kΩ电阻并对地并一个10nF电容构成低通滤波同时在CubeMX里把输入引脚配置为上下拉并使能数字滤波。两招齐下再跑1000次回原点测试零丢步。这个案例再次说明数字电路的设计不能只看逻辑功能还要考虑物理信号质量这也是“数字电子技术基础”真正落地到工程时最具价值的部分。6. 一些过来人的经验和学习建议学编码器与译码器这块内容最容易犯的毛病是只背真值表、不深挖逻辑。真值表背完一个星期就会忘但如果你理解了“编码器输出就是输入最小项的或逻辑”“译码器每个输出对应一个最小项”那么遇到任何型号的新芯片拿着数据手册就能自己推导。动手实践是唯一的捷径。芯片级验证可以买一块74LS138和74LS148插面包板点亮LED亲手体验一下“输入”和“输出”的对应关系FPGA验证可以写Verilog代码实现3-8译码器然后上板测试。理论学得再好不如到板子上看到真实波形来得扎实。如果你正走在嵌入式或FPGA方向上建议把编码器和译码器这一章当成“接口思维”的训练场。它们不仅在74系列芯片里SPI/I2C接口、地址译码、传感器数据解析、状态机编码处处都是“编码/译码”思想的延伸。基础打牢了后面学起来会顺很多。