IIC与AT24C02
1 IIC:集成电路总线
由Pilliphs(飞利浦)公司发明的。IIC也属于通信的一种,并且也是串行通信(按bit收发数据)。
IIC属于串行总线通信:
只有一根数据线 SDA: Serial Data 串行数据线
还有一根时钟线 SCL: Serial Clock 串行时钟线
SDA: 数据按Bit发送,先发MSB(最高位)
SCL: 传递时钟信号,进行同步作用。同步:约定好发送数据只能在时钟线的低跳变时,接收数据只能在高跳变时。
IIC是半双工通信:因为只有一根SDA。在发送的时候肯定不能接收数据。
IIC通信设备都会挂载在SDA和SCL总线上。那么任意时刻,只能有一个设备向总线上发送数据,但是接收没有限制。
为了让数据能够准确到达(而不是以广播的形式),我们给IIC总线上的设备都给一个唯一的设备地址,用来区分不同的IIC设备。
2 IIC时序图(IIC协议)
IIC数据通信的流程:
a.总线空闲(空闲是指没有数据通信的总线的状态)
约定:IIC总线空闲的时候,SDA和SCL都处于高电平(通过在总线上接了一个上拉电阻来实现。
接下来如果有一个设备需要给另外一个设备发送数据的话,就需要一个起始信号。
b.起始信号:用来表示要开始发数据了!!
SCL时钟线还是保持高电平
SDA数据线从高到低的跳变
模拟IIC的起始信号:
//空闲
SDA = 1;
SCL = 1;
delay();
//起始信号
SDA = 0;
delay();
有没有可能多个设备同时发送了起始信号?有可能,所以需要总线仲裁:决定谁的信号有效。
比如:在发送起始信号之前,判断IIC总线是否空闲,怎么做?
time_out=SCL_T//设定超时的时间为SCL的一个周期
while(SCL==1 && SDA == 1 && time_out--);
//解析:如果一个SCL周期内,SCL和SDA都是高电平,那么就说明没有人在总线上发数据。
c.发送数据:
数据包含用户真正发送的数据,也要包含设备地址(指定通信方)。
因为总线上有很多的设备,如果不指定设备地址,表示你想跟总线上所有的设备进行通信。
所以IIC协议规定:每个IIC总线上的设备都必须有一个IIC设备地址(7bits/10bits),并且,同一个IIC总线上的设备,地址肯定不一样。
IIC中的数据(包括设备地址)的发送都是按8bits进行发送。
设备的地址 = 7bits + R/W(读写位,占最低位)
bit0: 0 W 表示要给指定的地址发送数据(写)
bit0: 1 R 表示要从指定的地址接收数据(读)
例如: 设备B的地址是 101 0001,CPU要发送数据0x55给设备A
CPU START 1010 0010 0101 0101
1010 001 :代表设备地址 0:代表写 0101 0101:是写的数据
发送完一个字节(8bits)数据后,对方(接收方)必须返回一个ACK(应答位)。
ACK:在SDA数据线上的第9个周期,接收方给SDA一个低电平。
但是这里存在一个问题,就是如果发送数据的最后一个位本身就是一个低电平,那么SDA此时的电平状态就是0,这个时候,不管
对方应答还是不应答,发送方判断(读)SDA的数据都是0(应答)。
怎么解决:
发送方在发送完8bits数据后,一般会释放SDA(SDA=1)。
在第9个周期时,接收方如果收到了就会立即把发送方释放的SDA=1拉低到SDA=0,即给SDA一个低电平(表示我收到了),如果没有收那么就不会回应,不会拉低,那么SDA就是发送方释放的高电平。发送方由SDA高低电平就能判断出有没有人收。
例如:
CPU START 1010 0010 0101 0101
接收方: ACK ACK
总结数据的发送规则:
数据的发送起始就是根据要发送的数据bit给SDA线低电平或者高电平,先发送MSB(最高位)。
发送数据时,更改数据线的要求如下:
IIC协议规定:
在SCL时钟线低跳变的时候,可以改变SDA数据线的电平
所以发送时下降沿触发,每个下降沿可以发送1bit的数据
在SCL时钟线高电平的时候,SDA数据线保持稳定
所以接收是上升沿触发,每个上升沿到来,就会去SDA上采集数据。
d.停止信号:
SCL保持高电平,SDA从低电平到高电平的跳变。
所以一帧数据的格式:
发送: START + data(7bit addr + 1bit 0) + data(8bits) +…+ STOP
1bitACK 1bitACK
接收: START + data(7bit addr + 1bit 1) +data(8bits) +…+ STOP
1bitACK 1bitACK
另外的一个问题:
SDA线一般是谁发送数据就由谁控制,那么SCL谁来控制?
其实谁控制都可以,但是不能同时控制,而且大部分设备都没有时钟发送能力。因为没有时钟单元。
所以在32里面,一般就是由CPU作为控制者。
所以通过谁去控制SCL线就可以划分不同的角色:
IIC主设备 :Master产生IIC时钟线输出的设备
IIC从设备:Slave 被动接收时钟的设备
细分的话:
Master-Send 主发 Master-Receive 主收
Slave-Send 从发 Slave-Receive 从收
IIC总线上的时钟频率一般在几十khz到400khz。频率越高,通信速率越快但是越不稳定。
3 模拟IIC的代码
有一些芯片(比如51)没有IIC总线,能不能与一些IIC接口的模块进行通信?可以,只需要用两个GPIO口来模拟SDA和SCL即可。
/*
IIC_Write_Data:向指定的IIC设备写入数据
@addr:7bit的目标IIC设备的地址
@str:要发送的数据的字符串
@len:数据字符串的长度
返回值:
发送成功返回1
失败返回0
*/
int IIC_Write_Data(unsigned char addr,char *str,int len)
{
/*发送起始信号*/
IIC_Send_Start();
/*发送设备地址*/
int ret = IIC_Send_Byte((addr<<1)|0);//bit0表示读取数据 bit1-bit7:IIC设备地址
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;
}
//IIC_Send_Start:发送起始信号
void IIC_Send_Start()
{
/*空闲*/
SCL=1;
SDA=1;
delay(IIC_T);//IIC_T表示一个时钟信号的周期
/*起始信号*/
SDA=0;
delay(IIC_T);
}
/*
IIC_Send_Byte:往II总线上发送一个字节的数据
返回值:
成功返回1
失败返回0
*/
int IIC_Send_Byte(unsigned char ch)
{
/*最先发送最高位MSB,并且是在SCL的下降沿时候发送*/
int i;
for(i=7;i>=0;i--)//8个周期
{
SCL=0;
SDA=(ch>>i)&0x01;//发送了一个bit过去
delay(IIC_T/2);
SCL=1;
delay(IIC_T/2);
}
/*发送方在发送完数据之后要先释放SDA数据*/
SCL=0;//第九个周期开始
SDA=1;//拉高SDA
delay(IIC_T/2);
SCL=1;
delay(IIC_T/2);
if(SDA==1)
{
return 0;//代表无人应答,发送失败
}
else
{
return 1;
}
}
/*
IIC_Recv_Byte:在IIC总线上接收一个字节的数据
返回值:
接收的字节通过返回值返回
*/
int IIC_Recv_Byte()
{
unsigned char ch=0;
int i;
for(i=7;i>=0;i--)
{
SCL=0;
delay(IIC_T/2);
SCL=1;
if(SDA)
{
ch|=1<<i;
}
delay(IIC_T/2);
}
/*接收了前面8个bit之后,回复一个ACK*/
SCL=0;
delay();//延时一段非常短的时间让对方释放SDA
SDA=0;//回复ACK
delay(IIC_T/2);
SCL=1;
delay(IIC_T/2);
return ch;
}
//IIC_Send_Stop:发送停止信号
void IIC_Send_Stop()
{
SCL=1;//SCL保持高电平,SDA由低到高
SDA=0;
delay(IIC_T);
SDA=1;
delay(IIC_T);
}
4 STM32F4xx IIC控制器
STM32F4xx 有三个IIC控制器,有三条IIC总线。
IIC控制器是一个IIC的设备,它负责产生IIC的时许以及协议逻辑。
在STM32F4XX,CPU与IIC是通过系统总线通信。
7bits的主发时序
7bits的主收时序
5 STM32F4xx IIC固件库函数
IIC控制器的SDA和SCL也是通过GPIO口复用而来。
以原理图24C02这个为例: IIC_SCL:PB8 IIC_SDA:PB9
5.1 配置IIC引脚(GPIO控制器)
a.使能时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
b.GPIO初始化,记得要配置为复用模式
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType=GPIO_OType_OD;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
c.复用成什么功能
GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_I2C1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_I2C1);
5.2 初始化IIC的控制器
a.使能时钟IIC1,挂载在APB1总线上
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
b.初始化IIC 控制器
I2C_InitTypeDef结构体介绍
typedef struct
{
uint32_t I2C_ClockSpeed;//指定IIC总线时钟频率 100K-400K 数字越低越稳定,但是速率越慢
uint16_t I2C_Mode;//指定IIC的模式
I2C_Mode_I2C I2C模式 <–选这个
I2C_Mode_SMBusDevice 设备模式
I2C_Mode_SMBusHost 主机模式 uint16_t I2C_DutyCycle;//指定时钟线低电平与高电平的比率
I2C_DutyCycle_16_9 低电平:高电平 = 16:9 <–选这个
I2C_DutyCycle_2 低电平:高电平 = 2:1 uint16_t I2C_OwnAddress1;//指定IIC控制器的地址,一般随便指定,不能跟从设备地址一样
uint16_t I2C_Ack;//在收到IIC总线的数据的时候,是否回复ACK
I2C_Ack_Enable 回复
I2C_Ack_Disable 不回复 uint16_t I2C_AcknowledgedAddress; //指定IIC控制器地址的长度是7bits还是10bits
I2C_AcknowledgedAddress_7bit
I2C_AcknowledgedAddress_10bit
}I2C_InitTypeDef;
b.1 定义结构体变量
I2C_InitTypeDef I2C_InitStruct;
b.2 结构体成员赋值
I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_Mode=I2C_Mode_I2C;
I2C_InitStruct.I2C_Ack=I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_ClockSpeed=400000;
I2C_InitStruct.I2C_DutyCycle=I2C_DutyCycle_16_9;
I2C_InitStruct.I2C_OwnAddress1=0x77;//随便写,当你的CPU为从设备的时候需要用
b.3 初始化IIC
I2C_Init(I2C1,&I2C_InitStruct);
5.3 配置IIC的其他的功能
比如中断(基本用不到),其中有一个函数会用到:
主机自动回复使能
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState)
@I2Cx:IIC控制器的编号
@NewState:
ENABLE :当主机收到一个字节的数据后,会自动发送一个ACK应答
DISABLE:当主机收到一个字节的数据后,不产生ACK
I2C_AcknowledgeConfig(I2C1,ENABLE);
5.4 开启IIC控制器
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState)
@I2Cx:IIC控制器的编号
@NewState:
ENABLE 开启
I2C_Cmd(I2C1,ENABLE);
5.5 IIC总线读写流程
a.发送起始信号
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
I2C_GenerateSTART(I2C1,ENABLE);
b.获取指定的事件
ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT);
@I2Cx:IIC控制器的编号
@I2C_EVENT:指定要获取的事件
EV5:发送起始信号后的应答事件
I2C_EVENT_MASTER_MODE_SELECT
EV6:发送从地址的应答事件
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED 主发模式
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED 主收模式
EV7:主机可以读取数据了
I2C_EVENT_MASTER_BYTE_RECEIVED
EV8-2数据已经发送完成了
I2C_EVENT_MASTER_BYTE_TRANSMITTED
EV8数据正在发送中
I2C_EVENT_MASTER_BYTE_TRANSMITTING
返回值:
ERROR 获取的事件未发生
SUCCESS 获取的事件已经发生
具体要用到哪些事件根据时序图来选择
比如:发送起始信号之后需要等待EV5
I2C_GenerateSTART(I2C1,ENABLE);
while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
c.发送一个7bits的从设备地址
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction)
@I2Cx:IIC控制器的编号
@Address:7bit的设备地址 你在填入参数的时候需要左移一位
@I2C_Direction:读写模式
I2C_Direction_Transmitter 发送数据模式
I2C_Direction_Receiver 接收数据模式
d.发送数据
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data)
@I2Cx:IIC控制器的编号
@Data:要发送的数据 一个字节
e.接收数据
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx)
@I2Cx:IIC控制器的编号
返回值:接收的字节通过返回值返回
f.产生停止信号
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState)
@I2Cx:IIC控制器的编号
@NewState:
ENABLE 使能
g.获取IIC控制器的标志位
FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG)
@I2Cx:IIC控制器的编号
@I2C_FLAG:指定的标志位
I2C_FLAG_BUSY 表示IIC总线是否忙碌
如果被设置,则表示IIC总线忙碌
所以在发送起始信号之前,需要判断总线是否忙碌,如:
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) == SET);//总线不忙碌的时候才可以发送起始信号
I2C_GenerateSTART(I2C1,ENABLE);
h.清除IIC的状态标志
void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
6 AT24C02
在M4的板子上就有一个采用IIC通信方式的EEPROM存储器芯片AT24C04。AT24C02时序图:
EEPROM:是一个容量很小的存储器芯片,一般只有几k的数据,在实际应用中,一般用来存储其他模块的ID,MAC,版本号…
所以我们可以验证一下:看是否能够写入数据到AT24C02,再读出来。
AT24C02详情看其手册,后面的数字就是其容量: 2k
6.1 器件地址(IIC从设备地址)
AT24C02:7bits地址
地址: 1 0 1 0 A2 A1 A0
通过硬件原理图得知A2 A1 A0这三者都接地。所以地址: 1010000
为什么AT24C02不把设备地址写死。为什么还要浪费三个引脚?写死有可能与其他的设备冲突。
6.2 内部存储结构
AT24C02一共有2k bits(256bytes),分成32pages,每一页8bytes.
每一页都有自己的页地址(5bits)
每个字节也有自己的字节地址(3bits)
所以24C02的存储单元的地址:8bits=5bits_pd+3bits_wd
6.3 AT24C02的读写操作
AT24C02的读写操作可以分成写操作时序(写一个字节),页写操作时序(写一页),读操作时序
6.3.1 写操作时序(写一个字节)
写操作在接收完器件地址和给出ACK应答后,还需要接收一个8Bits的字地址。然后再是最后要接收一个字节的数据。
举个栗子:
假设需要往芯片中内部字地址为0x55处写入一个数据0xaa
CPU: START 从设备地址(1010 0000) 字地址(0x55) 数据(0xaa) STOP
24C02: A A A
6.3.2 页写操作时序(写一页–>8bytes)
AT24C02一页只有8个字节,所以一次写操作最多连续写8个字节。
AT24C02在页写的时候,每写入一个字节后,地址会+1。
需要注意的是地址仅仅只是低3bit加1,所以不能写到另外一页)。
例子:向0x00地址写入"12345678"
wd
00000 000 1
00000 001 2
00000 010 3
…
00000 111 8 向0x03地址写入"12345678"
00000 011 1
00000 100 2
00000 101 3
00000 110 4
00000 111 5
00000 000 6 -->超过8个字节后,覆盖前面的数据,从该页页首覆盖
00000 001 7
00000 010 8
问题:
那如果我想在0x05-0x0C上写入0x01 - 0x08该怎么办?
只能分两次写先在0x05的地址上写入3个字节 0x01 0x02 0x03
再去0x08地址上写入5个字节 0x04 0x05 0x06 0x07 0x08
6.3.3 读操作(可以跨页)
a.当前地址读
内部地址计数器保存着上次访问时最后一个地址加1的值。只要芯片有电,该地址就一直保存。当读到最后页的最后字节,地址会回转到0;当写到某页尾的最后一个字节,地址会回转到该页的首字节。接收器件地址(读/写选择位为"1")、EEPROM应答ACK后,当前地址的数据就随时钟送出。主器件无需应答"O",但需发送停止条件(见图)。
b.随机读
随机读需先写一个目标字地址,一旦EEPROM接收器件地址和字地址并应答了ACK,主器件就产生一个重复的起始条件。
然后,主器件发送器件地址(读/写选择位为"1"〉,EEPROM应答ACK,并随时钟送出数据。主器件无需应答"O",但需发送停止条件(见图)。
c.顺序读
顺序读可以通过“当前地址读"或"“随机读"启动。主器件接收到一个数据后,应答ACK。只要EEPROM接收到ACK,将自动增加字地址并继续随时钟发送后面的数据。若达到存储器地址末尾,地址自动回转到0,仍可继续顺序读取数据。
主器件不应答"O",而发送停止条件,即可结束顺序读操作(见图)。
总结:一般在读取之前,需要写一个字地址(伪写),表示从设备的哪里开始读。
如果读之前,不写字节地址,而直接读就会从芯片内部的(word_addr)处开始读。word_addr相当于光标。
CPU: 字地址
START 设备地址(1010000 0/W) word_addr(0x55) START 设备地址(1010000 1/R) A A NA STOP
24C02:
A A A data data … data
#include "stm32f4xx.h"
#include "delay.h"
#include "usart_lib.h"
#include "at24c02.h"
#include <stdio.h>
int fputc(int c,FILE *stream)
{
USART_SendData(USART1,c&0xFF);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
return 0;
}
int main(void)
{
//1.中断优先级进行分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init(168);
usart1_init();
AT24C02_init();
u8 data[10]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A};
Write_byte_to_24c02(0x0F,data,10);
u8 ch[10]={0};
Read_byte_from_24c02(0x0F,ch,10);
while(1)
{
int i;
for(i=0;i<10;i++)
{
printf("ch[%d]:0x%02x\r\n",i,ch[i]);
}
delay_ms(1000);
}
}
#ifndef __AT24C02_H__
#define __AT24C02_H__
#include "stm32f4xx.h"
#include <stdio.h>
#include "delay.h"
void AT24C02_init(void);
int wait_IIC_event(I2C_TypeDef* I2Cx,uint32_t I2C_EVENT,int timeout);
int AT24C02_is_busy(void);
int wait_24c02_finished_to_write(void);
int Write_a_byte_to_24c02(u8 addr,u8 data);
uint8_t Read_a_byte_from_24c02(uint8_t addr);
int Read_byte_from_24c02(uint8_t addr,uint8_t *data,int count);
int Write_byte_to_24c02(uint8_t addr,uint8_t *data,int count);
#endif
#include "at24c02.h"
/*
初始化AT24C02的通信端口
*/
void AT24C02_init()
{
//1.初始化GPIO控制器 --记得两个引脚都要配置为复用开漏输出
//SCL:PB8 SDA:PB9
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType=GPIO_OType_OD;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_I2C1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_I2C1);
//2.初始化IIC控制器
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_Mode=I2C_Mode_I2C;
I2C_InitStruct.I2C_Ack=I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_ClockSpeed=400000;
I2C_InitStruct.I2C_DutyCycle=I2C_DutyCycle_16_9;
I2C_InitStruct.I2C_OwnAddress1=0x77;//随便写,当你的CPU为从设备的时候需要用
I2C_Init(I2C1,&I2C_InitStruct);
//3.首先设置CPU为自动回复ACK
I2C_AcknowledgeConfig(I2C1,ENABLE);
//4.开启IIC
I2C_Cmd(I2C1,ENABLE);
}
/*
wait_IIC_event:等待某个事件发生(有限等待,不能死等)
@I2Cx:IIC控制器的编号
@I2C_EVENT:要等待的是什么事件
@timeout:超时时间,单位是us
返回值:成功返回1,失败返回0
*/
int wait_IIC_event(I2C_TypeDef* I2Cx,uint32_t I2C_EVENT,int timeout)
{
while(I2C_CheckEvent(I2Cx,I2C_EVENT) == ERROR && timeout--)
{
delay_us(1);
}
return (timeout == -1)?0:1;
}
/*
用来判断at24c02是否繁忙
返回值:能通信返回0 不能返回1
*/
int AT24C02_is_busy()
{
int times = 20;//只循环判断20次是否繁忙
while(times--)
{
I2C_GenerateSTART(I2C1,ENABLE);//发送起始信号
//判断EV5
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000) == 0)
{
//等待超时,事件5并未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
continue;
}
//总线此时是不繁忙,发送7bits的设备地址
//主要是检测IIC总线上有没有这个设备以及这个设备是否损坏
I2C_Send7bitAddress(I2C1,0xA0,I2C_Direction_Transmitter);
//判断EV6
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000))
{
//事件6发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
}
//超过20次了
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 1;
}
/*
等待24c02内部写周期完成
返回值:写周期完成返回1 未完成返回0
*/
int wait_24c02_finished_to_write()
{
unsigned char cnt = 20;
while(cnt--)
{
//判断总线是否繁忙
if(AT24C02_is_busy())
{
printf("IIC BUS is busy!\r\n");
continue;
}
I2C_GenerateSTART(I2C1,ENABLE);//发送起始信号
//判断EV5
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000) == 0)
{
//等待超时,事件5并未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
continue;
}
//总线此时是不繁忙,发送7bits的设备地址
I2C_Send7bitAddress(I2C1,0xA0,I2C_Direction_Transmitter);
//判断EV6
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000)==0)
{
//事件6未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
continue;
}
else//应答
{
return 1;//有应答代表写周期完成
}
}
return 0;
}
/*
Write_a_byte_to_24c02:向24c02中写入一个字节数据
@addr:写入数据的起始地址(字节地址:器件内的地址)
@data:要写入的数据
返回值:
成功的话返回1,失败返回-1
时序:
CPU: START 从设备地址(1010 0000) 字地址(0x55) 数据(0xaa) STOP
24c02 A A A
*/
int Write_a_byte_to_24c02(u8 addr,u8 data)
{
//1.等待总线不繁忙
if(AT24C02_is_busy())
{
printf("IIC bus is busy!!\r\n");
return -1;
}
//2.发送起始信号 要等待EV5
I2C_GenerateSTART(I2C1,ENABLE);//发送起始信号
//判断EV5
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000) == 0)
{
//等待超时,事件5并未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return -1;
}
//3.发送从设备地址0xA0(已经左移一位后的器件地址)要等待EV6
I2C_Send7bitAddress(I2C1,0xA0,I2C_Direction_Transmitter);
//判断EV6
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000)==0)
{
//事件6未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return -1;
}
//4.发送字地址要等待EV8-2
I2C_SendData(I2C1,addr);
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000)==0)
{
//事件8-2未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return -1;
}
//5.发送真正数据等待EV8-2
I2C_SendData(I2C1,data);
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000)==0)
{
//事件8-2未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return -1;
}
//6.发送停止信号
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
//7.此时启动应答查询(此时24c02进入写周期)
int num = wait_24c02_finished_to_write();
printf("flag == %d\r\n",num);
return 0;
}
/*
Read_a_byte_from_24c02:用来从AT24C04指定的存储空间中读取数据
@addr:指定要访问的存储单元(字地址)
返回值:接收的字节通过返回值返回
*/
uint8_t Read_a_byte_from_24c02(uint8_t addr)
{
//1.等待总线不繁忙
if(AT24C02_is_busy())
{
printf("IIC bus is busy!!\r\n");
return 0;
}
//2.发送起始信号 要等待EV5
I2C_GenerateSTART(I2C1,ENABLE);//发送起始信号
//判断EV5
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000) == 0)
{
//等待超时,事件5并未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
//3.发送从设备地址(写)要等待EV6
I2C_Send7bitAddress(I2C1,0xA0,I2C_Direction_Transmitter);
//判断EV6
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000)==0)
{
//事件6未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
//4.发送字地址addr要等待EV8-2
I2C_SendData(I2C1,addr);
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000)==0)
{
//事件8-2未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
//上述的1-4属于伪写:只是为了确定读的地址
//5.发送起始信号 要等待EV5
I2C_GenerateSTART(I2C1,ENABLE);//发送起始信号
//判断EV5
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000) == 0)
{
//等待超时,事件5并未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
//6.发送从设备地址(读)要等待EV7
I2C_Send7bitAddress(I2C1,0xA0,I2C_Direction_Receiver);
//判断EV7
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED,1000)==0)
{
//事件7未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
//7.读取数据
unsigned char ch = I2C_ReceiveData(I2C1);
//8.发送停止信号
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return ch;
}
/*
Read_byte_from_24c02:从24c02处接收多个字节的数据
@addr:字地址
@data:用来保存你接收到的数据
@count:你想要接收多少个字节
返回值:失败返回0
成功返回实际上接收到的字节数
*/
int Read_byte_from_24c02(uint8_t addr,uint8_t *data,int count)
{
//判断count是否合法
//比如:从地址0xFF读取2(count)个字节,count就是非法的
count = count <(256-addr)?count:(256-addr);
printf("count :%d\r\n",count);
//1.等待总线不繁忙
if(AT24C02_is_busy())
{
printf("IIC bus is busy!!\r\n");
return 0;
}
//2.发送起始信号 要等待EV5
I2C_GenerateSTART(I2C1,ENABLE);//发送起始信号
//判断EV5
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000) == 0)
{
//等待超时,事件5并未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
//3.发送从设备地址(写)要等待EV6
I2C_Send7bitAddress(I2C1,0xA0,I2C_Direction_Transmitter);
//判断EV6
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000)==0)
{
//事件6未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
//4.发送字地址addr要等待EV8-2
I2C_SendData(I2C1,addr);
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000)==0)
{
//事件8-2未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
//上述的1-4属于伪写:只是为了确定读的地址
//5.发送起始信号 要等待EV5
I2C_GenerateSTART(I2C1,ENABLE);//发送起始信号
//判断EV5
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000) == 0)
{
//等待超时,事件5并未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
//6.发送从设备地址(读)要等待EV7
I2C_Send7bitAddress(I2C1,0xA0,I2C_Direction_Receiver);
//7.读取数据
int i;
//先读取count-1个字节,因为最后一个的处理不同(最后一个字节不回复)
for(i=0;i<count-1;i++)
{
//判断EV7
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED,1000)==0)
{
//事件7未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
data[i]=I2C_ReceiveData(I2C1);
}
I2C_AcknowledgeConfig(I2C1,DISABLE);//关闭自动回复
//判断EV7
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED,1000)==0)
{
//事件7未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
data[i++]=I2C_ReceiveData(I2C1);
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
I2C_AcknowledgeConfig(I2C1,ENABLE);
return i;
}
/*
Write_byte_to_24c02:向24c02中写入数据
@addr:写入数据的起始地址
@data:要写入的数据
@count:要写入的字节数
返回值:
成功的话返回实际写入的字节数,失败返回0
*/
int Write_byte_to_24c02(uint8_t addr,uint8_t *data,int count)
{
//记录当前已经写入的字节数
int bytes=0;
//判断count是否合法
count = count <(256-addr)?count:(256-addr);
printf("count :%d\r\n",count);
page_write:
//1.等待总线不繁忙
if(AT24C02_is_busy())
{
printf("IIC bus is busy!!\r\n");
return -1;
}
//2.发送起始信号 要等待EV5
I2C_GenerateSTART(I2C1,ENABLE);//发送起始信号
//判断EV5
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000) == 0)
{
//等待超时,事件5并未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return -1;
}
//3.发送从设备地址0xA0(已经左移一位后的器件地址)要等待EV6
I2C_Send7bitAddress(I2C1,0xA0,I2C_Direction_Transmitter);
//判断EV6
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000)==0)
{
//事件6未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return -1;
}
//4.发送字地址要等待EV8-2
I2C_SendData(I2C1,addr);
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000)==0)
{
//事件8-2未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return -1;
}
int i;
//发送数据
//计算出当前写入的位置距离本页的末尾还剩多少个字节
//8-页内地址(addr的低3bit)
int page_bytes = 8-(addr&0x07);
//开始写入当前页
for(i=0;i<page_bytes&&bytes<count;i++)
{
I2C_SendData(I2C1,data[bytes]);
if(wait_IIC_event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000)==0)
{
//事件8-2未发生
I2C_GenerateSTOP(I2C1,ENABLE);//发送停止信号
return 0;
}
bytes++;
}
//发送停止信号
I2C_GenerateSTOP(I2C1,ENABLE);
if(bytes<count)
{
//本页没有写完,此时需要跨页(addr的高5位要加1)
addr=((addr>>3)+1)<<3;
int num = wait_24c02_finished_to_write();//等待C02的写周期完成
printf("flag == %d\r\n",num);
goto page_write;
}
int num = wait_24c02_finished_to_write();//等待C02的写周期完成
printf("flag == %d\r\n",num);
return bytes;
}