STM32F407 芯片的学习 day07 iic通信方式 主芯片 与 at24c02之间的通讯

IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送, 高速 IIC 总线一般可达 400kbps 以上。

I2C 通讯协议(Inter-Integrated Circuit),I2C是一种通信协议!!和USART串口和SPI、SDIO等等都是通信协议。由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、 CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
类似的,学习通信协议通常分为物理层和协议层来学习:



一.物理层
1.I2C常用的连接方式,百试不厌

特点主要有:
(1)支持多设备的总线
(2)一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3)每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
(4)总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
注意::I2C协议中,0的优先级高于3.3v,当不发送数据时都为高阻态。想发送逻辑1时,先查看是否为总线是否为3.3v,若为3.3v则发送,若为0v,表面有其他设备在发送逻辑0。
(5)标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式
下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。


二.协议层
I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。
1.基本读写过程


 2.起始和停止信号
当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。

 


在i2c工作中,有效数据即SDA只允许在SCLK低电平期间变化,在SCLK高电平期间不允许改变。所以,对于两类特殊的命令信号都是在SCLK在高电平期间SDA发生改变。


起始位如下图:在SCLK高电平期间SDA由高到低变化将被理解为起始位。

i2c起始位

 

停止位如下图:在SCLK高电平期间SDA由低到高变化将被理解为起始位。

i2c停止位


 3.IIC  模式的选择  (我们用的 主要是  主发送器 )

该接口在工作时可选用以下四种模式之一:
1.从发送器
2.从接收器
3.主发送器
4.主接收器
默认情况下,它以从模式工作。接口在生成起始位后会自动由从模式切换为主模式,并在出
现仲裁丢失或生成停止位时从主模式切换为从模式,从而实现多主模式功能。

在主模式下, I2C 接口会启动数据传输并生成时钟信号。串行数据传输始终是在出现起始位时开始,在出现停止位时结束。起始位和停止位均在主模式下由软件生成。


在从模式下,该接口能够识别其自身地址( 7 或 10 位)以及广播呼叫地址。广播呼叫地址检测可由软件使能或禁止。


通信数据格式为数据和地址均以 8 位字节传输, MSB 在前。起始位后紧随地址字节(7 位地址占据一个字节;10 位地址占据两个字节)。地址始终在主模式下传送。
在字节传输 8 个时钟周期后是第 9 个时钟脉冲,在此期间接收器必须向发送器发送一个应答位

总结一下就是:在通信时在主设备发送起始信号过后,串行数据开始传输,在第九个时钟时从设备要向主设备发送一个应答位。在串行数据传输时均是MSB在前。
 


4.通信流程
在主模式下, I 2 C 接口会启动数据传输并生成时钟信号。串行数据传输始终是在出现起始位
时开始,在出现停止位时结束。起始位和停止位均在主模式下由软件生成。
在从模式下,该接口能够识别其自身地址( 7 10 位)以及广播呼叫地址。广播呼叫地址
检测可由软件使能或禁止。
数据和地址均以 8 位字节传输 MSB 在前。起始位后紧随地址字节( 7 位地址占据一个字节;
10 位地址占据两个字节)。地址始终在主模式下传送。
在字节传输 8 个时钟周期后是第 9 个时钟脉冲,在此期间接收器必须向发送器发送一个应答

对于I2C地址很多人都分不清,i2c的地址组成一般是7+1,其中最后1位为读写位(一般0为写、1为读),注意这里7位很重要,一般数据手册中给出的都是这7位地址,换句话说也就是7前7位表示8位的一个数值。

应答(ACK)”和“非应答(NACK)”两种信号。
作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。

 

 

看到上图我们很好理解想要发送数据一般步骤为:

总结; (不管事件 中断 EVx  )

1.发送 起始位的信号 和 应答信号(应答信号i:希望对方给我发消息)

2.等待回应  

3.8位数据的 第一位  和 应答信号(应答信号i:希望对方给我发消息)

4.等待回应  

5.8位数据的 第二位  和 应答信号(应答信号i:希望对方给我发消息)

6.等待回应  

7.8位数据的 第三位  和 应答信号(应答信号i:希望对方给我发消息)

8.等待回应  

9.8位数据的 第四位  和 应答信号(应答信号i:希望对方给我发消息)

10.等待回应  

11.8位数据的 第五位  和 应答信号(应答信号i:希望对方给我发消息)

12.等待回应  

13.8位数据的 第六位  和 应答信号(应答信号i:希望对方给我发消息)

14.等待回应  

15.8位数据的 第七位  和 应答信号(应答信号i:希望对方给我发消息)

16.等待回应  

17.8位数据的 第八位  和 应答信号(应答信号i:希望对方给我发消息)

18.等待回应  

19.发送停止位


5.代码: IIC  

IIC.h

#ifndef __IIC_H
#define __IIC_H

#include "stm32f4xx.h"

#include "sys.h"
#include "delay.h"

#define SDA_OUT {GPIOB->MODER &= ~(3<<(7*2));GPIOB->MODER |= 1<<(7*2);}//先清零,后赋值  PB7引脚配为输出模式
#define SDA_IN {GPIOB->MODER &= ~(3<<(7*2));GPIOB->MODER |= 0<<(7*2);}//先清零,后赋值   PB7引脚配为输入模式

#define IIC_SCL  PBout(6)  //IIC的时钟线的输出数据寄存器
#define IIC_SDA  PBout(7)  //IIC的数据线的输出数据寄存器
#define READ_SDA  PBin(7)  //IIC的数据线的输入数据寄存器

void IIC_init(void);//IIC初始化

//起始信号
void IIC_start(void);

//停止信号
void IIC_stop(void);

//发送一个字节数据
void IIC_Send_Byte(u8 txd);

//接收一个字节数据
u8 IIC_READ_Byte(u8 ack);  

//主机向从机发送低电平应答信号
void IIC_ACK(void);

//主机向从机发送高电平非应答信号
void IIC_NACK(void);

//主机等待从机的应答信号
u8 IIC_wait_ack(void);

#endif

IIC.c

#include "iic.h"


//IIC引脚初始化。
void IIC_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	//时钟初始化
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
	
	//引脚初始化
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;//开漏输出
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;//上拉电阻
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;//输出速度
	GPIO_Init(GPIOB,&GPIO_InitStruct);
}

//起始信号
void IIC_start(void)
{
	//1.数据线切换成输出模式
	SDA_OUT;
	
	//2.总线切回空闲状态
	IIC_SDA = 1;
	IIC_SCL = 1;
	delay_us(2);
	
	//3.时钟线为高电平期间,数据线由高到低跳变。
	IIC_SDA = 0;
	delay_us(2);
	
	//4.时钟线为低电平,数据线的数据允许变化
	IIC_SCL = 0;
}

//停止信号
void IIC_stop(void)
{
	//1.数据线切换成输出模式
	SDA_OUT;
	
	//2.数据线切换成低电平
	IIC_SCL = 0;
	IIC_SDA = 0;
	delay_us(2);
	
	//3.时钟线为高电平期间,数据线由低到高的跳变
	IIC_SCL = 1;
	delay_us(2);
	IIC_SDA = 1;
}

//发送一个字节数据
void IIC_Send_Byte(u8 txd)
{
	u8 i;
	//1.数据线切换成输出模式
	SDA_OUT;
	
		//2.时钟线为低电平,数据线允许改变
	IIC_SCL = 0;
	for(i = 0;i < 8 ;i++)
	{
		//IIC_SDA = (txd & 0x80) >> 7;//数据从第八位开始发送。  
		IIC_SDA = txd >> 7;//数据从第八位开始发送。
		txd <<= 1;
		
		//时钟线为高电平期间 数据保持稳定。
		IIC_SCL = 1;
		delay_us(2);
		
		IIC_SCL = 0;//时钟线为低电平,数据线允许改变
		delay_us(2);
	}
}

//接收一个字节数据,当ack= 1,发送应答信号,ack= 0,发送非应答信号
u8 IIC_READ_Byte(u8 ack)
{
	u8 i,receive = 0;
	//1.数据线切成输入模式
	SDA_IN;
	
	for(i = 0;i < 8 ;i++)
	{
		IIC_SCL = 0;//时钟线为低电平,数据线允许改变
		delay_us(2);
		
		receive<<=1;
		if(READ_SDA) receive++;
		
		//时钟线为高电平期间 数据保持稳定。
		IIC_SCL = 1;
		delay_us(2);
	}
	
	if(ack) IIC_ACK();
	else IIC_NACK();
	
	return receive;
}

//主机向从机发送低电平应答信号
void IIC_ACK(void)
{
		//1.数据线切换成输出模式
	SDA_OUT;
	
	IIC_SCL = 0;//时钟线为低电平,数据线允许改变
	delay_us(2);
	
	IIC_SDA = 0;//低电平应答信号
	
	//时钟线为高电平期间 数据保持稳定。
	IIC_SCL = 1;
	delay_us(2);
	
	IIC_SCL = 0;//时钟线为低电平,数据线允许改变
}

//主机向从机发送高电平非应答信号
void IIC_NACK(void)
{
			//1.数据线切换成输出模式
	SDA_OUT;
	
	IIC_SCL = 0;//时钟线为低电平,数据线允许改变
	delay_us(2);
	
	IIC_SDA = 1;//高电平非应答信号
	
	//时钟线为高电平期间 数据保持稳定。
	IIC_SCL = 1;
	delay_us(2);
	
}

//主机等待从机的低电平应答信号
u8 IIC_wait_ack(void)
{
	u8 waittime = 0;
	//1.数据线切成输入模式
	SDA_IN;

	//2.总线切回空闲状态
	IIC_SDA = 1;delay_us(2);
	IIC_SCL = 1;
	delay_us(2);

	/*	
	IIC_SCL = 0;
	delay_us(2);
		*/
	//3.等待从机应答。
	while(READ_SDA)
	{
		waittime++;
		if(waittime > 250)
		{
			IIC_stop();
			return 1;//超时读取应答失败
		}
	}
	IIC_SCL = 0;//时钟线为低电平,数据线允许改变
	return 0;//读取到从机应答。
}

 


关于芯片 :  at24c02

原理图:

在这里插入图片描述

代码:

at24cxx.h

#ifndef __24CXX_H
#define __24CXX_H


#include "sys.h"

#define 	AT24C01			127
#define 	AT24C02			255
#define 	AT24C04			511
#define 	AT24C08			1023
#define 	AT24C16			2047
#define 	AT24C32			4095
#define 	AT24C64	    8191
#define 	AT24C128		16383
#define 	AT24C256		32767  


//蜘蛛侠开发板使用的是24c02,所以定义EE_TYPE为AT24C02
#define EE_TYPE AT24C02
					  
u8 AT24CXX_ReadOneByte(u16 ReadAddr);							//指定地址读取一个字节
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite);		//指定地址写入一个字节
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len);//指定地址开始写入指定长度的数据
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len);					//指定地址开始读取指定长度数据
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite);	//从指定地址开始写入指定长度的数据
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead);   	//从指定地址开始读出指定长度的数据

u8 AT24CXX_Check(void);  //检查器件
void AT24CXX_Init(void); //初始化IIC

void AT24CXXTest(void);
#endif

at24cxx.c


#include "sys.h" 		
#include "at24cxx.h" 
#include "iic.h"
#include "delay.h"
#include <stdio.h>
/************************************************************
函数名:AT24CXX_Init
功能: 初始化 AT24CXX GPIO 为输出工作模式
输入参数:无
输出参数:无
返回值:无

备注: AT24CXX_PIN --- PIN6|PIN7

************************************************************/

void AT24CXX_Init(void)
{
	IIC_init();
}

/************************************************************
函数名:AT24CXX_ReadOneByte
功能: 在AT24CXX指定地址读出一个数据
输入参数:ReadAddr
输出参数:无
返回值:读到的数据

备注: 

************************************************************/
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{				  
	  u8 temp=0;		  	    																 
    IIC_start();  
	if(EE_TYPE>AT24C16)
	{
		IIC_Send_Byte(0XA0);	   //发送写命令
		IIC_wait_ack();
		IIC_Send_Byte(ReadAddr>>8);//发送高地址	    
	}
	else 
	{
		IIC_Send_Byte(0XA0+((ReadAddr/256)<<1));   //发送器件地址0XA0,写数据
	}		
	
	IIC_wait_ack(); 
	
  IIC_Send_Byte(ReadAddr%256);   //发送低地址
	IIC_wait_ack();	   
	
	IIC_start();  	 	   
	IIC_Send_Byte(0XA1);           //进入接收模式			
	IIC_wait_ack();	 
	
  temp=IIC_READ_Byte(0);		   
  IIC_stop();//产生一个停止条件	 
	
	return temp;
}

/************************************************************
函数名:AT24CXX_WriteOneByte
功能: 在AT24CXX指定地址写入一个数据
输入参数:WriteAddr,DataToWrite
输出参数:无
返回值:

备注: 

************************************************************/
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{				   	  	    																 
    IIC_start();  
	if(EE_TYPE>AT24C16)
	{
		IIC_Send_Byte(0XA0);	    //发送写命令
		IIC_wait_ack();
		IIC_Send_Byte(WriteAddr>>8);//发送高地址	  
	}
	else 
	{
		IIC_Send_Byte(0XA0+((WriteAddr/256)<<1));   //发送器件地址0XA0,写数据 
	}		
	
	IIC_wait_ack();	   
	
  IIC_Send_Byte(WriteAddr%256);   //发送低地址
	IIC_wait_ack(); 	 
	
	IIC_Send_Byte(DataToWrite);     //发送字节							   
	IIC_wait_ack();  	
	
  IIC_stop();//产生一个停止条件 
	delay_ms(10);	 
}

/************************************************************
函数名:AT24CXX_WriteLenByte
功能: 在AT24CXX里面的指定地址开始写入长度为Len的数据
输入参数:WriteAddr,DataToWrite,Len
输出参数:无
返回值:无

备注: 

************************************************************/
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
{  	
	u8 t;
	for(t=0;t<Len;t++)
	{
		AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
	}												    
}

/************************************************************
函数名:AT24CXX_ReadLenByte
功能: 在AT24CXX里面的指定地址开始读出长度为Len的数据
输入参数:ReadAddr,Len
输出参数:无
返回值:读到的数据

备注: 

************************************************************/
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
{  	
	u8 t;
	u32 temp=0;
	for(t=0;t<Len;t++)
	{
		temp<<=8;
		temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); 	 				   
	}
	return temp;												    
}

/************************************************************
函数名:AT24CXX_Check
功能: 检查AT24CXX是否正常
输入参数:无
输出参数:无
返回值:1:检测失败
        0:检测成功

备注: 这里用了24XX的最后一个地址(255)来存储标志字.
       如果用其他24C系列,这个地址要修改

************************************************************/
u8 AT24CXX_Check(void)
{
	u8 temp;
	temp=AT24CXX_ReadOneByte(32767);//避免每次开机都写AT24CXX			   
	if(temp==0X55)return 0;		   
	else//排除第一次初始化的情况
	{
		AT24CXX_WriteOneByte(32767,0X55);
		temp=AT24CXX_ReadOneByte(32767);	  
		if(temp==0X55)return 0;
	}
	return 1;											  
}
/************************************************************
函数名:AT24CXX_Read
功能: 在AT24CXX里面的指定地址开始读出指定个数的数据
输入参数:ReadAddr,*pBuffer,NumToRead
输出参数:无
返回值:无

备注: 

************************************************************/

void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
	while(NumToRead)
	{
		*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);	
		NumToRead--;
	}
}  

/************************************************************
函数名:AT24CXX_Write
功能: 在AT24CXX里面的指定地址开始写入指定个数的数据
输入参数:WriteAddr,*pBuffer,NumToWrite
输出参数:无
返回值:无

备注: 

************************************************************/
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
	while(NumToWrite--)
	{
		AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
		WriteAddr++;
		pBuffer++;
	}
}


/*
 测试 AT24C02  蜘蛛侠开发板采用AT24C02  0 ~ 255字节
*/

void AT24CXXTest(void)
{
		uint16_t i = 0;
		uint8_t temp = 0;
	
		for(i = 0;i <255;i++)
		{
			AT24CXX_WriteOneByte(i,0x33);  
		}
		
		for(i = 0;i <255;i++) //1111 1111
		{
			temp = AT24CXX_ReadOneByte(i);
			
			 printf("temp: %x\r\n", temp);
		}
	
}


 

 

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值