STM32 —— SPI 读写 OLED 屏幕
STM32 —— SPI 读写 OLED 屏幕
实验目的
- 理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI或IIC接口实现以下功能:
显示自己的学号和姓名
上下或左右的滑动显示长字符,比如“Hello,欢迎来到重庆交通大学物联网205实训室!”或者一段歌词或诗词(使用硬件刷屏模式)
实验原理
具体的实验原理可以看我的另外几篇博客:
这里仅需要使用最基本的功能,实现显示汉字,并能够实现字符串滑动效果即可
HAL 库方法
CubeMX 项目配置
RCC 配置
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119161853081-1881649833.png)
SPI 配置
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119162059843-511575645.png)
这里我们的从机才是 OLED ,我们只需要实现单片机(主机)发送数据给 OLED (从机),无需 OLED 返回数据(而且也返回不了),所以选择 Transmission Only Master (仅主机传输)
这里的从机只有 OLED 一个,所以选择 Hardware NSS Singnal Disable(取消硬件片选),如果有多个从机,那就要开启
主机片选从机的条件是 CS 脚的电平拉低,如果只有一个从机的时候,可以将 CS 脚直接接地,将代码中所有关于 CS 脚的函数删掉即可,这样可以节省一个 I/O 口
因为 SPI 是时序工作方式,需要时钟,通过单片机的时钟分频而来,这里 4 分频就已经够用了
在我们配置好 SPI 之后,系统会自动帮我们配置好两个引脚:SPI1_SCK 和 SPI1_MOSI ,这两个引脚分别用于接屏幕上的 D0 和 D1 :
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119181107208-1488727157.png)
sys 配置
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120011613264-1960266122.png)
时钟配置
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119161919007-144110521.png)
引脚配置
这里在配置 SPI 的时候,已经自动配置好了 SCK(D0) 和 MOSI(D1) 这两个引脚,这里我我们还需要配置 3 个输出引脚,分别作为 RES 、DC 和 CS 引脚,配置如图:
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120011717135-796745154.png)
驱动程序准备
我们所使用的芯片是 STM32C8T6 ,但是由于官方历程中没有我们要使用的 STM32C8T6 的版本,所以这里选择使用 STM32RCT6 的版本
在我们所使用的芯片上已经存在硬件 SPI ,所以这里直接选择硬件 SPI 的 4 引脚接线法的版本即可
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119181618524-583436928.png)
然后我们进入其目录下,将 OLED 的驱动程序复制到我们的项目路径下,OLED 程序路径如下:\0.96inch_SPI_OLED_Module_SSD1306_MSP096X_V1.0\1-Demo\Demo_STM32\0.96inch_OLED_Demo_STM32F103RCT6_Hardware_4-wire_SPI\HARDWARE\OLED :
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119182712193-920413495.png)
这里我们将整个 OLED 目录都复制即可
补充一点,这里不尽需要驱动代码,还需要代码中的 gui.h 和 gui.c 这两个文件,可以自行新建文件及逆行你高靠背
代码设计
添加硬件驱动代码
首先我们需要添加我们的硬件驱动代码,并添加好头文件引用路径:
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120005542214-292471088.png)
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120005504578-1498893736.png)
这里可以使用文末给出的基于 HAL 库的驱动代码,但是代码中没有滑动相关代码,需要自己添加,这里建议使用官方历程驱动代码进行修改
官方历程中的驱动代码是基于标准库的代码,需要进行一定的修改,如果想要直接使用,也可以在文末下载我修改好的驱动代码
定义引脚
然后我们需要去修改自定义引脚,下面将提供两种方法:
- 直接修改 oled.h 头文件中的函数中的参数,我们直接将参数中设定的引脚改为我们在前面:已经设定好的引脚即可:
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119222209717-901046955.png)
修改后代码如下:
#define OLED_RES_Clr() HAL_GPIO_WritePin(OLED_RES_GPIO_Port,OLED_RES_Pin,GPIO_PIN_RESET)//RES
#define OLED_RES_Set() HAL_GPIO_WritePin(OLED_RES_GPIO_Port,OLED_RES_Pin,GPIO_PIN_SET)
#define OLED_DC_Clr() HAL_GPIO_WritePin(OLED_DC_GPIO_Port,OLED_DC_Pin,GPIO_PIN_RESET)//DC
#define OLED_DC_Set() HAL_GPIO_WritePin(OLED_DC_GPIO_Port,OLED_DC_Pin,GPIO_PIN_SET)
#define OLED_CS_Clr() HAL_GPIO_WritePin(OLED_CS_GPIO_Port,OLED_CS_Pin,GPIO_PIN_RESET)//CS
#define OLED_CS_Set() HAL_GPIO_WritePin(OLED_CS_GPIO_Port,OLED_CS_Pin,GPIO_PIN_SET)
- 在 oled.c 源文件中重定义对应的引脚,按照函数中的参数对我们使用的引脚进行定义即可:
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119230706593-1955798482.png)
定义引脚的代码如下:
// 设置(定义)RES、DC、CS 所使用的引脚
// RES
#define OLED_RES_GPIO_Port GPIOA
#define OLED_RES_Pin GPIO_PIN_6
// DC
#define OLED_DC_GPIO_Port GPIOA
#define OLED_DC_Pin GPIO_PIN_4
// CS
#define OLED_CS_GPIO_Port GPIOA
#define OLED_CS_Pin GPIO_PIN_3
#define OLED_RST_Clr() HAL_GPIO_WritePin(OLED_RES_GPIO_Port,OLED_RES_Pin,GPIO_PIN_RESET)//RES
#define OLED_RST_Set() HAL_GPIO_WritePin(OLED_RES_GPIO_Port,OLED_RES_Pin,GPIO_PIN_SET)
#define OLED_DC_Clr() HAL_GPIO_WritePin(OLED_DC_GPIO_Port,OLED_DC_Pin,GPIO_PIN_RESET)//DC
#define OLED_DC_Set() HAL_GPIO_WritePin(OLED_DC_GPIO_Port,OLED_DC_Pin,GPIO_PIN_SET)
#define OLED_CS_Clr() HAL_GPIO_WritePin(OLED_CS_GPIO_Port,OLED_CS_Pin,GPIO_PIN_RESET)//CS
#define OLED_CS_Set() HAL_GPIO_WritePin(OLED_CS_GPIO_Port,OLED_CS_Pin,GPIO_PIN_SET)
这里比较推荐第二种方案,便于我们后面修改了引脚后重新对引脚进行修改
SPI 写入
由于我们使用的是 SPI 协议,而且我们配置的是 SPI1 ,所以这里需要添加 SPI 写入代码到 oled.c 源文件中,代码如下:
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{
uint8_t rxdata = 1;
#if onlysend //如果只配置了发送 使用HAL_SPI_Transmit
HAL_SPI_Transmit(&hspi1,&TxData,1,100);
//while(HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY);
#else//如果配置了发送和接收 使用HAL_SPI_TransmitReceive
if(HAL_OK == HAL_SPI_TransmitReceive(&hspi1,&TxData,&rxdata,1,100))
{
return rxdata;
};
#endif
return rxdata;
}
然后我们需要修改我们的 OLED_WR_Byte 函数(命令发送函数),,将标准库函数 SPI_WriteByte() 修改为 我们刚写的函数 SPI1_ReadWriteByte() ,这样就配置好了我们的 SPI 发送命令
重写 OLED 端口初始化函数
由于官方历程给的是基于标准库的函数,这里需要修改为 HAL 库中对应的函数,官方历程代码如图:
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120000512192-1591389614.png)
修改后的代码如下:
void OLED_Init_GPIO(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_6, GPIO_PIN_RESET);//使能B端口时钟
GPIO_InitStructure.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_6; //GPIOB10,11,12
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_6, GPIO_PIN_SET);//使能B端口时钟
}
添加字库
如果我们是需要显示图片或汉字,就使用图片或汉字转换点阵的工具(下载连接放到文章末尾),将图片或汉字按照一定规则转换为点阵,然后再进行显示,这里以显示汉字为例:
首先设置汉字转点阵转换格式:
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119224700628-260621646.png)
然后进行转换:
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119224756451-382512350.png)
最后拷贝到:oledfont.c 的数组中:
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120005958422-290291521.png)
调用函数
在调用函数之前,我们需要在 main.c 源文件中调用相关头文件:
#include "oled.h"
#include "bmp.h"
然后我们就可以根据驱动文件中的函数进行设计代码:
首先需要初始化 OLED 及相关 GPIO 引脚,然后按照 gui.c 和 gui.h 中给出的函数定义进行函数调用即可,下面给出两个示例(只给出 main 函数代码):
- 显示固定内容:
GUI_ShowString(40,0,(uint8_t *)"ppqppl",16,1);
GUI_ShowCHinese(40,48,16,(uint8_t *)"欢迎你",1);
- 滑动显示内容:
OLED_Clear(0);
OLED_WR_Byte(0x2E,OLED_CMD); //关闭滚动
OLED_WR_Byte(0x27,OLED_CMD); //水平向左或者右滚动 26/27
OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
OLED_WR_Byte(0x00,OLED_CMD); //起始页 0
OLED_WR_Byte(0x07,OLED_CMD); //滚动时间间隔
OLED_WR_Byte(0x02,OLED_CMD); //终止页 2
OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
OLED_WR_Byte(0xFF,OLED_CMD); //虚拟字节
// 诗句
GUI_ShowCHinese(10,0,16,(uint8_t *)"春有百花秋有月",1);
OLED_WR_Byte(0x2F,OLED_CMD); //开启滚动
while (1)
{
//开启滚动
OLED_WR_Byte(0x2F,OLED_CMD);
// 延时,每次滚动需要通过延时来定位滚动的多少,这里可以自行测试延时的多少,
HAL_Delay(6200);
//这里提供滚动一个完整页面的延时是 6200 ms 即 6.2s
}
修改清屏(全黑)函数(默认不修改,除非产生报错)
有些人在写完程序后会在 OLED_Clear(0) 处产生报错,而当我们删除参数,仅调用函数时 OLED_Clear() 就不会,那么我们就需要修改 OLED_Clear 源代码
由于 HAL 库中的 OLED_Clear 函数默认不传参,而标准库中传参为 0 ,由于 HAL 库与标准库的不同,所以这里需要反转判断条件:
原函数如图片:
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120004055340-735948507.png)
修改后的函数如下:
void OLED_Clear(unsigned dat)
{
if(dat)
{
memset(OLED_buffer,0,sizeof(OLED_buffer));
}
else
{
memset(OLED_buffer,0xff,sizeof(OLED_buffer));
}
OLED_Display();
}
标准库方法
使用标准库比较简单,只需要将官方历程中的 STM32F103RCT6 对应的引脚改为 STM32F103C8T6 中对应的引脚即可使用,继续汉字显示可以直接调用驱动代码中给定的函数即可,这里分别介绍固定位置显示和滑动显示:
- 显示固定内容:
GUI_ShowString(40,0,(uint8_t *)"ppqppl",16,1);
GUI_ShowCHinese(40,48,16,(uint8_t *)"欢迎你",1);
- 滑动显示内容:
OLED_Clear(0);
OLED_WR_Byte(0x2E,OLED_CMD); //关闭滚动
OLED_WR_Byte(0x27,OLED_CMD); //水平向左或者右滚动 26/27
OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
OLED_WR_Byte(0x00,OLED_CMD); //起始页 0
OLED_WR_Byte(0x07,OLED_CMD); //滚动时间间隔
OLED_WR_Byte(0x02,OLED_CMD); //终止页 2
OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
OLED_WR_Byte(0xFF,OLED_CMD); //虚拟字节
// 诗句
GUI_ShowCHinese(10,0,16,(uint8_t *)"春有百花秋有月",1);
OLED_WR_Byte(0x2F,OLED_CMD); //开启滚动
while (1)
{
//开启滚动
OLED_WR_Byte(0x2F,OLED_CMD);
// 延时,每次滚动需要通过延时来定位滚动的多少,这里可以自行测试延时的多少,
HAL_Delay(6200);
//这里提供滚动一个完整页面的延时是 6200 ms 即 6.2s
}
官方历程中给出的方法位标准库方法,这里大家可以参考官方历程,但是官方历程中并没有 STM32F103C8T6 芯片对应版本,如需要使用对应版本,需要通过 STM32RCT6 版本进行修改。
运行测试
虚拟串口测试
由于需要使用外界 OLED 显示屏,无法进行虚拟串口测试
Proteus 仿真模拟
目前尚未解决 Proteus 仿真模拟中的一些问题,正在积极解决,会在解决后及时更新
接线示例
标准库接线方法
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120012440435-758138043.jpg)
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119183821130-453963998.jpg)
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119183827327-1514351010.jpg)
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119183831172-1018913818.jpg)
HAL 库接线方法
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120012440435-758138043.jpg)
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120012357675-935164040.jpg)
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120012401977-1977140867.jpg)
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221119183827327-1514351010.jpg)
运行结果
标准库运行结果
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120015644762-982184849.gif)
HAL库运行结果
![](https://img2022.cnblogs.com/blog/2829072/202211/2829072-20221120013738972-471755776.gif)
结果分析
能够正确的在屏幕上显示我们需要的文字,代码测试成功
错误解决方法
这里在我们刚刚进行驱动代码移植的时候,会有几百个报错,只需要我们耐心按照上文进行修改,就能够解决所有由于标准库和 HAL 库产生的差异而报错的问题
错误如下:
开始屏幕并不是全黑而是全亮
解决方法:
由于 HAL 库中使用 OLED_Clear 函数默认不传参,这里我们需要修改 OLED_Clear 原函数判断条件,从而达到清屏不是全亮而是全黑的效果,按照我们上文介绍的方法进行修改即可
参考资料
极度推荐:
这两篇文章帮我们解决了在 HAL 库下使用 OLED 屏幕
下载资料
-
这里提供修改前的驱动代码,标准库可以直接使用:
如果上述连接失效可以使用下面的连接:
-
这里提供我进行了修改之后的驱动代码,HAL 库可以直接使用,无需再进行修改:
如果上面给出的 Github 的下载源失效,可以尝试如下下载源:
-
这里特别感谢一下一位叫 连卡佛哈利 的博主,提供了在 HAL 库下使用 OLED 的相关驱动,但是在提供驱动中没有滑动相关的代码,下面是下载连接
连卡佛哈利 0.96 oled HAL库驱动 SPI STM32
博主提供的驱动虽然不完善,但是为我们提供了一个相关思路