基于STM32G4的0.96寸OLED显示屏驱动程序(HAL库),支持硬件/软件I2C

基于STM32G474的0.96寸OLED(SSD1306)显示屏驱动程序(4针脚I2C接口),支持硬件IIC/软件IIC,HAL库版。

这款驱动程序比较完善,可以实现 英文、整数、浮点数、汉字、图像、二进制数、十六进制数 等内容显示,可以画点、直线、矩形、圆、椭圆、三角形等,支持多种字体,差不多相当于一个简易版图形库了。

该程序是基于江协科技的代码二次修改的,原版程序是基于STM32F103的,且只支持软件I2C,我修改后支持硬件I2C,也可以修改宏定义改成使用软件I2C。

测试硬件为NUCLEO-G474RE开发板

关于OLED的驱动原理,以及驱动程序的使用教程可以看江协科技的视频:https://url.zeruns.tech/L7j6y

电子/单片机技术交流群:820537762

效果图

I2C协议简介

I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备(那些电平转化芯片),现在被广泛地使用在系统内多个集成电路(IC)间的通讯。

I2C只有一跟数据总线 SDA(Serial Data Line),串行数据总线,只能一位一位的发送数据,属于串行通信,采用半双工通信。

半双工通信:可以实现双向的通信,但不能在两个方向上同时进行,必须轮流交替进行,其实也可以理解成一种可以切换方向的单工通信,同一时刻必须只能一个方向传输,只需一根数据线。

对于I2C通讯协议把它分为物理层和协议层物理层规定通讯系统中具有机械、电子功能部分的特性(硬件部分),确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准(软件层面)。

I2C物理层

I2C 通讯设备之间的常用连接方式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(1) 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。

(2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线SDA(Serial Data Line ),一条串行时钟线SCL(Serial Clock Line )。数据线即用来表示数据,时钟线用于数据收发同步

(3) 总线通过上拉电阻接到电源。当 I2C 设备空闲时会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平

I2C通信时单片机GPIO口必须设置为开漏输出,否则可能会造成短路。

关于更多STM32的I2C相关信息和使用方法可以看这篇文章:https://url.zeruns.tech/JC0Ah

还有江协科技的STM32入门教程:https://www.bilibili.com/video/BV1th411z7sn?p=31

我这里就不详细讲解了。

使用说明

默认是使用硬件IIC的,用的I2C3,SCL是PA8,SDA是PC9。

硬件I2C

STM32CubeMX配置,找到你要用的I2C外设的引脚,并设置引脚功能为SCL和SDA,如下图所示是I2C3的SCL。

接着配置I2C外设,启用对应的I2C外设,速度模式设置为 Fast Mode Plus,速度改成 1000,其他默认就行。

配置GPIO,上面设置完后会自动把那两个引脚配置为复用开漏输出模式,接着只需要把IO输出速度改成 Very High ,还有GPIO标签(User Label)定义分别改成I2C3_SCLI2C3_SDA就行,如果是用的别的I2C也可以设置成别的值,代码对应处要修改一下。改完后点击生成代码。

OLED.c文件里,将 #define OLED_USE_SW_I2C 注释掉,将 #define OLED_USE_HW_I2C 取消注释,如果你用的是别的引脚作为I2C引脚,并且定义了别的名字那就将代码里的 I2C3_SCLI2C3_SDA 也改一下。

软件I2C

STM32CubeMX配置,设置两个引脚作为I2C的SCL和SDA信号线,修改IO口的 User Lable 分别为I2C3_SCLI2C3_SDA,如果改成别的需要到代码里修改一下,IO模式设置为开漏输出,默认输出电平高电平,上拉输出,速度设置到最高,如下图所示。改为后点击生成代码。

OLED.c文件里,将 #define OLED_USE_HW_I2C 注释掉,将 #define OLED_USE_SW_I2C 取消注释,如果你用的是别的引脚作为I2C引脚,并且定义了别的名字那就将代码里的 I2C3_SCLI2C3_SDA 也改一下。

需要用的元件

江协科技的STM32入门套件:https://s.click.taobao.com/NTn9Txt

程序

完整工程下载地址:

百度网盘:链接: https://url.zeruns.tech/0CQJG 提取码: 0169

123网盘(不限速):https://www.123pan.com/s/2Y9Djv-O0cvH.html 提取码:vvDt

Gitee开源地址:https://gitee.com/zeruns/STM32-HAL-OLED-I2C

GitHub开源地址:https://github.com/zeruns/STM32G4-OLED-SSD1306-I2C-HAL

求点个Star

工程使用Keil5创建,用Vscode+EIDE开发,两个软件都可以打开此工程。

工程文件全部使用UTF-8编码,如果打开显示乱码需要修改编辑器编码为UTF-8。

主要文件 OLED.c


/***************************************************************************************
 * 本程序由江协科技创建并免费开源共享
 * 你可以任意查看、使用和修改,并应用到自己的项目之中
 * 程序版权归江协科技所有,任何人或组织不得将其据为己有
 *
 * 程序名称:				0.96寸OLED显示屏驱动程序(4针脚I2C接口)
 * 程序创建时间:			2023.10.24
 * 当前程序版本:			V1.1
 * 当前版本发布时间:		2023.12.8
 *
 * 江协科技官方网站:		jiangxiekeji.com
 * 江协科技官方淘宝店:	jiangxiekeji.taobao.com
 * 程序介绍及更新动态:	jiangxiekeji.com/tutorial/oled.html
 *
 * 如果你发现程序中的漏洞或者笔误,可通过邮件向我们反馈:feedback@jiangxiekeji.com
 * 发送邮件之前,你可以先到更新动态页面查看最新程序,如果此问题已经修改,则无需再发邮件
 ***************************************************************************************
 */

/*
 * 本程序由zeruns二次修改
 * 修改内容:	从标准库版改成HAL库版,增加支持硬件I2C,可通过修改宏定义来选择是否启用硬件I2C
 * 修改日期:	2024.3.16
 * 博客:		https://blog.zeruns.tech
 * B站主页:	https://space.bilibili.com/8320520
*/

#include "main.h"
#include "OLED.h"
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdarg.h>

// 如果用到中文,编译器附加选项需要加 --no-multibyte-chars  (用AC6编译器的不用加)

/*
选择OLED驱动方式,默认使用硬件I2C。如果要用软件I2C就将硬件I2C那行的宏定义注释掉,将软件I2C那行的注释取消。
不能同时两个都同时取消注释!
在stm32cubemx中初始化时需要将SCL和SDA引脚的"user lable"分别设置为I2C3_SCL和I2C3_SDA。
*/
#define OLED_USE_HW_I2C	// 硬件I2C
//#define OLED_USE_SW_I2C	// 软件I2C

/*引脚定义,可在此处修改I2C通信引脚*/
#define OLED_SCL            I2C3_SCL_Pin // SCL
#define OLED_SDA            I2C3_SDA_Pin // SDA
#define OLED_SCL_GPIO_Port  I2C3_SCL_GPIO_Port
#define OLED_SDA_GPIO_Port  I2C3_SDA_GPIO_Port

/*STM32G474芯片的硬件I2C3: PA8 -- SCL; PC9 -- SDA */

#ifdef OLED_USE_HW_I2C
/*I2C接口,定义OLED屏使用哪个I2C接口*/
#define OLED_I2C            hi2c3
extern  I2C_HandleTypeDef   hi2c3;	//HAL库使用,指定硬件IIC接口
#endif

/*OLED从机地址*/
#define OLED_ADDRESS 0x3C << 1	// 0x3C是OLED的7位地址,左移1位最后位做读写位变成0x78

/*I2C超时时间*/
#define OLED_I2C_TIMEOUT 10
/*软件I2C用的延时时间,下面数值为170MHz主频要延时的值,如果你的主频不一样可以修改一下,100MHz以内的主频改成0就行*/
#define Delay_time 3

/**
 * 数据存储格式:
 * 纵向8点,高位在下,先从左到右,再从上到下
 * 每一个Bit对应一个像素点
 *
 *      B0 B0                  B0 B0
 *      B1 B1                  B1 B1
 *      B2 B2                  B2 B2
 *      B3 B3  ------------->  B3 B3 --
 *      B4 B4                  B4 B4  |
 *      B5 B5                  B5 B5  |
 *      B6 B6                  B6 B6  |
 *      B7 B7                  B7 B7  |
 *                                    |
 *  -----------------------------------
 *  |
 *  |   B0 B0                  B0 B0
 *  |   B1 B1                  B1 B1
 *  |   B2 B2                  B2 B2
 *  --> B3 B3  ------------->  B3 B3
 *      B4 B4                  B4 B4
 *      B5 B5                  B5 B5
 *      B6 B6                  B6 B6
 *      B7 B7                  B7 B7
 *
 * 坐标轴定义:
 * 左上角为(0, 0)点
 * 横向向右为X轴,取值范围:0~127
 * 纵向向下为Y轴,取值范围:0~63
 *
 *       0             X轴           127
 *      .------------------------------->
 *    0 |
 *      |
 *      |
 *      |
 *  Y轴 |
 *      |
 *      |
 *      |
 *   63 |
 *      v
 *
 */

/*全局变量*********************/
/**
 * OLED显存数组
 * 所有的显示函数,都只是对此显存数组进行读写
 * 随后调用OLED_Update函数或OLED_UpdateArea函数
 * 才会将显存数组的数据发送到OLED硬件,进行显示
 */
uint8_t OLED_DisplayBuf[8][128];
/*********************全局变量*/

#ifdef OLED_USE_SW_I2C
/**
 * @brief 向 OLED_SCL 写高低电平
 * 根据 BitValue 的值,将 OLED_SCL 置高电平或低电平。
 * @param BitValue 位值,0 或 1
 */
void OLED_W_SCL(uint8_t BitValue)
{
   
	/*根据BitValue的值,将SCL置高电平或者低电平*/
	HAL_GPIO_WritePin(OLED_SCL_GPIO_Port, OLED_SCL, (GPIO_PinState)BitValue);
	/*如果单片机速度过快,可在此添加适量延时,以避免超出I2C通信的最大速度*/
    for (volatile uint16_t i = 0; i < Delay_time; i++){
   
        //for (uint16_t j = 0; j < 10; j++);
    }
}

/**
  * @brief OLED写SDA高低电平
  * @param 要写入SDA的电平值,范围:0/1
  * @return 无
  * @note 当上层函数需要写SDA时,此函数会被调用
  *           用户需要根据参数传入的值,将SDA置为高电平或者低电平
  *           当参数传入0时,置SDA为低电平,当参数传入1时,置SDA为高电平
  */
void OLED_W_SDA(uint8_t BitValue)
{
   
	/*根据BitValue的值,将SDA置高电平或者低电平*/
	HAL_GPIO_WritePin(OLED_SDA_GPIO_Port, OLED_SDA, (GPIO_PinState)BitValue);
	/*如果单片机速度过快,可在此添加适量延时,以避免超出I2C通信的最大速度*/
	for (volatile uint16_t i = 0; i < Delay_time; i++){
   
        //for (uint16_t j = 0; j < 10; j++);
    }
}
#endif

/**
 * @brief OLED引脚初始化
 * @param  无
 * @retval 无
 * @note 当上层函数需要初始化时,此函数会被调用,
 *       用户需要将SCL和SDA引脚初始化为开漏模式,并释放引脚
 */
void OLED_GPIO_Init(void)
{
   
    uint32_t i, j;

    /*在初始化前,加入适量延时,待OLED供电稳定*/
    for (i = 0; i < 1000; i++) {
   
        for (j = 0; j < 1000; j++)
            ;
    }
#ifdef OLED_USE_SW_I2C
    __HAL_RCC_GPIOC_CLK_ENABLE();		// 使能GPIOC时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();       // 使能GPIOA时钟
	GPIO_InitTypeDef GPIO_InitStruct = {
   0};              // 定义结构体配置GPIO
 	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;	        // 设置GPIO模式为开漏输出模式
    GPIO_InitStruct.Pull = GPIO_PULLUP;                 // 内部上拉电阻
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;  // 设置GPIO速度为高速
	GPIO_InitStruct.Pin = I2C3_SDA_Pin;                 // 设置引脚
 	HAL_GPIO_Init(I2C3_SDA_GPIO_Port, &GPIO_InitStruct);// 初始化GPIO

    GPIO_InitStruct.Pin = I2C3_SCL_Pin;
    HAL_GPIO_Init(I2C3_SCL_GPIO_Port, &GPIO_InitStruct);

	/*释放SCL和SDA*/
	OLED_W_SCL(1);
	OLED_W_SDA(1);
#endif
}

// https://blog.zeruns.tech

/*通信协议*********************/

/**
 * @brief I2C起始
 * @param  无
 * @return 无
 */
void OLED_I2C_Start(void)
{
   
#ifdef OLED_USE_SW_I2C
	OLED_W_SDA(1);		//释放SDA,确保SDA为高电平
	OLED_W_SCL(1);		//释放SCL,确保SCL为高电平
	OLED_W_SDA(0);		//在SCL高电平期间,拉低SDA,产生起始信号
	OLED_W_SCL(0);		//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
#endif
}

/**
 * @brief I2C终止
 * @param  无
 * @return 无
 */
void OLED_I2C_Stop(void)
{
   
#ifdef OLED_USE_SW_I2C
	OLED_W_SDA(0);		//拉低SDA,确保SDA为低电平
	OLED_W_SCL(1);		//释放SCL,使SCL呈现高电平
	OLED_W_SDA(1);		//在SCL高电平期间,释放SDA,产生终止信号
#endif
}

/**
 * @brief I2C发送一个字节
 * @param Byte 要发送的一个字节数据,范围:0x00~0xFF
 * @return 无
 */
void OLED_I2C_SendByte(uint8_t Byte)
{
   
#ifdef OLED_USE_SW_I2C
	uint8_t i;
	/*循环8次,主机依次发送数据的每一位*/
	for (i = 0; i < 8; i++)
	{
   
		/*使用掩码的方式取出Byte的指定一位数据并写入到SDA线*/
		/*两个!的作用是,让所有非零的值变为1*/
		OLED_W_SDA(!!(Byte & (0x80 >> i)));
		OLED_W_SCL(1);	//释放SCL,从机在SCL高电平期间读取SDA
		OLED_W_SCL(0);	//拉低SCL,主机开始发送下一位数据
	}
	OLED_W_SCL(1);		//额外的一个时钟,不处理应答信号
	OLED_W_SCL(0);
#endif
}

/**
 * @brief OLED写命令
 * @param Command 要写入的命令值,范围:0x00~0xFF
 * @return 无
 */
void OLED_WriteCommand(uint8_t Command)
{
   
#ifdef OLED_USE_SW_I2C
    OLED_I2C_Start();           // I2C起始
	OLED_I2C_SendByte(0x78);		//发送OLED的I2C从机地址
	OLED_I2C_SendByte(0x00);	//控制字节,给0x00,表示即将写命令
    OLED_I2C_SendByte(Command); // 写入指定的命令
    OLED_I2C_Stop();            // I2C终止
#elif defined(OLED_USE_HW_I2C)
    uint8_t TxData[2] = {
   0x00, Command};
    HAL_I2C_Master_Transmit(&OLED_I2C, OLED_ADDRESS, (uint8_t*)TxData, 2, OLED_I2C_TIMEOUT);
#endif 
}

/**
 * @brief OLED写数据
 * @param Data 要写入数据的起始地址
 * @param Count 要写入数据的数量
 * @return 无
 */
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
   
    uint8_t i;
#ifdef OLED_USE_SW_I2C
    OLED_I2C_Start();        // I2C起始
	OLED_I2C_SendByte(0x78);		//发送OLED的I2C从机地址

    OLED_I2C_SendByte(0x40); // 控制字节,给0x40,表示即将写数据
    /*循环Count次,进行连续的数据写入*/
    for (i = 0; i < Count; i++) {
   
        OLED_I2C_SendByte(Data[i]); // 依次发送Data的每一个数据
    }
    OLED_I2C_Stop(); // I2C终止
#elif defined(OLED_USE_HW_I2C)
    uint8_t TxData[Count + 1]; // 分配一个新的数组,大小是Count + 1
    TxData[0] = 0x40; // 起始字节
    // 将Data指向的数据复制到TxData数组的剩余部分
    for (i = 0; i < Count; i++) {
   
        TxData[i + 1] = Data[i];
    }
    HAL_I2C_Master_Transmit(&OLED_I2C,
好的,以下是在STM32F103C8T6上使用I2C OLED的步骤: 1. 首先,您需要在STM32CubeMX中打开您的项目,并在“Pinout”选项卡中配置I2COLED引脚。选中SCL和SDA引脚,将其配置为I2C模式,并设置GPIO的输入/输出模式和速度。同时,您需要根据您的OLED型号选择正确的引脚并将其配置为输出模式。 2. 然后,在“Clock Configuration”选项卡中,启用I2C时钟,并设置I2C时钟的频率。 3. 接下来,在“Configuration”选项卡中,选择I2C外设,并根据您的需求进行配置。例如,您可以设置I2C的工作模式、地址长度、时钟占空比等。同时,您需要在代码中配置OLEDI2C地址和屏幕大小等参数。 4. 在代码中,您需要使用HAL或其他来初始化I2C外设,并设置I2COLED的参数。例如,您可以使用以下代码初始化I2C1和OLED: ``` I2C_HandleTypeDef hi2c1; void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } } #define OLED_I2C_ADDR 0x78 #define OLED_WIDTH 128 #define OLED_HEIGHT 64 void OLED_Init(void) { HAL_Delay(100); OLED_WR_Byte(0xAE, OLED_CMD); //关闭显示 OLED_WR_Byte(0xD5, OLED_CMD); //设置时钟分频因子,震荡频率 OLED_WR_Byte(0x80, OLED_CMD); //[3:0],分频因子;[7:4],震荡频率 OLED_WR_Byte(0xA8, OLED_CMD); //设置驱动路数 OLED_WR_Byte(0X3F, OLED_CMD); //默认0X3F(1/64) OLED_WR_Byte(0xD3, OLED_CMD); //设置显示偏移 OLED_WR_Byte(0X00, OLED_CMD); //默认为0 OLED_WR_Byte(0x40, OLED_CMD); //设置显示开始行 [5:0],行数. OLED_WR_Byte(0x8D, OLED_CMD); //电荷泵设置 OLED_WR_Byte(0x14, OLED_CMD); //bit2,开启/关闭 OLED_WR_Byte(0x20, OLED_CMD); //设置内存地址模式 OLED_WR_Byte(0x02, OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10; OLED_WR_Byte(0xA1, OLED_CMD); //段重定向设置 OLED_WR_Byte(0xC0, OLED_CMD); //设置COM扫描方向;默认为逆向 OLED_WR_Byte(0xDA, OLED_CMD); //设置COM硬件引脚配置 OLED_WR_Byte(0x12, OLED_CMD); //[5:4]配置 OLED_WR_Byte(0x81, OLED_CMD); //对比度设置 OLED_WR_Byte(0xEF, OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮) OLED_WR_Byte(0xD9, OLED_CMD); //设置预充电周期 OLED_WR_Byte(0xf1, OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2; OLED_WR_Byte(0xDB, OLED_CMD); //设置VCOMH 电压倍率 OLED_WR_Byte(0x40, OLED_CMD); //设置VCOMH 电压倍率 OLED_WR_Byte(0xA4, OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) OLED_WR_Byte(0xA6, OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示 OLED_WR_Byte(0xAF, OLED_CMD); //开启显示 } ``` 5. 接下来,您可以使用HAL或其他中提供的函数来向OLED写入数据。例如,您可以使用以下代码向OLED写入一个字节的数据: ``` HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, OLED_CMD, 1, &data, 1, 10); ``` 6. 最后,不要忘记在使用I2COLED外设后及时释放总线,以便其他设备可以访问它。例如,您可以使用以下代码释放I2C总线: ``` HAL_I2C_ReleaseBus(&hi2c1); ``` 希望这些步骤可以帮助您在STM32F103C8T6上使用I2C OLED
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值