简介:这份资源面向电子信息、自动化等专业的单片机课程设计与入门开发者,围绕51单片机驱动16×16 LED点阵屏展开,可完成姓名与学号显示、独立按键切换闪烁/静止/上下平移滚动等效果,静止时通过DS18B20读取并显示当前温度,显示姓名时蜂鸣器发出三短一长提示音,是一套可直接复现的完整课程设计参考方案。压缩包共53个文件、约30.48MB,包含C语言源码与头文件、Proteus仿真工程、Altium原理图、程序流程图、元件清单表格、字模提取工具及演示视频等,覆盖从硬件搭建到软件调试的主要环节。目前已有625人学习下载,适合需要快速理解点阵扫描、74HC595驱动与温度采集联动逻辑的读者对照实践。
1. 16x16 点阵上滚动学号姓名:这套 51 单片机方案到底解决什么问题
答辩现场最容易被问住的,不是代码写没写出来,而是"你这块屏为什么能显示你的名字"。16x16 点阵显示控制系统,本质是用 STC89C52RC 这类 51 单片机,通过行扫描加列数据的方式,在一块 256 个 LED 的矩阵上动态刷新出汉字和数字。它要解决的核心矛盾只有一个:单片机 IO 口不够用,但要点亮的灯有 256 个。所以必须引入 74HC595 做列驱动、74HC138 或三极管做行选通,把 32 个 IO 压缩成"4 位行地址 + 1 位串行数据 + 2 位时钟"。
这套东西适合三类人:课程设计要交实物加仿真的学生、想从点灯进阶到驱动矩阵的 51 入门者、以及需要一份完整 Proteus 原理图和源码做二次开发的人。学号和名字不是装饰,它是验证扫描时序对不对的最直观标尺——字能稳定滚动不拖影,说明行扫描频率和消隐都调对了。下面按"原理图怎么连、字模怎么取、代码怎么写、仿真怎么跑"的顺序拆开讲。
2. 原理图与物料清单:16x16 点阵的引脚到底怎么接
2.1 行扫描与列驱动的分工逻辑
16x16 点阵内部是 16 行 16 列,共 256 个发光二极管。如果直接用 51 的 32 个 IO 去驱动,STC89C52RC 总共才 32 个 IO,接完屏就没口可用了,而且 51 单个 IO 灌电流有限,一行 16 个灯同时亮会直接烧口。常见做法是行列分治:行线接 74HC138 译码器,用 3 根地址线选通 16 行中的一行;列线接两片 74HC595 级联,用串行方式把 16 位列数据打进去。
这样单片机只需要 P1.0~P1.2 给 138 地址、P2.0 给 595 数据、P2.1 给移位时钟、P2.2 给锁存时钟,一共 6 个 IO 就能控制整屏。行选通一次只亮一行,靠视觉暂留让人眼看到完整图形,所以刷新率必须够高,一般整屏扫完一轮控制在 5ms 以内,对应每行约 300μs。
提示:行驱动建议用 PNP 三极管(如 8550)做高电平选通,列驱动用 74HC595 灌电流拉低。这样行给高、列给低,对应 LED 点亮,符合共阳点阵的常规接法。
2.2 物料清单与 Proteus 元件对照
物料清单不用写得多花哨,但每一项都要能在 Proteus 里找到对应元件,否则仿真跑不起来。下面这张表是我实际用过的配置,元件名按 Proteus 元件库的检索关键词写,方便直接搜。
| 元件 | 型号/参数 | 数量 | Proteus 检索词 | 作用 |
|---|---|---|---|---|
| 单片机 | STC89C52RC | 1 | AT89C52 | 主控 |
| 点阵屏 | 16x16 共阳 | 1 | MATRIX-16X16 | 显示 |
| 移位寄存器 | 74HC595 | 2 | 74HC595 | 列驱动 |
| 译码器 | 74HC138 | 1 | 74HC138 | 行选通 |
| 三极管 | 8550 PNP | 16 | PNP | 行电流放大 |
| 晶振 | 11.0592MHz | 1 | CRYSTAL | 时钟 |
| 电容 | 30pF | 2 | CAP | 起振 |
| 电阻 | 220Ω | 16 | RES | 列限流 |
| 电阻 | 1kΩ | 16 | RES | 三极管基极 |
| 排阻 | 10kΩ | 1 | RESPACK-8 | P0 上拉 |
Proteus 里 16x16 点阵不一定叫 MATRIX-16X16,有些版本是 MATRIX-8X8 拼两个,或者用 LED-MATRIX-16X16。如果搜不到,就用两个 8x8 横向拼接,行线并联、列线分开接两片 595。晶振选 11.0592MHz 是为了串口波特率精确,纯显示用 12MHz 也行,但定时器初值要重算。
2.3 原理图连线步骤
连线顺序建议从电源和最小系统开始,再往外扩,避免中途发现单片机都没起振。
第一步,接最小系统。STC89C52RC 的 40 脚 VCC 接 +5V,20 脚 GND 接地,9 脚 RST 接复位电路(10μF 电容到 VCC、10kΩ 电阻到 GND),18、19 脚接晶振和两个 30pF 电容。EA 脚(31 脚)接 VCC,表示使用内部程序存储器。
第二步,接行驱动。P1.0、P1.1、P1.2 分别接 74HC138 的 A、B、C 输入,138 的 Y0~Y15 输出分别接 16 个 8550 的基极(串 1kΩ 电阻),8550 发射极接 +5V,集电极接点阵的 16 行。
第三步,接列驱动。P2.0 接第一片 595 的 DS(14 脚),第一片的 Q7'(9 脚)接第二片的 DS,两片 595 的 SHCP(11 脚)并联接 P2.1,STCP(12 脚)并联接 P2.2,MR(10 脚)接 VCC,OE(13 脚)接 GND。两片 595 的 Q0~Q7 共 16 个输出,各串 220Ω 电阻后接点阵的 16 列。
第四步,检查共阳共阴。共阳点阵的行是阳极、列是阴极,所以行给高电平、列给低电平才亮。如果仿真里全亮或全不亮,先查这个极性。
3. 字模提取与显示代码:学号姓名怎么变成 16 位数据
3.1 取模软件的参数设置
字模提取是这套系统里最容易翻车的一步,参数设错,屏上就是一堆乱码。我一般用 PCtoLCD2002 或类似的取模软件,关键设置如下:点阵格式选"阴码"还是"阳码"取决于你的列驱动逻辑,共阳屏配 595 灌电流,通常选"阴码"(0 表示亮);取模方式选"逐行式"还是"逐列式"要和你代码里的扫描顺序一致,我这里按逐行扫描、每行 16 位、高位在前来取。
具体操作:在软件里新建 16x16 点阵,输入"学号+姓名"的文字,比如"20230101张三",设置字体为宋体 12 号,然后生成字模数组。每个汉字占 32 字节(16 行 × 2 字节/行),数字和字母占 16 字节(16 行 × 1 字节/行,高位补 0)。取完后导出为 C 数组格式,直接粘进代码。
注意:取模时一定要确认"字节倒序"和"位倒序"两个选项。如果显示出来是镜像或者上下颠倒,就是这两个选项设反了,不用改代码,回取模软件勾一下就行。
3.2 字模数组的组织方式
假设要显示"20230101张三",取模后得到一个二维数组,每个元素是一行的 16 位数据。为了滚动方便,我习惯把所有字符拼成一个长的一维数组,每个字符 32 字节,滚动时按字节偏移取窗口。
// 16x16 点阵字模,每个字符 32 字节,逐行取模,高位在前 // 字符顺序:2 0 2 3 0 1 0 1 张 三 unsigned char code font[] = { // '2' 前16字节为上半部分,后16字节为下半部分(实际逐行取模为32字节连续) 0x00,0x00,0x1F,0xF0,0x10,0x10,0x10,0x10, 0x10,0x10,0x10,0x10,0x1F,0xF0,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // '0' 0x00,0x00,0x0F,0xE0,0x10,0x10,0x10,0x10, 0x10,0x10,0x10,0x10,0x0F,0xE0,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 后续字符按同样格式追加... };上面这段只是示意结构,实际取模软件生成的数组每个字符是 32 个字节连续排列,对应 16 行、每行 2 字节。代码里通过font[offset + row*2]和font[offset + row*2 + 1]取出某一行的高 8 位和低 8 位,合成 16 位列数据。offset 就是字符序号乘以 32。
3.3 扫描驱动代码与参数说明
扫描的核心是定时器中断,每进一次中断换一行,同时把该行的 16 位列数据通过 595 打出去。定时器初值决定刷新率,11.0592MHz 晶振下,定时 300μs 对应初值约 0xFE80 附近,具体用公式65536 - 300e-6 * 11059200 / 12算。
#include <reg52.h> sbit DS = P2^0; // 595 串行数据 sbit SHCP = P2^1; // 595 移位时钟 sbit STCP = P2^2; // 595 锁存时钟 sbit A138 = P1^0; // 138 地址 A sbit B138 = P1^1; // 138 地址 B sbit C138 = P1^2; // 138 地址 C unsigned char row = 0; // 当前扫描行 0~15 unsigned int offset = 0; // 滚动偏移,单位字节 // 向 595 发送 16 位数据,先高 8 位后低 8 位 void hc595_send(unsigned char high, unsigned char low) { unsigned char i; for (i = 0; i < 8; i++) { // 先发高字节 DS = (high & 0x80) ? 1 : 0; high <<= 1; SHCP = 0; SHCP = 1; // 上升沿移位 } for (i = 0; i < 8; i++) { // 再发低字节 DS = (low & 0x80) ? 1 : 0; low <<= 1; SHCP = 0; SHCP = 1; } STCP = 0; STCP = 1; // 上升沿锁存输出 } // 定时器0中断,每 300us 换一行 void timer0_isr() interrupt 1 { TH0 = 0xFE; TL0 = 0x80; // 重装初值 // 行选通:先消隐,再换行 A138 = row & 0x01; B138 = (row >> 1) & 0x01; C138 = (row >> 2) & 0x01; // 取当前行数据,offset 控制滚动 hc595_send(font[offset + row*2], font[offset + row*2 + 1]); row++; if (row >= 16) row = 0; } void main() { TMOD = 0x01; // 定时器0 模式1 TH0 = 0xFE; TL0 = 0x80; EA = 1; ET0 = 1; TR0 = 1; while (1) { // 主循环里控制 offset 递增实现滚动 // 每 50ms 偏移一个字节,速度可调 } }参数说明:TH0/TL0决定每行停留时间,值越小刷新越快但单行亮度越低;offset每次加 1 表示左移一个字节(8 像素),加 2 表示移一个完整字符宽度。滚动速度靠主循环里的延时控制,延时太长会看到明显跳动,太短则字还没看清就过去了,一般 30~80ms 移一字节比较舒服。
提示:595 发送顺序要和取模顺序严格对应。如果显示出来左右两半颠倒,把
hc595_send里高低字节的发送顺序对调即可,不用重取字模。
4. Proteus 仿真与联调:从全亮到稳定滚动的排查路径
4.1 仿真工程搭建与 HEX 加载
Proteus 里搭好原理图后,双击单片机,在 Program File 栏加载 Keil 编译出的 .hex 文件,Clock Frequency 填 11.0592MHz,和代码里的晶振一致。如果 Proteus 提示许可证没正确配置,通常是安装时 license 没导入,重新跑一遍安装目录下的 License 管理工具即可,这和电路本身无关。
仿真跑起来后,先别急着看滚动效果,第一步确认静态显示。把 offset 固定为 0,看第一屏是不是完整的"2"。如果全亮,查 138 的使能脚 E1、E2 是不是接了低电平、E3 接了高电平;如果全灭,查 595 的 OE 是不是误接了 VCC。这两个使能脚接错是新手最常见的翻车点。
4.2 拖影与闪烁的时序调整
滚动时如果看到字后面拖一条尾巴,说明消隐没做。所谓消隐,就是在换行之前先把 595 输出全部关掉,或者先把 138 的地址切到无效行,等数据稳定后再选通新行。我一般在上面的中断里,把行选通放到hc595_send之后,并且发送数据前先把 OE 拉高(如果 OE 接了 IO 控制),发完再拉低。
闪烁则是刷新率不够。16 行每行 300μs,一轮 4.8ms,对应约 208Hz,人眼基本看不出闪。如果每行给到 1ms,一轮 16ms 就只有 62Hz,边缘会明显抖。用示波器或者 Proteus 的虚拟示波器看 P2.1 的脉冲频率,正常应该在几十 kHz 量级。
4.3 学号姓名滚动不连贯的处理
滚动不连贯通常有两个原因:一是 offset 递增的步长和字符宽度不匹配,比如每个字符 32 字节,你每次加 1,那要 32 次才移完一个字,看起来就是慢慢爬;二是数组边界没处理好,滚到最后一个字符后面没有补空字节,导致突然跳回开头。
我的做法是在 font 数组末尾补 16 个 0x00 作为空白间隔,滚动时 offset 从 0 递增到sizeof(font) - 32,超过就归零。这样字与字之间有间隔,循环也自然。如果想让名字和学号分开滚,就在中间多补几个空白字符的 32 字节。
5. 避坑与常见问题:点阵调试里最容易踩的五个坑
5.1 现象:仿真里点阵全亮,代码没反应
原因:74HC138 的使能端接错。138 有 E1、E2 低有效和 E3 高有效三个使能脚,只要有一个不满足,所有输出都是高电平,行全部选通,配合列数据就是全亮。
解决:E1、E2 接 GND,E3 接 VCC。用万用表或 Proteus 探针确认 138 输出脚在地址变化时有没有正常拉低。
5.2 现象:显示的字上下颠倒
原因:取模时"字节倒序"没勾,或者代码里行扫描顺序和取模顺序相反。取模软件默认从上到下取,如果你的 row 从 15 递减到 0,就会颠倒。
解决:要么在取模软件里勾选字节倒序,要么把中断里的row改成从 15 开始递减。改代码更快,不用重新取模。
5.3 现象:某一列或某一行常亮不灭
原因:595 的某个输出脚虚焊或 Proteus 里连线漏了,也可能是该列的 220Ω 限流电阻没接,导致该列电流异常。实物上还可能是点阵内部该 LED 击穿。
解决:Proteus 里逐脚检查 595 输出到点阵列的连线;实物上用万用表二极管档逐个测 LED。如果是 595 输出常高,查该位的移位数据是不是被下一轮覆盖前就锁存了。
5.4 现象:滚动速度忽快忽慢
原因:主循环里用了delay函数控制 offset,但定时器中断会打断 delay,导致实际延时不准。或者 offset 递增放在了中断里,和扫描节奏耦合。
解决:offset 递增也放到定时器中断里,用一个计数器每 N 次中断加一次,这样滚动速度和扫描频率严格同步,不会受主循环其他代码影响。
5.5 现象:Keil 编译提示 code 空间不够
原因:STC89C52RC 只有 8KB Flash,如果字模数组用unsigned char而不是code修饰,会占用宝贵的 RAM,RAM 只有 256 字节,几个汉字就爆了。
解决:所有字模数组必须加code关键字存到 Flash。如果字符太多超过 8KB,换 STC89C54RD+ 或 STC12C5A60S2,Flash 更大,代码基本不用改。
6. 进阶技巧:用状态机让滚动和闪烁解耦
前面所有逻辑都塞在定时器中断里,能跑,但一旦要加"名字闪烁""学号静止、名字滚动"这类效果,中断里就会堆满 if-else,改一处崩一片。我后来改成状态机:定时器中断只负责最基础的换行扫描,滚动、闪烁、模式切换全部放到主循环的状态机里,用state变量驱动。
// 显示状态机,主循环调用 typedef enum { MODE_STATIC, MODE_SCROLL, MODE_BLINK } Mode; Mode mode = MODE_SCROLL; unsigned char blink_cnt = 0; void display_fsm() { switch (mode) { case MODE_STATIC: offset = 0; // 固定显示第一屏 break; case MODE_SCROLL: // 每 50ms 移一字节,由定时器标志位触发 if (scroll_flag) { scroll_flag = 0; offset++; if (offset > sizeof(font) - 32) offset = 0; } break; case MODE_BLINK: // 每 500ms 翻转一次显示开关 if (++blink_cnt >= 10) { blink_cnt = 0; display_on = !display_on; // 控制 OE 或清空列数据 } break; } }这个结构的好处是,加新模式只需要加一个 case,不用动中断里的扫描代码。scroll_flag在定时器中断里每 160 次置 1(300μs × 160 ≈ 48ms),这样滚动节奏和扫描完全同步。display_on为 0 时,在hc595_send里直接发 0x00、0x00,屏就灭了,比控制 OE 脚更省事。
验证方法很简单:把 mode 依次设为三个值,看屏的反应是否符合预期。如果 MODE_BLINK 下闪烁频率不对,调blink_cnt的阈值;如果 MODE_SCROLL 下滚动一顿一顿,检查scroll_flag是不是被主循环里其他耗时操作拖慢了。
我自己的习惯是,任何显示效果都先在这个状态机里跑通再焊实物,因为 Proteus 里改一个变量重新仿真只要几秒,实物上飞线改一处可能要半小时。这套 16x16 点阵加 51 单片机的组合,硬件成本不到三十块,但把扫描、译码、串并转换、字模、状态机这几个嵌入式核心概念全串了一遍,作为课程设计或者入门练手,性价比很高。希望帮到你。
本文还有配套的精品资源,点击获取