前言

本文内容均来自互联网与AI,此文只是本人的实践与整理 ,如有不正确的地方,请参考原文。

OLED显示屏驱动

正常嵌入式调试方式:

  • 串口调试
  • 显示屏调试
  • keil、IAR调试模式

简介

OLED(Organic Light Emitting Diode)有机发光二极管

  • 供电 3~5.5v
  • 通信协议 I2C/SPI
  • 分辨率 128*64

OLED驱动函数


在这里插入图片描述

接线

在这里插入图片描述

在这里插入图片描述

程序

I2C总线

1. 开启APB2总线时钟

在STM32中,大多数外设在使用之前都需要打开其对应的时钟,如(GPIO、UART、SPI等)

  • 调用RCC_APB2PeriphClockCmd rcc标准库函数
/**
  * @brief  开启/或关闭APB2总线外设时钟
  * @param  RCC_APB2Periph: 要控制的APB2外设
  *   This parameter can be any combination of the following values:
  * 	@arg RCC_APB2Periph_GPIOA:控制 GPIOA 外设
  *      	RCC_APB2Periph_USART1:控制 USART1 外设
  *      	RCC_APB2Periph_AFIO:控制复用功能 I/O 外设
  * @param  NewState: 指定开启或关闭
  * 	@arg ENABLE or DISABLE.
  * @retval None
  */
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
{
  /* 这是库函数的防御机制,用来在调试模式下检查传入参数是否正确,防止非法操作导致不可预测行为。 */
  assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph)); 
  assert_param(IS_FUNCTIONAL_STATE(NewState));
  if (NewState != DISABLE)
  {
    RCC->APB2ENR |= RCC_APB2Periph; //  设置对应的寄存器,打开外设时钟
  }
  else
  {
    RCC->APB2ENR &= ~RCC_APB2Periph; // 设置对应的寄存器,关闭外设时钟
  }
}
2. GPIO模拟I2C

将 PB8、PB9 设置为 GPIO 并通过宏函数控制电平变化模拟 I2C 协议,不依赖 STM32 的硬件 I2C1

软件 I2C 硬件 I2C
手动控制 GPIO,高低电平模拟时序 使用 STM32 的 I2C 外设(I2C1/2)控制
占用资源 占用 2 个任意 GPIO
自己写时序,麻烦但灵活 使用库函数,稳定但固定
可任意选择 GPIO 有限,依赖芯片设计
  • 初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;  // 创建 GPIO 初始化结构体变量,用于配置 GPIO 各项参数

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 设置引脚为开漏输出(Open-Drain),用于模拟 I2C 通信需要的电平特性
                                                  // 开漏允许多个设备连接在一条线上,拉低表示输出,释放表示输入(由上拉电阻拉高)

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置 GPIO 输出速度为 50MHz,代表引脚切换速度的上限
                                                   // 虽然软件 I2C 实际速度远小于 50MHz,但一般都使用最高速配置

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;         // 选择 GPIOB 的第 8 号引脚,即 PB8
GPIO_Init(GPIOB, &GPIO_InitStructure);            // 初始化 PB8:应用上述配置,将其设置为开漏输出模式

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;         // 更换目标引脚为 PB9(注意此时只修改了 Pin 项,其他设置沿用)
GPIO_Init(GPIOB, &GPIO_InitStructure);            // 初始化 PB9,同样配置为开漏输出,用作 I2C 的 SDA 线

  • 软件I2C宏函数
// 宏定义:OLED_W_SCL(x)
// 功能:将 GPIOB 的第 8 号引脚(PB8,也就是 I2C 的 SCL)输出为 x
// 参数:x 0 或 1
// 内部调用:GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
//   - GPIOB:目标端口
//   - GPIO_Pin_8:目标引脚(SCL)
//   - (BitAction)(x):将 x 强制类型转换为 BitAction(STM32 的枚举类型,取值为 Bit_RESET 或 Bit_SET)
// 使用示例:OLED_W_SCL(1) 表示拉高 SCL;OLED_W_SCL(0) 表示拉低 SCL
#define OLED_W_SCL(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))

// 宏定义:OLED_W_SDA(x)
// 功能:将 GPIOB 的第 9 号引脚(PB9,也就是 I2C 的 SDA)输出为 x
// 参数:x  0 或 1
// 内部调用:GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
//   - GPIOB:目标端口
//   - GPIO_Pin_9:目标引脚(SDA)
//   - (BitAction)(x):将 x 强制转换为 BitAction 枚举(Bit_RESET=0,Bit_SET=1)
// 使用示例:OLED_W_SDA(1) 表示拉高 SDA;OLED_W_SDA(0) 表示拉低 SDA
#define OLED_W_SDA(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
3. I2C基本时序规则(主设备)

在这里插入图片描述

  • 开始 SCL = 1 时,SDA 从 1 跳变为 0 ——> 表示通信开始
   _______         
SCL        \____       时钟保持高
     _____         
SDA      \____         数据线下跳沿触发开始
  • 结束 SCL = 1 时,SDA 从 0 跳变为 1 ——> 表示通信结束
                    _____
SCL         _______/       时钟为高
                  _______
SDA      ________/         数据线上跳沿触发停止
  • 主机写数据
主机控制 SDA 输出数据,并在 SCL 拉高时让从机采样:
    SCL  __|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|
    SDA   D7 D6 D5 D4 D3 D2 D1 D0   A
          ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑    ↑
       数据在 SCL 低时改变   |
       从机在 SCL 高时采样 --|
  • 主机读数据(这里并未使用)
从机控制 SDA,主机在 SCL 高时采样:

    SCL  __|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|
    SDA   D7 D6 D5 D4 D3 D2 D1 D0   A
          ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑    ↑
    从机在 SCL 低时准备数据 |
    主机在 SCL 高时采样 ----|
4. GPIO模拟I2C控制函数
/**
  * @brief  I2C开始
  * @param  无
  * @retval 无
  */
void OLED_I2C_Start(void)
{
	OLED_W_SDA(1);
	OLED_W_SCL(1);
	OLED_W_SDA(0);
	OLED_W_SCL(0);
}

/**
  * @brief  I2C停止
  * @param  无
  * @retval 无
  */
void OLED_I2C_Stop(void)
{
	OLED_W_SDA(0);
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
  * @brief  I2C发送一个字节
  * @param  Byte 要发送的一个字节
  * @retval 无
  */
void OLED_I2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i++)
	{
		OLED_W_SDA(!!(Byte & (0x80 >> i)));
		OLED_W_SCL(1);
		OLED_W_SCL(0);
	}
	OLED_W_SCL(1);	//额外的一个时钟,不处理应答信号
	OLED_W_SCL(0);
}

以上就完成了基本的I2C驱动函数,可以在此基础上封装一些函数与OLED芯片进行交互
OLED芯片支持的命令具体可以参考这篇文章 SSD1306 OLED驱动芯片 详细介绍

OLED模块控制

直接贴上薅来的代码

/**
  * @brief  OLED写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void OLED_WriteCommand(uint8_t Command)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x00);		//写命令
	OLED_I2C_SendByte(Command); 
	OLED_I2C_Stop();
}

/**
  * @brief  OLED写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void OLED_WriteData(uint8_t Data)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x40);		//写数据
	OLED_I2C_SendByte(Data);
	OLED_I2C_Stop();
}

/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval 无
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);					//设置Y位置
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		OLED_SetCursor(j, 0);
		for(i = 0; i < 128; i++)
		{
			OLED_WriteData(0x00);
		}
	}
}

/**
  * @brief  OLED显示一个字符
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的一个字符,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//显示下半部分内容
	}
}

/**
  * @brief  OLED显示字符串
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i++)
	{
		OLED_ShowChar(Line, Column + i, String[i]);
	}
}

/**
  * @brief  OLED次方函数
  * @retval 返回值等于X的Y次方
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}

/**
  * @brief  OLED显示数字(十进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~4294967295
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十进制,带符号数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-2147483648~2147483647
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
	uint8_t i;
	uint32_t Number1;
	if (Number >= 0)
	{
		OLED_ShowChar(Line, Column, '+');
		Number1 = Number;
	}
	else
	{
		OLED_ShowChar(Line, Column, '-');
		Number1 = -Number;
	}
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十六进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
  * @param  Length 要显示数字的长度,范围:1~8
  * @retval 无
  */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)							
	{
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		if (SingleNumber < 10)
		{
			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
		}
		else
		{
			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
		}
	}
}

/**
  * @brief  OLED显示数字(二进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
	}
}

/**
  * @brief  OLED初始化
  * @param  无
  * @retval 无
  */
void OLED_Init(void)
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i++)			//上电延时
	{
		for (j = 0; j < 1000; j++);
	}
	
	OLED_I2C_Init();			//端口初始化
	
	OLED_WriteCommand(0xAE);	//关闭显示
	
	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
	OLED_WriteCommand(0x80);
	
	OLED_WriteCommand(0xA8);	//设置多路复用率
	OLED_WriteCommand(0x3F);
	
	OLED_WriteCommand(0xD3);	//设置显示偏移
	OLED_WriteCommand(0x00);
	
	OLED_WriteCommand(0x40);	//设置显示开始行
	
	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
	
	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置

	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
	OLED_WriteCommand(0x12);
	
	OLED_WriteCommand(0x81);	//设置对比度控制
	OLED_WriteCommand(0xCF);

	OLED_WriteCommand(0xD9);	//设置预充电周期
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭

	OLED_WriteCommand(0xA6);	//设置正常/倒转显示

	OLED_WriteCommand(0x8D);	//设置充电泵
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//开启显示
		
	OLED_Clear();				//OLED清屏
}

实现效果

在这里插入图片描述

参考文章

模拟I2C怎么用–教你使用GPIO口模拟I2C总线协议`
STM32入门教程-2023版 细致讲解 中文字幕

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐