在前面的章节中详细介绍了C51语言的知识和MCS-51单片机的基本功能接口及应用,本章主要侧重于单片机应用系统设计,通过5个比较典型的设计实例,介绍单片机应用系统设计的步骤、思路、方法以及应用系统的硬件电路与软件设计等,使读者了解并掌握单片机系统的设计。本章主要包括以下内容:
· 简单的跑马灯设计;
· 矩形波发生器;
· 4路抢答器;
· AT24C02读写驱动程序;
· 双端口RAM IDT7132的使用。
13.1 简单的跑马灯设计
跑马灯可以用MCS-51单片机控制一个LED点阵来实现,参见5.2.3小节。一个简单的跑马灯的显示情况如图13.1所示,图中的每一个小方格代表一个发光二极管,黑色代表相应位置的发光二极管被点亮,白色的空格表示未被点亮。图13.1所示为从时刻1到时刻4这段时间LED点阵变化的情况,也就是每过一个时间片,“+”向左移动一个位置,因此如果有11个类似的时刻,看上去就是“+”从右边移入从左边移出,有从而产生跑马灯的效果。

图13.1 跑马灯的移动显示
13.1.1 硬件设计
由于图13.1中发光管的数量较多(8×3=24),直接使用MCS-51单片机的I/O端口是很不经济的,因此需要扩展外部I/O通道。对于这种简单的显示电路,通常采用常用的串入并出移位寄存器扩展I/O通道。由于LED点阵只有3组,每组8位,因此通常将这3组全部接在一片移位寄存器的输出端。
这种设计方法利用了人眼的视觉暂留效应,节约了硬件成本。人眼的视觉暂留时间是0.05s,当连续的图像变化超过每秒24帧画面的时候,人眼便无法分辨每幅单独的静态画面,因而看上去是平滑连续的视觉效果。在显示时,3组LED分时轮流显示,即某一组显示时其他两组全灭,只要循环显示一次的时间不超过40ms,人眼看到的效果就和3组同时显示的效果一样。
为保证足够的驱动电流,每组发光管的公共端电流由一只8550晶体管提供,由单片机的I/O端口控制晶体管的导通或截至。完整的电路如图13.2所示。

图13.2 8051单片机控制的跑马灯电路图
13.1.2 程序设计
在程序中产生一组发光二极管显示的时间片有两种方法。一种是在程序中采用延时,在这段延时时间中恒显示一组发光二极管,时间片用完后再显示另外一组并延时同样的时间,如此循环往复。这是一种堵塞型的写法,在延时的这段时间内单片机不能处理其他事务,效率低下。
另一种方法是采取定时器中断的方式。每产生一次定时器中断,就切换到下一组的显示,直到下一次定时器中断产生。这种方法占用CPU时间很少,在显示的同时还可以处理其他事务,效率较高,是通常采用的方法。
在本例中,每5ms产生一次定时器中断,切换一次显示,因此3组轮流一次要用15ms,刷新频率约为67Hz,完全满足刷新频率大于24Hz的要求。由于采用串行扩展I/O通道的方法,因此单片机向74LS164发送数据需要一定的时间。根据中断服务程序尽可能耗时短的原则,显示函数(包括对于74LS164的驱动)不应在中断服务程序中实现,而是应该仅在中断设置某个标志,通知显示函数当前应切换到哪一组显示即可。
需要注意的是,在切换显示的时候要将3个晶体管全部截至,也就是所有的发光二极管全灭,以防止由于74LS164输出端的数据变化而带来“串亮”现象。
程序流程如图13.3所示,其中LED_selection表示当前要显示哪一组发光二极管,当LED_selection为0时显示第一组,为1时显示第二组,为2时显示第三组。
程序如例13-1所示。
【例13-1】图13.2中使用12.0MHz的晶振,根据晶振频率设置定时器初值,每5ms产生一次中断,随后执行切换显示的动作,每500ms显示的图形向左移动一次。
#include <reg51.h>
typedef unsigned char uchar;
sbit CLK=P2^6;
sbit SEND_DATA=P2^7;
sbit en1=P2^2; //P2.2控制第一组发光二极管
sbit en2=P2^1; //P2.1控制第一组发光二极管
sbit en3=P2^0; //P2.0控制第一组发光二极管
bit switch_en; //允许切换标志
uchar LED_selection; //组选变量,指代要显示那一组
uchar index; //字型索引,指代要显示什么数据
uchar counter; //时间片计数,控制图形移动速度
uchar code led_code[3][11]= //每一组的字型
{{0x0,0x0,0x01,0x02,0x04,0x08,
0x10,0x20,0x40,0x80,0x0},
{0x0,0x01,0x03,0x07,0x0e,0x1c,
0x38,0x70,0xe0,0xc0,0x80},
{0x0,0x0,0x01,0x02,0x04,0x08,
0x10,0x20,0x40,0x80,0x0}};
void send_164(uchar); //74LS164驱动函数声明
void display(void); //显示函数声明
void main(void)
{
switch_en=0;
LED_selection=0;
index=0;counter=0; //对程序中的全局变量初始化
TR0=0; //禁止T0
TMOD=0x01; //T0选择工作方式1,16位定时器
TH0=0xEC; //定时时间为5ms时,TH0=0xEC
TL0=0x78; //TL0=0x78
EA=1; //使能CPU中断
ET0=1; //使能T0溢出中断
TR0=1; //T0开始运行
while(1) //无限循环
{display();} //调用显示函数
}
void display(void) //显示函数定义
{
if(switch_en==0) //若切换时机未到
return; //则返回
switch_en=0; //否则切换显示,切换一次后即清零以免误切换
en1=1;en2=1;en3=1; //关闭所有显示,防止“串亮”
if(counter==100) //如果过了500ms
{
counter=0; //counter归零
index++; //显示向左移动一次
index%=11; //若移动完,归零,重新开始
}
send_164(led_code[LED_selection][index]); //发送要显示的数据
if(LED_selection==1) //开相应的显示
en1=0;
else if(LED_selection==2)
en2=0;
else
en3=0;
}
void send_164(uchar d) //74LS164驱动函数声明
{
uchar i;
CLK=0;
for(i=0;i<=7;i++) //发送8位数据,高位在前
{
CLK=0; //将时钟信号置为低电平,为产生上升沿做准备
if((0x80>>i)&d==0) //如果当前要发送的位为0
SEND_DATA=0; //相应地将74LS164的数据信号置为低电平
else //否则
SEND_DATA=1; //将74LS164的数据信号置为高电平
CLK=1; //将时钟信号置为高电平,产生上升沿
CLK=1; //延时,以保证满足74LS164的时序
}
}
void isr_t0(void) interrupt 1
{
TH0=0xEC; //对TH0和TL0重新赋值
TL0=0x78;
counter++; //5ms计数加1,在display()函数中使用
switch_en=1; //允许切换
LED_selection++; //将组选计数值加1
LED_selection%=3; //如果是3,说明已经轮流一遍,归零,重新开始
}






