☰
74HC138译码器实战:从单片机IO扩展到数码管动态扫描
2026/10/5 3:10:40 网站建设 项目流程

1. 从单片机引脚不够用说起

做过单片机项目的人,迟早会遇到一个尴尬场景:传感器要接、按键要接、数码管也要接,单片机的IO口快被薅光了,可板子上还有好几路设备等着控制。我最早遇到这个问题是在做一个8位数码管显示的小项目,如果每一段、每一位都直接用单片机的引脚去驱动,那IO口直接告急,整个电路变得又乱又难查。后来同事丢给我一颗74HC138,说“三根线换八个口,拿去用”,从那一刻起我才真正体会到译码器在数字电路里的分量。

74HC138是一颗3线-8线译码器芯片,意思是输入3位二进制地址信号,输出8个独立通道的选通信号。只需要占用单片机3个IO口,就能控制8个外设的片选、8个数码管的位选、8颗LED的独立点亮,甚至通过级联扩展到16路、24路乃至更多。这颗芯片在51单片机、STM32、Arduino项目里出现频率极高,凡是涉及动态扫描、分时复用、外围扩展的地方,基本都能看到它的身影。

这篇文章就把74HC138的来龙去脉讲透:它内部到底做了什么、引脚怎么接、真值表怎么读、动态扫描怎么用它、怎么把两片138扩成4线-16线,以及我在实际焊接和调试中踩过的那些坑。适合刚接触数字电路的大学生、做电子设计的爱好者,还有想把手头项目IO口省出来的嵌入式开发者。没有什么玄乎的东西,全都是你拿起烙铁和万用表就能验证的内容。

2. 译码器的核心思路与选型理由

2.1 为什么需要译码器而不直接接IO

要理解74HC138的价值,得先明白一个朴素的问题:为什么不能用单片机引脚直接控制每个外设?答案很简单——引脚不够,而且很多外设的“选通”逻辑要求并不是简单的“给个高电平就行”。

举个例子,一个共阴数码管有8个段脚和1个位选脚,要显示8位数字,如果每一位都用独立IO去控制段码和位选,至少需要72个引脚,普通单片机根本扛不住。但用动态扫描就不一样了:所有数码管共享段码线,每次只点亮一位,轮流刷新,利用视觉暂留让人眼看到完整数字。这个方案里,到底“当前刷新哪一位”就需要一个译码电路来决定——3根地址线选通8个位选端,恰好就是74HC138最典型的用法。

另外一个原因是“锁存与隔离”。很多外设的片选端(比如存储芯片的CE脚、ADC的CS脚)要求在不选中时保持稳定状态,不能有毛刺。如果直接用单片机引脚逐个控制,每个外设都要占用一路带编程逻辑的输出,而使用译码器后,单片机的3根地址线一组电平变化,138输出端会自动屏蔽其它通道,天然形成片选隔离,这对硬件电路稳定性很有帮助。

2.2 74HC138的工作原理与内部逻辑

74HC138的逻辑本质上是一个“地址解析”过程。输入端的A0、A1、A2三个引脚组成一个3位二进制数,取值范围是000到111,对应十进制0到7。这8种组合分别唯一对应Y0到Y7这8个输出引脚中的一个。换句话说,它把“三根线的不同电平组合”翻译成“唯一一个输出端有效”的动作。

这里有一个数字电路新手极容易记混的地方:74HC138的输出是低电平有效,也就是说被选中的那个输出端会变成低电平(约0V),其余七个输出端保持高电平(约VCC)。很多教程画逻辑图时默认输出高电平表示选中,结果一接上电路LED不亮或者负载乱动,查半天发现是有效电平搞反了。这一点我会在后面的应用章节里专门展开。

芯片内部是靠与门和与非门组合实现的,每个输出对应一个最小项。你可以想象成8个“门卫”,A0、A1、A2三个人分别报出自己手里的牌,当组合结果和某个门卫手里的口令完全吻合时,那个门卫就让路(输出低电平),其余门卫全部拦住(输出高电平)。这种“一夫当关”的选通机制正是所有译码器、多路选择器共同的设计基础。

2.3 芯片引脚与真值表速查

74HC138常见封装是SOIC-16或者DIP-16,我用得最多的是DIP-16,因为可以直接插面包板。引脚定义需要烂熟于心,我列一份速查表:

引脚编号名称功能说明
1A0地址输入,最低位
2A1地址输入,中间位
3A2地址输入,最高位
4E1使能端,低电平有效
5E2使能端,低电平有效
6E3使能端,高电平有效
7Y7输出,对应地址111
8GND电源地
9Y6输出,对应地址110
10Y5输出,对应地址101
11Y4输出,对应地址100
12Y3输出,对应地址011
13Y2输出,对应地址010
14Y1输出,对应地址001
15Y0输出,对应地址000
16VCC电源正极,通常接3.3V或者5V

三个使能端E1、E2、E3的“脾气”各不相同。E1和E2是低电平有效,必须接地才能让芯片工作;E3是高电平有效,必须接高电平。三个条件同时满足,芯片才处于译码状态。只要有一个使能端条件不满足,所有输出全部为高电平,芯片休工。这个机制在级联扩展时非常有用,后面讲4线-16线扩展时你就知道这些使能脚能玩出什么花样。

真值表是理解138的钥匙,我直接给你一份整理好的:

E1E2E3A2A1A0选中的输出
001000Y0
001001Y1
001010Y2
001011Y3
001100Y4
001101Y5
001110Y6
001111Y7
其它任意组合任意任意任意任意任意全部输出高电平

从这张表可以读出两个关键信息:第一,只要使能条件不满足,无论地址怎么给,输出全高;第二,地址从000递增到111,对应输出从Y0到Y7依次变为低电平,一个不多一个不少。这个单调对应关系就是译码器名字的来源——把“码”翻译成“唯一通路”。

3. 典型应用场景与实操接线

3.1 基础玩法:LED流水灯与通道选通

我最推荐新手上手74HC138的方式是做一颗LED流水灯:三个按键或三根杜邦线模拟地址输入,8个LED分别接在Y0到Y7上。因为输出是低电平有效,LED的接法需要反过来——LED正极接VCC,负极串一个限流电阻接138的输出脚。当某一输出端变低时,电流从VCC流过LED和电阻到地,LED点亮。

很多人第一次接这个电路会把LED正极接在138输出上、负极接地,结果选中时输出低电平,LED两端几乎没有电压差,死活不亮。这就是“低电平有效”这个特性在实际电路中造成的第一道坎。要记住:低电平有效输出端更像一个“回流通道”,而不是“供电出口”。

这个项目的代码逻辑极其简单。如果你用Arduino,伪代码如下:

// A0 -> D2, A1 -> D3, A2 -> D4 int addrPins[3] = {2, 3, 4}; void setup() { for (int i = 0; i < 3; i++) { pinMode(addrPins[i], OUTPUT); } } void loop() { for (int addr = 0; addr < 8; addr++) { for (int bit = 0; bit < 3; bit++) { digitalWrite(addrPins[bit], (addr >> bit) & 1); } delay(200); } }

地址从0加1到7,每变化一次,选中的LED就换一个。跑起来之后你就亲眼看懂了“3根线控制8个输出”的含义。

3.2 数码管动态扫描的位选控制

如果你想让8位数码管同时显示不同数字,“段码共享、位选独立”是唯一可行的方案,而位选控制正是138的强项。我在实际项目中经常把138的8个输出分别接到8个数码管的公共端,通过快速轮询让它们看起来像是在同时显示。

连接思路是这样的:8个数码管的a到dp段并联在一起,接到单片机的一组IO口(或经74HC595等串转并芯片驱动);每个数码管的公共端接138的一个输出脚。高电平刷新时,数码管共阴的公共端应该被拉低才能导通,恰好138的输出是低电平有效,选中哪位,哪位的公共端就被拉低,这一位就亮了。

动态扫描要注意刷新频率不能太低。我一般控制在1ms切换一位,8位一轮下来8ms,等效刷新率125Hz左右,肉眼基本感觉不到闪烁。如果低于50Hz,就能明显看到抖动和闪动,那体验就很差了。这时候你要检查的不是138,而是你的延时逻辑。

还有一点值得提醒:数码管每段正常工作电流约5到10毫安,8段全亮时接近80毫安,而138每个输出脚的灌电流能力虽然足够,但你如果一路LED接很多负载,还是建议用三极管或者ULN2003做功率扩展,别让138又当译码器又当功率管,那样芯片很容易发热发烫。

3.3 两片138级联扩展成4线-16线

单片138只能输出8路,那16路怎么办?答案是用两片138级联。原理并不复杂:把两片138的A0、A1、A2并联接到4根地址线的低3位,第4根地址线(我们叫A3)接第一片的使能端、同时通过反相逻辑接第二片的使能端。A3为低时第一片工作,A3为高时第二片工作。这样一共就能得到16个独立选通的输出端。

具体接法里有一个细节值得琢磨:第二片的使能端怎么接。常见做法是用一个非门把A3反相后接第二片的E3,或者更简单——如果第二片用E1作为控制端,就把A3直接接E1。因为E1是低电平有效,A3为低时第二片被禁止,A3为高时第二片被使能。而第一片呢,把A3接在E3上,E3是高电平有效,A3为高时第一片被禁止,A3为低时第一片工作。一高一低刚好错开,谁也不抢。

接线时注意:两片138的地址引脚A0、A1、A2要一一对应并联,千万别交叉接反,否则地址映射关系就乱了。验证方法也很简单:给A3A2A1A0从0000逐步加到1111,观察16个输出是不是依次被拉低。我加完电之后习惯拿万用表直流电压档一支表笔对地,另一支逐个戳输出脚,每变一个地址就确认一个通道,全部走一遍才放心往下做负载接线。

如果16路还不够,还有更大规模的玩法——比如两片138分别作为“行选”和“列选”组成8x8点阵,或者三片138配合3线-8线加2线-4线,做成32路输出。原理都是在使能端上做文章,用高位地址线来“分区授权”,本质上跟你家里小区门禁一样:大门开了、楼栋门还要再开,每道门对应一个地址位。

4. STM32实战:用74HC138驱动8位共阴数码管

4.1 完整接线方案说明

光讲原理不够,必须来一个完完整整能跑起来的实战。这里我用STM32F103C8T6最小系统板加8位共阴数码管做一个“8位计数器”,从0一直加到99999999,循环显示。用到的核心器件:STM32最小系统板一块、74HC138一片、8位共阴数码管一个(或者8个独立共阴数码管拼起来)、8个220欧姆电阻、若干杜邦线或洞洞板焊接件。

接线分成两组。一组是段码线:STM32的PA0到PA7分别接数码管的a、b、c、d、e、f、g、dp,每个引脚串联一个220欧姆限流电阻。另一组是位选线:STM32的PB0、PB1、PB2接138的A0、A1、A2;PB3接138的E3作为总使能;E1和E2直接接地。138的Y0到Y7分别接8个数码管的公共端。注意共阴数码管公共端是阴极,138输出低电平时该位选通,正好匹配。

这个方案里STM32只用了11个GPIO,如果不用138,8位数码管直接接段码和位选,至少要16个GPIO,而且每次刷新还要手动控制8根位选线,代码又长又容易错。现在地址线3根加使能1根就把位选全部搞定了,省下来的GPIO可以去接按键、传感器、显示屏,工程扩展性一下子好很多。

4.2 底层驱动代码与显示逻辑拆解

为了让显示逻辑好维护,我习惯把“数字转段码”和“位选刷新”分开封装。数字转段码表是数码管驱动绕不开的基础:

const uint8_t segCode[10] = { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F // 9 };

这里每一位对应一个段:bit0对应a,bit1对应b,以此类推。如果你用的是共阴数码管,这个表可以直接用;要是共阳数码管,那就得取反。

位选刷新用138就非常简单,核心是把“当前要显示第几位”直接作为地址输出:

void setDigitPosition(uint8_t pos) { // pos范围0~7 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, (pos & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, (pos & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, (pos & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); }

把pos从0循环到7,138就依次把Y0到Y7拉低,每一位数码管依次获得“上场机会”。然后配合段码输出,把当前位的数字通过PA0到PA7写出去。中断定时器1毫秒触发一次刷新,每次刷新切换一位:

void displayNumber(uint32_t num) { static uint8_t currentPos = 0; uint8_t digit = num % 10; // 假设逐位提取,8位循环 // 锁存器清零段码,防止闪烁拖影 // 输出当前位数字的段码 // 设置位选 setDigitPosition(currentPos); currentPos = (currentPos + 1) % 8; }

实际生产级代码里,段码输出前要先关闭所有位选或者加一个锁存器清空显示,否则切换位选的瞬间,前一位的数据会残留在下一位上,看起来就是“拖影”或者“鬼影”。这个现象非常常见,排查思路基本就在时序上:先清显示再切地址,顺序不能反。

4.3 动态刷新时序的经验参数

关于动态扫描的时序,我实测下来有这么一组经验值,你可以直接抄:

参数项推荐值说明
每位点亮时间1ms太短亮度不够,太长会闪
完整刷新周期8ms8位扫描,等效125Hz
段码建立时间10us以上段码输出后稍作延时再切位选
消隐等待时间20us左右切换前清空显示,防拖影
总刷新率100~200Hz低于50Hz肉眼可见明显闪烁

有人为了省事把每位延时拉长到5ms,心想反正8位一轮也就40ms,结果肉眼一看闪得厉害。原因在于低于25Hz的刷新就会被人眼察觉,40ms周期只有25Hz,不闪才怪。所以别偷懒,1ms一位是最稳妥的甜点值。如果觉得亮度不够,可以考虑把限流电阻从220欧姆换成100欧姆,而不是去把延时拉长,那样会牺牲刷新率。

5. 常见问题排查与避坑经验

5.1 输出端驱动能力不够怎么办

74HC138说到底是一颗逻辑芯片,不是功率器件。它的输出灌电流能力大概在25mA左右(5V供电时),理论上点亮一颗LED或者驱动一个数码管的位选是够用的,但如果你想让某一输出同时点亮多个LED,或者直接驱动继电器、蜂鸣器这类感性负载,138就会被“榨干”。

正确做法是在输出端加一级驱动。最常用的是NPN三极管,比如S8050,基极串一个1k电阻接138输出,集电极接负载,发射极接地;或者用ULN2003这种达林顿驱动芯片,8路输入8路输出,内部自带续流二极管,接继电器非常省心。记住一个原则:138负责选路,驱动芯片负责出力,各司其职。逾越这条边界,芯片烧了不冤。

顺带提醒一下:138的输入脚千万不要悬空。CMOS工艺的芯片输入端悬空时,电平会处于不确定状态,既不是高也不是低,内部可能反复翻转,造成额外功耗甚至误动作。所有不用的输入引脚要么接VCC要么接地,使能端特别要注意,E1、E2不能悬空,E3不能悬空。

5.2 电源滤波与噪声问题

138工作频率低时几乎不会有人注意电源噪声,但一旦你把它用在高速扫描或者高频选通场景,VCC和GND之间的尖峰噪声就会冒出来。我在一次做16通道数据采集时,138切换地址的瞬间,输出端出现明显的毛刺,被后级放大器当成真实信号采了进去,排查了很久才发现是电源纹波耦合。

经验是:在138的VCC和GND之间紧挨着芯片放一个0.1uF的陶瓷电容,条件允许的话再并联一个10uF的电解电容。这个组合对绝大多数数字逻辑芯片都适用,电容要尽量靠近电源引脚焊,走线短一点,不要绕大圈连到电源入口。

另外,如果和电机、继电器之类感性负载共用电源,强烈建议把模拟地和数字地分开布置,或者至少用磁珠隔开。138本身不是高噪声源,但它很容易被外部的开关噪声干扰,给敏感的译码结果带来不确定的跳变。

5.3 常见问题速查表

现象可能原因排查方法
LED全部不亮使能端接错,E1/E2未接地或E3未接高万用表测各使能脚电压
LED全部常亮输出端接线方向反,把VCC接到了输出检查LED正负极方向
只有个别灯亮地址线接触不良或接错引脚核对A0/A1/A2与单片机IO的映射
显示有鬼影切换前没有消隐先关位选(全部输出无效),再送段码
显示闪烁明显刷新率太低缩短每位点亮时间到1ms左右
芯片发烫输出负载过大加三极管/ULN2003扩展
地址变化但输出不变使能端被意外拉高/拉低测量E3是否为高、E1/E2是否为低

5.4 面包板接触不良的隐蔽坑

新手最容易忽视的就是面包板接触不良。138引脚间距是标准的2.54mm,插入面包板后如果引脚没有完全插紧,看起来接触上了,实际可能只有一根或两根针有效接触。尤其使能脚一旦接触不良,它会浮空,芯片状态就变成薛定谔的猫——时好时坏。

我给的排查建议是:不要用眼睛判断接触,用万用表蜂鸣档逐脚测“芯片引脚到面包板预期接入点”之间的导通性。这个方法看起来笨,但真的能救老命。还有一个习惯值得养成:面包板上飞线尽量用固定颜色规矩——红色电源、黑色地、其他颜色信号,否则排查时你对着十几根杜邦线根本分不清谁是谁。

如果项目要往长期使用方向发展,我建议尽早从面包板转到洞洞板焊接。焊接时注意138的散热和引脚焊接时间,DIP封装一般450度烙铁尖碰两秒钟就够了,拖太久会把引脚上的镀层破坏,甚至伤到内部晶圆。

6. 最后分享几个实战心得

用了这么多年138,我最大的体会是:译码器看起来只是一颗逻辑芯片,但它的应用边界完全取决于你对“地址”这个概念理解得多深。三根线代表8种状态,四根线代表16种状态,这不是数学题,而是你硬件设计里可以用极低成本换极高扩展性的思考方式。

我最近一个项目里把138用在了多路ADC的通道选择上——4片ADC芯片共享SPI总线,每片用138的一个输出做片选,一个地址轮询下来直接完成32通道采集。整个设计只有一颗138加一堆排针,成本不到一块钱,却省掉了专用模拟开关芯片和大量IO口,这就是数字电路设计的魅力。

如果你刚接触这颗芯片,照着文中的流水灯实验先跑通一次,再去碰数码管扫描,最后试试两片级联。一步一步来,你会发现看起来复杂的数字电路底层逻辑其实非常质朴。等哪天你遇到“IO口不够用了”的问题,第一反应不是换更大封装的单片机,而是往扩展芯片方向想,那这篇内容就没白写。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询