STM32——IIC、EEPROM

I2C物理层
I2C通信设备常用的连接方式如图
在这里插入图片描述
特点:
(1)它是一个支持多设备的总线。
(2)I2C 总线只使用两条总线线路,SDA、SCL。
(3)每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
(4)总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
(5)多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
(6)具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚
不支持高速模式。
(7)连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。
I2C协议层
I2C 的协议定义了通信的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。
(1)数据有效性规定
在这里插入图片描述
每次数据传输都以字节为单位,每次传输的字节数不受限制。
(2)起始和停止信号
在这里插入图片描述
(3)应答响应
在这里插入图片描述
每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
由于某种原因从机不对主机寻址信号应答时(如从机正在进行实时性的处理工作而无法接收总线上的数据),它必须将数据线置于高电平,而由主机产生一个终止信号以结束总线的数据传送。
如果从机对主机进行了应答,但在数据传送一段时间后无法继续接收更多的数据时,从机可以通过对无法接收的第一个数据字节的“非应答”通知主机,主机则应发出终止信号以结束数据的继续传送。
当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号是由对从机的“非应答”来实现的。然后,从机释放SDA线,以允许主机产生终止信号。
(4)总线的寻址方式
I2C总线寻址按照从机地址位数可分为两种,一种是7位,另一种是10位。采用7位的寻址字节(寻址字节是起始信号后的第一个字节)的位定义如下:
在这里插入图片描述
10位寻址和7 位寻址兼容,而且可以结合使用。
当主机发送了一个地址后,总线上的每个器件都将头7 位与它自己的地址比较,如果一样,器件会判定它被主机寻址,其他地址不同的器件将被忽略后面的数据信号。至于是从机接收器还是从机发送器,都由R/W 位决定的。从机的地址由固定部分和可编程部分组成。在一个系统中可能希望接入多个相同的从机,从机地址中可编程部分决定了可接入总线该类器件的最大数目。如一个从机的7位寻址位有4位是固定位,3位是可编程位,这时仅能寻址8个同样的器件,即可以有8个同样的器件接入到该I2C总线系统中。
(5)数据传输
I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/W),用“0”表示主机发送(写)数据(W),“1”表示主机接收数据(R)。每次数据传送总是由主机产生的终止信
号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
在总线的一次数据传送过程中,可以有以下几种组合方式:
a、主机向从机发送数据,数据传送方向在整个传送过程中不变
在这里插入图片描述
b、主机在第一个字节后,立即从从机读数据
在这里插入图片描述
c、在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好反相
这里省略了一次主机的停止位是为了防止停止的时候被其他主机抢占总线而导致,该主机无法继续使用总线进行通信。
在这里插入图片描述
EEPR0M(AT24C02)介绍
AT24C01/02/04/08/16…是一个1K/2K/4K/8K/16K位串行CMOS,内部含有128/256/512/1024/2048个8位字节,AT24C01有一个8字节页写缓冲器,AT24C02/04/08/16有一个16字节页写缓冲器。该器件通过I2C总线接口进行操作,它有一个专门的写保护功能。
在这里插入图片描述
在这里插入图片描述
AT24C02器件地址为7位,高4位固定为1010,低3位由 A0/A1/A2信号线的电平决定。因为传输地址或数据是以字节为单位传送的,当传送地址时,器件地址占7位,还有最后一位(最低位R/W)用来选择读写方向,它与地址无关。
在这里插入图片描述
在这里插入图片描述
I2C总线时序
在这里插入图片描述
在这里插入图片描述
首先检测AT24C02芯片是否存在,如果存在则输出提示信息,然后通过按键K_UP和K_DOWN控制AT24C02数据读写,并输出写入和读取的数据信息,最后让D1指示灯闪烁提示系统正常运行。
IIC.h

#ifndef __IIC_H
#define __IIC_H

#include "system.h"

#define IIC_SCL  PBout(8)
#define IIC_SDA  PBout(9)
#define READ_SDA  PBin(9)

void IIC_Init(void);
void SDA_OUT(void);
void SDA_IN(void);
void IIC_Start(void);
void IIC_Stop(void);
u8 IIC_Wait_Ack(void);
void IIC_Ack(void);
void IIC_Nack(void);
void IIC_Send_Byte(u8 txd);
u8 IIC_Read_Byte(u8 ack);

#endif

IIC.c

#include "iic.h"
#include "sysTick.h"

void IIC_Init(void)
{
		GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量

		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); //使能端口时钟
	
		GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT; //输出模式
		GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9;//管脚设置
		GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;  //速度设置为100MHz
	  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
		GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化结构体
	
		IIC_SCL = 1;	//默认将SCL和SDA设置成高电平
    IIC_SDA = 1;

		
}

void SDA_OUT(void)
{
		GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
		GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT; //输出模式
		GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//管脚设置
		GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;  //速度设置为100MHz
	  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
		GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化结构体
}

void SDA_IN(void)
{
		GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
		GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN; //输入模式
		GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//管脚设置
		GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
		GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化结构体
}

//起始信号
void IIC_Start(void)
{
		SDA_OUT();
		IIC_SDA = 1;
		IIC_SCL = 1;
	  myDelay_us(5);  //大于4.7us
	  IIC_SDA = 0;
    myDelay_us(6);  //大于4us
	  IIC_SCL = 0;
}

//终止信号
void IIC_Stop(void)
{
    SDA_OUT();
	  IIC_SCL = 0;
		IIC_SDA = 0;
		IIC_SCL = 1;
	  myDelay_us(6);  //大于4us
	  IIC_SDA = 1;
    myDelay_us(6);  //大于4.7us
}

//等待应答 返回0表示应答成功
u8 IIC_Wait_Ack(void)
{
	  u8 tempTime = 0;
		SDA_IN();
		IIC_SDA = 1;    
		myDelay_us(1);
	  IIC_SCL = 1;
	  myDelay_us(1);
	  while(READ_SDA){    //非应答
			tempTime++;
			if(tempTime>250){
					 IIC_Stop();
				   return 1;
			}
			
		}
		IIC_SCL = 0;
		return 0;
}


//应答
void IIC_Ack(void)
{
		
	  IIC_SCL = 0;       
	  SDA_OUT();
	  IIC_SDA = 0;
	  myDelay_us(2);  
		IIC_SCL = 1;
	  myDelay_us(5);
	  IIC_SCL = 0;
}

//非应答
void IIC_Nack(void)
{
   	IIC_SCL = 0;
	  SDA_OUT();
	  IIC_SDA = 1;
	  myDelay_us(2);
	  IIC_SCL = 1;
	  myDelay_us(5);
	  IIC_SCL = 0;
}


//发送数据
void IIC_Send_Byte(u8 txd)
{
	  u8 t;
		SDA_OUT();
		IIC_SCL = 0;         //SCL为低电平时允许数据变化,所以可以写如数据
	  for(t=0;t<8;t++){    //8位传送
			if((txd&0x80)>0){     //从最高位开始发送
			    IIC_SDA = 1;
			}else{
			    IIC_SDA = 0;
			}
			txd <<= 1;
			myDelay_us(2);
			IIC_SCL = 1;
			myDelay_us(2);
			IIC_SCL = 0;
			myDelay_us(2);
		}

}

//读取数据
u8 IIC_Read_Byte(u8 ack)
{
	    u8 i;
	    u8 receive = 0;
			SDA_IN();

			for(i = 0;i<8;i++){
					IIC_SCL = 0;    
                  myDelay_us(2);
				  IIC_SCL = 1;    //SCL为高电平时数据不允许变化,此时可以读取数据
				  receive<<=1;
				  if(READ_SDA){
							receive++;
					}
					myDelay_us(1);
			}
			if(!ack){
					IIC_Nack();
			}else{
					IIC_Ack();
			}
			return  receive;
}

AT24Cxx.h

#ifndef __24Cxx_H
#define __24Cxx_H

#include "system.h"

#define  AT24C01  127    //为了兼容定义所有AT24Cxx地址
#define  AT24C02  255
#define  AT24C04  511
#define  AT24C08  1023
#define  AT24C16  2047
#define  AT24C32  4095
#define  AT24C64  8191
#define  AT24C128 16383
#define  AT24C256  32767

#define  EE_TYPE   AT24C02  //用于判断AT24C02


void AT24CXX_Init(void);
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite);
u8 w(u16 ReadAddr);
u8  AT24CXX_Check(void);


#endif


AT24Cxx.c

#include "24cxx.h"
#include "iic.h"
#include "SysTick.h"

void AT24CXX_Init(void)
{
			IIC_Init();                       //由于at24c02的SCL 和SDA与IIC中一致,所以直接调用IIC_Init就可以
}

void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
		IIC_Start();                        //主机发送起始信号
		if(EE_TYPE > AT24C16){              //器件地址24c16之后需要先发高地址再发低地址
				IIC_Send_Byte(0xA0);            //发送地址
				IIC_Wait_Ack();                 //等待应答
		    IIC_Send_Byte(WriteAddr>>8);    //发送高地址
		}else{
			  IIC_Send_Byte(0xA0+((WriteAddr/256)<<1));  //24c16以下超过24c02(8位)的器件地址,对其扩展
		
		}
		IIC_Wait_Ack();                     //等待应答
		IIC_Send_Byte(WriteAddr%256);       //发送低地址
		IIC_Wait_Ack();
		IIC_Send_Byte(DataToWrite);         //发送数据
		IIC_Wait_Ack();
		IIC_Stop();
		myDelay_ms(10);
}

u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
	  u8 temp = 0;
		IIC_Start();                        //主机发送起始信号
		if(EE_TYPE > AT24C16){              //器件地址24c16之后需要先发高地址再发低地址
				IIC_Send_Byte(0xA0);            //发送地址
				IIC_Wait_Ack();                 //等待应答
		    IIC_Send_Byte(ReadAddr>>8);    //发送高地址
		}else{
			  IIC_Send_Byte(0xA0+((ReadAddr/256)<<1));  //24c16以下超过24c02(8位)的器件地址,对其扩展
		
		}
		IIC_Wait_Ack();                     //等待应答
		IIC_Send_Byte(ReadAddr%256);       //发送低地址
		IIC_Wait_Ack();
		
		IIC_Start();          //发送起始信号之后改为读取操作0XA1
		IIC_Send_Byte(0xA1);  //发送器件地址
		IIC_Wait_Ack(); 
		temp = IIC_Read_Byte(0);
		IIC_Stop();
		
		return temp;
}

//判断AT24Cxx器件
u8  AT24CXX_Check(void)
{
		u8 temp;
	  temp = AT24CXX_ReadOneByte(255);   //地址
	  if(temp == 0x36){    //随便输入数值,刚开始里面没有数值
				return 0;
		}else{
		    AT24CXX_WriteOneByte(255,0x36);  //写入数值0x36
			  temp = AT24CXX_ReadOneByte(255); //读取数值
			  if(temp == 0x36){                //判断读出的数值等不等于写入的数值。
				    return 0;
				}
			  
		}
		return 1;   
}


main.c

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "key.h"
#include "dma.h"
#include "rtc.h"
#include "iic.h"
#include "24cxx.h"


int main()
{	
	u8 i=0;
	u8 key;
	u8 k = 0;
	SysTick_Init(168);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组 分2组
	LED_Init();
	USART1_Init(9600);
	KEY_Init();
	adcx_Init();
	TIM11_CH1_PWM_Init(255,0); //APB2总线,最大时钟168M,168/256 = 656.25KHz
	AT24CXX_Init();
	
	while(AT24CXX_Check()){        //判断AT24Cxx型号
		printf("AT24C02检测不正常!\r\n");
		myDelay_ms(500);
	}
	printf("AT24C02检测正常!\r\n");
	
	while(1)
	{
	  key = KEY_Scan(0);              //按键扫描
		if(key == KEY_UP){
			k++;
			if(k>255){
			  k=255;
			}
			AT24CXX_WriteOneByte(0,k);   //写入数据(起始地址,数据)
			printf("写入数据是%d\r\n",k);
		}
		if(key == KEY_DOWN){
			k = AT24CXX_ReadOneByte(0);   //从0地址开始读取
		  printf("读取的数据为%d\r\n",k);
		}
		i++;
		
		if(i%20==0){

			led1=!led1;
		}
		
		
		myDelay_ms(10);
	}
}


IIC通信过程:
1.主机发送起始信号启用总线
2.主机发送一个字节数据指明从机地址和后续字节的传送方向. (确定主机和谁(前7位地址)通信,以及主机通信的方向(第8位))
3.被寻址的从机发送应答信号回应主机。
4.发送器发送一个字节数据。
5.接收器发送应答信号回应发送器。
。。。。(循环步骤4,5)。
n.通信完成后主机发送停止信号释放总线。

起始信号和停止信号:
SCL为高电平时,SDA由高变低表示起始信号。
SCL为高电平时,SDA由低变高表示终止信号。
起始信号和停止信号都是有主机发出,起始信号产生后总线处于占用状态,停止信号产生后总线处于空闲状态。

同步信号:
IIC总线在进行数据传送时,时钟线SCL为低电平期间发送器相数据线SDA发送一位数据,在此期间,数据线上的信号允许发生变化,时钟线SCL为高电平期间,接收器从数据线上读取一位数据,在此期间数据线上的信号不允许发生改变,必须保持稳定。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值