【STM32标准库】【自制库】0.96寸OLED显示屏(SSD1306)(0)OLED初始化,软件I2C实现,基础数据发送

21 篇文章 10 订阅
8 篇文章 2 订阅


文章基于适用于STM32F4系列,作者使用STM32F401CCU6开发板。
本文章基于此系列和开发板展开讨论。

本系列以SSD1306为主控芯片的I2C接口0.96寸OLED屏幕为例介绍
内容较多,分节进行

链接

基础命令和寻址方法
IIC(I2C)协议

  1. OLED初始化,软件I2C实现,基础数据发送
  2. 满屏图像显示
  3. 全屏动画显示
  4. 显示字母和数字,汉字
  5. 画指定的点
  6. 指定两点画线段
  7. 指定圆心和半径画圆
  8. 指定圆心半径角度画圆弧

需求分析

本节主要是最基础的I2C实现和数据发送

听说STM32的硬件I2C容易在中断的影响下出现问题
为了更方便的换用不同管脚和作为库文件移植,
本系列使用软件I2C,即用GPIO模拟I2C时序

本节需求

  1. 使用GPIO模拟I2C时序
  2. 数据与命令的发送
  3. OLED的初始化

软件I2C实现

流程

  1. GPIO初始化
  2. 模拟起始与终止信号
  3. 模拟数据信号与应答

GPIO初始化

与外设通讯时一般都使用开漏输出的输出模式(不管是输入还是输出)
这是因为开漏输出有线与的功能
在这里插入图片描述
即数据线上的电平只有在单片机和外设都输出高电平时才为高
而且可以单片机与外设一个为高电平,一个为低电平而不引发短路(此时数据线为低电平)
有因为I2C的数据线和时钟线的默认电平为高电平

GPIO的配置在之前介绍过了,传送门

这是本文使用的初始化函数和相关头文件定义

头文件(OLED.H)

#define GPIO_group GPIOA					// I2C的GPIO组号
#define OLED_SCL GPIO_Pin_0					// I2C时钟的GPIO端口号
#define OLED_SDA GPIO_Pin_1					// I2C时钟的GPIO端口号
#define RCC_AHB1Periph RCC_AHB1Periph_GPIOA //所用组号的时钟

C文件(OLED.C)

//初始化GPIO
void OLED_GPIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;				//声明结构体
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph, ENABLE); //打开时钟
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;		//输出模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;		//开漏输出
	GPIO_InitStruct.GPIO_Pin = OLED_SCL | OLED_SDA; // SDA和SCL线
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;	//快速模式
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;		//上拉
	GPIO_Init(GPIO_group, &GPIO_InitStruct);		//初始化
	GPIO_SetBits(GPIO_group, OLED_SCL | OLED_SDA);	//设置为默认为高电平
}

起始信号

在I2C协议的文章中说过

I2C的起始信号是SCl为高电平时,SDA产生下降沿

在这里插入图片描述
因此这是起始信号的代码

头文件(OLED.H)

#define OLED_Write_SCL(x) GPIO_WriteBit(GPIO_group, OLED_SCL, x)
#define OLED_Write_SDA(x) GPIO_WriteBit(GPIO_group, OLED_SDA, x)

宏定义就是一个替换,这个(x)就是将后面出现x替换为传入的量,这就是写SDA和SCl的高低电平用

C文件(OLED.C)

__STATIC_INLINE void I2C_Start(void)
{
	OLED_Write_SDA(Bit_SET);
	OLED_Write_SCL(Bit_SET); //确保SCL与SDA为高电平
	// Delay_us();//这里可以加延迟,保证信号稳定,是SDA高电平持续的延迟,编者的OLED模块不需要加
	OLED_Write_SDA(Bit_RESET); //在SCL为高电平时SDA产生下降沿
	// Delay_us();//这里可以加延迟,保证信号稳定,是SDA与SCL下降沿之间的延迟,编者的OLED模块不需要加
	OLED_Write_SCL(Bit_RESET); //将SCL拉低,准备接受数据
}

函数声明前的__STATIC_INLINE是C++的一个关键字,意思是将这个函数作为内联函数
一句话就是以空间换时间,加速代码运行的

另外:单片机的两句命令之间会有一个固有的延迟,为(1/系统频率)(s)

终止信号

I2C的起始信号是SCl为高电平时,SDA产生上升沿

在这里插入图片描述

__STATIC_INLINE void I2C_End(void)
{							   //从发送数据到结束部分,默认SCL为低电平
	OLED_Write_SDA(Bit_RESET); //将SDA拉低
	OLED_Write_SCL(Bit_SET);
	// Delay_us();//这里可以加延迟,保证信号稳定,是SDA和SCL上升沿之间的延迟,编者的OLED模块不需要加
	OLED_Write_SDA(Bit_SET);
	// Delay_us();//这里可以加延迟,保证信号稳定,是SDA高电平持续的延迟,编者的OLED模块不需要加
}

数据信号与应答

数据是以8bit为1组从高位到低位发送的

void I2C_SendByte(u8 dat)
{
	u8 zj = dat;				 //不能更改dat
	for (int i = 7; i >= 0; i--) // 8次就行,怎么循环随意
	{							 //进入发送数据默认SCL为低电平
		if (zj & 0x80)			 //判断最高位
		{
			OLED_Write_SDA(Bit_SET);
		}
		else
		{
			OLED_Write_SDA(Bit_RESET);
		}
		OLED_Write_SCL(Bit_SET);   // SCL拉高,让外设读取数据
		OLED_Write_SCL(Bit_RESET); //准备读取下个数据
		zj <<= 1;				   //向左移位,让数据从高到低发送
	}
	OLED_Write_SCL(Bit_SET); //第九个周期,应答位
	//可以加验证的代码,一般不需要验证
	OLED_Write_SCL(Bit_RESET);
}

千万别忘了应答信号,可以不读取SDA的应答状态,但不能没有应答的时钟

到了这里,软件I2C发送数据的内容已经完成了,下面是针对SSD1306的命令部分了

数据与命令的发送

流程

  1. 发送起始信号
  2. 发送地址
  3. 选择命令或数据
  4. 发送命令或数据
  5. 发送结束信号

地址

本OLED模块的地址可以选择,改变这个电阻的链接位置即可
请添加图片描述

现在选择的地址是0x78

命令和数据的选择

在发送了地址之后,需要选择接下来发送的8bit是命令还是数据

如果是命令需要发送0x00

如果是命令需要发送0x40

命令的发送

C文件(OLED.C)

//写入控制命令的开始 写完数据后加结束命令
void OLED_Write_Ctrl_Start(void)
{
	I2C_Start();
	I2C_SendByte(0x78);
	I2C_SendByte(0x00);
}

例子

OLED_Write_Ctrl_Start();
I2C_SendByte(0xff);
I2C_SendByte(0x0f);
I2C_End();

注意:
1.调用开始发送命令函数后可以发送任意数量的命令(一条是8bit)
2.当需要发送数据时只需要调用开始发送数据的函数即可
3.当结束发送时一定要调用结束函数

数据的发送

C文件(OLED.C)

//写入控制命令的开始 写完数据后加结束命令
//写入数据的开始 写完数据后加结束命令
void OLED_Write_Data_Start(void)
{
	I2C_Start();
	I2C_SendByte(0x78);
	I2C_SendByte(0x40);
}

例子

OLED_Write_Data_Start();
I2C_SendByte(0xff);
I2C_SendByte(0x0f);
I2C_End();

注意:
1.调用开始发送数据函数后可以发送任意数量的数据(一条是8bit)
2.当需要发送命令时只需要调用开始发送命令的函数即可
3.当结束发送时一定要调用结束函数

OLED的初始化

oled初始化没什么需要多说的,大体分为2步

  1. OLED初始化设置
  2. 清屏

OLED初始化设置

首先上电后需要一段时间的延迟,几毫秒就行,不需要特别精确
之后是一堆有关OLED的设置,基本上不需要要修改

C文件(OLED.C)

// OLED的初始化
void OLED_Init(void)
{
	for (int i = 0; i < 1000; i++) //上电延时
	{
		for (int j = 0; j < 1000; j++)
			;
	}

	OLED_GPIO_Init();		 //初始化GPIO
	OLED_Write_Ctrl_Start(); //开始写入初始化命令
	I2C_SendByte(0xAE);		 //关闭显示

	I2C_SendByte(0xD5); //设置显示时钟分频比/振荡器频率
	I2C_SendByte(0x80);

	I2C_SendByte(0xA8); //设置多路复用率
	I2C_SendByte(0x3F);

	I2C_SendByte(0xD3); //设置显示偏移
	I2C_SendByte(0x00);

	I2C_SendByte(0x40); //设置显示开始行

	I2C_SendByte(0xA1); //设置左右方向,0xA1正常 0xA0左右反置

	I2C_SendByte(0xC8); //设置上下方向,0xC8正常 0xC0上下反置

	I2C_SendByte(0xDA); //设置COM引脚硬件配置
	I2C_SendByte(0x12);

	I2C_SendByte(0x81); //设置对比度控制
	I2C_SendByte(0xCF);

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

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

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

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

	I2C_SendByte(0x8D); //设置充电泵
	I2C_SendByte(0x14);
	I2C_SendByte(0x2E); //关闭滚动
	I2C_SendByte(0xAF); //开启显示

	I2C_End();
	OLED_Clear();
}

之后是清屏,本质上是将所有的列和页设置为0x00

C文件(OLED.C)

//清屏
void OLED_Clear(void)
{
	OLED_Write_Ctrl_Start();
	I2C_SendByte(0x20); //设置寻址模式 (0x00/0x01/0x02)
	I2C_SendByte(0x00); //水平寻址模式
	I2C_SendByte(0x21); //设置列地址
	I2C_SendByte(0x00);
	I2C_SendByte(0x7f);
	I2C_SendByte(0x22); //设置页地址
	I2C_SendByte(0x00);
	I2C_SendByte(0x07);
	I2C_End();
	OLED_Write_Data_Start();
	for (int i = 0; i < 8; i++)		  // 0-7页
		for (int j = 0; j < 128; j++) // 0-127列
			I2C_SendByte(0x00);
	I2C_End();
}

这里使用的水平寻址模式,在之前文章中说过,传送门

可以将清屏函数中的发送的数据改为0xFF,这样如果设置成功后则屏幕全部点亮,做为测试

成品

CSDN

链接:百度网盘
提取码:ierk

  • 5
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个使用 I2C 总线控制 0.96 OLED 幕进行局部清零的程序示例,基于 STM32 平台: ``` #include <Wire.h> #define OLED_ADDR 0x3C // OLEDI2C地址 #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 void OLED_Init(); void OLED_Clear(); void OLED_PartialClear(int x, int y, int w, int h); void setup() { Wire.begin(); OLED_Init(); } void loop() { // 指定需要清空的区域坐标 int x = 0; int y = 0; int w = 64; int h = 32; // 清空指定区域的像素点 OLED_PartialClear(x, y, w, h); delay(1000); // 等待一段时间后再进行下一次局部清空 } void OLED_Init() { Wire.beginTransmission(OLED_ADDR); Wire.write(0x00); Wire.write(0xAE); Wire.write(0xD5); Wire.write(0x80); Wire.write(0xA8); Wire.write(0x3F); Wire.write(0xD3); Wire.write(0x00); Wire.write(0x40); Wire.write(0x8D); Wire.write(0x14); Wire.write(0x20); Wire.write(0x00); Wire.write(0xA1); Wire.write(0xC8); Wire.write(0xDA); Wire.write(0x12); Wire.write(0x81); Wire.write(0xCF); Wire.write(0xD9); Wire.write(0xF1); Wire.write(0xDB); Wire.write(0x40); Wire.write(0xA4); Wire.write(0xA6); Wire.write(0xAF); Wire.endTransmission(); } void OLED_Clear() { for (int i = 0; i < SCREEN_HEIGHT / 8; i++) { Wire.beginTransmission(OLED_ADDR); Wire.write(0x40); Wire.write(0xB0 + i); Wire.write(0x00); Wire.endTransmission(); } } void OLED_PartialClear(int x, int y, int w, int h) { int x_end = x + w; int y_end = y + h; for (int i = x; i < x_end; i++) { for (int j = y; j < y_end; j++) { int page = j / 8; int bit = j % 8; int data = ~(1 << bit); Wire.beginTransmission(OLED_ADDR); Wire.write(0x40); Wire.write(0xB0 + page); Wire.write(i & 0x0F); Wire.write(0x10 | (i >> 4)); Wire.write(data); Wire.endTransmission(); } } } ``` 在该程序中,首先使用 `Wire` 初始化 I2C 总线,然后在 `OLED_Init()` 函数中初始化 OLED 显示屏的参数。在 `OLED_Clear()` 函数中实现了全局清的方法,而在 `OLED_PartialClear()` 函数中实现了局部清的方法。 在 `OLED_PartialClear()` 函数中,首先根据传入的坐标和大小计算出需要清空的区域,然后使用 `Wire.write()` 函数将每个像素点的数据写入 OLED 显示屏,从而实现局部清空的效果。 需要注意的是,在使用 `Wire.write()` 函数时,需要确保写入的数据格式正确,否则会导致程序出错或程序崩溃。同时,在进行局部清空时,也需要确保指定的区域不超出 OLED 显示屏的有效范围。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值