简介:这是一套基于AT89C52单片机LED灯电子沙漏的完整设计资源,包含Altium硬件原理图与印刷电路板工程文件,以及配套的软件源代码。硬件采用AD09设计,覆盖单片机控制板底板和LED显示板两个模块;软件提供Keil完整工程,涉及按键扫描、动态扫描显示与定时中断等常用编程逻辑,并含可烧录的hex文件,可作为电子设计课程或毕业设计参考。资源包共48个文件,以原理图、PCB、工程、C源码、hex等类型为主,另附日志、备份与说明文档,便于理解工程结构和复现开发过程,压缩包约37.54MB。目前已有1590人学习借鉴,方案完整、软硬件结合紧密,既能掌握电子沙漏工作原理,也适合移植到其他LED显示项目或二次开发,学习与扩展价值高。
1. 电子沙漏这个项目,拆开看就三件事
拿到“基于AT89C52单片机LED灯电子沙漏ALTIUM设计硬件原理图PCB+软件源代码.zip”这个压缩包名字,里面其实是一个完整的单片机综合训练项目:一颗AT89C52、一排LED、一块自己画的Altium原理图和PCB、一份单片机C语言源码。电子沙漏不是真的用沙子,而是用LED灯的点亮数量模拟沙子从上部流到下部,时间刻度由单片机定时器控制。常见于课程设计、电子竞赛和毕设预研,难点不在功能逻辑,而在LED矩阵怎么驱动、PCB怎么布局才能让布线不打架、软件怎么用定时器把刷新率和时间流逝同时做对。这篇文章直接按项目实际交付顺序捋一遍,把Altium设计、PCB Layout和源代码三个环节的关键参数和踩坑点讲清楚。
2. 硬件原理图设计:AT89C52的LED矩阵与驱动电路
2.1 电子沙漏的显示结构:LED矩阵怎么编排
电子沙漏的显示区域一般是两个三角形或对称的沙漏形状,每颗LED代表一粒“沙子”。例如上部5行、下部5行,每行2到8颗不等,总共几十颗LED。直接用AT89C52的I/O口是点不亮的:P0口灌电流能力尚可,但总共32个I/O,既要接管矩阵,又要接按键、状态指示,资源完全不够。所以必须把LED接成矩阵形式,用动态扫描方式分时点亮。
一个常用的排列是8列×9行,共72颗LED,足够画出一个对称沙漏轮廓。列线接LED的阴极,通过74HC573锁存器驱动;行线接LED阳极,用PNP三极管做行扫描。当某一行被选中时,该行所有LED的阳极呈高电平,同时列锁存器输出需要亮的列的低电平。扫描完一行再切下一行,刷新频率50Hz以上,肉眼看到的就是连续的点亮效果。
引脚分配参考下表,实际做原理图时按这个表去连线,不容易乱:
| 功能 | AT89C52引脚 | 连接目标 |
|---|---|---|
| 列数据 | P0.0 - P0.7 | 74HC573的D0-D7 |
| 列锁存 | P2.0 | 74HC573的LE |
| 行扫描位0 | P2.1 | 第1行三极管基极 |
| 行扫描位1 | P2.2 | 第2行三极管基极 |
| 行扫描位2 | P2.3 | 第3行三极管基极 |
| ... | ... | ... |
| 行扫描位8 | P2.7 | 第9行三极管基极 |
| 启动按键 | P3.2 | 按键到GND,外部中断0 |
| 复位键 | RST | 10uF电容到VCC,10k电阻到GND |
在写原理图之前,先在工程里新建一个C文件,把引脚宏定义写好,方便后续跟软件代码对应:
#define LED_DATA P0 #define LATCH_LED P2_0 #define ROW0 P2_1 #define ROW1 P2_2 #define ROW2 P2_3 #define ROW3 P2_4 #define ROW4 P2_5 #define ROW5 P2_6 #define ROW6 P2_7这里LATCH_LED是74HC573的锁存端LE,当P2_0从高变低时,P0的数据被锁存在573输出端。为什么选573不用373?573的输入和输出是同一方向,引脚排列更适合规则布线。
2.2 驱动电路:为什么不能直接接I/O口
AT89C52每个I/O口输出高电平时的拉电流只有几十微安,即使灌电流模式也限制在20mA以内。一颗LED正常发光需要5-10mA,整行8颗同时亮就需要至少40mA,单个I/O完全扛不住。更重要的是,74HC573本身也是TTL逻辑门,它的输出灌电流能力一般在20mA左右,也不够直接驱动8颗LED。所以列驱动需要再加限流电阻,行驱动必须用三极管放大。
实际常见的方案是:列线用74HC573输出低电平灌入LED阴极,每列串一个330Ω电阻。行线用PNP三极管(如S8550)作为高边驱动,基极串联1k电阻接到单片机I/O。当单片机输出低电平时,三极管导通,行线输出约5V,该行所有LED阳极就位。计算限流电阻的公式很简单:
R = (VCC - V_LED - V_CE_sat) / I_LED以VCC=5V、红色LED压降1.8V、三极管饱和压降约0.2V、电流I=8mA代入,R=(5-1.8-0.2)/0.008=375Ω,取标称值330Ω或390Ω都行。如果用的是蓝色或白色LED,压降约3V,R就变成(5-3-0.2)/0.008=225Ω,取220Ω。这个计算在原理图阶段就要做,不能等板子回来了再换电阻。
2.3 从原理图到网络表:Altium中的元件库与连线
在Altium Designer 20或21中建工程,先建元件库。AT89C52用DIP40封装,74HC573用SOIC-20或DIP-20,三极管用SOT-23或TO-92。LED矩阵部分,如果自己排列9行8列,最好画一个自定义的元件,把72个引脚在一个封装里排列成沙漏形状,这样在PCB上画板框时有根据。不过更工程化的做法是单独做一个LED阵列模块:原理图里放一个子图,子图内部就是9行8列LED,每颗LED带一个小的限流电阻。
原理图连线时注意:
- 每个电源引脚旁放0.1uF去耦电容,靠近AT89C52的VCC和GND,晶振两个脚对地接20pF电容。
- 复位电路用典型的10uF电解电容加10k电阻,上电自动复位,按键手动复位。
- EA引脚必须接VCC,否则单片机执行外部程序存储器,用不了内部Flash。
- 74HC573的OE引脚直接接地,让它永远使能,LE由单片机控制。
原理图检查通过后,用“Design > Update PCB Document”生成网络表,如果没有错误就进入PCB阶段。
3. 从原理图到PCB:Altium Designer布局布线与铺铜规则
3.1 PCB板框与顶层规划
电子沙漏的PCB最好做成长条状或方形,LED阵列居中排列,这样成品可以安在亚克力框架里。板子尺寸建议不要小于80mm×60mm,否则LED引脚太挤。Altium里用“Keep-Out Layer”画板框,用“Design > Board Shape > Define from selected objects”定义外形。板子选定双层板,这样正面放LED,背面走信号线和电源。
布局前先把元件分成三个区域:电源/复位区在左上角,单片机在左下角,LED阵列填充中间大部分空间。AT89C52的晶振必须靠近单片机X1和X2引脚,走线尽量短,晶振下方不要穿过其他信号线。去耦电容要直接放在VCC引脚附近,过孔到电源平面。
3.2 布局原则:LED阵列居中,扫描芯片贴近矩阵
74HC573锁存器放在LED阵列的右侧,缩短列线走线长度。行驱动三极管排成一行放在矩阵下方,每个三级管基极的1k电阻放在三极管旁边。这样虽然走线多,但回流路径短,扫描时的噪声小。按键放在板边,便于安装。
LED阵列的排列要和原理图里的编号对应。这里有个容易出错的地方:动态扫描时,同一行的LED阳极连在一起,同一列的阴极连在一起。在PCB Layout时,行线用较粗的走线(20mil以上),因为行线瞬间要通一整行的电流,峰值可达60-80mA。列线走10mil就够了。
3.3 布线规则与DRC检查
打开“Design > Rules”,按以下参数设置:
| 规则项 | 数值 | 说明 |
|---|---|---|
| 最小线宽 | 10mil(信号线)、20mil(电源/行线) | 行线细了会压降大,LED亮度不均 |
| 最小间距 | 8mil | 常规工艺能接受 |
| 过孔外径/孔径 | 40mil / 24mil | 大于厂家最小加工能力 |
| 电源网络铜皮宽度 | 30mil | 5V和GND走线尽量短粗 |
| 晶振走线 | 等长且远离干扰源 | 晶振两边铺地环 |
布线顺序先走晶振和复位线,再走行线和列线,最后走电源。双层板可以使用过孔换层,但列线尽量在同一层,不要频繁打孔。
Altium允许用脚本批量化设置规则。下面是一个DelphiScript示例,把选中的走线统一改成指定宽度,适合批量修线宽:
Procedure SetTrackWidth; Var Board : IPCB_Board; Iterator : IPCB_BoardIterator; Track : IPCB_Track; Begin Board := PCBServer.GetCurrentBoard; Iterator := Board.BoardIterator_Create; Iterator.AddFilter_ObjectSet(MkSet(eTrackObject)); Iterator.AddFilter_LayerSet(AllLayers); Iterator.AddFilter_MatchAll; Track := Iterator.FirstPCBObject; While Track <> Nil Do Begin Track.Width := MilsToCoord(20); PCBServer.PreProcess(Track); PCBServer.SendMessageToRobots(Track.I_ObjectAddress, c_Broadcast, PCBServer.I_ObjectAddress, ''); PCBServer.PostProcess(Track); Track := Iterator.NextPCBObject; End; End;这段脚本遍历板上所有走线,把宽度强制设为20mil。实际用的时候要先把信号线的网络筛选出来,别把电源线也改了。更稳的做法是在规则表里分别设置不同网络的宽度,然后用“Design > Rules”的查询语句,比如InNet(‘Row0’)给行线单独设20mil。
铺铜时GND网络在整个底层铺满铜,顶层在LED阵列周边铺一圈地铜,形成guard ring,减少外部干扰。铺铜间距(Clearance)要设成12mil,避免小间距产生的杂散电容影响扫描时序。
最后跑“Design > Board Clearance Check”和“Reports > Board Information”,确认没有未连接线。DRC报告里的“Width”和“Clearance”错误要逐条看,很多是过孔和焊盘间距问题,不要把规则一改全板忽略。
4. 软件源代码:定时器扫描、状态机与沙漏时序
4.1 单片机软件架构:主循环加定时中断
AT89C52的软件不用跑操作系统,一个定时器中断负责LED刷新,主循环负责时间累加和状态切换。定时器T0设为16位模式,每次溢出产生约4ms的中断,中断里扫描一行LED。9行扫描完一轮约36ms,刷新率约28Hz,略显闪烁。所以把定时器初值调整为2ms,一轮18ms,刷新率55Hz,视觉上就很稳定。
关键变量有三个:
- 扫描行号
scan_row:0-8,每中断一次加一,到9清零。 - 沙子当前总量
sand_count:表示应该亮多少颗LED。 - 运行状态
state:0静止、1运行、2暂停。
状态转换逻辑用switch-case写,清晰也容易扩展。按键采用外部中断INT0,按一下切换运行/暂停,长按3秒清零。长按检测在主循环里计时。
4.2 LED矩阵扫描表:把沙漏图形翻译成数组
电子沙漏的视觉效果是:上半部分沙子逐渐变少,下半部分逐渐变多。可以定义两组数组分别存放上下半部分的列亮灭数据。由于是行扫描,我们需要一个二维数组,第一维是行号,第二维是8个列位。
unsigned char code sand_upper[9][8] = { {0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00}, // 第0行 {0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x00}, // 第1行 {0x00, 0x18, 0x3C, 0x3C, 0x18, 0x00, 0x00, 0x00}, // 第2行 {0x00, 0x3C, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00}, // 第3行 // ... 按沙漏形状继续填 };实际上更高效的方式是直接存储每个行扫描时P0应该输出的值,例如一行8颗LED对应P0的8位,1表示灭,0表示亮。这样在中断服务函数里直接把数组值赋给P0,然后锁存。这个表是静态数据,放在code区省RAM。
4.3 中断服务函数与时间基准
2ms定时中断里完成两件事:刷新LED矩阵,同时累计tick_count。主循环每500ms将sand_count加一或减一,模拟一粒沙漏完。当沙漏上半部分全部变暗,下半部分全部变亮,一个周期结束。
void timer0_isr(void) interrupt 1 { static unsigned char row = 0; unsigned char col_data; col_data = sand_table[row][0]; // 从表中取当前行的列数据 P0 = col_data; // 送列数据 LATCH_LED = 1; // 锁存 LATCH_LED = 0; P2 &= 0x01; // 清除行扫描位,避免残影 P2 |= (0x02 << row); // 选通当前行 row++; if (row >= 9) row = 0; tick_count++; }注意清除和置位的顺序:先送列数据并锁存,再切换行,否则会出现“鬼影”——上一行的内容残留到下一行。正确顺序是:先关掉所有行(P2低四位清0),然后送新列数据,锁存,再打开新的行。上面的代码先送数据再清行,实际可能产生短暂混合,更稳妥的做法是如下所示:
P2 &= 0xE0; // 先关闭全部行,P2.1-P2.7置0 P0 = col_data; // 送列数据 LATCH_LED = 1; LATCH_LED = 0; P2 |= (0x02 << row); // 打开当前行这里参数说明:P2 &= 0xE0会保留高三位(用于其他功能),低五位清0。由于行数只有9行,P2.1-P2.7对应行选。0x02 << row在row为0时对应P2.1置1,row为8时对应P2.7置1,正好9行。
时间基准用tick_count实现。主循环里每500ms变化一次沙量,500ms可以由250个2ms中断得到。千万不要用软件延时函数做时间基准,因为LCD刷新和按键检测会干扰延时精度。
4.4 状态机:运行、暂停、清零
下面是主循环的状态处理,注意按键检测放主循环,不用占用中断时间。
void main(void) { sys_init(); // 配置定时器、外部中断 while (1) { if (state == RUN) { if (tick_count % 250 == 0) // 每500ms { if (upper_sand > 0) upper_sand--; if (lower_sand < 72) lower_sand++; } } if (state == PAUSE) { // 维持当前显示 } if (key_long_pressed) { upper_sand = 72; lower_sand = 0; state = RUN; } } }upper_sand和lower_sand只是逻辑计数,真正显示用的sand_table是预先算好的组合表。这里有个额外的做法:用两个计数器以及一个偏移指针去索引一组动画帧,而不是每一帧都从沙量计算LED形状。把沙漏从0到72颗LED的变化做成40个动画帧,每帧存储9行8列的显示数据,这样省去实时运算,也让动画过渡更自然。常见的课程设计不需要这么复杂,直接按沙量算行列亮度就够用。
5. 软硬件联调:从烧录到显示异常的排查技巧
板子焊完,用STC-ISP或者USB-ASP下载器把HEX文件烧进AT89C52。这里有个细节:AT89C52的ISP下载方式需要冷启动,先把下载软件准备好,点下载按钮后给板上电。如果用的是STC系列(STC89C52兼容),则用串口下载,但AT89C52本身不支持串口ISP,需要并行编程器。现在很多课程设计实际用的是宏晶STC89C52或者AT89S52,后者支持ISP。看压缩包名字是AT89C52,但工程里可能用的是AT89S52,两者引脚兼容,代码也通用,只是烧录方式不同。
通电后常见的故障有几种:
- 全不亮:先量VCC和GND是否短路,再量单片机复位脚是否为高电平,晶振两脚是否有波形。AT89C52正常工作时XTAL2脚应有约1/2电源幅度的正弦波。
- 只有一行亮:问题在三极管行扫描。用万用表量该行基极电压,看切换时是否从5V掉到0V。三极管贴反会导致整个驱动失效。
- 显示残影严重:软件里行切换时序不对,参考第四节的关闭顺序。如果时序正确还残影,就在每行切换之间加一个2-5us的空延迟,让列数据稳定。
最后一招验证刷新率是否足够:用手机摄像头对准LED矩阵,如果看到明显的横条纹,说明刷新率低于50Hz。将定时器初值改小,使中断间隔缩短到1.5ms左右,再测试。这是一个很容易被忽略的坑,很多代码在仿真里看着正常,实际板子扫描频率不够就会闪烁。
还可以在中断服务函数里加一个调试断点,用逻辑分析仪抓P2.1引脚波形,测量周期。如果一轮9行总耗时超过20ms,就要考虑定时器重装值是否算错了。定时器计数初值公式:初值=65536-(晶振频率/12×目标时间)。晶振12MHz,定时2ms,每个机器周期1us,需要计数2000次,初值=65536-2000=63536,即0xF830。注意初值是16位,要拆成两个8位赋给TH0和TL0。
如果板上晶振是11.0592MHz,为了串口波特率精确,定时器计算需要换算:周期=时钟/12=11059200/12=921600Hz,2ms需要1843个计数,初值=65536-1843=63693,对应0xF8CD。直接替换就好。把程序里的#define FOSC 12000000UL改成11059200,就可以通用了。
最后,整个项目调通后,建议把GERBER文件输出到工厂打样,或者直接用嘉立创EDA打开Altium导出的文件进行再次检查。Altium里输出GERBER文件时,勾选“RS-274-X”,包含所有层和钻孔文件,输出后导入在线Gerber查看器核对一遍孔位和走线是否和PCB一致。这是硬件交付前的最后一道工序,别省。
本文还有配套的精品资源,点击获取