单片机 GUI 设计(一)- 基础知识
单片机 GUI 设计(一)- 基础知识

单片机 GUI 设计(一)- 基础知识

基于 GD32F303ZET6 苹果派开发板

简介

在学习 GUI 之前,首先要了解一些 GUI 的基本概念,例如像素点格式、分辨率、以及 LCD 接口函数、触摸屏接口函数的使用等等。

像素点格式

单片机中常用的像素点格式有 ARGB8888、RGB888 和 RGB565,此处不讨论 OLED 这种只有黑白两种颜色的情况。

ARGB8888,顾名思义,就是一个像素点占据 4 个 字节(4 x 8),A 表示透明度,常常用于颜色混合,0 表示完全透明,0xFF 表示完全不透明。R、G、B 分别是三原色红、绿和蓝三种分量。相比于其它像素点格式,显示效果最好,但也是最耗内存的,适用于显示透明效果。

相比于 ARGB8888,RGB888 少了 A,即透明度,这使得 RGB888 不适用于显示透明效果,但正是因为少了透明度信息,使得 RGB888 所消耗的内存大大降低,适用于显示背景图片和方块。

学习过单片机图片显示的同学可能会注意过,有些位图(BMP)是 24 位的,有些是 32 位的。24 位的位图如下所示。位深度为 24 表示一个像素点占据 24Bit,属于 RGB888。因为像素点格式是 RGB888,所以该图片不具备透明度信息,不能显示出透明效果,只适合做为背景图片,或者一个方块。

位深度为 24 的位图

32 位的位图如下所示,位深度为 32 表示该位图使用的像素点格式为 ARGB888,具有透明度信息。这种图片因为带有透明度信息,所以更适用于图标、小窗口等场景。

位深度为 32 的位图

单片机中使用 RGB888 会引入一个问题,RGB888 都是占据 3 个字节大小,而 C 语言中并没有 3 个字节的数据类型,想要表达 RGB888,只能用数组或结构体的方式,如果用一个 32 位的数据类型存储像素点,则会损失一个字节的存储空间。三个字节的设计给数据存储、传输、处理带来了极大的不便,所以单片机中大多使用 RGB565 像素点格式。

一个 RGB565 像素点位宽为 16 位,占据 2 个字节大小,像素点格式按照 [RRRRRGGGGGGBBBBB] 排列。相比于 RGB888,RGB565 只提取了 RGB888 红色分量的高 5 位,绿色分量的高 6 位和蓝色分量的高 5 位,将原来需要消耗 3 个字节的内存缩小至 2 个字节,极大方便的数据的存储和传输,此时我们只需要一个简单的 “unsigned short”即可表达一个像素点。因为 RGB565 删减了 R、G、B 三种颜色的低位信息,使得 RGB565 在颜色分辨率上不如 RGB888,在显示渐变色背景时效果很差,而且我们也可以发现,好多时候在 PC 端整理好的图片,放到单片机显示的话会有色差,这都是因为 RGB565 颜色精度缺失造成的。

RGB888 转 RGB565 如下所示。

unsigned short RGB888ToRGB565(unsigned char r, unsigned char g, unsigned char b)
{
  r = r >> 3;
  g = g >> 2;
  b = b >> 3;

  return ((r << 11) | (g << 5) | (b << 0));
}

RGB565 转 RGB888 如下所示。

unsigned int RGB565ToRGB888(u16 rgb565)
{
  unsigned char r, g, b;
  r = ((0xF800 & rgb565) >> 11) & 0xFF;
  g = ((0x07E0 & rgb565) >> 5 ) & 0xFF;
  b = ((0x001F & rgb565) >> 0 ) & 0xFF;
  return ((r << 16) | (g << 8) | (b << 0));
}

单片机里寸土寸金,极度缺乏内存,为了节省成本,单片机里使用的像素点格式大多为 RGB565,所以本系列默认使用的像素点格式也为 RGB565。

屏幕及其分辨率

单片机里使用的屏幕主要有两类,一类是自带显存的 MCU 屏,另一类是不带显存的 RGB 屏。MCU 屏自带显存,占用资源少,支持 8080、SPI 或 IIC 等通信方式。使用 MCU 屏的单片机不需要额外准备一块显存,使用时只需要将像素点数据写入屏幕,更新部分区域即可。RGB 屏不自带显存,系统中需要额外一块显存。通常使用到 RGB 屏的处理器都会外拓一个 SDRAM,用作显存。相对而言,MCU 屏控制简单,成本低廉,但速度相对比较慢;RGB 屏成本高,时序复杂,但可以实时动态刷新,显示效果好。GD32F303ZET6 苹果派开发板使用的是 MCU 屏,适合显示静态画面,不适合实时动态刷新显示场合。

分辨率不同于尺寸,是指屏幕长宽方向各有多少个像素点。一般我们说的屏幕分辨率 800×480,是指屏幕横向(宽)有 800 个像素点,纵向(高)有 480 个像素点。常用的屏幕分辨率有 320×240、480×320、800×480。苹果派支持 800×480 分辨率的屏幕,可以显示丰富的图案。

原点、坐标和屏幕方向

不论是单片机的 GUI 开发,还是 PC 端、移动端的 UI 开发,都是默认屏幕原点在左上方,坐标则以像素点为单位。以 800×480 分辨率的屏幕为例,屏幕左上方定点为原点,坐标为(0,0),横坐标的取值范围为 0 ~ 799,纵坐标取值范围为 0 ~ 479。

对于 MCU 屏而言,可以通过改变屏幕的扫描方向实现横竖屏切换。而 RGB 屏的扫描方向是固定的,不能修改,所以只能通过坐标转换的方式实现竖屏效果。本系列教程以横屏,即宽 800、高 480 为默认方向

LCD 驱动

点开 LCD 的驱动,查看 LCD 的 API 函数,可以看到驱动提供的所有 API 函数,如下所示。

void LCDWriteCMD(u16 cmd);                                                 //向LCD写命令
void LCDWriteData(u16 data);                                               //向LCD写数据
u16  LCDReadData(void);                                                    //从LCD读数据
void LCDWriteReg(u16 reg, u16 value);                                      //写寄存器
u16  LCDReadReg(u16 reg);                                                  //读寄存器
void LCDSendWriteGramCMD(void);                                            //发送开始写GRAM命令
void LCDWriteRAM(u16 rgb);                                                 //写GRAM
u16  LCDBGRToRGB(u16 bgr);                                                 //BGR转RGB
u16  LCDReadPoint(u16 x, u16 y);                                           //读点
void LCDDisplayOn(void);                                                   //开显示
void LCDDisplayOff(void);                                                  //关显示
void LCDSetCursor(u16 x, u16 y);                                           //设置光标
void LCDScanDir(u8 dir);                                                   //设置屏扫描方向
void LCDDrawPoint(u16 x, u16 y);                                           //画点
void LCDFastDrawPoint(u16 x, u16 y, u16 color);                            //快速画点
void LCDSSDBackLightSet(u8 pwm);                                           //SSD1963 背光控制
void LCDDisplayDir(u8 dir);                                                //设置屏幕显示方向
void LCDSetWindow(u16 sx, u16 sy, u16 width, u16 height);                  //设置窗口
void InitLCD(void);                                                        //初始化
void LCDClear(u16 Color);                                                  //清屏
void LCDFill(u16 sx, u16 sy, u16 ex, u16 ey, u16 color);                   //填充单色
void LCDColorFill(u16 sx, u16 sy, u16 ex, u16 ey, u16 *color);             //填充指定颜色
void LCDDrawLine(u16 x1, u16 y1, u16 x2, u16 y2);                          //画线
void LCDDrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2);                     //画矩形
void LCDDrawCircle(u16 x0, u16 y0, u8 r);                                  //画圆
void LCDShowChar(u16 x, u16 y, u8 code, u8 size, u8 mode);                 //显示一个字符
u32  LCDPow(u8 m, u8 n);                                                   //m^n函数
void LCDShowNum(u16 x, u16 y, u32 num, u8 len, u8 size);                   //显示一个数字
void LCDShowxNum(u16 x, u16 y, u32 num, u8 len, u8 size, u8 mode);         //显示 数字
void LCDShowString(u16 x, u16 y, u16 width, u16 height, u8 size, char* p); //显示一个字符串,12/16字体

不要被那么多的函数给吓到,其实真正需要用到的也几个函数。

1、InitLCD 函数,该函数用于初始化 LCD,因为 LCD 模块中使用到了 EXMC,所以在初始化中还得先初始化 EXMC 模块。

2、LCDDisplayDir 函数,该函数用于设置屏幕方向,0 为竖屏,1 为横屏。

3、LCDReadPoint 函数,该函数用于从 MCU 的显存中读取一个像素点。

4、LCDDrawPoint 和 LCDFastDrawPoint 函数,这两个函数用于显示一个像素点。

5、LCDClear 、LCDFill 和 LCDColorFill 函数,这三个函数都是用于颜色填充。LCDClear 函数用于使用纯色填充整个屏幕, LCDFill 函数用于使用纯色填充区块,LCDColorFill 函数用于将颜色缓冲区里的数据绘制到屏幕上。

6、LCDDrawLine 和 LCDDrawCircle 函数,用于画线和画圆。

7、LCDShowChar 和 LCDShowString 函数,用于显示字符和字符串。

市面上大部分单片机 GUI 库,例如 EMWIN、LVGL 等,都只需要用户提供读点和画点这两个函数,即可使用它们提供的各种 API 函数。

触屏驱动

相比之下触摸屏驱动的 API 函数就显得简单明了如下所示。

void InitTouch(void);            //初始化触摸屏检测驱动模块
u8   ScanTouch(u16* x, u16* y);  //触屏扫描

InitTouch 函数用于初始化触屏驱动,ScanTouch 函数则用于触屏扫描,若检测到手指按下则返回 1,否则返回 0。

实验结果

实验结果如下所示。

源码

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