单片机 GUI 设计(三)- 按键控件
单片机 GUI 设计(三)- 按键控件

单片机 GUI 设计(三)- 按键控件

基于 GD32F303ZET6 苹果派开发板

按键类型

按键类型大致可以分为两类,电平型和边沿型。电平型类似于键盘输入,按键按下时持续输出事件,边沿型则只会在按键按下或抬起一瞬间输出事件。

电平型按键实现

想要实现电平型按键十分简单,只需要检测该区域有无触点按下即可,如下所示。这段程序在 (300, 190),即屏幕中央创建了一个长 200,宽 100 的触摸按键。检测到按键被按下时,将按键所在区域填充为蓝色,并打印 “Button press”,用于模拟事件输出。如果按键未被按下,则将按键所在区域填充为绿色。

这里使用了简单的蓝色和绿色区分按键按下和按键抬起两种状态,后边我们还将介绍如何在屏幕上显示图片,以及使用图片装饰按键。

int main(void)
{
  //按键区域
  const u16 x0 = 300;              //原点横坐标
  const u16 y0 = 190;              //原点纵坐标
  const u16 width = 200;           //按键宽度
  const u16 height = 100;          //按键高度
  const u16 x1 = x0 + width - 1;   //终点横坐标
  const u16 y1 = y0 + height - 1;  //终点纵坐标
  const u16 pressColor = BLUE;     //按下时显示的颜色
  const u16 releaseColor = GREEN;  //抬起时显示的颜色
  u16 x, y;                        //临时变量
  
  //初始化
  InitHardware();   //初始化硬件相关
  InitSoftware();   //初始化软件相关

  //设置 LCD 初始状态
  LCDDisplayDir(1); //横屏
  LCDClear(WHITE);  //白屏
  

  while(1)
  {
    //LED 闪烁
    LEDFlicker(5);
    
    //触屏扫描
    if(ScanTouch(&x, &y))
    {
      //触点落在了目标区域
      if((x >= x0) && (x <= x1) && (y >= y0) && (y <= y1))
      {
        LCDFill(x0, y0, x1, y1, pressColor);
        printf("Button press\r\n");
      }
      else
      {
        LCDFill(x0, y0, x1, y1, releaseColor);
      }
    }
    
    //未检测到按下
    else
    {
      LCDFill(x0, y0, x1, y1, releaseColor);
    }
    
    //延时 100 ms
    DelayNms(100);
  }
}

边沿型按键实现

将电平型按键做一个简单的封装,即可实现边沿型按键,如下所示。只需要新增一个变量保存上一次的状态,根据上一次的状态和当前状态即可判断出是否捕捉到了按键按下或抬起事件。边沿型按键可以输出按键按下事件,也可以输出按键抬起事件,为简单起见,本系列默认只输出按键按下事件。

int main(void)
{
  //按键区域
  const u16 x0 = 300;              //原点横坐标
  const u16 y0 = 190;              //原点纵坐标
  const u16 width = 200;           //按键宽度
  const u16 height = 100;          //按键高度
  const u16 x1 = x0 + width - 1;   //终点横坐标
  const u16 y1 = y0 + height - 1;  //终点纵坐标
  const u16 pressColor = BLUE;     //按下时显示的颜色
  const u16 releaseColor = GREEN;  //抬起时显示的颜色
  u8  lastState, currentState;     //按键上一个状态和当前状态,0-抬起,1-按下
  u16 x, y;                        //临时变量
  
  //初始化
  InitHardware();   //初始化硬件相关
  InitSoftware();   //初始化软件相关

  //设置 LCD 初始状态
  LCDDisplayDir(1); //横屏
  LCDClear(WHITE);  //白屏
  
  //按键初始化,默认处于抬起状态
  lastState = 0;
  currentState = 0;
  LCDFill(x0, y0, x1, y1, releaseColor);

  while(1)
  {
    //LED 闪烁
    LEDFlicker(50);
    
    //触屏扫描
    if(ScanTouch(&x, &y))
    {
      //触点落在了目标区域
      if((x >= x0) && (x <= x1) && (y >= y0) && (y <= y1))
      {
        currentState = 1;
      }
      else
      {
        currentState = 0;
      }
    }
    
    //未检测到按下
    else
    {
      currentState = 0;
    }
    
    //上一个状态是抬起而当前状态为按下,表示捕捉到了一个按下事件
    if((0 == lastState) && (1 == currentState))
    {
      LCDFill(x0, y0, x1, y1, pressColor);
      printf("Button press\r\n");
    }
    
    //上一个状态是按下而当前状态为抬起,表示捕捉到了一个抬起事件
    else if((1 == lastState) && (0 == currentState))
    {
      LCDFill(x0, y0, x1, y1, releaseColor);
    }
    
    //保存当前状态
    lastState = currentState;
    
    //延时 10 ms
    DelayNms(10);
  }
}

封装代码

将边沿型按键代码做个简单的封装,即可实现一个简单的按键控件驱动,该驱动的头文件如下所示。驱动头文件只包含两个 API 函数,ButtonCreate 函数用于创建按键,ButtonScan 函数则用于按键扫描,检测到按键按下则返回 1,否则返回 0。驱动支持在按键中央显示按键名字。

//按键控件
typedef struct
{
  u16   x, y, width, height;  //原点、宽度、高度
  char* text;                 //按键名字
  u8    textSize;             //字体大小,可以是 12、16、24
  u16   textColor;            //字体颜色
  u8    lastState;            //按键上一个状态,0-按下,1-抬起
  u16   pressColor;           //按键按下时的颜色
  u16   releaseColor;         //按键抬起时显示的颜色
}StructButton;

void ButtonCreate(StructButton* widget); //创建按键
u8   ButtonScan(StructButton* widget);   //按键扫描

该驱动的源文件如下所示。ShowText 函数是一个内部函数,用于在按键中央显示按键名。ButtonCreate 函数用于创建按键,主要是设置了按键的初始状态。ButtonScan 则是按键扫描,使用的扫描方式与 “边沿型按键实现” 使用的方法一致。

#include "Button.h"
#include "LCD.h"
#include "Touch.h"

//显示按键名字
static void ShowText(StructButton* widget)
{
  u16 tx, ty, len, twidth, i;

  //名字为空
  if(NULL == widget->text)
  {
    return;
  }
  
  //统计字符串长度
  len = 0;
  while(0 != widget->text[len])
  {
    len++;
  }
  
  //计算字符串占像素点长度
  twidth = (widget->textSize / 2) * len;
  
  //计算字符串横坐标起点
  tx = widget->x + ((widget->width - twidth) / 2);
  
  //计算字符串纵坐标起点
  ty = widget->y + ((widget->height - widget->textSize) / 2);
  
  //显示字符串
  i = 0;
  while(0 != widget->text[i])
  {
    g_iLCDPointColor = widget->textColor;
    LCDShowChar(tx + (widget->textSize / 2) * i, ty, widget->text[i], widget->textSize, 1);
    i++;
  }
}

//创建按键
void ButtonCreate(StructButton* widget)
{
  u16 x0, y0, x1, y1;
  
  //计算按键起点和终点
  x0 = widget->x;
  y0 = widget->y;
  x1 = x0 + widget->width - 1;
  y1 = y0 + widget->height - 1;

  //设置按键初始状态
  widget->lastState = 0;
  LCDFill(x0, y0, x1, y1, widget->releaseColor);
  
  //刷新名字显示
  ShowText(widget);
}

//按键扫描,返回:1-检测到按键按下,0-未检测到按键按下
u8 ButtonScan(StructButton* widget)
{
  u16 x, y, x0, y0, x1, y1;
  u8 currentState;
  u8 ret;
  
  //默认返回 0
  ret = 0;
  
  //计算按键起点和终点
  x0 = widget->x;
  y0 = widget->y;
  x1 = x0 + widget->width - 1;
  y1 = y0 + widget->height - 1;
  
  //触屏扫描
  currentState = 0;
  if(ScanTouch(&x, &y))
  {
    //触点落在了目标区域
    if((x >= x0) && (x <= x1) && (y >= y0) && (y <= y1))
    {
      currentState = 1;
    }
    else
    {
      currentState = 0;
    }
  }
  
  //上一个状态是抬起而当前状态为按下,表示捕捉到了一个按下事件
  if((0 == widget->lastState) && (1 == currentState))
  {
    LCDFill(x0, y0, x1, y1, widget->pressColor);
    ret = 1;
  }
    
  //上一个状态是按下而当前状态为抬起,表示捕捉到了一个抬起事件
  else if((1 == widget->lastState) && (0 == currentState))
  {
    LCDFill(x0, y0, x1, y1, widget->releaseColor);
    ret = 0;
  }
  
  //保存当前状态
  widget->lastState = currentState;
  
  //刷新名字显示
  ShowText(widget);
  
  //返回测量结果
  return ret;
}

后续章节中将会介绍如何使用图片包装美化按键。

实验结果

实验结果如下所示。

源码

本章节中的源码请参考《单片机 GUI 设计(零)- 大纲