基于尚硅谷(进阶篇·第6/7章)综合整理
FSMC(Flexible Static Memory Controller)是 STM32F10x 的可变静态存储控制器,可以连接 NOR Flash、PSRAM、SRAM、NAND Flash 和 PC Card。
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 |
┌───────────┐
HCLK ──────────────────→│ FSMC │
│ │
A[25:0] / D[15:0] ←───→│ FSMC_NE ├────→ 片选
│ FSMC_NWE ├────→ 写使能
│ FSMC_NOE ├────→ 读使能
│ FSMC_NBL ├────→ 字节掩码
└───────────┘
| 参数 | 规格 |
|---|---|
| 容量 | 512K × 16bit = 1MB |
| 接口 | 并行异步 SRAM |
| 数据总线 | 16 位 |
| 地址总线 | 19 位(A0~A18) |
| 访问时间 | 45ns/55ns |
| 工作电压 | 2.5V ~ 3.6V |
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# (高字节掩码)
FSMC Bank1 的配置通过两组寄存器完成:
每个 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 协议的片选、读/写使能和数据信号。
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| SRAM 读写值全为 0xFF | FSMC 未使能或 GPIO 未配置正确 | 检查 AHBENR 和 GPIO CRL/CRH |
| LCD 无显示 | RS 地址映射错误 | 确认 A11 连接,检查 CMD/DATA 地址 |
| 颜色显示异常 | 像素格式不匹配 | 检查 0x3A 寄存器值(0x55 = 16-bit) |
| 数据错位 | 数据总线宽度 / 字节序问题 | 确认 MWID 正确,检查时序 ADDSET |
| 访问速度慢 | DATAST 过大 | 在保证稳定的前提下减小 DATAST |
| LCD 花屏 | 初始化序列不完整 | 检查上电延时和寄存器配置序列 |