0.96寸ST7735驱动IPS彩屏在STM32上的移植与驱动详解最近在做一个需要小型显示屏的STM32项目选来选去最后用了这块0.96寸的IPS彩屏。它用的是ST7735驱动芯片分辨率是80x160尺寸小巧显示效果还挺细腻。但拿到手后发现厂家给的例程和自己的工程对不上移植过程踩了不少坑。今天我就把整个驱动移植的过程包括软件SPI和硬件SPI两种方式从头到尾详细捋一遍希望能帮你省点时间。这篇教程适合正在给STM32找小屏方案需要自己动手移植驱动的朋友。我会假设你已经有一个能正常编译的STM32工程比如用Keil或STM32CubeIDE并且对GPIO、SPI有基本了解。咱们的目标不是讲原理而是手把手带你走通移植流程点亮屏幕。1. 准备工作认识屏幕与获取资料工欲善其事必先利其器。在动手写代码之前咱们得先搞清楚这块屏的基本情况并把必要的资料准备好。1.1 屏幕规格与引脚我用的这块屏核心参数如下屏幕尺寸0.96英寸分辨率80 (水平) x 160 (垂直) 像素驱动芯片ST7735S接口类型SPI串行外设接口工作电压2.8V ~ 3.3V直接接3.3V就行模块尺寸24mm x 30mm它通过一个8Pin的排针2.54mm间距与主控连接。这8个引脚各自有明确的分工引脚标号引脚名称方向功能说明1GND-电源地2VCC输入电源正极 (2.8V-3.3V)3SCL输入SPI时钟线 (SCK)4SDA输入SPI数据线 (MOSI主机输出从机输入)5RES输入复位引脚低电平有效6DC输入数据/命令选择引脚 (高电平:数据低电平:命令)7CS输入片选引脚低电平有效8BLK输入背光控制引脚 (高电平点亮可接PWM调光)注意模块是SPI从机。SCL对应SPI的SCKSDA对应SPI的MOSICS就是片选NSS。如果MCU的GPIO资源紧张RES可以接到MCU的复位引脚这样MCU复位时屏幕也复位BLK可以直接接3.3V或悬空代价是无法控制背光亮度。1.2 获取驱动资料驱动移植离不开厂家提供的原始资料。你可以通过以下链接获取资料下载链接:https://pan.baidu.com/s/19DxY8JJEzNt4XYF_CwVbDw提取码:8888下载后你会找到最重要的几个文件数据手册ZJY096S0800TG01.pdf里面有详细的电气特性和时序参数。示例代码一个完整的工程里面包含了驱动屏幕的核心文件主要是lcd.c、lcd.h、lcd_init.c、lcd_init.h。我们的移植工作就是把这些文件“搬”到我们自己的工程里并做适配。2. 移植第一步文件导入与基础适配拿到厂家例程后别急着看代码先把它“放”到我们的工程里。2.1 导入文件到工程在你的STM32工程目录下比如User或Drivers文件夹里新建一个文件夹例如叫LCD。将厂家资料里的【LCD】文件夹下的所有源文件.c和.h复制到你刚建的LCD文件夹里。打开你的IDE如Keil MDK在项目管理器中将lcd.c和lcd_init.c添加到你的工程组如Application/User。在IDE的配置中添加LCD文件夹的路径到头文件包含路径Include Paths里这样编译器才能找到lcd.h和lcd_init.h。2.2 修改头文件引用厂家例程可能使用了它自己定义的系统头文件我们需要把它改成我们自己工程里用的。通常需要修改两个地方打开lcd_init.h文件找到类似#include “sys.h”的语句将其改为你自己工程中常用的板级支持包头文件例如#include “board.h”或#include “main.h”。同样地打开lcd.h文件进行相同的修改。接着打开lcd_init.c和lcd.c文件将里面的#include “delay.h”也替换成#include “board.h”前提是你的board.h里包含了延时函数声明或直接使用HAL库的HAL_Delay。2.3 定义数据类型宏厂家代码里可能用了u8、u16、u32这样的简写类型。我们需要在lcd_init.h和lcd.h文件的顶部在所有#include之后函数声明之前添加这些宏定义确保它们指向STM32标准库或HAL库的正确类型。在lcd_init.h和lcd.h中都添加以下代码#ifndef u8 #define u8 uint8_t #endif #ifndef u16 #define u16 uint16_t #endif #ifndef u32 #define u32 uint32_t #endif做完这些编译一下工程解决可能出现的头文件找不到或类型未定义的错误。基础框架就搭好了。3. 核心移植引脚与通信方式配置这是移植最关键的一步决定了屏幕如何与你的STM32芯片“对话”。你可以选择软件模拟SPI或硬件SPI。软件SPI灵活不挑引脚硬件SPI速度快节省CPU。咱们分别讲。3.1 方案一使用软件SPI模拟SPI软件SPI就是用普通的GPIO引脚通过代码控制电平变化来模拟SPI的时序。优点是任意GPIO都能用缺点是速度慢且时序靠软件延时在高主频下可能不稳定。厂家例程默认就是软件SPI。步骤1确定连接引脚首先根据你的开发板原理图和引脚分配为屏幕的6个控制引脚RES, DC, CS, BLK, SCL, SDA分别选定一个STM32的GPIO。记下它们对应的端口和引脚号例如RES - PA3DC - PA2CS - PA4BLK - PA1 (也可接3.3V常亮)SCL (SCK) - PA5SDA (MOSI) - PA7步骤2修改引脚定义宏打开lcd_init.h文件找到LCD端口定义部分。你会看到一系列类似#define LCD_RES_Clr() ...的宏。你需要根据你选择的引脚修改它们。例如如果你将RES接在PA3那么修改如下//-----------------LCD端口定义---------------- #define LCD_RES_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_3)//RES #define LCD_RES_Set() GPIO_SetBits(GPIOA,GPIO_Pin_3) #define LCD_DC_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_2)//DC #define LCD_DC_Set() GPIO_SetBits(GPIOA,GPIO_Pin_2) // ... 其他引脚CS、BLK、SCL、SDA依此类推步骤3编写引脚初始化函数在lcd_init.c中找到LCD_GPIO_Init(void)函数或者根据lcd_init.h里的声明自己实现一个。这个函数负责初始化你选定的GPIO为输出模式。下面是一个针对PA1, PA2, PA3, PA4, PA5, PA7的初始化示例使用标准外设库void LCD_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟 // 配置所有用到的引脚 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_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; // 输出速度50MHz GPIO_Init(GPIOA, GPIO_InitStructure); // 初始化后将CS、RES等引脚置为默认高电平 GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4); }提示软件SPI的时序时钟高低电平、数据建立时间已经在厂家提供的LCD_Writ_Bus(u8 dat)等函数中用delay_us之类的函数模拟好了所以我们只需要配好GPIO就行不用管SPI外设。3.2 方案二使用硬件SPI硬件SPI利用STM32内部的SPI外设控制器来生成精确的时钟和收发数据速度快可靠性高且不占用CPU时间。但引脚是固定的必须接到具有SPI复用功能的引脚上。步骤1确定硬件SPI引脚以STM32F103C8T6蓝色小板为例SPI1的引脚是PA5 - SPI1_SCKPA6 - SPI1_MISO (屏幕不需要可忽略)PA7 - SPI1_MOSI所以我们必须将屏幕的SCL接PA5SDA接PA7。其他控制引脚RES, DC, CS, BLK仍然是普通GPIO可以自由分配例如继续用PA2, PA3, PA4, PA1。步骤2修改引脚定义宏和软件SPI一样修改lcd_init.h中的宏定义但注意SCL和SDA的宏在硬件SPI模式下可能不会被直接调用因为数据发送改用SPI外设了但为了代码统一可以保留或注释掉。重点是确保RES、DC、CS、BLK的宏定义正确。步骤3编写硬件SPI初始化函数这是与软件SPI最大的不同。我们需要初始化两部分SPI外设本身以及作为普通GPIO的控制引脚。在lcd_init.c中重写LCD_GPIO_Init(void)函数void LCD_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure_SPI; GPIO_InitTypeDef GPIO_InitStructure_CTRL; SPI_InitTypeDef SPI_InitStructure; // 1. 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // 2. 初始化SPI引脚 (PA5-SCK, PA7-MOSI) 为复用推挽输出 GPIO_InitStructure_SPI.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure_SPI.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure_SPI.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure_SPI); // 3. 初始化控制引脚 (RES, DC, CS, BLK) 为普通推挽输出 GPIO_InitStructure_CTRL.GPIO_Pin GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4; GPIO_InitStructure_CTRL.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure_CTRL.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure_CTRL); // 默认置高 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; // 时钟相位第二个边沿采样 SPI_InitStructure.SPI_NSS SPI_NSS_Soft; // 软件控制NSS片选 SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; // 波特率预分频决定SPI速度 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_CPOL和SPI_CPHA时钟极性和相位必须与从设备ST7735的要求匹配。通常ST7735的模式是CPOL1, CPHA1即模式3但具体请以屏幕资料为准。这里例程设置的是High和2Edge即模式3。步骤4修改数据发送函数软件SPI的数据发送是靠模拟时序硬件SPI则需要调用库函数。找到lcd_init.c文件中的void LCD_Writ_Bus(u8 dat)函数它是底层字节发送函数需要重写。将原来的模拟时序代码替换为使用SPI外设发送的代码void LCD_Writ_Bus(u8 dat) { LCD_CS_Clr(); // 拉低片选开始传输 // 等待发送缓冲区为空表示可以写入新数据 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); // 通过SPI1发送一个字节数据 SPI_I2S_SendData(SPI1, dat); // 等待接收缓冲区非空表示数据已发送完毕可以读走以清空标志 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); SPI_I2S_ReceiveData(SPI1); // 读取数据清空RXNE标志 LCD_CS_Set(); // 拉高片选结束传输 }这个函数的核心就是调用SPI_I2S_SendData把数据丢给SPI外设然后等待它发送完成。4. 上电测试点亮你的第一块屏驱动移植好了最后一步就是写个简单的测试程序看看屏幕能不能亮起来。在你的main.c文件中包含必要的头文件然后在主循环前初始化屏幕并显示一些内容。#include main.h // 或你的工程主头文件 #include lcd_init.h #include lcd.h int main(void) { // 系统初始化时钟、延时等 SystemInit(); delay_init(); // 初始化LCD里面会调用我们修改好的LCD_GPIO_Init LCD_Init(); // 清屏为黑色 LCD_Fill(0, 0, LCD_W, LCD_H, BLACK); // 显示一些测试信息 LCD_ShowString(0, 0, (uint8_t *)Hello STM32!, WHITE, BLACK, 16, 0); LCD_ShowString(0, 20, (uint8_t *)0.96 IPS Test, BLUE, BLACK, 16, 0); // 画一个矩形框 LCD_DrawRectangle(10, 40, 70, 90, RED); // 填充一个矩形 LCD_Fill(15, 45, 65, 85, GREEN); while (1) { // 可以在这里添加动态刷新内容比如显示不断变化的数值 // LCD_ShowNum(...); // delay_ms(500); } }编译、下载到开发板。如果一切顺利你应该能看到屏幕点亮并显示“Hello STM32!”和彩色的图形。常见问题排查白屏或花屏最常见的是RES复位时序不对或者DC引脚在发送命令/数据时电平设置错误。检查LCD_Init()函数里对RES和DC的操作。全黑检查背光BLK引脚是否已接高电平3.3V。编译错误回头检查头文件包含路径、数据类型宏定义是否正确。硬件SPI没反应首先用逻辑分析仪或示波器检查SCK和MOSI引脚是否有波形。如果没有检查SPI初始化代码特别是时钟使能、引脚复用模式、以及SPI_Cmd(ENABLE)是否执行。也可以尝试降低SPI波特率增大SPI_BaudRatePrescaler的值。移植过程虽然繁琐但一步步理清引脚、通信方式和初始化流程后其实并不难。希望这篇教程能帮你顺利驱动起这块小巧的IPS屏。在实际项目中你还可以基于lcd.c中的画点、画线、显示字符函数封装出更丰富的图形界面。