目录
一、IIC
1、简介
IC总线是一种串行通信协议,也称为“I²C”或“TWI(Two Wire Interface)”。IIC总线由Philips Semiconductors(飞利浦半导体)公司于1980年推出,用于连接集成电路芯片及其它数字电路。它采用两根线串行传输数据(一根双向的数据线SDA和一根双向的时钟线SCL),可以连接多个设备,具有高效、简单、灵活的优点。
在IIC总线中,每一个设备都有一个唯一的地址,数据的传输是由主设备发起的,从设备进行响应。主设备可以向从设备发送读取或写入命令,也可以向多个设备广播命令,读取或写入多个设备的数据。另外,IIC总线还支持多主设备的操作,可以实现多个主设备对同一总线进行控制。总之,IIC总线是一种常用的通信协议,可以满足不同类型的集成电路芯片之间的数据传输需求。
2、IIC总线结构
3、总线协议
3.1、起始和停止条件
起始条件:SCL在高电平期间,SDA由高电平变为低电平。
停止条件:SCL在高电平期间,SDA由低电平变为高电平 。
3.2、数据的传输
发送字节:SCL低电平期间,主机将数据位依次放到SDA线上,然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化。连续循环八次即可传输一个字节(高位先行)。
接收字节:SCL低电平期间,从机将数据位依次放到SDA线上,然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化 。连续循环八次即可读取一个字节(高位先行)。
接收和发送的区别主要是在于是主机在操作数据线还是从机在操控,如果是从机要操控的话,主机要先释放数据线(也就是置为高电平)。
3.3、信号应答
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。
二、24C02
1、简介
24C02是一种串行电子存储器,其容量为2K位(256字节,24Cxx中的xx表示有多少Kbit,02Kbit=256Byte)。该存储器采用I2C总线协议进行通讯,可以通过SCL和SDA两条信号线进行读写操作。它广泛应用于模拟电子系统、数字电子系统和微处理器系统中,如计算机主板、电视机、手机、汽车电子等领域。由于它有低功耗、可靠性高等优点,因此在嵌入式系统和智能卡等领域也得到了广泛的应用
2、芯片引脚
VCC、GND就不用介绍了吧。
SCL是时钟信号线,SDA是数据信号线,用于实现IIC时序进行相应的读写操作。
WP是写保护引脚,顾名思义使能的时候就保护起来不让写入数据。
A0-A2是地址输入引脚,每个芯片都有自身的地址,这样才能实现IIC通信选取芯片,24C02的地址(共八位)前四位是固定的1010,后三位的就是有A0-A2决定的,假如全部接的是GND那么地址就是1010000。最后一位由所需操作决定,是读操作还是写操作。
3、写操作
3.1、字节写
写入一个字节:起始条件--->器件地址(第八位为写)--->写入的地址(接收应答)--->写入的数据(接收应答)--->停止条件
3.2、页写入
4、读操作
4.1、当前地址读
读取一个字节:起始条件--->器件地址--->接收读取到的数据(发送应答)--->停止条件
4.2、指定地址读
指定地址读:起始条件--->器件地址(写)--->想读取的地址--->起始条件--->器件地址(读)--->读取数据(给应答)--->停止条件
这么操作的原因在EEPROM中是有一个类似于指针的东西指向地址的,我们需要用写的方式将指针指向要读取的地址然后再进行读取的切换进行读取该地址上的数据。
三、实操部分
1.使用CubuMX进行配置(基于stm32zgt6)
1.1、生成工程
选择板子上相应的引脚,此时是黄色的说明还没有使能。
通过这一步骤使能之后,引脚就会变为绿色。
IIC配置,默认配置即可。(最好是配置一个USART1进行printf的重定向,这样方便打印结果)
生成工程之后就会出现这些HAL库封装的函数,函数很多,有需要用到的时候再去了解即可。
1.2、编写24C02读写函数
写入字节函数
#define AT24C02_READ_ADDRESS 0xA1
#define AT24C02_WRITE_ADDRESS 0xA0
void AT24C02_Write_Byte(uint16_t MemAddress,uint8_t *pData,uint16_t Size)
{
while(Size--)
{
while(HAL_I2C_Mem_Write(&hi2c1, AT24C02_WRITE_ADDRESS, MemAddress,I2C_MEMADD_SIZE_8BIT,pData,1,10) != HAL_OK){};
MemAddress++;
pData++;
}
}
hi2c:I2C的句柄
DevAddress:设备地址
MemAddress:设备的内存地址
MemAddSize:设备的内存地址的大小
pData:要写入的数据
Size:写入数据的大小
Timeout:最大超时时间
读取字节函数
void AT24C02_Read_Byte(uint16_t MemAddress,uint8_t *pData,uint16_t Size)
{
while(HAL_I2C_Mem_Read(&hi2c1,AT24C02_READ_ADDRESS,MemAddress, I2C_MEMADD_SIZE_8BIT,pData, Size, 10) != HAL_OK){};
}
HAL_I2C_Mem_Read()参数说明:
hi2c:I2C句柄
DevAddress:设备的地址
MemAddress:设备内存的地址
MemAddSize:设备内存的大小
pData:读取数据的接收
Size:读取的数据大小
Timeout:超时时间
main函数调用
int main(void)
{
uint8_t WBuf[50]="this is eeprom test!\r\n";
uint8_t RBuf[50];
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
HAL_Delay(20);
printf("the is eeprom test\n");
HAL_Delay(20);
AT24C02_Write_Byte(0,WBuf,sizeof(WBuf));
printf("eeprom write success!!!\r\n");
HAL_Delay(20);
while (1)
{
AT24C02_Read_Byte(0,RBuf,sizeof(RBuf));
printf("READ: %s\r\n",RBuf);
HAL_Delay(1000);
}
}
2.使用软件进行模拟时序
自定义iic.c和iic.h文件(此步骤省略)
.h文件
#ifndef _iic_H
#define _iic_H
#include "system.h"
/* IIC_SCL时钟端口、引脚定义 */
#define IIC_SCL_PORT GPIOB
#define IIC_SCL_PIN (GPIO_PIN_8)
#define IIC_SCL_PORT_RCC __HAL_RCC_GPIOB_CLK_ENABLE()
/* IIC_SDA时钟端口、引脚定义 */
#define IIC_SDA_PORT GPIOB
#define IIC_SDA_PIN (GPIO_PIN_9)
#define IIC_SDA_PORT_RCC __HAL_RCC_GPIOB_CLK_ENABLE()
//IO操作函数
#define IIC_SCL PBout(8) //SCL
#define IIC_SDA PBout(9) //SDA
#define READ_SDA PBin(9) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_Byte(u8 ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
#endif
.c文件
/*******************************************************************************
* 函 数 名 : IIC_Init
* 函数功能 : IIC初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
IIC_SCL_PORT_RCC; //使能时钟
IIC_SDA_PORT_RCC;
GPIO_InitStructure.Pin=IIC_SCL_PIN;
GPIO_InitStructure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_InitStructure.Pull=GPIO_PULLUP; //上拉
GPIO_InitStructure.Speed=GPIO_SPEED_FAST; //快速
HAL_GPIO_Init(IIC_SCL_PORT,&GPIO_InitStructure);
GPIO_InitStructure.Pin=IIC_SDA_PIN;
HAL_GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
IIC_SDA=1; //默认状态
IIC_SCL=1;
}
/*******************************************************************************
* 函 数 名 : SDA_OUT
* 函数功能 : SDA输出配置
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void SDA_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin=IIC_SDA_PIN;
GPIO_InitStructure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_InitStructure.Pull=GPIO_PULLUP; //上拉
GPIO_InitStructure.Speed=GPIO_SPEED_FAST; //快速
HAL_GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}
/*******************************************************************************
* 函 数 名 : SDA_IN
* 函数功能 : SDA输入配置
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void SDA_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin=IIC_SDA_PIN;
GPIO_InitStructure.Mode=GPIO_MODE_INPUT;
GPIO_InitStructure.Pull=GPIO_PULLUP; //上拉
GPIO_InitStructure.Speed=GPIO_SPEED_FAST; //快速
HAL_GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}
/*******************************************************************************
* 函 数 名 : IIC_Start
* 函数功能 : 产生IIC起始信号
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(5); //延时少量时间,防止翻转过快
IIC_SDA=0; //SCL高电平期间,SDA由高变低
delay_us(6); //同理
IIC_SCL=0; //钳住I2C总线,准备发送或接收数据
}
/*******************************************************************************
* 函 数 名 : IIC_Stop
* 函数功能 : 产生IIC停止信号
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//保证SDA处于低电平
IIC_SCL=1;
delay_us(6);
IIC_SDA=1;//发送I2C总线结束信号,SCL高电平期间SDA由低变高
delay_us(6);
}
/*******************************************************************************
* 函 数 名 : IIC_Wait_Ack
* 函数功能 : 等待应答信号到来
* 输 入 : 无
* 输 出 : 1,接收应答失败
0,接收应答成功
*******************************************************************************/
u8 IIC_Wait_Ack(void)
{
u8 tempTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1; //释放总线
delay_us(1);
IIC_SCL=1;
delay_us(1);
while(READ_SDA)
{
tempTime++;
if(tempTime>250)//判断是否超时
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
/*******************************************************************************
* 函 数 名 : IIC_Ack
* 函数功能 : 产生ACK应答
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Ack(void)
{
IIC_SCL=0;//确保SCL初始在低电平
SDA_OUT();
IIC_SDA=0;//将SDA拉低
delay_us(2);
IIC_SCL=1;//在SCL由高变低的期间,SDA低电平表示有应答
delay_us(5);
IIC_SCL=0;
}
/*******************************************************************************
* 函 数 名 : IIC_NAck
* 函数功能 : 产生NACK非应答
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;//步骤与上同理,只是发送的是1
delay_us(2);
IIC_SCL=1;
delay_us(5);
IIC_SCL=0;
}
/*******************************************************************************
* 函 数 名 : IIC_Send_Byte
* 函数功能 : IIC发送一个字节
* 输 入 : txd:发送一个字节
* 输 出 : 无
*******************************************************************************/
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
if((txd&0x80)>0) //0x80 1000 0000
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1; //高位先发,发完就将低位移上来
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1; //在SCL由低变高的期间,SDA是什么状态就表示什么数据
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
/*******************************************************************************
* 函 数 名 : IIC_Read_Byte
* 函数功能 : IIC读一个字节
* 输 入 : ack=1时,发送ACK,ack=0,发送nACK
* 输 出 : 应答或非应答
*******************************************************************************/
u8 IIC_Read_Byte(u8 ack)
{
u8 i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
注意!!!在HAL库中没有封装us级的延时,所以需要自己去封装。剩余24c02部分自行封装以及使用。
总结
本人是个菜鸟,如有错误,请指出,谢谢!