蓝桥杯单片机有一些基础模块的写法,他是没有一定的公式模板的。
好比动态数码管扫描显示,原理就是快速地 传输段选和位选。
但因为电路上添加了74HC573芯片,导致了我们移植别处 51单片机的动态扫描代码
会出现诸多问题,比如闪烁,鬼影,甚至崩溃不显示了......
备赛14届省赛时,我花费数日不断调整程序结构,结合芯片速率,尝试各种
段选位选上的延时,最终调试出来了一个比较稳定可靠的数码管动态扫描函数。
此篇文章将围绕我调试出的 数码管动态扫描函数来进行讲解,欢迎大佬批评指正!
为使讲解更完善,我会循序渐进地从硬件底层开始分析,
如果急于想看代码,可以点目录进行传送:
目录
电路图与数码管动态扫描原理简要介绍:
定时器的配置与使用:
数码管显示函数的编写与介绍:
整体工程代码:
显示效果展示:
我们先看一位数码管是怎么点亮和操作的:
下图中有四位数码管,我们先只看最左边的就行了:
我们看到,每一位数码管上八个段,每一个段都标有专属的a,b,c,d,e,f,g,dq.
(其中dq是右下角的小数点)
这八个字母分别对应着八根连在74hc573芯片上的线a,b,c,d,e,f,g,dq.
对应着的线打低电平(0)过去,就能使数码管对应段点亮了,
比如我想让数码管显示1,那只要让b和c亮,其余灭即可,端口读取的顺序是
dq g f e d c b a 所以对应段码为 1111 1001 ,转换为16进制数即为:0xf9
照这样,我们将0~9,甚至一些字母的显示段码推算出来,
就获得了常用字库,我们可以将其存在数组中:
此处贴出常用字库:
/*//数码管字库数组:
数组下标对应数字 字符段码速查:(PS:0~9数字和下标一致,0~9的下标 +20 就能加上小数点)
0_0 1_1 2_2 3_3 4_4 5_5 6_6 7_7 8_8 9_9 10_a
11_b 12_c 13_d 14_e 15_f 16_空 17_根线 18_H 19_P
20_0. 21_1. 22_2. 23_3. 24_4. 25_5. 26_6. 27_7. 28_8. 29_9. 30_L 31_n
*/
u8 code smgZK[35]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xff,0xbf,0x89,0x8c,0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0xc7,0xc8};
我们开发板板载了四片74hc573芯片,都是读取的单片机端口P0的高低电平情况,
其中有俩片是与我们数码管有关的,一片用于段选,一片用于位选。
位选很容易理解,就是com1~com8,对应74hc573芯片的引脚打高电平就能使
当前被选中位的数码管按照之前传过来的段码进行显示
而该如何选择哪片74hc573芯片来锁存 P0端口数据呢?
我们查阅原理图:
发现还有74hc138芯片(8选1)与或非门进行运算后,才能使能进行对应
Y6C_74hc573芯片 ,Y7C_74hc573芯片来锁存P0端口数据。
此处贴出74hc138使能74hc573芯片开启锁存函数:
//74HC138\74hc573芯片片选 函数
//传入一个参数,用来选择打开哪个74HC573锁存芯片
void inint_port(u8 select)
{
switch(select)
{
case 4: P2=(P2&0X1F)|0X80;break; //LED
case 5: P2=(P2&0X1F)|0XA0;break; //蜂鸣器继电器
case 6: P2=(P2&0X1F)|0XC0;break; //数码管位选
case 7: P2=(P2&0X1F)|0XE0;break; //数码管段选
}
}
可见我们开发板P0端口的强大,可以给许多模块传输控制信号。
从以上电路原理图可见,八位数码管连接着俩片74HC573芯片:
分别是a1~dq1 控制段选 的 Y7C_74HC573芯片 (a~g决定七段码显示,dq表示要不要加小数点)
以及com1~com8控制位选的 Y6C_74HC573芯片
数码管的动态扫描就是一位一位传送对应 位选 和 段选码,比如我想让八位数码管
显示1,2,3,4,5,6,7,8,那我就得:
打开com1 关闭其余com2~com8,然后传送1的段码
打开com2,关闭其余com1,com3~com8,然后传送2的段码
打开com3,关闭其余com1,com2,com4~com8,然后传送3的段码
以此类推到com8 完成一整个动态扫描。
其运用的原理就是快速的一位一位地扫描,运用肉眼的视觉暂留,
造成每一位都在显示的假象,其实本质是一位一位亮过去的。
定时器在蓝桥杯单片机中是十分重要的外设,这里简要介绍我们如何配置使用它
具体基础知识还是要大家自己努力学习才是!
我们可以用STC_ISP软件直接生成驱动定时器的代码:
这在比赛中也是提供此软件可以使用的:
以下示例 定时器0开启,12T模式 ,定时1ms的设置细节:
但它写的不是完整的开启定时器0,所以还需添加俩句来打开定时器0的中断:
这个函数就是用来初始化定时器0的,在主函数while(1)循环之外调用一次就行了。
其次就是中断服务函数的编写,函数名不重要,主要是对应中断号写正确就行了:
void TIMER0_serv() interrupt 1
{
}
这样,定时器0每计时1ms,就会进入中断函数一次,来执行里面的语句.
这样我们就能 精准、实时、快速、稳定 地刷新数码管了!
这是本人省赛以来一直使用的数码管打印函数,直接放在定时器中断中调用的
1~15ms为周期的刷新都十分稳定,只是15ms会变暗,建议5~8ms为周期刷新最好
一次性打印8位数码管,传送完位选,段选后先延时,再P0=0Xff;进行消除鬼影
nr1~nr8,不是直接传送的段码,而是作为字库数组的下标使用的,因为段码都
存在数组中,而且有些数值的运算用段码非常不方便。
//数码管打印函数
//传入八个参数u8 nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8 进行打印
//一次性打印八位数码管
void smg_display(u8 nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8)
{
u8 i;
i=0;
for(i=0;i<=8;i++) //循环扫描1~8位的数码管
{
inint_port(6); //每一位先开启位选
switch(i)
{
case 1:P0=0X01;inint_port(7);P0=smgZK[nr1];Delay250us();P0=0Xff;break;
case 2:P0=0X02;inint_port(7);P0=smgZK[nr2];Delay250us();P0=0Xff;break;
case 3:P0=0X04;inint_port(7);P0=smgZK[nr3];Delay250us();P0=0Xff;break;
case 4:P0=0X08;inint_port(7);P0=smgZK[nr4];Delay250us();P0=0Xff;break;
case 5:P0=0X10;inint_port(7);P0=smgZK[nr5];Delay250us();P0=0Xff;break;
case 6:P0=0X20;inint_port(7);P0=smgZK[nr6];Delay250us();P0=0Xff;break;
case 7:P0=0X40;inint_port(7);P0=smgZK[nr7];Delay250us();P0=0Xff;break;
case 8:P0=0X80;inint_port(7);P0=smgZK[nr8];Delay250us();P0=0Xff;break;
}
}
}
其中还调用了延时函数Delay250us();这也可用STC_ISP软件生成:
//@12.000MHz 数码管函数 必要的250us打印延时
//各类延时函数可在软件生成,不用记忆
void Delay250us()
{
unsigned char i, j;
i = 3;j = 232;
do{while (--j);} while (--i);
}
此工程为8ms一周期扫描数码管,动态扫描显示1~8;
#include "stc15f2k60s2.h"
typedef unsigned char u8;
//蜂鸣器引脚
sbit buzz=P0^6;
/*//数码管字库数组:
数组下标对应数字 字符段码速查:(PS:0~9数字和下标一致,0~9的下标 +20 就能加上小数点)
0_0 1_1 2_2 3_3 4_4 5_5 6_6 7_7 8_8 9_9 10_a
11_b 12_c 13_d 14_e 15_f 16_空 17_根线 18_H 19_P
20_0. 21_1. 22_2. 23_3. 24_4. 25_5. 26_6. 27_7. 28_8. 29_9. 30_L 31_n
*/
u8 code smgZK[35]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xff,0xbf,0x89,0x8c,0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0xc7,0xc8};
u8 nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8;//数码管八位打印内容,对应字库数组下标
void give_nr();
void inint_port(u8 select);
void Delay250us();
void smg_display(u8 nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8);
void Timer0Init(void);
void main()
{
inint_port(5); //清除蜂鸣器
buzz=0;
inint_port(4); //清除LED
P0=0XFF;
Timer0Init();
while(1)
{
}
}
//为nr1~nr8赋值为1~8
void give_nr()
{
nr1=1;nr2=2;nr3=3;nr4=4;
nr5=5;nr6=6;nr7=7;nr8=8;
}
//74HC138\74hc573芯片片选 函数
//传入一个参数,用来选择打开哪个74HC573锁存芯片
void inint_port(u8 select)
{
switch(select)
{
case 4: P2=(P2&0X1F)|0X80;break; //LED
case 5: P2=(P2&0X1F)|0XA0;break; //蜂鸣器继电器
case 6: P2=(P2&0X1F)|0XC0;break; //数码管位选
case 7: P2=(P2&0X1F)|0XE0;break; //数码管段选
}
}
//@12.000MHz 数码管函数 必要的250us打印延时
//各类延时函数可在软件生成,不用记忆
void Delay250us()
{
unsigned char i, j;
i = 3;j = 232;
do{while (--j);} while (--i);
}
//数码管打印函数
//传入八个参数u8 nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8 进行打印
//一次性打印八位数码管
void smg_display(u8 nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8)
{
u8 i;
i=0;
for(i=0;i<=8;i++)
{
inint_port(6);
switch(i)
{
case 1:P0=0X01;inint_port(7);P0=smgZK[nr1];Delay250us();P0=0Xff;break;
case 2:P0=0X02;inint_port(7);P0=smgZK[nr2];Delay250us();P0=0Xff;break;
case 3:P0=0X04;inint_port(7);P0=smgZK[nr3];Delay250us();P0=0Xff;break;
case 4:P0=0X08;inint_port(7);P0=smgZK[nr4];Delay250us();P0=0Xff;break;
case 5:P0=0X10;inint_port(7);P0=smgZK[nr5];Delay250us();P0=0Xff;break;
case 6:P0=0X20;inint_port(7);P0=smgZK[nr6];Delay250us();P0=0Xff;break;
case 7:P0=0X40;inint_port(7);P0=smgZK[nr7];Delay250us();P0=0Xff;break;
case 8:P0=0X80;inint_port(7);P0=smgZK[nr8];Delay250us();P0=0Xff;break;
}
}
}
//1毫秒@12.000MHz 定时器0 初始化函数
//此函数可在软件生成,别忘了添加 EA=1; ET0=1;这俩句
//此函数初始化定时器0为 1ms 中断一次,满足普遍要求
void Timer0Init(void)
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
EA=1; //打开总中断
ET0=1; //打开定时器0中断
}
//定时器0中断服务函数
void TIMER0_serv() interrupt 1
{
u8 i;
i++;
if(i==8)
{
i=0;
give_nr();
smg_display(nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8);
}
}