1. STM32 参考实现说明
默认参考工程基于 STM32F103 实现,4 个接口分别借助 STM32 的 SPI 外设与 FSMC 外设完成。下文逐一说明实现方式与选型原因,便于移植到其他平台时对照替换。下载代码
1.1 SPI 接口(spi.c)
实现方式: 使用 STM32 硬件 SPI2 外设,由 SPI2_Init 完成波特率、CPOL/CPHA、数据位等配置;CS / RS / BUSY 三根控制线由 GPIO_INIT 初始化为普通 GPIO。数据收发分别调用 SPI2_WriteByte 与 SPI2_ReadDyte,按字节逐个发送或读取。
关键代码:
GPIO 与 SPI 外设初始化(user_change 区):
static void spi_gpio_init(void)
{
GPIO_INIT();
SPI2_Init();
}CS / RS 控制线电平设置:
static void spi_rs_set_value(u8 value)
{
if(value == 0)RS0
else RS1
}
static void spi_cs_set_value(u8 value)
{
if(value == 0)CS_L();
else CS_H();
}按字节发送 / 读取:
static void spi_writebytes(u8* byte, u32 bytelen)
{
u32 i;
for(i=0;i<bytelen;i++)
{
SPI2_WriteByte(*byte);
byte++;
}
}
static void spi_readbytes(u8 register_address, u8* data, u32 datalen)
{
u32 i;
spi_wait_busy_sign();
for(i=0; i<datalen; i++)
{
spi_rs_set_value(0);
spi_cs_set_value(0);
spi_writebytes(®ister_address, 1);
spi_rs_set_value(1);
*data = SPI2_ReadDyte();
data++;
}
}选用原因:
- 4-wire SPI 是串行协议,时序对 CPOL/CPHA 相位敏感,硬件 SPI 可在时钟沿精确采样,避免软件位翻转带来的时序抖动。
- 硬件 SPI 可达 fPCLK/2 的最高速率(F103 上 SPI2 在 APB1,最高约 18 MHz),且支持 DMA,刷屏吞吐远高于 GPIO 模拟。
- STM32 SPI 外设自带发送缓冲与 RXNE 标志,单字节收发时序精确;spi_wait_busy_sign 轮询 LCD 的 BUSY 引脚信号,避免丢包导致显示异常。
1.2 QSPI 接口(qspi.c)
实现方式: STM32F1 系列无原生 QSPI 控制器,参考工程用 FSMC 模拟 QSPI。FSMC_GPIO_Init 与 FSMC_Config 将 FSMC 的 16 根数据线(D0~D15)全部初始化并配置为 NOR/PSRAM 模式;QSPI 协议使用 4 根 IO 线(IO0~IO3),硬件上由 FSMC 的 4 根数据线(如 D0~D3)映射承载,代码层面对数据进行位拆分以匹配 QSPI 的单线/4 线传输阶段。
- qspi_send_one_line_data:将 1 字节按位拆为 8 个 bit,每次向 FSMC 写入 1 位(单线模式),用于命令/地址段,此阶段只用 1 根 IO 线。
- qspi_send_four_line_data:将每字节拆为高 4 位与低 4 位两次写入(4 线模式),用于像素数据,此阶段使用全部 4 根 IO 线并行传输。
- qspi_read_bytes:单线逐位读取 8 次拼装成 1 字节。
关键代码:
FSMC 初始化(用 FSMC 模拟 QSPI 总线):
static void qspi_gpio_init(void)
{
GPIO_INIT();
FSMC_GPIO_Init();
FSMC_Config();
}单线模式发送(命令/地址段,按位拆分 8 次写入):
static void qspi_send_one_line_data(u8 senddata)
{
int ii = 0;
u8 data[8] ={0};
for(ii = 0;ii<8;ii++)
{
data[ii] = (senddata >> (7 - ii))&0x01;
}
for(ii = 0;ii<8;ii++)
{
FSMC_Write_Data(data[ii]);
}
}4 线模式发送(像素数据,高/低 4 位分两次写入):
static void qspi_send_four_line_data(u8* data, u32 datalen)
{
u32 i;
for(i=0;i<datalen;i++)
{
FSMC_Write_Data((*data >> 4) & 0x0f);
FSMC_Write_Data(*data & 0x0f);
data++;
}
}单线逐位读取拼装成字节:
static void qspi_read_bytes(u8* data, u32 datalen)
{
u8 dat;
u32 i ;
int j;
for(i=0; i<datalen; i++)
{
dat = 0;
for (j = 7; j >= 0; j--) {
dat |= (FSMC_Read_Bit() << j);
}
*data =dat;
data++;
}
}命令发送时序(封装单线发送):
static void qspi_writecmd(u8 cmd)
{
qspi_wait_busy_sign();
qspi_set_cs_value(0);
qspi_send_one_line_data(0x02);
qspi_send_one_line_data(0x00);
qspi_send_one_line_data(cmd);
qspi_send_one_line_data(0x00);
}选用原因:
- STM32F1 不具备专用 QSPI 外设,而 QSPI 协议要求精确的时钟边沿与 1/4 线切换,纯 GPIO 翻转时序难以稳定。
- FSMC 提供地址/数据分离的总线时序,可把 4 根 IO 线当作"1 位存储器数据线"操作,利用其硬件时序引擎保证每位翻转间隔一致,比 GPIO 软翻转更接近 QSPI 规范。
- 借助 FSMC 的地址映射特性,后续可平滑替换为带原生 QSPI 的 STM32F4/F7/L4/H7,仅需把 user_change 内的位拆分调用替换为 HAL_QSPI_Transmit 之类 API。
1.3 8080 8 位并口(8080_8b.c)
实现方式: 使用 FSMC 的 NOR/PSRAM 模式,FSMC_GPIO_Init 与 FSMC_Config 同样将 16 根数据线(D0~D15)全部初始化并完成 FSMC 时序参数(地址建立/数据建立/保持时间)配置,把命令地址与数据地址映射到不同 FSMC Bank 偏移。在 8080 8 位模式下,每次传输仅使用 D0~D7 共 8 根线承载一字节数据,D8~D15 保持空闲。
- 命令写入:先用 GPIO 拉低 D/C(_8080_8b_set_dc_value(0))与 CS,再调用 FSMC_Write_Cmd(cmd) 向命令地址写入一字节,FSMC 负责数据总线 D0~D7 与 /WR 写时序。
- 数据写入:先用 GPIO 拉高 D/C(_8080_8b_set_dc_value(1)),再调用 FSMC_Write_Data(data) 向数据地址写入,FSMC 负责数据总线与 /WR 写时序。
- 数据读取:由 GPIO 拉低 RD(_8080_8b_set_rd_value(0))触发 LCD 输出,再调用 FSMC_Read_Data 读取一字节,读完拉高 RD;/RD 的拉低/拉高由 GPIO 手动控制。
- CS、D/C、RD 三根控制线均由独立 GPIO 控制(CS_L/CS_H、DC_L/DC_H、RD_L/RD_H),FSMC 仅承担数据总线的读写时序。
关键代码:
FSMC 与控制线初始化:
static void _8080_8b_gpio_Init(void)
{
GPIO_INIT();
FSMC_GPIO_Init();
FSMC_Config();
}CS / DC / RD 控制线电平设置:
static void _8080_8b_set_cs_value(u8 value)
{
if(value==0)CS_L();
else {CS_H();}
}
static void _8080_8b_set_dc_value(u8 value)
{
if(value ==0)DC_L();
else DC_H();
}
static void _8080_8b_set_rd_value(u8 value)
{
if(value ==0){RD_L()}
else RD_H()
}命令 / 数据写入(FSMC 负责数据总线 D0~D7 与 /WR 写时序,D/C 由 GPIO 提前置位):
static void _8080_8b_write_cmd(u8 cmd)
{
FSMC_Write_Cmd(cmd);
}
static void _8080_8b_write_data(u8* data, u32 datalen)
{
u32 i;
for(i=0;i<datalen;i++)
{
FSMC_Write_Data(*data);
data++;
}
}数据读取(/RD 由 GPIO 手动拉低触发 LCD 输出,FSMC 读取数据总线一字节):
static void _8080_8b_read_bytes(u8* data, u32 datalen)
{
u32 i;
for(i=0; i<datalen; i++)
{
_8080_8b_set_rd_value(0);
*data = FSMC_Read_Data();
_8080_8b_set_rd_value(1);
data++;
}
}命令发送时序(D/C=0、CS=0):
static void _8080_8b_writecmd(u8 cmd)
{
_8080_8b_wait_busy_sign();
_8080_8b_set_dc_value(0);
_8080_8b_set_cs_value(0);
_8080_8b_write_cmd(cmd);
}选用原因:
- 8080 并口的数据总线时序与 FSMC 的 NOR/PSRAM 模式同构,FSMC 在硬件层直接生成数据总线 D0~D7 与 /WR 写时序,避免 CPU 逐拍翻转 8 根数据线;/CS、/RD、D/C 控制线由独立 GPIO 控制,仅需在收发前后拉电平即可。
- 数据吞吐随并口位宽线性提升,8 位并口单拍即可传 1 字节,比 SPI 单线快 8 倍,适合中等分辨率屏的稳定刷新。
- FSMC 将 LCD 命令/数据地址映射到线性地址空间,可像写内存一样直接对地址赋值,代码简洁。
1.4 8080 16 位并口(8080_16b.c)
实现方式: 与 8 位版本共用同一套 FSMC 配置(同样初始化 16 根数据线 D0~D15、同一 NOR/PSRAM 模式与时序参数),区别在于 8080 16 位模式下每次传输使用全部 16 根数据线承载一个半字(2 字节)。
- _8080_16b_write_data:将连续字节两两打包成 16 位半字,高位在前,通过 FSMC_Write_Data(datas) 一次写入 2 字节;奇数字节自动补齐为半字。
- _8080_16b_read_bytes:单字节粒度读取,每拍拉低 RD 后调用 FSMC_Read_Data。
- 命令/数据/CS/RD 控制时序与 8 位版本一致。
关键代码:
FSMC 与控制线初始化(与 8 位版本共用):
static void _8080_16b_gpio_init(void)
{
GPIO_INIT();
FSMC_GPIO_Init();
FSMC_Config();
}16 位数据写入(字节两两打包为半字,奇数字节补齐):
static void _8080_16b_write_data(u8* data, u32 datalen)
{
u32 i;
u16 datas;
for(i = 0; i < datalen - 1; i += 2)
{
datas = ((u16)data[i] << 8) | data[i+1];
FSMC_Write_Data(datas);
}
if(datalen % 2)
{
datas = ((u16)data[datalen-1] << 8);
FSMC_Write_Data(datas);
}
}单字节粒度读取(每拍拉低 RD 后调用 FSMC_Read_Data):
static void _8080_16b_read_bytes(u8* data, u32 datalen)
{
u32 i;
for(i=0; i<datalen; i++)
{
_8080_16b_set_rd_value(0);
*data = FSMC_Read_Data();
_8080_16b_set_rd_value(1);
data++;
}
}像素数据写入(直接走 16 位打包路径):
static void _8080_16b_writepix(u8* colorbuff, u32 len)
{
_8080_16b_write_data(colorbuff, len);
}选用原因:
- 16 位并口单拍传输 2 字节,理论吞吐较 8 位再翻倍,是 8080 接口下的最高带宽形态,适合高分辨率 / 高帧率屏(如 800×480 以上)。
- FSMC 16 位模式由硬件管理 D0~D15 数据总线与 /WR 写时序,单拍即可驱动全部 16 根数据线,避免了 GPIO 逐位翻转导致的位间偏移;/RD 仍由 GPIO 控制(RD_L/RD_H),字节两两打包为半字由软件在 _8080_16b_write_data 中完成。
- 命令与参数段仍按 8 位发送(命令字节只有 8 位有效),仅像素数据段按 16 位半字发送,既兼容控制器命令规范又最大化刷屏带宽,是 STM32 平台下 TR2S 的推荐配置。
1.5 STM32 实现与外设对照总览
| 接口源文件 | STM32 外设 | 关键 API | 外设选型原因 |
|---|---|---|---|
| interface/spi.c | 硬件 SPI2 | SPI2_Init / SPI2_WriteByte / SPI2_ReadDyte | 硬件 SPI 时序精确、速率高、支持 DMA |
| interface/qspi.c | FSMC(16 根数据线,其中 4 根映射为 QSPI 的 IO0~IO3) | FSMC_GPIO_Init / FSMC_Config / FSMC_Write_Data / FSMC_Read_Bit | F1 无原生 QSPI,FSMC 提供稳定的逐位时序 |
| interface/8080_8b.c | FSMC(16 根数据线,每次用 D0~D7 传 1 字节) | FSMC_Write_Cmd / FSMC_Write_Data / FSMC_Read_Data | FSMC 生成数据总线 D0~D7 与 /WR 写时序,/CS /RD /DC 由 GPIO 控制 |
| interface/8080_16b.c | FSMC(16 根数据线,每次用 D0~D15 传 2 字节) | FSMC_Write_Cmd / FSMC_Write_Data(u16) / FSMC_Read_Data | 16 位单拍传 2 字节,最高带宽,适合高分辨率屏 |
说明:上述实现仅作为 STM32 平台的参考,并非唯一可行方案。移植到其他 MCU 时,可按平台外设资源替换为:原生 QSPI 控制器、软件模拟GPIO、或外设 SPI;只要保持 user_change 区域内的函数签名与挂载逻辑不变,上层 API 无需改动。