普冉单片机PY32F002AF15P6TU + 0.96寸TFT ST7735s 80*160显示屏,使用软件SPI进行颜色填充

软件 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、时序易受中断影响、只能低速用

四、使用场景怎么选

  1. 屏幕、Flash、SD 卡、高速传感器 → 优先硬件 SPI
  2. 引脚不够、硬件 SPI 被占用、多个低速从器件 → 用软件 SPI
  3. 低速简单外设(如低速 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 */

结果:显示的颜色变为蓝色、绿色、红色、白色,而不是预期的红、绿、蓝、白色

重大突破!现在我们知道了两件事:

  1. 白色正确了! --- MADCTL=0xC0 + 关反色的配置是对的
  2. 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);
    }
}
相关推荐
楼兰公子2 小时前
SoC嵌入式硬件设计:原理图搭建与PCB画板系统教学(KiCad 10.0版)
嵌入式硬件·kicad
LCG元2 小时前
STM32实战:基于STM32F103的智能充电器(电压电流检测+PWM)
stm32·单片机·嵌入式硬件
feifeigo1232 小时前
汽车CAN J1939协议完整编程源码和STM32移植指南
stm32·嵌入式硬件·汽车
LCG元2 小时前
STM32实战:基于OpenMV与STM32的智能视觉追踪小车(颜色识别+舵机控制)
stm32·单片机·嵌入式硬件
星夜夏空992 小时前
STM32单片机学习(13) —— 串口通信协议
stm32·单片机·学习
崇山峻岭之间2 小时前
单片机时钟配置:HSE改为HSI
单片机·嵌入式硬件
QH139292318803 小时前
思仪 Ceyear 5256C 5G 终端综合测试仪
单片机·单元测试·集成测试·嵌入式实时数据库
jake·tang3 小时前
深度解析 VESC 参数辨识源码:电阻、电感与磁链
arm开发·c++·嵌入式硬件·算法·数学建模·傅立叶分析
崇山峻岭之间3 小时前
单片机时钟配置03
单片机·嵌入式硬件