长春建站公众号沈阳网站建设电话
长春建站公众号,沈阳网站建设电话,外卖小程序怎么制作,花店网站建设方案SSD1306驱动0.96寸SPI单色OLED屏#xff1a;基于CW32F030C8T6的软硬件SPI移植实战
最近在做一个基于国产CW32F030C8T6的小项目#xff0c;需要用到一块小巧的OLED屏来显示信息。我手头正好有一块中景园的0.96寸SPI单色屏#xff0c;驱动芯片是经典的SSD1306。虽然网上有很多…SSD1306驱动0.96寸SPI单色OLED屏基于CW32F030C8T6的软硬件SPI移植实战最近在做一个基于国产CW32F030C8T6的小项目需要用到一块小巧的OLED屏来显示信息。我手头正好有一块中景园的0.96寸SPI单色屏驱动芯片是经典的SSD1306。虽然网上有很多STM32的驱动例程但直接搬到CW32上还是得花点功夫。今天我就把整个移植过程包括软件模拟SPI和硬件SPI两种方式手把手地分享出来希望能帮到正在用CW32或者类似Cortex-M0/M0内核MCU的朋友们。1. 准备工作认识你的屏幕和开发板在开始敲代码之前咱们先得把“演员”认清楚。我用的这块屏幕是市面上非常常见的型号具体信息如下屏幕模块信息型号中景园0.96寸OLED显示屏驱动芯片SSD1306分辨率128 x 64 像素通信接口SPI4线制工作电压3.3V引脚7个GND, VCC, D0, D1, RES, DC, CS开发板信息主控CW32F030C8T6ARM Cortex-M0内核开发环境基于立创·CW32F030C8T6开发板的工程模板这块屏幕的引脚定义是移植的关键咱们得记牢屏幕引脚功能说明GND电源地VCC电源正接3.3VD0串行时钟线SCKD1串行数据线MOSIRES复位信号低电平有效DC数据/命令选择高数据低命令CS片选信号低电平使能注意模块是SPI从机。简单来说D0就是时钟线SCKD1就是主机输出数据线MOSICS就是片选NSS。RES和DC是控制信号需要我们用普通GPIO来控制。资料获取 原始资料和例程可以从提供的网盘链接下载提取码是8888。里面包含了屏幕的详细资料和厂家提供的驱动源码这是我们移植的基础。2. 工程搭建与源码导入拿到厂家例程后第一步就是把它放到我们的CW32工程里。复制文件找到厂家资料里的OLED文件夹通常包含oled.c和oled.h把它整个复制到你自己的CW32工程目录下。导入工程在你的IDE比如Keil MDK中将oled.c和oled.h文件添加到工程树里。基础准备确保你的工程里有一个可用的毫秒级延时函数delay_ms()这是屏幕初始化必需的。接下来我们需要对头文件做一些适应性修改让代码认识CW32的开发环境。打开oled.h文件找到类似#include “sys.h”的语句把它改成CW32开发板对应的头文件通常是board.h。// 将原来的 #include “sys.h” 修改为 #include “board.h”然后打开oled.c文件找到#include “delay.h”这一行。因为我们的延时函数可能不叫这个名字或者已经包含在board.h里了所以先把它注释掉后续我们会用自己工程里的延时函数。// 注释掉厂家例程的延时头文件 // #include “delay.h”3. 软件模拟SPI移植最灵活的接线方式软件模拟SPI顾名思义就是用程序控制几个普通的GPIO引脚通过拉高拉低来模拟SPI的通信时序。它的最大好处是引脚可以任意分配不占用硬件SPI外设非常灵活适合引脚紧张或者初学理解时序的场景。3.1 引脚连接规划我们先给屏幕和开发板牵好线。这里我以GPIOA的一组引脚为例你可以根据实际情况调整。屏幕引脚连接到CW32开发板引脚功能GNDGND电源地VCC3.3V电源正D0 (SCK)PA5模拟SPI时钟D1 (MOSI)PA7模拟SPI数据输出RESPA3复位控制DCPA2数据/命令控制CSPA4片选控制3.2 修改引脚宏定义接线确定后就要在代码里告诉单片机哪个引脚控制什么。我们需要修改oled.h也可能是lcd_init.h根据你的源码结构中的引脚宏定义。找到文件中关于“OLED端口定义”的部分修改成下面这样//-----------------OLED端口定义---------------- #define OLED_GPIO_PORT CW_GPIOA // 使用的GPIO端口 #define OLED_SCL_PIN GPIO_PIN_5 // SCK - PA5 #define OLED_MOSI_PIN GPIO_PIN_7 // MOSI - PA7 #define OLED_RES_PIN GPIO_PIN_3 // RES - PA3 #define OLED_DC_PIN GPIO_PIN_2 // DC - PA2 #define OLED_CS_PIN GPIO_PIN_4 // CS - PA4 // 下面是控制引脚高低电平的宏直接操作寄存器速度快 #define OLED_SCL_Clr() GPIO_WritePin(OLED_GPIO_PORT, OLED_SCL_PIN, GPIO_Pin_RESET) // 时钟线拉低 #define OLED_SCL_Set() GPIO_WritePin(OLED_GPIO_PORT, OLED_SCL_PIN, GPIO_Pin_SET) // 时钟线拉高 #define OLED_SDA_Clr() GPIO_WritePin(OLED_GPIO_PORT, OLED_MOSI_PIN, GPIO_Pin_RESET) // 数据线拉低 #define OLED_SDA_Set() GPIO_WritePin(OLED_GPIO_PORT, OLED_MOSI_PIN, GPIO_Pin_SET) // 数据线拉高 #define OLED_RES_Clr() GPIO_WritePin(OLED_GPIO_PORT, OLED_RES_PIN, GPIO_Pin_RESET) // 复位拉低 #define OLED_RES_Set() GPIO_WritePin(OLED_GPIO_PORT, OLED_RES_PIN, GPIO_Pin_SET) // 复位拉高 #define OLED_DC_Clr() GPIO_WritePin(OLED_GPIO_PORT, OLED_DC_PIN, GPIO_Pin_RESET) // DC拉低命令 #define OLED_DC_Set() GPIO_WritePin(OLED_GPIO_PORT, OLED_DC_PIN, GPIO_Pin_SET) // DC拉高数据 #define OLED_CS_Clr() GPIO_WritePin(OLED_GPIO_PORT, OLED_CS_PIN, GPIO_Pin_RESET) // 片选拉低使能 #define OLED_CS_Set() GPIO_WritePin(OLED_GPIO_PORT, OLED_CS_PIN, GPIO_Pin_SET) // 片选拉高失能3.3 修改初始化函数接下来修改oled.c中的OLED_Init(void)函数。主要是前半部分的GPIO初始化后半部分发送给SSD1306的初始化命令序列通常不需要改动。void OLED_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; // 定义一个GPIO初始化结构体 // 第一步打开GPIOA的时钟开关 __RCC_GPIOA_CLK_ENABLE(); // 第二步配置引脚属性 GPIO_InitStruct.Pins OLED_SCL_PIN | OLED_MOSI_PIN | OLED_RES_PIN | OLED_DC_PIN | OLED_CS_PIN; // 要初始化的所有引脚 GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; // 模式推挽输出。推挽输出驱动能力强适合直接控制。 GPIO_InitStruct.Speed GPIO_SPEED_HIGH; // 输出速度高速。对于模拟SPI速度可以设高一些。 GPIO_Init(OLED_GPIO_PORT, GPIO_InitStruct); // 调用初始化函数 // 第三步硬件复位屏幕 OLED_RES_Clr(); // RES引脚拉低开始复位 delay_ms(200); // 保持低电平一段时间确保复位完成 OLED_RES_Set(); // RES引脚拉高结束复位 // 第四步发送SSD1306初始化命令序列这部分代码通常不需要修改直接使用 OLED_WR_Byte(0xAE,OLED_CMD); // 关闭显示 OLED_WR_Byte(0x00,OLED_CMD); // 设置列地址低字节 OLED_WR_Byte(0x10,OLED_CMD); // 设置列地址高字节 // ... 后续一系列初始化命令 OLED_WR_Byte(0xAF,OLED_CMD); // 最后开启显示 }到这里软件模拟SPI的移植就基本完成了。厂家例程里已经写好了模拟时序的函数OLED_WR_Byte我们只需要配置好引脚它就能通过拉高拉低OLED_SCL_PIN和OLED_SDA_PIN来发送数据了。4. 硬件SPI移植解放CPU提升效率软件SPI虽然灵活但需要CPU不断地操作引脚模拟时序比较占用资源。如果项目对显示刷新速度有要求或者CPU还要处理其他任务那么使用硬件SPI是更好的选择。硬件SPI由单片机内部的专用电路负责产生时钟和收发数据CPU只需要把数据扔给SPI数据寄存器就行了效率高得多。4.1 硬件SPI与引脚复用CW32F030C8T6内部有SPI外设。使用硬件SPISCK和MOSI这两个引脚必须是具有SPI复用功能的引脚不能随便选。我们需要查阅CW32的数据手册找到SPI1外设对应的引脚。常见的映射是PA5为SPI1_SCKPA7为SPI1_MOSI。RES、DC、CS这三个控制引脚不涉及SPI协议仍然可以用普通GPIO。硬件SPI接线表屏幕引脚连接到CW32开发板引脚功能备注GNDGND电源地VCC3.3V电源正D0 (SCK)PA5SPI1时钟必须使用此复用功能D1 (MOSI)PA7SPI1主机输出必须使用此复用功能RESPA3复位控制普通GPIODCPA2数据/命令控制普通GPIOCSPA4片选控制普通GPIO软件控制提示我们这里用的是3线SPI模式只发送数据给屏幕只写所以只需要MOSI不需要MISO主机输入。片选CS也由软件控制而不是硬件SPI自动管理。4.2 修改宏定义与初始化硬件SPI的移植需要修改两部分引脚宏定义和初始化函数。首先在oled.h中修改宏定义增加SPI外设和引脚复用功能的定义//-----------------OLED端口定义---------------- #define BSP_SPI1 CW_SPI1 // 使用的SPI外设 // GPIO复用功能配置宏需要根据CW32的库函数来写以下是示例 #define SPI1_AF_SCK() PA05_AFx_SPI1SCK() // 将PA5复用为SPI1_SCK #define SPI1_AF_MOSI() PA07_AFx_SPI1MOSI() // 将PA7复用为SPI1_MOSI #define OLED_GPIO_PORT CW_GPIOA #define OLED_SCL_PIN GPIO_PIN_5 // 硬件SCK #define OLED_MOSI_PIN GPIO_PIN_7 // 硬件MOSI #define OLED_RES_PIN GPIO_PIN_3 #define OLED_DC_PIN GPIO_PIN_2 #define OLED_CS_PIN GPIO_PIN_4 // 控制引脚宏定义与软件SPI相同 #define OLED_RES_Clr() GPIO_WritePin(OLED_GPIO_PORT, OLED_RES_PIN, GPIO_Pin_RESET) #define OLED_RES_Set() GPIO_WritePin(OLED_GPIO_PORT, OLED_RES_PIN, GPIO_Pin_SET) #define OLED_DC_Clr() GPIO_WritePin(OLED_GPIO_PORT, OLED_DC_PIN, GPIO_Pin_RESET) #define OLED_DC_Set() GPIO_WritePin(OLED_GPIO_PORT, OLED_DC_PIN, GPIO_Pin_SET) #define OLED_CS_Clr() GPIO_WritePin(OLED_GPIO_PORT, OLED_CS_PIN, GPIO_Pin_RESET) #define OLED_CS_Set() GPIO_WritePin(OLED_GPIO_PORT, OLED_CS_PIN, GPIO_Pin_SET)然后大幅修改oled.c中的OLED_Init(void)函数加入SPI外设的初始化void OLED_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; SPI_InitTypeDef SPI_InitStructure; // 新增SPI初始化结构体 // 1. 打开时钟 __RCC_GPIOA_CLK_ENABLE(); __RCC_SPI1_CLK_ENABLE(); // 新增打开SPI1的时钟 // 2. 配置PA5和PA7为SPI复用功能 SPI1_AF_SCK(); // PA5复用为SPI1_SCK SPI1_AF_MOSI(); // PA7复用为SPI1_MOSI // 3. 初始化所有用到的GPIO包括SPI引脚和普通控制引脚 GPIO_InitStruct.Pins OLED_SCL_PIN | OLED_MOSI_PIN | OLED_RES_PIN | OLED_DC_PIN | OLED_CS_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; // 注意复用功能下模式可能由SPI外设自动管理但这里先配置为输出 GPIO_InitStruct.Speed GPIO_SPEED_HIGH; GPIO_Init(OLED_GPIO_PORT, GPIO_InitStruct); // 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; // 软件控制片选我们自己用GPIO控制CS SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; // 波特率预分频决定SPI速度 SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; // 高位MSB先发送 SPI_InitStructure.SPI_Speed SPI_Speed_Low; // SPI速度模式 SPI_Init(BSP_SPI1, SPI_InitStructure); // 初始化SPI1 SPI_Cmd(BSP_SPI1, ENABLE); // 使能SPI1外设 // 5. 后续的屏幕复位和初始化命令发送与软件SPI相同 OLED_RES_Clr(); delay_ms(200); OLED_RES_Set(); // ... 发送初始化命令序列 }注意SPI_CPOL时钟极性和SPI_CPHA时钟相位这两个参数非常重要合起来称为SPI模式。SSD1306通常工作在模式0CPOL0 CPHA0或模式3CPOL1 CPHA1。这里根据例程配置为模式3高空闲第二个边沿采样。如果屏幕不亮可以尝试切换模式。4.3 重写数据发送函数最关键的一步来了软件SPI的OLED_WR_Byte函数内部是用GPIO模拟时序的现在我们要把它改成使用硬件SPI发送。找到oled.c中的void OLED_WR_Byte(u8 dat, u8 cmd)函数替换为以下内容void OLED_WR_Byte(u8 dat, u8 cmd) { // 1. 设置DC引脚决定发送的是命令还是数据 if(cmd) OLED_DC_Set(); // 拉高DC表示接下来是数据 else OLED_DC_Clr(); // 拉低DC表示接下来是命令 // 2. 拉低片选CS选中OLED屏幕 OLED_CS_Clr(); // 3. 等待SPI发送缓冲区为空表示可以写入新数据了 while (SPI_GetFlagStatus(BSP_SPI1, SPI_FLAG_TXE) RESET); // 4. 将要发送的数据写入SPI数据寄存器 SPI_SendData(BSP_SPI1, dat); // 5. 等待SPI接收缓冲区非空对于只写操作这一步主要是为了等待发送完成 while (SPI_GetFlagStatus(BSP_SPI1, SPI_FLAG_RXNE) RESET); // 6. 读取接收到的数据清空缓冲区避免溢出。对于只写屏幕读到的数据可以忽略 uint16_t temp SPI_ReceiveData(BSP_SPI1); // 7. 拉高片选CS结束本次传输 OLED_CS_Set(); OLED_DC_Set(); // 可选将DC恢复为默认高电平 }这个函数的工作流程是先通过DC引脚告诉屏幕接下来要发的是什么然后选中屏幕CS拉低接着通过硬件SPI发送一个字节的数据发送完成后取消选中屏幕。5. 移植验证点亮你的第一行字无论采用哪种SPI方式移植完成后都需要测试。在主函数main.c里写一个简单的显示程序。#include “board.h” #include “oled.h” // 包含OLED驱动头文件 int32_t main(void) { board_init(); // 开发板基础初始化系统时钟、延时等 OLED_Init(); // 初始化OLED屏幕 OLED_Clear(); // 清屏 while(1) { // 在屏幕不同位置以不同字体大小显示“ABC” OLED_ShowString(0, 0, (uint8_t *)“ABC”, 8, 1); // 坐标(0,0)8号字体 OLED_ShowString(0, 8, (uint8_t *)“ABC”, 12, 1); // 坐标(0,8)12号字体 OLED_ShowString(0, 20, (uint8_t *)“ABC”, 16, 1);// 坐标(0,20)16号字体 OLED_ShowString(0, 36, (uint8_t *)“ABC”, 24, 1);// 坐标(0,36)24号字体 OLED_Refresh(); // 更新显示到屏幕 delay_ms(500); // 延时500ms OLED_Clear(); // 清屏实现闪烁效果可选 delay_ms(500); } }编译程序下载到CW32开发板上电后应该能看到屏幕上显示出大小不同的“ABC”字符串。如果屏幕没有显示别着急按以下步骤排查检查接线确保VCC接3.3VGND接地特别是D0、D1、RES、DC、CS这五根信号线是否连接正确且牢固。检查电源用万用表量一下屏幕VCC和GND之间是不是3.3V。检查初始化序列确保OLED_Init()函数中的复位时序拉低RES 200ms正确执行。检查SPI模式如果用的是硬件SPI尝试将SPI_CPOL和SPI_CPHA改为模式0CPOLLow,CPHA1Edge。检查波特率硬件SPI的预分频SPI_BaudRatePrescaler不要设得太快可以先从SPI_BaudRatePrescaler_64或_32开始尝试。逻辑分析仪如果有条件用逻辑分析仪抓一下SCK、MOSI、DC、CS的波形看数据是否正常发出时序是否符合要求。移植成功的完整工程代码包含软硬件SPI版本可以从提供的链接下载参考。希望这篇教程能让你顺利点亮手中的小屏幕在CW32的开发路上迈出坚实的一步。实际项目中如果不需要高速刷新软件SPI简单够用如果需要频繁更新显示内容硬件SPI的优势就非常明显了。