STM32CubeMX学习笔记(9)——I2C接口使用(读写EEPROM AT24C02)

一、I2C简介

I2C(Inter-Integrated Circuit ,内部集成电路) 总线是一种由飞利浦 Philip 公司开发的串行总线。是两条串行的总线,它由一根数据线(SDA)和一根 时钟线(SCL)组成。I2C 总线上可以接多个 I2C 设备,每个器件都有一个唯一的地址识别。同一时间只能有一个主设备,其他为从设备。通常 MCU 作为主设备控制,外设作为从设备。

STM32 的 I2C 外设可用作通讯的主机及从机,支持 100Kbit/s 和 400Kbit/s 的速率,支持 7 位、10 位设备地址,支持 DMA 数据传输,并具有数据校验功能。它的 I2C 外设还支持 SMBus2.0 协议,SMBus 协议与 I2C 类似,主要应用于笔记本电脑的电池管理中。

二、引脚分布

STM32 芯片有多个 I2C 外设,它们的 I2C 通讯信号引出到不同的 GPIO 引脚上,使用时必须配置到这些指定的引脚。PB8 PB9 为重映射。

三、EEPROM芯片

开发板中的 EEPROM 芯片型号:AT24C02。AT24C 系列为美国 ATMEL 公司推出的串行 COMS 型 EEPROM。芯片型号后两位表示芯片容量,例如 ATC24C02 为 2K。引脚图中 A0、A1、A2 为器件地址引脚,GND为地,VCC为正电源,WP为写保护,SCL为串行时钟线,SDA为串行数据线。

EEPROM 芯片中 WP 引脚具有写保护功能,当该引脚电平为高时,禁止写入数据,当引脚为低电平时,可写入数据,我们直接接地,不使用写保护功能。

AT24Cxx 设备地址为如下,前四位固定为 1010A2~A0为由管脚电平决定。AT24Cxx EEPROM Board模块中默认为接地。A2~A0000,最后一位 R/W 表示读写操作。所以由于 I2C 通讯时常常是地址跟读写方向连在一起构成一个 8 位数,且当 R/W 位为 0 时,表示写方向,所以加上 7 位地址,其值为 0xA0,常称该值为 I2C 设备的“写地址”;当 R/W 位为 1 时,表示读方向,加上 7 位地址,其值为 0xA1,常称该值为“读地址”。

四、新建工程

1. 打开 STM32CubeMX 软件,点击“新建工程”

2. 选择 MCU 和封装

3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)

选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置

4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire

五、I2C1

5.1 参数配置

Connectivity 中选择 I2C1 设置,并选择 I2C 内部集成电路

I2C 为默认设置不作修改。只需注意一下,I2C 为标准模式,I2C 传输速率 (I2C Clock Speed) 为 100KHz

5.2 生成代码

输入项目名和项目路径

选择应用的 IDE 开发环境 MDK-ARM V5

每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。

点击 GENERATE CODE 生成代码

5.3 添加全局变量

在 main.c 头部添加写地址 0xA0,读地址 0xA1,写缓存区 WriteBuffer,读缓存区 ReadBuffer

/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c1;
UART_HandleTypeDef huart1;

/* USER CODE BEGIN PV */
#define ADDR_24LCxx_Write 0xA0
#define ADDR_24LCxx_Read 0xA1
#define BufferSize 256
uint8_t WriteBuffer[BufferSize] = {0};
uint8_t ReadBuffer[BufferSize] = {0};
/* USER CODE END PV */

5.4 添加写入和读取函数

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_USART1_UART_Init();
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */
  printf("\r\n***************I2C Example*******************************\r\n");
  uint32_t i;
  uint8_t j;
  for(i = 0; i < 256; i++)
  {
    WriteBuffer[i] = i;    /* WriteBuffer init */
    printf("0x%02X ", WriteBuffer[i]);
    if(i % 16 == 15)
    {    
      printf("\n\r");
    }
  }
  /* wrinte date to EEPROM */
  for (j = 0; j < 32; j++)
  {
    if(HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, 8*j, I2C_MEMADD_SIZE_8BIT, WriteBuffer+8*j, 8, 100) == HAL_OK)
    {
      printf("\r\n EEPROM 24C02 Write Test OK \r\n");
    }
    else
    {
      printf("\r\n EEPROM 24C02 Write Test False \r\n");
    }
  } 
  /* read date from EEPROM */
  HAL_I2C_Mem_Read(&hi2c1, ADDR_24LCxx_Read, 0, I2C_MEMADD_SIZE_8BIT, ReadBuffer, BufferSize, 1000);
  for(i = 0; i < 256; i++)
  {
    printf("0x%02X  ",ReadBuffer[i]);
    if(i%16 == 15)    
    {
      printf("\n\r");
    }
  }
    
  if(memcmp(WriteBuffer,ReadBuffer,BufferSize) == 0 ) /* check date */
  {
    printf("\r\n EEPROM 24C02 Read Test OK\r\n");
  }
  else
  {
    printf("\r\n EEPROM 24C02 Read Test False\r\n");
  }

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

程序中先初始化写数据缓存。然后调用 HAL_I2C_Mem_Write() 函数将数据写入 EEPROM 中。根据函数返回值判断写操作是否正确。在 I2C 中可以找到内存写函数说明。

  • 第一个参数为 I2C 操作句柄。
  • 第二个参数为 EEPROM 的写操作设备地址。
  • 第三个参数为内存地址。
  • 第四个参数为内存地址长度,EEPROM 内存长度为 8bit。
  • 第五个参数为数据缓存的起始地址。
  • 第六个参数为传输数据的大小。 AT24C02 型号的芯片页写入时序最多可以一次 发送 8 个数据(即 n = 8 ),该值也称为页大小,某些型号的芯片每个页写入时序最多可传输 16 个数据。
  • 第七个参数为操作超时时间。
/**
  * @brief  Write an amount of data in blocking mode to a specific memory address
  * @param  hi2c Pointer to a I2C_HandleTypeDef structure that contains
  *                the configuration information for the specified I2C.
  * @param  DevAddress Target device address: The device 7 bits address value
  *         in datasheet must be shifted to the left before calling the interface
  * @param  MemAddress Internal memory address
  * @param  MemAddSize Size of internal memory address
  * @param  pData Pointer to data buffer
  * @param  Size Amount of data to be sent
  * @param  Timeout Timeout duration
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress,
                                    uint16_t MemAddress, uint16_t MemAddSize,
                                    uint8_t *pData, uint16_t Size, uint32_t Timeout)


调用 HAL_I2C_Mem_Read() 函数读取 EEPROM 中刚才写入的数据。HAL_I2C_Mem_Read() 函数描述如下。

  • 第一个参数为 I2C 操作句柄。
  • 第二个参数为 EEPROM 的读操作设备地址。
  • 第三个参数为内存地址。
  • 第四个参数为内存地址长度。
  • 第五个参数为读取数据存储的起始地址。
  • 第六个参数为传输数据的大小。
  • 第七个参数为操作超时时间。
/**
  * @brief  Read an amount of data in blocking mode from a specific memory address
  * @param  hi2c Pointer to a I2C_HandleTypeDef structure that contains
  *                the configuration information for the specified I2C.
  * @param  DevAddress Target device address: The device 7 bits address value
  *         in datasheet must be shifted to the left before calling the interface
  * @param  MemAddress Internal memory address
  * @param  MemAddSize Size of internal memory address
  * @param  pData Pointer to data buffer
  * @param  Size Amount of data to be sent
  * @param  Timeout Timeout duration
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress,
                                  uint16_t MemAddress, uint16_t MemAddSize, 
                                  uint8_t *pData, uint16_t Size, uint32_t Timeout)

程序最后调用 memcmp() 函数判断读写的两个缓存的数据是否一致。memcmp() 是比较内存区域是否相等,标准库里面的函数,在 main.c 前面添加 string.h 头文件。

5.5 查看打印

串口打印功能查看 STM32CubeMX学习笔记(6)——USART串口使用

5.6 HAL库与标准库代码比较

STM32CubeMX 使用 HAL 库生成的代码:

/**
  * @brief I2C1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C1_Init(void)
{

  /* USER CODE BEGIN I2C1_Init 0 */

  /* USER CODE END I2C1_Init 0 */

  /* USER CODE BEGIN I2C1_Init 1 */

  /* USER CODE END I2C1_Init 1 */
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C1_Init 2 */

  /* USER CODE END I2C1_Init 2 */
}

HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);

使用 STM32 标准库的代码:

/**
  * @brief  I2C I/O配置
  * @param  无
  * @retval 无
  */
static void I2C_GPIO_Config(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure; 

	/* 使能与 I2C 有关的时钟 */
	EEPROM_I2C_APBxClock_FUN ( EEPROM_I2C_CLK, ENABLE );
	EEPROM_I2C_GPIO_APBxClock_FUN ( EEPROM_I2C_GPIO_CLK, ENABLE );
	
    
  /* I2C_SCL、I2C_SDA*/
  GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;	       // 开漏输出
  GPIO_Init(EEPROM_I2C_SCL_PORT, &GPIO_InitStructure);
	
  GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SDA_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;	       // 开漏输出
  GPIO_Init(EEPROM_I2C_SDA_PORT, &GPIO_InitStructure);		
}

/**
  * @brief  I2C 工作模式配置
  * @param  无
  * @retval 无
  */
static void I2C_Mode_Config(void)
{
  I2C_InitTypeDef  I2C_InitStructure; 

  /* I2C 配置 */
  I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
	
	/* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
  I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	
  I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7; 
  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
	 
	/* I2C的寻址模式 */
  I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	
	/* 通信速率 */
  I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;
  
	/* I2C 初始化 */
  I2C_Init(EEPROM_I2Cx, &I2C_InitStructure);
  
	/* 使能 I2C */
  I2C_Cmd(EEPROM_I2Cx, ENABLE);   
}

void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

六、注意事项

用户代码要加在 USER CODE BEGIN NUSER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。


• 由 Leung 写于 2021 年 1 月 26 日

• 参考:STM32CubeMX系列教程9:内部集成电路(I2C)
    【STM32Cube_13】使用硬件I2C读写EEPROM(AT24C02)

很高兴为您解答这个问题!以下是使用软件I2C方式驱动AT24C02STM32F103代码: ``` #include "stm32f10x.h" #include "delay.h" #define SDA GPIO_Pin_6 #define SCL GPIO_Pin_7 #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(uint32_t)8<<28;} #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(uint32_t)3<<28;} #define IIC_SCL PBout(7) //SCL #define IIC_SDA PBout(6) //SDA #define READ_SDA PBin(6) //输入SDA void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); IIC_SDA = 1; IIC_SCL = 1; } void IIC_Start(void) { SDA_OUT(); IIC_SDA = 1; IIC_SCL = 1; delay_us(4); IIC_SDA = 0; delay_us(4); IIC_SCL = 0; } void IIC_Stop(void) { SDA_OUT(); IIC_SCL = 0; IIC_SDA = 0; delay_us(4); IIC_SCL = 1; IIC_SDA = 1; delay_us(4); } u8 IIC_Wait_Ack(void) { u8 ucErrTime = 0; SDA_IN(); IIC_SDA = 1; delay_us(4); IIC_SCL = 1; delay_us(4); while (READ_SDA) { ucErrTime++; if (ucErrTime > 250) { IIC_Stop(); return 1; } delay_us(1); } IIC_SCL = 0; return 0; } void IIC_Ack(void) { IIC_SCL = 0; SDA_OUT(); IIC_SDA = 0; delay_us(2); IIC_SCL = 1; delay_us(2); IIC_SCL = 0; } void IIC_NAck(void) { IIC_SCL = 0; SDA_OUT(); IIC_SDA = 1; delay_us(2); IIC_SCL = 1; delay_us(2); IIC_SCL = 0; } void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL = 0; for (t = 0; t < 8; t++) { if ((txd & 0x80) >> 7) IIC_SDA = 1; else IIC_SDA = 0; txd <<= 1; delay_us(2); IIC_SCL = 1; delay_us(2); IIC_SCL = 0; delay_us(2); } } u8 IIC_Read_Byte(unsigned char ack) { unsigned char i, receive = 0; SDA_IN(); 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_Ack(); else IIC_NAck(); return receive; } void AT24C02_WriteOneByte(u16 addr, u8 data) { IIC_Start(); IIC_Send_Byte(0xA0); IIC_Wait_Ack(); IIC_Send_Byte(addr >> 8); IIC_Wait_Ack(); IIC_Send_Byte(addr & 0xFF); IIC_Wait_Ack(); IIC_Send_Byte(data); IIC_Wait_Ack(); IIC_Stop(); delay_ms(5); } u8 AT24C02_ReadOneByte(u16 addr) { u8 temp; IIC_Start(); IIC_Send_Byte(0xA0); IIC_Wait_Ack(); IIC_Send_Byte(addr >> 8); IIC_Wait_Ack(); IIC_Send_Byte(addr & 0xFF); IIC_Wait_Ack(); IIC_Start(); IIC_Send_Byte(0xA1); IIC_Wait_Ack(); temp = IIC_Read_Byte(0); IIC_Stop(); return temp; } int main(void) { IIC_Init(); while (1) { AT24C02_WriteOneByte(0x00, 0x01); // 在地址0x00写入数据0x01 delay_ms(200); AT24C02_WriteOneByte(0x01, 0x02); // 在地址0x01写入数据0x02 delay_ms(200); AT24C02_WriteOneByte(0x02, 0x03); // 在地址0x02写入数据0x03 delay_ms(200); u8 val_0x00 = AT24C02_ReadOneByte(0x00); // 从地址0x00读取一个字节 u8 val_0x01 = AT24C02_ReadOneByte(0x01); // 从地址0x01读取一个字节 u8 val_0x02 = AT24C02_ReadOneByte(0x02); // 从地址0x02读取一个字节 printf("val_0x00=%d, val_0x01=%d, val_0x02=%d\n", val_0x00, val_0x01, val_0x02); // 打印读取的结果 delay_ms(1000); } } ``` 希望能够帮助您!如果您有更多问题,请随时和我联系。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leung_ManWah

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值