当前位置: 首页 > news >正文

【STM32】HAL库中的实现(八):I2C通信(以 AT24C02 为例)

本系列文章将HAL库与STM32CubeMX结合在一起讲解,并在此之前提供了基于标准库底层原理讲解,使您基于标准库完全理解各个模块的详细底层实现原理,也可以基于HAL库更快速的学会各个模块的使用方法。

资料参考:
STM32 I2C HAL 官方文档
AT24C02 Datasheet
以及结合本文提供的示例工程:我为你准备好的(可生成) CubeMX + Keil 工程模板


在之前的标准库中,STM32的硬件IIC非常复杂,而且它并不稳定,所以都不推荐使用。

但是在当前的HAL库中,对硬件IIC做了全新的优化,使得之前软件IIC几百行代码,在HAL库中,只需要寥寥几行就可以完成。

通过本篇博客您可以学到:

  1. IIC的基本原理讲解
  2. STM32CubeMX创建IIC例程(CubeMX 配置)
  3. 硬件连接
  4. HAL库IIC函数库(HAL 代码实现(含读写操作))
  5. AT24C02 芯片原理
  6. 调试与注意事项

工具条件:

  1. 芯片: STM32F103ZET6
  2. STM32CubeMx软件
  3. IDE: MDK-Keil软件
  4. STM32F1xx/STM32F4xxHAL库
  5. IIC: 使用硬件IIC1
  6. 原理图 和 芯片手册说明书

I2C的原理之前有写过,这里仅做HAL库的应用,如果需要复习请移步至:【STM32】IIC通信(提供完整实例代码)


IIC例程(由于此前的文章已经完整介绍了IIC的原理,这里借用HAL库实现IIC,我主要讲述的重点是编程代码的实现思路),如果想要更进一步理解IIC的详情,也可以移步至下面提供的博客转载链接之中,很多大佬们都已经写的非常专业了,此处无需重复。正好借此机会,我向大家介绍一下编程中隐性的编程思维。在你未来的工作中可以事半功倍,提升效率。

首先,用别人的代码不要直接拷贝,先看懂他实现了什么,代码的实现逻辑。我们一般在用别人代码的时候,先只是大概的看懂他做了什么,但拷贝代码进入自己的项目中时,我们还需要更进一步确定代码详细的实现逻辑。(如下图所示)

在这里插入图片描述

说的极端一点,你根本就不需要知道这个代码的内部具体是怎么实现的(但是必须能看懂),这种东西有很多很多,你只需要去移植别人写好的代码实现功能就可以了。 这篇文章正好提供了I2C的示例代码,你可以移植我的,也可以移植别人的,这些资源在网上到处都是,但你仍然需要看懂代码的内部逻辑和它具体实现了什么功能、是如何实现的。

同理,在实现其他的需求和功能、函数调用的过程,也与这里的IIC实现同理。

这里的HAL库实现IIC很简单,在CubeMX上把配置好底层,然后在网上找一篇写得比较好的代码或者问问AI,然后用别人已经写好的代码例程测试一下,这样IIC(别的知识点也同理)就能跑起来了。如果说你有自己的需求实现和别人的代码逻辑不一样,先把别人的代码跑起来,然后把他的逻辑看明白,再改成自己的逻辑跑一遍就完成了IIC部分对应的功能。重要的是这其中蕴藏的编程思路是最有价值的。

另外,收集资源不可以乱收集,分门别类的框架规整清楚。代码逻辑完善,搭建系统性思维框架。这样,学完了一圈之后,你之后做项目的时候需要什么,要用到什么的时候,把之前学过的瞟一眼拿过来直接用上即可,可以极大的提升工作效率。编程切忌不要陷入技术的死局。


好了,我们继续,这篇文章的IIC实现是从 STM32CubeMX 配置 → I2C外设初始化 → I2C读写 EEPROM → 串口输出验证 的全流程。

一、项目目标

使用 STM32 的 硬件 I2C 接口,通过 HAL 库驱动,读写 EEPROM(AT24C02 / AT24C04),并通过串口打印数据。

二、整体框架结构

STM32 I2C1 (PB6 PB7)↓
AT24C02 EEPROM(0xA0/0xA1)↓
串口 USART1 → 上位机串口助手

三、硬件连接说明看原理图)

首先在原理图上找到芯片的I2C引脚,及其控制的模块(此处为AT24C04 )

在这里插入图片描述

请添加图片描述

请添加图片描述
硬件连接说明:

AT24C02 引脚			说明					连接
VCC					电源					3.3V
GND					地					GND
SDA					数据线				STM32 → PB7(I2C1_SDA)
SCL					时钟线				STM32 → PB6(I2C1_SCL)
WP					写保护				GND(允许写入)
A0~A2				器件地址设置			接地(默认地址 0xA0/0xA1)注:✅ SDA/SCL 需上拉电阻(一般10kΩ)

☆ 扩展知识

如果你自己在编程过程中遇到了不懂的内容,需要自己去查阅 补充知识储备 即学即用):

AT24C04 模块是一种常见的 I²C接口的 EEPROM(电可擦除可编程只读存储器),主要用于在嵌入式系统中存储小容量的非易失性数据。它可以用来保存配置参数、设置、用户数据等信息,即使在断电后数据也不会丢失。

24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。在24C02里面有一个8字节的页写缓冲器。

在这里插入图片描述

  • A0,A1,A2:硬件地址引脚
  • WP:写保护引脚,接高电平只读,接地允许读和写
  • SCL和SDA:IIC总线

可以通过存储IC的型号来计算芯片的存储容量是多大,比如24C02后面的02表示的是可存储2Kbit的数据,转换为字节的存储量为21024/8 = 256byte;那么24C04后面的04表示的是可存储4Kbit的数据,转换为字节的储存量为41024/8 = 512byte;以此来类推其它型号的存储空间。

请添加图片描述

AT24C04模块的主要作用包括:

数据存储:
AT24C04 模块可以用来存储配置参数、设置、用户数据等,这些数据在设备断电后仍然需要保存。
掉电数据保护:
由于EEPROM的特性,AT24C04模块可以在断电的情况下保持数据,这对于需要保存配置信息或状态数据的应用非常重要。
I²C通信:
AT24C04 模块使用I²C 接口与主控制器进行通信,这是一种简单易用的串行通信协议,适合于在嵌入式系统中使用。
容量适中:
AT24C04 模块提供4Kb 的存储容量,对于一些只需要少量数据存储的应用来说足够。
易于使用:
AT24C04 模块通常采用8 脚封装,使用方便,易于在电路板上进行集成。

AT24C04模块在嵌入式系统中扮演着数据存储和掉电保护的重要角色,确保重要数据在设备断电后能够被保留,并且可以通过I²C接口方便地进行访问和管理

如果您希望了解更多此模块的具体信息,可以查阅资料 (自整理,此处链接皆为转载!尊重原作者,侵删!)

AT24C02 E2PROM芯片详解

利用AT24C02进行数据存储

AT24C02存储器的基本原理与应用

关于E2PROM


⚙️ 四、CubeMX 配置要点(见图)

  1. RCC 设置(设置高速外部时钟HSE 选择外部时钟源)
    • 开启 HSE 外部晶振(8MHz)
    • PLL ×9 → SYSCLK = 72MHz
    • APB1 = 36MHz(I2C 属于 APB1)

在这里插入图片描述

时钟源设置:
在这里插入图片描述

我的配置是 外部晶振为8MHz

  1. 选择外部时钟HSE 8MHz
  2. PLL锁相环倍频9倍
  3. 系统时钟来源选择为PLL
  4. 设置APB1分频器为 / 2
  5. 使能CSS监视时钟

32的时钟树框图不懂的话可以移步到: 《【STM32】系统时钟RCC详解(超详细,超全面)》 (转载,侵删!)

  1. IIC设置: I2C1 设置为I2C: 因为我们的硬件IIC 芯片一般都是主设备,也就是一般情况设置主模式即可)

项目设置:
模式Master  features  主模式特性
SDA / SCL:PB7 / PB6
IIC模式设置I2C Speed Mode:快速模式和标准模式。实际上也就是速率的选择。
时钟频率I2C Clock Speed:I2C传输速率,默认为100KHz
地址长度:7位( Primary Address Length selection: 从设备地址长度 设置从设备的地址是7bit还是10bit 大部分为7bit)—— Dual Address Acknowledged: 双地址确认
从设备初始地址:Primary slave address: 保持默认 
扩展模式Clock No Stretch Mode: 时钟没有扩展模式
IIC时钟拉伸(Clock stretching):默认启用(推荐)( clock stretching 通过将SCL线拉低来暂停一个传输。直到释放SCL线为高电平,传输才继续进行。clock stretching 是可选的,实际上大多数从设备不包括SCL驱动,所以它们不能 stretch 时钟。)

在这里插入图片描述

  1. 串口 USART1 设置( 因为我们需要将AT24C02中存储的数据发送到上位机上,所以需要设置下串口)

项目设置:
模式:异步
波特率:115200
TX / RX:PA9 / PA10

在这里插入图片描述
配置好了之后,然后点击GENERATE CODE 创建工程。


五、I2C HAL 库函数说明

i2c.c文件中可以看到IIC初始化函数。在stm32f1xx_hal_i2c.h头文件中可以看到I2C的操作函数。分别对应轮询,中断和DMA三种控制方式。 文件中的函数看起来多,但是只是发送和接收的方式改变了,函数的参数和本质功能并没有改变。比方说,IIC发送函数 还是发送函数,只不过有普通发送,DMA传输,中断 的几种发送模式。

这里我们使用的是普通发送,用其他的方式发送接收数据只是改下函数名即可。

  1. 写寄存器(带子地址)
HAL_I2C_Mem_Write(&hi2c1, DevAddr, MemAddr, I2C_MEMADD_SIZE_8BIT, data, size, timeout);

功能:IIC写数据

参数含义
DevAddrI2C设备地址,如 0xA0
MemAddrEEPROM 内部存储地址
data写入数据指针
size写入字节数
timeout超时时间(单位 ms)
  1. 读寄存器(带子地址)
HAL_I2C_Mem_Read(&hi2c1, DevAddr, MemAddr, I2C_MEMADD_SIZE_8BIT, data, size, timeout);

功能:IIC读一个字节

参数含义
*hi2c设置使用的是那个IIC 例:&hi2c2
DevAddress写入的地址 设置写入数据的地址 例 0xA0
*pData需要写入的数据
Size要发送的字节数
Timeout最大传输时间,超过传输时间将自动退出传输函数

Timeout: 最大读取时间,超过时间将自动退出读取函数

📜 六、主程序代码逻辑(main.c

✅ 定义区

#define AT24C02_ADDR 0xA0
#define BufferSize 256uint8_t WriteBuffer[BufferSize];
uint8_t ReadBuffer[BufferSize];

✅ 按页写入 EEPROM(每页 8 字节)

for (int i = 0; i < BufferSize; i++) {WriteBuffer[i] = i;
}for (int j = 0; j < 32; j++) {HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR, 8 * j, I2C_MEMADD_SIZE_8BIT, &WriteBuffer[8 * j], 8, 1000);HAL_Delay(10); // 写后必须延时,确保写周期完成
}
  • EEPROM 写入后有内部写周期(大约 5~10ms),必须延时。
  • 一页写入不能超过 8 字节(AT24C02 结构限制)。

✅ 3. 一次性读取全部数据

HAL_I2C_Mem_Read(&hi2c1, 0xA1, 0, I2C_MEMADD_SIZE_8BIT, ReadBuffer, 256, 1000);
  • 读取设备地址为 0xA1(0xA0 + 读位)

✅ 4. 打印数据到串口

for (int i = 0; i < BufferSize; i++) {printf("0x%02X ", ReadBuffer[i]);if ((i + 1) % 16 == 0)printf("\r\n");
}

📷 下图中串口调试助手成功显示从 EEPROM 读出 0~255 的数据。
理想中的数据打印(效果):

0x00 0x01 0x02 ... 0x0F
0x10 0x11 0x12 ... 0x1F
...
0xF0 0xF1 ... 0xFF数据如此输出打印,说明数据写入 & 读取成功
注意事项:AT24C02的IIC每次写之后要延时一段时间才能继续写 每次写之后要delay 5ms左右 不管硬件IIC采用何种形式(DMA,IT),都要确保两次写入的间隔大于5ms;读写函数最后一个超时调整为1000以上 因为我们一次写8个字节,延时要久一点AT24C02页写入只支持8个byte,所以需要分32次写入。这不是HAL库的bug,而是AT24C02的限制,其他的EEPROM可以支持更多byte的写入。注意读取AT24C02数据的时候延时也要久一点,否则会造成读的数据不完整

当然,你也可以每次写一个字节,分成256次写入,也是可以的↓

 // wrinte date to EEPROM 如果要一次写一个字节,写256次,用这里的代码for(i=0;i<BufferSize;i++) { HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, i, I2C_MEMADD_SIZE_8BIT,&WriteBuffer[i],10xff);//使用I2C块读,出错。因此采用此种方式,逐个单字节写入 HAL_Delay(5);//此处延时必加,与AT24C02写时序有关 } printf("\r\n EEPROM 24C02 Write Test OK \r\n"); 
完整代码示例
/* USER CODE BEGIN Header */
/********************************************************************************* @file           : main.c* @brief          : Main program body******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD *//* USER CODE END PTD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV */
#include <string.h>#define ADDR_24LCxx_Write 0xA0
#define ADDR_24LCxx_Read 0xA1
#define BufferSize 256uint8_t WriteBuffer[BufferSize],ReadBuffer[BufferSize];
uint16_t i;/* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 *//* USER CODE END 0 *//*** @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_GPIO_Init();MX_USART1_UART_Init();MX_I2C1_Init();/* USER CODE BEGIN 2 */HAL_UARTEx_ReceiveToIdle_IT(  &huart1 , U1RxData, U1RxDataSize);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){for(i=0; i<256; i++)WriteBuffer[i]=i;    /* WriteBuffer init */printf("\r\n***************I2C Example Z小旋测试*******************************\r\n");for (int j=0; j<32; j++){ if(HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, 8*j, I2C_MEMADD_SIZE_8BIT,WriteBuffer+8*j,8, 1000) == HAL_OK){ printf("\r\n EEPROM 24C02 Write Test OK \r\n");HAL_Delay(20);}else{ HAL_Delay(20);printf("\r\n EEPROM 24C02 Write Test False \r\n");}}/* // wrinte date to EEPROM 如果要一次写一个字节,写256次,用这里的代码 for(i=0;i<BufferSize;i++) { HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, i, I2C_MEMADD_SIZE_8BIT,&WriteBuffer[i],1,0xff);//使用I2C块读,出错。因此采用此种方式,逐个单字节写入 HAL_Delay(5);//此处延时必加,与AT24C02写时序有关 } printf("\r\n EEPROM 24C02 Write Test OK \r\n"); */HAL_I2C_Mem_Read(&hi2c1, ADDR_24LCxx_Read, 0, I2C_MEMADD_SIZE_8BIT,ReadBuffer,BufferSize, 0xff);for(i=0; i<256; i++)printf("0x%02X ",ReadBuffer[i]);/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK){Error_Handler();}
}/* USER CODE BEGIN 4 *//* USER CODE END 4 *//*** @brief  This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}#ifdef  USE_FULL_ASSERT
/*** @brief  Reports the name of the source file and the source line number*         where the assert_param error has occurred.* @param  file: pointer to the source file name* @param  line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

串口打印测试:

在这里插入图片描述


七、出现问题调试代码

可能出现的问题解决方法
写入失败检查延时(HAL_Delay(5~10))是否足够
数据错乱写入页大小超过8字节?
I2C 无响应看是否接好上拉电阻
地址不对注意设备地址是否左移过(HAL会自动处理)
波特率不匹配串口助手与 MCU 设置一致

综上所述,HAL I2C 优点显而易见:简洁、稳定、可扩展、代码量小,IIC通信的应用场景也很多,常用于EEPROM、传感器、OLED、RTC 等,EEPROM可存储掉电保存数据,不过要注意写入的时候(HAL_I2C_Mem_Write() / HAL_I2C_Mem_Read()),每页最多 8 字节,需延时写周期;写完代码后编译烧录代码,使用串口输出 + 示波器分析 SDA/SCL查看代码实现的效果。

IIC的更多应用场景
应用技术点
OLED 显示 EEPROM 内容I2C 驱动 SSD1306
多 EEPROM 管理改变 A0~A2 地址
EEPROM 写保护控制 WP 引脚
EEPROM 数据加密加密写入,验证读取
EEPROM + 实时时钟保存掉电时间记录

HAL 库下的 I2C 通信,配合 CubeMX 简化了硬件 I2C 的使用,稳定性大幅提升,在工业控制中应用广泛。

http://www.dtcms.com/a/342132.html

相关文章:

  • CentOS系统安装Git全攻略
  • 面试准备革命:面试汪 vs 传统方法,谁更胜一筹?
  • 「数据获取」《中国环境统计年鉴》(1998-2024)(获取方式看绑定的资源)
  • Linux命令大全-userdel命令
  • awk 命令的使用
  • 《P2700 逐个击破》
  • Design Compiler:逻辑库名与逻辑库文件名及其指定方式
  • 自学嵌入式第二十四天:数据结构(4)-栈
  • JetBrains Mono字体
  • Django ModelForm
  • 用 Python 写的自动化测试 WPF 程序的一个案例
  • Jmeter接口测试之文件上传
  • XXL-Job REST API 工具类完全解析:简化分布式任务调度集成
  • WebSocket和跨域问题
  • Android为ijkplayer设置音频发音类型usage
  • 如何用 SolveigMM Video Splitter 从视频中提取 AAC 音频
  • CMake3: CMake的嵌套使用与自定义库
  • Spring Event 企业级应用
  • 笔试——Day45
  • Prompt魔法:提示词工程与ChatGPT行业应用读书笔记:提示词设计全能指南
  • 第四章:大模型(LLM)】07.Prompt工程-(7)角色提示
  • Flink基础
  • 解锁工业级Prompt设计,打造高准确率AI应用
  • Web自动化测试:测试用例流程设计
  • Java设计模式-解释器模式
  • 策略模式 vs 适配器模式
  • 基于STM32设计的大棚育苗管理系统(4G+华为云IOT)_265
  • 移动应用抓包与调试实战 Charles工具在iOS和Android中的应用
  • 数据结构初阶:详解二叉树(三):链式二叉树
  • system\core\init\init.cpp----LoadBootScripts()解析init.rc(1)