STM32?I2C-EEPROM

来源:互联网 发布:sql基础教程光盘下载 编辑:程序博客网 时间:2024/04/29 10:27

I2C协议简介:
I2C(inter-integrated Circuit)协议是由Phiilps公司开发的,因为它的引脚少,硬件实现简单,可扩展性强(不像USART,CAN的外部收发设备),如今被广泛的使用在系统内多个集成电路(IC)之间的通信.
(根据I2C总线协议,我们可以把I2C分为物理层和协议层)
物理层:
1,使用两条总线线路,一条双向串行数据线(SDA),一条串行时钟线(SCL).
2,每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问.
3,多主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线.
4,具有三种传输模式:
标准模式传输速率为100Kbit/s
快速模式传输速率为400Kbit/s
高速模式传输速率为3.4Mbit/s(但是目前大多的I2C设备都不支持高速模式)
5,片上的滤波器可以滤去总线数据线上的毛刺波保证数据完整.
6,连接到相同总线的IC数量受到总线的最大电容400pF限制.
这里写图片描述

协议层:
I2C的协议包括起始和停止条件,数据有效性,响应,仲裁,时钟同步和地址广播等.(STM32有集成的硬件I2C接口,所以不需要用软件去模拟SDA和SCL时序).
这里写图片描述
主机写数据到从机
这里写图片描述
主机由从机读数据
(这两幅图表示的是主机和从机通讯时,SDA线的数据包序列)

S:传输开始信号
SLAVE ADDRESS:从机的地址
R/~W:传输方向选择 1为读,0为写
A/~A:应答或者非应答信号
DATA(带阴影的):数据由主机传输到从机
DATA(空白的):数据由从机传输到主机
P:停止传输信号

起始信号S产生后,所有从机就开始等待主机接下来广播的从机地址信号SLAVE ADDRESS,在I2C总线上,每个设备的地址都是唯一的.当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备会直接忽略之后的数据信号.(根据I2C总线协议,这个从机地址可是是7位或10位).
地址位的后面,是传输方向的选择(读写R/~W),从机接收到匹配的地址后,主机或从机会返回一个应答(A)或者非应答(~A)信号,只有接收到应答信号后,主机才能继续发送或接收数据.(根据I2C总线协议,之后的每一个操作都需要返回一个应答信号)

如果配置方向传输位是写数据:
广播完地址,接收到应答信号后,主机开始向从机传输数据(DATA),数据包的大小为8位,主机每发送一个数据,都要等待从机的应答信号(A)(重复这个过程可以向从机传输N个数据,这个N没有大小限制),当数据传输结束时,主机向从机发送一个停止传输信号(P),表示传输数据停止.

如果配置方向传输位是读数据:
广播完地址,接收到应答信号后,从机开始向主机返回数据(DATA),数据包的大小为8位,从机每发送一个数据,都会等待主机的应答信号(A)(重复这个过程可以返回N个数据,这个N没有大小限制),当主机希望停止接收数据时,就向从机返回一个非应答信号(~A),从机自动停止数据传输.

STM32的I2C特性和架构:
I2C接口特性:
1,STM32的中等容量和大容量型号的芯片均有2个的I2C总线接口.
2,能够工作于多主模式或从模式,分别为主接收器,主发送器,从接收器,从发送器.
3,支持标准模式100Kbit/s和快速模式400Kbit/s,不支持高速模式.
4,支持7位或10位寻址.
5,内置了硬件CRC发生器/校验器.
6,I2C的接收和发送都可以使用DMA操作.
7,支持系统管理总线(SMBus)2.0版

I2C架构:
这里写图片描述
可以看到,I2C所有的硬件架构都是根据SCL和SDA展开的(其中的SMBALERT用于SMBUS).
SCL线的时序就是I2C协议中的时钟信号,他由I2C接口根据时钟控制寄存器(CCR)控制,控制的参数主要是时钟频率.
SDA线的信号是通过一系列数据控制架构,在将要发送的数据的基础上,根据协议添加各种起始信号,应答信号,地址信号,实现以I2C协议的方式发送出去.读取数据则从SDA线上的信号中取出接收到的数据值.(发送和接收的数据都是被保存在数据寄存器(DR)上的).

I2C实例(EEPROM):
对EEPROM读写最常用的方式是使用I2C协议.STM32F103VET6它有2个I2C接口,本实验使用I2C1,对应地接到EEPROM(AT24CO2)的SCL和SDA线,实现I2C通讯,对EEPROM进行读写.
本例子是采用主模式,分别用作主发送器和主接收器.通过查询事件的方式来确保正常通讯.

main函数:
int main(void)
{
USART1_Config();//串口初始化

 I2C_EE_Init();//I2C的EEPROM的初始化 USART1_printf(USART1, "\r\n ("__DATE__ "-"__TIME__ ") \r\n"); I2C_Test(); while (1) { }

}
初始化完成后由串口向终端输出调试信息,接着是调用用户函数I2C_Test(),把配置好的I2C接口向EEPROM写入数据,再把这些数据读取出来进行校验,检查我们I2C是否正常工作.

I2C接口的初始化:
void I2C_EE_Init(void)
{
I2C_GPIO_Config();
I2C_Mode_Config();

/* 根据头文件 i2c_ee.h 中的定义来选择 EEPROM 要写入的地址 */
#ifdef EEPROM_Block0_ADDRESS
/* 选择 EEPROM Block0 来写入 */
EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
#endif

#ifdef EEPROM_Block1_ADDRESS
/* 选择 EEPROM Block1 来写入 */
EEPROM_ADDRESS = EEPROM_Block1_ADDRESS;
#endif

#ifdef EEPROM_Block2_ADDRESS
/* 选择 EEPROM Block2 来写入 */
EEPROM_ADDRESS = EEPROM_Block2_ADDRESS;
#endif

#ifdef EEPROM_Block3_ADDRESS
/* 选择 EEPROM Block3 来写入 */
EEPROM_ADDRESS = EEPROM_Block3_ADDRESS;
#endif
}
这里是先调用了I2C_GPIO_Config()配置好I2C所用的I/O端口,然后再调用 I2C_Mode_Config()配置I2C的工作模式,并且使能相关外设的时钟.

EEPROM的地址:
这里写图片描述
根据I2C协议,每一个连接到I2C总线上的设备都需要唯一的地址,EEPROM当然也有(找到EEPROM的数据手册,可以找到每一个块的地址)

这里写图片描述
这里使用的EEPROM的型号是AT24C02,容量是2K bits,图中EEPROM的高四位是做了硬性规定,最低位为R/W(读写位),能改变的位只有A2,A1,A0(在硬件设计中可以根据需求改变,这里都为接地,也就是0).所以根据读写位的不同,EEPROM的地址为0xA0或者0xA1.

GPIO端口的初始化:
static void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能与 I2C1 有关的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
/* PB6-I2C1_SCL、PB7-I2C1_SDA*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
在这个函数里面,使能了GPIO时钟,I2C1的时钟,并且对I2C1复用的两个引脚进行了初始化设置,模式设置为开漏输出(不记得工作方式的,可以看之前的博客,或者直接查询《STM32数据手册》的引脚定义部分和《STM32参考手册》的GPIO章节)

I2C模式初始化:
static void I2C_Mode_Configu(void)
{
I2C_InitTypeDef I2C_InitStructure;
/* I2C 配置 */
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 =I2C1_OWN_ADDRESS7;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;
/* I2C1 初始化 */
I2C_Init(I2C1, &I2C_InitStructure);
/* 使能 I2C1 */
I2C_Cmd(I2C1, ENABLE);
}
和所有外设初始化一样,I2C模式的初始化还是对他的初始化结构体进行参数配置。

I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
选择I2C的使用方式,这里面有I2C模式(I2C_Mode_I2C)和SMBus模式(I2C_Mode_SMBusDevice或者I2C_Mode_SMBusHost),这里选择了I2C模式

I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
这里是配置I2C的SCL线时钟的占空比.在STM32的I2C占空比配置中有两个选择:(这里使用了2:1)
高电平和低电平时间比 16:9(I2C_DutyCycle_16_9)
高电平和低电平时间比 2:1 ( I2C_DutyCycle_2)
(SCL线的时钟信号的高电平时间与低电平时间是没必要相同的,由于SDA线是在SCL线维持在高电平时读取或者写入数据的,而在SCL的低电平期间SDA的数据发生了变化,所以高电平时间较长就不容易出现数据错误.根据I2C协议,在快速模式和高速模式下SCL的高低电平时间可以不同)

I2C_InitStructure.I2C_OwnAddress1 =I2C1_OWN_ADDRESS7;
配置STM32的I2C设备自己的地址,根据I2C协议,每一个连接到I2C总线上的设备都需要唯一的地址(主机也一样)这里设置为自定义宏I2C1_OWN_ADDRESS7(0x0A)在STM32数据手册I2C章节可以找到.

I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
这里是配置为允许应答(I2C_Ack_Enable),这是绝大多数遵循I2C标准设备通讯的要求(如果改为禁止应答(I2C_Ack_Disable)往往会导致通讯错误).

I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
这里是选择I2C的寻址模式是7位还是10位.根据实际连接到I2C总线上设备的地址进行选择(这里是和EEPROM进行通讯,所以使用7位寻址模式(I2C_AcknowledgedAddress_7bit;)).

I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;
这里是设置I2C的传输速率,在调用初始化函数时,函数会根据我们输入的数字经过运算后把分频值写入到I2C的时钟控制寄存器(我们写入的这个值不能高于400KHz),这里自定义宏I2C_Speed(400000)
(实际上由于I2C使用的APB1时钟为36MHz,不是10MHz的整数倍,因此最终分频后输出的SCL线时钟并不是精确的400KHz)

对结构体赋值完毕后,就用I2C_Init()进行初始化,并调用I2C_Cmd()使能I2C外设.

EEPROM的读写:
void I2C_Test(void)
{
u16 i;
printf(“写入的数据\n\r”);
for ( i=0; i<=255; i++ ) //填充缓冲
{
I2c_Buf_Write[i] = i;
printf(“0x%02X “, I2c_Buf_Write[i]);
if(i%16 == 15)
printf(“\n\r”);
}
//将 I2c_Buf_Write 中顺序递增的数据写入 EERPOM 中
I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, 256);
printf(“\n\r 读出的数据\n\r”);
//将 EEPROM 读出数据顺序保持到 I2c_Buf_Read 中
I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256);
//将 I2c_Buf_Read 中的数据通过串口打印
for (i=0; i<256; i++)
{
if(I2c_Buf_Read[i] != I2c_Buf_Write[i])
{
printf(“0x%02X “, I2c_Buf_Read[i]);
printf(“错误:I2C EEPROM 写入与读出的数据不一致\n\r”);
return;
}
printf(“0x%02X “, I2c_Buf_Read[i]);
if(i%16 == 15)
printf(“\n\r”);
}
printf(“I2C(AT24C02)读写测试成功\n\r”);
}
这个函数实现的功能是把数值0~255按顺序填入缓冲区数组,并通过串口打印到终端,接着通过函数I2C_EE_BufferWrite()把缓冲区的数据写入EEPROM,写入成功后,再用I2C_EE_BufferRead把数据读取出来,进行校验,判断数据是否正确写入.

I2C_EE_BufferWrite()以及I2C_EE_BuffRead()具有三个输入参数,缓冲区指针(I2c_Buf_Write)(I2c_Buf_Read),写入到或者读出EEPROM的存储地址(EEP_Firstpage),以及字节数.
ps:(后续我会对这两个函数做详细的分析,因为这两个就是EEPROM读写的最重要的两个函数)

2 0
原创粉丝点击