2.11 测试程序的编写和调试
2.11.4 按键驱动的编写
考虑到以后程序的方便性,这里使用定时器中断方式来扫描按键。定时器每隔5ms中断一次,即每5ms
扫描一次键盘。
key.c
1)先初始化好一个定时器,此处选择定时器0作为按键扫描的定时器,而定时器1留作串口波特率发生器。
初始化定时器就是设置定时器的工作模式,启动定时器等。
//定时器0初始化,用来做按键扫描
void InitTimer0(void)
{
TMOD&=0xF0; //定时器低4位是控制定时器0的,先置0
TMOD|=0x01; //然后将最低位置1,最终选择16位定时器模式
ET0=1; //允许定时器0中断
TR0=1; //启动定时器0
}
2)再初始化键盘
volatile uint8 idata KeyCurrent,KeyOld,KeyNoChangedTime;
volatile uint8 idata KeyPress;
volatile uint8 idata KeyDown,KeyUp,KeyLast;
volatile uint8 KeyCanChange;
//函数功能:键盘初始化
void InitKeyboard(void)
{
KeyIO=0xFF; //键盘对应的口设置为输入状态
KeyPress=0; //无按键按下
KeyNoChangedTime=0;
KeyOld=0;
KeyCurrent=0;
KeyLast=0;
KeyDown=0;
KeyUp=0;
InitTimer0(); //初始化定时器
KeyCanChange=1; //允许键值改变
}
KeyCurrent、KeyOld、KeyLast和KeyNoChangedTime是扫描按键使用的变量,应用程序不直接使用它们。
KeyPress表示当前被按住不放的键;
KeyDown表示新按下的键;
KeyUp表示心松开的键;
KeyCanChange是应用程序用来控制是否允许新的扫描。
当某个按键被按下时,KeyPress对应位被置1,并且KeyDown对应位也置1;当按键松开后,KeyPress对应
位为0,KeyUp对应位被置1。
3)定时器0中断处理
//函数功能:定时器0中断处理。
//22.1184M晶体约5ms中断一次。
void Timer0Isr(void) interrupt 1
{
//定时器0重装,定时间隔为5ms,加15是为了修正重装所花费时间
//这个值可以通过软件仿真来确定,在这里设置断点,调整使两次运行
//时间差刚好为5ms即可。
TH0=(65536-Fclk/1000/12*5+15)/256;
TL0=(65536-Fclk/1000/12*5+15)%256;
if(!KeyCanChange)return; //如果正在处理按键,则不再扫描键盘
//开始键盘扫描
//保存按键状态到当前按键情况
//KeyCurrent总共有8个bit
//当某个开关按下时,对应的bit为1
KeyCurrent=GetKeyValue(); //读取键值,GetKeyValue()其实是个宏,不是函数,
//这里故意写成函数的样子,美观。它的定义在
//key.h文件中
if(KeyCurrent!=KeyOld) //如果两次值不等,说明按键情况发生了改变
{
KeyNoChangedTime=0; //键盘按下时间为0
KeyOld=KeyCurrent; //保存当前按键情况
return; //返回
}
else
{
KeyNoChangedTime++; //按下时间累计
if(KeyNoChangedTime>=1) //如果按下时间足够
{
KeyNoChangedTime=1;
KeyPress=KeyOld; //保存按键
KeyDown|=(~KeyLast)&(KeyPress); //求出新按下的键
KeyUp|=KeyLast&(~KeyPress); //求出新释放的键
KeyLast=KeyPress; //保存当前按键情况
}
}
}
2.11.5 串口驱动的编写
1. 串口驱动的重要性:
调试时,在对应位置打印出信息,可以知道程序走过哪些步骤;
2. usart.c
在使用串口之前,必须对串口进行初始化。
//函数功能:串口初始化
void InitUART(void)
{
EA=0; //暂时关闭中断
TMOD&=0x0F; //定时器1模式控制在高4位
TMOD|=0x20; //定时器1工作在模式2,自动重装模式
SCON=0x50; //串口工作在模式1
TH1=256-Fclk/(BitRate*12*16); //计算定时器重装值
TL1=256-Fclk/(BitRate*12*16);
PCON|=0x80; //串口波特率加倍
ES=1; //串行中断允许
TR1=1; //启动定时器1
REN=1; //允许接收
EA=1; //允许中断
}
串口中断处理函数
volatile uint8 Sending;
//函数功能:串口中断处理。
void UartISR(void) interrupt 4
{
if(RI) //收到数据
{
RI=0; //清中断请求,因为这里只发送,不接受,故此处只要清除中断标志即可
}
else //发送完一字节数据
{
TI=0;
Sending=0; //清正在发送标志
}
}
发送单个字符函数
//函数功能:往串口发送一字节数据。
//入口参数:d: 要发送的字节数据。
void UartPutChar(uint8 d)
{
SBUF=d; //将数据写入到串口缓冲
Sending=1; //设置发送标志
while(Sending); //等待发送完毕
}
发送一个字符串函数
//函数功能:发送一个字符串。
//入口参数:pd:要发送的字符串指针。
void Prints(uint8 * pd)
{
while((*pd)!='\0') //发送字符串,直到遇到0才结束
{
UartPutChar(*pd); //发送一个字符
pd++; //移动到下一个字符
}
}
以HEX格式发送一个整数
code uint8 HexTable[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
//函数功能:将短整数按十六进制发送。
//入口参数:待发送的整数。
void PrintShortIntHex(uint16 x)
{
uint8 i;
uint8 display_buffer[7];
display_buffer[6]=0;
display_buffer[0]='0';
display_buffer[1]='x';
for(i=5;i>=2;i--) //将整数转换为4个字节的HEX值
{
display_buffer[i]=HexTable[(x&0xf)];
x>>=4;
}
Prints(display_buffer);
}
2.11.6 PDIUSBD12读写函数和读ID的实现
1. 读D12的ID号的目的
知道D12是否正常工作
2. PDIUSBD12的读写时序图
分析:CS_N是片选信号。上图所示,只有片选信号拉低时,下面的操作才有意义。
A0是地址线,用于选择是命令还是数据。A0为1时,表示操作的是命令;A0为0时,表示操作的是数据。
WR_N是写信号,表示WR_N的上升沿将数据写入芯片中。数据必须在上升沿的前后稳定地保持一段时
间(即图中的tWDSU和tWDH)才能可靠写入。
RD_N是读选通信号,在读数据时,先将RD_N置低,等待tRLDD时间后,数据将出现在数据总线DATA
[7:0]上,这时可以读取数据。读取完后,将RD_N拉高,数据在总线上的数据将在tRHDZ时间后消失。
3. PDIUSBD12.c
根据上面的分析,可以得出:
写命令的操作过程为:先将A0置高(即设置为命令状态),再讲WR_N置低,把需要发送的命令放到数据总线上,
再讲WR_N置高。这样将产生一个上升沿,从而把数据写入了D12中。写完后,须将总线设置为输入状态,以避免
总线冲突。
写数据的操作过程为:只要将上面的A0设置为低即可(设置为数据状态)。
//函数功能:D12写命令。
//入口参数:Command:一字节命令。
void D12WriteCommand(uint8 Command)
{
D12SetCommandAddr(); //设置为命令地址
D12ClrWr(); //WR置低
D12SetPortOut(); //将数据口设置为输出状态(注意这里为空宏,移植时可能有用)
D12SetData(Command); //输出命令到数据口上
D12SetWr(); //WR置高
D12SetPortIn(); //将数据口设置为输入状态,以备后面输入使用
}
读一个字节的操作过程为:先将A0置低(即设置为数据状态),再将RD_N置低(表示读数据),读取P0口上的数据
并保存,最后将RD_N置高,结束读过程。最后函数返回读取到的数据。
//函数功能:读一字节D12数据。
//返 回:读回的一字节。
uint8 D12ReadByte(void)
{
uint8 temp;
D12SetDataAddr(); //设置为数据地址
D12ClrRd(); //RD置低
temp=D12GetData(); //读回数据
D12SetRd(); //RD置高
return temp; //返回读到数据
}
读取2字节的ID号
//函数功能:读D12的ID。
//返 回:D12的ID。
uint16 D12ReadID(void)
{
uint16 id;
D12WriteCommand(Read_ID); //写读ID命令
id=D12ReadByte(); //读回ID号低字节
id|=((uint16)D12ReadByte())<<8; //读回ID号高字节
return id;
}
4. 修改main函数
在main.c中增加读取并显示ID号的代码,检验芯片是否焊接正确。
main.c
/********************************************************************
函数功能:主函数。
入口参数:无。
返 回:无。
备 注:无。
********************************************************************/
void main(void) //主函数
{
uint8 i;
uint16 id;
EA=1; //打开中断
InitKeyboard(); //初始化按键
InitUART(); //初始化串口
for(i=0;i<16;i++) //显示信息
{
Prints(HeadTable[i]);
}
id=D12ReadID();
Prints("Your D12 chip\'s ID is: ");
PrintShortIntHex(id);
if(id==0x1012)
{
Prints(". ID is correct! Congratulations!\r\n\r\n");
}
else
{
Prints(". ID is incorrect! What a pity!\r\n\r\n");
}
while(1) //死循环
{
LEDs=~KeyPress; //将按键结果取反后控制LED
if(KeyDown) //有键按下
{ //处理按下的键
if(KeyDown&KEY1)
{
Prints("KEY1 down\r\n");
KeyDown&=~KEY1;
}
if(KeyDown&KEY2)
{
Prints("KEY2 down\r\n");
KeyDown&=~KEY2;
}
if(KeyDown&KEY3)
{
Prints("KEY3 down\r\n");
KeyDown&=~KEY3;
}
if(KeyDown&KEY4)
{
Prints("KEY4 down\r\n");
KeyDown&=~KEY4;
}
if(KeyDown&KEY5)
{
Prints("KEY5 down\r\n");
KeyDown&=~KEY5;
}
if(KeyDown&KEY6)
{
Prints("KEY6 down\r\n");
KeyDown&=~KEY6;
}
if(KeyDown&KEY7)
{
Prints("KEY7 down\r\n");
KeyDown&=~KEY7;
}
if(KeyDown&KEY8)
{
Prints("KEY8 down\r\n");
KeyDown&=~KEY8;
}
}
if(KeyUp)//有键释放
{//处理释放的键
if(KeyUp&KEY1)
{
Prints("KEY1 up\r\n");
KeyUp&=~KEY1;
}
if(KeyUp&KEY2)
{
Prints("KEY2 up\r\n");
KeyUp&=~KEY2;
}
if(KeyUp&KEY3)
{
Prints("KEY3 up\r\n");
KeyUp&=~KEY3;
}
if(KeyUp&KEY4)
{
Prints("KEY4 up\r\n");
KeyUp&=~KEY4;
}
if(KeyUp&KEY5)
{
Prints("KEY5 up\r\n");
KeyUp&=~KEY5;
}
if(KeyUp&KEY6)
{
Prints("KEY6 up\r\n");
KeyUp&=~KEY6;
}
if(KeyUp&KEY7)
{
Prints("KEY7 up\r\n");
KeyUp&=~KEY7;
}
if(KeyUp&KEY8)
{
Prints("KEY8 up\r\n");
KeyUp&=~KEY8;
}
}
}
}
如果一切正确,在串口调试助手上(波特率9600 数据位8 停止位1 无硬件流控制)将会显示如下所示信息。
其中日期和时间可能不一样,这取决于当前的编译时间。
2.12 本章小结
本章内容的必要性