1、IIC简介
IIC(Inter-Intergated Circuit,集成电路总线)由飞利浦(Pilliphs)公司发明,是一种串行总线通信。
有两根线:
SDA:Serial DAta 串行数据线
数据传输按bit位,属于半双工的协议。先传送最高bit(MSB)。
SCL:Serial CLock 串行时钟线
传递时钟信号,时钟信号是用来同步信号的。
同步:约定好发送数据只能在时钟线低电平,接收(采样)数据只能在时钟线的高电平。
因为只有一根数据线,所以IIC是半双工通信。
IIC通信设备都会挂载在SDA和SCL总线上,或者说SDA和SCL总线上会挂载很多设备。那么任意时刻,只能有一个设备向总线上发送数据,但是接收没有限制,都可以收。
为了让数据精确到达(而不是以广播的形式发送),我们给IIC总线上的每一个设备都给一个唯一的地址,这个地址就是设备地址,用来区分不同的I2C设备。
**2、IIC时序图(IIC协议)
IIC数据通信的大概流程:
a. 总线空闲(空闲指没有数据通信时总线的状态)
IIC总线在空闲的时候,SDA和SCL都处于高电平。
如果你想开始发送数据,则你需要先发送一个起始信号。
/*空闲*/
SCL = 1;
SDA = 1;
b. 起始信号:用来表示我要向总线上发送数据啦
SCL维持高电平不变
SDA从高到低产生下降沿
/* 起始信号 */
SDA = 0;
有没有可能IIC总线上的两个设备同时发送起始信号?
有可能。所以需要总线仲裁:决定谁的信号有效。比如:在发送起始信号前,判断IIC总线是否空闲。
怎么做:
time_out = SCL_T;//超时时间为一个SCL时钟周期
while(SCL == 1 && SDA == 1 && time_out--);
c. 发送数据
数据包括用户真正要发送的数据,也包括设备地址(指定通信方)。因为总线上有多个设备,其中一个发起一个起始信号,表示它要跟总线上的其它设备或者多个设备进行通信。
所以IIC协议规定,每一个IIC总线上的设备都必须要有一个IIC设备地址(7bit/10bit)并且同一个IIC总线上的设备的地址必须不一样。
IIC中数据(包括地址)的发送都是按8bit进行发送。
设备地址 = 7bit + 1bit R/W(读写位,占最低位)
bit0: 0 W 表示我要给指定地址的设备写入数据
bit0: 1 R 表示我要从指定地址的设备读取数据
发送完一个字节(8bit)后,对方(接收方)必须要回一个ACK(应答位)
ACK:在SDA数据线上的第9个时钟周期,接收方给SDA一个低
电平。如果数据的最后一个bit本身就是一个低电平,哪怕
对方不应答,那么SDA在第九个时钟周期的电平状态就是0,
这个时候,发送方可能会认为对方应答啦。
发送方在发送完8bit的数据后,一般都会释放SDA数据线(SDA == 1)。
例如
CPU(发送方):START 0000 0010 1100 0100 STOP
A(接收方): ACK ACK
数据的发送规则:
数据发送起始就是根据要发送的数据的bit的情况给SDA
线低电平或高电平,先发送MSB(最高位)。
发送数据时,更改数据线的要求如下:
在SCL时钟线低跳变的时候,可以改变SDA数据线的
电平,所以发送是下降沿触发,每一个下降沿可以发送
1bit的数据。
在SCL时钟线高跳变的时候,SDA数据线应该要保持稳定。
接收是上升沿触发,每一个上升沿到来,就会去SDA上
采集1bit的数据。
d. 停止信号
SCL保持高电平
SDA从低电平到高电平跳变
通过谁控制SCL线区分不同的IIC设备:
IIC主设备:Master
产生IIC时钟输出的设备,它控制IIC总线的传输速率。
IIC从设备:Slave
被动接收IIC时钟的设备。
Master-Send 主发 Master-Receive 主收
时钟提供者既可以收也可以发
Slave-Send 从发 Slave-Receive 从收
IIC总线上的时钟频率一般在几十K hz~400K hz 之间,频率越低通信速率就越慢,但是也越稳定。
3、模拟IIC
在某些芯片上(如:C51)它没有I2C总线,没有I2C控制器,所以需要用两个GPIO口去模拟SDA和SCL
/*
IIC_Send_Start:发送IIC起始信号
*/
void IIC_Send_Start(void)
{
/*空闲*/
SCL = 1;
SDA = 1;
delay(IIC_T); //IIC_T:IIC时钟信号的周期-->延时一个时钟周期
/*起始信号*/
SDA = 0;
delay(IIC_T);
}
/*
IIC_Send_Stop:发送IIC停止信号
*/
void IIC_Send_Stop(void)
{
SCL = 1;
SDA = 0;
delay(IIC_T); //IIC_T:IIC时钟信号的周期-->延时一个时钟周期
SDA = 1;
delay(IIC_T);
}
/*
IIC_Send_Byte:将一个字节的数据发送出去
@ch : 要发送的数据,1个字节
@返回值 : 发送并成功返回1(表示接收方回应了一个Ack)
失败返回0
*/
int IIC_Send_Byte(char ch)
{
/*MSB(最高位)先发,每次发送8bit,并且是在SCL下降沿的时候发送*/
int i;
for(i = 7;i >= 0;i--) //8个SCL时钟周期中发送8bit
{
SCL = 0; //下降沿产生
SDA = (ch >> i) & 0x01; //每次发送一个bit
delay(IIC_T/2);
SCL = 1;
delay(IIC_T/2); //延时一会等待对方去接收
}
/*发送发在发送完8个bit后,一般会释放SDA数据线*/
/*同时在第9个周期,等待接收方回应一个ACK*/
SCL = 0; //第9个周期开始
SDA = 1; //释放数据线
delay(IIC_T/2); //等接收方应答
SCL = 1;
if(SDA)
{
return 0; //意味着别人没有应答我
}
else
{
return 1; //意味着别人应答我啦
}
delay(IIC_T/2); //第九个周期结束
}
/*
IIC_Recv_Byte:从IIC总线上接收一个字节
@返回值 : 将接收到的字节返回
*/
unsigned char IIC_Recv_Byte(void)
{
/*先接收最高bit,陆陆续续的会收到8bit*/
int i;
unsigned char ch = 0;
for(i = 7;i >= 0;i--)
{
SCL = 0; //给半个时钟周期的低电平,让对方发送数据
delay(IIC_T/2);
SCL = 1; //上升沿读取数据
if(SDA)
{
ch |= (1 << i);
}
delay(IIC_T/2);
}
/*在第九个时钟周期会回应对方(发送ACK)*/
SCL = 0; //第九个时钟周期开始
delay(); //延时一段非常短的时间让对方释放数据线
SDA = 0; //回应应答信号ACK
delay(IIC_T/2);
SCL = 1;
delay(IIC_T/2); //第九个周期结束
return ch;
}
/*
IIC_Write_Bytes:向指定的IIC设备发送数据
@addr : 7bit的目标IIC设备的 地址
@str : 要发送的数据字符串
@len : 要发送的数据字符串的长度
@返回值 : 发送成功返回1,返回失败返回0
*/
int IIC_Write_Bytes(unsigned char addr,unsigned char *str,int len)
{
//发送起始信号
IIC_Send_Start();
//发送设备地址 = addr + 读写位(0)
int ret = IIC_Send_Byte((addr << 1) | 0);
if(ret == 0) //代表没有应答
{
IIC_Send_Stop(); //发送停止信号
return 0;
}
//发送数据
int i;
for(i = 0;i < len;i++)
{
ret = IIC_Send_Byte(str[i]);
if(ret == 0) //代表没有应答
{
IIC_Send_Stop(); //发送停止信号
return 0;
}
}
//发送停止信号
IIC_Send_Stop();
return 1;
}
4、STM32F4xx IIC控制器
STM32F4xx 有三个IIC控制器,有三条IIC的总线。
IIC控制器是一个IIC的设备,它负责产生IIC的时序以及协议逻辑。
在STM32F4xx中,CPU与I2C是通过系统总线进行通信的。
如果没有I2C的控制器,那么CPU只能通过GPIO口
来模拟,即I2C时序、协议逻辑等都需要软件去模拟。
5、STM32F4xx I2C固件库函数
IIC控制器的SDA和SCL其实也是通过GPIO口复用而来。
1)初始化I2C引脚
a. 使能GPIO分组时钟
PB8-->I2C_SCL
PB9-->I2C_SDA
b. GPIO初始化配置
GPIO_Init() ---> AF_MODE(复用模式)
c. 将GPIO口复用成什么功能
GPIO_PinAFConfig()
2) 初始化IIC控制器
a. 使能IIC控制器
RCC_APB1...
b. 初始化IIC
I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct)
@I2Cx:指定I2C控制器的编号
I2C1/I2C2/I2C3
@I2C_InitStruct:指向初始化信息结构体
3)配置I2C控制器的一些其它功能
比如:中断
I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState)
4)开启I2C控制器
I2C_Cmd(I2C_TypeDef* I2Cx,FunctionalState NewState)
5)I2C总线读写流程
a. 发送起始信号
I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState)
b. 获取指定的事件
I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
例如:
//发送一个起始信号
I2C_GenerateSTART(I2C1,ENABLE);
//在发送起始信号之后,应该要等待EV5事件(等待起始信号发生成功/从从模式切换到主模式)发生
while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
c. 发送一个7bit的从设备地址
I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction)
d. 发送数据
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data)
e. 接收数据
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx)
f. 发送停止信号
I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState)
g. 获取I2C控制器的状态标志
FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG)
@I2C_FLAG:指定状态标志位
I2C_FLAG_BUSY表示I2C总线是否忙碌
如果被设置,意味着总线忙碌,不能发送起始信号
h. 清除I2C控制器的状态标志
I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG)
6、AT24C02
采用IIC进行通信的EEPROM存储器芯片–>AT24C02
EEPROM: 是一个小容量的存储器芯片,一般存储几K的数据,在实际产品中,一般用来存储一些其它模块的ID/MAC/版本号…
a. 器件地址
24C02 address : 1010 000 (A2 A1 A0在物理上都已经接地)
b. 内存存储结构
24C02一共有2K bit(256bytes),分为32页,每页8个字节。
每页有自己的页地址(5bit)
每一个字节都有自己的字节地址(3bit)
所以24C02的存储单元地址为:
8bit = 5bit page_addr + 3bit word_addr
c. AT24C02写操作
AT24C02的读写操作可以分为写操作时序(写一个字节)、页写操作时序(写1页)
1. 写操作时序(写一个字节)
在中文参考手册第8页
假设需要往AT24C02内部字节地址为0x55处写入一个数据0xAA
MCU: START 从设备地址(1010 0000) 字节地址(0x55) 数据(0xAA) STOP
AT24C02: ACK ACK ACK
2. 页写操作时序(写一页-->8bytes)
AT24C02一页有8个字节,一次写操作最多可以连续写8个字节,在页写时序时,每写入一个字节后,字地址会自动 + 1
需要注意的是地址仅仅是字地址(低3bit)会+1,页地址不会+1--->不能跨页
假设需要往AT24C02内部字节地址为0x00处写入一个数据0x01,0x02....0x08
MCU: START 从设备地址(1010 0000) 字节地址(0x00) 0x01 0x02 ... STOP
AT24C02: ACK ACK ACK
d. 读操作时序
一般在读取之前,需要先写入一个字地址,表示从设备的哪个存储单元开始读。
如果读之前,不写字节地址,而直接从芯片内部的地址计数器指定的地址那里开始读。
地址计数器:相当于光标
MCU: START 从设备地址(1010 0000) 字地址 START 从设备地址(1010 0001) A A NA STOP
AT24C02: ACK ACK A DATA1 DATA2 ....DATAn
/*
IIC1_Init:IIC1的初始化函数
IIC1主要用来与MCU内部集成的AT24C02存储器芯片进行通信
PB8-->I2C1_SCL
PB9-->I2C1_SDA
*/
void IIC1_Init(void)
{
//1.初始化I2C引脚
//1.1 使能GPIO分组时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
//1.2 GPIO初始化配置
GPIO_InitTypeDef g;
g.GPIO_Mode = GPIO_Mode_AF; //GPIO复用
/*
如果配置成推挽模式,当要实现输入检测的时候,就会受到输出电路没有关闭的影响,因为之前的输出电平是一直存在的,造成输入电路和输出电路的短接的情况,所以只能配置成开漏电路,就算当CPU输出1,由于P-MOS管处于关闭状态,IO端口的电平将完全由外部电路决定,因此CPU可以在输入数据寄存器中读取到外部电路的信号而不是自己输出的1。
*/
g.GPIO_OType = GPIO_OType_OD; //输出开漏
g.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
g.GPIO_PuPd = GPIO_PuPd_UP; //上拉
g.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&g);
//1.3 将GPIO口复用成什么功能
GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_I2C1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_I2C1);
//2.初始化IIC控制器
//2.1 使能IIC控制器
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
//2.2 初始化IIC
I2C_InitTypeDef i;
i.I2C_Ack = I2C_Ack_Enable; //自动回应ACK
i.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //IIC控制器地址为7bit
i.I2C_ClockSpeed = 400000; //通信速率400KhZ
i.I2C_DutyCycle = I2C_DutyCycle_16_9;
i.I2C_Mode = I2C_Mode_I2C;
i.I2C_OwnAddress1 = 0x66; //随便写
I2C_Init(I2C1,&i);
//3.启动I2C
I2C_Cmd(I2C1,ENABLE);
}
/*
Wait_IIC_Event:用于等待IIC1总线上的某个事件发生(限时等待)
@I2Cx : 要等待的是哪个总线上的事件
@I2C_EVENT:要等待的是哪个事件
@timeout: 最多等待多久(超时时间,单位us)
@返回值 : 等待的事件发生返回1,超时没有发生则返回0
*/
static u32 Wait_IIC_Event(I2C_TypeDef *I2Cx,u32 I2C_EVENT,int timeout)
{
//当事件没有发生或没有超时的时候则继续等待
while(I2C_CheckEvent(I2Cx,I2C_EVENT) == ERROR && timeout--)
{
//每隔1us判断一次事件有没有发生或者有没有超时
Delay_us(1);
}
//timeout为-1的话则意味着超时啦
return (timeout == -1) ? 0 : 1;
}
/*
AT24C02_Is_Busy:测试IIC总线是否繁忙以及24C02是否能与MCU正常通信
@返回值 : 收到24C02的应答则返回0,没有收到ACK则返回1
*/
static u32 AT24C02_Is_Busy(void)
{
int time = 20;
while(time--) //测试多次
{
//测试IIC总线是否繁忙
I2C_GenerateSTART(I2C1,ENABLE); //发送起始位
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
{
//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
I2C_GenerateSTOP(I2C1,ENABLE); //结束此次测试
continue;
}
//意味着起始信号发送成功-->I2C总线不繁忙
//接下来测试24C02是否能与MCU正常通信-->根据24C02有无回应即可
I2C_Send7bitAddress(I2C1,Write24C02_ADDR,I2C_Direction_Transmitter);
if(Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000))
{
//意味着EV6事件(对方应答啦)发生
I2C_GenerateSTOP(I2C1,ENABLE); //结束此次测试
return 0;
}
}
I2C_GenerateSTOP(I2C1,ENABLE); //发送结束信号
return 1;
}
/*
Write_A_Byte_To_24C02:用来往24C02中特定的存储单元中写入一个字节
@addr : 要往24C02的哪个存储单元中写入数据
@data : 要写入的数据
@返回值 : 写入成功返回0,写入失败返回-1
时序:
MCU: START 从设备地址(1010 0000) 字节地址 数据 STOP
AT24C02: ACK ACK ACK
*/
s32 Write_A_Byte_To_24C02(u8 addr,u8 data)
{
//判断IIC总线是否繁忙,繁忙则等,不繁忙则发送数据
//while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) == SET);
//实际项目中,我们一般不这么做,因为有可能会陷入死等
if(AT24C02_Is_Busy())
{
printf("IIC Is Busy!!\r\n");
return -1;
}
printf("IIC is ready to write!\r\n");
//发送起始信号
I2C_GenerateSTART(I2C1,ENABLE); //发送起始位
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
{
//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送从设备地址
I2C_Send7bitAddress(I2C1,Write24C02_ADDR,I2C_Direction_Transmitter);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000))
{
//意味着EV6事件(发送从设备地址之后的应答事件)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送字地址
I2C_SendData(I2C1,addr);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
{
//意味着EV8_2事件(数据已经发送完成)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送真正的数据
I2C_SendData(I2C1,data);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
{
//意味着EV8_2事件(数据未发送完成)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送停止信号,结束此次数据传输
I2C_GenerateSTOP(I2C1,ENABLE);
return 0;
}
/*
Read_A_Byte_From_24C02:用来从24C02的addr存储空间中读取一个字节
@addr : 指定从24C02的哪个存储空间中读取数据
@data : 读取出来的数据保存到data指向的空间中去
@返回值 : 读取成功返回0,读取失败返回-1
时序:
MCU: START 从设备地址(1010 0000) 字地址 START 从设备地址(1010 0001) NA STOP
AT24C02: ACK ACK A DATA
*/
s32 Read_A_Byte_From_24C02(u8 addr,u8 *data)
{
if(AT24C02_Is_Busy())
{
printf("IIC Is Busy!!\r\n");
return -1;
}
printf("IIC is ready to read!\r\n");
//发送起始信号
I2C_GenerateSTART(I2C1,ENABLE);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
{
//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送从设备地址
I2C_Send7bitAddress(I2C1,Write24C02_ADDR,I2C_Direction_Transmitter);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000))
{
//意味着EV6事件(对方应答啦)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送字地址
I2C_SendData(I2C1,addr);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
{
//意味着EV8_2事件(数据未发送完成)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送起始信号
I2C_GenerateSTART(I2C1,ENABLE);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
{
//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送从设备地址
I2C_Send7bitAddress(I2C1,Read24C02_ADDR,I2C_Direction_Receiver);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED,1000))
{
//意味着EV7事件(主机可以读取数据啦)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//读取数据
*data = I2C_ReceiveData(I2C1);
//发送停止信号
I2C_GenerateSTOP(I2C1,ENABLE);
return 0;
}
/*
Read_Bytes_From_24C02:用于从24C02中连续读取多个字节
@addr : 表示从24C02的哪个存储单元开始读
@data : 指向的空间用来保存读取出来的数据
@count : 表示读取的字节数
@返回值 : 读取成功返回真正读取到的字节数,读取失败返回-1
*/
s32 Read_Bytes_From_24C02(u8 addr,u8 *data,int count)
{
if(addr > 0xff)
{
return -1;
}
if(AT24C02_Is_Busy())
{
printf("IIC Is Busy!!\r\n");
return -1;
}
printf("IIC is ready to read!\r\n");
//发送起始信号
I2C_GenerateSTART(I2C1,ENABLE);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
{
//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送从设备地址
I2C_Send7bitAddress(I2C1,Write24C02_ADDR,I2C_Direction_Transmitter);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000))
{
//意味着EV6事件(对方应答啦)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送字地址
I2C_SendData(I2C1,addr);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
{
//意味着EV8_2事件(数据未发送完成)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送起始信号
I2C_GenerateSTART(I2C1,ENABLE);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
{
//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送从设备地址
I2C_Send7bitAddress(I2C1,Read24C02_ADDR,I2C_Direction_Receiver);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,1000))
{
//意味着EV6事件(主机切换成主收模式)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//读取数据
//先读取count-1个字节,因为我们在初始化IIC1的时候指定IIC1控制器会自动回应ACK,
//所以IIC1在接收完24C02 count-1个字节的时候已经帮我们回复了count-1g个ACK
//但是在接收最后一个字节(第count个字节)的时候,IIC1不应该继续回复ACK啦
int i;
for(i = 0;i < count - 1;i++)
{
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED,1000))
{
//意味着EV7事件(主机可以读取数据啦)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
data[i] = I2C_ReceiveData(I2C1);
}
//在去接收最后一个24C02发给IIC1的字节的时候让IIC1停止自动回复ACK
//那么IIC1在接收完最后一个字节后,24C02就会收不到IIC1的回应而选择不继续给IIC1发送数据
I2C_AcknowledgeConfig(I2C1,DISABLE);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED,1000))
{
//意味着EV7事件(主机可以读取数据啦)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
data[i++] = I2C_ReceiveData(I2C1);
//发送停止信号
I2C_GenerateSTOP(I2C1,ENABLE);
//让IIC1恢复自动回复ACK
I2C_AcknowledgeConfig(I2C1,ENABLE);
return i;
}
/*
Write_Bytes_To_24C02:用于向24C02中连续写入多个字节
@addr : 表示从24C02的哪个存储单元开始写
@data : 要写入到24C02中的数据
@count : 要写入的字节数
@返回值 : 成功返回写入的字节数,失败返回-1
*/
s32 Write_Bytes_To_24C02(u8 addr,u8 *data,int count)
{
//用来记录已经写入了多少个字节
int bytes = 0;
//判断count是否合法
count = (count <= (256 - addr)) ? count : (256 - addr);
page_write:
if(AT24C02_Is_Busy())
{
printf("IIC Is Busy!!\r\n");
return -1;
}
//发送起始信号
I2C_GenerateSTART(I2C1,ENABLE); //发送起始位
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
{
//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送从设备地址
I2C_Send7bitAddress(I2C1,Write24C02_ADDR,I2C_Direction_Transmitter);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000))
{
//意味着EV6事件(对方应答啦)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//发送字地址
I2C_SendData(I2C1,addr);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
{
//意味着EV8_2事件(数据未发送完成)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
//写入的字节数不能超过当前页剩余的字节数
//计算出当前写入位置addr距离该页的末尾有多少个字节
//8 - 页内地址 = 当前页剩余的字节数
int page_bytes = 8 - (addr & 0x07);
page_bytes = (page_bytes > (count - bytes)) ? (count - bytes) : page_bytes;
printf("IIC is ready to write %d bytes!\r\n",page_bytes);
//往24C02中写入数据
int i;
for(i = 0;i < page_bytes;i++)
{
I2C_SendData(I2C1,data[bytes]);
if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
{
//意味着EV8_2事件(数据未发送完成)未发生
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
bytes++;
}
//发送停止信号,结束此次数据传输
I2C_GenerateSTOP(I2C1,ENABLE);
if(bytes < count)
{
//意味着还没有写完,得从下一页继续开始写
//addr += page_bytes;
//页地址加1
addr = ((addr >> 3) + 1) << 3;
goto page_write;
}
return bytes;
}
标签:ENABLE,通信协议,发送,地址,IIC,I2C,I2C1
From: https://www.cnblogs.com/amxiang/p/16912932.html