STM32F030 硬件I2C驱动 AT24C16

qwert99STM32 2021-12-16 15:04:52 2780阅读 举报

网络上很多F1系列的ATC24的读写程序,但F0几乎没有。由于F0完全重写了I2C,所以以往的代码并不能直接使用,修改事件、接口上会浪费很多时间,特别是对于使用F0系列进行入门的新手。

先讲解AT24C系列器件的介绍 和 IIC的通信流程,不喜勿喷, 望请修正。。。。。GO

图 AT24C01/02/04/08/16的外形级封装和引脚说明

AT24C系列为美国ATMEL公司推出的串行COMS型E2PROM,是典型的串行通信E2PROM 。

AT24CXX是IIC总线串行器件,具有工作电源宽(1.8~6.0 V),抗干扰能力强(输入引脚内置施密特触发器滤波抑制噪声),功耗低(写状态时最大工作电流3 mA),高可靠性(写次数100万次,数据保存100年),支持在线编程等特点.

 

从上面两张图片可以得知:

AT :ATMEL公司出品

24:  系列号

C :商业

XX : 存储容量 ,举例 01 –> 1K à 128 字节

    02 à 2K à 256 字节

   …………….

    16à 16K à 2K 字节

I2C总线协议规定,任何将数据传送到总线的器件作为发送器。任何从总线接收数据的器件为接收器。

主器件控制串行时钟和起始、停止信号的发生。主器件任何期间都可以发送或接收数据,但是主器件控制数据传送模式(发送或者接收)。

WP写保护引脚:当该引脚连接到VCC,I2C器件内的内容被写保护(只能读)。如果允许对器件进行正常的读写,那么WP引脚需连接到地或者悬空。

通过器件地址输入端A0、A1、A2可以实现讲最多8个at24c01器件和at24c02器件、4个at24c04器件、2个at24c08器件、1个at24c16器件连接到总线上。当总线上只有一个器件时,A0、A1、A2可以连接到地或者悬空。

下面重点分析一下这句话:为什么 at24c01/at24c02 可以挂接8个器件呢?为什么到了 at24C16 却只能挂接1个器件呢???

WHY? 

器件识别控制字节的作用

以at24c01/at24c02 和at24C16  举例:

I2C总线上所有外围器件都有唯一的地址,这个地址由器件地址和引脚地址两部分组成。共7位。

器件地址是I2C器件固有的地址编码,器件出厂时已经给定,不可更改。

引脚地址由I2C总线外围器件的地址引脚A0、A1、A2决定,根据其在电路中接电源正极、接地或者悬空的不同,形成不同的地址代码。引脚地址数也决定了同一器件可接入总线的最大数目。

 

此时于引脚地址无关,与 P2、P1、P0有关,即页地址有关,页地址高三位是器件识别控制字节的1-3位,器件上的A0,A1,A2,就无效了,所以只能接1个AT24C16器件。

 

页地址是什么乖乖,从何而来呢????

上图可知 AT24C16 存储容量 16K = 2K字节 = 128(页面数)* 16 (每页的字节数) = 2^11 (寻址地址位数 11位)。

AT24C16内部有2048*8位的存储容量,即可以存储2K字节的数据。这2K字节被放在128个页内,每页存放16个字节。所以对AT24C16内部的访问需要11位地址(0-7ff)。

 

举个实际的例子:

对AT24C16访问时,按照页地址和页偏移量的方式进行访问。

比如要访问第100页的第3个字节,则在发送寻址的时候,就要发送0X0643,其中页地址的高三位放在器件地址中。

第100页的第3个字节  ==  0X0643

0643 = 6 * 256 + 4 * 16  + 3 =  (6*16+4)*16 + 3 = 1603 

就是 100页的第3个字节。

所以在编写程序对AT24C16第100页的第3个字节进行写数据的时候,步骤如下:

1)发送起始信号;

2)发送器件地址0XA6(1010 0110,1010是固定地址,011是页地址的高三位,0表示写操作);

3)发送操作地址0X43(0100 0011,0100是页地址的低四位,0011是页地址偏移量,即第100页内的第三个字节,

4)发送要写的数据,

5)发送终止信号。

 

AT24C02属于AT24CXX系列,是一个2K位串行CMOS EEPROM, 内部含有256个8位字节,CATALYST公司的先进CMOS技术实质上减少了器件的功耗。AT24C02有一个16字节页写缓冲器。该器件通过IIC总线接口进行操作,有一个专门的写保护功能。


附几张杂图:

看一下10位地址的读写:



废话不多说,进入正题。(哈哈,每篇文章都用这个开头)

I2C 的配置

static void InitI2C()
{
  I2C_InitTypeDef I2C_InitStructure;
  GPIO_InitTypeDef GPIO_InitA;
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);//使能I2C1,I2C2的时钟
  RCC_I2CCLKConfig(RCC_I2C1CLK_SYSCLK);//时钟源设定
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_1);  //配置PB8 成第二功能引脚 I2C1_SCL
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_1);  //配置PB9 成第二功能引脚 I2C1_SDA
  GPIO_InitA.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
  GPIO_InitA.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitA.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitA.GPIO_OType = GPIO_OType_PP;
  GPIO_InitA.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOB, &GPIO_InitA);
  I2C_InitStructure.I2C_Mode = I2C_Mode_SMBusHost;
  I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
  I2C_InitStructure.I2C_AnalogFilter = I2C_AnalogFilter_Enable;
  I2C_InitStructure.I2C_DigitalFilter = 0x01;
  I2C_InitStructure.I2C_OwnAddress1 = 0x00;
  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
  I2C_InitStructure.I2C_Timing = 0x0090174F;
  I2C_Init(I2C1, &I2C_InitStructure);
  I2C_Cmd(I2C1, ENABLE);
}

一样的配置方案,I2C_Timing的意思请移步本博客GY30那篇文章。
I2C引脚为PB8 与PB9(使用的C8T6,f4p6可以用PA的)

#define AT24C16_Base_Address 0xA0
void AT24C16_WriteByte(uint8_t Page,uint8_t WordAddress,uint8_t Data);
uint8_t AT24C16_ReadByte(uint8_t Page,uint8_t WordAddress);
void AT24C16_PageWrite(uint8_t Page,uint8_t WordAddress,uint8_t Length,uint8_t* Data);
void AT24C16_SequentialRead(uint8_t Page,uint8_t WordAddress, uint8_t length , uint8_t* p);

下面是相关函数:

void AT24C16_WriteByte(uint8_t Page,uint8_t WordAddress,uint8_t Data)
{
    if(WordAddress > 0x10)
    {
        return;
    }
    WordAddress |= ( Page & 0x0F ) << 4;
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET);//IF BUSY
    I2C_TransferHandling(I2C1,AT24C16_Base_Address | ( ( Page & 0xF0 ) >> 3 ),2,I2C_AutoEnd_Mode,I2C_Generate_Start_Write);
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TXIS) == RESET);//If Write OK
    I2C_SendData(I2C1,WordAddress);
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TXIS) == RESET);//If Write OK
    I2C_SendData(I2C1,Data);
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_STOPF) == RESET);
}
uint8_t AT24C16_ReadByte(uint8_t Page,uint8_t WordAddress)
{
    uint8_t Recev = 0x00;
    if(WordAddress > 0x10)
    {
        return 0;
    }
    WordAddress |= ( Page & 0x0F ) << 4;
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET);//IF BUSY
    I2C_TransferHandling(I2C1,AT24C16_Base_Address | ( ( Page & 0xF0 ) >> 3 ),1,I2C_SoftEnd_Mode,I2C_Generate_Start_Write);
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TXIS) == RESET);//If Write OK
    I2C_SendData(I2C1,WordAddress);
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TC) == RESET);
    I2C_TransferHandling(I2C1,AT24C16_Base_Address | ( ( Page & 0xF0 ) >> 3 ),1,I2C_AutoEnd_Mode,I2C_Generate_Start_Read);
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET);
    Recev = I2C_ReceiveData(I2C1);
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_STOPF) == RESET);
    return Recev;
}

下面是页读取,页写入:

void AT24C16_PageWrite(uint8_t Page,uint8_t WordAddress,uint8_t Length,uint8_t* Data)
{
    uint8_t i = 0;
    if(WordAddress > 0x10)
    {
        return;
    }
    WordAddress |= ( Page & 0x0F ) << 4;
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET);//IF BUSY
    I2C_TransferHandling(I2C1,AT24C16_Base_Address | ( ( Page & 0xF0 ) >> 3 ),Length + 1,I2C_AutoEnd_Mode,I2C_Generate_Start_Write);
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TXIS) == RESET);//If Write OK
    I2C_SendData(I2C1,WordAddress);
    for(i = 0;i < Length; i++)
    {
        while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TXIS) == RESET);//If Write OK
        I2C_SendData(I2C1,Data[i]);
    }
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_STOPF) == RESET);
}
void AT24C16_SequentialRead(uint8_t Page,uint8_t WordAddress, uint8_t length , uint8_t* p)
{
    uint8_t i;
    if(WordAddress > 0x10)
    {
        return;
    }
    WordAddress |= ( Page & 0x0F ) << 4;
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET);//IF BUSY
    I2C_TransferHandling(I2C1,AT24C16_Base_Address | ( ( Page & 0xF0 ) >> 3 ),1,I2C_SoftEnd_Mode,I2C_Generate_Start_Write);
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TXIS) == RESET);//If Write OK
    I2C_SendData(I2C1,WordAddress);
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TC) == RESET);
    I2C_TransferHandling(I2C1,AT24C16_Base_Address | ( ( Page & 0xF0 ) >> 3 ),length,I2C_AutoEnd_Mode,I2C_Generate_Start_Read);
    for(i = 0;i < length;i++)
    {
        while(I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET);
        p[i] = I2C_ReceiveData(I2C1);
    }
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_STOPF) == RESET);
}

感觉没什么说的,GY30那篇文章基本都说完了,添点小知识点吧。


I2C_AutoEnd_Mode,顾名思义,操作length字节后自动添加STOP。

I2C_SoftEnd_Mode ,同样顾名思义,操作length字节后需要手动添加STOP。( I2C_GenerateSTOP() )

这个模式比自动多了一步,需要 I2C_GetFlagStatus(I2C1, I2C_FLAG_TC) ,Translate Completed,是否传输完成,自动模式下访问这个会得到Reset值,然而手动模式下需要访问他,然后生成Stop。


然后……差不多了吧?举个 上面网址的例子吧,我觉得很多人不会看……

所以在编写程序对AT24C16第100页的第3个字节进行写数据的时候,步骤如下:
1)发送起始信号;
2)发送器件地址0XA6(1010 0110,1010是固定地址,011是页地址的高三位,0表示写操作);
3)发送操作地址0X43(0100 0011,0100是页地址的低四位,0011是页地址偏移量,即第100页内的第三个字节,
4)发送要写的数据,
5)发送终止信号。


我相信各位最起码都看了AT24C16的地址了,0xA0。


P0P1P2为页地址高三位,发送的字地址(WordAddress)高四位为页地址的第四位,低四位为字地址。

AT24C16有128页,每页16bytes。所以正好匹配上。

写的间隔至少为5ms,否则用循环等待的话I2C会卡死。


以上。

另:代码我测试是通过的,若有Bug欢迎指出。






标签: #STM32# #AT24C16# #I2C#

版权声明:
作者:qwert99
链接:https://www.dianziwang.net/p/6c39800f00750.html
来源:STM32
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以点击 “举报”


登录 后发表评论
0条评论
还没有人评论过~