C语言嵌入式系统编程修炼之道——键盘操作篇

 
C 语言嵌入式系统编程修炼之道——键盘操作篇
1. 处理功能键
功能键的问题在于,用户界面并非固定的,用户功能键的选择将使屏幕画面处于不同的显示状态下。例如,主画面如图 1
1 主画面
当用户在设置 XX 上按下 Enter 键之后,画面就切换到了设置 XX 的界面,如图 2
2 切换到设置 XX 画面
程序如何判断用户处于哪一画面,并在该画面的程序状态下调用对应的功能键处理函数,而且保证良好的结构,是一个值得思考的问题。
让我们来看看 WIN32 编程中用到的“窗口”概念,当消息( message )被发送给不同窗口的时候,该窗口的消息处理函数(是一个 callback 函数)最终被调用,而在该窗口的消息处理函数中,又根据消息的类型调用了该窗口中的对应处理函数 。通过这种方式, WIN32 有效的组织了不同的窗口,并处理不同窗口情况下的消息。
我们从中学习到的就是:
1 )将不同的画面类比为 WIN32 中不同的窗口,将窗口中的各种元素(菜单、按钮等)包含在窗口之中;
2 给各个画面提供一个功能键“消息”处理函数,该函数接收按键信息为参数;
3 )在各画面的功能键“消息”处理函数中,判断按键类型和当前焦点元素,并调用对应元素的按键处理函数。
/* 将窗口元素、消息处理函数封装在窗口中 */
struct windows
{
  BYTE currentFocus;
  ELEMENT element[ELEMENT_NUM];
  void  (*messageFun) (BYTE keyValue);
  …
};
/* 消息处理函数 */
void messageFunction(BYTE keyValue)
{
  BYTE i = 0;
  /* 获得焦点元素 */
  while ( (element [i].ID!= currentFocus)&& (i < ELEMENT_NUM) )
   {
   i++;
   }
  /* “消息映射” */
   if(i < ELEMENT_NUM)
   {
   switch(keyValue)
     {
     case OK:
     element[i].OnOk();
     break;
     …
     }
   }
}
在窗口的消息处理函数中调用相应元素按键函数的过程类似于“消息映射”,这是我们从 WIN32 编程中学习到的。编程到了一个境界,很多东西都是相通的了。其它地方的思想可以拿过来为我所用,是为编程中的“拿来主义”。
在这个例子中,如果我们还想玩得更大一点,我们可以借鉴 MFC 中处理 MESSAGE_MAP 的方法,我们也可以学习 MFC 定义几个精妙的宏来实现“消息映射”。
2. 处理数字键
用户输入数字时是一位一位输入的,每一位的输入都对应着屏幕上的一个显示位置( x 坐标, y 坐标)。此外,程序还需要记录该位置输入的值,所以有效组织用户数字输入的最佳方式是定义一个结构体,将坐标和数值捆绑在一起:
/* 用户数字输入结构体 */
typedef struct tagInputNum
  {
    BYTE byNum;  /* 接收用户输入赋值 */
       BYTE xPos;     /* 数字输入在屏幕上的显示位置 x 坐标 */
       BYTE yPos;     /* 数字输入在屏幕上的显示位置 y 坐标 */
   }InputNum,  *LPInputNum;
那么接收用户输入就可以定义一个结构体数组,用数组中的各位组成一个完整的数字:
InputNum inputElement[NUM_LENGTH]; /* 接收用户数字输入的数组 */
/* 数字按键处理函数 */
extern void onNumKey(BYTE num)
{
      if(num==0|| num==1)  /* 只接收二进制输入 */
      {
/* 在屏幕上显示用户输入 */
DrawText(inputElement[currentElementInputPlace].xPos, inputElement[currentElementInputPlace].yPos, "%1d", num);
     /* 将输入赋值给数组元素 */
      inputElement[currentElementInputPlace].byNum = num;
     /* 焦点及光标右移 */
      moveToRight();
      }
}
将数字每一位输入的坐标和输入值捆绑后,在数字键处理函数中就可以较有结构的组织程序,使程序显得很紧凑。
3. 整理用户输入
     继续第 2 节的例子,在第 2 节的 onNumKey 函数中,只是获取了数字的每一位,因而我们需要将其转化为有效数据,譬如要转化为有效的 XXX 数据,其方法是:
/* 2 进制数据位转化为有效数据: XXX */
void convertToXXX()
{
    BYTE i;
    XXX = 0;
    for (i = 0; i < NUM_LENGTH; i++)
     {
     XXX += inputElement[i].byNum*power(2, NUM_LENGTH - i - 1);
     }        
}
反之,我们也可能需要在屏幕上显示那些有效的数据位,因为我们也需要能够反向转化:
/* 从有效数据转化为 2 进制数据位: XXX */
void convertFromXXX()
{
    BYTE i;
    XXX = 0;
    for (i = 0; i < NUM_LENGTH; i++)
     {
     inputElement[i].byNum = XXX / power(2, NUM_LENGTH - i - 1) % 2;
     }        
}
当然在上面的例子中,因为数据是 2 进制的,用 power 函数不是很好的选择,直接用“ << >> ”移位操作效率更高,我们仅是为了说明问题的方便。试想,如果用户输入是十进制的, power 函数或许是唯一的选择了。
本篇给出了键盘操作所涉及的各个方面:功能键处理、数字键处理及用户输入整理,基本上提供了一个全套的按键处理方案。对于功能键处理方法,将 LCD 屏幕与 Windows 窗口进行类比,提出了较新颖地解决屏幕、键盘繁杂交互问题的方案。
计算机学的许多知识都具有相通性,因而,不断追赶时髦技术而忽略基本功的做法是徒劳无意的。我们最多需要“精通”三种语言(精通,一个在如今的求职简历里泛滥成灾的词语),最佳拍档是汇编、 C C++ (或 JAVA ),很显然,如果你“精通”了这三种语言,其它语言你应该是可以很快“熟悉”的,否则你就没有“精通”它们。
 
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
uchar key_Value=0xff; /*存放键值*/ uchar count=8;//设LED上显示的位数,一开始为8位 uchar temp=1;//控制按键没按一次只执行一次的变量 uchar timecount=0;//计时的辅助变量 uchar cancel=0;//取消按钮是否生效 uchar setTime=0;//是否开始计时的变量 uchar getdate=0;//日历 uchar time09[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; /*LED灯0~9译码*/ uchar ch0[8]={0x3f,0x6f,0x40,0x3f,0x4f,0x40,0x5b,0x3f}; /*09-03-20*/ uchar ch[8]={0x3f,0x6f,0x40,0x3f,0x4f,0x40,0x5b,0x3f}; /*09-03-20*/ uchar date[8]={0x3f,0x6f,0x40,0x3f,0x4f,0x40,0x5b,0x3f}; /*09-03-20*/ uchar tempdate[8]={0x3f,0x6f,0x40,0x3f,0x4f,0x40,0x5b,0x3f}; /*09-03-20*/ void main(void) { initUart(); //初始化串口 TMOD=0x10; //设置定时器1为工作方式1 TH1=-10000>>8;TL1=-10000 % 256;//定时器1每10000计数脉冲发生1次中断,12MHz晶振,定时时间10000us TCON=0x40; //内部脉冲计数 IE=0x88; //打开定时器中断 key_Value=0xff; while(1) { for(counter=0; counter<8; counter++) { ledConPort=counter; dataPort= ch[counter]; /*点亮选中的LED灯*/ time(5); /*延时5毫秒,感觉不出扫描显示*/ if(setTime==1&&getdate==0){ timecount++; if(timecount==200){ timecount=0; timechange(); } } } } } /********** 初始化串口波特率 ************/ void initUart(void)//初始化串口波特率,使用定时器2 { //Setup the serial port for 9600 baud at 11.0592MHz SCON = 0x50; //串口工作在方式1 RCAP2H=(65536-(3456/96))>>8; RCAP2L=(65536-(3456/96))%256; T2CON=0x34; TI = 1; //置位TI } /******* 定时器/计数器1中断服务程序 ***/ void timer1int(void) interrupt 3 { EA=0;//关总中断 TR1=0;//停止计数 TH1=-10000>>8;TL1=-10000 % 256;//定时器1每10000计数脉冲发生1次中断,12MHz晶振,定时时间10000us TR1=1;//启动计数 key_Value=keyscan(); if(key_Value==0xff)temp=1; if(temp==1){ set_keyvaule(); if(key_Value!=0xff)temp=0; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值