目录

一、前言

大家好,我是 Hello_Embed。上一篇我们详细解析了 FreeRTOS 的 5 种堆实现,掌握了内存管理的核心技能。而要开展后续的 FreeRTOS 多任务项目(如打砖块游戏、数据可视化),必须先解决 “显示输出” 的问题 —— 因此本篇笔记聚焦**2.4 寸 TFT-LCD 屏的驱动开发 **。

本次驱动基于 CT117E-M4 开发板(STM32G431RBT6),屏幕采用 8080 并行时序接口,核心目标是实现 LCD 的初始化、格式化文本显示、颜色测试等基础功能。笔记内容整合了蓝桥杯嵌入式资料包和米醋电子工作室的核心资料,兼顾原理讲解与实战代码,确保大家能快速复现并用于后续项目。

二、LCD 驱动核心原理

2.1 硬件基础(原理图与屏幕参数)

屏幕核心参数
  • 尺寸:2.4 寸
  • 类型:MCU 接口型 TFT-LCD
  • 分辨率:240×320 像素
  • 接口类型:8080 并行时序(数据传输速度快,适合嵌入式实时显示)
硬件原理图

请添加图片描述

2.2 引脚冲突问题与解决方案

开发板上 LCD 与 LED 共用部分 GPIO 引脚,但无需担心信号冲突,核心原因的是锁存器的隔离作用

  1. 锁存器会缓存 LED 的控制信号,LCD 引脚信号的变化不会直接影响 LED 状态;
  2. 仅当向锁存器发送 “更新信号” 时,LED 状态才会改变,与 LCD 的读写操作完全解耦;
  3. 注意:不建议随意修改 LCD 引脚 ——8080 时序对引脚功能(如 RS、WR、CS)有严格定义,修改后大概率导致时序错乱。

2.3 8080 并行时序协议解析

8080 时序是 LCD 与 MCU 通信的核心协议,可通俗理解为 “多车道高速公路 + 交通指挥系统”:

  • 多车道(数据线):GPIOC0~GPIOC15(16 根线),同时传输 16 位数据,速度快;
  • 交通指挥(控制信号):
    • 片选信号(CS,PB9):LCD 的 “开关”,低电平时允许 MCU 与 LCD 通信;
    • 命令 / 数据选择(RS,PB8):“路牌”,低电平时传输命令(如清屏、设置颜色),高电平时传输显示数据;
    • 写使能(WR,PB5):“写允许开关”,低电平时 MCU 向 LCD 写入命令 / 数据;
    • 读使能(RD,PA8):“读允许开关”,低电平时 MCU 从 LCD 读取数据(本次暂不使用)。

2.4 信号操作逻辑(读命令 / 读数据 / 写数据)

1. 写命令(MCU→LCD 发送控制指令)
  1. 拉低 RS(PB8=0):告知 LCD 即将传输命令;
  2. 通过 GPIOC 输出命令代码;
  3. 拉低 WR(PB5=0):允许写入;
  4. 拉高 WR(PB5=1):完成写入,LCD 执行命令。
2. 写数据(MCU→LCD 发送显示数据)
  1. 拉高 RS(PB8=1):告知 LCD 即将传输显示数据;
  2. 通过 GPIOC 输出数据(如字符编码、颜色值);
  3. 拉低 WR(PB5=0):允许写入;
  4. 拉高 WR(PB5=1):完成写入,数据显示在 LCD 上。
3. 读数据(MCU←LCD 读取状态 / 数据)
  1. 拉高 RS(PB8=1):告知 LCD 即将读取数据;
  2. 拉低 RD(PA8=0):允许读取;
  3. 从 GPIOC 读取 LCD 返回的数据;
  4. 拉高 RD(PA8=1):完成读取。

三、CubeMX GPIO 配置步骤

根据 8080 时序要求,需配置以下 GPIO 引脚(均为推挽输出,初始高电平,无上下拉):

  1. GPIOC 配置:
    • 引脚:PC0~PC15(16 根数据线);
    • 模式:GPIO_Output;
    • 上下拉:No pull-up and no pull-down。
  2. GPIOB 配置:
    • 引脚:PB5(WR)、PB8(RS)、PB9(CS);
    • 模式:GPIO_Output;
    • 初始电平:High(默认关闭状态,避免误触发)。
  3. GPIOA 配置:
    • 引脚:PA8(RD);
    • 模式:GPIO_Output;
    • 初始电平:High(暂不使用读功能)。

配置示意图:
请添加图片描述

四、核心代码实现

蓝桥杯官方驱动已实现 LCD 的底层操作(如LCD_DisplayStringLineLCD_SetTextColor),我们只需封装上层应用函数,简化显示操作。

4.1 格式化显示函数(LcdSprintf)

功能:支持类似printf的格式化输出(如显示变量、数值),适配 LCD 行显示。

#include "lcd.h"
#include "stdio.h"
#include "stdarg.h"
/**
* @brief  格式化字符串并显示在LCD指定行
* @param  Line:LCD行号(如Line0~Line9,对应240×320分辨率的10行)
* @param  format:格式化字符串(支持%d、%s、%f等占位符)
* @param  ...:可变参数(与格式化字符串的占位符对应)
* @retval 无
* @note   依赖标准库的可变参数宏(va_list/va_start/va_end)和vsprintf函数
*/
void LcdSprintf(uint8_t Line, char *format, ...) {
char String[21];  // 缓冲区:每行最多显示20个字符(含结束符'\0')
va_list arg;      // 存储可变参数列表的变量
va_start(arg, format);                // 初始化可变参数列表,指向format后的第一个参数
vsprintf(String, format, arg);        // 将格式化字符串+可变参数写入缓冲区String
va_end(arg);                          // 清理可变参数列表,避免内存泄漏
// 调用官方驱动函数,在指定行显示字符串(u8为uint8_t的别名)
LCD_DisplayStringLine(Line, (u8 *)String);
}

4.2 增强版初始化函数(LCD_Init2)

功能:解决官方LCD_Init可能存在的初始化不稳定问题,增加电源稳定延时和双重验证。

/**
* @brief  LCD增强版初始化函数(解决初始化失败问题)
* @retval uint8_t:0=初始化成功,非0=失败(本次暂返回0,可扩展错误检测)
* @note   包含引脚配置、电源开启、延时稳定、参数初始化等完整流程
*/
uint8_t LCD_Init2(void)
{
uint8_t status = 0;  // 初始化状态标志
/* 1. 配置LCD控制引脚(RS/WR/CS/RD等,官方驱动已实现) */
LCD_CtrlLinesConfig();
/* 2. 延时300ms,确保LCD硬件电源完全稳定(关键!避免上电未就绪导致初始化失败) */
HAL_Delay(300);
/* 3. 开启LCD电源并稳定 */
LCD_PowerOn();
HAL_Delay(100);
/* 4. 执行官方初始化(配置LCD控制器寄存器,如分辨率、时序参数) */
LCD_Init();
HAL_Delay(200);
/* 5. 双重开启显示,确保LCD稳定点亮 */
LCD_DisplayOn();
HAL_Delay(50);
LCD_DisplayOn();
HAL_Delay(50);
/* 6. 设置默认显示参数(文本色=黑色,背景色=白色) */
LCD_SetTextColor(Black);    // 文本颜色(官方驱动定义的颜色宏)
LCD_SetBackColor(White);    // 背景颜色
/* 7. 双重清屏,清除残留数据,确保显示纯净 */
LCD_Clear(White);
HAL_Delay(50);
LCD_Clear(White);
HAL_Delay(50);
return status;  // 返回初始化状态(0=成功)
}

4.3 测试与诊断函数

1. 基础测试函数(LCD_Test)

功能:验证 LCD 是否正常显示文本,演示基本 API 的使用。

/**
* @brief  LCD基础功能测试函数
* @retval 无
* @note   显示固定文本,验证行显示、颜色设置功能
*/
void LCD_Test(void)
{
LCD_SetTextColor(Blue);     // 设置文本颜色为蓝色
LCD_SetBackColor(White);    // 背景色保持白色
LcdSprintf(Line0, "Hello LCD!");       // 第0行:欢迎语
LcdSprintf(Line1, "FreeRTOS Project"); // 第1行:项目名称
LcdSprintf(Line2, "Version 1.0");      // 第2行:版本号
LCD_SetTextColor(Black);    // 文本颜色改为黑色
LcdSprintf(Line4, "-------------------"); // 第4行:分隔线
LCD_SetTextColor(Red);      // 文本颜色改为红色
LcdSprintf(Line6, "Use LCD_Init2()");  // 第6行:提示使用增强版初始化
LcdSprintf(Line7, "for init");         // 第7行:补充说明
}
2. 颜色诊断函数(LCD_Diagnostics)

功能:测试 LCD 的颜色显示和清屏功能,用于排查显示异常。

/**
* @brief  LCD颜色与显示诊断函数
* @retval 无
* @note   循环测试不同文本色、背景色,验证LCD硬件是否正常
*/
void LCD_Diagnostics(void)
{
/* 1. 初始清屏(白色背景) */
LCD_Clear(White);
HAL_Delay(1000);  // 显示1秒
/* 2. 显示诊断模式提示 */
LCD_SetTextColor(Black);
LCD_SetBackColor(White);
LcdSprintf(Line0, "DIAGNOSTIC MODE");
HAL_Delay(1000);
/* 3. 测试不同文本颜色 */
LCD_SetTextColor(Blue);
LcdSprintf(Line2, "BLUE TEXT");  // 蓝色文本
HAL_Delay(500);
LCD_SetTextColor(Red);
LcdSprintf(Line3, "RED TEXT");    // 红色文本
HAL_Delay(500);
LCD_SetTextColor(Green);
LcdSprintf(Line4, "GREEN TEXT");  // 绿色文本
HAL_Delay(500);
/* 4. 测试反转颜色(白色文本+黑色背景) */
LCD_SetTextColor(White);
LCD_SetBackColor(Black);
LcdSprintf(Line6, "INVERTED COLORS");
HAL_Delay(1000);
/* 5. 测试清屏功能(黄色背景) */
LCD_Clear(Yellow);
HAL_Delay(500);
/* 6. 显示诊断完成 */
LCD_SetTextColor(Black);
LCD_SetBackColor(Yellow);
LcdSprintf(Line7, "Finished!");
HAL_Delay(1000);
}

4.4 头文件声明(lcd_driver.h)

#ifndef _LCD_DRIVER
#define _LCD_DRIVER
#include "stdint.h"
/**
* @brief  LCD增强版初始化函数
* @note   解决官方LCD_Init初始化不稳定问题,包含完整的电源和延时控制
* @retval uint8_t:0=成功,非0=失败
*/
uint8_t LCD_Init2(void);
/**
* @brief  LCD格式化显示函数
* @param  Line:显示行号(Line0~Line9)
* @param  format:格式化字符串(支持%d、%s等占位符)
* @param  ...:可变参数(与格式化字符串对应)
* @retval 无
*/
void LcdSprintf(uint8_t Line, char *format, ...);
/**
* @brief  LCD基础测试函数
* @note   演示文本显示、颜色设置功能
* @retval 无
*/
void LCD_Test(void);
/**
* @brief  LCD诊断函数
* @note   测试颜色显示和清屏功能,用于硬件排查
* @retval 无
*/
void LCD_Diagnostics(void);
#endif

五、主函数测试与现象验证

测试流程

  1. main.c中添加头文件:#include "lcd_driver.h"
  2. main函数中调用初始化和测试函数:
int main(void)
{
/* 系统初始化(HAL库自动生成) */
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
/* LCD初始化与测试 */
uint8_t lcd_status = LCD_Init2();  // 调用增强版初始化
if (lcd_status == 0) {            // 初始化成功
LCD_Test();                    // 执行基础测试
// LCD_Diagnostics();          // 如需颜色测试,注释上一行,启用此行
} else {
// 初始化失败处理(如闪烁LED报警,可自行扩展)
}
/* 后续FreeRTOS初始化与任务创建(上一篇笔记内容) */
osKernelInitialize();
MX_FREERTOS_Init();
osKernelStart();
while (1) {
// 调度器启动后,此处不会执行
}
}

测试现象

  1. 基础测试(LCD_Test):
    请添加图片描述

    • 第 0 行显示 “Hello LCD!”(蓝色);
    • 第 1-2 行显示项目信息(蓝色);
    • 第 4 行显示分隔线(黑色);
    • 第 6-7 行显示初始化提示(红色)。
  2. 颜色诊断(LCD_Diagnostics):
    请添加图片描述

    • 依次显示蓝色、红色、绿色文本;
    • 切换黑色背景 + 白色文本(反转色);
    • 清屏为黄色,最终显示 “Finished!”,全程无乱码或花屏。

六、下一篇预告

本次我们成功实现了 2.4 寸 TFT-LCD 的驱动开发,具备了 FreeRTOS 多任务项目的 “显示输出能力”。下一篇,我们将回归 FreeRTOS 实战:

  1. 创建声光色影多任务;

  2. 演示多任务并发执行;

  3. 讲解任务优先级配置与调度器工作机制。

    尽管部分硬件可能存在差异,但核心是掌握多任务的设计思想,为后续复杂项目打下基础。

七、结尾

本篇笔记的核心是 “LCD 驱动的工程化实现”—— 从原理层面解析了 8080 时序和信号逻辑,从实战层面提供了稳定的初始化函数、格式化显示函数,以及测试诊断工具,确保大家能快速复现并用于实际项目。

LCD 驱动是嵌入式可视化项目的基础,后续的 FreeRTOS 多任务项目(如数据监控、小游戏)都需要依赖它输出信息。掌握LcdSprintf这类格式化显示函数,能大幅简化文本和变量的显示操作,提高开发效率。

从堆管理到 LCD 驱动,我们已经为 FreeRTOS 复杂项目铺垫了核心基础。接下来,我们将把这些模块整合起来,通过多任务实现 “声光色影” 的联动效果,真正体会 RTOS 的优势。我是 Hello_Embed,下一篇多任务实战不见不散,欢迎持续关注!