STM32——OLED外设库的讲解(1/2)

引言

本来我是不打算说OLED这个外设的,但我发现好多教程只是在教你原理,而不教你如何构建库。也正是这个原因出现了很多C/V工程师。

目的

在这个章节中我会让你知道这个库如何建立,为什么要这么建立,我自己的库我该如何使用!

注意

当你了解IIC协议,OLED工作原理后再去看!不了解你甚至都不知道在干什么!

一个合格的OLED库

OLED初始化

void OLED_GPIO_Init(void)
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i ++)
	{
		for (j = 0; j < 1000; j ++);
	}
	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

首先解决第一个问题,为什么我要写一个循环,因为在某些硬件上,当OLED或其他设备刚上电时,需要一些时间让其稳定工作。因此,加入这样的延时可以确保在初始化GPIO之前,OLED或其他设备已经准备好。(其实是在给OLED上的VCC引脚充足的时间为其供电)但请注意,这种延时方式并不是最精确的,并且其实际延时时间取决于CPU的速度和编译器优化。但对于我们小工程来说完全可以了哈!

IIC协议设定

void OLED_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_6, (BitAction)BitValue);
}

void OLED_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_7, (BitAction)BitValue);
}

IIC协议就是控制SCL,SDA两线程的高低电平变化从而达到控制数据的收发功能。所以IIC对高低电平的控制函数就OLED库构建的地基。OLED所有显示函数都少不了对SCL,SDA电平的控制。

具体我就不说了,明天我会主要讲讲IIC协议。

直接看代码!

GPIO_WriteBit(GPIOB, GPIO_Pin_6, (BitAction)BitValue);我觉得大家应该都知道,这其实就是将(BitAction)BitValue写入GPIOB的引脚6。这里的问题就是(BitAction)BitValue是什么意思。

(BitAction)BitValue

这里将BitValue强制转换为BitAction枚举类型。BitAction是一个枚举类型,通常定义在STM32的GPIO库中,它有两个可能的值:Bit_RESET(通常为0)和Bit_SET(通常为1)。

直接看我们的固件库。

typedef enum
{ Bit_RESET = 0,
  Bit_SET
}BitAction;

含义是设定一个枚举类型,名字叫BitAction。它的取值分为0(Bit_RESET)/1(Bit_SET)。

所以说,我这样使用OLED_W_SCL(1)意思就是将SCL拉成高电平!下一个操作SDA的函数同理。 

但我要说的是,操作SCL,SDA时如果单片机运行速度过快,就有可能无法捕捉SCL,SDA的设定。所以我们在这里可以加入一个延时函数。专业来讲

当微控制器的时钟速度很快时,即使你的代码逻辑是正确的,但由于执行速度过快,可能会在I2C时序要求上产生问题。例如,你可能在SCL线的一个时钟周期内更改了SDA线的值多次,或者SDA线的值在SCL线的一个时钟周期内没有保持稳定足够长的时间。

为了避免这种情况,有时需要在SDA线的值改变后添加一些延时,以确保SDA线的值在SCL线的时钟周期内保持稳定。这种延时通常被称为“位时间”或“位周期”延时,并且它的长度应该与I2C通信的时钟速度相匹配。

 那么接下来OLED显示的函数就是对SCL,SDA的配合进行操作。

OLED显示

/**
  * 函    数:I2C起始
  * 参    数:无
  * 返 回 值:无
  */
void OLED_I2C_Start(void)
{
	OLED_W_SDA(1);		//释放SDA,确保SDA为高电平
	OLED_W_SCL(1);		//释放SCL,确保SCL为高电平
	OLED_W_SDA(0);		//在SCL高电平期间,拉低SDA,产生起始信号
	OLED_W_SCL(0);		//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}

/**
  * 函    数:I2C终止
  * 参    数:无
  * 返 回 值:无
  */
void OLED_I2C_Stop(void)
{
	OLED_W_SDA(0);		//拉低SDA,确保SDA为低电平
	OLED_W_SCL(1);		//释放SCL,使SCL呈现高电平
	OLED_W_SDA(1);		//在SCL高电平期间,释放SDA,产生终止信号
}

配合我的图看吧! 

灰色部分表示产生动作,所以我们只看灰色前的操作就行。所以当释放SDA(使SDA从低电平变为高电平), 释放SCL(使SCL保持高电平)——>拉低SDA后就会产生起始信号,及意味着我的IIC准备好了。但是在其他操作时我们仍需要将SCL拉低,这样称为占用总线,意思是告诉IIC谁已经准备好和你通信了!所以为了方便我们将SCL拉低一起写入产生起始信号的函数中。

 那么在停止信号函数中,我们将SDA拉低,将SCL释放(意思是我们的通讯已经结束了,我SCL已经不需要占据总线了,所以将SCL释放)——>但你自己的SCL释放了,仅仅只有你SCL知到,所以为了确保SDA也清楚,我们就需要叫SDA释放(称为应答)。函数结束后,IIC停止传输数据。

再详细生动点讲,我会在明天说,所以先点个关注吧。

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);	//释放SCL,从机在SCL高电平期间读取SDA
		OLED_W_SCL(0);	//拉低SCL,主机开始发送下一位数据
	}
	
	OLED_W_SCL(1);		//额外的一个时钟,不处理应答信号
	OLED_W_SCL(0);
}

一个字节有八位所以当我们应当一位一位地发送,因此我们这里使用到了一个循环。

OLED_W_SDA(!!(Byte & (0x80 >> i)))

0x80 >> i: 通过右移操作,我们得到从最高位(第7位)开始,每一位都为1的掩码。例如,当i=0时,掩码为0x80(二进制10000000);当i=1时,掩码为0x40(二进制01000000),以此类推。Byte & (0x80 >> i): 使用按位与操作,我们可以检查Byte的当前位(由i指定)是否为1。

!!: 这是一个双非操作。在C语言中,非操作!会将非零值转换为0,将零值转换为1。双非操作确保结果要么是0(如果Byte的当前位为0),要么是1(如果Byte的当前位为1)。这样,我们就可以得到一个表示当前位状态的布尔值(0或1)。OLED_W_SDA(...): 这是一个函数调用,用于设置SDA线的状态。根据前面的解释,它将设置SDA线为高(如果Byte的当前位为1)或低(如果Byte的当前位为0)。

OLED_W_SCL(1) 将SCL线设置为高电平,允许从机(在这里是OLED屏幕)在SCL高电平期间读取SDA线的状态。

OLED_W_SCL(0)将SCL线拉低,准备发送下一位数据。

但在发送完8位数据后,通常会有一个额外的时钟周期用于从机(如OLED屏幕)发送应答信号(ACK)。因为OLED屏幕不需要应答信号但此周期确实存在所以我们重复上一个周期的SCL的操作即可。

到这里,你已经攻克了OLED中最难理解的部分!

出厂函数

(0xAE);	//设置显示开启/关闭,0xAE关闭,0xAF开启	
(0xD5);	//设置显示时钟分频比/振荡器频率
(0x80);	//0x00~0xFF	
(0xA8);	//设置多路复用率
(0x3F);	//0x0E~0x3F	
(0xD3);	//设置显示偏移
(0x00);	//0x00~0x7F	
(0x40);	//设置显示开始行,0x40~0x7F	
(0xA1);	//设置左右方向,0xA1正常,0xA0左右反置	
(0xC8);	//设置上下方向,0xC8正常,0xC0上下反置
(0xDA);	//设置COM引脚硬件配置
(0x12);
(0x26);
(0x29);
(0x81);	//设置对比度
(0xCF);	//0x00~0xFF
(0xD9);	//设置预充电周期
(0xF1);
(0xDB);	//设置VCOMH取消选择级别
(0x30);
(0xA4);	//设置整个显示打开/关闭
(0xA6);	//设置正常/反色显示,0xA6正常,0xA7反色
(0x8D);	//设置充电泵
(0x14);
(0xAF);	//开启显示

这些数据都是OLED出厂 就有的函数,当我们给OLED发送这些信号后,我们的OLED就会根据以上代码的解析作出反应,无需我们编程!!!!(还有很多出厂函数,小伙伴们可以自行上网搜索,我们就不列举了)

所以他们的使用往往是

先打开IIC,再使用OLED_I2C_SentByte(0xAE),再写入我们要发送的数据,再关闭IIC。

但我们总不能用一次就写一次吧,所以我们将其写入一个集成函数中

void OLED_WriteCommand(uint8_t Command)
{
	OLED_I2C_Start();				//I2C起始
	OLED_I2C_SendByte(0x78);		//发送OLED的I2C从机地址
	OLED_I2C_SendByte(0x00);		//控制字节,给0x00,表示即将写命令
	OLED_I2C_SendByte(Command);		//写入指定的命令
	OLED_I2C_Stop();				//I2C终止
}

void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
	uint8_t i;
	
	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终止
}

 这里的0x78,0x00,0x40也属于原厂函数哦。

在这里我只解释

0x78

发送字节0x78,这通常是OLED显示屏的I2C从机地址(也称为设备地址或目标地址)。在I2C通信中,发送方(通常是微控制器或处理器)需要知道它正在与哪个设备通信。(就是选择我们的设备!

0x00 0x40

一个字节0x00。这个字节通常是控制字节或命令/数据选择字节。

在我们OLED显示屏的I2C通信协议中,0x00通常表示接下来的数据是命令(而不是数据)。这意味着后续发送的字节将被解释为命令,而不是要在屏幕上显示的数据。与之相对,另一个常见的值(例如0x40)表示接下来的数据是显示数据,应该被写入显示屏的某个位置。

 0x00后面出现了OLED_I2C_SendByte(Command)(我们发送命令的函数)

0x40后出现了

for (i = 0; i < Count; i ++)
    {
        OLED_I2C_SendByte(Data[i]);    //依次发送Data的每一个数据
    }

OLED显示的本质

OLED显示的本质就是操作我们的像素点,通过改变像素点的位置与分布来控制我们的内容。

我们举一个清屏函数

void OLED_Clear(void)
{
	uint8_t i, j;
	for (j = 0; j < 8; j ++)				//遍历8页
	{
		for (i = 0; i < 128; i ++)			//遍历128列
		{
			OLED_DisplayBuf[j][i] = 0x00;	//将显存数组数据全部清零
		}
	}
}

我们将OLED的8页内容的每页的128列全部设置为空(0000 0000)。
  对于uint8_t OLED_DisplayBuf[8][128];其实就是OLED显示的一个缓冲序列,我们想让OLED现实的内容都将会先进入此区间等待我们操作。因为OLED显存数组所有的显示函数,都只是对此显存数组进行读写,随后调用OLED_Update函数或OLED_UpdateArea函数才会将显存数组的数据发送到OLED硬件进行显示。

循循渐进我们直接来讲讲更新函数!

void OLED_Update(void)
{
	uint8_t j;
	/*遍历每一页*/
	for (j = 0; j < 8; j ++)
	{
		/*设置光标位置为每一页的第一列*/
		OLED_SetCursor(j, 0);
		/*连续写入128个数据,将显存数组的数据写入到OLED硬件*/
		OLED_WriteData(OLED_DisplayBuf[j], 128);
	}
}

 我们将光标放入8页中每一页的第一列,这样子我们便可以在第一列按顺序写入我们缓存中的数据!其他的我们都认识但是这里出现了一个SetCursor函数,那么我们继续往回看!

void OLED_SetCursor(uint8_t Page, uint8_t X)
{
	
	/*通过指令设置页地址和列地址*/
	OLED_WriteCommand(0xB0 | Page);					//设置页位置
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}

光标函数,我们将通过命令设置页的位置,列的位置!(相当于一个框出页面)

呢么我们认识一下区域更新函数。

void OLED_UpdateArea(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height)
{
	uint8_t j;
	
	/*参数检查,保证指定区域不会超出屏幕范围*/
	if (X > 127) {return;}
	if (Y > 63) {return;}
	if (X + Width > 128) {Width = 128 - X;}
	if (Y + Height > 64) {Height = 64 - Y;}
	
	/*遍历指定区域涉及的相关页*/
	/*(Y + Height - 1) / 8 + 1的目的是(Y + Height) / 8并向上取整*/
	for (j = Y / 8; j < (Y + Height - 1) / 8 + 1; j ++)
	{
		/*设置光标位置为相关页的指定列*/
		OLED_SetCursor(j, X);
		/*连续写入Width个数据,将显存数组的数据写入到OLED硬件*/
		OLED_WriteData(&OLED_DisplayBuf[j][X], Width);
	}
}

 选定特定的页与列,然后放置光标,填入我们缓冲序列中的内容。(值得一提的是,我们自选时为了防止选定区域时超出范围造成乱码,我们需要有检查参数的函数)
首先,函数检查X和Y的值是否超出了屏幕的范围(假设屏幕宽度为128像素,高度为64像素)。如果超出,函数立即返回,不执行任何操作。
接下来,它检查更新区域的右边界和下边界是否超出了屏幕范围。如果超出,它会相应地调整Width和Height的值,以确保更新区域完全在屏幕内。


for (j = Y / 8; j < (Y + Height - 1) / 8 + 1; j ++)
这个循环遍历了更新区域涉及的“页”。在OLED显示中,显存通常是以字节(8位)为单位组织的,而不是以像素为单位。因此,即使一个像素行只有几个像素宽,它也会占用整个字节。
Y / 8 计算出更新区域的起始页。
(Y + Height - 1) / 8 + 1 计算出更新区域的结束页(注意这里使用-1和+1是为了确保包含完整的最后一行)。

已经不早了,楼主要睡了哈,剩下的明天更新!!! 

 

  • 35
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
stm32f103oled是一种基于Cortex-M3内核的微控制器,它配备了一个128x64个像素的OLED显示屏,可以通过标准进行编程。 使用stm32f103oled标准程序,我们可以通过简单的API函数来控制OLED显示屏的各个方面。例如,我们可以使用中的函数来初始化OLED屏幕,设置像素点的颜色和位置,绘制基本的图形和文本,滚动显示内容等。 要开始编写stm32f103oled标准程序,我们首先需要在开发环境中安装相关的文件和工具链。然后,我们可以使用C语言来编写代码,通过调用标准函数来实现所需的功能。 在程序中,我们可以使用函数来设置OLED屏幕的引脚和通信方式。接下来,我们可以初始化OLED屏幕并设置各种参数,如显示亮度和对比度。 然后,我们可以使用函数来绘制图形和文本。例如,我们可以使用函数来绘制线条、矩形、圆形以及各种几何图形。同时,我们还可以使用函数来显示文本,并可以选择文字的字体和大小。 最后,我们可以使用函数来控制OLED屏幕的刷新和滚动。通过调用相应的函数,我们可以更新显示内容,并且可以设置滚动方向和速度。 总的来说,stm32f103oled标准程序可以提供一套简单而强大的API函数,用于控制OLED显示屏。通过编写合适的代码,我们可以实现各种各样的功能,并能够灵活地显示图形和文本。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值