文章包含对于本来是使用延时函数来实现动态数码管的人,
刚开始学习用定时器实现动态数码管通常会遇到的问题:
1.外设无法正常显示
2.led灯状态出现异常
以下均是自己学习研究所得,可能还有不足,也请以辩证眼光看待
如果后面我发现不足的话会在第15届省赛前发出来。
完整源码
main.c
#include<STC15F2K60S2.h>
#include<smg.h>
#include<iic.h>
bit x;//界面切换标志
bit y;//DAC输出切换标志
bit z;//led灯启用禁止标志
bit w;//数码管启用禁止标志
bit Key_up = 1;//按键松开标志
bit AD_flag = 1;//AD输出标志
bit led_flag = 1;//led状态更新标志
unsigned int count_f = 0;//在计数器0中记录频率
unsigned int F = 0; //频率记录
unsigned char Ad = 0;//记录Rb3的数字量
unsigned char V = 0;//用于DA输出的记录
//界面切换
void Display()
{
if(w)
Mode2();
else
{
if(x == 0)
{
Mode1(Ad);
if(y == 0)
V = 102;
else
V = Ad;
}
else
Mode0(F);
}
}
//20毫秒延时用于按键消抖
void Delay20ms(void)
{
unsigned char data i, j;
i = 234;
j = 115;
do
{
while (--j);
} while (--i);
}
//按键扫描函数
void KeyScan()
{
if(Key_up && (P33 == 0 || P32 == 0 || P31 == 0 || P30 == 0))
{
Delay20ms();
Key_up = 0;
if(P33 == 0)x = !x;
if(P32 == 0)y = !y;
if(P31 == 0)z = !z;
if(P30 == 0)w = !w;
}
else if(P33 ==1 && P32 ==1 && P31 ==1 && P30 ==1)
{
Key_up = 1;
}
}
//点亮某个led灯且不改变其他led灯状态
void Ledx(unsigned char pos,n)
{
if(n)
P0 = P0 | 0x01 << (pos - 1);
else
P0 = P0 & ~(0x01 << (pos -1));
SelectHC573(4);
}
//led灯状态改变
void led()
{
static unsigned char temp = 0;
if(z)
{
P0 = 0xff;
SelectHC573(4);
}
else
{
if(!x) //L1,L2
{
Ledx(1,0);
Ledx(2,1);
}
else
{
Ledx(1,1);
Ledx(2,0);
}
temp = Ad * 10 / 51;
if(temp >= 35)Ledx(3,0); //L3
else if(temp >= 25)Ledx(3,1);
else if(temp >= 15)Ledx(3,0);
else Ledx(3,1);
if(F >= 10000)Ledx(4,0); //L4
else if(F >= 5000)Ledx(4,1);
else if(F >= 1000)Ledx(4,0);
else Ledx(4,1);
if(!y)Ledx(5,1); //L5
else Ledx(5,0);
}
}
//主函数
void main()
{
Init_System();
while(1)
{
KeyScan();
if(AD_flag)
{
Ad = Ad_Read();
AD_flag = 0;
Da_Write(V);
}
Display();
if(led_flag)
{
led_flag = 0;
led();
}
}
}
//计数器0的中断服务函数
void Service_Timer0() interrupt 1
{
count_f++;
}
//定时器1的中断服务函数
void ServiceTimer1() interrupt 3
{
unsigned int i,i1,i2,i3;
TH1 = (65536 - 1000) / 256;
TL1 = (65536 - 1000) % 256;
SMG_Byte(i,Seg_buf[i]);
if(++i == 8)i = 0;
//一秒传出一个新的频率
if(++i1 == 1000)
{
F = count_f;
i1 = 0;
count_f = 0;
}
//400毫秒传出一个AD读取标志
if(++i2 == 400)
{
i2 = 0;
AD_flag = 1;
}
//10毫秒传出一个led更新标志
if(++i3 == 10)
{
led_flag = 1;
i3 = 0;
}
}
smg.c
与数码管相关的代码基本都在这里面
#include<STC15F2K60S2.h>
//不带小数点的段码表
code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0x88, //A
0x83, //b
0xc6, //C
0xa1, //d
0x86, //E
0x8e //F
};
//带小数点的段码表
code unsigned char Seg_point[] =
{
0x40, //0.
0x79, //1.
0x24, //2.
0x30, //3.
0x19, //4.
0x12, //5.
0x02, //6.
0x78, //7.
0x00, //8.
0x10, //9.
};
//数码管在这里读取段码
unsigned char Seg_buf[] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};
//锁存器选择
void SelectHC573(unsigned char n)
{
switch(n)
{
case 4:
P2 = (P2 & 0x1f) | 0x80;
break;
case 5:
P2 = (P2 & 0x1f) | 0xa0;
break;
case 6:
P2 = (P2 & 0x1f) | 0xc0;
break;
case 7:
P2 = (P2 & 0x1f) | 0xe0;
break;
}
P2 = (P2 & 0x1f)| 0x00;//锁存器打开后会马上关闭
}
//初始化相关器件
void Init_System()
{
P0 = 0x00;
SelectHC573(5);
P0 = 0xff;
SelectHC573(4);
TMOD = 0x16;//计数器0和定时器1
TH0 = 0xff;//初始化计数器0,来一个脉冲就溢出
TL0 = 0xff;
TH1 = (65536 - 1000) / 256;//1ms定时
TL1 = (65536 - 1000) % 256;
ET0 = 1;
ET1 = 1;
EA = 1;
TR1 = 1;
TR0 = 1;
}
//数码管的位选和段选
void SMG_Byte(unsigned char pos,value)
{
P0 = 0xff;
SelectHC573(7);
P0 = 0x01 << pos;
SelectHC573(6);
P0 = value;
SelectHC573(7);
P0 = 0xff;
}
//频率界面
void Mode0(unsigned int F)
{
Seg_buf[0] = Seg_Table[15];
Seg_buf[1] = 0xff;
if(F > 99999)
Seg_buf[2] = Seg_Table[F / 100000];
else
Seg_buf[2] = 0xff;
if(F > 9999)
Seg_buf[3] = Seg_Table[F / 10000 % 10];
else
Seg_buf[3] = 0xff;
if(F > 999)
Seg_buf[4] = Seg_Table[F / 1000 % 10];
else
Seg_buf[4] = 0xff;
if(F > 99)
Seg_buf[5] = Seg_Table[F / 100 % 10];
else
Seg_buf[5] = 0xff;
if(F > 9)
Seg_buf[6] = Seg_Table[F / 10 % 10];
else
Seg_buf[6] = 0xff;
Seg_buf[7] = Seg_Table[F % 10];
}
//电压界面
void Mode1(unsigned char V)
{
Seg_buf[0] = 0xc1;
Seg_buf[1] = 0xff;
Seg_buf[2] = 0xff;
Seg_buf[3] = 0xff;
Seg_buf[4] = 0xff;
Seg_buf[5] = Seg_point[V / 51];
Seg_buf[6] = Seg_Table[(V * 10) / 51 % 10];
Seg_buf[7] = Seg_Table[V * 100 / 51 % 10];
}
//停用数码管
void Mode2()
{
Seg_buf[0] = 0xff;
Seg_buf[1] = 0xff;
Seg_buf[2] = 0xff;
Seg_buf[3] = 0xff;
Seg_buf[4] = 0xff;
Seg_buf[5] = 0xff;
Seg_buf[6] = 0xff;
Seg_buf[7] = 0xff;
}
smg.h
#ifndef _smg_h_
#define _smg_h_
code unsigned char Seg_Table[];
code unsigned char Seg_point[];
unsigned char Seg_buf[];
void SelectHC573(unsigned char n);
void Init_System();
void SMG_Byte(unsigned char pos,value);
void Mode0(unsigned int F);
void Mode1(unsigned char V);
void Mode2();
#endif
iic.c
对iic.c的底层代码的调用在最后面
#include<STC15F2K60S2.h>
#include<intrins.h>
sbit sda = P2^1;
sbit scl = P2^0;
#define DELAY_TIME 5
//
static void I2C_Delay(unsigned char n)
{
do
{
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
}
while(n--);
}
//
void I2CStart(void)
{
sda = 1;
scl = 1;
I2C_Delay(DELAY_TIME);
sda = 0;
I2C_Delay(DELAY_TIME);
scl = 0;
}
//
void I2CStop(void)
{
sda = 0;
scl = 1;
I2C_Delay(DELAY_TIME);
sda = 1;
I2C_Delay(DELAY_TIME);
}
//
void I2CSendByte(unsigned char byt)
{
unsigned char i;
for(i=0; i<8; i++){
scl = 0;
I2C_Delay(DELAY_TIME);
if(byt & 0x80){
sda = 1;
}
else{
sda = 0;
}
I2C_Delay(DELAY_TIME);
scl = 1;
byt <<= 1;
I2C_Delay(DELAY_TIME);
}
scl = 0;
}
//
unsigned char I2CReceiveByte(void)
{
unsigned char da;
unsigned char i;
for(i=0;i<8;i++){
scl = 1;
I2C_Delay(DELAY_TIME);
da <<= 1;
if(sda)
da |= 0x01;
scl = 0;
I2C_Delay(DELAY_TIME);
}
return da;
}
//
unsigned char I2CWaitAck(void)
{
unsigned char ackbit;
scl = 1;
I2C_Delay(DELAY_TIME);
ackbit = sda;
scl = 0;
I2C_Delay(DELAY_TIME);
return ackbit;
}
//
void I2CSendAck(unsigned char ackbit)
{
scl = 0;
sda = ackbit;
I2C_Delay(DELAY_TIME);
scl = 1;
I2C_Delay(DELAY_TIME);
scl = 0;
sda = 1;
I2C_Delay(DELAY_TIME);
}
void Da_Write(unsigned char n)
{
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(0x40);
I2CWaitAck();
I2CSendByte(n);
I2CWaitAck();
I2CStop();
}
unsigned char Ad_Read()
{
unsigned char temp;
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(0x03);
I2CWaitAck();
I2CStart();
I2CSendByte(0x91);
I2CWaitAck();
temp = I2CReceiveByte();
I2CSendAck(1);
I2CStop();
return temp;
}
iic.h
#ifndef _iic_h_
#define _iic_h_
void Da_Write(unsigned char n);
unsigned char Ad_Read();
#endif
-
本次代码和我以前的代码有很大的不同。主要不同的是在键盘扫描函数和数码管的动态实现,其中最重要的是数码管的动态现实是用定时器来实现的。
以前我是使用延时函数完成的数码管的动态显示,但是在后来发现有一个巨大的弊端。在超声波模块在超声波发出后就必须进行while循环停留计时,但是又不能在while循环中调用数码管显示函数。 -
因为此时的while循环是要进行计时的,收到超声波传回信号就要马上退出循环并停止计时,用所计时间计算距离。若是在while循环中调用数码管显示函数则可能在要退出循环时才刚开始执行数码管显示函数,这样就会导致计时时间增多,进而影响超声波测距。若是不调用数码管显示函数 ,在超过最大测距时,也就是进行了65.536毫秒while停留,这已经影响了数码管的动态显示。
-
最后找到了用定时器进行数码管的动态显示能解决这个问题。但是对于刚开始使用定时器进行数码管的动态显示会遇到两个问题。
-
1.由于少了用于数码管动态显示的8毫秒延时导致主函数中的while(1)的死循环运行一遍的时间少了几倍到几十倍,我也不知道我的话准不准确但是我是这样理解的:**程序运行一遍的时间过短导致外设的相关函数还没来得及读出数据就再次被执行。**当然解决方法就是进行一个定时,每个多少时间执行一次外设的函数。如下
-
中断服务函数在每400毫秒就传出一个标志用于执行外设的函数
-
2.由于数码管的动态显示是用定时器完成的,那么就会导致不知道会在哪里进入中断服务函数。而进入中断服务函数中P0引脚的值就会改变,若是在led()函数的ledx(3,1)函数中的P0的值更改之后进入中断服务函数。也就是在ledx()函数中的else之后SelectHC573(4)之前进入中断服务函数,那么P0值被改变就会引起led灯不按要求点亮。实际上运行出来结果会有很多led灯点亮,完全不符合要求。
void led()
{
static unsigned char temp = 0;
if(z)
{
P0 = 0xff;
SelectHC573(4);
}
else
{
if(!x) //L1,L2
{
Ledx(1,0);
Ledx(2,1);
}
else
{
Ledx(1,1);
Ledx(2,0);
}
temp = Ad * 10 / 51;
if(temp >= 35)Ledx(3,0); //L3
else if(temp >= 25)Ledx(3,1);
else if(temp >= 15)Ledx(3,0);
else Ledx(3,1);
if(F >= 10000)Ledx(4,0); //L4
else if(F >= 5000)Ledx(4,1);
else if(F >= 1000)Ledx(4,0);
else Ledx(4,1);
if(!y)Ledx(5,1); //L5
else Ledx(5,0);
}
}
//点亮某个led灯且不改变其他led灯状态
void Ledx(unsigned char pos,n)
{
if(n)
P0 = P0 | 0x01 << (pos - 1);
else
P0 = P0 & ~(0x01 << (pos -1));
SelectHC573(4);
}
- 可以在两个地方进行改进就能完全避免这个情况
- 1.在数码管的段选位选函数中,也就是下面的最后一行添加P0 = 0xff;这样就会在每次运行完中断服务函数后将P0 全部置1 。不会点亮无关led灯。同时这行代码也引出了后一个问题,不过就这一行代码就已经解决了led灯百分之九十九的led灯异常。
//数码管的位选和段选
void SMG_Byte(unsigned char pos,value)
{
P0 = 0xff;
SelectHC573(7);
P0 = 0x01 << pos;
SelectHC573(6);
P0 = value;
SelectHC573(7);
P0 = 0xff;
}
-2. 剩下的一个led灯异常就是在主函数中执行以下函数,若x一直为0按理说应该L1应该是一直点亮的,但是实际运行结果偶尔L1会突然变暗下去在亮起来,由于时间过短看起来就是一个led灯不稳定的闪烁,但是不是很明显。
- 解决就与外设的方案一样,进行定时扫描。不要太长10ms就行。
void led()
{
static unsigned char temp = 0;
if(z)
{
P0 = 0xff;
SelectHC573(4);
}
else
{
if(!x) //L1,L2
{
Ledx(1,0);
Ledx(2,1);
}
else
{
Ledx(1,1);
Ledx(2,0);
}
temp = Ad * 10 / 51;
if(temp >= 35)Ledx(3,0); //L3
else if(temp >= 25)Ledx(3,1);
else if(temp >= 15)Ledx(3,0);
else Ledx(3,1);
if(F >= 10000)Ledx(4,0); //L4
else if(F >= 5000)Ledx(4,1);
else if(F >= 1000)Ledx(4,0);
else Ledx(4,1);
if(!y)Ledx(5,1); //L5
else Ledx(5,0);
}
}
- 至于造成的原因与解决的理由,就我个人的理解来就是:还是在ledx()函数改变P0值之后进入中断服务函数中导致P0 = 0xff;退出中断服务函数后使要点亮led灯熄灭。当然这个所谓的概率可能只有不到十分之一。但是不要忘了代码运行一遍地时间本来就不多,可能也就是微秒级别。然而视觉停留则是在毫秒级别。在数量级的差距下以及现实都是以秒计算的,进行一个叠加就有很大的概率会影响视觉停留效果。
- 如果进行一个10毫秒的定时执行。10毫秒已经超过了视觉停留的时间了,那么现在,在ledx()函数改变P0值之后进入中断服务函数的概率可能就几千分之一,所谓小概率事件为不可能事件。当然也不会影响led灯的常亮。
- 以上全为我个人理解,请以辩证的眼光看待!!!