STM32CubeMX + HAL 库:用FSMC接口驱动TFT LCD屏幕显示
本实验通过STM32的FSMC接口驱动2.8寸TFTLCD屏幕(ILI9341驱动芯片),实现了屏幕初始化、清屏和 标准 ASCII 字符集显示功能。实验详细介绍了TFTLCD的工作原理、FSMC配置方法以及与OLED的对比特性,重点阐述了FSMC在驱动LCD时的特殊应用——使用地址线区分命令/数据寄存器而非直接寻址。实验代码实现了字符显示、字符串输出等功能,验证了驱动程序的正确性。通过本实验掌握
1. 概述
1.1 实验目的
本实验通过 FSMC 接口驱动 TFT LCD 屏幕,实现屏幕的初始化、清屏及基本图形显示控制,旨在使学生掌握 TFT LCD 的工作原理和主动矩阵显示特点,熟悉驱动芯片与接口协议的关系,并通过实验掌握嵌入式系统中高速显示模块的控制方法和调试技巧,为后续开发图形化界面和复杂显示应用打下基础。本实验使用的屏幕为 2.8 寸 TFT LCD,分辨率为 320×240,驱动芯片为 ILI9341
1.2 TFT LCD介绍
1.2.1 介绍

TFT LCD 是一种主动矩阵液晶显示屏,每个像素点都配有独立的薄膜晶体管开关,通过调节液晶分子的排列来控制光线透过,实现图像显示。与普通被动矩阵 LCD 相比,TFT LCD 支持高速刷新、高分辨率和彩色显示。液晶本身不发光,需要背光源照射,因此属于透射式显示屏。
实验中常用的小型 TFT LCD 屏幕通常尺寸为 1.44"~3.5",分辨率从 128×128 到 320×480 不等,常用驱动芯片有 ST7735、ILI9341、ST7789、ILI9488、HX8357 等。数据传输接口一般为 SPI 或 8080 并口,便于与单片机(如 STM32)进行实验控制。
1.2.2 与OLED对比
| 特性 | LCD(液晶屏) | OLED(有机发光二极管屏) |
|---|---|---|
| 发光原理 | 背光透射液晶,不自发光 | 每个像素自发光,不需要背光 |
| 显示效果 | 对比度一般,黑色偏灰,视角受限 | 对比度高,黑色纯粹,视角广 |
| 功耗 | 背光常亮,功耗固定 | 黑色像素不发光,功耗低,显示纯黑省电 |
| 厚度 | 需要背光模组,整体厚度较大 | 不需要背光,屏幕可以更薄 |
| 寿命 | 背光寿命长,液晶本身稳定 | 蓝光 OLED 寿命较短,可能出现烧屏现象 |
| 接口 | SPI / 8080 并口 / RGB | SPI / I²C(IIC)/ 并口 |
| 响应速度 | 毫秒级,适合普通显示 | 微秒级,非常快,适合动态图像 |
| 典型尺寸 | 小屏到中屏(1.44"~7") | 小屏居多(0.96"、1.3"、1.5" 等),也有大屏 |
| 典型应用 | 工业显示、仪表、车载屏、HMI | 手表、手环、智能小设备、微型显示模块 |
1.2.3 驱动芯片
OLED驱动芯片如下
| 驱动芯片 | 常见尺寸 | 分辨率 | 接口类型 | 典型应用场景 |
|---|---|---|---|---|
| SSD1306 | 0.96" ~ 1.3" | 128×64 | I²C / SPI | 手表、手环、小型显示模块 |
| SH1106 | 1.3" ~ 1.5" | 128×64 | I²C / SPI | 小型智能设备、实验板 |
| SSD1327 | 1.5" ~ 2.0" | 128×128 | SPI | 彩色或灰度小屏 OLED |
| SSD1331 | 0.96" ~ 1.5" | 96×64 / 96×128 | SPI | 彩色小屏,便携设备 |
| SSD1351 | 1.5" ~ 2.4" | 128×128 / 128×160 | SPI | 彩色 OLED,手持设备 |
| SH1107 | 1.54" ~ 2.0" | 128×128 | I²C / SPI | 小型实验模块、智能设备 |
TFT LCD驱动芯片如下:
| 驱动芯片 | 屏幕尺寸 | 常见分辨率 | 常见接口类型 | 典型应用场景 |
|---|---|---|---|---|
| ST7735 | 1.44" ~ 2.0" | 128×128 / 128×160 | SPI | 小尺寸彩屏、手持设备、可穿戴设备 |
| ILI9341 | 2.4" ~ 3.2" | 240×320 (QVGA) | SPI / 8080 并口 | 小中屏、STM32 开发板、工业小屏 |
| ST7789 | 1.3" ~ 2.4" | 240×320 / 240×240 | SPI | 小屏、手持终端、智能手表 |
| ILI9488 | 3.5" | 320×480 | 8080 并口 / SPI | 中屏、仪表、家电屏幕 |
| HX8357 | 3.5" | 320×480 | 8080 并口 / SPI | 中屏、工业显示屏、手持设备 |
| SSD1963 | 4.3" ~ 7.0" | 480×272 ~ 800×480 | 8080 并口 / RGB | 中大屏、工业触控、HMI、车载屏 |
| RA8875 | 4.3" ~ 7.0" | 480×272 ~ 800×480 | 8080 并口 / RGB | 中大屏、工业人机界面、车载屏 |
| NT35510 | 5.0" ~ >7.0" | 800×480 / 1080×1920 | RGB / MIPI-DSI | 中大屏、手机屏、平板屏、智能设备 |
| OTM8009A | >7.0" | 720×1280 | MIPI-DSI | 大屏、高分辨率手机屏、智能终端 |
| ILI9881C | >7.0" | 1080×1920 | MIPI-DSI | 大屏、智能手机、平板 |
-
SPI接口:主要用于小屏(<3.5")或便携设备,布线简单,速度适中。
-
8080 并口:中屏常用,传输速度比 SPI 快,适合 3.5"~7" 的屏幕。
-
RGB 接口:中大屏(>4.3")多用,支持高刷新率和丰富颜色。
-
MIPI-DSI:高分辨率大屏(手机、平板),高速、节省 IO 引脚。
1.2.4 引脚连线
除了红色引脚外,其他引脚连线是固定的,红色引脚连线要与第2章节的配置相对应。
| LCD 引脚 | 功能说明 | STM32 FSMC 对应引脚 |
|---|---|---|
| D0–D15 | 8/16 位数据总线 | FSMC_D0 ~ FSMC_D15 |
| CS# | 片选信号(低有效) | FSMC_NEx(推荐NE4) |
| RS / A0 / DC | 命令/数据选择 | FSMC_Ax(推荐A10) |
| WR# | 写使能(低有效) | FSMC_NWE |
| RD# | 读使能(低有效) | FSMC_NOE |
| RESET# | 硬件复位(低有效) | MCU GPIO(任意普通 IO) |
| BL / LED | 背光控制 | MCU GPIO 或 PWM(可调亮度)本文PB0 |
| VCC | 电源(3.3V 或 5V,依模组而定) | — |
| GND | 地 | — |
| NC | 空脚 | 悬空即可 |

1.2.5 FSMC 驱动
在使用 FSMC(Flexible Static Memory Controller)驱动普通存储芯片(如 SRAM、NOR Flash)时,MCU 的地址线(FSMC_A[24:0])会直接作为存储芯片的地址输入,用于定位存储单元;数据线(FSMC_D[15:0])则作为数据读写的通道。这样,MCU 访问某个地址时,FSMC 会直接在总线上输出对应的地址和数据。
然而,对于带有显示控制器的 LCD 模块,内部并不像普通存储芯片那样直接由地址线选择存储单元。LCD 内部显存的读写是通过 命令/数据寄存器 方式进行的:
-
MCU 需要先向 LCD 的 命令寄存器 写入一个命令(例如设置显存地址、刷新窗口等)。
-
随后再向 LCD 的 数据寄存器 写入显示数据,LCD 控制器才会将其存入对应的显存区域。
在这种模式下,FSMC 的地址线就不会用于提供真实的显存地址,而是常用来实现 命令/数据选择(RS 或 DCX 引脚):
-
当 FSMC 地址线某一位(通常是 A10 )为 0 时,表示访问 LCD 的命令寄存器;
-
当该地址线为 1 时,表示访问 LCD 的数据寄存器。
因此,对于 MCU 来说,无论是写命令还是写数据,本质上都是向 FSMC 外设映射的地址空间中写数据。区别仅在于访问的地址不同,从而在硬件上驱动了 LCD 的 RS 引脚,决定此次写入是命令还是数据。
需要注意的是:
-
本实验中使用的是 16 位数据总线(FSMC_D[15:0])。
-
由于 16 位总线一次传输两个字节,因此 MCU 的地址总线会整体 右移一位(地址对齐)。也就是说,FSMC 输出的地址信号 HADDR[25:1] 实际映射到 FSMC_A[24:0] 引脚。
-
对于软件而言,访问
0x60000000可能表示写命令,而访问0x60000800则表示写数据,这取决于具体硬件连线中哪个地址线连接到了 RS 引脚。
1.2.6 与存储芯片区别
| 接口类型 | 普通存储芯片(SRAM/PSRAM) | LCD 控制器(带显存接口) | 说明 |
|---|---|---|---|
| 地址接口 | FSMC_A[n] → 存储芯片地址引脚,对应物理存储单元 | FSMC_A[x](常用 A10)→ 区分命令寄存器 / 数据寄存器 | 存储器:真实寻址;LCD:寄存器选择 |
| 数据接口 | FSMC_D[15:0] → 双向数据总线,直接读写存储单元 | FSMC_D[15:0] → 双向数据总线,写命令或像素数据 | 数据总线用法相同 |
| 控制接口 | - FSMC_NOE → 读使能(OE) - FSMC_NWE → 写使能(WE) - FSMC_NE[x] → 片选(CS) - FSMC_NBL0/NBL1 → 字节屏蔽 | - FSMC_NOE → LCD 读使能 - FSMC_NWE → LCD 写使能 - FSMC_NE[x] → LCD 芯片选 - FSMC_A[x] → 区分命令/数据 | 控制信号基本一致,只是 LCD 把地址线功能“简化为命令/数据选择” |
| 寻址方式 | FSMC_A[24:0] ← HADDR[25:1],真实物理地址映射 | FSMC_A[24:0] ← HADDR[25:1],但仅用少量地址线(如 A10)连接 LCD 的 RS/DC 引脚,用来区分命令/数据寄存器 | 存储芯片是真实寻址,LCD 是命令/数据寄存器选择信号 |
| 字节屏蔽/对齐 | HADDR[0] 生成 FSMC_NBL0/NBL1,实现对 16 位总线的高/低字节访问 | 一般不用字节屏蔽,LCD 接口按 16 位像素数据传输 | LCD 像素写入以半字(16bit)/字(32bit)为主 |
| 访问方式 | 数据即存储单元内容 | 数据可能是命令,也可能是显存像素数据 | MCU 端透明,区别在于外设解释方式不同 |
2 STM32CubeMx配置
2.1 FSMC配置
这里唯一需要注意的是,读写片选引脚与FSMC_Ax哪一个地址线对应上,然后选对应的地址线即可。

2.2 背光配置
本文是PB0引脚连上了LCD的背光引脚
3 MDK keil配置
3.1 C/C++配置
这个地方一定要选optimization=level0,因为FSMC时序相对复杂,不能使用编译软件进行代码自动优化,不然会打乱时序,屏幕驱动失败。

4. VSCode
4.1 lcd.h
#ifndef __LCD_H
#define __LCD_H
#include "fsmc.h"
#include "stm32f1xx_hal.h" // 方便使用 HAL_Delay、GPIO 等
// 宏定义
#define SRAM_BANK1_4 0x6c000000
#define LCD_ADDR_CMD (uint16_t *)SRAM_BANK1_4 // 写命令指针
#define LCD_ADDR_DATA (uint16_t *)(SRAM_BANK1_4 + (1 << 11)) // 写数据指针
// 显示屏的宽高
#define LCD_W 320
#define LCD_H 480
// RGB565基础颜色
// 基本颜色(RGB565格式)
#define WHITE 0xFFFF // 白色 (R:31, G:63, B:31)
#define BLACK 0x0000 // 黑色 (R:0, G:0, B:0)
#define RED 0xF800 // 红色 (R:31, G:0, B:0)
#define GREEN 0x07E0 // 绿色 (R:0, G:63, B:0)
#define BLUE 0x001F // 蓝色 (R:0, G:0, B:31)
#define CYAN 0x07FF // 青色 (R:0, G:63, B:31)
#define MAGENTA 0xF81F // 品红 (R:31, G:0, B:31)
#define YELLOW 0xFFE0 // 黄色 (R:31, G:63, B:0)
#define GRAY 0x8410 // 灰色 (R:16, G:32, B:16)
// 深浅颜色
#define DARK_RED 0x8000 // 深红色 (R:16, G:0, B:0)
#define LIGHT_BLUE 0x7D7C // 浅蓝色 (R:15, G:31, B:15)
#define ORANGE 0xFC00 // 橙色 (R:31, G:32, B:0)
#define PINK 0xF81F // 粉色 (R:31, G:0, B:31)
#define PURPLE 0x8010 // 紫色 (R:16, G:0, B:16)
// 补充一些常用颜色
#define BROWN 0xA145 // 棕色 (R:20, G:32, B:5)
#define LIGHT_GRAY 0xC618 // 浅灰色 (R:24, G:36, B:24)
#define DARK_GRAY 0x4208 // 深灰色 (R:8, G:16, B:8)
#define SKY_BLUE 0x867D // 天蓝色 (R:17, G:39, B:29)
#define ORCHID 0xF15F // 兰花紫 (R:30, G:12, B:31)
#define GOLD 0xFEA0 // 金色 (R:31, G:53, B:0)
// ======================= 基本控制操作 =======================
void LCD_Init(void);
void LCD_Reset(void);
void LCD_BGOn(void);
void LCD_BGOff(void);
void LCD_WriteCmd(uint16_t cmd);
void LCD_WriteData(uint16_t data);
uint16_t LCD_ReadData(void);
uint32_t LCD_ReadID(void);
void LCD_SetArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void LCD_ClearAll(uint16_t color);
// ======================= 绘图相关 =========================
void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color); // 画点
void LCD_ShowChar(uint16_t x, uint16_t y, char chr, uint16_t color, uint16_t bg); // 显示字符
void LCD_ShowString(uint16_t x, uint16_t y, const char *str, uint16_t color, uint16_t bg); // 显示字符串
// LCD 显示 8x16 字符
void LCD_ShowChar8x16(uint16_t x, uint16_t y, char chr, uint16_t color, uint16_t bg);
void LCD_ShowString8x16(uint16_t x, uint16_t y, const char *str, uint16_t color, uint16_t bg);
#endif
4.2 lcd.c
#include "lcd.h"
#include "font.h"
#include "stm32f1xx_hal.h" // 确保 HAL 函数可用
// ======================= 基本控制操作 =======================
// LCD 初始化
void LCD_Init(void) {
LCD_BGOn(); // 打开背光
// 软件复位
LCD_WriteCmd(0x01);
HAL_Delay(50);
// 退出睡眠模式
LCD_WriteCmd(0x11);
HAL_Delay(120);
// 设置像素格式为16位/像素(RGB565)
LCD_WriteCmd(0x3A);
LCD_WriteData(0x55);
// 设置显示方向(BGR位)
LCD_WriteCmd(0x36);
LCD_WriteData(0x08 | 0x40); // MX=0, MY=1, MV=0, BGR=1
// 开启显示
LCD_WriteCmd(0x29);
}
// LCD 复位(空函数,可根据硬件添加复位操作)
void LCD_Reset(void) {}
// 打开/关闭背光
void LCD_BGOn(void) {
HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_SET);
}
void LCD_BGOff(void) {
HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_RESET);
}
// ======================= 低级操作 =======================
// 写命令/数据
void LCD_WriteCmd(uint16_t cmd) {
*LCD_ADDR_CMD = cmd;
}
void LCD_WriteData(uint16_t data) {
*LCD_ADDR_DATA = data;
}
uint16_t LCD_ReadData(void) {
return *LCD_ADDR_DATA;
}
// 读取LCD ID
uint32_t LCD_ReadID(void) {
LCD_WriteCmd(0xD3);
uint32_t id = 0;
LCD_ReadData(); // 第一次读取无效
id |= (LCD_ReadData() & 0xFF) << 16;
id |= (LCD_ReadData() & 0xFF) << 8;
id |= (LCD_ReadData() & 0xFF);
return id;
}
// 设置绘制区域
void LCD_SetArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
LCD_WriteCmd(0x2A);
LCD_WriteData((x >> 8) & 0xFF);
LCD_WriteData(x & 0xFF);
LCD_WriteData(((x + w - 1) >> 8) & 0xFF);
LCD_WriteData((x + w - 1) & 0xFF);
LCD_WriteCmd(0x2B);
LCD_WriteData((y >> 8) & 0xFF);
LCD_WriteData(y & 0xFF);
LCD_WriteData(((y + h - 1) >> 8) & 0xFF);
LCD_WriteData((y + h - 1) & 0xFF);
}
// 清屏
void LCD_ClearAll(uint16_t color) {
LCD_SetArea(0, 0, LCD_W, LCD_H);
LCD_WriteCmd(0x2C);
for (uint32_t i = 0; i < LCD_W * LCD_H; i++) {
LCD_WriteData(color);
}
}
// ======================= 绘图相关 =======================
// 画点
void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color)
{
LCD_SetArea(x, y, 1, 1); // 设置1x1区域
LCD_WriteCmd(0x2C); // 写入GRAM命令
LCD_WriteData(color);
}
// 显示一个 ASCII 字符
void LCD_ShowChar(uint16_t x, uint16_t y, char chr, uint16_t color, uint16_t bg) {
uint8_t line, i;
if (chr < 0x20 || chr > 0x7E) chr = '?'; // 非法字符显示 '?'
const uint8_t *pFont = ASCII_Font6x8[chr - 0x20];
for (i = 0; i < 6; i++) { // 每列
line = pFont[i];
for (uint8_t j = 0; j < 8; j++) { // 每行
if (line & 0x01)
LCD_DrawPixel(x + i, y + j, color);
else
LCD_DrawPixel(x + i, y + j, bg);
line >>= 1;
}
}
}
// 显示字符串
void LCD_ShowString(uint16_t x, uint16_t y, const char *str, uint16_t color, uint16_t bg) {
while (*str) {
LCD_ShowChar(x, y, *str, color, bg);
x += 6; // 6像素宽
str++;
}
}
4.3 font.h
#ifndef __FONT_H__
#define __FONT_H__
// 6x8 ASCII 字库 (0x20 - 0x7F),每个字符占6字节
const uint8_t ASCII_Font6x8[][6] = {
{0x00,0x00,0x00,0x00,0x00,0x00}, // 20
{0x00,0x00,0x5F,0x00,0x00,0x00}, // 21 !
{0x00,0x07,0x00,0x07,0x00,0x00}, // 22 "
{0x14,0x7F,0x14,0x7F,0x14,0x00}, // 23 #
{0x24,0x2A,0x7F,0x2A,0x12,0x00}, // 24 $
{0x23,0x13,0x08,0x64,0x62,0x00}, // 25 %
{0x36,0x49,0x55,0x22,0x50,0x00}, // 26 &
{0x00,0x05,0x03,0x00,0x00,0x00}, // 27 '
{0x00,0x1C,0x22,0x41,0x00,0x00}, // 28 (
{0x00,0x41,0x22,0x1C,0x00,0x00}, // 29 )
{0x14,0x08,0x3E,0x08,0x14,0x00}, // 2A *
{0x08,0x08,0x3E,0x08,0x08,0x00}, // 2B +
{0x00,0x50,0x30,0x00,0x00,0x00}, // 2C ,
{0x08,0x08,0x08,0x08,0x08,0x00}, // 2D -
{0x00,0x60,0x60,0x00,0x00,0x00}, // 2E .
{0x20,0x10,0x08,0x04,0x02,0x00}, // 2F /
{0x3E,0x51,0x49,0x45,0x3E,0x00}, // 30 0
{0x00,0x42,0x7F,0x40,0x00,0x00}, // 31 1
{0x42,0x61,0x51,0x49,0x46,0x00}, // 32 2
{0x21,0x41,0x45,0x4B,0x31,0x00}, // 33 3
{0x18,0x14,0x12,0x7F,0x10,0x00}, // 34 4
{0x27,0x45,0x45,0x45,0x39,0x00}, // 35 5
{0x3C,0x4A,0x49,0x49,0x30,0x00}, // 36 6
{0x01,0x71,0x09,0x05,0x03,0x00}, // 37 7
{0x36,0x49,0x49,0x49,0x36,0x00}, // 38 8
{0x06,0x49,0x49,0x29,0x1E,0x00}, // 39 9
{0x00,0x36,0x36,0x00,0x00,0x00}, // 3A :
{0x00,0x56,0x36,0x00,0x00,0x00}, // 3B ;
{0x08,0x14,0x22,0x41,0x00,0x00}, // 3C <
{0x14,0x14,0x14,0x14,0x14,0x00}, // 3D =
{0x41,0x22,0x14,0x08,0x00,0x00}, // 3E >
{0x02,0x01,0x51,0x09,0x06,0x00}, // 3F ?
{0x32,0x49,0x79,0x41,0x3E,0x00}, // 40 @
{0x7E,0x11,0x11,0x11,0x7E,0x00}, // 41 A
{0x7F,0x49,0x49,0x49,0x36,0x00}, // 42 B
{0x3E,0x41,0x41,0x41,0x22,0x00}, // 43 C
{0x7F,0x41,0x41,0x22,0x1C,0x00}, // 44 D
{0x7F,0x49,0x49,0x49,0x41,0x00}, // 45 E
{0x7F,0x09,0x09,0x09,0x01,0x00}, // 46 F
{0x3E,0x41,0x49,0x49,0x7A,0x00}, // 47 G
{0x7F,0x08,0x08,0x08,0x7F,0x00}, // 48 H
{0x00,0x41,0x7F,0x41,0x00,0x00}, // 49 I
{0x20,0x40,0x41,0x3F,0x01,0x00}, // 4A J
{0x7F,0x08,0x14,0x22,0x41,0x00}, // 4B K
{0x7F,0x40,0x40,0x40,0x40,0x00}, // 4C L
{0x7F,0x02,0x0C,0x02,0x7F,0x00}, // 4D M
{0x7F,0x04,0x08,0x10,0x7F,0x00}, // 4E N
{0x3E,0x41,0x41,0x41,0x3E,0x00}, // 4F O
{0x7F,0x09,0x09,0x09,0x06,0x00}, // 50 P
{0x3E,0x41,0x51,0x21,0x5E,0x00}, // 51 Q
{0x7F,0x09,0x19,0x29,0x46,0x00}, // 52 R
{0x46,0x49,0x49,0x49,0x31,0x00}, // 53 S
{0x01,0x01,0x7F,0x01,0x01,0x00}, // 54 T
{0x3F,0x40,0x40,0x40,0x3F,0x00}, // 55 U
{0x1F,0x20,0x40,0x20,0x1F,0x00}, // 56 V
{0x7F,0x20,0x18,0x20,0x7F,0x00}, // 57 W
{0x63,0x14,0x08,0x14,0x63,0x00}, // 58 X
{0x07,0x08,0x70,0x08,0x07,0x00}, // 59 Y
{0x61,0x51,0x49,0x45,0x43,0x00}, // 5A Z
{0x00,0x7F,0x41,0x41,0x00,0x00}, // 5B [
{0x02,0x04,0x08,0x10,0x20,0x00}, // 5C backslash
{0x00,0x41,0x41,0x7F,0x00,0x00}, // 5D ]
{0x04,0x02,0x01,0x02,0x04,0x00}, // 5E ^
{0x40,0x40,0x40,0x40,0x40,0x00}, // 5F _
{0x00,0x01,0x02,0x04,0x00,0x00}, // 60 `
{0x20,0x54,0x54,0x54,0x78,0x00}, // 61 a
{0x7F,0x48,0x44,0x44,0x38,0x00}, // 62 b
{0x38,0x44,0x44,0x44,0x20,0x00}, // 63 c
{0x38,0x44,0x44,0x48,0x7F,0x00}, // 64 d
{0x38,0x54,0x54,0x54,0x18,0x00}, // 65 e
{0x08,0x7E,0x09,0x01,0x02,0x00}, // 66 f
{0x0C,0x52,0x52,0x52,0x3E,0x00}, // 67 g
{0x7F,0x08,0x04,0x04,0x78,0x00}, // 68 h
{0x00,0x44,0x7D,0x40,0x00,0x00}, // 69 i
{0x20,0x40,0x44,0x3D,0x00,0x00}, // 6A j
{0x7F,0x10,0x28,0x44,0x00,0x00}, // 6B k
{0x00,0x41,0x7F,0x40,0x00,0x00}, // 6C l
{0x7C,0x04,0x18,0x04,0x78,0x00}, // 6D m
{0x7C,0x08,0x04,0x04,0x78,0x00}, // 6E n
{0x38,0x44,0x44,0x44,0x38,0x00}, // 6F o
{0x7C,0x14,0x14,0x14,0x08,0x00}, // 70 p
{0x08,0x14,0x14,0x18,0x7C,0x00}, // 71 q
{0x7C,0x08,0x04,0x04,0x08,0x00}, // 72 r
{0x48,0x54,0x54,0x54,0x20,0x00}, // 73 s
{0x04,0x3F,0x44,0x40,0x20,0x00}, // 74 t
{0x3C,0x40,0x40,0x20,0x7C,0x00}, // 75 u
{0x1C,0x20,0x40,0x20,0x1C,0x00}, // 76 v
{0x3C,0x40,0x30,0x40,0x3C,0x00}, // 77 w
{0x44,0x28,0x10,0x28,0x44,0x00}, // 78 x
{0x0C,0x50,0x50,0x50,0x3C,0x00}, // 79 y
{0x44,0x64,0x54,0x4C,0x44,0x00}, // 7A z
{0x00,0x08,0x36,0x41,0x00,0x00}, // 7B {
{0x00,0x00,0x7F,0x00,0x00,0x00}, // 7C |
{0x00,0x41,0x36,0x08,0x00,0x00}, // 7D }
{0x10,0x08,0x08,0x10,0x08,0x00}, // 7E ~
};
#endif /* __FONT_H__ */
4.4 main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
#include "fsmc.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "lcd.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
// 温度传感器参数(参�?? STM32F103 手册�???
#define V25 1.43f // 25°C 时的电压(典型�?�)
#define AVG_SLOPE 0.0043f // 平均斜率�???4.3 mV/°C�???
#define VREF 3.3f // ADC 参�?�电压(默认 3.3V�???
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint16_t adc_values[2] = {0};
float p, t, h; // 气压传感器BMP280输出参数
float temper, humidity; //ATH20输出参数
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_I2C1_Init();
MX_FSMC_Init();
/* USER CODE BEGIN 2 */
LCD_Init();
uint32_t id= LCD_ReadID();
LCD_ClearAll(RED);
HAL_Delay(2000);
LCD_ClearAll(BLUE);
HAL_Delay(2000);
LCD_ClearAll(BLACK);
LCD_ShowString(10, 10, "HELLO FSMC LCD", WHITE, PURPLE);
LCD_ShowString(10, 30, "ASCII TEST OK", YELLOW, BLUE);
char buf[32]; // 用来存放格式化字符串
sprintf(buf, "LCD_ReadID = %#x", id); // 格式化 ID
LCD_ShowString(10, 50, buf, GOLD, ORCHID); // 显示在 (10,50)
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("Hello World!\n"); // 打印调试信息
HAL_Delay(2000); // 2秒延迟(若需更高实时性,改用定时器中断)
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
5. 实验验证

本实验在 LCD 显示驱动的基础上,集成了 标准 ASCII 字符集的 8×16 字模数组。通过编写并调用字符显示函数,可以将对应的点阵数据正确映射到屏幕上,实现 任意英文字符和字符串的显示。
该设计的核心思路是:
-
将 ASCII 码与点阵字模建立索引关系;
-
在程序中读取字模数据,逐列逐行解析像素点;
-
根据像素点的状态,在 LCD 上绘制前景色或背景色;
-
通过循环调用字符显示函数,实现字符串的连续显示。
最终效果是,在屏幕上能够直观、清晰地显示出英文字符串。
这种方法不仅可以验证 LCD 的基本驱动是否正确,还能够作为后续开发产品的显示功能。
更多推荐




所有评论(0)