IIC通信(STM32)

一、IIC概念

 1、两根通信线:SCL(Serial Clock)、SDA(Serial Data) 同步,半双工

 2、带数据应答

 3、支持总线挂载多设备(一主多从、多主多从)一般使用一主多从。一主多从的模式就是,主  机产生一个起始条件,之后以广播的形式发送一个设备地址到IIC总线上进行寻址,地址相同的就被选中之后就可以进行通信了。

二、IIC特征

1、SDA SCL 都是双向线路 都通过一个电流源或上拉电阻连接到正的电源电压, 当总线空闲时
这两条线路都是高电平。连接到总线的器件输出级必须是漏极开路或集电极开路才能执行线与的功
能。IIC总线上数据的传输速率在标准模式下可达 100kbit/s 在快速模式下可达 400kbit/s 在高速模
式下 可达 3.4Mbit/s 连接到总线的接口数量只由总线电容是 400pF 的限制决定.
PS: IIC不能使用推挽输出,原因是推挽输出驱动能力强,电平的跳变很快,而IIC又只有SDA一条线在数据传输。如果电平驱动能力太强,IIC进行输入输出切换过程会产生冲突,就会引起短路。

2、位传输、数据的有效性

每传输一个数据位就产生 一个时钟脉冲。SDA 线上的数据必须在时钟的高电平周期保持稳定数据

线的高或低电平状态只有SCL 线的时钟信号是低电平时才能改变(时钟线是低电平时才可以切

换数据线的高低电平,时钟线在高电平的时候数据线才会发送数据此时数据上的电平在时钟线为高

电平的时候要保持稳定)这样的数据才会有效。

3、起始和停止条件

*在IIC 总线中,唯一出现的是被定义为起始 S 和停止 P 条件

*其中一种情况是在 SCL 线是高电平时 SDA 线从高电平向低电平切换这个情况表示起始条件

*当 SCL 是高电平时 SDA 线由低电平向高电平切换表示停止条件

*起始和停止条件一般由主机产生 ,总线在起始条件后被认为处于忙的状态 在停止条件的某段时间后总线被认为再次处于空闲状态

*如果产生重复起始 Sr 条件而不产生停止条件 ,总线会一直处于忙的状态 ,此时的起始条件 S 和重复起始 Sr 条件在功能上是一样的 (见图 10),因此在本文档的剩余部分,符号 S 将作为一个通用 的术语既表示起始条件又表示重复起始条件,除非有特别声明的 Sr。

*如果连接到总线的器件合并了必要的接口硬件,那么用它们检测起始和停止条件十分简便,但是 没有这种接口的微控制器在每个时钟周期至少要采样 SDA 线两次来判别有没有发生电平切换

三、数据传输

1、字节格式
发送到 SDA 线上的每个字节必须为 8 位 ,每次传输可以发送的字节数量不受限制, 每个字节后必须跟一个响应位,首先传输的是数据的最高位 MSB(见图 6)。如果 从机要完成一些其他功能后(例如一个内部中断服务程序)才能接收或发送下一个完整的数据字节 ,可以使时钟线 SCL 保持低电平迫使主机进入等待状态。 当从机准备好接收下一个数据字节并 释放时钟线 SCL 后 ,数据传输继续。
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL(高),从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,主机 需要释放SDA(高)

2、响应

数据传输必须带响应 。相关的响应时钟脉冲由主机产生,在响应的时钟脉冲期间, 发送器释放 SDA 线 (高)
在响应的时钟脉冲期间 , 接收器必须将 SDA 线拉低, 使它在这个时钟脉冲的高电平期间保持稳定的低电平
通常被寻址的接收器在接收到的 每个字节 后 ,除了用 CBUS 地址开头的报文, 必须产生一个响应
*当从机不能响应从机地址时 ,(例如它正在执行一些实时函数不能接收或发送)从机必须使 数据线保持高电平主机然后产生一个停止条件终止传输或者产生重复起始条件开始新的传输
*如果从机接收器响应了从机地址但是在传输了一段时间后不能接收更多数据字节,主机必须 再一次终止传输。 这个情况用从机在第一个字节后没有产生响应来表示 。从机使数据线保持高电平 ,主机产生一 个停止或重复起始条件。
*如果传输中有主机接收器 ,它必须通过在 从机不产生时钟的最后一个字节不产生一个响应 ,向从机发送器通知数据结束 。从机 发送器必须释放数据线 ,允许主机产生一个停止或重复起始条件
*发送应答:主机在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答(从机释放SDA)
*接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据(此时SDA的控制权是从机),判断从机是否应答,数据0表示应答,数据1表示非应答( 主机在接收之前,需要释放SDA,目的是让SDA控制回到从机,从机要发送应答数据
PS:主机释放SDA控制权和从机获得SDA控制权的时间几乎在同一时刻,主机释放SDA,让SDA自动的回到高电平,但是如果从机应答了,从机会抓住SDA不放(这样就产生应答了),反之,从机发送数据需要主机应答也是一样

 

四、仲裁和时钟同步

1、同步

所有主机在 SCL 线上产生它们自己的时钟来传输 I 2 C 总线上的报文 ,数据只在时钟的高电平周期有效 , 因此需要一个确定的时钟进行逐位仲裁。

PS:SDA一般都是主机来控制,只有从机应答或者主机读取从机数据的时候主机才会把SDA的控制权交给从机。SCL是由主机来控制的,当有多个主机的时候,会进行仲裁,SCL线低电平周期最长的会获得仲裁权,这时就会成为主机,其它主机有从机功能的和从机设备就会成为从机。

2、仲裁

*主机只能在总线空闲的时侯启动传输 ,两个或多个主机可能在起始条件的最小持续时间 (tHD;STA) 内 产生一个起始条件 ,结果在总线上产生一个规定的起始条件。

*当 SCL 线是高电平时 ,仲裁在 SDA 线发生 ,这样在其他主机发送低电平时 ,发送高电平的主机将断开它的数据输出级 ,因为总线上的电平与它自己的电平不相同。

*如果主机也结合了从机功能 ,而且在寻址阶段丢失仲裁 ,它很可能就是赢得仲裁的主机在寻址的器件 ,因此丢失仲裁的主机必须立即切换到它的从机模式。

 I 2 C 总线的地址和数据信息由赢得仲裁的主机决定 ,在仲裁过程中不会丢失信息。

五、广播呼叫地址

广播呼叫地址是用来寻址连接到 I 2 C 总线上的每个器件 ,但是 如果器件在广播呼叫结构中不需要任何数据 ,它可以通过不发出响应来忽略这个地址 ,如果器件要求从广播呼叫地址得到数据,
它会响应这个地址并作为从机 -接收器运转 ,第二个和接下来的字节会被能处理这些数据的每个从机接收器响应

六、读写时序

从机地址有7位和10位,一般都是使用七位,下面的图也是七位。

写时序:指定地址写,对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data),ps:下面图中设备地址中高7位才是设备地址,最低位表示主机要读数据还是写数据,0(写),1(读)

指定地址写:对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data),里面的地址会自动加一

读时序:当前地址读,对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

读时序:指定地址读 对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data),地址会自动加一 ps:指定地址读主机先发送设备地址进行寻址,从机应答之后,主机继续发送指定写的地址,从机应答后,主机会继续产生一个起始条件,之后先从机发送设备地址进行寻址,从机应答之后,主机再进行读取数据(SDA的控制权在从机),主机读取到数据之后再进行应答(SDA的控制权回到主机),最后主机产生一个终止条件。这样就是一个完整的时序了。

PS:任何不想要继续接收数据设备,可以在接收最后一个数据时,直接不应答,这样就停止接收数据了。

七、代码

硬件IIC:

#include "oled.h"
#include "codetab.h"
void I2cOledConfig(void)
{
	GPIO_InitTypeDef oledGpioInit;
	I2C_InitTypeDef oledI2cInit;
	
	//初始化时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
	
	//初始化PB6--SCL PB7--SDA
	oledGpioInit.GPIO_Mode  = GPIO_Mode_AF_OD;
	oledGpioInit.GPIO_Pin   = GPIO_Pin_6 | GPIO_Pin_7;
	oledGpioInit.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&oledGpioInit);
	
	//I2c初始化
	I2C_DeInit(I2C1);				//将I2Cx外设寄存器重设为默认值
	oledI2cInit.I2C_Ack					= I2C_Ack_Enable;//从机的应答使能
	oledI2cInit.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//地址有效位
	oledI2cInit.I2C_ClockSpeed			= 400000;//400k 速度要低于400k
	oledI2cInit.I2C_DutyCycle			= I2C_DutyCycle_2;//时钟占空比,low:high可以选2:0或者16:9
	oledI2cInit.I2C_Mode				= I2C_Mode_I2C;//模式
	oledI2cInit.I2C_OwnAddress1			= 0x30;//主机地址(随意)
	I2C_Init(I2C1, &oledI2cInit);
	I2C_Cmd(I2C1, ENABLE);				//使能I2c
	
}
//i2C写一个字节
void I2cWriteBity(uint8_t addr,uint8_t data)
{

	while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));//检查I2c是否繁忙
	
	I2C_GenerateSTART(I2C1, ENABLE);//开始信号
	while(!I2C_CheckEvent(I2C1,  I2C_EVENT_MASTER_MODE_SELECT));//ev5(检查开始信号有没有发送成功),主模式
	
	I2C_Send7bitAddress(I2C1, OLED_ADDR, I2C_Direction_Transmitter);//发送器件地址寻找设备
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//器件等待正确的地址
	
	I2C_SendData(I2C1, addr);//器件向主机发送寄存器地址
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));
	
	I2C_SendData(I2C1, data);//器件向主机发送数据 
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));
	
	I2C_GenerateSTOP(I2C1, ENABLE);//关闭I2c总线
	
	
}

//写命令
void WriteCmd(unsigned char I2cCmd)
{
	I2cWriteBity(0x00,I2cCmd);//0x00是oled用来写命令的

}

//写数据
void WriteData(unsigned char I2cData)
{
	I2cWriteBity(0x40,I2cData);//0x40是oled用来写数据的
}
//初始化oled,厂家提供的驱动 
void OLED_Init(void)
{
	ms_delay(100);
	WriteCmd(0xAE); //display off
	WriteCmd(0x20);	//Set Memory Addressing Mode	
	WriteCmd(0x10);	//00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
	WriteCmd(0xb0);	//Set Page Start Address for Page Addressing Mode,0-7
	WriteCmd(0xc8);	//Set COM Output Scan Direction
	WriteCmd(0x00); //---set low column address
	WriteCmd(0x10); //---set high column address
	WriteCmd(0x40); //--set start line address
	WriteCmd(0x81); //--set contrast control register
	WriteCmd(0xff); //áá?èμ÷?ú 0x00~0xff
	WriteCmd(0xa1); //--set segment re-map 0 to 127
	WriteCmd(0xa6); //--set normal display
	WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
	WriteCmd(0x3F); //
	WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
	WriteCmd(0xd3); //-set display offset
	WriteCmd(0x00); //-not offset
	WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
	WriteCmd(0xf0); //--set divide ratio
	WriteCmd(0xd9); //--set pre-charge period
	WriteCmd(0x22); //
	WriteCmd(0xda); //--set com pins hardware configuration
	WriteCmd(0x12);
	WriteCmd(0xdb); //--set vcomh
	WriteCmd(0x20); //0x20,0.77xVcc
	WriteCmd(0x8d); //--set DC-DC enable
	WriteCmd(0x14); //
	WriteCmd(0xaf); //--turn on oled panel
}
//设置oled起点坐标
void OLED_SetPos(unsigned char x,unsigned char y)
{
	WriteCmd(0xb0+y);//页地址
	WriteCmd((x&0xf0)>>4|0x10);//列高四位
	WriteCmd((x&0x0f)|0x01);//列低四位
}

//全屏填充
void OLED_Fill(unsigned char FillData)
{
	unsigned char m,n;
	for(m=0;m<8;m++)//八页
	{
		WriteCmd(0xb0+m);//第几页0~7
		WriteCmd(0x00);//每一页第一列低四位的起始地址
		WriteCmd(0x10);//每一页第一列高四位的起始地址
		for(n = 0;n < 128;n++)//128列
		{
			WriteData(FillData);
		}
	}
}

//清屏
void OLED_Clear(void)
{
	OLED_Fill(0x00);//00为全灭
}

//OLED 打开
void OLED_OPen(void)
{
	WriteCmd(0x8d);//设置电荷泵
	WriteCmd(0x14);//开启电荷泵
	WriteCmd(0xaf);//OLED唤醒
}

//OLED 关闭
void OLED_Close(void)
{
	WriteCmd(0x8d);//设置电荷泵
	WriteCmd(0x10);//关闭电荷泵
	WriteCmd(0xae);//OLED关闭
}

//显示字符串,ascall码的格式
void OLED_DisplayStr(unsigned char x,unsigned char y,unsigned char ch[],unsigned char mode)
{
	unsigned char i = 0,c = 0,j = 0;
	switch(mode)
	{
		case 1:
		{
			while(ch[j] != '\0')
			{
				c = ch[j] - 32;
				if(x>126)//判断一页是否写满
				{
					x = 0;
					y++;
				}
				OLED_SetPos(x,y);
				//一个字符用一页写8(行)*6(列)
				for(i = 0;i < 6 ;i++)
					WriteData(F6x8[c][i]);//为什么要写C,C代表的是数组里面的第几行
				x+=6;//写一个字符需要6列的像素
				j++;

			}
		}break;
		
		case 2:
		{
			while(ch[j] != '\0')
			{
				c = ch[j] - 32;
				if(x>120)//判断一页是否写满
				{
					x = 0;
					y++;
				}
				OLED_SetPos(x,y);
				//一个字符用两页写8(列)*16(行)
				for(i = 0;i < 8 ;i++)				//乘c是表示该字符在第几位
					WriteData(F8X16[c*16+i]);//16个十六进制数代表一个字符,前八位字符的上半部分
				OLED_SetPos(x,y+1);
				for(i = 0;i < 8 ;i++)
					WriteData(F8X16[c*16+i+8]);//后八位字符的上半部分
				x+=8;//列的像素点移动8列
				j++;//下一个字符
			}
		}break;
			
	}
}
//一个汉字用32个16进制数表示16*16,N表示的是第几个汉字
void displayCN(unsigned char x,unsigned char y,unsigned char N)
{
	unsigned char wm = 0;
	unsigned int addr = 32*N;
	if(x>125 )//判断一页是否写满
	{
		x = 0;
		y+=2;
	}
	OLED_SetPos(x,y);
	for(wm=0;wm<16;wm++)//字符的上半部分
	{
		WriteData(F16x16[addr+wm]);
	}
	OLED_SetPos(x,y+1);
	for(wm=0;wm<16;wm++)//写一个字符的下半部分
	{
		WriteData(F16x16[addr+wm+16]);
	}
}
//x0第几列,y0第几页,x1要填充的列数,y1要填充的页数,bmp填充的图片的16进制数组
void OLED_BMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{

	unsigned char x,y;
	unsigned int j = 0;
	//确定页数的大小
//	if(y1%8==0)
//		y = y1/8;
//	else 
//		y = y1/8+1;
	//每一页都进行画点
	for(y = y0;y<y1;y++)//每一页
	{
		OLED_SetPos(x0,y);
		for(x = x0;x<x1;x++)//每一列进行填充
		{
			WriteData(BMP[j++]);
		}
	}
	
}

软件IIC:

#include "softoled.h"
#include "stm32f10x.h"
#include "SystemTick.h"
#include "softcodetab.h"
//进行gpio的初始化,pb0--SCL PB1--SDA
 static void SoftOLED_GpioInit(void)
{
	GPIO_InitTypeDef softOLED_Init;
	
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
	
	softOLED_Init.GPIO_Mode  = GPIO_Mode_Out_OD;
	softOLED_Init.GPIO_Pin	 = GPIO_Pin_0 | GPIO_Pin_1; 
	softOLED_Init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &softOLED_Init);
	
	OLED_SCL_SET();  	
	OLED_SDA_SET();
	
}

//软件iic的起始条件
static void softOLed_Start(void)
{ 	
	OLED_SDA_SET();
	OLED_SCL_SET(); 
	us_delay(1);
	
	OLED_SDA_RESET();
	us_delay(1);	
	OLED_SCL_RESET();
	us_delay(1);
}

//软件iic的停止条件
static void softOLed_Stop(void)
{	
	OLED_SDA_RESET();
	us_delay(1);
	OLED_SCL_SET();
	us_delay(1);
	OLED_SDA_SET();
	us_delay(1);
}
//软件iic的响应信号
static int softOLED_Ack(void)
{
	unsigned int ack;
	
	
	OLED_SCL_RESET();//主机时钟线拉低(时钟线拉低之后数据线才可以改变状态)
	us_delay(1);
	OLED_SDA_SET();//主机数据线拉高(数据线拉高之前准备好)
	us_delay(1);
	OLED_SCL_SET();//主机时钟线拉高
	us_delay(1);
	
	if(OLED_READ_SDA)
	{
		ack = OLED_NO_ACK_SDA;
		
	}
	else 
	{
		ack = OLED_ACK_SDA;

	}
	OLED_SCL_RESET();//把主机时钟线置回去(时钟线低电平的时候SDA才可以改变状态)
	us_delay(1);
	
	return ack;
	
}

//软件IIC写一个字节
static void softOLED_Write_Byte(unsigned char byte)
{
	unsigned char i;
	for(i = 0;i < 8;i++)
	{
		OLED_SCL_RESET();//把主机时钟线拉低
		us_delay(1);
		if(byte & 0x80) //1000 0000读取最高位,高位先行
			OLED_SDA_SET();//最高位为1,所以将其设置成高电平
		else
			OLED_SDA_RESET();//最高位为0,所以将其设置成低电平
		byte <<= 1;//数据左移1位,准备读取第7位,依次读下来
		us_delay(1);
		
		OLED_SCL_SET();//将时钟置高,产生上升沿,将得到1位数据发送出去
		us_delay(1);
	}
	
	OLED_SCL_RESET();//将时钟线拉低 给下一个字节发送进行做准备
	us_delay(1);
	
	while(softOLED_Ack());//等待从机响应
}

//软件IIC写命令
static void softOLED_WriteCommand(unsigned char Command)
{
	softOLed_Start();//起始条件
	softOLED_Write_Byte(0x78);//写入oled的设备地址 去寻找对应的oled设备
	softOLED_Write_Byte(0x00);//0x00是oled的写命令的地址0x40是写数据的地址
	softOLED_Write_Byte(Command);//向0x00这个可以写命令的地址写入对应的指令
	softOLed_Stop();//发送停止信号
	//GPIO_SetBits(GPIOA,  GPIO_Pin_1);
}

//软件IIC写数据
static void softOLED_WriteData(unsigned char Data)
{
	softOLed_Start();				//起始条件
	softOLED_Write_Byte(0x78);		//写入oled的设备地址 去寻找对应的oled设备
	softOLED_Write_Byte(0x40);		//0x00是oled的写命令的地址0x40是写数据的地址
	softOLED_Write_Byte(Data);	    //向0x40这个可以写数据的地址写入对应的数据
	softOLed_Stop();				//发送停止信号
}

//对OLED写入一个字节
void SoftOLED_Write_Byte(unsigned char dat,unsigned char cmd)
{
	if(cmd)
	{
		softOLED_WriteData(dat);//写数据
	}
	else 
	{
		softOLED_WriteCommand(dat);//写命令
	}
}
//设置坐标x是列,y是页
void SoftOLED_SetPos(unsigned char x,unsigned char y)
{
	SoftOLED_Write_Byte(0xb0+y,OLED_CMD);//写入页地址
	SoftOLED_Write_Byte(x&0x0f,OLED_CMD);
	SoftOLED_Write_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
}
//打开OLED
void SoftOLED_Open(void)
{
	SoftOLED_Write_Byte(0x8D,OLED_CMD);//设置电荷泵
	SoftOLED_Write_Byte(0x14,OLED_CMD);//开启电荷泵
	SoftOLED_Write_Byte(0xAF,OLED_CMD);//设置显示开
}
//关闭OLED
void SoftOLED_Close(void)
{
	SoftOLED_Write_Byte(0x8D,OLED_CMD);//设置电荷泵
	SoftOLED_Write_Byte(0x10,OLED_CMD);//关闭电荷泵
	SoftOLED_Write_Byte(0xAE,OLED_CMD);//设置显示关
}

//填充
void SoftOLED_Fill(unsigned char dat)
{
	unsigned char i,j;
	for(i = 0;i < 8;i++)
	{
		SoftOLED_Write_Byte(0xb0+i,OLED_CMD);//每一页
		SoftOLED_Write_Byte(0x00,OLED_CMD);//每一页第一列低四位的起始地址
		SoftOLED_Write_Byte(0x10,OLED_CMD);//每一页第一列高四位的起始地址
		for(j = 0;j < 128;j++)
		{
			SoftOLED_Write_Byte(dat,OLED_DATA);
		}
	}
}
//清屏
void SoftOLED_Clear(void)
{
	SoftOLED_Fill(0x00);
}
	
//写一个字符
void SoftOLED_DisplayChr(unsigned char x,unsigned char y,unsigned char ch,unsigned char size)
{
	unsigned char c = 0,i = 0;
	c = ch - 32;//获取字符的偏移量
//	if(x > 120)//超过最大的列数
//	{
//		x = 0;			//回到第一列
//		y += 2;			//换两页进行写字符
//	}
	

	if(size == 16)
	{
		if(x > 128)//超过最大的列数
		{
			x = 0;			//回到第一列
			y += 2;			//换两页进行写字符
		}
		SoftOLED_SetPos(x,y);//设置是哪一页哪一列
		for(i = 0;i < 8;i++)
		{
			SoftOLED_Write_Byte(softF8X16[c*16+i],OLED_DATA);//字符的上半部分
		}
		SoftOLED_SetPos(x,y+1);
		for(i = 0;i < 8;i++)
			SoftOLED_Write_Byte(softF8X16[c*16+i+8],OLED_DATA);//字符的下半部分
	}
	
	else if(size == 8)
	{
		if(x > 122)//超过最大的列数
		{
			x = 0;			//回到第一列
			y += 1;			//换两页进行写字符
		}
		SoftOLED_SetPos(x,y);//设置是哪一页哪一列
		for(i = 0;i < 6;i++)
			SoftOLED_Write_Byte(softF6x8[c][i],OLED_DATA);
	}
}
//写一个字符串
void softOLED_DisplayStr(unsigned char x,unsigned char y,unsigned char ch[],unsigned char mode)
{
	unsigned char j = 0;
	while(ch[j] != '\0')
	{
		if(mode == 1)
		{
			SoftOLED_DisplayChr(x,y,ch[j],8);
			x += 6;
			j++;
		}
		else if(mode == 2)
		{
			SoftOLED_DisplayChr(x,y,ch[j],16);
			x += 8;
			j++;	
		}
	}
	
}

//写一个汉字
void softOLED_DisplayCN(unsigned char x,unsigned char y,unsigned char N)
{
	unsigned char j = 0;
	unsigned int addr = N*32;
	if(x>110)
	{
		x = 0;
		y += 2;
	}
	SoftOLED_SetPos(x,y);//设置是哪一页哪一列
	for(j = 0;j<16;j++)
		SoftOLED_Write_Byte(softF16x16[addr+j],OLED_DATA);
	SoftOLED_SetPos(x,y+1);
	for(j = 0;j<16;j++)
		SoftOLED_Write_Byte(softF16x16[addr+j+16],OLED_DATA);
}

x0第几列,y0第几页,x1要填充的列数,y1要填充的页数,bmp填充的图片的16进制数组
void softOLED_DisplayPicture(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{

	unsigned char x,y;
	unsigned int j = 0;
	//确定页数的大小
//	if(y1%8==0)
//		y = y1/8;
//	else 
//		y = y1/8+1;
	//每一页都进行画点
	for(y = y0;y<y1;y++)//每一页
	{
		SoftOLED_SetPos(x0,y);
		for(x = x0;x<x1;x++)//每一列进行填充
		{
			SoftOLED_Write_Byte(BMP[j++],OLED_DATA);
		}
	}
}
//软件OLED初始化
void SoftOLED_Init(void)
{
//	ms_delay(100);
//	SoftOLED_Write_Byte(0xAE,OLED_CMD); //display off
//	SoftOLED_Write_Byte(0x20,OLED_CMD);	//Set Memory Addressing Mode	
//	SoftOLED_Write_Byte(0x10,OLED_CMD);	//00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
//	SoftOLED_Write_Byte(0xb0,OLED_CMD);	//Set Page Start Address for Page Addressing Mode,0-7
//	SoftOLED_Write_Byte(0xc8,OLED_CMD);	//Set COM Output Scan Direction
//	SoftOLED_Write_Byte(0x00,OLED_CMD); //---set low column address
//	SoftOLED_Write_Byte(0x10,OLED_CMD); //---set high column address
//	SoftOLED_Write_Byte(0x40,OLED_CMD); //--set start line address
//	SoftOLED_Write_Byte(0x81,OLED_CMD); //--set contrast control register
//	SoftOLED_Write_Byte(0xff,OLED_CMD); //áá?èμ÷?ú 0x00~0xff
//	SoftOLED_Write_Byte(0xa1,OLED_CMD); //--set segment re-map 0 to 127
//	SoftOLED_Write_Byte(0xa6,OLED_CMD); //--set normal display
//	SoftOLED_Write_Byte(0xa8,OLED_CMD); //--set multiplex ratio(1 to 64)
//	SoftOLED_Write_Byte(0x3F,OLED_CMD); //
//	SoftOLED_Write_Byte(0xa4,OLED_CMD); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
//	SoftOLED_Write_Byte(0xd3,OLED_CMD); //-set display offset
//	SoftOLED_Write_Byte(0x00,OLED_CMD); //-not offset
//	SoftOLED_Write_Byte(0xd5,OLED_CMD); //--set display clock divide ratio/oscillator frequency
//	SoftOLED_Write_Byte(0xf0,OLED_CMD); //--set divide ratio
//	SoftOLED_Write_Byte(0xd9,OLED_CMD); //--set pre-charge period
//	SoftOLED_Write_Byte(0x22,OLED_CMD); //
//	SoftOLED_Write_Byte(0xda,OLED_CMD); //--set com pins hardware configuration
//	SoftOLED_Write_Byte(0x12,OLED_CMD);
//	SoftOLED_Write_Byte(0xdb,OLED_CMD); //--set vcomh
//	SoftOLED_Write_Byte(0x20,OLED_CMD); //0x20,0.77xVcc
//	SoftOLED_Write_Byte(0x8d,OLED_CMD); //--set DC-DC enable
//	SoftOLED_Write_Byte(0x14,OLED_CMD); //
//	SoftOLED_Write_Byte(0xaf,OLED_CMD); //--turn on oled panel
	
	
	SoftOLED_GpioInit();	//GPIO?ú3?ê??ˉ
 
	ms_delay(200);	//?ó3ù£?óéóúμ¥???úé?μ?3?ê??ˉ±èOLED?ì£??ùò?±?D??óé??ó3ù£?μè′yOLEDé??′??íê3é

	SoftOLED_Write_Byte(0xAE,OLED_CMD);	//1?±???ê?

	SoftOLED_Write_Byte(0x00,OLED_CMD);	//éè??μíáDμ??·
	SoftOLED_Write_Byte(0x10,OLED_CMD);	//éè????áDμ??·
	SoftOLED_Write_Byte(0x40,OLED_CMD);	//éè???eê?DDμ??·
	SoftOLED_Write_Byte(0xB0,OLED_CMD);	//éè??ò3μ??·

	SoftOLED_Write_Byte(0x81,OLED_CMD); 	// ??±è?èéè??£??ééè??áá?è
	SoftOLED_Write_Byte(0xFF,OLED_CMD);	//  265  

	SoftOLED_Write_Byte(0xA1,OLED_CMD);	//éè????£¨SEG£?μ??eê?ó3é?μ??·£?columnμ?127μ??·ê?SEG0μ?μ??·
	SoftOLED_Write_Byte(0xA6,OLED_CMD);	//?y3£??ê?£?0xa7????ê?

	SoftOLED_Write_Byte(0xA8,OLED_CMD);	//éè???y?ˉ?·êy£¨16~64£?
	SoftOLED_Write_Byte(0x3F,OLED_CMD);	//64duty
	
	SoftOLED_Write_Byte(0xC8,OLED_CMD);	//??ó3é??£ê?£?COM[N-1]~COM0é¨?è

	SoftOLED_Write_Byte(0xD3,OLED_CMD);	//éè????ê???ò?
	SoftOLED_Write_Byte(0x00,OLED_CMD);	//?T??ò?
	
	SoftOLED_Write_Byte(0xD5,OLED_CMD);	//éè???eμ′?÷·??μ
	SoftOLED_Write_Byte(0x80,OLED_CMD);	//ê1ó???è??μ
	
	SoftOLED_Write_Byte(0xD9,OLED_CMD);	//éè?? Pre-Charge Period
	SoftOLED_Write_Byte(0xF1,OLED_CMD);	//ê1ó?1ù·?í????μ
	
	SoftOLED_Write_Byte(0xDA,OLED_CMD);	//éè?? com pin configuartion
	SoftOLED_Write_Byte(0x12,OLED_CMD);	//ê1ó???è??μ
	
	SoftOLED_Write_Byte(0xDB,OLED_CMD);	//éè?? Vcomh£??éμ÷?úáá?裨??è?£?
	SoftOLED_Write_Byte(0x40,OLED_CMD);	ê1ó?1ù·?í????μ
	
	SoftOLED_Write_Byte(0x8D,OLED_CMD);	//éè??OLEDμ?oé±?
	SoftOLED_Write_Byte(0x14,OLED_CMD);	//?a??ê?
	
	SoftOLED_Write_Byte(0xAF,OLED_CMD);	//?a??OLED??°???ê?
	SoftOLED_GpioInit();
	SoftOLED_Clear();        //???á
	SoftOLED_SetPos(0,0); 	 //éè??êy?YD′è?μ??eê?DD?¢áD
}



八、引脚图

所有I2C设备的SCL连在一起,SDA连在一起

设备的SCL和SDA均要配置成开漏输出模式

SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/645787.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

clone方法总结Java

Java中Object类当中有许多方法&#xff0c;如图所示&#xff1a; clone方法就是其中一种&#xff0c;分为浅拷贝&#xff0c;深拷贝举一个例子&#xff1a; 浅拷贝&#xff1a; 在Person类当中右键鼠标然后&#xff0c;选中Generate&#xff1a; 然后重写clone方法 protecte…

Linux-应用编程学习笔记(三、文件属性和目录)

一、文件类型 1、普通文件&#xff08;ls -l 文件&#xff0c;权限前边第一个"-"代表普通文件&#xff1b;stat 文件&#xff09; 文本文件&#xff1a;ASCII字符 二进制文件&#xff1a;数字0/1 2、目录文件&#xff08;‘’d&#xff09;&#xff1a;文件夹 3…

每日两题 / 79. 单词搜索 39. 组合总和(LeetCode热题100)

79. 单词搜索 - 力扣&#xff08;LeetCode&#xff09; 遍历board&#xff0c;遇到字符等于word的第一个字符时&#xff0c;进行dfs回溯 设置访问数组&#xff0c;标记已经走过的坐标 每次dfs时&#xff0c;往四个方向走&#xff0c;若当前字符不匹配则回溯&#xff0c;记得消…

【深度学习】paddlets,时序数据预测

文章目录 一、环境二、题目1三、题目2四、题目3五、函数参数 资料&#xff1a; https://paddlets.readthedocs.io/zh-cn/latest/source/api/paddlets.models.base.html#paddlets.models.base.BaseModel.recursive_predict https://aistudio.baidu.com/projectdetail/5866171?…

记一次MySQL执行修改语句超时问题

异常问题 原因分析 这个问题发生在开发环境&#xff0c;怀疑是提交事务时终止项目运行&#xff0c;没有提交该事务&#xff0c;造成死锁 调试该事务时时间太长&#xff0c;为什么说有这个原因呢&#xff0c;因为通过查找日志显示 The client was disconnected by the server …

$subcribe的使用

$subcribe的使用 只要是store都有$subscribe函数&#xff0c;是订阅的意思&#xff0c;可以监测到store中数据的变化 使用$subscribe函数可以实现刷新不丢失&#xff0c;将数据保存到浏览器的本地存储中&#xff0c;每次进入页面都使用localStorage的数据填充页面

【credit_based流控机制】

credit_based流控机制 1 credit_based way1.1 Principle1.3 DFD1.4 Module1.4.1 Interface1.4.2 Code Block 在网络芯片处理大流量报文中&#xff0c;一般主要是两种机制&#xff1a;1.valid–ready反压(backpressure)机制&#xff1b;2.credit信用机制&#xff1b; credit机制…

电商API接口(api商品数据)【电商商品实时数据采集API接口】

众多品牌选择电商API实时数据采集接口进行采购&#xff0c;主要是出于以下几个重要原因&#xff1a; 第一&#xff0c;高效便捷。比价工具通过自动化的方式获取价格信息&#xff0c;避免了繁琐的人工操作&#xff0c;大大节省了时间和精力。 第二&#xff0c;精准比较。API比价…

常见的CSS布局

1 左侧固定宽度&#xff0c;右侧自适应宽度的两列布局实现 HTML: <div className"outer"><div className"left">固定宽度</div><div className"right">自适应宽度</div></div> 方法1&#xff1a;左侧div设…

美发店服务预约会员小程序的作用是什么

美发店不同于美容美甲&#xff0c;男女都是必需且年龄层几乎不限&#xff0c;商家在市场拓展时只要方法得当相对比较容易&#xff0c;当今客户适应于线上信息获取、咨询及实际内容开展&#xff0c;商家也需要赋能和提升自身服务效率&#xff0c;合理化管理。 运用【雨科】平台…

C语言基础(六)

C语言基础 指针与一维数组总结 * p、* (p)、&#xff08;\*p&#xff09;、* p、*(p)、*p区别和用法运算优先级p与p区别*p与 *&#xff08;p&#xff09;与&#xff08;*p&#xff09;*p与 *&#xff08;p&#xff09;与 *p 指针常量与一维数组的关系指针变量与一维数组的关系数…

002 仿muduo库实现高性能服务器组件_整体框架

​&#x1f308;个人主页&#xff1a;Fan_558 &#x1f525; 系列专栏&#xff1a;仿muduo &#x1f339;关注我&#x1f4aa;&#x1f3fb;带你学更多知识 文章目录 前言项目框架小结 前言 本文不会包含任何项目模块的代码&#xff0c;旨在向你介绍项目具体分为哪几个模块&am…

文档档案管理系统整体建设方案书(实际项目原件word2024)

1.系统概述 1.1.需求描述 1.2.需求分析 1.3.重难点分析 1.4.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 数据备份技术 3.系统功能设计 3.1.功能清单列表 3.2.基础数据管理 3.3.位置管理 3.4.文档使用 3.5.文档管理 软件全套资料包获取方式①&#xff1a;软件项…

揭秘 淘宝死店采集私信筛选,号称日赚500+

淘宝死店采集工具为电子商务创业者揭示了一个领域的新机遇&#xff0c;通过提供一系列深入分析和资源挖掘的功能&#xff0c;展现了从失败中寻找成功之道的独特方法论。以下是如何通过这种工具寻找电商平台中的隐含机会的几个关键方面&#xff1a; 分析失败的深层原因&#x…

【启程Golang之旅】深入解析函数的奥秘与技巧

欢迎来到Golang的世界&#xff01;在当今快节奏的软件开发领域&#xff0c;选择一种高效、简洁的编程语言至关重要。而在这方面&#xff0c;Golang&#xff08;又称Go&#xff09;无疑是一个备受瞩目的选择。在本文中&#xff0c;带领您探索Golang的世界&#xff0c;一步步地了…

html5+css3+js学习记录(1)-- html

1 vscode前端插件 1.1 Web标准 2 文档声明与字符编码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewpor…

【全开源】海报在线制作系统源码(ThinkPHP+FastAdmin+UniApp)

打造个性化创意海报的利器 引言 在数字化时代&#xff0c;海报作为一种重要的宣传媒介&#xff0c;其设计质量和效率直接影响着宣传效果。为了满足广大用户对于个性化、高效制作海报的需求&#xff0c;海报在线制作系统源码应运而生。本文将详细介绍海报在线制作系统源码的特…

【电子元件】TL431 电压基准

TL431(C23892)是一种常用的可调节精密电压基准和电压调节器。它广泛应用于电源管理、精密参考电压和稳压电路等领域。以下是TL431的一些关键特点和使用方法&#xff1a; 关键特点 可调输出电压&#xff1a;TL431的输出电压可以通过外部电阻网络在2.495V到36V范围内调整。精度高…

【知识图谱】探索攻略:基础、构建、高级应用与相关论文方向

【知识图谱】相关文章汇总 写在最前面一、什么是知识图谱&#xff1f;二、相关历史文章代码实现&#xff1a;简单的知识图谱可视化知识图谱前身&#xff1a;信息抽取知识图谱应用1&#xff1a;社交网络分析知识图谱应用2&#xff1a;威胁情报挖掘知识图谱应用3&#xff1a;Code…

当AWR1843发送完设置的固定帧后,如何使其再发送第一次的帧?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…