I2C通信协议编程实现

 代码实现at24c02主设备与从设备之间的I2C数据通信

(at24c02设备原理图资料在分享的stm32资料中)

代码的编辑思路是实现开始信号,结束信号,发送从设备地址和写信号,发送写片内地址,发送写的数据,把这些步骤都封装成代码函数,调用即可。

#include <stm32f4xx.h>
#include <iic.h>
#include <delay.h>
#include <stdio.h>



void ic_init(void)
{	
    //时钟初始化结构体
    GPIO_InitTypeDef GPIO_InitStruct;
	
	//1.开启GPIOB的时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
	
	//2.GPIO初始化 PB8 PB9引脚
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //输出模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//高速
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;//引脚为9和10
	GPIO_Init(GPIOB,&GPIO_InitStruct);
}	

//设置串行数据线SDA传输方向(双向通信),传入引脚的模式
void set_sda_io(GPIOMode_TypeDef IO)
{
	
    GPIO_InitTypeDef GPIO_InitStruct;
	
	//2.GPIO初始化 PB9
	GPIO_InitStruct.GPIO_Mode = IO;
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//高速
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;//引脚为9号引脚
	GPIO_Init(GPIOB,&GPIO_InitStruct);
}



//起始信号
void iic_start(void)
{
	//设置串行数据总线SDA输出模式
	set_sda_io(GPIO_Mode_OUT);
	
	//两条总线处于空闲状态,默认为高电平
	SCL = 1;      //时钟总线高电平
	SDA_OUT = 1;  //数据总线低电平
	delay_us(5);
	
	//串行数据总线SDA拉低
	SDA_OUT = 0;
	delay_us(5);
	
	//SDA拉低之后,再拉低时钟总线SCL,钳住总线
	SCL = 0;
}



//停止信号
void iic_stop(void)
{
	//串行数据总线SDA输出模式
	set_sda_io(GPIO_Mode_OUT);
	
	
    //串行数据总线和时钟总线为低电平
    SCL = 0;
	SDA_OUT = 0;
	delay_us(5);
	
	//串行时钟总线SCL拉高
	SCL = 1;
	delay_us(5);
	
	//拉高串行数据总线SDA,此时SCL和SDA均为高,总线空闲
	SDA_OUT = 1;
}



//等待从设备应答,返回0表示有效应答,返回1表示无效应答
u8 iic_wait_ack(void)
{
	u8 ack = 0;
	
	//串行数据总线SDA输入模式,接收应答
	set_sda_io(GPIO_Mode_IN);
	
	//拉高串行时钟总线SCL,允许从设备操作SDA
	SCL = 1;
	delay_us(5);
	
	//读取串行数据总线SDA电平
	if(SDA_IN){
		ack = 1;//无效应答
		iic_stop();//收到无效应答,立即停止通信
	}
	else{
		ack = 0;
	}
	
	SCL = 0;
	delay_us(5);
	
	return ack;
}


//产生应答信号,传参ack=0有效应答,ack=1无效应答
void iic_ack(u8 ack)
{
	//设置串行数据总线SDA输出模式
	set_sda_io(GPIO_Mode_OUT);
	
	//拉低时钟线,准备修改数据线电平
	SCL = 0;
	delay_us(5);
	
	//设置串行数据总线SDA电平
	if(ack){
		SDA_OUT = 1;
	}
	else{
		SDA_OUT = 0;//有效应答
	}
	
	delay_us(5);
	
	//数据稳定后拉高时钟线让从设备接收ack
	SCL = 1;
	delay_us(5);
	
	SCL = 0;
}

//发送1字节数据 --------- 先高后低
void iic_send_byte(u8 txd)
{
	u8 i;
	
	//设置数据串行总线SDA输出模式
	set_sda_io(GPIO_Mode_OUT);
	
	SCL = 0;
	delay_us(5);
	
	//从高到低一次发送每一位
    //判断txd是高电平还是低电平,只需要&(与运算)上 1<<(7-i)就可以判断
    //1<<(7-i) 实际上就是 1左移7位,不清楚的同学好好复习位运算
	for(i=0;i<8;i++){
		if(txd&1<<(7-i)){
			SDA_OUT = 1;
		}
		else{
			SDA_OUT = 0;
		}
		delay_us(5);
		
		//拉高时钟线总线,让从设备读走这一位
		SCL = 1;
		delay_us(5);
		//拉低时钟线,准备发送下一位
		SCL = 0;
	}
}

//接收1字节数据
u8 iic_recv_byte(void)
{
	u8 rxd = 0,i;
	
	//设置串行时钟总线SDA输入模式
	set_sda_io(GPIO_Mode_IN);
	
	SCL = 0;
	
	//从高到低依次接收每一位
	for(i=0;i<8;i++){
		
     //等待对方设置好电平
		delay_us(5);

	//高电平期间读取1位数据
		SCL = 1;
		if(SDA_IN)
			rxd |= 1<<(7-i);
		
		//准备接收下一位
		delay_us(5);
		SCL = 0;
	}
	
	return rxd;
}

//at24c02写1字节数据
void at24c02_write_byte(u8 addr,u8 data)
{
	u8 ack;
	
	//起始信号
	iic_start();
	
	//发送从设备地址+写信号  0x50<<1 | 0 = 0xa0
    //从设备地址(0x50)左移1位 或运算 0
	iic_send_byte(0xa0);

	//等待ACK
	ack = iic_wait_ack();
	if(ack){
		printf("ack failed 1\r\n");
		return ;
	}
	
	//发送写的片内地址
	iic_send_byte(addr);
	//等待ACK
	ack = iic_wait_ack();
	if(ack){
		printf("ack failed 2\r\n");
		return ;
	}
	
	//发送写的数据
	iic_send_byte(data);
	//等待ACK
	ack = iic_wait_ack();
	if(ack){
		printf("ack failed 3\r\n");
		return ;
	}
	
	//停止信号
	iic_stop();
}

//at24c02写1页数据
void at24c02_write_page(u8 addr,u8 *data,u8 len)
{
	u8 ack;
	
	//起始信号
	iic_start();
	
	//发送从设备地址+写信号  0x50<<1 | 0 = 0xa0
	iic_send_byte(0xa0);
	//等待ACK
	ack = iic_wait_ack();
	if(ack){
		printf("ack failed 1\r\n");
		return ;
	}
	
	//发送写的片内地址
	iic_send_byte(addr);
	//等待ACK
	ack = iic_wait_ack();
	if(ack){
		printf("ack failed 2\r\n");
		return ;
	}
	
	//发送写的数据
	while(len--){
		iic_send_byte(*data++);
		//等待ACK
		ack = iic_wait_ack();
		if(ack){
			printf("ack failed 3\r\n");
			return ;
		}
	}
	
	//停止信号
	iic_stop();
}

//at24c02读1字节
u8 at24c02_read_byte(u8 addr)
{
	u8 ack,data;
	
	//起始信号
	iic_start();
	
	//发送从设备地址+写信号  0x50<<1 | 0 = 0xa0
	iic_send_byte(0xa0);
	//等待ACK
	ack = iic_wait_ack();
	if(ack){
		printf("ack failed 1\r\n");
		return 0;
	}
	
	//发送读的片内地址
	iic_send_byte(addr);
	//等待ACK
	ack = iic_wait_ack();
	if(ack){
		printf("ack failed 2\r\n");
		return 0;
	}
	
	//起始信号
	iic_start();
	
	//发送从设备地址+读信号  0x50<<1 | 1 = 0xa1
	iic_send_byte(0xa1);
	//等待ACK
	ack = iic_wait_ack();
	if(ack){
		printf("ack failed 3\r\n");
		return 0;
	}
	
	//接收读的1字节数据
	data = iic_recv_byte();
	//无效应答
	iic_ack(1);
	
	//停止信号
	iic_stop();
	
	return data;
}

//at24c02连续读
void at24c02_seq_read(u8 addr,u8 *data,u8 len)
{
	u8 ack;
	
	//起始信号
	iic_start();
	
	//发送从设备地址+写信号  0x50<<1 | 0 = 0xa0
	iic_send_byte(0xa0);
	//等待ACK
	ack = iic_wait_ack();
	if(ack){
		printf("ack failed 1\r\n");
		return;
	}
	
	//发送读的片内地址
	iic_send_byte(addr);
	//等待ACK
	ack = iic_wait_ack();
	if(ack){
		printf("ack failed 2\r\n");
		return;
	}
	
	//起始信号
	iic_start();
	
	//发送从设备地址+读信号  0x50<<1 | 1 = 0xa1
	iic_send_byte(0xa1);
	//等待ACK
	ack = iic_wait_ack();
	if(ack){
		printf("ack failed 3\r\n");
		return;
	}
	
	//读取数据
	while(len--){
		//接收读的数据
		*data++ = iic_recv_byte();
		
		if(len>0){
			//有效应答
			iic_ack(0);
		}
		else{
			iic_ack(1);
		}
		
	}
	
	//停止信号
	iic_stop();
}

猜你喜欢

转载自blog.csdn.net/weixin_44651073/article/details/125793497