基于 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,所以该图片不具备透明度信息,不能显示出透明效果,只适合做为背景图片,或者一个方块。

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

单片机中使用 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。
实验结果
实验结果如下所示。
-基础知识源码-实验结果-20230304-1024x768.jpg)
源码
本章节中的源码请参考《单片机 GUI 设计(零)- 大纲》