2.11 测试程序的编写和调试
2.11.4 按键驱动的编写
考虑到以后程序的方便性,这里使用定时器中断方式来扫描按键。定时器每隔5ms中断一次,即每5ms
扫描一次键盘。
key.c
1)先初始化好一个定时器,此处选择定时器0作为按键扫描的定时器,而定时器1留作串口波特率发生器。
初始化定时器就是设置定时器的工作模式,启动定时器等。
//定时器0初始化,用来做按键扫描void InitTimer0(void){TMOD&=0xF0; //定时器低4位是控制定时器0的,先置0TMOD|=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为1KeyCurrent=GetKeyValue(); //读取键值,GetKeyValue()其实是个宏,不是函数,//这里故意写成函数的样子,美观。它的定义在//key.h文件中if(KeyCurrent!=KeyOld) //如果两次值不等,说明按键情况发生了改变{KeyNoChangedTime=0; //键盘按下时间为0KeyOld=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; //串口工作在模式1TH1=256-Fclk/(BitRate*12*16); //计算定时器重装值TL1=256-Fclk/(BitRate*12*16);PCON|=0x80; //串口波特率加倍ES=1; //串行中断允许TR1=1; //启动定时器1REN=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; //将按键结果取反后控制LEDif(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 本章小结
本章内容的必要性
397

被折叠的 条评论
为什么被折叠?



