news 2026/9/17 2:52:32

5个IO驱动18颗LED显示“188”:查理复用原理与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
5个IO驱动18颗LED显示“188”:查理复用原理与实战

最近在做一个小设备,上面要固定显示“188”这个三位数字——就是价格牌、倒计时器、工装治具上很常见的那种显示需求。按常规玩法,驱动三位数码管至少要7根段选线加3根位选线,也就是10个IO。可我手上这颗TM52F1363总共也没剩几个IO,其他功能还得占线,于是我把主意打到了查理复用(Charlieplexing)上。最后的结果是:5个IO口驱动18颗LED,拼出了“188”这个显示单元。

先把丑话说前面:这不是5个IO驱动188个数码管,5个IO的查理复用理论极限是20颗LED。“188”在这个项目里指的是显示目标,整块显示单元由18颗独立LED组成。本文从原理、接线、代码到实测踩坑全部记录一遍,给同样在IO紧张里挣扎的嵌入式朋友一份能直接抄的作业。

1. “188”到底是什么:需求来源与硬边界

1.1 三个字符背后的LED账本

项目场景是一个小型控制面板,需要固定显示“188”三个字符。拆开看:左边的“1”只需要2段LED,中间和右边的“8”各需要7段LED,这就已经是16颗了。如果还希望在小数点位置做文章,比如显示成“18.8”或“188.”这种价格格式,就得再加2颗小数点LED,合计18颗。

这18颗LED就是整个项目的硬件基础。为什么强调这个数字?因为5个IO的查理复用刚好能驱动5×(5-1)=20颗LED,18颗意味着方案要贴着上限走,多一颗都塞不进去,这算是查理复用的一次“极致压榨”。

1.2 一个容易踩的前提:成品数码管做不了查理复用

很多人看到“数码管”三个字,下意识就拿起抽屉里的共阴三位数码管,结果怎么接都点不亮,最后跑来问是不是算法写错了。问题不在算法,在硬件结构。

成品共阴数码管把所有LED段的阴极在内部连到一起,引出一个公共端;共阳数码管则是所有阳极共用。这种结构下,每颗LED只有一端是独立的,另一端钉死在公共端上。而查理复用的前提是:每颗LED的两端都能接到任意两个不同的IO上,通过“一个IO输出高、另一个IO输出低”来点亮它。成品数码管是星形连接,不是两两交叉的网络,自然没法套查理复用。

所以这个项目用的是独立LED,在PCB上按“188”的字形摆放,每一颗LED的阳极和阴极都单独引线,这是查理复用的正确打开方式。如果你非要用成品数码管,也得选那种每段LED引脚全独立出来的特殊封装,普通共阴共阳管直接放弃。

2. 查理复用原理:5个IO为什么能点亮18颗LED

2.1 三态IO是基础,不是可选项

平时写单片机程序,IO口要么输出高、要么输出低,最多再读个输入,三态这个概念用得不多。查理复用恰恰把第三种状态——高阻输入态,当成了核心工具。

点亮一颗LED的本质是什么?一颗LED的阳极接IOA,阴极接IOB,IOA输出高、IOB输出低,电流从A穿过LED流到B,这颗LED就亮了。如果这时候有第三个IO还在输出低电平,而它恰好和IOA之间也有一颗LED,就会出现一条不该有的电流通路,这就是鬼影的雏形。所以查理复用的铁律是:任意时刻只能有一对IO处于输出状态,其余全部切成高阻输入,把引脚和内部电路完全断开,不让电流有任何可抄的近路。

2.2 n×(n-1)是怎么算出来的

查理复用的容量推导一点也不玄乎。5个IO口,先挑一个输出高,有5种选法;再从剩下的4个里挑一个输出低,有4种选法。5×4=20,这就是5个IO能驱动的LED数量上限。

关键在于这20个组合并不是20个无序点对,而是有序点对。拿IOA和IOB这对来说,“A高B低”能点亮一颗LED,“B高A低”能点亮另一颗LED。所以每两个IO之间可以放两颗极性相反的LED,5个IO一共C(5,2)=10对,每对两颗,总共20颗。打个比方:5个人两两握手,不区分谁主动就是10种组合,区分谁先伸手就是20种,每只“主动的手”都能点亮一颗LED。

也正因为每个LED都对应一个“谁高谁低”的组合,所以查理复用不需要额外的行列选通信号,这是它比传统矩阵扫描省IO的根本原因。

2.3 不能同时点亮两颗以上,只能用扫描换视觉

既然同一时刻只能有一对IO在驱动,那一轮扫描就只能点一颗LED,整个“188”数字是靠快速轮流点亮各段、利用人眼视觉暂留“骗”出来的。你看到的是一个完整的“188”,实际上每一颗LED都在以极低的占空比工作。

这个特点决定了方案的亮度天花板:占空比低,平均亮度就低;要补偿亮度就得拉高峰值电流,但峰值电流又受IO驱动能力和LED额定电流限制。这一堆矛盾会在第5节详细说,这里先记住结论——查理复用本质上是“用时间换引脚”。

2.4 和传统方案的对比

驱动方式5个IO口可驱动的LED数量说明
直接驱动约2颗每颗LED独占两个IO
行列扫描4~6颗5个IO做2×2矩阵已经很勉强
查理复用20颗每对IO双向利用

这张表能看出查理复用在引脚利用率上的碾压优势。但这个优势不是免费的,代价是刷新逻辑复杂、亮度偏低、不能同时点亮多颗LED。项目里要权衡清楚,不要盲目上。

3. 18颗LED怎么挂到5根线上:接线表与电阻方案

3.1 LED与IO组合的映射关系

设5根线为A、B、C、D、E,对应MCU的P1.0到P1.4。18颗LED的分配方案我做了下面这张表,每行代表一颗LED,箭头方向表示点亮时的电流方向,也就是“阳极接X、阴极接Y”。

段名所属字符点亮时电流方向说明
L_B左边“1”A→B左上竖
L_C左边“1”B→A右上竖
M_A中间“8”A→C上横
M_B中间“8”C→A左上竖
M_C中间“8”A→D右上竖
M_D中间“8”D→A中横
M_E中间“8”A→E左下竖
M_F中间“8”E→A右下竖
M_G中间“8”B→C下横
R_A右边“8”C→B上横
R_B右边“8”B→D左上竖
R_C右边“8”D→B右上竖
R_D右边“8”B→E中横
R_E右边“8”E→B左下竖
R_F右边“8”C→D右下竖
R_G右边“8”D→C下横
DP1小数点1C→E“18.8”的小数点
DP2小数点2E→C“188.”的小数点

A-B、A-C、A-D、A-E、B-C、B-D、B-E、C-D、C-E这9对IO全用满了,每对正反两个方向各一颗。剩下的D-E这对IO暂时空着,以后想加状态灯或者复用按键,正好有地方接。这张映射表不是唯一的,只要每个LED都占用一个唯一的有序组合就行,不重不漏即可。

PCB布线时注意一点:5根干线从MCU出来后在显示区走一圈,每颗LED就近挂在对应两根线上。距离差异会造成走线电阻不同,但LED是毫安级电流,几厘米的走线压降基本可以忽略,不用刻意做等长。

3.2 每对IO只需要一颗限流电阻

刚上手查理复用的人最容易踩的坑,就是给每颗LED单独配一个限流电阻。做完才发现,同一对IO上的两颗反并联LED完全可以共用一个电阻。

道理很简单:同一时刻只会有一颗导通。当A高B低时,电流走A→R→LED1→B;当B高A低时,电流走B→LED2→R→A。两颗LED极性相反,天然互斥,电阻串在两根IO之间的公共支路上,两个方向都能限流。所以10对IO只需要10颗电阻,而不是18颗。板子面积和BOM成本都能省一大截。

IOA ---[R]---*---[LED1]---*--- IOB | | *---[LED2]---*

LED1阳极朝左、LED2阳极朝右,R是公共限流电阻。这个结构就是查理复用最小的硬件单元。

3.3 电阻取值和亮度预估

电阻取值得先定峰值电流。扫描方式下LED是脉冲点亮的,假设一轮扫描18步,每颗LED亮1步,占空比就是1/18。想让平均亮度接近1.5mA的直流效果,峰值电流就得做到1.5×18=27mA——这个数字对很多LED和IO口都已经偏大了。所以实际项目里一般把平均亮度目标定低些,比如峰值电流12~15mA,室内显示完全够看。

红色LED在3.3V供电下的压降大约2.0V,再算上IO内部的压降约0.2V,限流电阻取:

R ≈ (3.3 - 2.0 - 0.2) / 0.012 ≈ 92Ω

市面上取标称100Ω就很合适。如果系统是5V供电,同样的思路算下来大约220~330Ω。蓝、绿、白LED压降在3.0V左右,3.3V供电时留给电阻的压降空间非常小,要么电阻取到10Ω以下(基本接近直连),要么提高供电电压,否则亮度很难做上去。这也是为什么查理复用方案里我最推荐用红色LED。

4. TM52F1363上的驱动实现

4.1 三态IO在TM52系列上的配置思路

TM52F1363是十速科技的增强型8051内核MCU,工作电压宽、价格低,做这种IO紧张的小项目很合适。它的IO口模式通过端口配置寄存器切换,不同封装的数据手册上寄存器命名和位定义略有差异,但逻辑都是同一套:把引脚配成推挽输出、准双向、开漏或高阻输入。

查理复用关心的就是推挽输出和高阻输入之间的快速切换。这里给一份通用风格的代码示意,寄存器名以你手头芯片手册为准:

// 以TM52系列端口配置寄存器为例,实际命名请参照数据手册 #define CHARLIE_PIN_MASK 0x1F // 低5位对应P1.0 - P1.4 // 把5个IO全部切成高阻输入 void charlie_all_input(void) { P1M1 |= CHARLIE_PIN_MASK; P1M0 |= CHARLIE_PIN_MASK; P1 &= ~CHARLIE_PIN_MASK; } // 点亮 anode 为高、cathode 为低的这颗LED void charlie_on(unsigned char anode, unsigned char cathode) { charlie_all_input(); // 先全部高阻,关断上一颗 P1 |= (1 << anode); // 高脚预写1 P1 &= ~(1 << cathode); // 低脚预写0 // 只把目标两脚切到推挽输出,其余保持高阻 P1M1 &= ~((1 << anode) | (1 << cathode)); P1M0 |= ((1 << anode) | (1 << cathode)); }

这里的操作顺序是刻意的:先把所有IO切高阻,再写端口数据寄存器,最后才把目标两脚切到推挽输出。顺序反了的话,IO还处于输出状态时写端口数据,可能会瞬间输出一个错误电平,点亮不该亮的LED。

4.2 段码表和定时器扫描

显示缓冲区用一个18字节的数组led_buf[],1代表这一颗LED要亮。扫描函数在定时器中断里跑,每次中断只点亮一颗LED,18次中断走完一轮。

// 接线表:每一行是LED的阳极、阴极所在IO编号,按第3节的映射表填写 const unsigned char led_table[18][2] = { {0,1}, {1,0}, {0,2}, {2,0}, {0,3}, {3,0}, {0,4}, {4,0}, {1,2}, {2,1}, {1,3}, {3,1}, {1,4}, {4,1}, {2,3}, {3,2}, {2,4}, {4,2} }; unsigned char led_buf[18]; unsigned char cur_idx = 0; // 每次定时器中断调用一次 void charlie_step(void) { unsigned char i; charlie_all_input(); // 关掉上一个LED,留出死区 for (i = 0; i < 18; i++) { cur_idx = (cur_idx + 1) % 18; if (led_buf[cur_idx]) { break; } } if (led_buf[cur_idx]) { charlie_on(led_table[cur_idx][0], led_table[cur_idx][1]); } }

这段代码的巧妙之处在于,不论当前有哪些LED需要亮,中断周期都是固定的。cur_idx始终在18个位置里循环,灭着的段被跳过,亮的段被点亮,所以整个扫描帧周期恒定为18个中断周期,不会出现“亮的LED越多,刷新率越低”的问题。

4.3 点亮“188”和主循环

固定显示“188”时,只需要初始化一次显示缓冲区:

void init_display_188(void) { // 左边“1”:L_B、L_C亮 led_buf[0] = 1; led_buf[1] = 1; // 中间“8”:M_A ~ M_G全亮 led_buf[2] = led_buf[3] = led_buf[4] = 1; led_buf[5] = led_buf[6] = led_buf[7] = led_buf[8] = 1; // 右边“8”:R_A ~ R_G全亮 led_buf[9] = led_buf[10] = led_buf[11] = 1; led_buf[12] = led_buf[13] = led_buf[14] = led_buf[15] = 1; // 小数点先不亮 led_buf[16] = 0; led_buf[17] = 0; }

初始化定时器,把中断周期设在250μs左右,一轮扫描18步就是4.5ms,等效刷新率约222Hz,肉眼看不出闪烁。主循环只需要开全局中断,然后该干嘛干嘛,显示完全由中断维护:

void main(void) { timer0_init_250us(); // 初始化定时器,250us中断一次 init_display_188(); EA = 1; // 开总中断 while (1) { // 其他业务逻辑 } }

如果以后想让数字变化,直接改led_buf里的内容就行。不过要记住,第一位的硬件只接了2段,它没法显示除“1”以外的数字,这是“188专用显示”的天然限制。

5. 实测中的三个坑:鬼影、亮度不匀、IO发热

5.1 鬼影:切换瞬间的短路路径

查理复用跑起来最容易看到的现象是“毛刺”,就是不该亮的LED隐约亮一下,这叫鬼影。根源在IO切换的瞬间:假设上一颗LED是A高B低,下一颗是C高D低,如果B还没来得及从低电平释放,C已经输出高电平,而B和C之间恰好还有一颗LED,它就会在这个瞬间被点亮。

解决办法就是4.1节代码里已经体现的:每一次切换之前,先把全部IO切成高阻,留出几个NOP的死区时间,再切目标引脚。高阻状态下所有输出路径都断开了,旧状态不会和新状态交叉。实测下来,只要保证“全高阻→死区→输出”这个顺序,鬼影基本能消除。如果还看到轻微鬼影,在charlie_all_input之后加三五个空指令,效果立竿见影。

5.2 亮度忽明忽暗:帧周期不稳定的连锁反应

查理复用方案里有一类隐蔽的亮度问题:如果你用“只扫描需要亮的LED”的思路——比如这次的18颗LED只亮了16颗,你让扫描函数跳过灭的两颗,直接缩短一轮的步数——那么显示不同数字时,因为亮的颗数不同,每颗LED的占空比会变化,看起来就是亮度跟着显示内容一起跳变。

这也是我坚持固定扫描18步、灭的段也占一个中断周期的原因。不管当前实际亮了多少颗,每颗LED的点亮间隔都是固定的18个中断周期,占空比恒定,亮度自然稳定。这个经验是拿示波器一点一点跟出来的,建议直接抄。

5.3 IO发热和电流极限

查理复用在同一时刻只有2个IO在出力,看上去单片IO的电流压力不大。但要注意峰值电流取值:红色LED压降按2.0V算,3.3V供电、100Ω电阻时峰值电流约11mA,IO口一般能承受20~30mA,单看没有风险。问题是脉冲电流的瞬时速率比较高,加上扫描切换瞬间多个引脚同时翻转,如果供电走线细、退耦电容离芯片远,就可能出现VDD跌落或者地弹,表现为亮度闪烁甚至系统复位。

应对措施有三条:一是电源引脚旁边放一个100nF和一个10μF电容,位置尽量贴近MCU;二是峰值电流不要一味往30mA顶,15mA以内是比较舒服的工作点;三是如果扫描时发现IO发烫,优先检查是不是某颗LED接反了——接反的LED虽然不亮,但反向击穿时可能把电流导到别的地方,产生额外损耗。还有一个不常见的坑:高速扫描时,LED结电容和走线电感形成的振铃,可能在IO上产生超过VDD的尖峰电压。低压MCU余量不大时,可以在靠近IO的地方加一个5V肖特基二极管做钳位,但这属于锦上添花,低速扫描基本用不上。

6. 往下走:从5个IO到6个IO,以及按键复用

6.1 6个IO能直接把通用三位数码管拿下

5个IO的20颗LED容量卡死了“全功能三位数码管”的路,但如果IO多一点,比如6个,6×(6-1)=30颗LED,情况就完全不一样。一套标准的3位7段数码管,不带小数点是21颗LED,带3个小数点是24颗,全塞进30颗的容量里还有富余。

这意味着只要把IO从5个加到6个,就能把“188专用显示”升级成“任意三位数字显示”,接线和代码逻辑一模一样,只是映射表变长、扫描步数变多。如果你在选型阶段,手里还有什么功能要和显示抢IO,强烈建议算一笔账:多留1个IO,显示方案的天花板能从专用跳到通用。

6.2 空闲IO复用出按键功能

第3节里空出的D-E这对IO,除了接状态灯,还能用来做按键检测。思想还是那个“交叉”的思路:显示扫描期间D-E不参与任何LED驱动,可以完全置为高阻;在按键检测窗口,把D输出高、E输出低,如果它们之间被一个按键短接,IOE就能读到低电平。反过来E高D低,又能识别另一个方向的按键。类似的拓扑再配合IO的内部上下拉,几个IO就能撬动比IO数量更多的按键,这就是网上常说的“两个IO口做4个按键”的原理。

做按键复用要特别注意时序隔离:检测按键时,必须先把所有IO切成高阻,确保LED完全关断,再切到按键检测模式。否则LED支路会对电平判断形成干扰,按一次键可能出现闪好几下LED的怪现象。这几个功能都放在定时器的不同时间片里做,逻辑上不冲突。

我在这个项目上最大的体会是:查理复用的原理确实不复杂,但真正决定成败的是硬件结构。第一次我拿着成品共阴数码管试,按常规思路怎么接都不对,后来才想明白每颗LED必须独立引出两端这个前提。把映射表和电阻方案先定死,代码反而是最顺理成章的一步。如果大家也想在TM52F1363或者其他51内核MCU上复刻,建议先把LED接线图画清楚再动手,顺序一旦搞反,排查鬼影和亮度问题会让你怀疑人生。

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

DNGM(1,1)灰色预测模型:原理、Python实现与销量预测实战

做数据分析这几年&#xff0c;最头疼的往往不是算法不够高级&#xff0c;而是手头数据实在太少。刚接到一个客户项目时&#xff0c;历史销量可能只有几个月的样本&#xff0c;别说对照周期&#xff0c;连基本统计显著性都凑不出来。这时候硬上ARIMA、回归这类大样本模型&#x…

作者头像 李华
网站建设 2026/9/17 2:50:22

Notepad-- 实操指南:从安装到批量查找、文件对比的完整流程

Notepad-- 实操指南&#xff1a;从安装到批量查找、文件对比的完整流程 【免费下载链接】notepad-- 一个支持windows/linux/mac的文本编辑器&#xff0c;目标是做中国人自己的编辑器&#xff0c;来自中国。 项目地址: https://gitcode.com/GitHub_Trending/no/notepad-- …

作者头像 李华
网站建设 2026/9/17 2:49:58

AIGC检测率太高?9个降AI率工具实测与论文降重完整流程

开学季一到&#xff0c;后台私信里塞满了同一个问题&#xff1a;“学长&#xff0c;论文用AI写的&#xff0c;AIGC检测28%&#xff0c;怎么降下来&#xff1f;”说真的&#xff0c;每次看到这种问题我都有点恍惚&#xff0c;感觉现在本科生的论文焦虑已经从“查重”转移到了“查…

作者头像 李华