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×128320×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 字模数组。通过编写并调用字符显示函数,可以将对应的点阵数据正确映射到屏幕上,实现 任意英文字符和字符串的显示

该设计的核心思路是:

  1. 将 ASCII 码与点阵字模建立索引关系;

  2. 在程序中读取字模数据,逐列逐行解析像素点;

  3. 根据像素点的状态,在 LCD 上绘制前景色或背景色;

  4. 通过循环调用字符显示函数,实现字符串的连续显示。

最终效果是,在屏幕上能够直观、清晰地显示出英文字符串。
这种方法不仅可以验证 LCD 的基本驱动是否正确,还能够作为后续开发产品的显示功能。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐