GD32VW553驱动0.96寸SPI单色OLED屏(SSD1306)移植与实战

GD32VW553驱动0.96寸SPI单色OLED屏(SSD1306)移植与实战 GD32VW553驱动0.96寸SPI单色OLED屏(SSD1306)移植与实战最近在玩GD32VW553这块RISC-V的开发板想给它接个小屏幕显示点信息0.96寸的OLED屏是个不错的选择小巧省电。网上找了一圈发现很多STM32的驱动代码但直接用在GD32VW553上不行得自己动手移植。今天我就把整个移植过程从硬件连接到代码修改一步步分享给大家保证你跟着做就能点亮屏幕。1. 硬件准备与连接1.1 认识我们的OLED屏咱们用的这块屏是市面上最常见的0.96寸OLED驱动芯片是SSD1306通信接口是SPI。先看看它的基本参数参数规格工作电压3.3V工作电流约15mA模块尺寸27.3 x 27.8 mm分辨率128 x 64 像素驱动芯片SSD1306通信协议SPI管脚数量7 Pin (2.54mm间距排针)注意这块屏是3.3V供电的千万别接5V会烧掉1.2 引脚定义与连接OLED模块有7个引脚每个引脚的作用如下OLED引脚功能说明连接GD32VW553引脚GND电源地GNDVCC电源正(3.3V)3V3SCLSPI时钟线(SCK)PA11SDASPI数据线(MOSI)PA9RES复位信号PA7DC数据/命令选择PA6CS片选信号PB11这里我选择的引脚都是GD32VW553上比较常用的GPIO你也可以根据自己需求调整只要保证SPI引脚对应正确就行。连接的时候要注意先断电再接线防止短路杜邦线要插牢接触不良最头疼电源和地线一定要接对2. 工程搭建与文件准备2.1 获取驱动源码首先需要找到OLED的驱动代码。我用的这份代码是从一个通用的SSD1306驱动修改过来的包含了基本的显示功能。提示如果你手头没有现成的驱动代码可以到文章开头提到的资料链接下载提取码是8888。2.2 创建工程模板如果你还没有GD32VW553的工程建议先创建一个基础工程模板。GD32官方提供了完整的开发环境配置指南这里我就不赘述了。关键步骤使用GD32VW553的官方固件库配置好系统时钟和延时函数确保SPI外设能正常使用2.3 添加OLED驱动文件把下载的OLED驱动文件复制到你的工程目录里主要需要两个文件oled.c- 驱动实现文件oled.h- 头文件包含函数声明和宏定义在工程中添加这两个文件然后在main.c中包含oled.h头文件。3. 驱动代码移植与修改3.1 修改头文件配置首先打开oled.h文件我们需要修改引脚定义和数据类型定义。#ifndef __OLED_H #define __OLED_H #include gd32vw55x.h #include systick.h // 定义常用的数据类型 #ifndef u8 #define u8 uint8_t #endif #ifndef u16 #define u16 uint16_t #endif #ifndef u32 #define u32 uint32_t #endif // 延时函数宏定义 #ifndef delay_ms #define delay_ms(x) delay_1ms(x) #endif #ifndef delay_us #define delay_us(x) delay_1us(x) #endif //-----------------OLED端口定义---------------- // 使能相关外设时钟 #define OLED_RCU_ENABLE() rcu_periph_clock_enable(RCU_GPIOA); \ rcu_periph_clock_enable(RCU_GPIOB); \ rcu_periph_clock_enable(RCU_SPI) // SPI引脚定义 #define OLED_SCL_PORT GPIOA #define OLED_SCL_PIN GPIO_PIN_11 #define OLED_SCL_AF GPIO_AF_0 #define OLED_SDA_PORT GPIOA #define OLED_SDA_PIN GPIO_PIN_9 #define OLED_SDA_AF GPIO_AF_0 // 控制引脚定义 #define OLED_RES_PORT GPIOA #define OLED_RES_PIN GPIO_PIN_7 #define OLED_DC_PORT GPIOA #define OLED_DC_PIN GPIO_PIN_6 #define OLED_CS_PORT GPIOB #define OLED_CS_PIN GPIO_PIN_11 // 控制引脚操作宏 #define OLED_RES_Clr() gpio_bit_write(OLED_RES_PORT, OLED_RES_PIN, 0) // RES低电平 #define OLED_RES_Set() gpio_bit_write(OLED_RES_PORT, OLED_RES_PIN, 1) // RES高电平 #define OLED_DC_Clr() gpio_bit_write(OLED_DC_PORT, OLED_DC_PIN, 0) // DC低电平 #define OLED_DC_Set() gpio_bit_write(OLED_DC_PORT, OLED_DC_PIN, 1) // DC高电平 #define OLED_CS_Clr() gpio_bit_write(OLED_CS_PORT, OLED_CS_PIN, 0) // CS低电平 #define OLED_CS_Set() gpio_bit_write(OLED_CS_PORT, OLED_CS_PIN, 1) // CS高电平 // 命令/数据模式定义 #define OLED_CMD 0 // 写命令 #define OLED_DATA 1 // 写数据 // 函数声明 void OLED_Init(void); void OLED_Clear(void); void OLED_Refresh(void); void OLED_ShowChar(u8 x, u8 y, u8 chr, u8 size1, u8 mode); void OLED_ShowString(u8 x, u8 y, u8 *chr, u8 size1, u8 mode); void OLED_ShowChinese(u8 x, u8 y, u8 num, u8 size1, u8 mode); void OLED_ShowNum(u8 x, u8 y, u32 num, u8 len, u8 size1, u8 mode); void OLED_ShowPicture(u8 x, u8 y, u8 sizex, u8 sizey, u8 BMP[], u8 mode); // ... 其他函数声明 #endif这里有几个关键点需要注意把原来的#include sys.h改成了#include systick.h因为GD32的延时函数在systick.h里引脚定义要和你实际连接的引脚一致GPIO复用功能GPIO_AF_0表示SPI功能这是GD32的配置方式3.2 修改初始化函数接下来修改oled.c中的OLED_Init()函数这是驱动的核心初始化部分。void OLED_Init(void) { // 1. 使能相关外设时钟 OLED_RCU_ENABLE(); // 2. 配置SPI引脚为复用功能 gpio_af_set(OLED_SCL_PORT, OLED_SCL_AF, OLED_SCL_PIN); gpio_af_set(OLED_SDA_PORT, OLED_SDA_AF, OLED_SDA_PIN); // 3. 设置引脚模式 gpio_mode_set(OLED_SCL_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, OLED_SCL_PIN); gpio_mode_set(OLED_SDA_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, OLED_SDA_PIN); gpio_mode_set(OLED_RES_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, OLED_RES_PIN); gpio_mode_set(OLED_DC_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, OLED_DC_PIN); gpio_mode_set(OLED_CS_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, OLED_CS_PIN); // 4. 设置输出选项 gpio_output_options_set(OLED_SCL_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, OLED_SCL_PIN); gpio_output_options_set(OLED_SDA_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, OLED_SDA_PIN); gpio_output_options_set(OLED_RES_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, OLED_RES_PIN); gpio_output_options_set(OLED_DC_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, OLED_DC_PIN); gpio_output_options_set(OLED_CS_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, OLED_CS_PIN); // 5. 初始化控制引脚状态 OLED_CS_Set(); // 片选拉高初始不选中 OLED_DC_Set(); // 数据/命令线拉高 // 6. 配置SPI参数 spi_parameter_struct spi_init_struct; /* 配置SPI参数 */ spi_init_struct.trans_mode SPI_TRANSMODE_FULLDUPLEX; // 全双工模式 spi_init_struct.device_mode SPI_MASTER; // 主机模式 spi_init_struct.frame_size SPI_FRAMESIZE_8BIT; // 8位数据帧 spi_init_struct.clock_polarity_phase SPI_CK_PL_HIGH_PH_2EDGE; // 时钟极性相位 spi_init_struct.nss SPI_NSS_SOFT; // 软件控制片选 spi_init_struct.prescale SPI_PSC_32; // 32分频 spi_init_struct.endian SPI_ENDIAN_MSB; // 高位在前 // 7. 初始化SPI spi_init(spi_init_struct); /* 使能SPI */ spi_enable(); // 8. OLED硬件复位 OLED_RES_Clr(); // 复位引脚拉低 delay_ms(200); // 保持低电平200ms OLED_RES_Set(); // 复位引脚拉高 // 9. 发送初始化命令序列 OLED_WR_Byte(0xAE, OLED_CMD); // 关闭显示 OLED_WR_Byte(0x00, OLED_CMD); // 设置低列地址 OLED_WR_Byte(0x10, OLED_CMD); // 设置高列地址 OLED_WR_Byte(0x40, OLED_CMD); // 设置起始行地址 OLED_WR_Byte(0x81, OLED_CMD); // 设置对比度控制 OLED_WR_Byte(0xCF, OLED_CMD); // 设置对比度值 OLED_WR_Byte(0xA1, OLED_CMD); // 设置段重映射正常显示 OLED_WR_Byte(0xC8, OLED_CMD); // 设置COM扫描方向正常显示 OLED_WR_Byte(0xA6, OLED_CMD); // 设置正常显示 OLED_WR_Byte(0xA8, OLED_CMD); // 设置多路复用比率 OLED_WR_Byte(0x3f, OLED_CMD); // 1/64占空比 OLED_WR_Byte(0xD3, OLED_CMD); // 设置显示偏移 OLED_WR_Byte(0x00, OLED_CMD); // 无偏移 OLED_WR_Byte(0xd5, OLED_CMD); // 设置显示时钟分频比 OLED_WR_Byte(0x80, OLED_CMD); // 设置时钟为100帧/秒 OLED_WR_Byte(0xD9, OLED_CMD); // 设置预充电周期 OLED_WR_Byte(0xF1, OLED_CMD); // 预充电15个时钟放电1个时钟 OLED_WR_Byte(0xDA, OLED_CMD); // 设置COM引脚硬件配置 OLED_WR_Byte(0x12, OLED_CMD); // 备用配置 OLED_WR_Byte(0xDB, OLED_CMD); // 设置VCOMH OLED_WR_Byte(0x40, OLED_CMD); // 设置VCOM取消选择级别 OLED_WR_Byte(0x20, OLED_CMD); // 设置页寻址模式 OLED_WR_Byte(0x02, OLED_CMD); // 页寻址模式 OLED_WR_Byte(0x8D, OLED_CMD); // 电荷泵设置 OLED_WR_Byte(0x14, OLED_CMD); // 使能电荷泵 OLED_WR_Byte(0xA4, OLED_CMD); // 禁用整个显示开启 OLED_WR_Byte(0xA6, OLED_CMD); // 禁用反色显示 // 10. 清屏并开启显示 OLED_Clear(); OLED_WR_Byte(0xAF, OLED_CMD); // 开启显示 }这个初始化函数做了很多事情我简单解释一下关键步骤步骤1-4配置GPIOSPI引脚要设置为复用功能控制引脚设置为推挽输出步骤5初始化控制引脚状态防止误操作步骤6-7配置SPI参数这里用的是软件控制片选时钟32分频步骤8硬件复位OLED这是必须的确保OLED从确定状态开始工作步骤9发送一系列初始化命令这些命令是SSD1306芯片规定的步骤10清屏并开启显示注意SPI的时钟极性相位配置SPI_CK_PL_HIGH_PH_2EDGE要和OLED屏的时序要求一致不同厂家的屏可能不同。3.3 修改字节写入函数SPI通信的核心是OLED_WR_Byte()函数它负责通过SPI发送一个字节的数据或命令。void OLED_WR_Byte(u8 dat, u8 cmd) { uint8_t recv_data 0; // 1. 设置DC引脚命令模式还是数据模式 if(cmd) OLED_DC_Set(); // 数据模式 else OLED_DC_Clr(); // 命令模式 // 2. 拉低片选选中OLED OLED_CS_Clr(); // 3. 等待发送缓冲区为空 while(RESET spi_flag_get(SPI_FLAG_TBE)); // 4. 通过SPI发送数据 spi_data_transmit(dat); // 5. 等待接收缓冲区不为空全双工模式需要读取 while(RESET spi_flag_get(SPI_FLAG_RBNE)); recv_data spi_data_receive(); // 6. 拉高片选取消选中 OLED_CS_Set(); OLED_DC_Set(); // 恢复DC为高电平 }这个函数的关键点DC引脚控制OLED通过DC引脚区分发送的是命令还是数据0是命令1是数据片选控制每次通信前要拉低CS通信完成后拉高SPI发送使用GD32的SPI库函数发送数据接收数据全双工模式下发送的同时也会接收虽然用不上但要读取以清空缓冲区4. 编写测试程序4.1 主函数测试代码现在驱动已经移植好了我们来写个测试程序验证一下。在main.c中添加以下代码#include gd32vw55x.h #include systick.h #include stdio.h #include main.h #include gd32vw553h_eval.h #include oled.h #include bmp.h // 如果有图片数据的话 int main(void) { // 1. 系统初始化 systick_config(); // 配置系统滴答定时器 eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1); // 2. 初始化外设可选 gd_eval_led_init(LED1); gd_eval_com_init(EVAL_COM0); // 3. 初始化OLED OLED_Init(); OLED_ColorTurn(0); // 0正常显示1反色显示 OLED_DisplayTurn(0); // 0正常方向1旋转180度 uint8_t t ; // 用于显示变化的ASCII字符 while(1) { // 测试1显示图片如果有图片数据 // OLED_ShowPicture(0, 0, 128, 64, BMP1, 1); // OLED_Refresh(); // delay_1ms(1000); // OLED_Clear(); // 测试2显示汉字 OLED_ShowChinese(0, 0, 0, 16, 1); // 显示第0个汉字16x16大小 OLED_ShowChinese(18, 0, 1, 16, 1); // 显示第1个汉字 OLED_ShowChinese(36, 0, 2, 16, 1); // 显示第2个汉字 OLED_ShowChinese(54, 0, 3, 16, 1); // 显示第3个汉字 OLED_ShowChinese(72, 0, 4, 16, 1); // 显示第4个汉字 // 测试3显示英文字符串 OLED_ShowString(8, 16, GD32VW553, 16, 1); OLED_ShowString(20, 32, OLED Test, 16, 1); // 测试4显示ASCII字符 OLED_ShowString(0, 48, ASCII:, 16, 1); OLED_ShowChar(48, 48, t, 16, 1); // 显示ASCII字符 t; if(t ~) t ; // 循环显示 // 测试5显示数字 OLED_ShowNum(90, 48, 1234, 4, 16, 1); OLED_Refresh(); // 更新显示 delay_1ms(500); // 延时500ms OLED_Clear(); // 清屏 // 测试6不同大小的字符 OLED_ShowString(0, 0, ABC, 8, 1); // 6x8字体 OLED_ShowString(0, 8, ABC, 12, 1); // 6x12字体 OLED_ShowString(0, 20, ABC, 16, 1); // 8x16字体 OLED_ShowString(0, 36, ABC, 24, 1); // 12x24字体 OLED_Refresh(); delay_1ms(500); OLED_Clear(); } }4.2 添加字库文件要显示汉字和字符还需要字库文件。通常是一个oledfont.h文件里面包含了各种字体的点阵数据。如果你没有现成的字库可以自己生成或者从网上下载。字库文件通常包含ASCII字符的点阵数据6x8, 8x16等常用汉字的点阵数据16x16, 24x24等在oled.c中需要包含这个字库文件#include oledfont.h5. 常见问题与调试技巧5.1 屏幕不亮或显示异常如果屏幕完全不亮按以下步骤排查检查电源用万用表测量VCC和GND之间是否为3.3V检查接线确认所有引脚连接正确特别是SCL、SDA、DC、RES、CS这5根线检查复位RES引脚要先拉低200ms再拉高这个时序很重要检查SPI配置确认SPI时钟频率不要太高OLED屏一般支持到10MHz左右5.2 显示内容错乱如果屏幕能亮但显示内容不对检查DC引脚发送命令时DC要拉低发送数据时DC要拉高检查SPI模式SSD1306通常使用模式0或模式3确认SPI_CK_PL_HIGH_PH_2EDGE是否正确检查初始化命令确保所有初始化命令都正确发送检查显存更新修改显存后要调用OLED_Refresh()才能更新到屏幕5.3 显示有残影或闪烁调整对比度修改初始化命令中的对比度值0xCF检查电源稳定性OLED对电源比较敏感确保3.3V稳定调整刷新频率降低刷新频率或增加延时5.4 性能优化建议减少全屏刷新只刷新变化的部分不要每次都全屏刷新使用局部更新SSD1306支持设置显示区域可以只更新特定区域优化SPI速度在保证稳定的前提下提高SPI时钟频率使用DMA传输如果需要大量数据传输可以考虑使用SPI的DMA功能6. 实际应用扩展移植成功后你可以在实际项目中使用这个OLED驱动了。这里分享几个我在项目中常用的技巧显示实时数据比如显示传感器数据、系统状态等// 显示温度数据 void show_temperature(float temp) { char buffer[20]; sprintf(buffer, Temp: %.1fC, temp); OLED_ShowString(0, 0, buffer, 16, 1); OLED_Refresh(); }制作简单菜单通过按键切换显示内容typedef enum { MENU_MAIN, MENU_SETTINGS, MENU_INFO } menu_state_t; menu_state_t current_menu MENU_MAIN; void update_display(void) { OLED_Clear(); switch(current_menu) { case MENU_MAIN: OLED_ShowString(0, 0, Main Menu, 16, 1); OLED_ShowString(0, 20, 1. Settings, 16, 1); OLED_ShowString(0, 40, 2. Info, 16, 1); break; case MENU_SETTINGS: // 显示设置菜单 break; case MENU_INFO: // 显示信息 break; } OLED_Refresh(); }显示进度条用于显示进度或电量void draw_progress_bar(u8 x, u8 y, u8 width, u8 height, u8 progress) { // 画边框 OLED_DrawLine(x, y, x width, y, 1); OLED_DrawLine(x, y height, x width, y height, 1); OLED_DrawLine(x, y, x, y height, 1); OLED_DrawLine(x width, y, x width, y height, 1); // 填充进度 u8 fill_width (width - 2) * progress / 100; for(u8 i 0; i height - 2; i) { OLED_DrawLine(x 1, y 1 i, x 1 fill_width, y 1 i, 1); } OLED_Refresh(); }移植过程中最常遇到的问题就是引脚配置不对和SPI时序问题。如果遇到问题建议先用逻辑分析仪或示波器抓一下SPI波形看看时钟、数据、片选这些信号是否正常。还有一个坑要注意GD32的SPI库函数和STM32的有些差异特别是时钟配置部分。如果发现通信不正常可以尝试调整SPI的预分频值降低通信速度试试。好了以上就是GD32VW553驱动0.96寸OLED屏的完整移植过程。按照这个步骤来应该能顺利点亮屏幕。在实际项目中你可以根据需要添加更多显示功能比如图表、动画等。