单片机最小系统设计制作训练.docx
《单片机最小系统设计制作训练.docx》由会员分享,可在线阅读,更多相关《单片机最小系统设计制作训练.docx(37页珍藏版)》请在冰豆网上搜索。
单片机最小系统设计制作训练
(此文档为word格式,下载后您可任意编辑修改!
)
单片机最小系统设计制作训练
3.1单片机最小系统设计制作
3.1.1单片机最小系统电路板硬件设计
单片机的主要功能是负责整个系统的控制,不承担复杂的数据处理任务,因此在设计单片机最小系统时通常选用AT89C5l、AT89C52、AT89S51、AT89S52(S系列芯片支持ISP功能)等型号的8位单片机作为MCU。
一个典型的单片机最小系统一般由时钟电路、复位电路、片外RAM、片外ROM、按键、数码管、液晶显示器、外部扩展接口等部分组成,图3.1、图3.2分别给出了单片机最小系统的结构框图、原理图。
图3.1单片机最小系统的结构框图
图3.2原理图
单片机最小系统时钟、复位、译码电路简介
1、时钟源电路
单片机内部具有一个高增益反相放大器,用于构成振荡器。
通常在引脚XTALl和XTAL2跨接石英晶体和两个补偿电容构成自激振荡器,结构如图2中Y1、C16、C17。
可以根据情况选择6MHz、12MHz或24MHz等频率的石英晶体,补偿电容通常选择30pF左右的瓷片电容。
2、复位电路
单片机小系统采用上电自动复位和手动按键复位两种方式实现系统的复位操作。
上电复位要求接通电源后,自动实现复位操作。
手动复位要求在电源接通的条件下,在单片机运行期间,用按钮开关操作使单片机复位。
其结构如图2中R24、R26、C18和K17。
上电自动复位通过电容C18充电来实现。
手动按键复位是通过按键将电阻R26与VCC接通来实现。
3、地址译码电路
最小系统上的全部硬件除EEPROM以外均是采用总线方式进行扩展的,每一个硬件均占用特定的物理地址。
为了减少芯片的使用数量和降低PCB板布线的复杂度,本系统使用小规模可编程逻辑器件GAL代替74系列芯片实现译码电路。
具体硬件见图2中U24。
3.2键盘显示电路设计
3.2.1键盘接口电路及程序设计
单片机键盘通常使用机械触点式按键开关,其主要功能是把机械上的通断转换成为电气上的逻辑关系。
也就是说,它能提供标准的TTL逻辑电平,以便与通用数字系统的逻辑电平相容。
小系统上设置了一个2行乘8列的阵列式键盘,系统硬件电路如图4所示。
电路结构采用总线扩展方式进行设计,同时使用P13和P14进行行选择,按键信号通过一片74LS245挂接到数据总线上,片选信号为KEY_CS,为其分配的物理地址为0xA100。
图3.3键盘接口电路
由于系统的键盘接口采用的是总线方式,因此读取按键数值变得相当方便,下面是使用C编写的读取键盘程序:
#defineKEYXBYTE[0xA100]//键盘地址
sbitfirst_row=P1^4;//键盘第一行控制
sbitsecond_row=P1^3;//键盘第二行控制
ucharM_key;//键盘数值暂存单元
first_row=0;//读取第一行键盘数值
second_row=1;
M_key=KEY;
first_row=1;//读取第二行键盘数值
second_row=0;
M_key=KEY;
系统采用定时扫描的方式(扫描间隔为4ms,内部定时器定时中断间隔为2ms,每两次定时中断进行一次键盘扫描)进行键盘识别,设计程序时通常要进行以下四个方面的处理:
(1)每隔4ms读取一次键盘的数值,判断有无按键按下。
具体方法是令first_row=0,second_row=0,M_key=KEY,判断M_key的值是否为0xFF,如果等于0xFF说明没有按键按下,如果不等于0xFF说明有按键按下。
(2)去除按键的机械抖动影响。
通过设置状态标志位first_getkey来判断连续两次扫描键盘是否都检测到有按键按下。
如果没有连续两次都检测到按键按下则按照键抖动处理;否则,认为确实有按键按下。
(3)准确输出按键值keynum,并提供获得有效按键标志getkey。
(4)防止按键冲突。
在获得有效按键以后设定状态标志位keyon来实现每次只处理一个按键,且无论一次按键时间有多长,系统仅执行一次按键功能程序。
键盘识别程序流程如图3.4所示。
程序代码将在介绍完数码管显示器以后统一给出。
图3.4键盘识别程序流程
3.2.2数码管接口电路及程序设计
本系统共设置了8个7段码数码管显示器,电路结构如图3.5所示。
图3.58个7段码数码管显示器电路
电路结构同样采用总线扩展方式进行设计,其中使用的数码管为连4位的共阳型数码管。
通过芯片U15(74HC573)锁存,为数码管提供段码数据。
通过芯片U14(74HC573)、U13(74HC138)以及三极管Q1—Q8将低三位地址A2..0进行硬件译码,为每个数码管提供一个唯一的物理地址,具体地址为0xA000—0xA007。
此外本电路结构还考虑了不同数码管进行显示切换时的消隐问题,在编写程序时不用通过额外的处理进行消隐。
由于为每个数码管都分配了一个固定的物理地址,在编写程序时只要将相应的段码数据写入到对应的地址当中便可以完成显示,例如要在第二个数码管上显示“1”,使用C语言办成实现如下:
#define7SEG_LED2XBYTE[0xA001]//第二个数码管的地址定义
7SEG_LED2=0xF9;//将“1”的段码数据“0xF9”输出到段码锁
//存器U15上,同时低三位地址A2..0“001”
//经过硬件译码使位码LED2为高。
通过上面一条语句便可以实现在第二个数码管上显示“1”的操作。
但由于全部数码管的段码线共用,在同一时刻只能点亮一个数码管,所以在实际应用中必须采用动态扫描的方式进行8个数码管的显示。
具体实现方法是使用内部定时器每2ms产生一次定时中断,系统在每进入到一次定时中断后更新一次显示内容,对于每个数码管来说其显示的周期为16ms,由于显示频率足够高人眼感觉不到闪烁的存在。
数码管显示程序流程如下:
图3.6数码管显示程序流程
在编写程序时考虑到单片机的资源利用情况,使用一个定时器为键盘扫描和数码管显示更新提供定时服务,定时中断函数流程如图3.7所示。
定时器定时间隔为2ms,每次进入中断调用一次显示更新函数,每两次进入中断调用一次扫描键盘函数。
图3.8给出了利用以上给出的键盘扫描和数码管显示以及中断函数实现一个最简单系统的主程序流程图。
在主程序中通过查询方式判断getkey(获得有效按键标志位,当获得一个有效按键后键盘扫描函数讲其置为1),当获得有效按键后令所有的数码管显示按键的数值。
图3.7定时中断函数流程
图3.8主程序流程图
C程序代码如下:
#include
#include
#include
#defineucharunsignedchar
/*数码管物理地址*/
#defineLED1XBYTE[0xA000]
#defineLED2XBYTE[0xA001]
#defineLED3XBYTE[0xA002]
#defineLED4XBYTE[0xA003]
#defineLED5XBYTE[0xA004]
#defineLED6XBYTE[0xA005]
#defineLED7XBYTE[0xA006]
#defineLED8XBYTE[0xA007]
/*键盘物理地址*/
#defineKEYXBYTE[0xA100]
/*扫描键盘使用的变量*/
sbitfirst_row=P1^4;//键盘第一行控制
sbitsecond_row=P1^3;//键盘第二行控制
bitfirst_getkey=0,control_readkey=0;//读键盘过程中的标志位
bitgetkey=0;//获得有效键值标志位,等于1时代表得到一个有效键值
bitkeyon=0;//防止按键冲突标志位
ucharkeynum=0;//获得的有效按键值寄存器
/*数码管显示使用的变量和常量*/
ucharlednum=0;//数码管显示位控制寄存器
ucharcodesegtab[18]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,
0xa1,0x86,0x8e,0x8c,0xff};//七段码段码表
//"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","P","black"
/*函数声明*/
voidleddisp(void);//数码管显示更新函数
voidreadkey(void);//键盘扫描函数
/*T0定时中断处理函数*/
voidintT0()interrupt1
{
TH0=-4230/256;//定时器中断时间间隔2ms
TL0=-4230%256;
leddisp();//每次定时中断显示更新一次
if(control_readkey==1)//每两次定时中断扫描一次键盘
{
readkey();
}
control_readkey=!
control_readkey;
}
/*主函数*/
voidmain(void)
{
TMOD=0x01;//设定定时器T0工作模式为模式1
TH0=-4230/256;//定时器中断时间间隔2ms
TL0=-4230%256;
TCON=0x10;
ET0=1;
EA=1;
while
(1)//等待获得有效按键
{
if(getkey==1)//判断是否获得有效按键
{
getkey=0;//当获得有效按键时,清除标志位。
led[0]=keynum;//令全部数码管显示按键值
led[1]=keynum;
led[2]=keynum;
led[3]=keynum;
led[4]=keynum;
led[5]=keynum;
led[6]=keynum;
led[7]=keynum;
}
}
}
/***************************************************
键盘扫描函数
原型:
voidreadkey(void);
功能:
当获得有效按键时,令getkey=1,keynum为按键值
****************************************************/
voidreadkey(void)
{
ucharM_key=0;////键盘数值暂存单元
first_row=0;
second_row=0;
M_key=KEY;
if(M_key!
=0xff)//如果有连续两次按键按下,认为有有效按键按下。
消除按键抖动
{
if(first_getkey==0)
{
first_getkey=1;
}
else//当有有效按键按下时,进一步识别是哪一个按键
{
if(keyon==0)//防止按键冲突,当还有未释放的按键时不对其它按键动作响应
{
first_row=0;//扫描第一行按键
second_row=1;
M_key=KEY;
if(M_key!
=0xff)
{
switch(M_key)
{
case0xfe:
keynum=0x00;
break;
case0xfd:
keynum=0x01;
break;
case0xfb:
keynum=0x02;
break;
case0xf7:
keynum=0x03;
break;
case0xef:
keynum=0x04;
break;
case0xdf:
keynum=0x05;
break;
case0xbf:
keynum=0x06;
break;
case0x7f:
keynum=0x07;
break;
}
}
else
{
second_row=0;//扫描第二行按键
first_row=1;
M_key=KEY;
switch(M_key)
{
case0xfe:
keynum=0x08;
break;
case0xfd:
keynum=0x09;
break;
case0xfb:
keynum=0x0a;
break;
case0xf7:
keynum=0x0b;
break;
case0xef:
keynum=0x0c;
break;
case0xdf:
keynum=0x0d;
break;
case0xbf:
keynum=0x0e;
break;
case0x7f:
keynum=0x0f;
break;
}
}
getkey=1;//获得有效按键数值
keyon=1;//防止按键冲突,当获得有效按键时将其置1
}
}
}
else
{
first_getkey=0;
keyon=0;//防止按键冲突,当所有的按键都释放时将其清0
}
}
/***************************************************
数码管显示函数
原型:
voidleddisp(void);
功能:
每次调用轮流显示一位数码管
****************************************************/
voidleddisp(void)
{
switch(lednum)//选择需要显示的数码位
{
case0:
LED1=segtab[led[0]];
break;
case1:
LED2=segtab[led[1]];
break;
case2:
LED3=segtab[led[2]];
break;
case3:
LED4=segtab[led[3]];
break;
case4:
LED5=segtab[led[4]];
break;
case5:
LED6=segtab[led[5]];
break;
case6:
LED7=segtab[led[6]];
break;
case7:
LED8=segtab[led[7]];
break;
}
if(lednum==0)//更新需要显示的数码管位置
{
lednum=7;
}
else
{
lednum=lednum-1;
}
}
3.2.3液晶接口电路及程序设计
传统的显示器件数码管已经不能满足显示复杂操作界面的要求。
因此最小系统中除了数码管显示器以外,还接入了一个液晶显示模块,其型号为SGM12864C,可以显示64行128列的点阵数据,通过编写相应的程序可以显示英文、汉字或图形,可以实现比较复杂的用户操作界面。
硬件接口电路如图3.9所示。
液晶模块的结构及操作控制请参阅SMG12864C.PDF。
图3.9硬件接口电路
在硬件设计中使用译码电路提供的LCD_R_CS、LCD_L_CS、LCD_E为液晶模块提供片选及使能信号。
使用系统的地址信号A0控制向液晶写入的是命令字还是数据字。
此外将液晶的读写控制端接地,禁止从液晶中读数据,在向液晶中写入一个数据或命令后延时一段时间再向其中写入新的数据,避免由于液晶处在忙状态导致写入错误的情况发生。
根据地址译码器提供的地址以及信号A0,可以得出向液晶左右两个控制器中写入命令和数据的物理地址,下面给出在C语言中的具体定义:
#defineLCD_L_DATAXBYTE[0xA201]//左半边液晶数据地址
#defineLCD_R_DATAXBYTE[0xA301]//右半边液晶数据地址
#defineLCD_L_CommandXBYTE[0xA200]//左半边液晶命令地址
#defineLCD_R_CommandXBYTE[0xA300]//右半边液晶命令地址
为了使液晶能够显示字符、汉字以及图形,需要对其进行正确的设置,具体过程如下:
(1)在系统上电后对其进行初始化设置。
向左右两部分控制器写入控制字0xC0,设置显示的初始行。
向左右两部分控制器写入控制字0x3F,将液晶的左右两部分显示开启。
此部分功能由后面给出程序中的lcd_initial()函数完成。
(2)在液晶指定位置显示给定的数据。
完成液晶的初始化以后,通过写入命令字确定显示的列地址和页地址,然后写入需要显示的数据。
以下给出了在液晶指定位置显示大小为8*8字符、16*16汉字以及128*64图形的C语言程序,用户可以根据需要利用函数lcd_write_byte()编写显示任意大小图形和文字的函数。
#include
#include
#include
#defineucharunsignedchar
#defineLCD_L_DATAXBYTE[0xA201]//左半边液晶数据地址
#defineLCD_R_DATAXBYTE[0xA301]//右半边液晶数据地址
#defineLCD_L_CommandXBYTE[0xA200]//左半边液晶命令地址
#defineLCD_R_CommandXBYTE[0xA300]//右半边液晶命令地址
ucharcodeG[8]={0x00,0x00,0x3e,0x41,0x49,0x49,0x7a,0x00};/*G*/
ucharcodeU[8]={0x00,0x00,0x3f,0x40,0x40,0x40,0x3f,0x00};/*U*/
ucharcodeO[8]={0x00,0x00,0x3e,0x41,0x41,0x41,0x3e,0x00};/*O*/
/*--宋体12;此字体下对应的点阵为:
宽x高=16x16--*/
/*--文字:
国--*/
ucharcodeguo[32]=
{0x00,0xFE,0x02,0x0A,0x8A,0x8A,0x8A,0xFA,0x8A,0x8A,0x8A,0x0A,0x02,0xFE,0x00,0x00,
0x00,0xFF,0x40,0x48,0x48,0x48,0x48,0x4F,0x48,0x49,0x4E,0x48,0x40,0xFF,0x00,0x00};
/*--文字:
防--*/
ucharcodefang[32]=
{0x00,0xFE,0x22,0x5A,0x86,0x02,0x08,0x08,0xF9,0x8E,0x88,0x88,0x88,0x08,0x08,0x00,0x00,
0xFF,0x04,0x08,0x47,0x20,0x18,0x07,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00};
/*--文字:
科--*/
ucharcodeke[32]=
{0x10,0x12,0x92,0x72,0xFE,0x51,0x91,0x00,0x22,0xCC,0x00,0x00,0xFF,0x00,0x00,0x00,0x04,
0x02,0x01,0x00,0xFF,0x00,0x04,0x04,0x04,0x02,0x02,0x02,0xFF,0x01,0x01,0x00};
/*--文字:
技--*/
ucharcodeji[32]=
{0x08,0x08,0x88,0xFF,0x48,0x28,0x00,0xC8,0x48,0x48,0x7F,0x48,0xC8,0x48,0x08,0x00,0x01,
0x41,0x80,0x7F,0x00,0x40,0x40,0x20,0x13,0x0C,0x0C,0x12,0x21,0x60,0x20,0x00};
/*--文字:
大--*/
ucharcodeda[32]=
{0x20,0x20,0x20,0x20,0x20,0x20,0xA0,0x7F,0xA0,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x00,
0x80,0x40,0x20,0x10,0x0C,0x03,0x00,0x01,0x06,0x08,0x30,0x60,0xC0,0x40,0x00};
/*--文字:
学--*/
ucharcodexue[32]=
{0x40,0x30,0x10,0x12,0x5C,0x54,0x50,0x51,0x5E,0xD4,0x50,0x18,0x57,0x32,0x10,0x00,0x00,
0x02,0x02,0x02,0x02,0x02,0x42,0x82,0x7F,0x02,0x02,0x02,0x02,0x02,0x02,0x00};
/**********************************
液晶驱动函数声明
***********************************/
voidlcd_initial(void);
voidlcd_write_byte(ucharxpos,ucharypos,uchar*byte);
voidlcd_write_char(ucharchar_xpos,ucharchar_ypos,uchar*char_source_addr);
voidlcd_write_hanzi(ucharhanzi_xpos,ucharhanzi_ypos,uchar*hanzi_source_addr);
voidlcd_clear(void);
voidlcd_fill(void);
voiddelay(uchartime_nop);
voidmain(void)
{
lcd_initial();//初始化液晶
lcd_clear();//液晶清屏
lcd_write_char(0,0,G);//显示"A"
lcd_write_char(1,0,U);//显示"B"
lcd_write_char(2,0,O);//显示"C"
lcd_write_hanzi(2,2,guo);//显示"国"
lcd