梁山派天空星驱动0.96寸ST7735 SPI彩屏移植实战
梁山派天空星驱动0.96寸ST7735 SPI彩屏移植实战最近在做一个需要小尺寸显示屏的项目手头正好有一块0.96寸的IPS彩屏驱动芯片是ST7735。屏幕虽小但显示效果很细腻。我用的主控板是梁山派天空星STM32F407网上找的例程大多是针对其他开发板的直接拿来用肯定不行。折腾了大半天总算把驱动成功移植过来了。今天就把这个完整的移植过程包括软件SPI和硬件SPI两种方式一步步分享给大家希望能帮你少走点弯路。这篇教程适合正在为STM32项目寻找小型显示屏方案的嵌入式爱好者或学生。我会从资料获取、工程搭建、引脚配置到两种驱动方式的代码修改最后验证显示效果手把手带你走完整个流程。就算你之前没接触过SPI屏跟着做也能点亮它。1. 准备工作认识屏幕与获取资料工欲善其事必先利其器。咱们先搞清楚要驱动的对象是什么并把必要的资料准备好。1.1 屏幕模块简介我用的这块屏是0.96英寸的IPS液晶屏驱动芯片是ST7735。IPS屏的优势是可视角度大色彩表现也不错。它的核心参数如下工作电压2.8V ~ 3.3V直接用开发板的3.3V供电就行分辨率80 x 160像素RGB排列通信接口SPI串行外设接口物理接口8个Pin脚2.54mm间距排针屏幕的引脚虽然标了8个但实际通信和基本控制用不到全部。具体每个脚是干嘛的咱们后面接线的时候再细说。1.2 资料获取与工程准备这块屏的厂家提供了完整的资料包里面包含了数据手册、原理图和最重要的——已经写好的驱动例程。这是我们移植的基础。资料下载你可以通过提供的链接获取资料包。解压后找到名为【LCD】的文件夹这里面就是屏幕的驱动源码。准备你的工程你需要一个基于STM32F4系列因为我们用的是天空星STM32F407的空白工程。工程里必须有一个能用的毫秒级延时函数delay_ms。如果你还没有可以用STM32CubeMX生成一个或者用开发板提供的标准例程工程。导入驱动文件把你工程目录准备好后将厂家资料里的整个【LCD】文件夹复制到你工程的源代码目录下比如Src或User文件夹里。2. 基础移植让工程编译通过把文件复制过来只是第一步直接编译肯定会报一堆错误因为厂家的例程是针对他们自己的开发环境写的。咱们需要做一些“粗改”先让工程能编译。2.1 修改头文件引用厂家的代码里引用了一些他们自己的头文件我们需要换成STM32标准库或HAL库的头文件。在你的IDE如Keil、IAR或VSCodePlatformIO中将lcd.c和lcd_init.c这两个源文件添加到你的工程。打开lcd_init.h和lcd.h这两个头文件。找到里面#include “sys.h”这样的语句把它统一改成#include “stm32f4xx.h”。这个头文件包含了STM32F4系列的所有寄存器定义。接着打开lcd_init.c和lcd.c源文件把里面#include “delay.h”的语句改成你工程里实际使用的延时函数头文件。根据原始资料提示这里改为#include “board.h”。2.2 定义数据类型别名编译一下你可能会看到关于u8、u16、u32这些类型未定义的错误。这是因为厂家用这些简写来代替uint8_t、uint16_t等标准类型。解决办法很简单在lcd_init.h和lcd.h文件的开头在#include语句之后其他代码之前添加以下宏定义#ifndef u8 #define u8 uint8_t #endif #ifndef u16 #define u16 uint16_t #endif #ifndef u32 #define u32 uint32_t #endif这样当编译器遇到u8时就会把它当作uint8_t来处理。再次编译之前的类型错误应该就消失了。注意如果你的工程环境已经全局定义了这些类型比如在stdint.h中也可能不会报错。但加上这些定义可以确保兼容性。3. 核心步骤引脚配置与驱动方式选择这是移植最关键的一步决定了屏幕怎么和你的单片机“对话”。屏幕有8个引脚但并非全部必须连接。3.1 屏幕引脚功能说明在接线前我们必须明白每个引脚的作用。这8个引脚通常如下表所示引脚标号引脚名称方向功能说明1LED/ BLK输入背光控制。接高电平3.3V背光最亮接PWM可调光也可悬空默认亮。2SCK/ SCL输入SPI时钟线。主机单片机输出时钟给从机屏幕。3SDA/ MOSI输入SPI数据线。主机输出数据给从机。4DC/ A0输入数据/命令选择线。高电平表示接下来传输的是数据低电平表示是命令。5RESET/ RST输入复位引脚低电平有效。用于硬件复位屏幕。6CS/ SCE输入片选引脚低电平有效。当多个SPI设备挂在同一总线上时用来选择当前要通信的设备。7GND-电源地。8VCC-电源正极3.3V。引脚节省技巧如果你的单片机GPIO口非常紧张有两个引脚可以简化RESET引脚可以直接接到单片机的复位引脚NRST上。这样单片机每次复位时屏幕也跟着复位一次。缺点是无法在程序运行中单独复位屏幕。BLK背光引脚可以直接接到3.3V上让它常亮或者悬空模块内部可能已有上拉。代价是失去了用PWM调节背光亮度的能力。接下来我们分两种方式来驱动软件模拟SPI和硬件SPI。软件SPI简单灵活不挑引脚硬件SPI速度快节省CPU。3.2 方案一软件SPI移植简单通用软件SPI的本质就是用单片机的普通IO口通过代码控制电平变化来模拟出SPI通信的时序波形。厂家提供的例程默认就是这种方式好处是你可以把屏幕接到任意一组空闲的IO上非常灵活。第一步确定接线假设我们选择使用GPIOA的一组引脚来连接屏幕可以这样分配PA5- SCK (时钟线)PA7- SDA/MOSI (数据线)PA2- DC (数据/命令线)PA3- RESET (复位线)PA4- CS (片选线)PA1- BLK (背光线)3.3V- VCCGND- GND第二步修改引脚定义宏打开lcd_init.h文件找到类似下面这样的端口定义宏根据你的接线修改它们。这些宏定义了如何控制每个引脚输出高或低电平。//-----------------LCD端口定义---------------- #define LCD_RES_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_3) //RES 引脚置低 #define LCD_RES_Set() GPIO_SetBits(GPIOA,GPIO_Pin_3) //RES 引脚置高 #define LCD_DC_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_2) //DC 引脚置低 #define LCD_DC_Set() GPIO_SetBits(GPIOA,GPIO_Pin_2) //DC 引脚置高 #define LCD_CS_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_4) //CS 引脚置低 #define LCD_CS_Set() GPIO_SetBits(GPIOA,GPIO_Pin_4) //CS 引脚置高 #define LCD_BLK_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_1) //BLK 引脚置低 #define LCD_BLK_Set() GPIO_SetBits(GPIOA,GPIO_Pin_1) //BLK 引脚置高 // 注意SCK和SDAMOSI的时序由软件模拟函数控制不在这里定义第三步编写GPIO初始化函数我们需要一个函数来初始化上面用到的所有GPIO引脚。在lcd_init.c中找到或添加LCD_GPIO_Init函数并修改如下void LCD_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 第一步打开GPIOA的时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // 第二步配置GPIO引脚参数 // 我们把PA1, PA2, PA3, PA4, PA5, PA7都初始化为输出模式 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_OUT; // 普通输出模式 GPIO_InitStructure.GPIO_OType GPIO_OType_PP; // 推挽输出驱动能力强 GPIO_InitStructure.GPIO_Speed GPIO_Speed_100MHz; // 输出速度控制LED用50MHz也够 GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_UP; // 内部上拉稳定电平 GPIO_Init(GPIOA, GPIO_InitStructure); // 第三步初始化后将所有引脚设置为高电平根据屏幕逻辑CS、RESET等默认应为高 GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7); }到这里软件SPI的硬件配置就完成了。厂家例程里已经写好了模拟SPI时序的函数比如LCD_Writ_Bus所以我们不需要改动通信部分的代码。3.3 方案二硬件SPI移植高效稳定硬件SPI是利用STM32内部专用的SPI外设来通信。所有的时钟生成、数据移位都由硬件完成速度远快于软件模拟并且不占用CPU时间。第一步硬件SPI引脚选择STM32F407有多个SPI外设SPI1~SPI6每个SPI对应固定的引脚。我们不能像软件SPI那样随意接。以SPI1为例它的主要引脚映射是PA5- SPI1_SCKPA6- SPI1_MISO (主设备输入本例未使用)PA7- SPI1_MOSI (主设备输出)所以我们必须将屏幕的SCK接PA5SDA/MOSI接PA7。其他控制引脚DC, RESET, CS, BLK仍然是普通GPIO我们可以自由分配这里延续之前的接法。第二步修改引脚定义与初始化同样先修改lcd_init.h中的引脚定义宏和软件SPI一样因为控制引脚没变。然后重写LCD_GPIO_Init函数。这个函数现在要做两件事1. 初始化普通GPIO2. 配置并启用SPI1外设。void LCD_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure_SPI; GPIO_InitTypeDef GPIO_InitStructure_GPIO; SPI_InitTypeDef SPI_InitStructure; // 1. 打开GPIOA和SPI1的时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // 2. 配置PA5和PA7为SPI1的复用功能(AF) GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1); // PA5复用为SPI1_SCK GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1); // PA7复用为SPI1_MOSI // 初始化SPI功能引脚 (PA5, PA7) GPIO_InitStructure_SPI.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure_SPI.GPIO_Mode GPIO_Mode_AF; // 复用功能模式 GPIO_InitStructure_SPI.GPIO_OType GPIO_OType_PP; // 推挽输出 GPIO_InitStructure_SPI.GPIO_Speed GPIO_Speed_100MHz; // 高速 GPIO_InitStructure_SPI.GPIO_PuPd GPIO_PuPd_UP; // 上拉 GPIO_Init(GPIOA, GPIO_InitStructure_SPI); // 3. 初始化普通GPIO控制引脚 (PA1, PA2, PA3, PA4) GPIO_InitStructure_GPIO.GPIO_Pin GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4; GPIO_InitStructure_GPIO.GPIO_Mode GPIO_Mode_OUT; // 普通输出 GPIO_InitStructure_GPIO.GPIO_OType GPIO_OType_PP; GPIO_InitStructure_GPIO.GPIO_Speed GPIO_Speed_100MHz; GPIO_InitStructure_GPIO.GPIO_PuPd GPIO_PuPd_UP; GPIO_Init(GPIOA, GPIO_InitStructure_GPIO); // 初始化后置高 GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4); // 4. 配置SPI1外设参数 SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; // 全双工虽然只发不收 SPI_InitStructure.SPI_Mode SPI_Mode_Master; // 主机模式 SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; // 8位数据帧 SPI_InitStructure.SPI_CPOL SPI_CPOL_High; // 时钟极性空闲时为高 SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; // 时钟相位第二个边沿采样 // 这两项CPOL和CPHA必须严格按照屏幕数据手册设置ST7735通常是模式3CPOL1, CPHA1 SPI_InitStructure.SPI_NSS SPI_NSS_Soft; // 软件控制NSS片选我们用GPIO控制CS SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; // 波特率预分频决定SPI速度 // 分频数越小速度越快。F407主频168MHz分频4后约42MHz。如果屏幕不稳定可以尝试加大分频如8, 16。 SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; // 高位先发送 SPI_InitStructure.SPI_CRCPolynomial 7; // CRC多项式本例不用可忽略 SPI_Init(SPI1, SPI_InitStructure); // 5. 使能SPI1 SPI_Cmd(SPI1, ENABLE); }第三步修改数据发送函数软件SPI的数据发送是靠模拟时序现在用了硬件SPI我们需要修改底层的字节发送函数。在lcd_init.c文件中找到void LCD_Writ_Bus(u8 dat)函数它负责发送一个字节的数据。将其修改为使用硬件SPI发送void LCD_Writ_Bus(u8 dat) { LCD_CS_Clr(); // 拉低片选开始通信 // 等待发送缓冲区为空表示可以发送新数据了 while(RESET SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)); // 通过SPI1发送一个字节数据 SPI_I2S_SendData(SPI1, dat); // 等待接收缓冲区非空对于只发送不接收的设备此步骤可确保数据已发送完成 while(RESET SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)); SPI_I2S_ReceiveData(SPI1); // 读取接收到的数据丢弃 LCD_CS_Set(); // 拉高片选结束本次通信 }提示SPI_I2S_FLAG_RXNE标志位在数据被接收移入时置位。对于单向发送等待这个标志可以确保上一个字节已经完全从移位寄存器中发出去了避免数据覆盖。这是一个保证时序完整的常用技巧。4. 移植验证点亮屏幕并显示内容所有代码修改完成后编译工程确保没有错误。接下来写一个简单的main函数来测试屏幕是否工作正常。在你的main.c文件中可以写入如下测试代码#include board.h #include bsp_uart.h #include stdio.h #include lcd_init.h #include lcd.h // 注意pic.h是显示图片需要的头文件如果你没有图片数据可以去掉这行和相关的显示函数。 int main(void) { board_init(); // 开发板基础初始化时钟、系统滴答定时器等 uart1_init(115200U); // 初始化串口用于调试打印可选 float test_number 0.0f; LCD_Init(); // 初始化屏幕就是调用我们刚才修改的那些函数 LCD_Fill(0, 0, LCD_W, LCD_H, BLACK); // 清屏为黑色 // 显示屏幕分辨率 LCD_ShowString(0, 16*2, (uint8_t *)LCD_W:, WHITE, BLACK, 16, 0); LCD_ShowIntNum(48, 16*2, LCD_W, 3, WHITE, BLACK, 16); // 显示宽度值 LCD_ShowString(80, 16*2, (uint8_t *)LCD_H:, WHITE, BLACK, 16, 0); LCD_ShowIntNum(128, 16*2, LCD_H, 3, WHITE, BLACK, 16); // 显示高度值 while(1) { // 显示一个递增的浮点数测试动态刷新 LCD_ShowString(0, 16*3, (uint8_t *)Num:, WHITE, BLACK, 16, 0); LCD_ShowFloatNum1(8*4, 16*3, test_number, 4, WHITE, BLACK, 16); // 在指定位置显示浮点数 test_number 0.11; delay_ms(1000); // 延时1秒 } }将程序下载到梁山派天空星开发板上电后你应该能看到屏幕背光亮起。屏幕先全黑然后顶部显示“LCD_W: 80”和“LCD_H: 160”证明了屏幕分辨率正确读取。下方有一个浮点数每隔1秒增加0.11。如果显示正常恭喜你移植成功了如果白屏、花屏或者显示错乱请按以下顺序排查电源和接线确保VCC和GND接对接线牢固。引脚配置回头仔细检查lcd_init.h中的宏定义和LCD_GPIO_Init函数里的引脚是否和你实际的接线一一对应。SPI模式对于硬件SPISPI_CPOL和SPI_CPHA的设置至关重要必须匹配ST7735的要求通常是模式0或模式3。可以尝试切换一下。SPI速度如果硬件SPI下显示异常尝试降低波特率预分频值比如从SPI_BaudRatePrescaler_4改为SPI_BaudRatePrescaler_8或16过高的速度可能导致通信不稳定。复位时序确保在初始化序列中复位引脚RESET有正确的低电平脉冲。可以检查厂家LCD_Init()函数内部的复位操作。