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:是写的数据
image-20230331130148321

发送完一个字节(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=0delay(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是通过系统总线通信。

STM32F407中文参考手册

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;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个基于Arduino的iic读取AT24C02的示例代码: ``` #include <Wire.h> #define EEPROM_ADDR 0x50 // EEPROM地址 #define EEPROM_SIZE 256 // EEPROM容量 void setup() { Serial.begin(9600); Wire.begin(); // 初始化I2C总线 } void loop() { // 读取整个EEPROM byte data[EEPROM_SIZE]; readEEPROM(0, data, EEPROM_SIZE); // 输出EEPROM数据 for (int i = 0; i < EEPROM_SIZE; i++) { Serial.print(data[i], HEX); Serial.print(" "); if ((i+1) % 16 == 0) { Serial.println(); } } Serial.println(); delay(5000); } // 从EEPROM中读取数据 void readEEPROM(int addr, byte* data, int len) { Wire.beginTransmission(EEPROM_ADDR); Wire.write((byte)(addr >> 8)); // 地址高位 Wire.write((byte)(addr & 0xFF)); // 地址低位 Wire.endTransmission(); Wire.requestFrom(EEPROM_ADDR, len); for (int i = 0; i < len && Wire.available(); i++) { data[i] = Wire.read(); } } ``` 在上面的示例代码中,我们定义了EEPROM的地址为0x50,容量为256字节。在`setup()`函数中,我们初始化了I2C总线,然后在`loop()`函数中,我们读取整个EEPROM的数据,并输出到串口。在`readEEPROM()`函数中,我们先通过I2C总线发送地址和要读取的起始地址,然后通过`Wire.requestFrom()`函数请求读取数据。最后,我们通过`Wire.read()`函数读取数据并保存到`data`数组中。 要使用此代码,您需要将AT24C02连接到Arduino的I2C总线上,并将A0、A1和A2引脚连接到GND或VCC以设置EEPROM的I2C地址。最后,您可以使用Arduino IDE的串口监视器查看EEPROM的内容。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

QJ敬

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

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

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

打赏作者

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

抵扣说明:

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

余额充值