这段时间一直在折腾 LCD和Keyboard 的事情。这两部分作为一个使用广泛的 UI,不得不引起重视。
[1]硬件实现平台为:
DSP:TMS320F2812-2-B
LCD:OCMJ4*8C(VCC=3.3V)
Keyboard:4*4矩阵按键键盘
[2]软件开发环境及仿真器:
CCS 3.x
ICETECK-5100USB仿真器
[3]关键代码:
第一,main中的InitSysCtrl()、中断处理因为或为广泛所知、或在这里我没有用到,在此不做详细描述。
第二,端口的初始化(InitGpio)当然得说明:
EALLOW;
//设置F口(link to LCD)低8位为数字I/O端口,
//数据传输方向:输出
GpioMuxRegs.GPFMUX.all=0x0000;
GpioMuxRegs.GPFDIR.all=0x00FF;
//设置B口低8位(link to Keyboard)位数字I/O端口,
//数据传输方向:低4(L)--输入,高4(H)--输出(1)
GpioMuxRegs.GPBMUX.all=0x0000;
GpioMuxRegs.GPBDIR.all=0x00f0;
GpioDataRegs.GPBDAT.all=0x00f0;
EDIS;
第三,LCD的初始化Init_LCD代码(附加LCD个控制端口定义):
//macro define as follow:
#define RST GpioDataRegs.GPFDAT.bit.GPIOF0
//reset signal
#define PSB GpioDataRegs.GPFDAT.bit.GPIOF1
//serial or parall mode
#define SCLK GpioDataRegs.GPFDAT.bit.GPIOF2
//write or data
#define STD GpioDataRegs.GPFDAT.bit.GPIOF3
//analog clock
#define CS GpioDataRegs.GPFDAT.bit.GPIOF4
//command or data
#define SCLKr GpioDataRegs.GPFSET.bit.GPIOF2//串行时钟输入
#define SCLKd GpioDataRegs.GPFCLEAR.bit.GPIOF2 //串行时钟输入
/*------------------LCD初始化-----------------*/
void Init_LCD()
{
RST=0;
Delay1(50);
RST=1; /*低电平有效*/
//#define rst GpioDataRegs.GPADAT.bit.GPIOA0//Reset Signal 低电平有效
PSB=0; /*串行模式*/
//#define psb GpioDataRegs.GPADAT.bit.GPIOA1//H=并口; L=串口;
wr_lcd(COMMAND,0x30); /*30---基本指令动作*/
//DB4==DL==1:8-BIT 控制接口
//DB2==RE==0==基本指令集动作
Delay1(200);
wr_lcd(COMMAND,0x01); /*清屏,地址指针指向00H*/
//把 DDRAM 地址计数器 调整为‘00H’,光标回到原点,该功能不影响显示DDRAM
Delay1(200);
wr_lcd(COMMAND,0x06); /*光标的移动方向*/
//DB1==I/D==1,光标右移,AC地址计数器 自动加1
//DB2==SH==1,且DDRAM为写状态:整体显示移动,方向由I/D决定
Delay1(200);
wr_lcd(COMMAND,0x0c); /*开显示,关游标*/
//DB0==B==0:光标位置不反白闪烁
//DB1==C==0:光标显示为OFF
//DB2==D==1:整体显示ON
Delay1(200);
chn_disp(Tab4);
//clrram();
//lat_disp(0x00,0x00);
}
第四,这里是写LCD的关键代码wr_lcd(...)(包含了LCD的模拟时钟的实现、数据命令的串行传送):
/*----------------wr_lcd(...)---------------------*/
void wr_lcd(uchar dat_command,uchar content)
{
uchar a,i,j;
a=content;
//KickDog();
CS=1; //datas
STD=0; //write
//SCLK=0;//serials model
SCLKd=1;
//std=1;//read
Delay1(5);
//Synchronizing bit Strings
STD=1; //Synchronizing bit String
for(i=0;i<5;i++)
{
SCLKr=1;
Delay1(5);
SCLKd=1;
Delay1(5);
}
//write or read
STD=0; //write
SCLKr=1;
Delay1(5);
SCLKd=1;
Delay1(5);
//command or data
if(dat_command==1)
//std=1; //data
STD=1; //datas
else
//std=0; //command
STD=0; //command
SCLKr=1;
Delay1(5);
SCLKd=1;
Delay1(5);
//bit-filler
STD=0;
SCLKr=1;
Delay1(5);
SCLKd=1;
Delay1(5);
//start to translate The Two Bytes
for(j=0;j<2;j++)
{
for(i=0;i<4;i++)
{
if( a & 0x0080 )
STD=1; //high-logic(in a byte of data)
else
STD=0; //low-logic(in a byte of data)
SCLKr=1;
Delay1(5);
SCLKd=1;
Delay1(5);
a=a<<1;
}
//bit-fillers
for(i=0;i<4;i++)
{
STD=0; //four low-logic bits to fill a byte fully
SCLKr=1;
Delay1(5);
SCLKd=1;
Delay1(5);
}
}
CS=0; //command
}
第五,键盘扫描程序Keyprocess(),在这里我遇到一些问题,因此困扰了好一段时间。后面会详细说明,先把键盘扫描程序说明如下:
uchar KeyProcess()
{ // int nRes;
if(L1 == 1 || L2 == 1 || L3 == 1 || L4 == 1)
//延时一点时间,消除抖动
delay1(10) ;//延时一点时间
//读取各个管脚的状态
//是否有键被按下
if(L1 == 1 || L2 == 1 || L3 == 1|| L4 == 1)
{
//H1输出低电平
H1=1;
H2=0;
H3=0;
H4=0;
delay1(10);
if (L1 == 1) nRes = 0x30;
if (L2 == 1) nRes = 0x31;
if (L3 == 1) nRes = 0x32;
if (L4 == 1) nRes = 0x33;
//H2输出低电平
H2=1;
H1=0;
H3=0;
H4=0;
delay1(10);
if (L1 == 1) nRes = 0x34;
if (L2 == 1) nRes = 0x35;
if (L3 == 1) nRes = 0x36;
if (L4 == 1) nRes = 0x37;
//H3输出低电平
H1=0;
H2=0;
H3=1;
H4=0;
delay1(10);
if (L1 == 1) nRes = 0x38;
if (L2 == 1) nRes = 0x39;
if (L3 == 1) nRes = 0x41;
if (L4 == 1) nRes = 0x42;
//H4输出低电平
H1=0;
H2=0;
H4=1;
H3=0;
delay1(10);
if (L1 == 1) nRes = 0x43;
if (L2 == 1) nRes = 0x44;
if (L3 == 1) nRes = 0x45;
if (L4 == 1) nRes = 0x46;
//恢复以前值。
H1=1;
H2=1;
H3=1;
H4=1;
//for(;;)
//{
//if(L1 == 0&& L2 == 0 && L3 == 0 && L4 == 0)
//{
delay1(10);
//if(L1 == 0&& L2 == 0&& L3 == 0&& L4 == 0)
// {
//等待松开按键
// break;
// }
//}
//}
}
return nRes; //返回键值
}
网友可以发现,以上矩阵键盘扫描程序的实现相当繁琐,但是相对于键盘扫描的原理体现的较好,呵呵。当然有好的方法来改进,这里不列出,有机会了专门写个东西出来。
第六,既然我没有用到中断,那么对于键盘扫描必然得放到main中的死循环中,网友可能说这不是一个好的方法。是的,我承认最后把它放入中断中去处理,但是这里我主要是测试 LCD+Keyboard ,为突出重点、简明关系,采取了这样的做法。
下面贴出代码:
while(1)
{
w=KeyProcess(); //uchar w,作为一个全局变量,保存按键值 + 送LCD显示值
chn_disp(Tab4);
Data_Add();
Chn_AddDisp(Tab2);
KickDog();
}
第七,最后把出现的问题说明下:
[1]在接上板子,下载好程序,进行测试时发现在没有矩阵键盘按键发生时,程序可以正确的按照设置好的初始值进行循环,LCD显示如预期(保存有图片)。
[2]当我试图按下一个按键时,程序进入了某个不确定的死循环,LCD不在进行数据更新。
[3]我进行了几次挂起程序运行,发现显示的汇编代码并不是停在一条语句处。那么程序必然在一个存在几条语句循环的地方进行着死循环。
[4]我不能确定进入了哪个循环中,汇编代码也提供不了多少信息。
[5]可以肯定的是,在没有按键发生时,程序可以进行正常的运行;在有按键发生时,程序马上进入了死循环,同时LCD不能进行数据刷新。
[6]示波器观察:在没有按键发生时,可以在LCD的 SCLK端 观察到模拟时钟信号;当按下任一按键的瞬间,示波器上的模拟时钟信号马上失去。
[7]利用 CCS 的 'Add to Watch Window ' 功能,设置了相应断点后,观察了 w / nRes,两个变量值的变化情况。
在程序初始化运行后, w / nRes 正是所设定的值,当按键发生时,由 'Watch Window'观察到:nRes 的值得到了‘及时地’更新,而 w 的值确还是初始值。
结论是:在没有按键发生时,程序可以正常运行;当有按键发生时,程序没有跳出 Keyprocess(),这样说的理由有两个:
(1)w 的值没有得到更新,所以 Keyprocess() 没有返回 已经更新的、uchar 类型的 nRes 给 w;
(2)示波器在按键发生前后的波形显示,正是程序在 Keyprocess() 中没有退出,导致后面的 wr_lcd() 不 能进入去继续为LCD提供时钟信号、提供更新数据。
[8]经过以上多方面验证,可以把问题锁定在 Keyprocess()中的以下代码:
//恢复以前值。
H1=1;
H2=1;
H3=1;
H4=1;
for(;;)
{
if(L1 == 0&& L2 == 0 && L3 == 0 && L4 == 0)
{
delay1(10);
if(L1 == 0&& L2 == 0&& L3 == 0&& L4 == 0)
{
//等待松开按键
break;
}
}
}
}
return nRes; //返回键值
[9]由以上代码可知,程序进入死循环的条件为:
L1 & 2 & 3 & 4 != 0
那么,出现的问题应该是在矩阵键盘上了(即硬件问题)。在按键按下并松开后,相应列电平并没有为0。
[10]找到问题后,为获得直观结果,对代码进行相应改动如下:
//恢复以前值。
H1=1;
H2=1;
H3=1;
H4=1;
//for(;;)
//{
//if(L1 == 0&& L2 == 0 && L3 == 0 && L4 == 0)
//{
delay1(10);
//if(L1 == 0&& L2 == 0&& L3 == 0&& L4 == 0)
// {
//等待松开按键
// break;
// }
//}
//}
}
return nRes; //返回键值
编译组建后,发现程序可以实时的将按键值显示在 LCD上,并观察 w 、nRes,二者的值达到了同步。
(有图片保存结果)
[11]以下将进行两方面的测试,找出硬件问题的所在:
(1)硬件测试:主要测试相应管脚的电平变化情况;
(2)软件测试:通过观察 L1/2/3/4 个宏的值,来确定问题所在。
…………