软件 SPI vs 硬件 SPI 核心区别
一、本质原理
硬件 SPI
芯片自带 SPI 外设控制器 ,有专用硬件引脚(SCK、MOSI、MISO、CS),由硬件时序自动发波形,CPU 只负责发数据、收数据,不用管时钟高低电平。
软件 SPI
用普通 GPIO 口模拟 SCK、MOSI、MISO 时序,靠代码循环延时、高低电平翻转来模拟 SPI 时钟和收发逻辑,纯软件跑时序。
二、关键区别一览表
| 对比项 | 硬件 SPI | 软件 SPI |
|---|---|---|
| 引脚 | 固定专用 SPI 引脚 | 任意普通 GPIO 都能模拟,灵活 |
| 速率 | 极高,可达几 MHz~ 几十 MHz | 慢,一般几十 kHz~ 几百 kHz |
| CPU 占用 | 极低,硬件自动移位收发 | 很高,全程占用 CPU 死循环翻转电平 |
| 时序精度 | 精准稳定,无抖动 | 受系统调度、中断影响,时序有误差 |
| 多设备 | 只能用硬件 SPI 总线,CS 自己控制 | 随便多组 GPIO 模拟多路 SPI,互不干扰 |
| 兼容性 | 必须外设支持硬件 SPI | 任何单片机、任何引脚都能用 |
| 资源占用 | 占用硬件 SPI 外设资源 | 只占 GPIO,不占用硬件外设 |
| 干扰容错 | 抗干扰强,高速通信稳定 | 时序弱,高速容易丢数据、通信失败 |
三、优缺点总结
硬件 SPI
✅ 速度快、省 CPU、时序准、通信稳定❌ 引脚固定、硬件外设数量有限、不能随便换引脚
软件 SPI
✅ 引脚任意选、不占用硬件 SPI 外设、适合多片从设备分开模拟❌ 速度慢、耗 CPU、时序易受中断影响、只能低速用
四、使用场景怎么选
- 屏幕、Flash、SD 卡、高速传感器 → 优先硬件 SPI
- 引脚不够、硬件 SPI 被占用、多个低速从器件 → 用软件 SPI
- 低速简单外设(如低速 DAC、传感器),为了布线方便随便选 GPIO → 软件 SPI 就行。
纯软件 SPI 测试
一、测试屏幕背光
直接使用GPIO对屏幕背光的pin脚拉高。看屏幕效果及测量电压。
int main(void)
{
HAL_Init();
SystemClock_Config();
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); // 直接拉高
while(1) { }
}
PB1 = 3.2V,说明 GPIO 硬件完全正常,现在想用软件 SPI 绕过硬件 SPI 来验证屏幕显示。首先使用软件SPI的形式对屏幕进行颜色填充测试。
二、纯软件 SPI 测试程序说明
完全不依赖任何硬件外设------没有 SPI 初始化、没有 UART 初始化、没有 I2C 初始化,全部用 GPIO bit-bang 模拟 SPI 时序。
引脚配置(全部 GPIO 普通输出模式)
| 引脚 | 功能 | 说明 |
|---|---|---|
| PA1 | SCK | 软件模拟时钟,空闲低电平 |
| PA7 | MOSI | 软件模拟数据输出 |
| PA4 | DC | 命令/数据选择 |
| PB0 | CS | 片选,低电平有效 |
| PB1 | BLK | 背光,高电平点亮 |
执行流程
HAL_Init → SystemClock_Config → GPIO初始化 → 背光点亮 → LCD_Init → 循环填色
↑ ↑
不经过UART/SPI 纯GPIO模拟
软件代码
#include "st7735.h"
#include "main.h"
#include <stdio.h>
#include <string.h>
/*
* 纯软件SPI测试版本
* 不初始化SPI/UART/I2C,只用GPIO模拟SPI时序驱动ST7735
* 目的:排除硬件SPI外设问题,验证屏幕是否正常工作
*
* 接线:
* PA1 = SCK (软件模拟时钟)
* PA7 = MOSI (软件模拟数据)
* PA4 = DC (命令/数据选择)
* PB0 = CS (片选,低电平有效)
* PB1 = BLK (背光,高电平点亮)
*/
/* ========== 简易延时 ========== */
static void Delay_us(uint32_t us)
{
/* HSI 8MHz, 约125ns per loop */
volatile uint32_t count = us * 2U;
while(count--) { __NOP(); }
}
static void Delay_ms(uint32_t ms)
{
while(ms--) { Delay_us(1000); }
}
/* ========== 软件SPI底层 ========== */
/* 软件SPI发送1字节 (MSB first, SPI Mode 0: CPOL=0, CPHA=0) */
static void SoftSPI_WriteByte(uint8_t data)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
/* 设置数据位 (MSB first) */
if (data & 0x80)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);
else
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);
/* SCK上升沿,ST7735采样数据 */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
/* SCK下降沿,准备下一位 */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
data <<= 1;
}
}
/* 发送命令 (DC=0) */
static void LCD_WriteCmd(uint8_t cmd)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); /* DC = 0 (命令) */
SoftSPI_WriteByte(cmd);
}
/* 发送数据 (DC=1) */
static void LCD_WriteData8(uint8_t data)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); /* DC = 1 (数据) */
SoftSPI_WriteByte(data);
}
/* 发送16位数据 */
static void LCD_WriteData16(uint16_t data)
{
LCD_WriteData8((uint8_t)(data >> 8));
LCD_WriteData8((uint8_t)(data & 0xFF));
}
/* ========== ST7735初始化命令 ========== */
static void LCD_Init(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); /* CS = 0 (选中) */
/* Software Reset */
LCD_WriteCmd(0x01);
Delay_ms(150);
/* Out of Sleep */
LCD_WriteCmd(0x11);
Delay_ms(255);
/* Frame Rate Control (Normal Mode) */
LCD_WriteCmd(0xB1);
LCD_WriteData8(0x01);
LCD_WriteData8(0x2C);
LCD_WriteData8(0x2D);
/* Frame Rate Control (Idle Mode) */
LCD_WriteCmd(0xB2);
LCD_WriteData8(0x01);
LCD_WriteData8(0x2C);
LCD_WriteData8(0x2D);
/* Frame Rate Control (Partial Mode) */
LCD_WriteCmd(0xB3);
LCD_WriteData8(0x01);
LCD_WriteData8(0x2C);
LCD_WriteData8(0x2D);
LCD_WriteData8(0x01);
LCD_WriteData8(0x2C);
LCD_WriteData8(0x2D);
/* Display Inversion Control */
LCD_WriteCmd(0xB4);
LCD_WriteData8(0x07);
/* Power Control 1 */
LCD_WriteCmd(0xC0);
LCD_WriteData8(0xA2);
LCD_WriteData8(0x02);
LCD_WriteData8(0x84);
/* Power Control 2 */
LCD_WriteCmd(0xC1);
LCD_WriteData8(0xC5);
/* Power Control 3 */
LCD_WriteCmd(0xC2);
LCD_WriteData8(0x0A);
LCD_WriteData8(0x00);
/* Power Control 4 */
LCD_WriteCmd(0xC3);
LCD_WriteData8(0x8A);
LCD_WriteData8(0x2A);
/* Power Control 5 */
LCD_WriteCmd(0xC4);
LCD_WriteData8(0x8A);
LCD_WriteData8(0xEE);
/* VCOM Control */
LCD_WriteCmd(0xC5);
LCD_WriteData8(0x0E);
/* Inversion Off */
LCD_WriteCmd(0x20);
/* Memory Access Control */
LCD_WriteCmd(0x36);
LCD_WriteData8(0xC8); /* MY=1, MX=1, BGR */
/* Color Mode: 16-bit */
LCD_WriteCmd(0x3A);
LCD_WriteData8(0x05);
/* Column Address Set (80列: 0~79) */
LCD_WriteCmd(0x2A);
LCD_WriteData8(0x00);
LCD_WriteData8(0x00);
LCD_WriteData8(0x00);
LCD_WriteData8(0x4F);
/* Row Address Set (160行: 0~159) */
LCD_WriteCmd(0x2B);
LCD_WriteData8(0x00);
LCD_WriteData8(0x00);
LCD_WriteData8(0x00);
LCD_WriteData8(0x9F);
/* Inversion On (0.96寸屏需要) */
LCD_WriteCmd(0x21);
/* Gamma Correction (Positive) */
LCD_WriteCmd(0xE0);
LCD_WriteData8(0x02); LCD_WriteData8(0x1C);
LCD_WriteData8(0x07); LCD_WriteData8(0x12);
LCD_WriteData8(0x37); LCD_WriteData8(0x32);
LCD_WriteData8(0x29); LCD_WriteData8(0x2D);
LCD_WriteData8(0x29); LCD_WriteData8(0x25);
LCD_WriteData8(0x2B); LCD_WriteData8(0x39);
LCD_WriteData8(0x00); LCD_WriteData8(0x01);
LCD_WriteData8(0x03); LCD_WriteData8(0x10);
/* Gamma Correction (Negative) */
LCD_WriteCmd(0xE1);
LCD_WriteData8(0x03); LCD_WriteData8(0x1D);
LCD_WriteData8(0x07); LCD_WriteData8(0x06);
LCD_WriteData8(0x2E); LCD_WriteData8(0x2C);
LCD_WriteData8(0x29); LCD_WriteData8(0x2D);
LCD_WriteData8(0x2E); LCD_WriteData8(0x2E);
LCD_WriteData8(0x37); LCD_WriteData8(0x3F);
LCD_WriteData8(0x00); LCD_WriteData8(0x00);
LCD_WriteData8(0x02); LCD_WriteData8(0x10);
/* Normal Display On */
LCD_WriteCmd(0x13);
Delay_ms(10);
/* Display On */
LCD_WriteCmd(0x29);
Delay_ms(100);
}
/* ========== 全屏填充颜色 ========== */
static void LCD_FillColor(uint16_t color)
{
uint16_t i;
uint32_t total = 80U * 160U; /* 80x160 = 12800 像素 */
/* 设置写RAM窗口 */
LCD_WriteCmd(0x2A);
LCD_WriteData8(0x00); LCD_WriteData8(0x00); /* XS = 0 */
LCD_WriteData8(0x00); LCD_WriteData8(0x4F); /* XE = 79 */
LCD_WriteCmd(0x2B);
LCD_WriteData8(0x00); LCD_WriteData8(0x00); /* YS = 0 */
LCD_WriteData8(0x00); LCD_WriteData8(0x9F); /* YE = 159 */
LCD_WriteCmd(0x2C); /* 写RAM */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); /* DC = 1 */
for (i = 0; i < total; i++)
{
SoftSPI_WriteByte((uint8_t)(color >> 8));
SoftSPI_WriteByte((uint8_t)(color & 0xFF));
}
}
/* ========== 系统时钟 ========== */
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = 0x01U;
RCC_OscInitStruct.HSIState = 0x01U;
RCC_OscInitStruct.HSIDiv = 0x00U;
RCC_OscInitStruct.HSICalibrationValue = 0x00U;
RCC_OscInitStruct.LSIState = 0x00U;
if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { while(1); }
RCC_ClkInitStruct.ClockType = 0x07U;
RCC_ClkInitStruct.SYSCLKSource = 0x00U;
RCC_ClkInitStruct.AHBCLKDivider = 0x00U;
RCC_ClkInitStruct.APB1CLKDivider = 0x00U;
if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, 0x00U) != HAL_OK) { while(1); }
}
/* ========== 主函数 ========== */
int main(void)
{
HAL_Init();
SystemClock_Config();
/* 使能GPIO时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
/* PA1(SCK)、PA4(DC)、PA7(MOSI) → 推挽输出 */
GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_7;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* PB0(CS)、PB1(BLK) → 推挽输出 */
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* 初始电平 */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); /* BLK = 1 (点亮背光) */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); /* CS = 1 (未选中) */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); /* DC = 1 */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); /* SCK = 0 (空闲低) */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET); /* MOSI = 0 */
/* 初始化屏幕 */
LCD_Init();
/* 循环切换颜色 */
while (1)
{
LCD_FillColor(0xF800); /* 红色 */
Delay_ms(1000);
LCD_FillColor(0x07E0); /* 绿色 */
Delay_ms(1000);
LCD_FillColor(0x001F); /* 蓝色 */
Delay_ms(1000);
LCD_FillColor(0xFFFF); /* 白色 */
Delay_ms(1000);
}
}
显示结果
如下,有两个问题:
- 顶部有彩色噪点/花屏
- 下半部分显示纯色块的颜色不符合预期 ,应该是红、绿、蓝、白,实际是黄、玫红、浅蓝、黑

解决颜色错乱问题
屏幕确实在显示颜色(不是黑屏),说明数据传输是通的 。问题是颜色的通道分配。当前代码先 0x20(INVOFF) 再 0x21(INVON),最终反色是开启的。反色 + BGR 通道交换会组合出非常混乱的颜色:
修改反色 + BGR 通道交换两个选项进行测试。
| 项目 | 现在(颜色错但能显示) | 测试 |
|---|---|---|
| MADCTL | 0xC8 (BGR=1) | 0xC0 (BGR=0) |
| 反色 | 0x21 (ON) | 0x20 (OFF) |
| 颜色 | 原始RGB565值 | 原始RGB565值 |
| 偏移/方向 | 26/1, MY=1, MX=1 | 26/1, MY=1, MX=1 |
烧录后观察颜色,这次的关键是 MADCTL=0xC0 + 关反色 的组合。
/* Inversion Off - 先不反色,测试基础颜色是否正确 */
// LCD_WriteCmd(0x20); /* 已在上面发送 */
/* Inversion Off - 先不反色,测试基础颜色 */
LCD_WriteCmd(0x20); /* Inversion Off */
/* Memory Access Control */
LCD_WriteCmd(0x36);
LCD_WriteData8(0xC0); /* MY=1, MX=1, MV=0, ML=0, BGR=0 */
结果:显示的颜色变为蓝色、绿色、红色、白色,而不是预期的红、绿、蓝、白色
重大突破!现在我们知道了两件事:
- 白色正确了! --- MADCTL=0xC0 + 关反色的配置是对的
- R 和 B 通道反了 --- 发红(0xF800)显示蓝,发蓝(0x001F)显示红,绿色正确
这说明 ST7735S 内部显存就是 BGR 顺序,我们需要 BGR=1 来让芯片自动交换。但之前 0xC8(BGR=1)时白色是黑的......因为那时有反色开启!
所以根因是:之前的 0xC8 + 反色 = 反色抵消了 BGR 交换 + 反白了 。正确组合应该是 MADCTL=0xC8 + 关反色:
现在配置是:
| 项目 | 值 |
|---|---|
| MADCTL | 0xC8 (MY=1, MX=1, BGR=1) |
| 反色 | OFF (0x20) |
| 颜色 | 标准 RGB565 (红0xF800, 绿0x07E0, 蓝0x001F, 白0xFFFF) |
解决顶部有彩色噪点/花屏
换个思路------用最简单可靠的方法:直接写满整个内部显存 132×162,不设置偏移,让所有像素都被覆盖。这样无论真实偏移是多少,有效显示区域一定被刷到:
/* ========== 全屏填充颜色 ========== */
static void LCD_FillColor(uint16_t color)
{
uint16_t i;
uint32_t total = 132U * 162U; /* 写满整个内部显存,避免偏移计算误差 */
/* 设置写RAM窗口 (内部显存全范围 132x162) */
LCD_WriteCmd(0x2A); /* Column Address Set */
LCD_WriteData8(0x00); LCD_WriteData8(0x00); /* XS = 0 */
LCD_WriteData8(0x00); LCD_WriteData8(0x83); /* XE = 131 */
LCD_WriteCmd(0x2B); /* Row Address Set */
LCD_WriteData8(0x00); LCD_WriteData8(0x00); /* YS = 0 */
LCD_WriteData8(0x00); LCD_WriteData8(0xA1); /* YE = 161 */
LCD_WriteCmd(0x2C); /* Memory Write */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); /* DC = 1 */
for (i = 0; i < total; i++)
{
SoftSPI_WriteByte((uint8_t)(color >> 8));
SoftSPI_WriteByte((uint8_t)(color & 0xFF));
}
}
这样 LCD_FillColor 直接写满整个内部 132×162 显存(21384 像素),无论真实偏移是多少,物理显示区域一定会被完整覆盖。代价是每个颜色刷新稍慢一点(多了 854 个像素),但 500ms 间隔完全够用。
初始化里的 CASET/RASET 不影响显示,可以不动。烧录以后,马赛克完全消失。
其他问题
屏幕没有填充颜色,背光一直闪
最可能的原因:MADCTL=0x00 导致屏幕方向完全反转 (之前的 MY=1, MX=1 控制的是行列扫描方向),初始化序列中 CASET/RASET 的设置可能因为方向变化导致芯片内部指针卡死,后续的 0x2C 写 RAM 命令无法正常工作。
0xC0(MY=1, MX=1, BGR=0)时方向是正常的,我改成 0x00 把 MY 和 MX 也清掉了,导致屏幕扫描方向完全错误,RAM 写入地址和 CASET/RASET 不匹配,数据写不进去。
最终代码
#include "st7735.h"
#include "main.h"
#include <stdio.h>
#include <string.h>
/*
* 纯软件SPI测试版本
* 不初始化SPI/UART/I2C,只用GPIO模拟SPI时序驱动ST7735
* 目的:排除硬件SPI外设问题,验证屏幕是否正常工作
*
* 接线:
* PA1 = SCK (软件模拟时钟)
* PA7 = MOSI (软件模拟数据)
* PA4 = DC (命令/数据选择)
* PB0 = CS (片选,低电平有效)
* PB1 = BLK (背光,高电平点亮)
*/
/* ========== 简易延时 ========== */
static void Delay_us(uint32_t us)
{
/* HSI 8MHz, 约125ns per loop */
volatile uint32_t count = us * 2U;
while(count--) { __NOP(); }
}
static void Delay_ms(uint32_t ms)
{
while(ms--) { Delay_us(1000); }
}
/* ========== 软件SPI底层 ========== */
/* 软件SPI发送1字节 (MSB first, SPI Mode 0: CPOL=0, CPHA=0) */
static void SoftSPI_WriteByte(uint8_t data)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
/* 设置数据位 (MSB first) */
if (data & 0x80)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);
else
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);
/* SCK上升沿,ST7735采样数据 */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
/* SCK下降沿,准备下一位 */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
data <<= 1;
}
}
/* 发送命令 (DC=0) */
static void LCD_WriteCmd(uint8_t cmd)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); /* DC = 0 (命令) */
SoftSPI_WriteByte(cmd);
}
/* 发送数据 (DC=1) */
static void LCD_WriteData8(uint8_t data)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); /* DC = 1 (数据) */
SoftSPI_WriteByte(data);
}
/* 发送16位数据 */
static void LCD_WriteData16(uint16_t data)
{
LCD_WriteData8((uint8_t)(data >> 8));
LCD_WriteData8((uint8_t)(data & 0xFF));
}
/* ========== ST7735初始化命令 ========== */
static void LCD_Init(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); /* CS = 0 (选中) */
/* Software Reset */
LCD_WriteCmd(0x01);
Delay_ms(150);
/* Out of Sleep */
LCD_WriteCmd(0x11);
Delay_ms(255);
/* Frame Rate Control (Normal Mode) */
LCD_WriteCmd(0xB1);
LCD_WriteData8(0x01);
LCD_WriteData8(0x2C);
LCD_WriteData8(0x2D);
/* Frame Rate Control (Idle Mode) */
LCD_WriteCmd(0xB2);
LCD_WriteData8(0x01);
LCD_WriteData8(0x2C);
LCD_WriteData8(0x2D);
/* Frame Rate Control (Partial Mode) */
LCD_WriteCmd(0xB3);
LCD_WriteData8(0x01);
LCD_WriteData8(0x2C);
LCD_WriteData8(0x2D);
LCD_WriteData8(0x01);
LCD_WriteData8(0x2C);
LCD_WriteData8(0x2D);
/* Display Inversion Control */
LCD_WriteCmd(0xB4);
LCD_WriteData8(0x07);
/* Power Control 1 */
LCD_WriteCmd(0xC0);
LCD_WriteData8(0xA2);
LCD_WriteData8(0x02);
LCD_WriteData8(0x84);
/* Power Control 2 */
LCD_WriteCmd(0xC1);
LCD_WriteData8(0xC5);
/* Power Control 3 */
LCD_WriteCmd(0xC2);
LCD_WriteData8(0x0A);
LCD_WriteData8(0x00);
/* Power Control 4 */
LCD_WriteCmd(0xC3);
LCD_WriteData8(0x8A);
LCD_WriteData8(0x2A);
/* Power Control 5 */
LCD_WriteCmd(0xC4);
LCD_WriteData8(0x8A);
LCD_WriteData8(0xEE);
/* VCOM Control */
LCD_WriteCmd(0xC5);
LCD_WriteData8(0x0E);
/* Inversion Off - 先不反色,测试基础颜色 */
LCD_WriteCmd(0x20); /* Inversion Off */
/* Memory Access Control */
LCD_WriteCmd(0x36);
LCD_WriteData8(0xC8); /* MY=1, MX=1, MV=0, ML=0, BGR=1 - 自动交换R/B通道 */
/* Color Mode: 16-bit */
LCD_WriteCmd(0x3A);
LCD_WriteData8(0x05);
/* Column Address Set (偏移24, 宽82列: 24~105) */
LCD_WriteCmd(0x2A);
LCD_WriteData8(0x00);
LCD_WriteData8(0x18); /* XS = 24 */
LCD_WriteData8(0x00);
LCD_WriteData8(0x69); /* XE = 105 */
/* Row Address Set (偏移1, 160行: 1~160) */
LCD_WriteCmd(0x2B);
LCD_WriteData8(0x00);
LCD_WriteData8(0x01); /* YS = 1 */
LCD_WriteData8(0x00);
LCD_WriteData8(0xA0); /* YE = 160 */
/* Gamma Correction (Positive) */
LCD_WriteCmd(0xE0);
LCD_WriteData8(0x02); LCD_WriteData8(0x1C);
LCD_WriteData8(0x07); LCD_WriteData8(0x12);
LCD_WriteData8(0x37); LCD_WriteData8(0x32);
LCD_WriteData8(0x29); LCD_WriteData8(0x2D);
LCD_WriteData8(0x29); LCD_WriteData8(0x25);
LCD_WriteData8(0x2B); LCD_WriteData8(0x39);
LCD_WriteData8(0x00); LCD_WriteData8(0x01);
LCD_WriteData8(0x03); LCD_WriteData8(0x10);
/* Gamma Correction (Negative) */
LCD_WriteCmd(0xE1);
LCD_WriteData8(0x03); LCD_WriteData8(0x1D);
LCD_WriteData8(0x07); LCD_WriteData8(0x06);
LCD_WriteData8(0x2E); LCD_WriteData8(0x2C);
LCD_WriteData8(0x29); LCD_WriteData8(0x2D);
LCD_WriteData8(0x2E); LCD_WriteData8(0x2E);
LCD_WriteData8(0x37); LCD_WriteData8(0x3F);
LCD_WriteData8(0x00); LCD_WriteData8(0x00);
LCD_WriteData8(0x02); LCD_WriteData8(0x10);
/* Normal Display On */
LCD_WriteCmd(0x13);
Delay_ms(10);
/* Display On */
LCD_WriteCmd(0x29);
Delay_ms(100);
/* 关闭睡眠模式,防止自动息屏 */
LCD_WriteCmd(0x38); /* Idle Mode Off */
Delay_ms(10);
}
/* ========== 全屏填充颜色 ========== */
static void LCD_FillColor(uint16_t color)
{
uint16_t i;
uint32_t total = 132U * 162U; /* 写满整个内部显存,避免偏移计算误差 */
/* 设置写RAM窗口 (内部显存全范围 132x162) */
LCD_WriteCmd(0x2A); /* Column Address Set */
LCD_WriteData8(0x00); LCD_WriteData8(0x00); /* XS = 0 */
LCD_WriteData8(0x00); LCD_WriteData8(0x83); /* XE = 131 */
LCD_WriteCmd(0x2B); /* Row Address Set */
LCD_WriteData8(0x00); LCD_WriteData8(0x00); /* YS = 0 */
LCD_WriteData8(0x00); LCD_WriteData8(0xA1); /* YE = 161 */
LCD_WriteCmd(0x2C); /* Memory Write */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); /* DC = 1 */
for (i = 0; i < total; i++)
{
SoftSPI_WriteByte((uint8_t)(color >> 8));
SoftSPI_WriteByte((uint8_t)(color & 0xFF));
}
}
/* ========== 系统时钟 ========== */
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = 0x01U;
RCC_OscInitStruct.HSIState = 0x01U;
RCC_OscInitStruct.HSIDiv = 0x00U;
RCC_OscInitStruct.HSICalibrationValue = 0x00U;
RCC_OscInitStruct.LSIState = 0x00U;
if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { while(1); }
RCC_ClkInitStruct.ClockType = 0x07U;
RCC_ClkInitStruct.SYSCLKSource = 0x00U;
RCC_ClkInitStruct.AHBCLKDivider = 0x00U;
RCC_ClkInitStruct.APB1CLKDivider = 0x00U;
if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, 0x00U) != HAL_OK) { while(1); }
}
/* ========== 主函数 ========== */
int main(void)
{
HAL_Init();
SystemClock_Config();
/* 使能GPIO时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
/* PA1(SCK)、PA4(DC)、PA7(MOSI) → 推挽输出 */
GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_7;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* PB0(CS)、PB1(BLK) → 推挽输出 */
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* 初始电平 */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); /* BLK = 1 (点亮背光) */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); /* CS = 1 (未选中) */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); /* DC = 1 */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); /* SCK = 0 (空闲低) */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET); /* MOSI = 0 */
/* 初始化屏幕 */
LCD_Init();
/* 循环切换颜色 - 每500ms刷新一次,防止息屏 */
while (1)
{
LCD_FillColor(0xF800); /* 红色 RGB565 */
Delay_ms(500);
LCD_FillColor(0x07E0); /* 绿色 RGB565 */
Delay_ms(500);
LCD_FillColor(0x001F); /* 蓝色 RGB565 */
Delay_ms(500);
LCD_FillColor(0xFFFF); /* 白色 RGB565 */
Delay_ms(500);
}
}
