STM32---IIC的基本原理(通过普通IO模拟IIC读取OLED)

IIC简介

IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接
微控制器及其外围设备。它是半双工通信方式。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号
开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。

IIC的优点

IIC总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。
IIC总线的另一个优点是,它支持多主控(multimastering), 其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。

在这里插入图片描述
IIC串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号是由主控器件产生。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。对于并联在一条总线上的每个IC都有唯一的地址。

在这里插入图片描述
一般情况下,数据线SDA和时钟线SCL都是处于上拉电阻状态。因为:在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

目前绝大多数的MCU都附带IIC总线接口,STM32也不例外。但是在本文中,我们不使用STM32的硬件IIC来读取OLED,而是通过软件IIC的方式来模拟

原因是因为:STM32的硬件IIC非常复杂,更重要的是它并不稳定,故不推荐使用

IIC协议

IIC总线在传输数据的过程中一共有三种类型信号,分别为:开始信号、结束信号和应答信号。这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。同时我们还要介绍其空闲状态、数据的有效性、数据传输。
在这里插入图片描述
这可能会比较复杂,可以先看一份简化了的时序图:
在这里插入图片描述

IIC总线空闲状态

当IIC总线的数据线SDA和时钟线SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

IIC总线起始信号与停止信号

起始信号:当时钟线SCL为高期间,数据线SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号;
停止信号:当时钟线SCL为高期间,数据线SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。

在这里插入图片描述

IIC总线应答信号

发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。

在这里插入图片描述
对于反馈有效应答位ACK的要求是:接收器在第9个时钟脉冲之前的低电平期间将数据线SDA拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放数据线SDA,以便主控接收器发送一个停止信号P。

IIC总线数据有效性

IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定;只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
即:数据在时钟线SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。

在这里插入图片描述

数据的传达

在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。

延时时间

在这里插入图片描述

IIC总线的数据传送

IIC总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。

也就是说,主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据
在这里插入图片描述
主设备往从设备中写数据。数据传输格式如下:
在这里插入图片描述
淡蓝色部分表示数据由主机向从机传送,粉红色部分则表示数据由从机向主机传送。
写用0来表示(高电平),读用1来表示(低电平)。
主设备从从设备中读数据。数据传输格式如下:

在这里插入图片描述
在从机产生响应时,主机从发送变成接收,从机从接收变成发送。之后,数据由从机发送,主机接收,每个应答由主机产生,时钟信号仍由主机产生。若主机要终止本次传输,则发送一个非应答信号,接着主机产生停止条件。

主设备往从设备中写数据,然后重启起始条件,紧接着从从设备中读取数据;或者是主设备从从设备中读数据,然后重启起始条件,紧接着主设备往从设备中写数据。数据传输格式如下:
在这里插入图片描述
在多主的通信系统中,总线上有多个节点,它们都有自己的寻址地址,可以作为从节点被别的节点访问,同时它们都可以作为主节点向其它的节点发送控制字节和传送数据。但是如果有两个或两个以上的节点都向总线上发送启动信号并开始传送数据,这样就形成了冲突。要解决这种冲突,就要进行仲裁的判决,这就是I2C总线上的仲裁。

IIC总线底层驱动程序分析

这里我应用PA5,PA7来模拟IIC,其中PA5为时钟线,PA7为数据线。
首先进行一些必要的宏定义:

#ifndef __OLED__H
#define __OLED__H			  	 


#include "stm32f10x.h"


/*--------------------引脚定义--------------------------*/
#define 		OLED_SCLK_Set()			GPIO_SetBits(GPIOA,GPIO_Pin_5)	//PA5(SCL)输出高
#define			OLED_SCLK_Clr()			GPIO_ResetBits(GPIOA,GPIO_Pin_5)	//PA5(SCL)输出低
#define			OLED_SDIN_Set()			GPIO_SetBits(GPIOA,GPIO_Pin_7)	//PA7(SDA)输出高
#define			OLED_SDIN_Clr()			GPIO_ResetBits(GPIOA,GPIO_Pin_7)	//PA7(SDA)输出高
#define 		OLED_READ_SDIN()		GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7)	//读取PA7(SDA)电平
		

/*definition--------------------------------------------*/
#define OLED_CMD  0	//写命令
#define OLED_DATA 1	//写数据

#define SIZE 		16		//显示字符的大小
#define Max_Column	128		//最大列数
#define Max_Row		64		//最大行数
#define X_WIDTH 	128		//X轴的宽度
#define Y_WIDTH 	64	    //Y轴的宽度					  				   
#define	IIC_ACK		0		//应答
#define	IIC_NO_ACK	1		//不应答




/*-----------------声明函数-----------------------------*/
void OLED_WR_Byte(unsigned char dat,unsigned char cmd);	  //OLED写字节函数
void OLED_Display_On(void);	//开显示函数
void OLED_Display_Off(void);//关显示函数
void OLED_Init(void);	//OLED初始化函数
void OLED_Clear(void);  //清屏函数
void OLED_ShowChar(unsigned char x,unsigned char y,unsigned char chr); //显示字符函数
void OLED_ShowNum(unsigned char x,unsigned char y,unsigned int num,unsigned char len,unsigned char size2); //在指定的位置,显示一个指定数的长度大小函数
void OLED_ShowString(unsigned char x,unsigned char y, unsigned char *p);	 //在指定位置开始显示字符串函数
void OLED_Set_Pos(unsigned char x, unsigned char y);		//画点函数
void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no); //声明在指定位置显示汉字函数
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]); //显示图片函数
void OLED_Scroll(void);	//滚动函数

#endif  

由于**IIC是半双工通信方式**,因而数据线SDA可能会数据输入,也可能是数据输出,需要定义IIC_SDA来进行输出、READ_SDA来进行输入,与此同时就要对IO口进行模式配置:SDA_IN()和SDA_OUT()。

而时钟线SCL一直是输出的,所以就没有数据线SDA麻烦了。


/*includes----------------------------------------------*/
#include "oled.h"
#include "oledfont.h" 
/*definition--------------------------------------------*/


/*
	@brief			延迟1us
	@param			无
	@retval			无
 */
static void delay(unsigned char num)
{
    
    
	uint8_t i = 10;
	while(num--)
	{
    
    
		while(i--);
	}
}


/*
	@brief			ms延迟函数
	@param			无
	@retval			无
*/
static void delay_ms(unsigned int ms)//延迟函数,MS级别
{
    
    
	unsigned int x,y;
	for(x = ms;x>0;x--)
 {
    
    
		for(y = 12000;y>0;y--);
 }
}


/*
	@brief			初始化OLED与单片机的IO接口
	@param			无
	@retval			无
 */
static void OLED_GPIO_Init(void)
{
    
    
	GPIO_InitTypeDef GPIO_InitStructure;	//定义一个GPIO_InitTypeDef类型的结构体
	
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE);	//打开GPIOC的外设时钟
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;	//选择控制的引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;	//设置为通用开漏输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//设置输出速率为50MHz
	GPIO_Init(GPIOA,&GPIO_InitStructure);	//调用库函数初始化GPIOA
	
	OLED_SCLK_Set();	//设PC2(SCL)为高电平
	OLED_SDIN_Set();	//设PC3(SDA)为高电平
}


/*
	@brief			模拟IIC起始信号
	@param			无
	@retval			无
 */
static void OLED_IIC_Start(void)
{
    
    

	OLED_SCLK_Set();	//时钟线置高
	OLED_SDIN_Set();	//信号线置高
	delay(1);	//延迟1us
	OLED_SDIN_Clr();	//信号线置低
	delay(1);	//延迟1us
	OLED_SCLK_Clr();	//时钟线置低
	delay(1);	//延迟1us
}


/*
	@brief			模拟IIC停止信号
	@param			无
	@retval			无
 */
static void OLED_IIC_Stop(void)
{
    
    
	OLED_SDIN_Clr();	//信号线置低
	delay(1);	//延迟1us
	OLED_SCLK_Set();	//时钟线置高
	delay(1);	//延迟1us
	OLED_SDIN_Set();	//信号线置高
	delay(1);	//延迟1us
}


/*
	@brief			模拟IIC读取从机应答信号
	@param			无
	@retval			无
 */
static unsigned char IIC_Wait_Ack(void)
{
    
    
	unsigned char ack;

	OLED_SCLK_Clr();	//时钟线置低
	delay(1);	//延迟1us
	OLED_SDIN_Set();	//信号线置高
	delay(1);	//延迟1us
	OLED_SCLK_Set();	//时钟线置高
	delay(1);	//延迟1us

	if(OLED_READ_SDIN())	//读取SDA的电平
		ack = IIC_NO_ACK;	//如果为1,则从机没有应答
	else
		ack = IIC_ACK;		//如果为0,则从机应答

	OLED_SCLK_Clr();//时钟线置低
	delay(1);	//延迟1us

	return ack;	//返回读取到的应答信息
}


/*
	@brief			模拟IIC主机发送应答信号
	@param			无
	@retval			无
 */
// static void IIC_Send_Ack(unsigned char ack)
// {
    
    
// 	OLED_SCLK_Clr();	//时钟线置低
// 	delay(1);	//延迟1us

// 	if(ack == IIC_ACK)	//根据情况发送应答/不应答
// 		OLED_SDIN_Clr();	//应答
// 	else
// 		OLED_SDIN_Set();	//不应答
// 	delay(1);	//延迟1us

// 	OLED_SCLK_Set();	//时钟线置高,发送应答/不应答信号
// 	delay(1);	//延迟1us
// 	OLED_SCLK_Clr();	//时钟线置低
// 	delay(1);	//延迟1us
// }


/*
	@brief			IIC写入一个字节
	@param			IIC_Byte:写入的字节
	@retval			无
 */
static void Write_IIC_Byte(unsigned char IIC_Byte)
{
    
    
	unsigned char i;  //定义变量
	for(i=0;i<8;i++) //for循环8次
	{
    
    
		OLED_SCLK_Clr();	//时钟线置低,为传输数据做准备
		delay(1);	//延迟1us

		if(IIC_Byte & 0x80)	//读取最高位
		  	OLED_SDIN_Set();//最高位为1
		else
			OLED_SDIN_Clr();	//最高位为0

		IIC_Byte <<= 1;  //数据左移1位
		delay(1);	//延迟1us
		OLED_SCLK_Set(); //时钟线置高,产生上升沿,把数据发送出去
		delay(1);	//延迟1us
	}
	OLED_SCLK_Clr();	//时钟线置低
	delay(1);	//延迟1us

	while(IIC_Wait_Ack());	//从机应答
}



/*
	@brief			IIC写读取一个字节
	@param			ack:应答/不应答
	@retval			无
 */
// static unsigned char Read_IIC_Byte(unsigned char ack)
// {
    
    
// 	unsigned char data = 0;  //定义变量
// 	unsigned char i;  //定义变量

// 	OLED_SCLK_Clr();	//时钟线置低,为传输数据做准备
// 	delay(1);	//延迟1us

// 	OLED_SDIN_Set();

// 	for(i=0;i<8;i++) //for循环8次
// 	{
    
    
// 		OLED_SCLK_Set();	//时钟线置高,读取数据
// 		delay(1);	//延迟1us

// 		data <<= 1;	//左移1位
// 		if(OLED_READ_SDIN())	//读取最高位
// 		  	data |= 0x01;	//读到高位位1
// 		else 
// 			data &= 0xfe;	//读到高位为0

// 		OLED_SCLK_Clr();	//时钟线置低,准备读取下一个数据
// 		delay(1);	//延迟1us
// 	}

// 	IIC_Send_Ack(ack);	//发送应答/不应答

// 	return data;	//返回读取到的数据
// }


/*
	@brief			IIC写入命令
	@param			IIC_Command:写入的命令
	@retval			无
 */
static void Write_IIC_Command(unsigned char IIC_Command)
{
    
    
   OLED_IIC_Start();
   Write_IIC_Byte(0x78);//写入从机地址,SD0 = 0
   Write_IIC_Byte(0x00);//写入命令
   Write_IIC_Byte(IIC_Command);//数据
   OLED_IIC_Stop();  //发送停止信号
}


/*
	@brief			IIC写入数据
	@param			IIC_Data:数据
	@retval			无
 */
static void Write_IIC_Data(unsigned char IIC_Data)
{
    
    
   OLED_IIC_Start();
   Write_IIC_Byte(0x78);	//写入从机地址,SD0 = 0
   Write_IIC_Byte(0x40);	//写入数据
   Write_IIC_Byte(IIC_Data);//数据
   OLED_IIC_Stop();		//发送停止信号
}


/*
	@brief			对OLED写入一个字节
	@param			dat:数据
					cmd:1,写诶数据;0,写入命令
	@retval			无
 */
void OLED_WR_Byte(unsigned char dat,unsigned char cmd)
{
    
    
	if(cmd) 
	  {
    
    
       Write_IIC_Data(dat); //写入数据
	  }
	else {
    
    
       Write_IIC_Command(dat); //写入命令
	}
}

读取oled芯片程序

void OLED_Display_Off(void)
{
    
    
	OLED_WR_Byte(0XAE,OLED_CMD);  //关显示
	OLED_WR_Byte(0X8D,OLED_CMD);  //设置OLED电荷泵
	OLED_WR_Byte(0X10,OLED_CMD);  //失能,关
}		   			 


/*
	@brief			清屏
	@param			无
	@retval			无
 */	  
void OLED_Clear(void)  
{
    
      
	unsigned char i,n;		    //定义变量
	for(i=0;i<8;i++)  
	{
    
      
		OLED_WR_Byte (0xb0+i,OLED_CMD);    //从0~7页依次写入
		OLED_WR_Byte (0x00,OLED_CMD);      //列低地址
		OLED_WR_Byte (0x10,OLED_CMD);      //列高地址  
		for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); //写入 0 清屏
	}
}


/*
	@brief			显示一个字符
	@param			x:起始列
					y:起始页,SIZE = 16占两页;SIZE = 12占1页
					chr:字符
	@retval			无
 */
void OLED_ShowChar(unsigned char x,unsigned char y,unsigned char chr)
{
    
          	
	unsigned char c=0,i=0;	
		c=chr-' '; //获取字符的偏移量	
		if(x>Max_Column-1){
    
    x=0;y=y+2;} //如果列数超出了范围,就从下2页的第0列开始

		if(SIZE ==16) //字符大小如果为 16 = 8*16
			{
    
    
				OLED_Set_Pos(x,y);	//从x y 开始画点
				for(i=0;i<8;i++)  //循环8次 占8列
				OLED_WR_Byte(F8X16[c*16+i],OLED_DATA); //找出字符 c 的数组位置,先在第一页把列画完
				OLED_Set_Pos(x,y+1); //页数加1
				for(i=0;i<8;i++)  //循环8次
				OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA); //把第二页的列数画完
			}
		else 	//字符大小为 6 = 6*8
			{
    
    	
				OLED_Set_Pos(x,y+1); //一页就可以画完
				for(i=0;i<6;i++) //循环6次 ,占6列
				OLED_WR_Byte(F6x8[c][i],OLED_DATA); //把字符画完
			}
}


/*
	@brief			计算m^n
	@param			m:输入的一个数
					n:输入数的次方
	@retval			result:一个数的n次方
 */
unsigned int oled_pow(unsigned char m,unsigned char n)
{
    
    
	unsigned int result=1;	 
	while(n--)result*=m;    
	return result;
}				  


/*
	@brief			在指定的位置,显示一个指定长度大小的数
	@param			x:起始列
					y:起始页
					num:数字
					len:数字的长度
					size:显示数字的大小
	@retval			无
 */		  
void OLED_ShowNum(unsigned char x,unsigned char y,unsigned int num,unsigned char len,unsigned char size)
{
    
             	
	unsigned char t,temp;  //定义变量
	unsigned char enshow=0;		//定义变量

	for(t=0;t<len;t++)
	{
    
    
		temp=(num/oled_pow(10,len-t-1))%10;//取出输入数的每个位,由高到低
		if(enshow==0&&t<(len-1)) //enshow:是否为第一个数;t<(len-1):判断是否为最后一个数
		{
    
    
			if(temp==0) //如果该数为0 
			{
    
    
				OLED_ShowChar(x+(size/2)*t,y,' ');//显示 0 ;x+(size2/2)*t根据字体大小偏移的列数(8)
				continue; //跳过剩下语句,继续重复循环(避免重复显示)
			}else enshow=1; 
		}
	 	OLED_ShowChar(x+(size/2)*t,y,temp+'0'); //显示一个位;x+(size2/2)*t根据字体大小偏移的列数(8)
	}
} 


/*
	@brief			显示字符串
	@param			x:起始列
					y:起始页
					*chr:第一个字符首地址
	@retval			无
 */
void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *chr)
{
    
    
	unsigned char j=0; //定义变量

	while (chr[j]!='\0') //如果不是最后一个字符
	{
    
    		
		OLED_ShowChar(x,y,chr[j]); //显示字符
		x+=8; //列数加8 ,一个字符的列数占8
		if(x>=128){
    
    x=0;y+=2;} //如果x大于等于128,切换页,从该页的第一列显示
		j++; //下一个字符
	}
}


/*
	@brief			显示中文
	@param			x:起始列;一个字体占16列
					y:起始页;一个字体占两页
					no:字体的序号
	@retval			无
 */
void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no)
{
    
          			    
	unsigned char t,adder=0; //定义变量

	OLED_Set_Pos(x,y);	//从 x y 开始画点,先画第一页
    for(t=0;t<16;t++) //循环16次,画第一页的16列
		{
    
    
			OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);//画no在数组位置的第一页16列的点
			adder+=1; //数组地址加1
     	}	
		OLED_Set_Pos(x,y+1); //画第二页
    for(t=0;t<16;t++)//循环16次,画第二页的16列
		{
    
    	
			OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);//画no在数组位置的第二页16列的点
			adder+=1;//数组地址加1
        }					
}


/*
	@brief			显示图片
	@param			x0:起始列地址
					y0:起始页地址
					x1:终止列地址
					y1:终止页地址
					BMP[]:存放图片代码的数组
	@retval			无
 */
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;   //判断终止页是否为8的整数倍
 	 else y=y1/8+1;

		for(y=y0;y<y1;y++) //从起始页开始,画到终止页
		{
    
    
			OLED_Set_Pos(x0,y); //在页的起始列开始画
   			for(x=x0;x<x1;x++) //画x1 - x0 列
	    		{
    
    
	    			OLED_WR_Byte(BMP[j++],OLED_DATA);	//画图片的点    	
	    		}
		}
} 


/*
	@brief			OLED初始化函数
	@param			无
	@retval			无
 */				    
void OLED_Init(void)
{
    
    
	OLED_GPIO_Init();	//GPIO口初始化
 
	delay_ms(200);	//延迟,由于单片机上电初始化比OLED快,所以必须加上延迟,等待OLED上复位完成

	OLED_WR_Byte(0xAE,OLED_CMD);	//关闭显示

	OLED_WR_Byte(0x00,OLED_CMD);	//设置低列地址
	OLED_WR_Byte(0x10,OLED_CMD);	//设置高列地址
	OLED_WR_Byte(0x40,OLED_CMD);	//设置起始行地址
	OLED_WR_Byte(0xB0,OLED_CMD);	//设置页地址

	OLED_WR_Byte(0x81,OLED_CMD); 	// 对比度设置,可设置亮度
	OLED_WR_Byte(0xFF,OLED_CMD);	//  265  

	OLED_WR_Byte(0xA1,OLED_CMD);	//设置段(SEG)的起始映射地址;column的127地址是SEG0的地址
	OLED_WR_Byte(0xA6,OLED_CMD);	//正常显示;0xa7逆显示

	OLED_WR_Byte(0xA8,OLED_CMD);	//设置驱动路数(16~64)
	OLED_WR_Byte(0x3F,OLED_CMD);	//64duty
	
	OLED_WR_Byte(0xC8,OLED_CMD);	//重映射模式,COM[N-1]~COM0扫描

	OLED_WR_Byte(0xD3,OLED_CMD);	//设置显示偏移
	OLED_WR_Byte(0x00,OLED_CMD);	//无偏移
	
	OLED_WR_Byte(0xD5,OLED_CMD);	//设置震荡器分频
	OLED_WR_Byte(0x80,OLED_CMD);	//使用默认值
	
	OLED_WR_Byte(0xD9,OLED_CMD);	//设置 Pre-Charge Period
	OLED_WR_Byte(0xF1,OLED_CMD);	//使用官方推荐值
	
	OLED_WR_Byte(0xDA,OLED_CMD);	//设置 com pin configuartion
	OLED_WR_Byte(0x12,OLED_CMD);	//使用默认值
	
	OLED_WR_Byte(0xDB,OLED_CMD);	//设置 Vcomh,可调节亮度(默认)
	OLED_WR_Byte(0x40,OLED_CMD);	使用官方推荐值
	
	OLED_WR_Byte(0x8D,OLED_CMD);	//设置OLED电荷泵
	OLED_WR_Byte(0x14,OLED_CMD);	//开显示
	
	OLED_WR_Byte(0xAF,OLED_CMD);	//开启OLED面板显示
	OLED_Clear();        //清屏
	OLED_Set_Pos(0,0); 	 //设置数据写入的起始行、列
}  


/*
	@brief			OLED滚屏函数,范围0~1页,水平向左
	@param			无
	@retval			无
 */	
void OLED_Scroll(void)
{
    
    
	OLED_WR_Byte(0x2E,OLED_CMD);	//关闭滚动
	OLED_WR_Byte(0x27,OLED_CMD);	//水平向左滚动
	OLED_WR_Byte(0x00,OLED_CMD);	//虚拟字节
	OLED_WR_Byte(0x00,OLED_CMD);	//起始页 0
	OLED_WR_Byte(0x00,OLED_CMD);	//滚动时间间隔
	OLED_WR_Byte(0x01,OLED_CMD);	//终止页 1
	OLED_WR_Byte(0x00,OLED_CMD);	//虚拟字节
	OLED_WR_Byte(0xFF,OLED_CMD);	//虚拟字节
	OLED_WR_Byte(0x2F,OLED_CMD);	//开启滚动
}

主函数初始化程序:

int main(void)
{
    
    
	SystemInit();
	OLED_Init();  //OLED初始化
	OLED_Clear();
	
	OLED_ShowString(30,2,"OLED TEST");// OLED TEST
	OLED_ShowCHinese(16,0,0);// 技
	OLED_ShowCHinese(32,0,1);// 新
	OLED_ShowCHinese(48,0,2);// 电
	OLED_ShowCHinese(64,0,3);// 子
	OLED_ShowCHinese(80,0,4);// 科
	OLED_ShowCHinese(96,0,5);// 技
	
	while(1)
	{
    
    
		
	}
}

IIC总结

1.进行数据传送时,在SCL为高电平期间,SDA线上电平必须保持稳定,只有SCL为低时,才允许SDA线上电平改变状态。并且每个字节传送时都是高位在前;
2.对于应答信号,ACK=0时为有效应答位,说明从机已经成功接收到该字节,若为1则说明接受不成功;
3.如果从机需要延迟下一个数据字节开始传送的时间,可以通过把SCL电平拉低并保持来强制主机进入等待状态;
4.主机完成一次通信后还想继续占用总线在进行一次通信,而又不释放总线,就要利用重启动信号。它既作为前一次数据传输的结束,又作为后一次传输的开始;
5.总线冲突时,按“低电平优先”的仲裁原则,把总线判给在数据线上先发送低电平的主器件
6.在特殊情况下,若需禁止所有发生在I2C总线上的通信,可采用封锁或关闭总线,具体操作为在总线上的任一器件将SCL锁定在低电平即可;
7.SDA仲裁和SCL时钟同步处理过程没有先后关系,而是同时进行的

猜你喜欢

转载自blog.csdn.net/weixin_43491077/article/details/110057158