这是笔者关于51单片机学习的第5篇学习笔记了,通过对51单片机各个基础模块的学习终于到了小结的时候了。
先上结果视频
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //数码管显示字符转换表 0 1 2 3 4 5 6 7 8 9 A B C D E F
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = { //数码管显示缓冲区,初值0xFF确保启动时都不亮
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
//unsigned char i = 0; //动态扫描的索引
//unsigned int cnt = 0; //记录T0中断次数
unsigned char flag1s = 0; //1秒定时标志
unsigned char code image[] =
{ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, //点阵
0x99,0x99,0x99,0x99,0x99,0x81,0xC3,0xFF,
0x99,0x00,0x00,0x00,0x81,0xC3,0xE7,0xFF,
0xC3,0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,0xC3,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
};
void main()
{
char j; //循环变量
unsigned long sec = 0; //记录经过的秒数
unsigned char buf[6]; //中间转换缓冲区
EA = 1; //中断打开
TMOD = 0x11; //设置T1和T0都为模式1
TH0 = 0xFC; //定时器0赋值 定时2ms
TL0 = 0x67;
ET0 = 1; //定时器0中断使能(启动T0中断)
TR0 = 1; // 定时器0使能(启动T0)
TH1 = 0xFC; //为T0赋初值0xFC67,定时1ms
TL1 = 0x67;
ET1 = 1; //使能T1中断
TR1 = 1; //启动T1
while (1)
{
if (flag1s == 1) //判断1秒定时标志
{
flag1s = 0; //1秒定时标志清零
sec++; //秒计数自加1
//将sec按十进制位从低到高依次提取到buf数组中
buf[0] = sec%10;
buf[1] = sec/10%10;
buf[2] = sec/100%10;
buf[3] = sec/1000%10;
buf[4] = sec/10000%10;
buf[5] = sec/100000%10;
//从最高为开始,遇到0不显示,遇到非0退出循环
for (j=5; j>=1; j--) //共6个数码管用下标表示就是0-5,最高位数码管的下标是5
{
if (buf[j] == 0) //从最高位j=5的时候开始倒序判断缓冲区的值,遇到非零跳出函数
LedBuff[j] = 0xFF; //假设sec为123,通过取值函数我们知道buf[] = {3,2,1,0,0,0} buf[3] buf[4] buf[5] 都为0代表LedBuff[5],LedBuff[4],LedBuff[3]
else //被赋值位0xFF.0xff即数码管LED关闭什么都不显示
break; //buf[2]不为0进入else函数随后跳出,这种的j值就是2
}
//将剩余的有效数字位如实转换
for ( ; j>=0; j--) //for()起始未对j操作,j即保持上个循环结束时的值,j的初始值由上个for函数提供
{
LedBuff[j] = LedChar[buf[j]]; // 函数循环执行第一次结果是 LedBuff[2] = LedChar[buf[2]]=LedChar[1]=0xF9 这个值是1
} // 同理LedBuff[1]=2 ledBuff[0] =3 对于中断里的扫描函数 P0 = LedBuff[] = {3,2,1,0xff,0xff,0xff}
} //只显示3位高位置0xff不显示达成
}
}
/* 定时器0中断服务函数 */
void InterruptTimer1() interrupt 3 //定时器1
{
static unsigned char j = 0;
static unsigned char i = 0; //动态扫描的索引
static unsigned int cnt = 0; //记录T0中断次数
static unsigned int shift1 = 0x01; //(LED1)shift1在最左边端口1那么一开始会左移
static unsigned int shift2 = 0x80;// (LED2)shift2在最右边端口8那么一开始会右移
static unsigned char dir1 = 0;
P0 = 0xFF; //该句必须放在最ENLED和ADDR3的上面,如果放在原先的位置任然会有鬼影的出现
ENLED = 0; //使能U3
ADDR3 = 1; //因为需要动态改变ADDR0-2的值,所以不需要再初始化了
TH1 = 0xFC; //重新加载初值
TL1 = 0x67;
cnt++; //中断次数计数值加1
j++;
if (cnt >= 1000) //中断1000次即1秒
{
cnt = 0; //清零计数值以重新开始下1秒计时
flag1s = 1; //设置1秒定时标志为1
}
//以下代码完成数码管动态扫描刷新
// P0 = 0xFF; //显示消隐这句放在这会产生鬼影
switch (i)
{
case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break; //数码管扫描
case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
case 5: ADDR2=1; ADDR1=0; ADDR0=1; i++; P0=LedBuff[5]; break;
case 6: ADDR2=1; ADDR1=1; ADDR0=0; i = 0; //LED扫描
P0 = ~(shift1+shift2); //P0等于循环变量取反
if( j>= 200) //延时200ms
{
j = 0;
if(shift1 == 0x08 && dir1 == 0) //判断是否 shit1运动到端口4并且是向左移动
{
shift1 = shift1<<1; //shift1在端口4 0x08的位置往左移一位
shift2 = shift2>>1; //shift2在端口5 0x10的位置往右移一位,
} // 因为是相对运动的因此判断了shift1的方向shift2 的方向就确认了
if(shift1 == 0x10 && dir1 == 1) //判断是否 shift1运动到端口5并且是向右运动
{
shift1 = shift1>>1; // shift1在端口5 0x10的位置并且向右移动一位
shift2 = shift2<<1; // shift2在端口4 0x08的位置往左移一位
}
if(dir1 == 0 ) //判断是否 shift1往左移动
{
{shift1 = shift1<<1; //循环变量1左移1位
shift2 = shift2>>1;
} //循环变量2右移1位
if(shift1 ==0x80 && shift2 == 0x01) //循环变量都移到最左端和最右端
{
dir1 = 1; //换向
break ; //跳出switch语句
}
}
if(dir1 == 1) // 判断是否 shift1往右移动
{
{
shift1 = shift1>>1 ; //换向后循环变量1右移
shift2 = shift2<<1 ; //换向后循环变量2左移
}
if(shift1 == 0x01 && shift2 == 0x80) //判断循环变量1到最右端且循环变量2到最 左端
{
dir1 = 0; //又开始换向换回到原先的方向
}
}
}
break;
default: break;
}
}
void TtrTimer0 () interrupt 1 //定时器0
{
static unsigned char k = 0;
static unsigned char index = 39;
static unsigned char tmr = 0;
P0 = 0xFF; 该句必须放在ENLED和ADDR3上面,如果放在原先的位置就会有鬼影的出现
ENLED = 0; //38译码器U4使能
ADDR3 = 0; //38译码器U4使能
TH0 = 0xFC; //定时器0重新赋值
TL0 = 0x67;
// P0 = 0xFF; //这句放在这会产生鬼影
switch(k)
{
case 7: ADDR2 = 0; ADDR1 = 0; ADDR0 = 0; k = 0; P0 = image[index-7]; break; //点阵扫描
case 6: ADDR2 = 0; ADDR1 = 0; ADDR0 = 1; k++; P0 = image[index-6]; break;
case 5: ADDR2 = 0; ADDR1 = 1; ADDR0 = 0; k++; P0 = image[index-5]; break;
case 4: ADDR2 = 0; ADDR1 = 1; ADDR0 = 1; k++; P0 = image[index-4]; break;
case 3: ADDR2 = 1; ADDR1 = 0; ADDR0 = 0; k++; P0 = image[index-3]; break;
case 2: ADDR2 = 1; ADDR1 = 0; ADDR0 = 1; k++; P0 = image[index-2]; break;
case 1: ADDR2 = 1; ADDR1 = 1; ADDR0 = 0; k++; P0 = image[index-1]; break;
case 0: ADDR2 = 1; ADDR1 = 1; ADDR0 = 1; k++; P0 = image[index-0]; break;
default: break;
}
//P0 = 0xFF;
tmr++;
if(tmr >= 250)
{
tmr = 0;
index--;
if(index <= 8)
{
index = 39;
}
}
程序已经实现了三个模块的同时工作,该篇的程序前文都有放出,笔者只是把各个功能模块整合在了一起,在实现这个程序的时候笔者遇到了一个问题导致了鬼影的产生。后来通过逻辑的分析排除了,因此关于电路的程序模块的使用时候要注意逻辑的关系。
先看下鬼影的产生视频
这个产生的原因就是消除鬼影的语句 P0 = 0xFF; 之前的博文都是放在刷新语句的前面了,现在是放在中断函数里ADDR3和ENLED的前面,因为这两句是切换38译码器U3和U4的语句,
举个例子:上图是定时器0的中断函数程序,假设在进入该中断前已经执行了中断1的刷新语句的case3语句,
定时器1中断的case3语句是
case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;,接着我们进入定时器0的中断,如果一开始不加上 P0 = 0xFF;这句,执行了ENLED = 0; ADDR3 = 0; 语句,三八译码器从U3切换到U4了,这时点阵LED上的就会显示图象,显示的图像就由前个中断刷新语句ADDR2=0; ADDR1=1; ADDR0=1这句产生。
前篇博文我们说到若想点阵LED某个灯点亮,P0口最少要有一个端口输出低电平,
ENLED = 0; ADDR3 = 0;使U4使能,
可以看出只要U4使能后,ADDR0 ADDR1 ADDR2无论如何取值,它的输出断必然有一个低电平。
因此在U3与U4切换的时候,把P0的所有端口全部置1,即短暂关闭点阵LED,就可以避免鬼影的产生。