历史上的今天
返回首页

历史上的今天

今天是:2025年04月04日(星期五)

2020年04月04日 | STM32——EEPROM

2020-04-04 来源:eefocus

一、I2C接口读写EEPROM(AT24C02)


——主模式,分别用作主发送器和主接收器。通过查询事件的方式来确保正常通信。

1、I2C接口初始化


与其他对GPIO 复用的外设一样,它先调用了用户函数I2C_GPIO_Confi g() 配置好 I 2 C 所用的 I/O端口,然后再调用用户函数 I2C_Mode_Confi gu() 设置 I 2 C 的工作模式,并使能相关外设的时钟。


void I2C_EE_Init(void)

{

  I2C_GPIO_Config();

  I2C_Mode_Config();

 

  /* 根据头文件 i2c_ee.  14 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

}


(1)EEPROM地址

AT24C02:256字节,高四位硬性规定,最低位是R/W(传输方向选择位),在制作硬件时,我们可以根据需要改变的是地址位中的 A2、A1、A0 位。原理图上面全接地,所以它的地址为 :0xA0 或 0xA1。


2、GPIO端口初始化


static void I2C_GPIO_Config(void)

{

  GPIO_InitTypeDef GPIO_InitStructure;

 

  /* 使能与 I2C1 有关的时钟 */

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

 

  /*  配置SCL 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);

}

3、I2C模式初始化


typedef struct

{

  uint32_t I2C_ClockSpeed;

  uint16_t I2C_Mode;

  uint16_t I2C_DutyCycle;

  uint16_t I2C_OwnAddress1;

  uint16_t I2C_Ack;

  uint16_t I2C_AcknowledgedAddress;

} I2C_InitTypeDef;

(1)I2C_Mode:本成员是选择 I 2 C 的使用方式,有 I 2 C 模式(I2C_Mode_I2C)和SMBus 模式。(I2C_Mode_SMBusDevice、I2C_Mode_SMBusHost)


(2)I2C_DutyCycle:设置的是 I 2 C 的 SCL 线时钟的占空比。在 STM32 的 I 2 C 占空比配置中有两个选择,分别为高电平时间和低电平时间之比为16 :9 (I2C_DutyCycle_16_9)和 2 :1( I2C_DutyCycle_2)。


(3)I2C_OwnAddress1:本 成 员 配 置 的 是 STM32 的 I 2 C 设 备 自 己 的 地 址, 每个 连 接 到 I 2 C 总线上的设备都要有一个自己的地址,作为主机也不例外。这个地址可以被配置为 7 位和 10 位地址。我们把这个地址设置为 0x0A (自定义宏I2C1_OWN_ADDRESS7 的值)。


(4)I2C_Ack_Enable:本成员关于 I 2 C 应答设置,设置为使能则每接收到一个字节就返回一个应答信号。配置为允许应答(I2C_Ack_Enable),这是绝大多数遵循 I 2 C标准的设备通信的要求,改为禁止应答 (I2C_Ack_Disable)往往会导致通信错误。


(5)I2C_AcknowledgeAddress:本成员选择 I 2 C 的寻址模式是 7 位还是 10 位地址。这需要根据实际连接到 I 2C 总线上设备的地址进行选择。与 EEPROM 进行通信,使用的为 7 位寻址模式(I2C_AcknowledgedAddress_7bit)。


(6)I2C_ClockSpeed:本成员设置的是 I 2 C 的传输速率,在调用初始化函数时,函数会根据我们输入的数值经过运算后把分频值写入到 I 2 C 的时钟控制寄存器。而我们写入的这个参数值不得高于 400 kHz。——400000


对结构体成员赋值完成后,我们调用库函数 I2C_Init() 根据我们的配置对 I 2 C 进行初始化, 并调用库函数 I2C_Cmd() 使能I 2 C 外设。 


static void I2C_Mode_Configu(void)

{

  I2C_InitTypeDef I2C_InitStructure;

 

  I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;  /* I2C 配置 */

 

  I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;  /* 高电平数据稳定,低电平数据变化 SCL  时钟线的占空比 */

  I2C_InitStructure.I2C_OwnAddress1 = I2C1_OWN_ADDRESS7;

  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;

  I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;  /* I2C 的寻址模式 */

  I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;  /* 通信速率 */

 

  I2C_Init(I2C1, &I2C_InitStructure);  /* I2C1 初始化 */

  I2C_Cmd(I2C1, ENABLE);  /* 使能 I2C1 */

}


二、对EEPROM的读写操作


void I2C_Test(void)

{

  u16 i;

 

  printf("写入的数据nr");

 

  for ( i = 0; i <= 255; i++ ) //填充缓冲

  {

    I2c_Buf_Write[i] = i;

    printf("0x%02X ", I2c_Buf_Write[i]);

    if (i % 16 == 15)

    {

      printf("nr");

    }

  }

 

I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, 256);  //将 I2c_Buf_Write 中顺序递增的数据写入 EERPOM 中

 

  printf("nr 写成功nr");

  printf("nr 读出的数据nr");

 

  I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256);  //将 EEPROM 读出数据顺序保持到 I2c_Buf_Read 中

 

//将 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 写入与读出的数据不一致nr");

      return;

    }

    printf("0x%02X ", I2c_Buf_Read[i]);

    if (i % 16 == 15)

    {

      printf("nr");

    }

  }

  printf("I2C(AT24C02)读写测试成功nr");

}


功能是把数值 0 ~ 255 按顺序填入缓冲区数组,并通过串口打印到终端,接着通过用户函数I2C_EE_BufferWrite()把缓冲区的数据写入EEPROM。写入成功之后,利用用户函数 I2C_EE_BufferRead() 把数据读取出来,进行校验,判断数据是否被正确写入。 


void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite)

{

  u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;

 

  Addr = WriteAddr % I2C_PageSize;

  count = I2C_PageSize - Addr;

  NumOfPage = NumByteToWrite / I2C_PageSize;

  NumOfSingle = NumByteToWrite % I2C_PageSize;

 

  /* If WriteAddr is I2C_PageSize aligned */

  if (Addr == 0)

  {

    /* If NumByteToWrite < I2C_PageSize */

    if (NumOfPage == 0)

    {

      I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);

      I2C_EE_WaitEepromStandbyState();

    }

    /* If NumByteToWrite > I2C_PageSize */

    else

    {

      while (NumOfPage--)

      {

        I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);

        I2C_EE_WaitEepromStandbyState();

        WriteAddr += I2C_PageSize;

        pBuffer += I2C_PageSize;

      }

 

      if (NumOfSingle != 0)

      {

        I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);

        I2C_EE_WaitEepromStandbyState();

      }

    }

  }

  /* If WriteAddr is not I2C_PageSize aligned */

  else

  {

    /* If NumByteToWrite < I2C_PageSize */

    if (NumOfPage == 0)

    {

      I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);

      I2C_EE_WaitEepromStandbyState();

    }

    /* If NumByteToWrite > I2C_PageSize */

    else

    {

      NumByteToWrite -= count;

      NumOfPage = NumByteToWrite / I2C_PageSize;

      NumOfSingle = NumByteToWrite % I2C_PageSize;

 

      if (count != 0)

      {

        I2C_EE_PageWrite(pBuffer, WriteAddr, count);

        I2C_EE_WaitEepromStandbyState();

        WriteAddr += count;

        pBuffer += count;

      }

 

      while (NumOfPage--)

      {

        I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);

        I2C_EE_WaitEepromStandbyState();

        WriteAddr += I2C_PageSize;

        pBuffer += I2C_PageSize;

      }

      if (NumOfSingle != 0)

      {

        I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);

        I2C_EE_WaitEepromStandbyState();

      }

    }

  }

}


AT24C02 的 EEPROM 分为 32 页,每页可存储8个字节的数据,若在同一页写入超过 8 字节,则超过的部分会被写在该页的起始地址,这样部分数据会被覆盖。为了把连续的缓冲区数组按页写入 EEPROM,就需要对缓冲区进入分页处理。I2C_EE_BufferWrite() 函数根据我们输入的缓冲区大小参数 NumByteToWrite,计算出我们需要写入多少页,并计算写入位置。分页处理好之后,调用 I2C_EE_PageWrite() 函数,这个函数是与 EEPROM 进行I2 C通信的最底层函数,它与 STM32 的 I 2 C 库函数使用密切相关。


void I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)

{

  while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

 

  I2C_GenerateSTART(I2C1, ENABLE);  /* Send START condition */

  while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));  /* Test on EV5 and clear it */

 

 

  I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);  /* Send EEPROM address for write */

  while (!I2C_CheckEvent(I2C1,  I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));  /* Test on EV6 and clear it */

 

 

  I2C_SendData(I2C1, WriteAddr);  /* Send the EEPROM's internal address to write to */

  while (! I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));  /* Test on EV8 and clear it */

 

  while (NumByteToWrite--)  /* While there is data to be written */

  {

    I2C_SendData(I2C1, *pBuffer);    /* Send the current byte */

    pBuffer++;    /* Point to the next byte to be written */

    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) );    /* Test on EV8 and clear it */

  }

 

  I2C_GenerateSTOP(I2C1, ENABLE);  /* Send STOP condition */

}


1、EEPROM页写入时序


这个页写入的函数是根据 EEPROM 的页写入时序来编写的。

调用库函数I2C_Generate START() 产生 I 2 C 的通信起始信号 S。


调用库函数I2C_Send7bitAddress() 把前面条件编译中赋值的变量EEPROM_ADDRESS 地 址 通 过 I 2 C1接口发送出去,数据传输方向为STM32的I2 C发送数据(I2C_Direction_Transmitter)。


调 用 库 函 数I2C_SendData() , 请 注 意 这 个 库 函 数 的 输 入 参 数 为WriteAddr,根据 EEPROM 的页写入时序,发送完 I 2 C 的地址后的第一个数据并不就是要写入 EEPROM 的数据, EEPROM 对这个数据解释为将要对存储矩阵写入的地址,这个参数 WriteAddr 是在我们调用 I2C_EE_PageWrite() 函数时作为参数输入的。这个库函数实际上是把数据传输到数据寄存器,再由 I 2 C 模块根据 I 2 C 协议发送出

去。


调用I2C_SendData() 函数,向 EEPROM 发送要写入的数据,根据EEPROM 的页写入时序,这些数据将会被写入到前面发送的页地址中,若连续写入超过一页的最大字节数(8个),则多出来的数据会重新从该页的起始地址连续写入,覆盖前面的数据。


调用库函数I2C_Generate STOP() 产生 I 2 C 传输结束信号,完成一次 I2 C 通信。


2、I2C事件检测


在 I2 C的通信过程中,会产生一系列的事件,出现事件后在相应的寄存器中会产生标志位。

若发出了起始信号,会产生事件 5(EV5),即 STM32 的 I 2 C成为主机模式;继续发送完 I 2C 设备寻址并得到应答后,会产生 EV6,即 STM32 的 I 2C 成为数据发送端;之后发送数据完成会产生 EV8 等。我们在做出 I 2 C 通信操作时,可以通过循环调用库函数I2C_CheckEvent()进行事件查询,以确保上一操作完成后才进行下一操作。


3、等到EEPROM内部写入完成


void I2C_EE_WaitEepromStandbyState(void)

{

  vu16 SR1_Tmp = 0;

  do

  {

    I2C_GenerateSTART(I2C1, ENABLE);    /* Send START condition */

    SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1);    /* Read I2C1 SR1 register */

推荐阅读

史海拾趣

问答坊 | AI 解惑

DS1302的问题

我在做一个基于单片机的电子万年历设计,其中用到了DS1302,因为之前没学过,所以不太懂,老师给了一个阳历程序流程图,看不太懂,希望高手可以帮我解释一下这个流程图,谢谢大家!…

查看全部问答>

可察言观色的车载机器人

好像很早前就有个一个概念,现在用在了车载上。 AIDA将具备察言观色的能力,能够根据面部表情以及其它线索读懂驾驶者的心情 如果驾驶者心情不好,它甚至还会表现出很同情的样子。 AIDA能够根据路况报道为驾驶者寻找最理想的回家路线 ...…

查看全部问答>

移植网卡驱动 读DM9000 ID的问题 有做过的进来帮忙看看

读出的ID总不对       平台2440  DM9000  接片选4 #define BSP_BASE_REG_PA_DM9000_IOBASE           0x20000300 DNW输出如下:      ...…

查看全部问答>

巴山工作室,为您解决技术难题和提供技术服务.

   巴山工作室有非常强大软硬件技术力量,能为您或您的企业解决一些复杂技术难题,    同时也您企业减少一些不必要的人力成本.        巴山工作室有非常强大软硬件技术力量,能为您或您的企业解决一些复 ...…

查看全部问答>

程序启动时的图片加载问题

我现在是在EVC下做一个简单的对话框程序,程序是在2440XP开发板上运行,系统是WinCE5.0 这个程序就是一个基于对话框的程序,首先我给对话框设置一张背景图片,然后还在对话框上加了一些CBitmapButton和CStatic这些控件我都把它们的背景设置成一些 ...…

查看全部问答>

一个开发板的样本驱动程序 用下面的编译参数直接编译,出现以下错误

[root@localhost turner]# arm-linux-gcc -D__KERNEL__ -I/opt/FriendlyARM/SBC2410/kernel/include -Wall -Wstrict-prototypes -Wno-trigraphs -Os -mapcs -fno-strict-aliasing -fno-common -fno-common -pipe -mapcs-32 -march=armv4 -mtu ...…

查看全部问答>

侃侃我的成果

本人长期从事单片机开发应用,积累了一定的经验。本人将毕生研究心得集成了文字,放在我的博客里,希望大家光临指导,并给点意见。我的博客地址;http://blog.mcuol.com/user/Article/500.html…

查看全部问答>

请教香版:关于USBCustom_HID中显示ST的问题

香版:您好! 我在使用Custom_Hid的时候,发现其需要安装驱动,但其启动好像是PC系统自带,这倒没有什么问题。 问题是,在第一次插入PC的时候,会提示ST等信息。而作为我们设计的产品,希望能提示公司自己的ID或者什么都不显示。不知道这样 ...…

查看全部问答>

【求助】远程通信问题

    各位大哥大姐:    小弟最近在设计方案,涉及到两块板子的通信问题。 要求两块板的电源,地完全隔离;能够进行数据通信,通信距离在10米至30米。 小弟我苦思几天,通信不过就如下几种: 232,485,CAN,红外。但好像这 ...…

查看全部问答>

【设计工具】Quartus中FFT模块中文说明

Altera FFT兆核函数2.0.0版简介一.FFT兆核函数简介Altera FFT兆核函数2.0.0版是一个高性能、参数化快速傅立叶变换(FFT)处理器,对Altera Stratix II、Stratix GX、Stratix以及Cyclone器件系列已经进行了设计优化。FFT兆核函数可以完成高性能复数FFT ...…

查看全部问答>