IIC通讯协议与EEPROM简介

IIC简介

​IIC总线(Inter-Integrated Circuit)即集成电路总线,是Philips公司设计出来的一种简单、双向、二线制、同步串行总线。I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。IIC总线是一个多向控制总线,多个器件(从机)可以同时挂载到一个主机控制的一条总线上。每个连接在总线上的设备都是通过唯一的地址和其他器件通信,主机和从机的角色可互换,

​主机和从机的概念。主机就是负责整个系统的任务协调与分配,从机一般是通过接收主机的指令从而完成某些特定的任务,主机和从机之间通过总线连接,进行数据通讯。我们平时使用的个人电脑也就是这个概念。
在这里插入图片描述

IIC特征

​ SDA 和SCL 都是双向线路,都通过一个电流源或上拉电阻连接到正的电源电压当总线空闲时,这两条线路都是高电平。连接到总线的器件输出级必须是漏极开路或集电极开路才能执行线与的功能。I²C总线上数据的传输速率在标准模式下可达100kbit/s, 在快速模式下可达400kbit/s, 在高速模式下可达3.4Mbit/s,,不是所有的单片机都支持高速模式。 连接到总线的接口数量只由总线电容是400pF 的限制决定。

数据的有效性

​ 在时钟的高电平周期内,SDA线上的数据必须保持稳定,数据线仅可以在时钟SCL为低电平时改变。
在这里插入图片描述

起始信号与终止信号

​ 起始条件:当SCL为高电平的时候,SDA线上由高到低的跳变被定义为起始条件,结束条件:当SCL为高电平的时候,SDA线上由低到高的跳变被定义为停止条件,要注意起始和终止信号都是由主机发出的,连接到I2C总线上的器件,若具有I2C总线的硬件接口,则很容易检测到起始和终止信号。总线在起始条件之后,视为忙状态,在停止条件之后被视为空闲状态。在这里插入图片描述

应答信号

​ 每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答。
在这里插入图片描述

IIC一次完整通讯过程

① SDA和SCL开始都为高,然后主机将SDA拉低,表示开始信号;

② 在接下来的8个时间周期里,主机控制SDA的高低,发送从机地址。其中第8位如果为0,表示接下来是写操作,即主机传输数据给从机;如果为1,表示接下来是读操作,即从机传输数据给主机;另外,数据传输是从最高位到最低位,因此传输方式为MSB(Most Significant Bit)。

③ 总线中对应从机地址的设备,发出应答信号;

④ 在接下来的8个时间周期里,如果是写操作,则主机控制SDA的高低;如果是读操作,则从机控制SDA的高低;

⑤ 每次传输完成,接收数据的设备,都发出应答信号;

⑥ 最后,在SCL为高时,主机由低拉高SDA,表示停止信号,整个传输结束;

EEPROM简介

​EEPROM的全称是“电可擦除可编程只读存储器”,即Electrically Erasable Programmable Read-Only Memory。通常用于存放用户配置信息数据。EEPROM和Flash的本质上是一样的,从结构上,Flash按扇区操作,EEPROM通常按字节操作。

​BL24C02F与AT24C02是一样的,2 Kbits (256bytes), 每页大小为16 Byte

​EEPROM(BL24C02F)设备需要一个8位的设备地址字,以使芯片能够进行读或写操作,如下图所示A0,A1,A2由设计者自己确定,开发者根据原理图可以确定取值,R/W为读写位,1代表读,0代表写。
在这里插入图片描述

写保护

​BL24C02F有一个Write Protect(WP)引脚,提供硬件数据保护。当连接到地面(GND)时,Write Protect(WP)引脚允许正常的读写操作。当写保护引脚连接到Vcc时,写保护功能已启用,具体操作如表2所示
在这里插入图片描述

写BL24C02F

BL24C02F支持字节写模式和页写模式。字节写模式是一个地址一个数据的写;页写模式是连续写数据,一个地址多个数据的写,但是页写模式不能自动跨页,如果超出一页长度,超出的数据会覆盖原先写入的数据。每次写完之后,再到下次写之前,需要间隔一定的时间通常为5ms时间(具体得看芯片手册)

字节写模式
在这里插入图片描述

页写模式

在这里插入图片描述

读BL24C02F

​ BL24C02F支持当前地址读模式、随机地址读模式和顺序读模式。当前地址读模式就是在上一次读/写操作之后的最后位置,继续读出数据,比如上次读/写在地址n,接下来可以直接从n+1处读出数据;随机地址读模式是指定数据地址,然后读出数据;顺序读模式是连续读出多个数据。

​ 在当前地址读模式下,无需发送数据地址,数据地址为上一次读/写操作之后的位置,时序如图 9所示,注意在结尾,主机接收数据后,无需产生应答信号。
在这里插入图片描述

​ 在随机地址读模式下,需要先发送设备地址,待读的数据地址,接着再重新发出开始信号,设备地址,读出数据时序如图 10 所示。
在这里插入图片描述

​ 在顺序读模式下,需要先从当前地址读模式或随机地址读模式启动,随后便可连续读多个数据,时序如图11所示
在这里插入图片描述
EEPROM单字节写数据逻辑分析仪波形分析如下图所示,下图表示向EEPROM地址0x00写入数据0x40的整个写入过程波形
在这里插入图片描述
EEPROM读取数据逻辑分析仪波形如下图所示,下图表示从EEPROM地址0x00读出一个数据,数据为0x40的整个读过程波形
在这里插入图片描述

IIC 软件驱动

下面所写程序有点水,还未完善,封装的有点问题后续持续更新,正常驱动使用IIC协议的器件是没有问题的,有需要的用的可以拿走。
xx_iic.h

#ifndef __XX_IIC_H
#define __XX_IIC_H

/*发送速率*/
#define IIC_SPEED 	3	

#define ERROR_IIC_NO_ACK	0
#define IIC_ACK_SUCCESS		1 
#define IIC_SUCCESS 	1
#define IIC_FAIL		0

typedef signed char int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;

typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;

typedef struct 
{
	void (*iic_init)(void);						
	void (*iic_sda_output)(void);		
	void (*iic_sda_input)(void);
	void (*iic_scl_high)(void);
	void (*iic_scl_low)(void);
	void (*iic_sda_high)(void);
	void (*iic_sda_low)(void);
	void (*iic_delay_us)(uint32_t mus);
	uint8_t (*read_sda)(void);
	uint8_t *rxd;
	uint8_t *txd;
}IIC_TypeDef;


extern int8_t iic_send_data(IIC_TypeDef *xIIC,uint8_t SlaveAddr,uint8_t addr,uint8_t data[],uint8_t datalen);
extern int8_t iic_read_data(IIC_TypeDef *xIIC,uint8_t SlaveAddr,uint8_t addr,uint8_t data[],uint8_t datalen);
#endif

#include "xx_iic.h"

/**
 * @brief iic起始信号
 */
static void iic_start_signal(IIC_TypeDef *xIIC)
{
	xIIC->iic_sda_output();
	
	xIIC->iic_sda_high();
	xIIC->iic_delay_us(IIC_SPEED);
	xIIC->iic_scl_high();
	xIIC->iic_delay_us(IIC_SPEED);
	
	xIIC->iic_sda_low();
	xIIC->iic_delay_us(IIC_SPEED);
	
	xIIC->iic_scl_low();
	return;
}

/**
 * @brief iic停止信号
 */
static void iic_stop_signal(IIC_TypeDef *xIIC)
{
	xIIC->iic_sda_output();
	
	xIIC->iic_scl_low();
	xIIC->iic_delay_us(IIC_SPEED);
	xIIC->iic_sda_low();
	xIIC->iic_delay_us(IIC_SPEED);
	
	xIIC->iic_scl_high();
	xIIC->iic_delay_us(IIC_SPEED);

	xIIC->iic_sda_high();
	xIIC->iic_delay_us(IIC_SPEED);

	return;
}

/**
 * @brief iic等待从机应答
 * @return ERROR_IIC_NO_ACK 无应答
 * @return IIC_ACK_SUCCESS 从机应答成功
 */
static int8_t iic_wait_Ack(IIC_TypeDef *xIIC)
{
	uint8_t Errtime = 0;
	
	xIIC->iic_sda_input();
	
	xIIC->iic_sda_high();
	xIIC->iic_delay_us(IIC_SPEED);
	xIIC->iic_scl_high();
	xIIC->iic_delay_us(IIC_SPEED);
	
	while(xIIC->read_sda())
	{
		Errtime++;
		if(Errtime>250)
		{
			iic_stop_signal(xIIC);
			return ERROR_IIC_NO_ACK;
		}
	}
	xIIC->iic_scl_low();
	return IIC_ACK_SUCCESS;
}

/**
 * @brief iic应答从机
 */
static void iic_Ack(IIC_TypeDef *xIIC)
{
	xIIC->iic_sda_output();
	
	xIIC->iic_scl_low();
	xIIC->iic_delay_us(IIC_SPEED);
	
	xIIC->iic_sda_low();
	xIIC->iic_delay_us(IIC_SPEED);
	
	xIIC->iic_scl_high();
	xIIC->iic_delay_us(IIC_SPEED);
	xIIC->iic_scl_low();
	return;
}

/**
 * @brief iic不应答从机
 */
static void iic_nAck(IIC_TypeDef *xIIC)
{
	xIIC->iic_sda_output();
	
	xIIC->iic_scl_low();
	xIIC->iic_delay_us(IIC_SPEED);
	
	xIIC->iic_sda_high();
	xIIC->iic_delay_us(IIC_SPEED);
	
	xIIC->iic_scl_high();
	xIIC->iic_delay_us(IIC_SPEED);
	xIIC->iic_scl_low();	
	return;
}


/**
 * @brief iic主机发送一字节数据
 */
void iic_send_byte(IIC_TypeDef *xIIC,uint8_t txd)
{
	uint8_t i = 0;
	xIIC->iic_sda_output();
	
	xIIC->iic_scl_low();	
	for(i = 0;i<8;i++)
	{
		if((txd&0x80)>>7)
		{
			xIIC->iic_sda_high();
		}
		else
		{
			xIIC->iic_sda_low();
		}
		txd<<=1; 
		xIIC->iic_delay_us(IIC_SPEED);
		
		xIIC->iic_scl_high();
		xIIC->iic_delay_us(IIC_SPEED);
		xIIC->iic_scl_low();
		xIIC->iic_delay_us(IIC_SPEED);	
	}
}

/**
 * @brief iic主机
 * @param Ack: 1:应答 0:非应答
 */
uint8_t iic_read_byte(IIC_TypeDef *xIIC,uint8_t Ack)
{
	uint8_t i = 0,receive = 0;
	xIIC->iic_sda_input();

	for(i = 0;i<8;i++)
	{	
		xIIC->iic_scl_low();
		xIIC->iic_delay_us(IIC_SPEED);
		xIIC->iic_scl_high();
		receive<<=1;
		if(xIIC->read_sda())
				receive++;         
		xIIC->iic_delay_us(IIC_SPEED);	
		xIIC->iic_scl_low();
	}
	
	if (!Ack)
	{
			iic_nAck(xIIC);
	}
	else
	{
			iic_Ack(xIIC);
	}   
    return receive;
}

/**
 * @brief iic主机发送数据至从机
 * @param SlaveAddr: 从机地址(不带读写位的地址)
* @param data:发送数据缓冲区
 * @param datalen: 数据长度
 */
int8_t iic_send_data(IIC_TypeDef *xIIC,uint8_t SlaveAddr,uint8_t addr,uint8_t data[],uint8_t datalen)
{
	uint8_t i = 0;
	int8_t error = 0;
	
	iic_start_signal(xIIC);
	
	iic_send_byte(xIIC,SlaveAddr << 1);
	error = iic_wait_Ack(xIIC);
	if(error == ERROR_IIC_NO_ACK)
		return ERROR_IIC_NO_ACK;
	
	iic_send_byte(xIIC,addr);
	error = iic_wait_Ack(xIIC);
	if(error == ERROR_IIC_NO_ACK)
		return ERROR_IIC_NO_ACK;
	
	for(i = 0;i<datalen;i++)
	{
		iic_send_byte(xIIC,data[i]);
		error = iic_wait_Ack(xIIC);
		if(error == ERROR_IIC_NO_ACK)
			return ERROR_IIC_NO_ACK;
	}
	iic_stop_signal(xIIC);
	return IIC_SUCCESS;
}

/**
 * @brief iic主机接收数据从从机
 * @param SlaveAddr: 从机地址(不带读写位的地址)
 * @param data:接收数据缓冲区
 * @param datalen: 数据长度
 */
int8_t iic_read_data(IIC_TypeDef *xIIC,uint8_t SlaveAddr,uint8_t addr,uint8_t data[],uint8_t datalen)
{
	uint8_t i = 0;
	int8_t error = 0;

	iic_start_signal(xIIC);
	iic_send_byte(xIIC,(SlaveAddr << 1));
	error = iic_wait_Ack(xIIC);
	if(error == ERROR_IIC_NO_ACK)
		return ERROR_IIC_NO_ACK;
	
	iic_send_byte(xIIC,addr);
	error = iic_wait_Ack(xIIC);
	if(error == ERROR_IIC_NO_ACK)
		return ERROR_IIC_NO_ACK;
	
	iic_start_signal(xIIC);
	iic_send_byte(xIIC,(SlaveAddr << 1) | 1);
	error = iic_wait_Ack(xIIC);
	if(error == ERROR_IIC_NO_ACK)
		return ERROR_IIC_NO_ACK;
	for(i = 0;i<datalen - 1;i++)
	{
		data[i] = iic_read_byte(xIIC,1);
	}
	data[i] = iic_read_byte(xIIC,0);
	iic_stop_signal(xIIC);
	return IIC_SUCCESS;	
}

上述代码具体使用的话需要提供下图所示的一些函数包括初始化函数(IO初始化)、SDA拉高拉低读取、SCL拉高拉低、SDA输入输出、延时函数
在这里插入图片描述

IIC_TypeDef IIC1 = 
{
	IIC_init,
	SDA_OUT,
	SDA_IN,
	SCL_H,
	SCL_L,
	SDA_H,
	SDA_L,
	Delay_us,
	SDA_READ,
	iic_rxd,
	iic_send,
};
IIC1.iic_init();
iic_send_data(IIC1,0x50,0x00,data,1);
  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

威威攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值