蓝桥杯备赛笔记.docx
《蓝桥杯备赛笔记.docx》由会员分享,可在线阅读,更多相关《蓝桥杯备赛笔记.docx(97页珍藏版)》请在冰豆网上搜索。
![蓝桥杯备赛笔记.docx](https://file1.bdocx.com/fileroot1/2023-7/6/db59e9b2-d93f-4b80-805b-681f9afe1003/db59e9b2-d93f-4b80-805b-681f9afe10031.gif)
蓝桥杯备赛笔记
1、P0口的复用
P2口高3位的值
38译码器低电平端口
功能
备注
000(0x00)
Y0
无
001(0x20)
Y1
010(0x40)
Y2
011(0x60)
Y3
8255的CE引脚
0:
使能8255
1:
禁用8255
100(0x80)
Y4
LED灯锁存信号
0:
点亮LED灯
1:
熄灭LED灯
101(0xA0)
Y5
UNL2003输出锁存信号
0:
断开继电器、禁用直流电机、关闭蜂鸣器
1:
闭合继电器、转动直流电机、开启蜂鸣器
110(0xC0)
Y6
数码管位选锁存信号
0:
禁用该位
1:
使能该位
111(0xE0)
Y7
数码管段选锁存信号
0:
点亮该段
1:
熄灭该段
PS1:
上电后需给所有锁存器初始化(Y5,Y6初始化为0x00,其余初始化为0xFF)
PS2:
使用P0口时,按如下方式:
禁用所有寄存器——P0口赋值——打开目标寄存器——禁用所用寄存器
PS3:
锁存器高电平选通,低电平关闭
/**********************************************
*@brief初始化开发板
*@paramnone
*@returnnone
************************************************/
voidinitial_board(void)
{
P0_BUS_COMcom;
P2&=0x1F;//禁用所有锁存器
for(com=3;com<8;com++){
if(com==UNL2003||com==DIGITAL_BIT)
P0=0x00;
else
P0=0xFF;
P2|=com<<5;
_nop_();
P2&=0x1F;
}
}
/**********************************************
*@brief通过P0总线传输数据
*@paramcom:
总线占用的端口;databuf:
传输的数据
*@returnnone
************************************************/
voidP0_BUS(unsignedcharcom,unsignedchardatabuf)
{
P2&=0x1F;//禁用所有锁存器
P0=databuf;
P2|=com<<5;
_nop_();
P2&=0x1f;
}
PS4:
数码管段码
unsignedcharcodeNUM[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
2、按键
一、单按键
单按键按如下流程图获取按键值:
分层思想在按键上的应用:
1、硬件层
从I/0口获取按键信息映射到keybuf(按键寄存器)上
2、驱动层
根据keybuf的值,分析按键是否有效(是否有抖动产生的),并返回按键编码
3、应用层
对不同按键的响应
分层的好处:
如果按键连接的I/O变化,或者按键所在的I/O口不连续,只需修改硬件层的程序,驱动层和应用层则不受影响。
/**********************************************
*@brief初始化键盘
*@paramnone
*@returnnone
************************************************/
voidinitial_key(void)
{
KEY0=1;
KEY1=1;
KEY2=1;
KEY3=1;
}
/**********************************************
*@brief读键值(硬件层)
*@paramnone
*@return读取的键值
************************************************/
staticuint8get_keybuf(void)
{
uint8keybuf;
keybuf=P3&0x0F;
returnkeybuf;
}
/**********************************************
*@brief获取按键编码值(驱动层)
*@paramnone
*@return按键的编码值
************************************************/
uint8readkey(void)
{
staticuint8lastkey=NOKEY;//上一次获取的键值
staticuint16keycount=0;//获取同一键值的次数
staticuint16keyovertime=KEY_OVER_TIME;//进入连击延时
staticuint16upspeed=0;//加速相应(长按越久连击延时越短)
uint8keytemp=NOKEY;//保存当前获取的键值
keytemp=get_keybuf();
if(keytemp==NOKEY){//是否有按键按下
keycount=0;
upspeed=0;
keyovertime=KEY_OVER_TIME;//退出连击模式
returnNOKEY;
}else{
if(keytemp==lastkey){//是否和上一次键值相同
if(++keycount==KEY_WOBBLE_TIME){//是否到达有效按键次数
returnkeytemp;
}else{
if(keycount>keyovertime){//是否长按进入连击模式
keycount=0;
if(upspeedupspeed+=50;
keyovertime=KEY_QUICK_TIME-upspeed;//进入连击模式
}
returnNOKEY;
}//else
}else{
lastkey=keytemp;//保留上一次按键值
keycount=0;
upspeed=0;
keyovertime=KEY_OVER_TIME;
returnNOKEY;
}//else
}//else
}
二、矩阵键盘
矩阵键盘原理介绍:
如图,当按下S7时P30和P37将相连。
这个时候有四种情况:
按下S7前P30的状态
按下S7前P37的状态
按下S7后
0
0
都为0
0
1
都为0(I/O口特性,P37会被P30拉低)
1
0
都为0
1
1
都为1
由上表可知,在S7按下前,只有在P30和P37处于不同状态下,S7才能使P30或P37状态产生变化,这样单片机才能检测到按键。
因此,矩阵键盘不能像单按键一样,把I/O全部拉高就可以(如果全部拉高,即P30与P37都为1,这样不管按键怎么按,两个I/O的状态始终不变,单片机当然无法检测到)。
其他按键同理可得,既然状态设为不同按键才能产生响应,那么要初始化为0xF0H还是0x0FH呢?
如果初始化为0x0FH,同一行按键按下时(例如S7,S11,S15,S19)都会使P30变为低电平,无法分别是同一行中哪一个按键产生反应。
若初始化为0XF0H,则会无法分辨同列的按键。
怎么办?
通常有以下两种方法:
1、扫描法
1)初始化为0xFFH
2)拉低P30(即先扫描第一行)
3)检测P34~P37中有无被拉低的引脚
4)若检测到P34~P37中有引脚被拉低,即可以确定按键,若无引脚被拉低,初始化为0xFFH后扫描下一行。
charget_num(void)
{
unsignedchari,j,m,n;
for(i=1,m=0x01;i<=4;i++,m=m<<1)
{
P3=0xFF&~m;
for(j=1,n=0x10;j<=4;j++,n=n<<1)
if((P3&n)==0)
returni*4-(4-j)-1;
}
return16;
}
2、线翻转法
1)令P3=0x0F;
2)获取P3口的值,获取行按键,保存到KEY_ROW里
3)令P3=0xF0;
4)获取P3口的值,获取列按键,保存到KEY_COL里
5)结合KEY_ROW和KEY_COL(KEY_COL|KEY_ROW),即可获取按键值。
假设按下的按键为S4,按以上步骤,则,KEY_ROW的值为0x07H,KEY_COL的值为0x70H。
KEY_COL|KEY_ROW=0x77。
其他按键同理,可以获得不同编码。
结合单按键的算法,对矩阵键盘进行消抖(把行按键当成单按键看待,当行按键消抖完成后,再获取一次列按键,生成按键编码返回)。
/**********************************************
*@brief初始化键盘
*@paramnone
*@returnnone
************************************************/
voidinitial_key(void)
{
KEYBOARD=0xFF;
}
/**********************************************
*@brief读键值(硬件层)
*@paramRowOrCol:
0为读取行键值,1为读取列键值
*@return读取的键值
************************************************/
staticuint8get_keybuf(BOOLRowOrCol)
{
uint8keybuf;
if(RowOrCol){
KEYBOARD=0xF0;
keybuf=KEYBOARD&0xF0;
}else{
KEYBOARD=0x0F;
keybuf=KEYBOARD&0x0F;
}
returnkeybuf;
}
/**********************************************
*@brief获取按键编码值(驱动层)
*@paramnone
*@return按键的编码值
************************************************/
uint8readkey(void)
{
staticuint8lastkey=NOKEY;//上一次获取的键值
staticuint16keycount=0;//获取同一键值的次数
staticuint16keyovertime=KEY_OVER_TIME;//进入连击延时
staticuint16upspeed=0;//加速相应(长按越久连击延时越短)
uint8keytemp_row=NOKEY;//保存当前获取的行键值
uint8keytemp_col=NOKEY;//保存当前获取的列键值
uint8keyvalue=NOKEY;
keytemp_row=get_keybuf(0);
if(keytemp_row==(NOKEY&0x0F)){//是否有按键按下
keycount=0;
upspeed=0;
keyovertime=KEY_OVER_TIME;//退出连击模式
returnNOKEY;
}else{
if(keytemp_row==lastkey){//是否和上一次键值相同
if(++keycount==KEY_WOBBLE_TIME){//是否到达有效按键次数
keytemp_col=get_keybuf
(1);
switch(keytemp_row|keytemp_col){
case0x77:
keyvalue=0;break;
case0xB7:
keyvalue=1;break;
case0xD7:
keyvalue=2;break;
case0xE7:
keyvalue=3;break;
case0x7B:
keyvalue=4;break;
case0xBB:
keyvalue=5;break;
case0xDB:
keyvalue=6;break;
case0xEB:
keyvalue=7;break;
case0x7D:
keyvalue=8;break;
case0xBD:
keyvalue=9;break;
case0xDD:
keyvalue=10;break;
case0xED:
keyvalue=11;break;
case0x7E:
keyvalue=12;break;
case0xBE:
keyvalue=13;break;
case0xDE:
keyvalue=14;break;
case0xEE:
keyvalue=15;break;
default:
break;
}
returnkeyvalue;
}else{
if(keycount>keyovertime){//是否长按进入连击模式
keycount=0;
if(upspeedupspeed+=25;//增加连击响应的速度
keyovertime=KEY_QUICK_TIME-upspeed;//进入连击模式
}
returnNOKEY;
}//else
}else{
lastkey=keytemp_row;//保留上一次按键值
keycount=0;
upspeed=0;
keyovertime=KEY_OVER_TIME;
returnNOKEY;
}//else
}//else
}
如果相对多键同时按也产生响应时(视为不同与单按键),只需在switch添加响应的键值即可。
例如同时按下S4,S5,添加的键值应为:
0x73H
PS:
以上程序若使用IAP15F2K61S2-89C52转换板,着会发现,按下键值键盘第一列与第二列时没有反应(从左往右),这是应为,IAP15F2K61S2-89C52转换板默认用P42和P44代替P36和P37。
按照分层思想,只需更改硬件层的代码,即可解决问题。
/**********************************************
*@brief读键值
*@paramRowOrCol:
0为读取行键值,1为读取列键值
*@return读取的键值
************************************************/
staticuint8get_keybuf(BOOLRowOrCol)
{
uint8keybuf;
if(RowOrCol){
KEYBOARD=0x30;
P4|=0x14;//由于转接板问题,键盘列3、列4分别接在P42、P44上
keybuf=KEYBOARD&0x30;
if(P4&0x04)
keybuf|=0x40;
if(P4&0x10)
keybuf|=0x80;
}else{
KEYBOARD=0x0F;
P4&=0xEB;
keybuf=KEYBOARD&0x0F;
}
returnkeybuf;
}
三、外部中断
IAP15有5个外部中断,分别为
中断类型
占用引脚
中断号
相关寄存器
INT0
P3.2
0
IE(B0):
是否开启中断
TCON(B0,B1):
中断的触发类型、
中断标志位
INT1
P3.3
2
IE(B2):
是否开启中断
TCON(B2,B3):
中断的触发类型、
中断标志位
INT2
P3.6
10
INT_CLKO(B3):
是否开启中断
INT3
P3.7
11
INT_CLKO(B4):
是否开启中断
INT4
P3.0
16
INT_CLKO(B6):
是否开启中断
在CT107D89训练板上,可以直接使用有INT0,INT1,INT4三个外部中断,其中INT0和INT1可以通过配置TCON寄存器中的IT0,IT1位选择下降沿触发
(1)或上升沿与下降沿触发(0),INT4仅能使用下降沿触发
使用外部中断通常需要以下两步:
①:
初始化所需中断(配置相关寄存器)
②:
编写中断服务程序
①初始化中断
开启相关中断位即总中断,选择合适的中断模式。
以上是初始化需要做的。
INT0和INT1中断允许位在IE寄存器
EX0:
0:
关闭INT0;1:
开启INT0;
EX1:
0:
关闭INT1;1:
开启INT1;
IE寄存器可以按位寻址,以开启INT0为例,
可以用IE|=0x01H;来开启INT0。
或者可以使用EX0=1;来开启INT0。
不管使用哪种方式,最后都不要忘记打开总中断开关EA。
INT4中断允许位在INT_CLKO寄存器
EX4:
0:
关闭INT4;1:
开启INT4;
该寄存器不能按位寻址,因此只能用INT_CLKO|=0x40H;来开启INT4。
允许中断中后,可以在TCON寄存器配置外部中断类型
IT0:
0:
INT0仅下降沿触发;1:
INT0可以由上升沿和下降沿触发
IE0:
INT0中断标志位,产生INT0中断后置‘1’,响应中断后硬件自动清0
IT1:
0:
INT1仅下降沿触发;1:
INT1可以由上升沿和下降沿触发
IE1:
INT1中断标志位,产生INT1中断后置‘1’,响应中断后硬件自动清0
仅INT0和INT1支持中断类型的选择,INT4只能使用下降沿触发中断。
当IT0=0时,INT0上升沿与下降沿触发中断;当IT0=1时,INT0下降沿触发中断。
INT1与IT1的关系同上
若上电后开启INT0和INT1中断,不配置IT0与IT1,默认为上升沿与下降沿触发中断。
当产生INT0中断时,IE0会被置1,(INT1中断置位IE1),然后进入中断服务程序,完成中断服务后,硬件自动清零。
INT4的中断标志隐藏,用户不可见。
(INT2,INT3的中断标志位对用户也是隐藏的)。
②编写中断服务程序
中断服务程序就是个特殊的函数。
通常函数要程序要主动在主函数或其他函数内调用,中断函数不能直接调用,只有在相应的中断触发后,CPU会自动进入中断函数。
voidINT0_Routine(void)interrupt0
{
/*dosomething*/
}
以上是INT0的中断函数。
中断函数一般有以下几个特点:
①返回值和参数都为空
②函数名后跟随“interrupt+中断号”
中断函数名可以任意命名,只要名称符合C语言的规范即可,没有特殊的要求。
以下是使用INT0的例子:
voidinitial_exinterrupt(void)
{
INT0=1;
IT0=1;//0:
上升沿和下降沿触发;1:
仅下降沿触发
EX0=1;//使能外部中断1
EA=1;
}
voidINT0_Routine(void)interrupt0
{
COUNT++;
}
四、定时器
IAP15拥有3个定时器:
定时器0,定时器1,定时器2
定时器
溢出中断号
拥有模式
相关寄存器
定时器0
1
模式0 :
16位自动重装模式
模式1:
16位不可重装模式
模式2 :
8位自动重装模式
模式3 :
16位自动重装模式(产生不可屏蔽中断)
AUXR(B7):
配置1T或12T
INT_CLKO(B0) :
是否输出时钟
TMOD(B0~3) :
选择定时器模式
TL0,TH0
IE(B1):
是否开启中断
TCON(B4,B5) :
是否启动定时器、
定时器中断标志位
定时器1
3
模式0 :
16位自动重装模式
模式1:
16位不可重装模式
模式2 :
8位自动重装模式
AUXR(B6):
配置1T或12T
INT_CLKO(B1) :
是否输出时钟
TMOD(B4~7) :
选择定时器模式
TL1,TH1
IE(B3):
是否开启中断
TCON(B6,B7) :
是否启动定时器、
定时器中断标志位
定时器2
12
模式0 :
16位自动重装模式
AUXR(B2,B3,B4):
配置1T或12T、
定时还是计数、
是否启动定时器
INT_CLKO(B2) :
是否输出时钟
IE2(B2) :
是否开启中断
T2L,T2H
使用定时器时,一般需要以下几个步骤:
①选择1T模式或者12T模式(配置AUXR寄存器);
②是否输出时钟(配置INT_CLKO寄存器);
③设置定时器模式(配置TMOD、AUXR寄存器);
④设置初值(TL0,TH0,TL1,TH1,T2L,T2H);
⑤是否开启中断(配置IE、IE2寄存器);
⑥开始运行定时器(配置TCON、AUXR寄存器)
⑦编写相应的中断服务函数(开启定时器中断的情况下)。
下面详细介绍上面步骤:
①选择1T模式或者12T模式(配置AUXR寄存器);
T0x12:
0:
定时器0以12T模式运行;1:
定时器0以1T模式运行
T1x12:
0:
定时器1以12T模式运行;1:
定时器1以1T模式运行
T2x12:
0:
定时器2以12T模式运行;1:
定时器2以1T模式运行
当对计时的精度较高,时间较短(微妙级)时,应选用1T模式;当对计时的精度要求较低,时间较长(毫秒级),最