STM32 —— SPI 读写 OLED 屏幕

STM32 —— SPI 读写 OLED 屏幕

实验目的

  1. 理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI或IIC接口实现以下功能:

显示自己的学号和姓名

上下或左右的滑动显示长字符,比如“Hello,欢迎来到重庆交通大学物联网205实训室!”或者一段歌词或诗词(使用硬件刷屏模式)

实验原理

具体的实验原理可以看我的另外几篇博客:

  1. STM32 —— SPI 协议入门

  2. STM32 —— OLED 屏幕入门

这里仅需要使用最基本的功能,实现显示汉字,并能够实现字符串滑动效果即可

HAL 库方法

CubeMX 项目配置

RCC 配置

SPI 配置

这里我们的从机才是 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 :

sys 配置

时钟配置

引脚配置

这里在配置 SPI 的时候,已经自动配置好了 SCK(D0) 和 MOSI(D1) 这两个引脚,这里我我们还需要配置 3 个输出引脚,分别作为 RES 、DC 和 CS 引脚,配置如图:

驱动程序准备

我们所使用的芯片是 STM32C8T6 ,但是由于官方历程中没有我们要使用的 STM32C8T6 的版本,所以这里选择使用 STM32RCT6 的版本

在我们所使用的芯片上已经存在硬件 SPI ,所以这里直接选择硬件 SPI 的 4 引脚接线法的版本即可

然后我们进入其目录下,将 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 :

这里我们将整个 OLED 目录都复制即可

补充一点,这里不尽需要驱动代码,还需要代码中的 gui.h 和 gui.c 这两个文件,可以自行新建文件及逆行你高靠背

代码设计

添加硬件驱动代码

首先我们需要添加我们的硬件驱动代码,并添加好头文件引用路径:


这里可以使用文末给出的基于 HAL 库的驱动代码,但是代码中没有滑动相关代码,需要自己添加,这里建议使用官方历程驱动代码进行修改

官方历程中的驱动代码是基于标准库的代码,需要进行一定的修改,如果想要直接使用,也可以在文末下载我修改好的驱动代码

定义引脚

然后我们需要去修改自定义引脚,下面将提供两种方法:

  1. 直接修改 oled.h 头文件中的函数中的参数,我们直接将参数中设定的引脚改为我们在前面:已经设定好的引脚即可:

修改后代码如下:

#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)
  1. 在 oled.c 源文件中重定义对应的引脚,按照函数中的参数对我们使用的引脚进行定义即可:

定义引脚的代码如下:

// 设置(定义)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 库中对应的函数,官方历程代码如图:

修改后的代码如下:

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端口时钟
}

添加字库

如果我们是需要显示图片或汉字,就使用图片或汉字转换点阵的工具(下载连接放到文章末尾),将图片或汉字按照一定规则转换为点阵,然后再进行显示,这里以显示汉字为例:

首先设置汉字转点阵转换格式:

然后进行转换:

最后拷贝到:oledfont.c 的数组中:

调用函数

在调用函数之前,我们需要在 main.c 源文件中调用相关头文件:

#include "oled.h"
#include "bmp.h"

然后我们就可以根据驱动文件中的函数进行设计代码:

首先需要初始化 OLED 及相关 GPIO 引脚,然后按照 gui.c 和 gui.h 中给出的函数定义进行函数调用即可,下面给出两个示例(只给出 main 函数代码):

  1. 显示固定内容:
GUI_ShowString(40,0,(uint8_t *)"ppqppl",16,1);

GUI_ShowCHinese(40,48,16,(uint8_t *)"欢迎你",1);
  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 库与标准库的不同,所以这里需要反转判断条件:

原函数如图片:

修改后的函数如下:

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 中对应的引脚即可使用,继续汉字显示可以直接调用驱动代码中给定的函数即可,这里分别介绍固定位置显示和滑动显示:

  1. 显示固定内容:
GUI_ShowString(40,0,(uint8_t *)"ppqppl",16,1);

GUI_ShowCHinese(40,48,16,(uint8_t *)"欢迎你",1);
  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 仿真模拟中的一些问题,正在积极解决,会在解决后及时更新

接线示例

标准库接线方法




HAL 库接线方法





运行结果

标准库运行结果

HAL库运行结果

结果分析

能够正确的在屏幕上显示我们需要的文字,代码测试成功

错误解决方法

这里在我们刚刚进行驱动代码移植的时候,会有几百个报错,只需要我们耐心按照上文进行修改,就能够解决所有由于标准库和 HAL 库产生的差异而报错的问题

错误如下:

开始屏幕并不是全黑而是全亮

解决方法:

由于 HAL 库中使用 OLED_Clear 函数默认不传参,这里我们需要修改 OLED_Clear 原函数判断条件,从而达到清屏不是全亮而是全黑的效果,按照我们上文介绍的方法进行修改即可

参考资料

极度推荐:

  1. 【STM32】HAL库在7针脚0.96寸OLED屏上的移植---硬件SPI(一)

  2. 0.96 oled HAL库驱动 SPI STM32

这两篇文章帮我们解决了在 HAL 库下使用 OLED 屏幕

下载资料

  1. 这里提供修改前的驱动代码,标准库可以直接使用:

    官方历程驱动代码 下载源1

    如果上述连接失效可以使用下面的连接:

    官方历程驱动代码 下载源2

  2. 这里提供我进行了修改之后的驱动代码,HAL 库可以直接使用,无需再进行修改:

    HAL库 OLED 驱动代码 下载源1

    如果上面给出的 Github 的下载源失效,可以尝试如下下载源:

    HAL库 OLED 驱动代码 下载源2

  3. 这里特别感谢一下一位叫 连卡佛哈利 的博主,提供了在 HAL 库下使用 OLED 的相关驱动,但是在提供驱动中没有滑动相关的代码,下面是下载连接

    连卡佛哈利 0.96 oled HAL库驱动 SPI STM32

    博主提供的驱动虽然不完善,但是为我们提供了一个相关思路

posted @ 2022-11-20 02:15  ppqppl  阅读(616)  评论(0编辑  收藏  举报