当前位置: 首页 > news >正文

0.96寸ST7735驱动IPS彩屏在STM32上的移植与驱动详解

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

下载后,你会找到最重要的几个文件:

  1. 数据手册ZJY096S0800TG01.pdf,里面有详细的电气特性和时序参数。
  2. 示例代码:一个完整的工程,里面包含了驱动屏幕的核心文件,主要是lcd.clcd.hlcd_init.clcd_init.h。我们的移植工作就是把这些文件“搬”到我们自己的工程里,并做适配。

2. 移植第一步:文件导入与基础适配

拿到厂家例程后,别急着看代码,先把它“放”到我们的工程里。

2.1 导入文件到工程

  1. 在你的STM32工程目录下(比如UserDrivers文件夹里),新建一个文件夹,例如叫LCD
  2. 将厂家资料里的【LCD】文件夹下的所有源文件(.c.h)复制到你刚建的LCD文件夹里。
  3. 打开你的IDE(如Keil MDK),在项目管理器中,将lcd.clcd_init.c添加到你的工程组(如Application/User)。
  4. 在IDE的配置中,添加LCD文件夹的路径到头文件包含路径(Include Paths)里,这样编译器才能找到lcd.hlcd_init.h

2.2 修改头文件引用

厂家例程可能使用了它自己定义的系统头文件,我们需要把它改成我们自己工程里用的。通常需要修改两个地方:

  1. 打开lcd_init.h文件,找到类似#include “sys.h”的语句,将其改为你自己工程中常用的板级支持包头文件,例如#include “board.h”#include “main.h”
  2. 同样地,打开lcd.h文件,进行相同的修改。
  3. 接着,打开lcd_init.clcd.c文件,将里面的#include “delay.h”也替换成#include “board.h”(前提是你的board.h里包含了延时函数声明或直接使用HAL库的HAL_Delay)。

2.3 定义数据类型宏

厂家代码里可能用了u8u16u32这样的简写类型。我们需要在lcd_init.hlcd.h文件的顶部(在所有#include之后,函数声明之前)添加这些宏定义,确保它们指向STM32标准库或HAL库的正确类型。

lcd_init.hlcd.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 -> PA3
  • DC -> PA2
  • CS -> PA4
  • BLK -> PA1 (也可接3.3V常亮)
  • SCL (SCK) -> PA5
  • SDA (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_SCK
  • PA6 -> SPI1_MISO (屏幕不需要,可忽略)
  • PA7 -> SPI1_MOSI

所以,我们必须将屏幕的SCL接PA5,SDA接PA7。其他控制引脚(RES, DC, CS, BLK)仍然是普通GPIO,可以自由分配(例如继续用PA2, PA3, PA4, PA1)。

步骤2:修改引脚定义宏和软件SPI一样,修改lcd_init.h中的宏定义,但注意SCLSDA的宏在硬件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_CPOLSPI_CPHA(时钟极性和相位)必须与从设备(ST7735)的要求匹配。通常ST7735的模式是CPOL=1, CPHA=1(即模式3),但具体请以屏幕资料为准。这里例程设置的是High2Edge,即模式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!”和彩色的图形。

常见问题排查

  1. 白屏或花屏:最常见的是RES复位时序不对,或者DC引脚在发送命令/数据时电平设置错误。检查LCD_Init()函数里对RESDC的操作。
  2. 全黑:检查背光BLK引脚是否已接高电平(3.3V)。
  3. 编译错误:回头检查头文件包含路径、数据类型宏定义是否正确。
  4. 硬件SPI没反应:首先用逻辑分析仪或示波器检查SCKMOSI引脚是否有波形。如果没有,检查SPI初始化代码,特别是时钟使能、引脚复用模式、以及SPI_Cmd(ENABLE)是否执行。也可以尝试降低SPI波特率(增大SPI_BaudRatePrescaler的值)。

移植过程虽然繁琐,但一步步理清引脚、通信方式和初始化流程后,其实并不难。希望这篇教程能帮你顺利驱动起这块小巧的IPS屏。在实际项目中,你还可以基于lcd.c中的画点、画线、显示字符函数,封装出更丰富的图形界面。

http://www.cnnetsun.cn/news/1302745.html

相关文章:

  • NEURAL MASK 在Web开发中的应用:构建一个在线老照片修复平台
  • 具身智能学习路线
  • Lychee-Rerank企业面试系统应用:Java八股文智能匹配
  • 实时手机检测-通用高性能部署:共享内存IPC优化多进程并发检测吞吐
  • RyzenAdj完全掌控指南:释放AMD锐龙处理器的终极性能潜力
  • HDU:杭电 2019 复试真题汇总
  • 基于麻雀搜索优化算法优化最小二乘支持向量机(SSA-LSSVM)的多输出数据回归预测 SSA-...
  • 基于ESP32与ESP-ADF框架:三合一智能音箱(蓝牙/网络电台/AI对话)DIY全流程解析
  • JiYuTrainer实战通关:从原理到应用的零门槛之旅
  • 技术突破:让旧Mac重获新生的极限释放指南
  • Hotkey Detective:一站式解决Windows热键冲突问题
  • 3步完成Magma智能体部署:Linux系统环境配置全指南
  • 裂隙相控制方程
  • Qwen3-14b_int4_awq多轮对话效果展示:Chainlit界面中上下文保持与逻辑连贯性案例
  • OPENPPP2静态隧道UDP中断问题排查与解决
  • 立创EDA实战:基于ESP8266的智能温控杯架《暖男杯架》开源项目全解析
  • 实战指南:用Docker快速搭建Canal+MySQL+Kafka数据同步环境(附避坑技巧)
  • 从网络IO到高并发Reactor模式:吃透网络库设计核心逻辑
  • 4个步骤掌握WebPlotDigitizer高效图表数据提取
  • Chrome无法上网,但其他浏览器正常
  • 突破动画迁移瓶颈:mixamo_converter实现Mixamo到Unreal Engine 4根骨骼动画的无缝转换
  • 【后端必看】什么是 Elasticsearch?都要学什么?
  • Chromium WebRTC 架构解析:从信令协商到媒体传输的实现原理
  • 提升FF14副本效率:MMORPG玩家的动画等待问题解决方案
  • Qwen3-ASR-0.6B与QT开发:跨平台语音应用构建
  • Python循环入门:彻底搞懂for循环,看这一篇就够了!
  • 如何利用开源工具让老旧设备焕发新生:系统升级完整指南
  • Ollama工具调用实战:5分钟搞定电商客服自动化退货流程(附完整代码)
  • 热键冲突智能诊断系统:破解Windows快捷键资源竞争的技术方案
  • 【限时技术红利】Docker 27日志审计增强:唯一支持实时日志篡改检测的开源容器运行时(实测延迟<127ms)