|
|
@@ -0,0 +1,644 @@
|
|
|
+# 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] |
|
|
|
+
|
|
|
+#### FSMC 初始化函数
|
|
|
+
|
|
|
+```c
|
|
|
+#include "stm32f10x_fsmc.h"
|
|
|
+
|
|
|
+void FSMC_SRAM_Init(void)
|
|
|
+{
|
|
|
+ // 1. 时钟使能
|
|
|
+ RCC->AHBENR |= RCC_AHBENR_FSMCEN; // FSMC 时钟
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_IOPDEN; // FSMC_D[0..3]
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_IOPEEN; // FSMC_D[4..15]
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_IOPFEN; // FSMC_A[0..3], FSMC_NWE, NOE
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_IOPGEN; // FSMC_A[4..18], FSMC_NE3
|
|
|
+
|
|
|
+ // 2. GPIO 配置 — 所有 FSMC 引脚设为复用推挽输出 50MHz
|
|
|
+ // 数据线 PD0~PD15
|
|
|
+ GPIO_SetBits(GPIOD, 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)
|
|
|
+ // BCR: 片选控制
|
|
|
+ FSMC_Bank1->BTCR[4] = 0; // 先清零
|
|
|
+ FSMC_Bank1->BTCR[4] |= FSMC_BCR1_MBKEN; // 使能存储器 bank
|
|
|
+ FSMC_Bank1->BTCR[4] &= ~FSMC_BCR1_MTYP; // MTYP = 00 (SRAM)
|
|
|
+ FSMC_Bank1->BTCR[4] |= FSMC_BCR1_MWID_1; // MWID = 16 位
|
|
|
+ FSMC_Bank1->BTCR[4] &= ~FSMC_BCR1_MWID_0;
|
|
|
+ FSMC_Bank1->BTCR[4] |= FSMC_BCR1_WREN; // 写使能
|
|
|
+
|
|
|
+ // BTR: 时序
|
|
|
+ FSMC_Bank1->BTCR[5] = 0;
|
|
|
+ FSMC_Bank1->BTCR[5] |= (0 << 0); // ADDSET = 0 (地址建立时间)
|
|
|
+ FSMC_Bank1->BTCR[5] |= (3 << 8); // DATAST = 3 (数据建立时间)
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### GPIO 初始化详细实现
|
|
|
+
|
|
|
+```c
|
|
|
+void FSMC_GPIO_Init(void)
|
|
|
+{
|
|
|
+ // PD0~PD7 (数据低字节) — CRL
|
|
|
+ GPIOD->CRL = 0xBBBBBBBB; // 复用推挽输出 50MHz
|
|
|
+
|
|
|
+ // PD8~PD15 (数据高字节) — CRH
|
|
|
+ GPIOD->CRH = 0xBBBBBBBB;
|
|
|
+
|
|
|
+ // PD4 (NOE), PD5 (NWE)
|
|
|
+ GPIOD->CRL |= 0x00BB0000; // 复用推挽
|
|
|
+
|
|
|
+ // PE0, PE1 (NBL0, NBL1)
|
|
|
+ GPIOE->CRL = 0xBBBBBBBB;
|
|
|
+
|
|
|
+ // PF0~PF3 (地址低 4 位)
|
|
|
+ GPIOF->CRL = 0xBBBBBBBB;
|
|
|
+
|
|
|
+ // PG0~PG3 (地址线)
|
|
|
+ // PG4~PG9 (地址线)
|
|
|
+ // PG10 (NE3)
|
|
|
+ // PG12~PG15 (地址线)
|
|
|
+ GPIOG->CRL = 0xBBBBBBBB;
|
|
|
+ GPIOG->CRH = 0xBBBBBBBB; // 包含 NE3
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+> **注意**:`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;
|
|
|
+
|
|
|
+ // PD 数据线
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_IOPDEN;
|
|
|
+ GPIOD->CRL = 0xBBBBBBBB;
|
|
|
+ GPIOD->CRH = 0xBBBBBBBB;
|
|
|
+
|
|
|
+ // ... 类似配置所有 FSMC 引脚
|
|
|
+
|
|
|
+ // FSMC 控制寄存器
|
|
|
+ FSMC_Bank1->BTCR[4] = (1<<0) | (1<<1) | (1<<4) | (1<<12);
|
|
|
+ // Bit0=MBKEN, Bit1=MTYP0=0(SRAM), Bit4=MWID=16bit, Bit12=WREN
|
|
|
+
|
|
|
+ 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
|
|
|
+#define LCD_BASE ((uint32_t)0x6C000000)
|
|
|
+#define LCD_CMD_ADDR (LCD_BASE) // RS=0: 命令
|
|
|
+#define LCD_DATA_ADDR (LCD_BASE + 0x00000800) // RS=1: 数据
|
|
|
+
|
|
|
+// 命令/数据写函数
|
|
|
+#define LCD_WriteCmd(cmd) (*(volatile uint16_t *)LCD_CMD_ADDR = (cmd))
|
|
|
+#define LCD_WriteData(data) (*(volatile uint16_t *)LCD_DATA_ADDR = (data))
|
|
|
+
|
|
|
+// 读数据
|
|
|
+#define LCD_ReadData() (*(volatile uint16_t *)LCD_DATA_ADDR)
|
|
|
+```
|
|
|
+
|
|
|
+**原理**: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;
|
|
|
+ // ... GPIO 配置同 SRAM 部分
|
|
|
+
|
|
|
+ // 使用 Bank1 Region4 (NE4)
|
|
|
+ FSMC_Bank1->BTCR[6] = 0;
|
|
|
+ FSMC_Bank1->BTCR[6] |= FSMC_BCR1_MBKEN; // 使能 bank
|
|
|
+ FSMC_Bank1->BTCR[6] &= ~FSMC_BCR1_MTYP; // MTYP=00(SRAM)
|
|
|
+ FSMC_Bank1->BTCR[6] |= FSMC_BCR1_MWID_1; // MWID=16bit
|
|
|
+ FSMC_Bank1->BTCR[6] &= ~FSMC_BCR1_MWID_0;
|
|
|
+ FSMC_Bank1->BTCR[6] |= FSMC_BCR1_WREN; // 写使能
|
|
|
+
|
|
|
+ // 时序:LCD 通常较慢
|
|
|
+ FSMC_Bank1->BTCR[7] = 0;
|
|
|
+ FSMC_Bank1->BTCR[7] |= (1 << 0); // ADDSET = 1
|
|
|
+ FSMC_Bank1->BTCR[7] |= (2 << 8); // DATAST = 2
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 3.5 LCD 驱动函数(尚硅谷风格)
|
|
|
+
|
|
|
+#### 基本写操作
|
|
|
+
|
|
|
+```c
|
|
|
+/* 命令和数据写入 */
|
|
|
+void LCD_WriteReg(uint16_t reg)
|
|
|
+{
|
|
|
+ *(volatile uint16_t *)LCD_CMD_ADDR = reg;
|
|
|
+}
|
|
|
+
|
|
|
+void LCD_WriteData(uint16_t data)
|
|
|
+{
|
|
|
+ *(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 需要先输出一个读周期)
|
|
|
+ volatile uint16_t dummy = *(volatile uint16_t *)LCD_DATA_ADDR;
|
|
|
+ (void)dummy;
|
|
|
+ // 再读真实数据
|
|
|
+ return *(volatile uint16_t *)LCD_DATA_ADDR;
|
|
|
+}
|
|
|
+
|
|
|
+uint16_t LCD_ReadReg(uint16_t reg)
|
|
|
+{
|
|
|
+ LCD_WriteReg(reg);
|
|
|
+ return LCD_ReadData();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 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 花屏 | 初始化序列不完整 | 检查上电延时和寄存器配置序列 |
|