STM32模拟IIC通信

可以结合之前的一篇文章来看,都是一样的,不过本文是模拟IIC,之前的是硬件IIC。
传送门:STM32F429–I2C通信(读写EEPROM,串口返回测试数据)

一、定义

IIC 即Inter-Integrated Circuit(集成电路总线),这种总线类型是由菲利浦半导体公司在八十年代初设计出来的,主要是用来连接整体电路(ICS),IIC是一种多向控制总线,也就是说多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实施数据传输的控制源。这种方式简化了信号传输总线。

I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。

与SPI相同的是,都是多选一,即可以有多个从设备,但是一次通信的时候,只能选择其中的一个。且IIC的从设备选择上,有7位地址,谁控制了时钟线,谁就是从机,而不是像SPI那样通过片选信号线CS来选择。

二、读写AT24C02

(1)电路原理图如下:WP引脚接地,表明可读可写,如果接VCC只读不可写。
在这里插入图片描述
(2)引脚说明图如下:

在这里插入图片描述

A0-A2,接在了GND,代表是0,R/-W,W的上面是有一横的,代表0有效,读是1有效,所以读操作是0XA0,写操作是0XA1。

(3)地址说明:
在这里插入图片描述

(4)通信过程
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
(1)起始信号:由上图可知,读写的速率为100KHZ,那么1/100khz= 10us,在起始信号的时候,高地电平各占一半,即至少需要持续5us。SCL持续高电平,直到SDA线由高电平到低电平变化,SCL才变为低电平。可以写出代码:

GPIO初始化
void i2c_init(void)
{
    
    
	//使能GPIOB时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);			

	//PB8 PB9初始化设置 
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_8 | GPIO_Pin_9;		//8号和9号引脚
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;			    	//普通输出模式,
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;					//推挽输出,驱动LED需要电流驱动
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;		    	//100MHz
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;				    //上拉
	GPIO_Init(GPIOB, &GPIO_InitStructure);							//初始化GPIOB,把配置的数据写入寄存器						

	//i2c引脚初始化状态,默认为高电平
	SCL =1;
	SDA_W=1;


}
自定义更改输入输出模式
void i2c_sda_mode(uint32_t iomode)
{
    
    

	//PB9初始化设置 
	GPIO_InitStructure.GPIO_Pin   =  GPIO_Pin_9;		//9号引脚
	GPIO_InitStructure.GPIO_Mode  = iomode;				//输出模式/输入模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;	//推挽输出,驱动LED需要电流驱动
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//100MHz
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;		//上拉
	GPIO_Init(GPIOB, &GPIO_InitStructure);			//初始化GPIOB,把配置的数据写入寄存器		


}
起始信号

void i2c_start(void)
{
    
    
	//保证SDA引脚为输出模式
	i2c_sda_mode(GPIO_Mode_OUT);
	
	
	SCL =1;
	SDA_W=1;	
	
	delay_us(5);//100KHz通信速率,但是不能超过400KHz
	
	SDA_W=0;
	
	delay_us(5);
	
	SCL =0;		//保持占用I2C总线,允许数据改变
}

(2)结束信号
读上图,输出模式,SCL和SDA都为低电平,延时一段时间,SCL变为高电平,延时一段时间,SDA变为高电平,持续一段时间即可。

void i2c_stop(void)
{
    
    
	//保证SDA引脚为输出模式
	i2c_sda_mode(GPIO_Mode_OUT);


	SCL =0;
	SDA_W=0;
	
	delay_us(5);
	
	SCL =1;
	
	delay_us(5);
	
	SDA_W=1;
	
	delay_us(5);
}

(3)发送1字节数据
拉低时钟SCL,允许数据进行变化,延时一段时间;
然后判断传进来的data每一位的值,如果读取的data位7为1,那么SDA=1;如果位7为0,那么SDA=0;然后拉高SCL延时一段时间,这样就完成了一位的发送,以此循环八次。因为要允许下一次循环可以改变数据,所以还要把SCL变为低电平。

void i2c_send_byte(uint8_t txd)
{
    
    
	uint32_t i=0;
	//保证SDA引脚为输出模式
	i2c_sda_mode(GPIO_Mode_OUT);

	//保证SCL引脚开始的时候为低电平,允许数据的改变
	SCL =0;
	delay_us(5);
	
	//连续发送8个bit,采用最高有效位优先进行发送
	for(i=0; i<8; i++)
	{
    
    
		if(txd & (1<<(7-i)))
			SDA_W=1;
		else
			SDA_W=0;
		
		delay_us(5);
		
		//锁存数据,让从机进行识别
		SCL=1;
		delay_us(5);
		
		//允许改变数据,从机无视该数据
		SCL=0;
		delay_us(5);		
	
	}
}

(4)等待从机应答
有应答:低电平;无应答;高电平

uint8_t i2c_wait_ack(void)
{
    
    
	uint8_t ack=0;
	
	//保证SDA引脚为输入模式
	i2c_sda_mode(GPIO_Mode_IN);
	
	SCL=1;
	delay_us(5);
	
	//有应答为低电平,无应答为高电平
	if(SDA_R)		//无应答
	{
    
    
		ack=1;
		i2c_stop();
	}
	else			//有应答
		ack=0;

	
	
	SCL =0;			//保持占用I2C总线,允许数据改变
	delay_us(5);
	
	return ack;
}

(5)整合上面四部分的代码


void at24c02_write(uint8_t addr,uint8_t *pbuf,uint8_t len)
{
    
    
	uint8_t ack=0;
	
	//发送启动信号
	i2c_start();
	
	//发送寻址地址为0xA0,写访问操作
	i2c_send_byte(0xA0);
	
	//等待应答
	ack = i2c_wait_ack();
	
	if(ack)
	{
    
    
		printf("24c02 ack device address fail\r\n");
		
		return;
	
	}

	printf("24c02 is online\r\n");

	//发送数据存储地址
	i2c_send_byte(addr);
	
	//等待应答
	ack = i2c_wait_ack();
	
	if(ack)
	{
    
    
		printf("24c02 ack word address fail\r\n");
		
		return;
	
	}	
	
	printf("24c02 word address ok\r\n");	
	
	while(len--)
	{
    
    
		//发送数据
		i2c_send_byte(*pbuf++);
		
		//等待应答
		ack = i2c_wait_ack();
		
		if(ack)
		{
    
    
			printf("24c02 ack send data fail\r\n");
			return;
		
		}		
	
	
	}
	
	//发送停止信号,整个通信过程结束
	i2c_stop();
	
	printf("24c02 write ok\r\n");

}

主函数调用

i2c_init();

printf("24c02 write addr 0 data is 1\r\n");

memset(buf,2,sizeof buf);

//页编程最大是8个字节
at24c02_write(0,buf,8);

delay_ms(500);
memset(buf,0,sizeof buf);
printf("24c02 read addr 0 data is:\r\n");

(6)读数据,从机发送数据,主机发送应答。
在这里插入图片描述

1字节数据
uint8_t i2c_recv_byte(void)
{
    
    
	uint32_t i=0;
	uint8_t  rxd=0;
	
	//保证SDA引脚为输出模式
	i2c_sda_mode(GPIO_Mode_IN);

	//保证SCL引脚开始的时候为低电平,允许数据的改变
	SCL =0;
	delay_us(5);
	
	//连续接收8个bit,采用最高有效位优先进行接收
	for(i=0; i<8; i++)
	{
    
    

		
		//delay_us(5);
		
		//锁存数据
		SCL=1;
		delay_us(5);
		
		if(SDA_R)
			rxd|=1<<(7-i);		
		
		//允许改变数据
		SCL=0;
		delay_us(5);		
	
	}
	
	return rxd;

}
主机发送应答
void i2c_ack(uint8_t ack)
{
    
    

	//保证SDA引脚为输出模式
	i2c_sda_mode(GPIO_Mode_OUT);

	//保证SCL引脚开始的时候为低电平,允许数据的改变
	SCL =0;
	delay_us(5);
	

	if(ack)
		SDA_W=1;
	else
		SDA_W=0;
	
	delay_us(5);
	
	//锁存数据,让从机进行识别
	SCL=1;
	delay_us(5);
	
	//允许改变数据,从机无视该数据
	SCL=0;
	delay_us(5);		

}

读取数据函数
void at24c02_read(uint8_t addr,uint8_t *pbuf,uint8_t len)
{
    
    
	uint8_t ack=0;
	
	//发送启动信号
	i2c_start();
	
	//发送寻址地址为0xA0,写访问操作
	i2c_send_byte(0xA0);
	
	//等待应答
	ack = i2c_wait_ack();
	
	if(ack)
	{
    
    
		printf("24c02 ack device address fail\r\n");
		
		return;
	
	}

	printf("24c02 is online\r\n");

	//发送数据存储地址
	i2c_send_byte(addr);
	
	//等待应答
	ack = i2c_wait_ack();
	
	if(ack)
	{
    
    
		printf("24c02 ack word address 1 fail\r\n");
		
		return;
	
	}	
	
	printf("24c02 word address ok\r\n");	
	
	//重新发送启动信号
	i2c_start();
	
	//发送寻址地址为0xA1,读访问操作
	i2c_send_byte(0xA1);
	
	//等待应答
	ack = i2c_wait_ack();
	
	if(ack)
	{
    
    
		printf("24c02 ack device address 2 fail\r\n");
		
		return;
	
	}
	
	len=len-1;
	
	while(len--)
	{
    
    
		//接收数据
		*pbuf++=i2c_recv_byte();
		
		//主动发送应答给从机
		i2c_ack(0);
	}
	
	//接收数据
	*pbuf=i2c_recv_byte();
	
	//主动发送无应答给从机
	i2c_ack(1);	
	
	//发送停止信号,整个通信过程结束
	i2c_stop();
	
	printf("24c02 read ok\r\n");

}

调用

 at24c02_read(0,buf,8);

for(i=0;i<8;i++)
{
	printf("%02X ",buf[i]);


}
注意;在写和读直接需要加延时

猜你喜欢

转载自blog.csdn.net/ABCisCOOL/article/details/115248988