17-FSMC与LCD显示.md 27 KB

STM32 FSMC 外部存储器控制

基于尚硅谷(进阶篇·第6/7章)综合整理


1. FSMC 简介

FSMC(Flexible Static Memory Controller)是 STM32F10x 的可变静态存储控制器,可以连接 NOR Flash、PSRAM、SRAM、NAND Flash 和 PC Card。

1.1 关键特性

  • 支持 3 种存储器类型:NOR/PSRAM/SRAM、NAND Flash、PC Card
  • 4 个片选(NE1~NE4),对应 Bank1 的 4 个区域
  • 数据总线宽度:8/16 位(可配置)
  • 地址总线最高 26 位(HADDR[25:0])
  • 支持复用/非复用模式
  • 支持同步/异步传输

1.2 存储映射

FSMC 将外部存储器映射到 Cortex-M3 的 4GB 地址空间的特定区域:

地址范围            存储器类型        片选
0x6000 0000 ~
  0x6FFF FFFF      NOR/PSRAM/SRAM   NE1~NE4

0x7000 0000 ~
  0x7FFF FFFF      NAND Flash       NCE2~NCE3

0x8000 0000 ~
  0x8FFF FFFF      PC Card          NCE4_1~NCE4_2

Bank1 被分为 4 个区域,每个 64MB:

区域 地址范围 片选引脚 使用场景
Region1 0x6000 0000~ NE1 NOR/SRAM
Region2 0x6400 0000~ NE2 NOR/SRAM
Region3 0x6800 0000~ NE3 SRAM / LCD
Region4 0x6C00 0000~ NE4 SRAM / LCD

1.3 FSMC 内部框图

                        ┌───────────┐
HCLK ──────────────────→│   FSMC    │
                        │           │
A[25:0] / D[15:0] ←───→│  FSMC_NE  ├────→ 片选
                        │  FSMC_NWE ├────→ 写使能
                        │  FSMC_NOE ├────→ 读使能
                        │  FSMC_NBL ├────→ 字节掩码
                        └───────────┘

2. 扩展外部 SRAM(IS62WV51216)

2.1 IS62WV51216 简介

参数 规格
容量 512K × 16bit = 1MB
接口 并行异步 SRAM
数据总线 16 位
地址总线 19 位(A0~A18)
访问时间 45ns/55ns
工作电压 2.5V ~ 3.6V

2.2 硬件连接

STM32 FSMC              IS62WV51216
─────────               ───────────
FSMC_A[18:0] ─────────── A[18:0]
FSMC_D[15:0] ─────────── DQ[15:0]
FSMC_NE3     ─────────── CS#           (片选, 地址 0x6800 0000)
FSMC_NWE     ─────────── WE#           (写使能)
FSMC_NOE     ─────────── OE#           (输出使能)
FSMC_NBL0    ─────────── LB#           (低字节掩码)
FSMC_NBL1    ─────────── UB#           (高字节掩码)

2.3 FSMC 寄存器配置(尚硅谷风格)

FSMC Bank1 的配置通过两组寄存器完成:

  • BCR (片选控制寄存器):配置存储器类型、数据宽度、时序模式
  • BTR (片选时序寄存器):配置地址建立时间、数据保持时间

每个 Region 对应一组 BCR/BTR,索引关系:

区域 BCR 寄存器 BTR 寄存器
Region1 BTCR[0] BTCR[1]
Region2 BTCR[2] BTCR[3]
Region3 BTCR[4] BTCR[5]
Region4 BTCR[6] BTCR[7]

寄存器位域详解

BCR (Bus Control Register) — 片选控制寄存器(以 BCR1 为例, 32位):

位域 说明
MBKEN bit0 存储器Bank使能(1=使能)
MUXEN bit1 地址/数据复用模式(1=AD[15:0]复用)
MTYP[1:0] bits 3:2 存储器类型: 00=SRAM, 01=PSRAM, 10=NOR
MWID[1:0] bits 5:4 数据总线宽度: 00=8bit, 01=16bit, 10=32bit
WREN bit12 写使能(1=允许写入)

BTR (Bus Timing Register) — 片选时序寄存器(32位):

位域 说明
ADDSET[3:0] bits 3:0 地址建立时间(HCLK周期数)
DATAST[7:0] bits 15:8 数据建立时间(HCLK周期数)
ACCMOD[1:0] bits 29:28 访问模式: 00=模式A, 01=模式B, 10=模式C, 11=模式D

尚硅谷讲解:ADDSET和DATAST的物理含义

ADDSET和DATAST本质上是在控制FSMC状态机的等待周期数。以写操作为例:

> HCLK   ████░░████░░████░░████░░
> A[25:0]──[地址有效]────────────→
> NEx    ────┐             ┌─────
>            │             │
> NWE    ────┘             └─────
>            ↑ADDSET↑      ↑DATAST↑
>            (地址→NWE)    (NWE→数据写入)
> ```
> ADDSET=0时地址和NEx/NWE同时变化;ADDSET增大则地址先建立、NWE后拉低,适合慢速设备。

#### FSMC 初始化函数

c #include "stm32f10x_fsmc.h"

void FSMC_SRAM_Init(void) {

// ── 1. 时钟使能 ──
// AHBENR[bit8]=FSMCEN: 使能FSMC时钟(AHB总线)
RCC->AHBENR |= RCC_AHBENR_FSMCEN;       // |= 按位或: FSMCEN置1, 使能FSMC时钟
// APB2ENR: 使能各GPIO端口时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPDEN;     // |= 按位或: IOPDEN置1, 使能GPIOD时钟(数据线D[0..3])
RCC->APB2ENR |= RCC_APB2ENR_IOPEEN;     // |= 按位或: IOPEEN置1, 使能GPIOE时钟(数据线D[4..15])
RCC->APB2ENR |= RCC_APB2ENR_IOPFEN;     // |= 按位或: IOPFEN置1, 使能GPIOF时钟(地址A[0..3],NWE,NOE)
RCC->APB2ENR |= RCC_APB2ENR_IOPGEN;     // |= 按位或: IOPGEN置1, 使能GPIOG时钟(地址A[4..18],NE3)

// ── 2. GPIO 配置 — 所有 FSMC 引脚设为复用推挽输出 50MHz ──
// 数据线 PD0~PD15
GPIO_SetBits(GPIOD, 0xFFFF);             // 直接赋值: ODR寄存器写0xFFFF, 初始高电平避免毛刺
// ... 实际需要逐组配置 GPIO_CRL/CRH

// 地址线 PE0/PE1, PF0~PF3, PG0~PG15

// 控制信号: NWE(PD5), NOE(PD4), NE3(PG10), NBL0(PE0), NBL1(PE1)

// ── 3. FSMC 时序配置 — Bank1 Region3 (NE3) ──
// BTCR[4] = BCR3(Bank1 Control Register 3), BTCR[5] = BTR3(Bank1 Timing Register 3)
// BCR寄存器位定义:
//   MBKEN(bit0) : Memory Bank Enable — 使能存储区域
//   MTYP[1:0](bits 3:2) : Memory Type — 00=SRAM, 01=PSRAM, 10=NOR
//   MWID[1:0](bits 5:4) : Memory Data Bus Width — 00=8bit, 01=16bit, 10=32bit
//   WREN(bit12) : Write Enable — 写使能
//
// BTR寄存器位定义:
//   ADDSET[3:0](bits 3:0) : Address Setup Time — 地址建立时间(HCLK周期数)
//   DATAST[7:0](bits 15:8) : Data Setup Time — 数据建立时间(HCLK周期数)
FSMC_Bank1->BTCR[4] = 0;                 // 直接赋值: BCR寄存器先全清0
FSMC_Bank1->BTCR[4] |= FSMC_BCR1_MBKEN;  // |= 按位或: MBKEN(bit0)置1, 使能该Bank
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR1_MTYP;  // &= ~ 按位与+取反: MTYP[1:0]清0 → 00(SRAM类型)
FSMC_Bank1->BTCR[4] |= FSMC_BCR1_MWID_1; // |= 按位或: MWID_1(bit5)置1
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR1_MWID_0;// &= ~ 按位与+取反: MWID_0(bit4)清0 → MWID=01(16位总线)
FSMC_Bank1->BTCR[4] |= FSMC_BCR1_WREN;   // |= 按位或: WREN(bit12)置1, 使能写操作

// BTR: 时序配置
FSMC_Bank1->BTCR[5] = 0;                 // 直接赋值: BTR寄存器先全清0
// ADDSET[3:0] = 0: 地址建立时间为0个HCLK周期(地址与NEx同时有效)
FSMC_Bank1->BTCR[5] |= (0 << 0);         // |= 按位或+左移: ADDSET=0写入bit3~bit0
// DATAST[7:0] = 3: 数据建立时间为3个HCLK周期(72MHz→约41.7ns)
FSMC_Bank1->BTCR[5] |= (3 << 8);         // |= 按位或+左移: DATAST=3写入bit15~bit8

}


#### GPIO 初始化详细实现

c void FSMC_GPIO_Init(void) {

// CRL/CRH寄存器: 每4位控制一个引脚, 格式为 CNF[1:0] + MODE[1:0]
// 0xB = 0b1011 → CNF=10(复用推挽), MODE=11(输出50MHz)
// PD0~PD7 (数据低字节) — CRL(控制低8位引脚)
GPIOD->CRL = 0xBBBBBBBB;     // 直接赋值: PD0~PD7全部设为复用推挽50MHz
// ^^ 每4位=0xB: PDn_CNF[1:0]=10, PDn_MODE[1:0]=11

// PD8~PD15 (数据高字节) — CRH(控制高8位引脚)
GPIOD->CRH = 0xBBBBBBBB;     // 直接赋值: PD8~PD15复用推挽50MHz

// PD4 (NOE), PD5 (NWE) — 只改PD4/PD5, 保留其他位
// 0x00BB0000: PD4(bit16~19)=B, PD5(bit20~23)=B
GPIOD->CRL |= 0x00BB0000;    // |= 按位或: 仅PD4/PD5设为复用推挽, 不影响PD0~PD3

// PE0, PE1 (NBL0, NBL1)
GPIOE->CRL = 0xBBBBBBBB;     // 直接赋值: PE0~PE7全设为复用推挽50MHz

// PF0~PF3 (地址低4位)
GPIOF->CRL = 0xBBBBBBBB;     // 直接赋值: PF0~PF7全设为复用推挽50MHz

// PG0~PG3 (地址线) — CRL
// PG4~PG9 (地址线) — CRH
// PG10 (NE3) — CRH
// PG12~PG15 (地址线) — CRH
GPIOG->CRL = 0xBBBBBBBB;     // 直接赋值: PG0~PG7设为复用推挽50MHz
GPIOG->CRH = 0xBBBBBBBB;     // 直接赋值: PG8~PG15(含NE3)设为复用推挽50MHz

}


> **注意**:`0xBBBBBBBB` 表示每 4 位配置为 `1011`,即 MODE=11(50MHz),CNF=10(复用推挽)。

#### 简化版宏配置(尚硅谷风格)

c /* FSMC 参数宏 */ #define FSMC_BANK1_REGION3 ((uint32_t)0x68000000)

/* 初始化封装 */ void FSMC_Init(void) {

// ── 开时钟 ──
RCC->AHBENR |= RCC_AHBENR_FSMCEN;      // |= 按位或: FSMCEN置1, 使能FSMC时钟

// PD 数据线
RCC->APB2ENR |= RCC_APB2ENR_IOPDEN;    // |= 按位或: IOPDEN置1, 使能GPIOD时钟
GPIOD->CRL = 0xBBBBBBBB;               // 直接赋值: PD0~PD7复用推挽50MHz
GPIOD->CRH = 0xBBBBBBBB;               // 直接赋值: PD8~PD15复用推挽50MHz

// ... 类似配置所有 FSMC 引脚

// ── FSMC 控制寄存器 BCR3 ──
// 寄存器值 = bit0(MBKEN) | bit1(MUXEN) | bit4(MWID0) | bit12(WREN)
//   bit0=1:  使能Bank1 Region3
//   bit1=1:  使能地址/数据复用模式(MUXEN)
//   bit4=1:  MWID[1:0]=01 → 16位数据总线
//   bit12=1: WREN写使能
//   bit3~bit2=00: MTYP[1:0]=00 → SRAM类型
FSMC_Bank1->BTCR[4] = (1<<0) | (1<<1) | (1<<4) | (1<<12);
// ^^ =直接赋值, <<左移构造位掩码, |按位或合并各标志位

// ── FSMC 时序寄存器 BTR3 ──
//   ADDSET[3:0] = 0:  地址建立时间0个HCLK
//   DATAST[7:0] = 3:  数据建立时间3个HCLK
FSMC_Bank1->BTCR[5] = (0<<0) | (3<<8);  // =赋值, <<左移, |按位或: ADDSET=0, DATAST=3

}


### 2.4 SRAM 访问

配置完成后,直接通过指针访问映射地址即可读写 SRAM:

c // 方法 1:KEIL 扩展关键字 uint8_t v1 attribute((at(0x68000000))); // 8 位变量 uint16_t v16 attribute((at(0x68000002))); // 16 位变量

// 方法 2:指针直接访问 uint8_t *p = (uint8_t *)0x68000001; *p = 100;

uint16_t *pw = (uint16_t *)0x68000000; *pw = 0xAABB;

// 方法 3:数组操作 uint16_t sram_buf[256]; // 实际上需要指定地址 // 或使用指针数组 #define SRAM_BASE ((uint16_t *)0x68000000) SRAM_BASE[0] = 0x1234; // 写入第一个 16 位单元 uint16_t val = SRAM_BASE[0]; // 读取

// 内存拷贝示例 void SRAM_Test(void) {

uint8_t src[10] = "STM32FSMC";
uint8_t dst[10] = {0};

// 写入 SRAM
for (int i = 0; i < 10; i++)
    ((uint8_t *)0x68000000)[i] = src[i];

// 从 SRAM 读出
for (int i = 0; i < 10; i++)
    dst[i] = ((uint8_t *)0x68000000)[i];

// 此时 dst[] == "STM32FSMC"

}


---

## 3. LCD 显示(ILI9486 + FSMC 8080 时序)

### 3.1 ILI9486 简介

ILI9486 是一款常见的 TFT-LCD 驱动芯片,支持 16-bit/18-bit RGB 接口和 MCU 8080 并行接口。

| 参数         | 规格                    |
|--------------|-------------------------|
| 分辨率       | 320 × 480               |
| 颜色深度     | 16-bit (RGB565)/18-bit  |
| 接口         | 8080 并行 / SPI / RGB   |
| 驱动 IC      | ILI9486                  |

### 3.2 8080 并行接口时序

8080 接口是一种异步并行总线,类似 SRAM 的读/写时序,因此 FSMC 可以**无缝驱动** LCD。

关键信号:

| 信号  | 功能                     | 连接                   |
|-------|--------------------------|------------------------|
| CS    | 片选(低有效)           | FSMC_NE4 (0x6C00 0000)|
| RS    | 命令/数据选择            | FSMC_A11               |
| RD    | 读使能(低有效)         | FSMC_NOE               |
| WR    | 写使能(低有效)         | FSMC_NWE               |
| D[15:0]| 16 位数据总线           | FSMC_D[15:0]           |

### 3.3 地址映射

RS 连接到 A11,决定访问的是命令寄存器还是数据寄存器:

LCD 命令地址 (RS=0): 0x6C00 0000 (A11 = 0)

LCD 数据地址 (RS=1): 0x6C00 0800 (A11 = 1)


c // LCD映射基址: 0x6C00 0000 ← Bank1 Region4(NE4片选)的起始地址 #define LCD_BASE ((uint32_t)0x6C000000) // 命令地址: A11=0(RS=0), 即地址bit11=0 #define LCD_CMD_ADDR (LCD_BASE) // RS=0: 写命令寄存器 // 数据地址: A11=1(RS=1), bit11置1 → 0x6C00 0000 + 0x800 = 0x6C00 0800 #define LCD_DATA_ADDR (LCD_BASE + 0x00000800) // RS=1: 写数据寄存器

// 宏: 向命令地址写入16位命令值 // *(volatile uint16_t )addr: 将addr强制转为16位指针再解引用 // volatile防止编译器优化(确保每次写入都产生外部总线访问) #define LCD_WriteCmd(cmd) ((volatile uint16_t )LCD_CMD_ADDR = (cmd)) // ^^ volatile uint16_t: 指向外部存储器的16位指针 // = cmd: 写入操作 → FSMC自动产生片选+写使能时序

// 宏: 向数据地址写入16位数据 #define LCD_WriteData(data) (*(volatile uint16_t *)LCD_DATA_ADDR = (data))

// 宏: 从数据地址读取16位数据 #define LCD_ReadData() (*(volatile uint16_t *)LCD_DATA_ADDR) // ^^ 读取操作 → FSMC自动产生片选+读使能时序


**原理**:FSMC 将外部设备视为存储器,当 CPU 访问 `0x6C000000` 时,FSMC 自动产生片选 NE4 以及 NWE/NOE 时序。A11 的状态由地址的第 12 位决定(A11 = bit 11),所以 `0x6C000800` 的 bit 11 = 1 使 RS 为高。

### 3.4 FSMC 配置(LCD 方式)

LCD 需要通过 FSMC 的 NOR/PSRAM 模式 A 来模拟 8080 时序:

c void FSMC_LCD_Init(void) {

// ── 使能 FSMC 时钟和 GPIO ──
RCC->AHBENR |= RCC_AHBENR_FSMCEN;      // |= 按位或: FSMCEN置1, 使能FSMC时钟(AHB)
// ... GPIO 配置同 SRAM 部分

// ── 使用 Bank1 Region4 (NE4) ──
// BTCR[6] = BCR4, BTCR[7] = BTR4 (Bank1 Region4的控制和时序寄存器)
// BCR寄存器关键位:
//   MBKEN(bit0) : 使能该Bank区域
//   MTYP[1:0](bits 3:2) : 存储器类型, 00=SRAM(兼容8080时序)
//   MWID[1:0](bits 5:4) : 数据总线宽度, 01=16位
//   WREN(bit12) : 写使能
FSMC_Bank1->BTCR[6] = 0;                // 直接赋值: BCR4先清0
FSMC_Bank1->BTCR[6] |= FSMC_BCR1_MBKEN; // |= 按位或: MBKEN(bit0)置1, 使能Bank1 Region4
FSMC_Bank1->BTCR[6] &= ~FSMC_BCR1_MTYP; // &= ~ 按位与+取反: MTYP位(bit3~bit2)清0 → SRAM模式
FSMC_Bank1->BTCR[6] |= FSMC_BCR1_MWID_1;// |= 按位或: MWID_1(bit5)置1
FSMC_Bank1->BTCR[6] &= ~FSMC_BCR1_MWID_0;// &= ~: MWID_0(bit4)清0 → MWID[1:0]=01(16位数据总线)
FSMC_Bank1->BTCR[6] |= FSMC_BCR1_WREN;  // |= 按位或: WREN(bit12)置1, 使能写操作

// ── 时序配置:LCD通常需要较长的建立时间 ──
// BTR4寄存器:
//   ADDSET[3:0](bits 3:0) : 地址建立时间(HCLK周期数)
//   DATAST[7:0](bits 15:8) : 数据建立时间(HCLK周期数)
FSMC_Bank1->BTCR[7] = 0;                // 直接赋值: BTR4先清0
// ADDSET=1: 地址信号(NE4)和RS信号建立后等待1个HCLK再拉低NWE
FSMC_Bank1->BTCR[7] |= (1 << 0);        // |= 按位或+左移: ADDSET=1写入bit3~bit0
// DATAST=2: 数据建立时间2个HCLK, 保证LCD的tDSW时序要求
FSMC_Bank1->BTCR[7] |= (2 << 8);        // |= 按位或+左移: DATAST=2写入bit15~bit8

}


### 3.5 LCD 驱动函数(尚硅谷风格)

#### 基本写操作

c /* 命令和数据写入 */ void LCD_WriteReg(uint16_t reg) {

// *(volatile uint16_t *): 将命令地址强制转为16位指针, volatile确保不被优化
// = reg: 向该地址写入16位值 → FSMC自动产生NE4片选+NWE写时序
//   RS(A11)=0 → LCD将数据线上的值解释为命令
*(volatile uint16_t *)LCD_CMD_ADDR = reg;  // 直接赋值: 写命令寄存器

}

void LCD_WriteData(uint16_t data) {

// RS(A11)=1 → LCD将数据线上的值解释为数据
*(volatile uint16_t *)LCD_DATA_ADDR = data; // 直接赋值: 写数据寄存器

}

/* 组合操作:先写命令,再写数据 */ void LCD_WriteRegValue(uint16_t reg, uint16_t value) {

LCD_WriteReg(reg);
LCD_WriteData(value);

}


#### 读操作

c uint16_t LCD_ReadData(void) {

// 先读一个虚拟数据: FSMC需要先输出一个读周期(驱动dummy read)
// 对LCD_DATA_ADDR的读取 → FSMC产生NE4片选+NOE读时序, RS=1
volatile uint16_t dummy = *(volatile uint16_t *)LCD_DATA_ADDR;
// ^^ volatile: 强制生成真实的存储器读取指令(防止编译器优化掉)
(void)dummy;
// 再读真实数据 — 此时LCD已准备好输出
return *(volatile uint16_t *)LCD_DATA_ADDR;  // 直接读取: 返回16位数据

}

uint16_t LCD_ReadReg(uint16_t reg) {

LCD_WriteReg(reg);                           // 先写命令(RS=0)
return LCD_ReadData();                       // 再读数据(RS=1)

}


#### LCD 初始化序列(ILI9486)

ILI9486 的初始化通常需要发送一串配置寄存器命令。以下是精简版:

c void LCD_Init(void) {

// 等待 LCD 上电稳定
Delay_ms(120);

// 软件复位
LCD_WriteRegValue(0x01, 0x0000);   // 软件复位
Delay_ms(120);

// 电源控制
LCD_WriteRegValue(0xC0, 0x1000);   // POWER_CONTROL_1
LCD_WriteRegValue(0xC1, 0x0005);   // POWER_CONTROL_2
LCD_WriteRegValue(0xC2, 0x01C0);   // POWER_CONTROL_3 (VCOM)
LCD_WriteRegValue(0xC5, 0x0086);   // VCOM_CONTROL

// 帧率控制
LCD_WriteRegValue(0xB0, 0x00);     // 内部帧率
LCD_WriteRegValue(0xB1, 0x00B0);   // 帧率 = 70Hz
LCD_WriteRegValue(0xB4, 0x0002);   // 显示反相控制

// Gamma 校正
LCD_WriteRegValue(0xE0, 0x00);     // Positive Gamma
// ... 完整 Gamma 设置约 15 个寄存器

// 显示控制
LCD_WriteRegValue(0x36, 0x0048);   // 内存访问控制 (旋转/镜像)
LCD_WriteRegValue(0x3A, 0x0055);   // 像素格式: 16-bit (RGB565)

// 退出睡眠
LCD_WriteRegValue(0x11, 0x0000);   // SLEEP_OUT
Delay_ms(120);

// 开启显示
LCD_WriteRegValue(0x29, 0x0000);   // DISPLAY_ON

}


#### 绘图函数

c /* 设置坐标窗口 */ void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {

LCD_WriteRegValue(0x2A, (x1 >> 8) & 0xFF);    // 列地址起始高8位
LCD_WriteRegValue(0x2A, x1 & 0xFF);            // 列地址起始低8位
LCD_WriteRegValue(0x2A, (x2 >> 8) & 0xFF);    // 列地址结束高8位
LCD_WriteRegValue(0x2A, x2 & 0xFF);            // 列地址结束低8位
LCD_WriteRegValue(0x2B, (y1 >> 8) & 0xFF);    // 行地址起始高8位
LCD_WriteRegValue(0x2B, y1 & 0xFF);            // 行地址起始低8位
LCD_WriteRegValue(0x2B, (y2 >> 8) & 0xFF);    // 行地址结束高8位
LCD_WriteRegValue(0x2B, y2 & 0xFF);            // 行地址结束低8位
LCD_WriteReg(0x2C);                            // 内存写命令

}

/* 画点 */ void LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color) {

LCD_SetWindow(x, y, x, y);
LCD_WriteData(color);

}

/* 全屏填充 */ void LCD_Fill(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) {

uint32_t total = (x2 - x1 + 1) * (y2 - y1 + 1);
LCD_SetWindow(x1, y1, x2, y2);
for (uint32_t i = 0; i < total; i++)
    LCD_WriteData(color);

}

/* 清屏 */ void LCD_Clear(uint16_t color) {

LCD_Fill(0, 0, 319, 479, color);

}

/* 画线(Bresenham 算法) */ void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) {

int16_t dx = (x2 > x1) ? (x2 - x1) : (x1 - x2);
int16_t dy = (y2 > y1) ? (y2 - y1) : (y1 - y2);
int16_t sx = (x2 > x1) ? 1 : -1;
int16_t sy = (y2 > y1) ? 1 : -1;
int16_t err = dx - dy;

while (1) {
    LCD_DrawPoint(x1, y1, color);
    if (x1 == x2 && y1 == y2) break;
    int16_t e2 = 2 * err;
    if (e2 > -dy) { err -= dy; x1 += sx; }
    if (e2 < dx)  { err += dx; y1 += sy; }
}

}

/* 画圆(Bresenham 算法) */ void LCD_DrawCircle(uint16_t cx, uint16_t cy, uint16_t r, uint16_t color) {

int16_t x = 0, y = r;
int16_t d = 3 - 2 * r;
while (x <= y) {
    LCD_DrawPoint(cx + x, cy + y, color);
    LCD_DrawPoint(cx - x, cy + y, color);
    LCD_DrawPoint(cx + x, cy - y, color);
    LCD_DrawPoint(cx - x, cy - y, color);
    LCD_DrawPoint(cx + y, cy + x, color);
    LCD_DrawPoint(cx - y, cy + x, color);
    LCD_DrawPoint(cx + y, cy - x, color);
    LCD_DrawPoint(cx - y, cy - x, color);
    if (d < 0) d += 4 * x + 6;
    else { d += 4 * (x - y) + 10; y--; }
    x++;
}

}


#### 字符显示

c /* 显示 ASCII 字符(8×16 点阵) */ void LCD_ShowChar(uint16_t x, uint16_t y, char ch, uint16_t fc, uint16_t bc) {

extern const uint8_t ascii_8x16[][16];  // 字库数组

if (ch < 32 || ch > 126) ch = ' ';
uint8_t idx = ch - 32;

for (uint8_t row = 0; row < 16; row++) {
    uint8_t data = ascii_8x16[idx][row];
    for (uint8_t col = 0; col < 8; col++) {
        if (data & (0x80 >> col))
            LCD_DrawPoint(x + col, y + row, fc);   // 前景色
        else
            LCD_DrawPoint(x + col, y + row, bc);   // 背景色
    }
}

}

/* 显示字符串 */ void LCD_ShowString(uint16_t x, uint16_t y, const char *str, uint16_t fc, uint16_t bc) {

while (*str) {
    // 自动换行
    if (x > 320 - 8) { x = 0; y += 16; }
    if (y > 480 - 16) break;
    LCD_ShowChar(x, y, *str, fc, bc);
    x += 8;
    str++;
}

}

/* 显示十进制数字 */ void LCD_ShowNum(uint16_t x, uint16_t y, uint32_t num, uint8_t len, uint16_t fc, uint16_t bc) {

char buf[12];
sprintf(buf, "%*lu", len, (unsigned long)num);
LCD_ShowString(x, y, buf, fc, bc);

}

/* 显示十六进制数字 */ void LCD_ShowHexNum(uint16_t x, uint16_t y, uint32_t num, uint8_t len, uint16_t fc, uint16_t bc) {

char buf[12];
sprintf(buf, "%0*X", len, (unsigned int)num);
LCD_ShowString(x, y, buf, fc, bc);

}


### 3.6 完整初始化调用示例

c void LCD_Init(void);

int main(void) {

// 初始化 FSMC (LCD 部分)
FSMC_LCD_Init();

// 初始化 LCD 驱动
LCD_Init();

// 清屏为蓝色
LCD_Clear(0x001F);        // RGB565: 0x001F = Blue

// 绘图
LCD_DrawLine(0, 0, 319, 479, 0xFFFF);    // 白色对角线
LCD_DrawCircle(160, 240, 100, 0xFFE0);   // 黄色圆
LCD_ShowString(50, 200, "Hello STM32!", 0xFFFF, 0x0000);

while (1) {}

}


---

## 4. 标准库 FSMC 驱动

标准外设库将 FSMC 配置封装为 `FSMC_NORSRAMInit()` 等函数:

c void FSMC_LCD_StdPeriph_Init(void) {

FSMC_NORSRAMInitTypeDef FSMC_InitStructure;
FSMC_NORSRAMTimingInitTypeDef FSMC_TimingStructure;

// 时序:ADDSET=1, DATAST=2
FSMC_TimingStructure.FSMC_AddressSetupTime = 1;
FSMC_TimingStructure.FSMC_AddressHoldTime = 0;
FSMC_TimingStructure.FSMC_DataSetupTime = 2;
FSMC_TimingStructure.FSMC_BusTurnAroundDuration = 0;
FSMC_TimingStructure.FSMC_CLKDivision = 0;
FSMC_TimingStructure.FSMC_DataLatency = 0;
FSMC_TimingStructure.FSMC_AccessMode = FSMC_AccessMode_A;

// Bank1 Region4
FSMC_InitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;
FSMC_InitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
FSMC_InitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
FSMC_InitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
FSMC_InitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
FSMC_InitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_InitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_InitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_InitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_InitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_InitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
FSMC_InitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_InitStructure.FSMC_ReadWriteTimingStruct = &FSMC_TimingStructure;
FSMC_InitStructure.FSMC_WriteTimingStruct = &FSMC_TimingStructure;

FSMC_NORSRAMInit(&FSMC_InitStructure);
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);

}


---

## 5. FSMC 与 8080 时序对比

8080 并行接口的读写时序与 SRAM 的异步读写在 FSMC 视角下完全一致:

写时序(8080): CS# ──┐ ┌────

      │   │

RS ───┼───┼──── (决定命令/数据)

      │   │

WR# ───┴─┐ ┌────

         │ │

D[15:0] ──┘ └──── (有效数据)

读时序(8080): CS# ──┐ ┌────

      │   │

RS ───┼───┼────

      │   │

RD# ───┴─┐ ┌────

         │ │

D[15:0] ──┘ └──── (LCD 输出数据) ```

FSMC 将上述时序参数化为 ADDSET(地址建立时间)和 DATAST(数据建立时间),自动产生符合 8080 协议的片选、读/写使能和数据信号。


6. 常见问题

问题 可能原因 解决方法
SRAM 读写值全为 0xFF FSMC 未使能或 GPIO 未配置正确 检查 AHBENR 和 GPIO CRL/CRH
LCD 无显示 RS 地址映射错误 确认 A11 连接,检查 CMD/DATA 地址
颜色显示异常 像素格式不匹配 检查 0x3A 寄存器值(0x55 = 16-bit)
数据错位 数据总线宽度 / 字节序问题 确认 MWID 正确,检查时序 ADDSET
访问速度慢 DATAST 过大 在保证稳定的前提下减小 DATAST
LCD 花屏 初始化序列不完整 检查上电延时和寄存器配置序列