细说STM32F407单片机通过IIC读写EEPROM 24C02

目录

一、操作说明

二、工程配置

[1、时钟、DEBUG、GPIO、USART6、NVIC、Code Generator](#1、时钟、DEBUG、GPIO、USART6、NVIC、Code Generator)

[2、 IIC2](#2、 IIC2)

[(1)Master Features组,主设备参数](#(1)Master Features组,主设备参数)

[(2)Slave Features组,从设备参数](#(2)Slave Features组,从设备参数)

三、软件设计

1、KELED

2、EEPROM

(1)24cxx.c

(2)24cxx.h

3、main.c

四、下载与运行


本文旨在通过使用STM32F407的IIC总线读写 EEPROM 24C02。

一些细节可以参考本文作者写的其他文章。

参考文章1:细说STM32F407单片机轮询方式读写SPI FLASH W25Q16BV-CSDN博客 https://wenchm.blog.csdn.net/article/details/144587209

参考文章2,细说STM32F407单片机IIC总线基础知识-CSDN博客 https://wenchm.blog.csdn.net/article/details/144717358

一、操作说明

项目使用旺宝红龙开发板,STM32F407ZGT6 KIT V1.0。 使用开发板上的按键S2、S3、S4、S5,依次按下诸键后执行如下操作,并且使用开发板上的LED灯,依次显示操作状态。S6是复位键。

cpp 复制代码
[S2]KeyUp   = Write a number     LED1
[S3]KeyDown = Read the number    LED2
[S4]KeyLeft = Write a string     LED3
[S5]KeyRight= Read the string    LED4

二、工程配置

1、时钟、DEBUG、GPIO、USART6、NVIC、Code Generator

与参考文章2相同。

I2C的中断事件主要是表示传输过程和错误的一些事件,由于I2C通信是一种应答式通信,与其他外设的轮询式操作类似,本例不开启I2C2的中断。I2C也具有DMA功能,但是24C02操作的数据量小,没有使用DMA的必要。如果需要使用I2C接口的中断或DMA数据传输功能,可看参考文章中介绍的中断方式和DMA方式相关函数。

2、 IIC2

占用管脚PF0、PF1。默认全部设置。

(1)Master Features组,主设备参数

  • I2C Speed Mode,速度模式。可选标准模式(Standard Mode)或快速模式(Fast Mode)。
  • I2C Clock Speed (Hz),I2C时钟速度。标准模式最大值为100kHz,快速模式最大值为400kHz。
  • Fast Mode Duty Cycle,快速模式占空比。选择快速模式后这个参数会出现,用于设置时钟信号的占空比,是一个周期内低电平与高电平的时间比,有2:1和16:9两种选项。

本示例中,速度模式选择标准模式。

(2)Slave Features组,从设备参数

  • Clock No Stretch Mode,禁止时钟延长。设置为Disabled表示允许时钟延长。
  • Primary AddressLength selection,设备主地址长度。可选7-bit或10-bit,本例选择7-bit。
  • DualAddress Acknowledge,双地址确认。从设备可以有两个地址,如果设置为Enabled。还会出现一个Secondary slave address参数,用于设置从设备副地址。
  • Primary slave address,从设备主地址。设置从设备主地址,作为I2C从设备时才需要设置。
  • General Call address detection,广播呼叫检测。设置为Disabled表示禁止广播呼叫,不对地址0x00应答;否则,就是允许广播呼叫,对地址0x00应答。

STM32F407是I2C主设备,无须设置从设备地址。24C02是I2C从设备,其从设备地址是0xA0。

三、软件设计

1、KELED

keyled.c和keyled.h与参考文章相同。

2、EEPROM

在项目中创建EEPROM文件夹,并在其中设计24cxx.c和24cxx.h。

为便于程序的移植,在文件24cxx.h中定义一个宏I2C_HANDLE替代hi2c2。如果使用了不同的I2C接口,只需修改这个宏定义即可,而I2C接口的外设初始化由CubeMX自动生成的函数完成。在24cxx.h中还定义了表示24C02地址的宏DEV_ADDR_24CXX,如果实际电路中的24C02的I2C地址被修改了,修改这个宏即可。

在文件24cxx.h中定义了5个函数,前面4个都是直接封装I2C的HAL传输函数实现的,最后1个函数EP24C_WriteLongData()能自动将一个长的数据拆分为多个页(每页8字节)写入,这5个函数都使用I2C的阻塞式存储器数据传输函数。

(1)24cxx.c

cpp 复制代码
/* 文件: 24cxx.c
 * 描述: 24C02驱动程序源程序文件
 */

#include "24cxx.h"
#define	EP24C_TIMEOUT 200						//超时等待时间,单位:ms
#define	EP24C_MEMADD_SIZE I2C_MEMADD_SIZE_8BIT  //存储器地址大小,8位地址

//检查设备是否准备好I2C通讯,返回HAL_OK 表示OK
HAL_StatusTypeDef EP24C_IsDeviceReady(void)
{
	uint32_t Trials=10;		//尝试次数
	HAL_StatusTypeDef result=HAL_I2C_IsDeviceReady(&I2C_HANDLE,DEV_ADDR_24CXX,Trials,EP24C_TIMEOUT);
	return result;
}

//向任意地址写入一个字节的数据, memAddr是存储器内部地址,byteData是需要写入的1个字节的数据
HAL_StatusTypeDef EP24C_WriteOneByte(uint16_t memAddress,uint8_t byteData)
{
	HAL_StatusTypeDef result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
			EP24C_MEMADD_SIZE,&byteData,1,EP24C_TIMEOUT);
	return	result;
}

//从任意地址读出一个字节的数据, memAddr是存储器内部地址,byteData是读出的1个字节的数据,若返回HAL_OK表示读取成功
HAL_StatusTypeDef EP24C_ReadOneByte(uint16_t memAddress,uint8_t *byteData)
{
	HAL_StatusTypeDef result=HAL_I2C_Mem_Read(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
			EP24C_MEMADD_SIZE,byteData,1,EP24C_TIMEOUT);
	return result;
}

//连续读取数据,任意地址,任意长度,不受页的限制
HAL_StatusTypeDef EP24C_ReadBytes(uint16_t memAddress,uint8_t *pBuffer,uint16_t bufferLen)
{
	if(bufferLen>MEM_SIZE_24CXX)	//超过总存储容量
		return HAL_ERROR;

	HAL_StatusTypeDef result=HAL_I2C_Mem_Read(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
			EP24C_MEMADD_SIZE,pBuffer,bufferLen,EP24C_TIMEOUT);
	return result;
}

//限定在一个页内写入连续数据,最多8字节。任意起始地址,但是起始地址+数据长度不能超过页边界
HAL_StatusTypeDef EP24C_WriteInOnePage(uint16_t memAddress,uint8_t *pBuffer,uint16_t bufferLen)
{
	if(bufferLen>PAGE_SIZE_24CXX)	//数据长度不能大于页的大小
		return HAL_ERROR;

	HAL_StatusTypeDef result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
			EP24C_MEMADD_SIZE,pBuffer,bufferLen,EP24C_TIMEOUT);
	return result;
}

//写任意长的数据,可以超过8字节,但数据地址必须从页首开始,即 8*N。自动分解为多次写入
HAL_StatusTypeDef EP24C_WriteLongData(uint16_t memAddress,uint8_t *pBuffer,uint16_t bufferLen)
{
	if(bufferLen>MEM_SIZE_24CXX)	//超过总存储容量
		return HAL_ERROR;

	HAL_StatusTypeDef result=HAL_ERROR;
	if(bufferLen<=PAGE_SIZE_24CXX)	//不超过1个page,直接写入后退出
	{
		result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
				EP24C_MEMADD_SIZE,pBuffer,bufferLen,EP24C_TIMEOUT);
		return result;
	}

	uint8_t *pt=pBuffer;			//临时指针,不能改变传入的指针
	uint16_t pageCount=bufferLen/PAGE_SIZE_24CXX;	//Page个数
	for(uint16_t i=0; i<pageCount; i++)	//一次写入一个page的数据
	{
		result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
				EP24C_MEMADD_SIZE,pt,PAGE_SIZE_24CXX,EP24C_TIMEOUT);
		pt += PAGE_SIZE_24CXX;
		memAddress += PAGE_SIZE_24CXX;
		HAL_Delay(5);					//必须有延时,以等待页写完
		if (result != HAL_OK)
			return result;
	}

	uint16_t leftBytes=bufferLen % PAGE_SIZE_24CXX;  //余数
	if (leftBytes>0)					//写入剩余的数据
		result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
				EP24C_MEMADD_SIZE,pt,leftBytes,EP24C_TIMEOUT);
	return result;
}

(2)24cxx.h

cpp 复制代码
/*
 *  文件名:24cxx.h
 * 	功能描述: 24C02 EEPROM驱动程序,使用HAL库
 *
 * 	(1)24C02是 256字节EEPROM,可以单个字节读写,连续读写时按页读写,每页8字节。所以,按页读写时最多8字节,页号0--31
 *
 * 	(2)连续写入数据时要注意不要超过页的边界,否则从页的开始处重新写,会覆盖原来的内容。
 *
 * 	(3)24C02地址是 0b1010xxxy, 其中xxx由芯片的地址引脚A2,A1,A0决定,一般接地,所以是0xA0, HAL库在读写时自动在最后一位写0或1进行读或写操作
 *
 * 	(4)24C02的地址数据长度是8位,使用宏定义符号I2C_MEMADD_SIZE_8BIT
 */


#ifndef __24cxx_H
#define __24cxx_H

#include "stm32f4xx_hal.h"
#include "i2c.h" 				//i2c.h中定义了hi2c2
/* 两个与硬件相关的定义,   */
#define I2C_HANDLE hi2c2		//I2C外设对象变量,利用i2c.h中定义的hi2c2
#define	DEV_ADDR_24CXX 0x00A0	//24C02的写地址
// EEPROM存储器参数
#define	PAGE_SIZE_24CXX 0x0008	//24C02的Page大小为8字节
#define	MEM_SIZE_24CXX (uint16_t)256 //24C02总共容量字节数,8*32=256字节

//检查设备是否准备好
HAL_StatusTypeDef EP24C_IsDeviceReady(void);

//在任意地址写入一个字节
HAL_StatusTypeDef EP24C_WriteOneByte(uint16_t memAddress, uint8_t byteData);

//在任意地址读出一个字节
HAL_StatusTypeDef EP24C_ReadOneByte(uint16_t memAddress, uint8_t *byteData);

//连续读取数据,任意地址,任意长度,不受页的限制
HAL_StatusTypeDef EP24C_ReadBytes(uint16_t memAddress, uint8_t *pBuffer, uint16_t bufferLen);

/*限定在一个页内写入连续数据,最多8字节。任意起始地址,
 *但是起始地址+数据长度不能超过页边界,即不能超过地址8*N-1 */
HAL_StatusTypeDef EP24C_WriteInOnePage(uint16_t memAddress, uint8_t *pBuffer, uint16_t bufferLen);

//写任意长的数据,可以超过8字节,但数据地址必须从页首开始,即 8*N
HAL_StatusTypeDef EP24C_WriteLongData(uint16_t memAddress, uint8_t *pBuffer, uint16_t bufferLen);

#endif

3、main.c

cpp 复制代码
/* USER CODE BEGIN Includes */
#include "keyled.h"
#include "24cxx.h"
#include <stdio.h>
/* USER CODE END Includes */
cpp 复制代码
/* USER CODE BEGIN 2 */
  printf("Demo17_1:I2C Interface\r\n");
  printf("24C02:EEPROM, 256 bytes\r\n");
  printf("8 bytes/page, 32 pages\r\n");
  printf("I2C Device Address=0xA0\r\n");
  if (EP24C_IsDeviceReady() == HAL_OK)
  	printf("Device is ready.\r\n");

  //显示菜单
  printf("[S2]KeyUp   = Write a number\r\n");
  printf("[S3]KeyDown = Read the number\r\n");
  printf("[S4]KeyLeft = Write a string\r\n");
  printf("[S5]KeyRight= Read the string\r\n");

  // MCU output low level LED light is on
  LED1_OFF();
  LED2_OFF();
  LED3_OFF();
  LED4_OFF();
  /* USER CODE END 2 */
cpp 复制代码
  /* USER CODE BEGIN WHILE */
  uint8_t num1 = 107,num2;
  uint16_t addr_any = 4;	//任意地址, 0-255
  uint16_t addr_page = 2*8;	//Page2起始地址
  //uint8_t infoStr[50];	//用于生成显示信息字符��?
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	KEYS curKey = ScanPressedKey(KEY_WAIT_ALWAYS);

	switch(curKey)
	{
	  case KEY_UP:
	  {
		if (EP24C_WriteOneByte(addr_any,num1) == HAL_OK)
		  printf("Write %d at Address %d.\r\n",num1,addr_any);
		LED1_ON();
		LED2_OFF();
		LED3_OFF();
		LED4_OFF();
	  }
	  break;

	  case KEY_DOWN:
	  {
		if (EP24C_ReadOneByte(addr_any,&num2) == HAL_OK)
		   printf("Read out %d at Address %d.\r\n",num2,addr_any);
		LED1_OFF();
		LED2_ON();
		LED3_OFF();
		LED4_OFF();
	  }
	  break;

	  case KEY_LEFT:
	  {
		uint8_t strIn[] = "University of Petroleum";	//自动加'\0'
		if (EP24C_WriteLongData(addr_page,strIn,sizeof(strIn)) == HAL_OK)
		  printf("Write string from Page2:%s\r\n",strIn);
		LED1_OFF();
		LED2_OFF();
		LED3_ON();
		LED4_OFF();
	  }
	  break;

	  case KEY_RIGHT:
	  {
		uint8_t strOut[50];
		if (EP24C_ReadBytes(addr_page,strOut,50) == HAL_OK)
		  printf("Read string from Page2:%s\r\n",strOut);
		LED1_OFF();
		LED2_OFF();
		LED3_OFF();
		LED4_ON();
	  }
	  break;

	  case KEY_NONE:
	  {
		LED1_OFF();
		LED2_OFF();
		LED3_OFF();
		LED4_OFF();
	  }
	  break;

	  default:
	  break;
	}
	printf("** Reselect menu or reset **\r\n");
	HAL_Delay(500);	//延时500,消除按键后抖动
  }
  /* USER CODE END 3 */
}

上述程序中有一个I2C_HandleTypeDef类型的结构体变量hi2c2,这是表示I2C2接口的外设对象变量,24C02的驱动程序文件24cxx.c中就使用这个外设对象变量访问I2C2接口。函数MX_I2C2_Init()中对hi2c2的各成员变量赋值,各赋值语句与STM32CubeIDE里的设置是对应的。完成hi2c2的赋值后,执行HAL_I2C_Init(&hi2c2)对I2C2接口进行初始化。

HAL_I2C_MspInit()是I2C接口的MSP初始化函数,在函数HAL_I2C_Init()里被调用。函数HAL_I2C_MspInit()的主要功能是对I2C2接口的复用引脚PF0和PF1进行GPIO引脚配置。

由STM32CubeIDE自动生成的代码,这里省略。

cpp 复制代码
/* USER CODE BEGIN 4 */

//串口打印
int __io_putchar(int ch)
{
	HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);
	return ch;
}
/* USER CODE END 4 */

四、下载与运行

下载,运行。依次按下开发板上的S2、S3、S4、S5按键,执行写入1字节,读出1字节、写入页字符串,读出页字符串。按下S6键,执行复位。

相关推荐
小菜鸟学代码··3 小时前
STM32文件详解
stm32·单片机·嵌入式硬件
马浩同学4 小时前
【GD32】从零开始学GD32单片机 | DAC数模转换器 + 三角波输出例程
c语言·单片机·嵌入式硬件·mcu
最后一个bug7 小时前
STM32MP1linux根文件系统目录作用
linux·c语言·arm开发·单片机·嵌入式硬件
wenchm7 小时前
细说STM32F407单片机IIC总线基础知识
stm32·单片机·嵌入式硬件
嵌入式lover8 小时前
STM32项目之环境空气质量检测系统软件设计
stm32·单片机·嵌入式硬件
kenwblack9 小时前
STM32 SPI读取SD卡
stm32·单片机
兰_博9 小时前
51单片机驱动1602液晶显示
单片机·嵌入式硬件·51单片机
深圳市青牛科技实业有限公司 小芋圆9 小时前
开关电源特点、分类、工作方式
前端·科技·单片机·物联网·分类·数据挖掘·新能源
我qq不是451516529 小时前
单片机优先级
单片机·嵌入式硬件
相醉为友11 小时前
在开发嵌入式系统时,尤其是处理大数时,会遇到取值范围的问题。51单片机通常没有内建大整数支持,因此我们需要采用不同的方法来解决这一问题
单片机·嵌入式硬件·51单片机