尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

51单片机16x16点阵驱动:74HC595级联、扫描显示与Proteus仿真

51单片机16x16点阵驱动:74HC595级联、扫描显示与Proteus仿真 简介本资源为基于AT89C51单片机的16x16点阵LED电子显示屏设计说明文档面向电子信息、自动化等专业的单片机课程设计、毕业设计学习者及入门开发者。文档围绕以AT89C51为核心的控制系统讲解如何用一片74HC154行驱动器配合两片74HC595列驱动器由4块8x8点阵模块拼成16x16点阵实现全屏显示一个汉字的动态显示方案。压缩包内仅1个doc文件约392KB集中收纳了硬件设计思路、行与列驱动电路原理、总体电路、汇编与C语言程序设计及使用说明、附录程序清单等章节内容。读者可据此理清点阵屏行列扫描与动态显示的实现脉络掌握点阵数据提取、字库调用和显示点阳极阴极电平控制方法并参考电路图与程序完成自己的课程设计或实物调试。目前已有139人学习下载适合需要完整设计报告与排错思路的初学者参照。1. 32 根 IO 不是真正的问题16 行加 16 列16x16 点阵看起来刚好吃掉 51 单片机 P0 到 P3 全部 32 根 IO。很多人就是照这个式子开始画原理图的焊完才发现 P0 是开漏结构、不加上拉推不动串口烧录又占了 P3.0/P3.1再想挂个按键或者蜂鸣器就没位置了。真正卡住的是电流。一行 16 个 LED 同时发光峰值电流能到 200mA 量级任何一款 51 的单个 IO 都撑不住整颗芯片的总灌电流也就几十毫安。所以 16x16 点阵的设计重点从来不是把 32 根线接满而是用尽量少的 IO 把大电流移到片外去解决。这份设计说明要走的链路是这样先用 4 片 74HC595 把 32 路输出压到 3 根 IO 上再把行扫描时序、消隐、字模取模方式讲清楚然后是 Proteus 仿真怎么拼出 16x16最后落到实物上电后的排查顺序。2. 16x16 点阵的极性判断与驱动链路选型2.1 共阳还是共阴先确定扫描极性再画图市面上一体化的 16x16 点阵模块不多更常见的做法是拿 4 块 8x8 模块1088AS、1088BS 这类拼。每块 8x8 是 8 行加 8 列共 16 个引脚4 块拼起来之后行线按上下分成 16 路列线按左右分成 16 路总数还是 32 根。这里必须先确认模块的公共端极性因为它直接决定扫描时行和列各输出什么电平。很多人抄了一份代码发现全屏亮反、该亮的不亮八成就是共阳共阴搞混了。模块类型公共端点亮条件行驱动应输出列驱动应输出共阴1088AS行 阴极行低、列高低电平灌电流高电平拉电流共阳1088BS行 阳极行高、列低高电平拉电流低电平灌电流判断方法很土但有效用万用表二极管档红表笔固定接某一脚黑表笔依次扫其余引脚如果有一组能点亮且红笔那脚是公共端那就是共阳反过来黑笔固定、红笔扫就是共阴。连线时要特别注意跨模块的并联关系。左上和右上共享行 0 到行 7左下和右下共享行 8 到行 15左列 0 到 7 由左上和左下并联右列 8 到 15 由右上和右下并联。这样接完任意时刻选中一行只有这一行上的 16 个 LED 可能点亮另外三块因为行没被选中而保持熄灭。2.2 为什么用 74HC595 级联而不是 74HC138 译码行选常见的另一种做法是 74HC138 加反相器或者直接用 74HC154 做 4-16 译码。译码方案的优点是行选逻辑简单缺点是输出低有效如果点阵是共阳的还得再加一级反相而且列数据仍然要另外找 16 路输出。方案占用 IO行选实现列数据实现主要问题直接 IO 驱动32P1/P2 八位P0/P3 八位电流超限IO 全占满74HC138 x2 译码4 34 根地址线2 片 595低有效需反相行电流仍要外扩74HC595 x4 级联32 片 5952 片 595需要一次性移 32 位时序稍复杂4 片 595 串成一条链只需要数据SER、移位时钟SRCLK、锁存时钟RCLK三根线行和列的数据一起移进去再统一锁存。省下来的 IO 可以接按键、温度传感器或者串口这是我在实际项目里更倾向的方案。需要注意的是 74HC595 本身的电流能力。它的每个输出脚连续电流上限大约 35mA但 VCC 和 GND 引脚的总电流上限只有 70mA 左右。一片 595 带 8 列如果 8 列同时点亮、每列 12mA总电流就到 96mA 了已经越界。所以列驱动后面要接 ULN2803 这类达林顿阵列行驱动因为要输出高电平得用 UDN2981 或者 8550 三极管。2.3 限流电阻和峰值电流的计算扫描显示的平均亮度取决于峰值电流和占空比。16 行扫描的占空比是 1/16如果按静态 5mA 去设计实际看上去会非常暗所以峰值电流要往上提。# 16x16 点阵限流电阻与功耗估算 VCC 5.0 # 系统供电 Vf 2.0 # 红色 LED 正向压降绿/蓝约 3.0~3.2 Vce 0.3 # 驱动器饱和压降ULN2803 约 0.9~1.2三极管约 0.3 If_peak 0.012 # 目标峰值电流 12mA R (VCC - Vf - Vce) / If_peak print(限流电阻计算值%.1f 欧 % R) print(取标称值 220 欧实际峰值电流 %.1f mA % ((VCC - Vf - Vce) / 220 * 1000)) # 单行全亮时的总电流 I_row 16 * (VCC - Vf - Vce) / 220 print(单行全亮总电流%.0f mA % (I_row * 1000))按 220Ω 算下来峰值电流约 12mA一整行 16 个 LED 全亮时行驱动要过 190mA 左右这就是为什么行驱动必须上三极管或专用驱动芯片。如果点阵是共阳、行接阳极建议用 UDN2981它 8 通道、每通道 500mA两片正好覆盖 16 行如果是共阴、行接阴极ULN2803 两片就够了。限流电阻放在列线上每列一个不要图省事整片共用一个。因为每一列在同一时刻只对应一个 LED单列电流就是 12mA220Ω 的 1/4W 电阻完全够用。3. 51 单片机 16x16 点阵的 C51 扫描代码3.1 74HC595 的字节发送函数与时序595 的移位是上升沿有效数据在 SRCLK 上升沿被读入移位寄存器32 位全部移完之后给 RCLK 一个上升沿数据才出现在输出脚上。整个过程中 OE 要保持低电平使能输出。#include reg52.h sbit SER P1^0; // 74HC595 串行数据输入 sbit SRCLK P1^1; // 移位寄存器时钟上升沿移入 sbit RCLK P1^2; // 存储寄存器时钟上升沿锁存输出 sbit OE P1^3; // 输出使能低有效用于 PWM 调光 /* 发送一个字节先发最高位 */ void HC595_WriteByte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { SER (dat 0x80) ? 1 : 0; // 取最高位送到数据线 dat 1; // 左移准备下一位 SRCLK 0; SRCLK 1; // 上升沿595 采样数据线 } } /* 一次发送整帧行高、行低、列高、列低共 32 位 */ void HC595_SendFrame(unsigned char rowH, unsigned char rowL, unsigned char colH, unsigned char colL) { HC595_WriteByte(rowH); // 最先发的位会走到链尾顺序由硬件接线决定 HC595_WriteByte(rowL); HC595_WriteByte(colH); HC595_WriteByte(colL); RCLK 0; RCLK 1; // 锁存32 路输出同时更新 }这里最容易出错的是发送顺序。74HC595 是串入并出第一个发出去的位会一路移到链的最末端。假设硬件连接是 MCU → 列低 → 列高 → 行低 → 行高那么最后发出去的那一字节才会留在第一片 595 上。调整顺序的办法只有两个要么按硬件实测结果调换调用次序要么在原理图阶段就把 4 片的串联顺序定义清楚再写代码。如果发现显示出来整个画面是镜像或者上下颠倒不用改代码直接交换 rowH 和 rowL 的调用位置就能验证是不是字节序问题。3.2 定时器 T0 中断里的行扫描与消隐扫描周期要控制在 16ms 以内否则人眼会看到明显闪烁。16 行分下来每行 1ms 对应约 60Hz 刷新率为了留余量我一般把 T0 设成 2ms 一行、8 位行序列分两轮走完或者直接 1ms 一行。unsigned char code RowMask[16] { 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80, // 行 0~7对应行低字节 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80 // 行 8~15对应行高字节 }; unsigned char xdata FrameBuf[32]; // 16 行 x 2 字节的显示缓冲区 unsigned char scanRow 0; void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; // T0 方式116 位定时 TH0 (65536 - 1000) / 256; // 12MHz 晶振下定时 1ms TL0 (65536 - 1000) % 256; ET0 1; EA 1; TR0 1; } void Timer0_ISR(void) interrupt 1 { unsigned char rowL 0, rowH 0; TH0 (65536 - 1000) / 256; TL0 (65536 - 1000) % 256; /* 1. 先消隐把行输出全部关掉避免上一行的残影串到下一行 */ HC595_SendFrame(0x00, 0x00, 0x00, 0x00); /* 2. 组装当前行的行选信号 */ if (scanRow 8) rowL RowMask[scanRow]; else rowH RowMask[scanRow]; /* 3. 发送当前行的列数据两字节和行选信号 */ HC595_SendFrame(rowH, rowL, FrameBuf[scanRow * 2], FrameBuf[scanRow * 2 1]); scanRow; if (scanRow 16) scanRow 0; }中断函数里有三件事必须按顺序做先消隐、再组装行选、最后发帧。消隐这一步不能省否则上一行的列数据会和新行选叠加出现一条竖着的“鬼影”。发帧时列数据的两个字节顺序和字模高低位要对上如果显示出来左右反了把FrameBuf[scanRow*2]和FrameBuf[scanRow*21]互换即可。3.3 字模怎么取让 32 字节和扫描顺序对齐16x16 汉字每个字符 32 字节按逐行式取模时每行 2 字节、共 16 行。取模软件设置必须和扫描代码里的列字节顺序一致这是新手最容易踩的坑。设置项推荐值说明点阵格式阴码点亮位为 1与 FrameBuf 直接对应取模方式逐行式每行 2 字节共 16 行和扫描顺序一致取模走向顺向高位在前对应代码里dat 0x80先发最高位输出格式C51 数组直接贴进code区省手动改格式/* “单”字 16x16 阴码、逐行式、顺向共 32 字节 */ unsigned char code Zi_Dan[32] { 0x00,0x00,0x1F,0xF0,0x10,0x10,0x10,0x10, 0x1F,0xF0,0x10,0x10,0x10,0x10,0x1F,0xF0, 0x00,0x00,0x3F,0xF8,0x01,0x00,0x01,0x00, 0x01,0x00,0x01,0x00,0x7F,0xFE,0x00,0x00 }; void LoadZi(unsigned char *zi) { unsigned char i; for (i 0; i 32; i) FrameBuf[i] zi[i]; // 直接平移到扫描缓冲区 }如果取模时选了“逆向低位在前”显示出来会变成横条纹错位的乱码这时候要么改取模设置要么在发送前对每个字节做一次位反转。实测下来直接把取模软件设置改对比在代码里加位反转函数稳妥得多。4. Proteus 仿真搭建与实物调试排查4.1 用 4 个 8x8 元件拼出 16x16 点阵Proteus 元件库里通常找不到现成的 16x16 点阵。常见做法是在元件搜索框里找MATRIX-8X8-RED共阳或MATRIX-8X8-GREEN共阴放 4 个按象限拼成 2x2 阵列然后手动连线。连线时注意 Proteus 的矩阵元件引脚方向和实物模块可能相反元件属性里能看到ROW和COL的说明先点进去确认公共端是哪一侧。4 个模块的行线按上下两组并联列线按左右两组并联总线用BUS工具画比一根根拉线清晰。驱动部分用74HC595加ULN2803时钟源选AT89C52加 12MHz 晶振与复位电路。如果嫌 4 片 595 连线太乱仿真阶段也可以只画 1 片 595 加 8x8 点阵先验证扫描逻辑跑通之后再扩到 16x16。4.2 不亮、鬼影、闪烁的排查表现象常见原因处理方法全屏不亮OE 悬空、RCLK 没接、P0 无上拉测 OE 电平量锁存脚波形P0 加 10k 上拉排阻只有一行亮行选只发了一个字节、串接顺序反了检查 HC595 链上 4 片的输出对应哪 8 路有竖条鬼影换行前未消隐在中断开头补一次全零发送再发新帧整屏闪烁扫描周期大于 20ms把 T0 重载值改小或减少每行停留时间亮度不均、某列偏暗该列限流电阻偏大、595 输出脚虚焊逐列量电阻补焊 595 输出脚显示左右镜像字模取模走向反了或列字节顺序颠倒交换列高、列低两个字节排查时建议把扫描频率先降下来比如改成 100ms 一行。这样人眼能直接看到当前扫到哪一行、哪一列亮鬼影和错位会变得非常明显比盯着 60Hz 的模糊画面猜要快得多。4.3 从仿真到烧录上电自检的正确顺序仿真跑通不等于实物能跑因为实物的电流和线阻是仿真里没有的。我的习惯是先写一段自检程序按固定顺序点亮上电后用肉眼逐项确认。/* 上电自检全亮 - 逐行走点 - 逐列走点 */ void SelfTest(void) { unsigned char i, j; unsigned char *p; /* 1. 全屏点亮 1 秒检查所有 LED 是否都能亮 */ for (i 0; i 32; i) FrameBuf[i] 0xFF; for (i 0; i 200; i) Delay1ms(); /* 2. 逐行走点每行停 300ms检查行驱动是否逐路正常 */ for (i 0; i 16; i) { for (j 0; j 32; j) FrameBuf[j] 0x00; FrameBuf[i * 2] 0x80; // 每行最左一个点 DelayMs(300); } /* 3. 逐列走点检查列驱动和限流电阻 */ for (i 0; i 16; i) { for (j 0; j 32; j) FrameBuf[j] 0x00; p FrameBuf[0]; FrameBuf[(i / 8) * 2 (i % 8) * 2] 0x01; // 按列位置置位 DelayMs(300); } }自检时先测电源电压4 片 595 加两片驱动芯片全亮的时候电流会到几百毫安用 5V/1A 的电源比较稳。上电前先用万用表量 VCC 和 GND 之间有没有短路。烧录器下载时记得把 P3.0/P3.1 上的外设断开否则下载容易失败。5. 16x16 点阵做滚动显示与 PWM 调光的落地写法滚动显示的关键是把每一行当成一个 16 位无符号数来移位而不是按字节移。按字节移会破坏跨字节的进位出现一列一列跳着走的情况。/* 整屏左移一列每行独立移位不跨行进位 */ void ScrollLeft(void) { unsigned char i; unsigned int v; EA 0; // 移位期间关中断避免扫描到半帧数据 for (i 0; i 16; i) { v ((unsigned int)FrameBuf[i * 2] 8) | FrameBuf[i * 2 1]; v 1; FrameBuf[i * 2] (unsigned char)(v 8); FrameBuf[i * 2 1] (unsigned char)(v 0xFF); } EA 1; }16 位拼接再左移的做法保证高位字节的低位能正确进到下一个字节。每次调用移一列滚动速度靠调用间隔控制把这段放进 T0 中断里每 16 帧移一次效果就是每秒移动约 4 列视觉上比较舒服。注意移位前关中断否则可能出现半帧新数据、半帧旧数据同时显示。调光用 595 的 OE 脚最省事。把 OE 从 P1.3 接出来用定时器 T1 产生一个 1kHz 左右的 PWM占空比越大、点亮时间越长画面越亮。unsigned char duty 8; // 占空比 0~1010 为最亮 void Timer1_Init(void) { TMOD 0x0F; TMOD | 0x10; // T1 方式1 TH1 (65536 - 100) / 256; // 100us 一步10 步一个 PWM 周期 TL1 (65536 - 100) % 256; ET1 1; TR1 1; } unsigned char pwmCnt 0; void Timer1_ISR(void) interrupt 3 { TH1 (65536 - 100) / 256; TL1 (65536 - 100) % 256; pwmCnt; if (pwmCnt 10) pwmCnt 0; OE (pwmCnt duty) ? 0 : 1; // 0 使能1 关闭 }T1 和 T0 同时跑的时候要注意中断优先级。扫描中断负责刷新PWM 中断负责亮度两者都不要写太长否则会互相拖慢。如果发现调光时画面开始抖先把 T0 的重载值算准确认 1ms 的扫描间隔没有被 T1 打断太久。亮度调节不建议直接改限流电阻因为那是固定值改起来要动硬件。用 OE 做软件调光可以在同一个固件里针对不同环境亮度切换占空比白天调到 10、夜里调到 3 都不需要重新焊板子。本文还有配套的精品资源点击获取
返回列表