|
|
研究完数码管时钟,我就准备做一个自己设计的点阵时钟。搜索过淘宝,自己现阶段能模仿的只有点阵时钟,而且是51自己中断计时,不加时钟芯片的那种。见下图。淘宝售卖的散件才30多元钱(可能还ESP芯片控制的,更高级),我自己做,从做PCB设计,到打板,采购零件,焊接,还要做外壳,不是3D打印,就是激光切割亚克力板,估计还得要自己做外壳的机械设计,想想都头痛。钱不会少花,估计得百元,但这是自己成长的必经之路,不走一遍还不行。这一遍走通了,我才敢进行更复杂的设计,并实践出来。
下面是点阵时钟的显示部分程序,按键修改时间部分,可以参考文章:解决了初学者常遇到的糟心问题,51单片机proteus仿真数码管点阵的动态显示乱码故障 - 单片机学习教程(初学者入门)
为了便于阅读,我让AI尽量帮我把每一句都写上了注释。关于AI编程,我的体会是可以让它给你思路,暂时别指望它能编出完美运行的程序,人脑和电脑目前还不能完美统一。反正AI给我出的程序是漏洞百出,冗余颇多,越改越烂,而且它还一本正经给你瞎解释。最终还是按我自己的思路与构思框架,将程序修改完善。像取个字模啥的烂事交给AI干还是挺让人舒心的。
/*
* 24小时点阵时钟程序
* 使用三个8*8点阵(24列)作为一个整体显示时间:HH:MM
* 布局:[间隔1列][小时十位4列][间隔1列][小时个位4列][间隔1列][冒号2列][间隔1列][分钟十位4列][间隔1列][分钟个位4列][间隔1列]
* 冒号每秒闪烁一次(亮1秒、灭1秒),指示秒的走动
*
* 硬件配置:
* - 行驱动:1个74HC595芯片,输出8位行选通信号
* - 列驱动:3个74HC595芯片,分别控制三个点阵的8位列数据
* - 级联方式:4片595串行级联,共32位数据一次移入后由RCLK同时锁存输出
*
* 列数据位序(按实际硬件接线):
* 每个595的最高位输出Q7(bit7)接该组第1列(最左),最低位Q0(bit0)接第8列(最右):
* - 小时595:bit7->第1列 ... bit0->第8列
* - 中间595:bit7->第9列 ... bit0->第16列
* - 分钟595:bit7->第17列 ... bit0->第24列
* 因此列字节内高位在左、低位在右(正序),字模也按"高位在左"定义
*
* 引脚定义:
* P3.0 -> SER (串行数据输入)
* P3.1 -> SRCLK (移位寄存器时钟)
* P3.2 -> RCLK (存储寄存器锁存时钟)
*
* 硬件极性:
* - 行控制:低电平有效(0=点亮)
* - 列控制:高电平有效(1=点亮)
*
* 显示布局(第1列为最左列,按每组字节内的位号标注):
* col0(小时595):bit7=第1列(间隔) bit6~3=第2~5列(小时十位) bit2=第6列(间隔) bit1~0=第7~8列(小时个位左2列)
* col1(中间595):bit7~6=第9~10列(小时个位右2列) bit5=第11列(间隔) bit4~3=第12~13列(冒号) bit2=第14列(间隔) bit1~0=第15~16列(分钟十位左2列)
* col2(分钟595):bit7~6=第17~18列(分钟十位右2列) bit5=第19列(间隔) bit4~1=第20~23列(分钟个位) bit0=第24列(间隔)
*
* 时序设计(11.0592MHz晶振,12分频后1个机器周期约1.085us):
* - 定时器0:约5ms中断一次,逐行刷新点阵;
* - 中断内只做一次32位移位(约0.5ms内完成)
* 保证主循环和秒计时有CPU时间可用(若中断耗时超过周期,主循环将被
* 完全饿死,表现为时间不走、冒号不闪)
* -
*/
// 引入51单片机寄存器定义头文件
- #include <reg51.h>
- // 定义74HC595芯片的串行数据引脚,接P3.0
- sbit SER = P3^0;
- // 定义74HC595芯片的移位寄存器时钟引脚,接P3.1
- sbit SRCLK = P3^1;
- // 定义74HC595芯片的存储寄存器锁存时钟引脚,接P3.2
- sbit RCLK = P3^2;
- // 显示缓冲区,8行×3字节(24列),每行对应24个LED列数据
- // 每个字节内高位在左(bit7=该组第1列),与硬件接线一致
- // display_buf[i][0] = 小时595:间隔 + h1(4列) + 间隔 + h2左2列
- // display_buf[i][1] = 中间595:h2右2列 + 间隔 + 冒号 + 间隔 + m1左2列
- // display_buf[i][2] = 分钟595:m1右2列 + 间隔 + m2(4列) + 间隔
- // 缓冲区内1=点亮,与列硬件极性(高电平点亮)一致,输出时无需取反
- unsigned char display_buf[8][3] = {0};
- // 当前扫描的行号,0-7循环
- unsigned char current_row = 0;
- // 当前小时数,范围0-23
- unsigned char hours = 12;
- // 当前分钟数,范围0-59
- unsigned char minutes = 0;
- // 当前秒数,范围0-59
- unsigned char seconds = 0;
- // 毫秒计数器,由定时器1每约1ms加1,累计到1000时秒数加1
- unsigned int ms_count = 0;
- // 定义秒标志位
- unsigned char secflag=0;
- // 冒号闪烁标志:秒数为偶数时置1(冒号亮),奇数时置0(冒号灭)
- // 由定时器0中断在每次秒变化时更新;主循环检测到秒变化后重绘显示,实现每秒闪烁
- bit colon_blink = 1;
- // 定义T0定时器高8位,低8位重载数值变量
- unsigned char T0RH,T0RL;
- // 4列宽数字字模表,digits_4col[数字][行号]
- // 字模为正序(高位在左):bit3=数字最左列(第1列),bit0=数字最右列(第4列),这样设置硬件及取出的字模让人感觉清晰,后面的逻辑转换也更容易懂
- // 与硬件"bit7=最左列"的位序一致,1=点亮,0=熄灭
- // 每个数字占满8行(统一高度)
- // 加code关键字:字模表存放在程序存储器ROM中,不占用宝贵的内部RAM
- unsigned char code digits_4col[10][8] = {
- {0x06, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x06}, // 0
- {0x04, 0x0C, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x04}, // 1
- {0x06, 0x09, 0x01, 0x02, 0x04, 0x08, 0x09, 0x06}, // 2
- {0x06, 0x01, 0x01, 0x07, 0x01, 0x01, 0x01, 0x06}, // 3
- {0x09, 0x09, 0x09, 0x0F, 0x01, 0x01, 0x01, 0x01}, // 4
- {0x0F, 0x08, 0x08, 0x0F, 0x01, 0x01, 0x01, 0x0F}, // 5
- {0x06, 0x08, 0x08, 0x07, 0x09, 0x09, 0x09, 0x06}, // 6
- {0x0F, 0x01, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08}, // 7
- {0x06, 0x09, 0x09, 0x06, 0x09, 0x09, 0x09, 0x06}, // 8
- {0x06, 0x09, 0x09, 0x0F, 0x01, 0x01, 0x01, 0x06} // 9
- };
- // 向74HC595芯片发送一个字节的数据
- // 参数dat:要发送的8位数据(高位在前)
- // 说明:使用固定掩码循环代替"变量位数移位",每位移位开销小、耗时确定,
- // 保证显示扫描中断能在一个扫描周期内完成,不阻塞秒计时和主循环
- void shift_out_byte(unsigned char dat)
- {
- unsigned char mask;
- // 从最高位(bit7)到最低位(bit0)依次发送
- for(mask = 0x80; mask != 0; mask >>= 1)
- {
- // 将当前位写入SER引脚(掩码对应位为1则SER=1,否则SER=0)
- SER = (dat & mask) ? 1 : 0;
- // SRCLK产生上升沿,将SER上的数据移入移位寄存器
- SRCLK = 0; // 拉低时钟,建立数据
- SRCLK = 1; // 拉高时钟,上升沿移入数据
- }
- }
- // 向4个级联的74HC595芯片发送32位数据(1字节行+3字节列)
- // 参数row_data:行选通数据(8位)
- // 参数col0:小时595列数据(第1~8列),间隔+h1+间隔+h2左2列
- // 参数col1:中间595列数据(第9~16列),h2右2列+间隔+冒号+间隔+m1左2列
- // 参数col2:分钟595列数据(第17~24列),m1右2列+间隔+m2+间隔
- // 发送顺序:row_data -> col2 -> col1 -> col0
- // 先发送的数据经过整条级联链,最终row_data到达最远端的行595,
- // col2/col1/col0分别落在分钟/中间/小时点阵对应的列595
- // 全部32位移完后RCLK统一锁存,行与列同时更新输出,所以就不需要消隐了。
- void shift_out_32bit(unsigned char row_data, unsigned char col0, unsigned char col1, unsigned char col2)
- {
- // 1. 发送行数据(移到级联链最远端的行驱动595)
- shift_out_byte(row_data);
- // 2. 发送分钟点阵列数据(col2)
- shift_out_byte(col2);
- // 3. 发送中间点阵列数据(col1,含冒号和小时个位高2位)
- shift_out_byte(col1);
- // 4. 发送小时点阵列数据(col0,最后发送,落在最靠近MCU的列595)
- shift_out_byte(col0);
- // RCLK产生上升沿,将移位寄存器中的32位数据同时锁存到输出端
- RCLK = 0; // 拉低锁存时钟
- RCLK = 1; // 拉高时钟,数据输出到引脚
- }
- // 初始化函数:配置定时器和中断
- void configtime(unsigned char ms)
- {
- unsigned long tmp;
- tmp=ms*11059200/12/1000;
- //延时补偿,我根据经验加了15,当然也不一定是15,根据你的检测平均而来。
- tmp=65536-tmp+15;
- T0RH=(unsigned char)(tmp>>8);
- T0RL=(unsigned char)tmp;
- EA=1;
- TMOD=0X01;
- TH0=T0RH;
- TL0=T0RL;
- ET0=1;
- TR0=1;
- }
- // 定时器0中断服务函数,用于点阵动态扫描刷新
- // 每约5ms触发一次,依次点亮每行LED
- // 说明:4片595的输出由RCLK同时锁存,行与列在同一锁存边沿一起切换,
- // 不存在"新行数据遇上旧列数据"的中间状态,因此不需要消隐步骤,
- // 每次中断只移位一次32位,中断耗时约0.5ms,远小于扫描周期,
- // 主循环和秒计时都能分到CPU时间
- void display_refresh() interrupt 1
- {
- // 重置定时器0初值,下次中断间隔约5ms
- TH0 =T0RH; // 高8位
- TL0 = T0RL; // 低8位
- // ========== 显示当前行 ==========
- // ~(0x01 << current_row):当前行位为0(行低有效,0=点亮该行)
- // display_buf[current_row][x]:列数据直接输出(列高有效,1=点亮)
- shift_out_32bit(~(0x01 << current_row),
- display_buf[current_row][0],
- display_buf[current_row][1],
- display_buf[current_row][2]);
- // 行号递增,循环扫描0-7行
- current_row++;
- if(current_row >= 8)
- current_row = 0;
- // 毫秒计数器递增
- ms_count++;
- // 累计1000ms(1秒)
- if(ms_count >= 200)
- {
- // 重置毫秒计数器
- ms_count = 0;
- secflag=1;
- }
- }
- // 更新显示缓冲区函数:将当前时间转换为24列LED点阵数据
- // 列字节内高位在左(bit7=该组第1列),字模正序(bit3=数字最左列)
- void update_display()
- {
- unsigned char i; // 行索引
- unsigned char h1 = hours / 10; // 小时十位(0-2)
- unsigned char h2 = hours % 10; // 小时个位(0-9)
- unsigned char m1 = minutes / 10; // 分钟十位(0-5)
- unsigned char m2 = minutes % 10; // 分钟个位(0-9)
- unsigned char temp[3]; // 临时缓冲区,构建当前行的24列数据
- unsigned char d; // 当前数字的行数据
- // 逐行构建显示数据(共8行)
- for(i = 0; i < 8; i++)
- {
- // 初始化临时缓冲区为全0
- temp[0] = 0x00;
- temp[1] = 0x00;
- temp[2] = 0x00;
- // ========== 小时十位(h1):第2~5列 -> col0的bit6~3 ==========
- // 字模bit3(最左列)对应col0的bit6,即左移3位:
- // bit3->bit6(第2列), bit2->bit5(第3列), bit1->bit4(第4列), bit0->bit3(第5列)
- d = digits_4col[h1][i] & 0x0F;
- temp[0] |= (d << 3);
- // ========== 小时个位(h2):第7~10列,跨col0和col1两个字节 ==========
- // 左2列(第7~8列):字模bit3->col0的bit1(第7列),bit2->bit0(第8列),即右移2位
- // 右2列(第9~10列):字模bit1->col1的bit7(第9列),bit0->bit6(第10列),即左移6位
- d = digits_4col[h2][i] & 0x0F;
- temp[0] |= (d >> 2);
- temp[1] |= (d << 6);
- // ========== 冒号:第12~13列 -> col1的bit4~3 ==========
- // 只在第2、3、5、6行点亮(形成上下两个圆点)
- // colon_blink为1时显示,实现每秒闪烁
- // 0x18 = 0b00011000,对应col1的bit4(第12列)和bit3(第13列)
- if(colon_blink && (i == 2 || i == 3 || i == 5 || i == 6))
- {
- temp[1] |= 0x18;
- }
- // ========== 分钟十位(m1):第15~18列,跨col1和col2两个字节 ==========
- // 左2列(第15~16列):字模bit3->col1的bit1(第15列),bit2->bit0(第16列),即右移2位
- // 右2列(第17~18列):字模bit1->col2的bit7(第17列),bit0->bit6(第18列),即左移6位
- d = digits_4col[m1][i] & 0x0F;
- temp[1] |= (d >> 2);
- temp[2] |= (d << 6);
- // ========== 分钟个位(m2):第20~23列 -> col2的bit4~1 ==========
- // 字模bit3(最左列)对应col2的bit4,即左移1位:
- // bit3->bit4(第20列), bit2->bit3(第21列), bit1->bit2(第22列), bit0->bit1(第23列)
- d = digits_4col[m2][i] & 0x0F;
- temp[2] |= (d << 1);
- // 将构建好的当前行数据写入显示缓冲区
- display_buf[i][0] = temp[0];
- display_buf[i][1] = temp[1];
- display_buf[i][2] = temp[2];
- }
- }
- void time()
- {
- if (secflag==1)
- {
- secflag=0;
- // 冒号闪烁控制:秒数为偶数时亮,奇数时灭(每秒切换一次)
- colon_blink = (seconds % 2 == 0);
- // 秒数加1
- update_display();
- seconds++;
- // 秒数达到60,进位到分钟
- if(seconds >= 60)
- {
- seconds = 0;
- update_display(); // 可能不需要,第一个update_display()就把时分秒显示全干了。
- minutes++;
- // 分钟达到60,进位到小时
- if(minutes >= 60)
- {
- minutes = 0;
- update_display(); // 也许用不到,第一个update_display()就把时分秒的事办了。
- hours++;
- // 小时达到24,归零(24小时制)
- if(hours >= 24)
- hours = 0;
- }
- }
- }
- }
- // 主函数
- void main()
- {
- // 初始化定时器和中断
- configtime(5);
- // 首次更新显示缓冲区(初始时间12:00,秒数为偶数,冒号点亮)
- // 主循环:持续检测时间变化并更新显示
- while(1)
- {
- time();
- }
- }
复制代码
|
|