锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

STM32基于IIC协议的OLED模块的使用

时间:2022-09-21 16:30:00 复合式通用序列总线连接器

前言

一、项目涉及的内容

项目简介

二、模块实操

1. IIC模块

1.1 IIC协议格式

1.2开始信号与停止信号

1.3 写数据

1.3.1 硬件IIC代码编写

1.3.2 软件模拟IIC代码编写

2. OLED板块


前言

本文用于使用IIC协议操作OLED总结了屏幕。


一、项目涉及的内容

本实验的硬件组成包括STM32F103C8T6开发板、OLED模块(0.96寸4针IIC接口OLED显示屏),设计的软件模块有GPIO、IIC、系统定时器SysTick,接下来,我将解释这些主要模块,并回顾项目的重点。我希望你能有所收获。

项目简介

通过IIC传输和控制数据OLED屏幕显示文本和图像。


二、模块实操

1. IIC模块

IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 连接微控制器及其外围设备的公司开发的两线串行总线。一个 I2C 通信只需使用两条总线,一条双向串行数据线(SDA) ,串行时钟线(SCL)。数据线SDA用来表示数据,时钟线SCL用于数据收发同步。SCL由主机生成,SDA由主机或从机生成。

I2C同时属于半双工,即同时串行通信SDA一个设备只能发送信号。IIC以字节的形式传输,每次传输8位(一个字节)。

接口是通过SCL与SDA连接到IIC总线上的,接口可以下述4种模式中的一种运行:
● 从发送模式
● 接收器模式
● 主发送器模式
● 主接收器模式
默认情况下,模块从模式工作。接口自动从模式切换到主模式;当仲裁丢
丢失(两个或两个以上的主要设备应优先通信)或产生停止信号时,从主模式切换到从模式。主模式时, I2C接口启动数据传输,产生时钟信号。串行数据传输始终以初始条件和停止条件开始。

1.1 IIC协议格式

IIC简单总结一下通信方式:

  1. 主机产生开始信号,跟随开始条件后的1或2个字节为地址(7位模式为1个字节, 10位模式为2个字节),字节的最后一位确定读/写。从机I2C接口可以识别自己的地址(7或10)和广播呼叫地址,I2C与从机地址相同的从机开始与主机通信。
  2. 第二个字节传输从机寄存器地址,即我们想将内容传输到哪个地址,分为数据寄存器和命令寄存器。它们像名字一样传输到相应的寄存器,表示要传输数据/命令。
  3. 从第三个字节开始,它被传送到从机数据,以查看命令或数据的数量。主机每次发送一次
    个字节数据应等待从机响应信号(ACK),重复这个过程可以从机器传输到 N 个数据,
    这个 N 大小没有限制。

图片来源:零死角玩转 STM32F103—霸道>>>第24章 I2C—读写EEPROM>>>图 24-4 I2C 通信复合格式


1.2开始信号和停止信号

I2C 在传输数据的过程中,总线有三种信号, 它们分别是始信号、结束信号和响应
信号。
开始信号: SCL 高电平时, SDA 数据从高电平跳转到低电平跳转
结束信号: SCL 高电平时, SDA 数据从低电平跳转到高电平跳转结束。
响应信号:接收数据 IC 在接收到 8bit 数据发送到数据后 IC 发出特定的低电平脉冲,
表示已收到数据。 CPU 向受控单位发出信号后,等待受控单位发出响应信号, CPU 接
收到响应信号后,根据实际情况判断信号是否继续传输。未收到响应信号的,判断为
受控单元出现故障。
在这些信号中,起始信号是必要的,没有结束信号和响应信号。

图片来源:STM32F1xx中文参考手册>>>24 I2C接口>>>24.3 I2C功能描述>>>24.3.1 模式选择

IIC通信中,数据和地址按8位/字节传输,高位在前。跟随起始条件的1或2个字节为地址(7位模式为1个字节, 10位模式为2个字节)。地址式只发送地址。我们选择了7位地址模式。我们可以看到,在字节传输8小时后的第9小时内,接收器必须返回响应位置(ACK)给发送器。


1.3 写数据

IIC通信有两种方式:软件和硬件。软件引脚的定义将更加灵活。硬件引脚是固定的,但通信效率将更高。

1.3.1 硬件IIC代码编写

STM32的IIC片上外设专门负责实现IIC通信协议,只要配置好外设,就会根据协议的要求自动产生通信信号,收发数据并缓存,CPU数据收发可以通过检测外设状态和访问数据寄存器来完成。

stm32F1系列的硬件I2C默认情况下使用外设PB6和PB7两个引脚。

图片来源:STM32F1xx中文参考手册 >>>8.3.9 I2C1 复用功能重映射

我们分别配置GPIO口与I2C外设结构体成员,然后调用库函数GPIO_Init()与I2C_Init() 将结构体的配置写入寄存器。

//硬件IIC初始化配置 void I2C_Configuration(void) {  GPIO_InitTypeDef GPIO_InitStructure;  I2C_InitTypeDef I2C_InitStructure;    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);  RCC_APB1PeriphClockCmd( RCC_APB1Periph_I2C1, ENABLE);      //PB6 --SCL; PB7 --SDA  GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init( GPIOB, &GPIO_InitStructure); //初始化GPIO配置

	I2C_DeInit( I2C1);	//复位初始化IIC的配置
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //使能响应
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //指定地址长度,可为7或10
	I2C_InitStructure.I2C_ClockSpeed = 400000; //设置SCL时钟频率,400kHz
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //时钟占空比,可选low/high = 2:0或16:9
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //指定工作模式为IIC,可选IIC模式及SMBUS模式
	I2C_InitStructure.I2C_OwnAddress1 = 0X10; //自身的IIC设备地址
	I2C_Init( I2C1, &I2C_InitStructure); //初始化IIC外设配置
	I2C_Cmd( I2C1, ENABLE); //使能IIC外设
}

IIC主机发送符合协议内容的数据出去之后,IIC外设会产生相应的时间EVx,也就是IIC外设的相关寄存器会自动置0或置1,我们只要去读取对应的寄存器就可以确定事件是否发生。

 图片来源:STM32F1xx中文参考手册>>>24 I2C接口>>>24.3.1 模式选择>>>图245 主发送器传送序列图

图片来源:STM32F1xx中文参考手册>>>24 I2C接口>>>24.3.3 I2C主模式>>>图242 I2C的功能框图

我们要发送数据,要做的有以下几件事情:

  1. 先检查IIC总线是否有被其他的主机使用
  2. 如果IIC总线处于空闲状态,我们就使用库函数I2C_GenerateSTART()产生IIC起始信号
  3. 发送从机地址
  4. 发送寄存器地址,确定是要输入命令还是数据内容
  5. 往命令/数据寄存器发送数据内容
  6. 关闭IIC总线,让IIC总线重新进入空闲状态等待被主机激活

其中除了一开始检查IIC总线是否处于繁忙状态外,每一件事情我们都要去检测对应的EVx事件是否发生,等待每一个事情触发事件后我们继续进行下一步,检测的函数以及对应的事件都已经在文件stm32f10x_i2c.h与stm32f10x_i2c.c中定义好了。

//硬件IIC写字节
void I2C_WriteByte(uint8_t addr, uint8_t data)
{
	//先检查I2C总线是否繁忙
	while( I2C_GetFlagStatus( I2C1, I2C_FLAG_BUSY)); 
	//开启I2C1
	I2C_GenerateSTART( I2C1, ENABLE); 	
	while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_MODE_SELECT)); //检查是否应答,等待触发EV5
	//发送器件地址
	I2C_Send7bitAddress( I2C1, Slave_Address, I2C_Direction_Transmitter); 	
	while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));	//等待触发EV6
    //发送寄存器地址
	I2C_SendData( I2C1, addr);	
	while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));	//等待触发EV8
	//发送数据
	I2C_SendData( I2C1, data);	
	while( !I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) ); //等待触发EV8_2
	//关闭I2C1总线
	I2C_GenerateSTOP( I2C1, ENABLE);	
}

1.3.2 软件模拟IIC代码编写

我们也可以直接控制STM32的两个GPIO引脚分别用作SCL及SDA,然后按照上述信号的时序要求,遵循通讯协议,控制引脚的输出(如果是接收数据则是读取SDA电平),就可以实现IIC通讯。直接控制 GPIO 引脚电平产生通讯时序,由 CPU 控制每个时刻的引脚状态,这种方式称之为“软件模拟协议”方式。

我们进行初始化配置的时候,就不需要配置IIC外设了,只需要对GPIO口进行配置就可以了。

//软件模拟IIC协议初始化
void OLED_GPIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
	
	//PB0 --SCL; PB1 --SDA
    //I2C支持多个主设备与多个从设备连接在同一根总线上,如果不开漏输出,会出现短路现象
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init( GPIOB, &GPIO_InitStructure);
	
	OLED_SCLK_Set(); //初始化时钟总线高电平
	OLED_SDIN_Set(); 初始化总线高电平
}

 软件模拟IIC时序的话,就要按照IIC协议规定的方式严格执行控制引脚的输出。

 图片来源:STM32F1xx中文参考手册>>>24 I2C接口>>>24.3 I2C功能描述>>>24.3.1 模式选择

//模拟I2C起始信号
static void OLED_IIC_Start(void)
{
	OLED_SCLK_Set(); //时钟总线高电平
	OLED_SDIN_Set(); //数据总线高电平
	us_delay(1);
	
	OLED_SDIN_Clr();
	us_delay(1);
	OLED_SCLK_Clr();
	us_delay(1);
}

//模拟I2C结束信号
static void OLED_IIC_Stop(void)
{
	OLED_SDIN_Clr(); //数据总线低电平
	us_delay(1);
	OLED_SCLK_Set(); //时钟总线高电平
	us_delay(1);
	OLED_SDIN_Set();
	us_delay(1);	
}

//模拟I2C读取从机应答信号
static unsigned char IIC_Wait_Ack(void)
{
	unsigned char ack;
	OLED_SCLK_Clr(); //时钟总线低电平
	us_delay(1);
	OLED_SDIN_Set(); //数据总线高电平
	us_delay(1);	
	OLED_SCLK_Set(); //时钟总线高电平
	us_delay(1);
	
	if(OLED_READ_SDIN()) //无应答
	{
		ack = IIC_NO_ACK;
	}
	else
	{
		ack = IIC_ACK;
	}		
	OLED_SCLK_Clr(); //时钟线置低
	us_delay(1);	
	return ack;
}

传输时, SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“ 1”,为低电平时表示数据“0”。当 SCL为低电平时, SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。传输数据的时候,将SCL置低,然后设置SDA总线对应的引脚电平为高/低,SDA电平确定后再讲SCL置高,将8个位由高到低依次发送出去。

//IIC传输一个字节,传输时先从高位开始
static void Write_IIC_Byte(unsigned char IIC_Byte)
{
	unsigned char i; //定义变量
	for(i=0;i<8;i++)
	{
		OLED_SCLK_Clr();  //时钟线置低
		us_delay(1);
		if(IIC_Byte & 0x80) //读取最高位
			OLED_SDIN_Set();  //最高位为1
		else
			OLED_SDIN_Clr();  //最高位为0
		
		us_delay(1);
		OLED_SCLK_Set(); //时钟线置高,产生上升沿把数据送出去
		us_delay(1);
		
		IIC_Byte <<= 1; //数据左移一位
	}
	OLED_SCLK_Clr();  //时钟线置低
	us_delay(1);
	
	IIC_Wait_Ack();   //从机等待(这边应该是要监测返回值是否正确,当不应答时重新进行连接或者暂停传输数据)
}

2. OLED板块

我们用的0.96寸OLED显示屏芯片是SSD1306,这里我们将OLED屏幕作为从机,数据和应答都是通过SDA发送的,SSD1306的IIC通讯接口的数据总线是由SDAout和SDAin输入组成的,SDAin和SDAout绑定到了一起作为SDA,SDAout引脚可以不连接,当不连接时,应答信号将会被IIC总线忽略。

当主机通过开始条件初始化IIC通讯,并且广播的从机地址与我们要接收数据的从机地址相同时,发起开始信号的主机与从机双方将建立通讯,控制字节或数据字节开始通过SDA传输。

数据位的解读是基于D/C#位引脚的输入,如果 D/C#引脚是高,D[7:0]就被解读为写到图像显示数据 RAM( GDDRAM)中的显示数据,GDDRAM 列地址指针将会在每次数据写之后自动加 1。
如果是低, D[7:0]的输入就被解读为一个命令。然后数据输入就会被解码并写到相关的命令
寄存器中。

GDDRAM 是一个为映射静态 RAM 保存位模式来显示。该 RAM 的大小为 128 * 64 为, RAM
分为 8 页,从 PAFE0 到 PAGE7,用于单色 128 * 64 点阵显示,如下图所示
 

 

 图片来源:SSD1306-OLED驱动芯片中文手册

SSD1306 中有三种不同的内存地址模式:页地址模式,水平地址模式,垂直地址模式。我们使用页地址模式,在页地址模式下,在显示 RAM 读写之后,列地址指针自动加一。如果列地址指针达到了列的结束地址,列地址指针重置为列开始地址并且页地址指针不会改变。用户需要设置新的页
和列地址来访问下一页 RAM 内容
。页地址模式下 PAGE 和列地址指针的移动模式参考下图
 

使用下面的步骤来定义开始 RAM 访问的位置:
1. 通过命令 B0h 到 B7h 来设置目标显示位置的页开始地址
2. 通过 00h~0Fh 来设置低开始列地址的指针
3. 通过命令 10h~1Fh 来设置高开始列地址
比如说,如果页地址设置为 B2h,低列地址是 03h 高列地址为 00h,那么就意味着开始列是
PAGE2 的 SEG3.RAM 访问指针的位置如下图所示。输出数据字节将写到 RAM 列 3 的位置。

 OLED屏幕的初始化代码用到的指令都是芯片手册上有固定的格式,厂家一般会提供初始化代码,格式略有不同,这部分就不贴出来了。

下边代码分别是设置屏幕显示的位置、显示字符、显示字符串、显示中文、显示图片

  • 设置图像显示的起点行、列
//设置数据写入的起始行,列
//x:列的起始低地址与起始高地址
//y:起始页地址  0-7
void OLED_Set_Pos(unsigned char x, unsigned char y)
{
	OLED_WR_Byte( 0xb0+y, OLED_CMD); //写入页地址
	OLED_WR_Byte( x&0x0f, OLED_CMD); //写入列的地址,低半个字节
	OLED_WR_Byte( (x&0xf0)>>4 | 0x10, OLED_CMD); //写入列地址,高半个字节		
}
  •  设置OLED屏幕显示ASCII码字符,有8*16与6*8两种字符大小
//显示字符
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char chr)
{
	unsigned char j = 0;
	chr = chr - ' '; //获取字符的偏移量
	
	if( x >= Max_Column)//让x限制在128列内
		x=0;	
	
	if( SIZE == 16) //字符8*16(高度是16,所以要分为两个上下两部分来发送)
	{		
		OLED_Set_Pos(x,y); //上半段
		for(j=0; j<8; j++)
		{
			OLED_WR_Byte( F8X16[chr*16+j],OLED_DAT);
		}	
		OLED_Set_Pos(x,y+1); //下半段
		for(j=0; j<8; j++)
		{
			OLED_WR_Byte( F8X16[chr*16+j+8],OLED_DAT);
		}			
	}
	else if( SIZE == 6) //字符6*8
	{
		OLED_Set_Pos(x,y);
		for(j=0; j<6; j++)
			OLED_WR_Byte( F6x8[chr*8][j],OLED_DAT);
	}		
	
}	
  •  设置OLED屏幕显示字符串
//显示字符串
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *chr)
{
	unsigned char j=0;
	while(chr[j] != '\0') //判断是不是最后一个字符
	{
		if(SIZE == 16)
		{
			if( x >= Max_Column)
			{
				x=0;
				y=y+2;
			}	
			OLED_ShowChar(x,y,chr[j]);	//显示字符
			x=x+8; //一个字符占8列
		}
		else if(SIZE == 6)
		{
			if( x >= Max_Column)
			{
				x=0;
				y=y+1;
			}
			OLED_ShowChar(x,y,chr[j]);	//显示字符			
			x=x+6; //一个字符占6列			
		}
		j++; //下一个字符
	}
	
}
  • 设置OLED屏幕显示中文字体 
//显示文字
void OLED_ShowChinese(unsigned char x, unsigned char y, unsigned char list)
{
	unsigned char i=0;
	
	OLED_Set_Pos(x, y); //中文字体上半部分
	for(i=0;i<16;i++)
	{
		OLED_WR_Byte( F16x16[list*32+i], OLED_DAT); //一个字体占用了16+16个字节,所以是list乘以32,下半部分就再加上16
	}
	
	OLED_Set_Pos(x,y+1); //中文字体下半部分
	for(i=0;i<16;i++)
	{
		OLED_WR_Byte( F16x16[list*32+16+i], OLED_DAT);
	}		
}
  •  设置OLED屏幕显示bmp格式图像
//显示图片
void OLED_DrawBMP(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char BMP[])
{
	unsigned int j =0;
	unsigned char x,y;
	if(y1%8 == 0)
		y = y1/8;
	else
		y = y1/8 +1;
	for(y=y0;y
锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章