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

STM32H750 I2C介绍及应用

第十七章 I2C介绍及应用

1. I2C 简介

I2C(Inter-Integrated Circuit,内部集成电路总线)是 STM32H750VBT6 中用于连接低速外围设备的同步串行通信接口,由飞利浦(NXP)开发,采用 两线制 设计:

  • SDA:串行数据线(Serial Data)
  • SCL:串行时钟线(Serial Clock)

I2C 支持 多主多从架构7 位/10 位地址寻址上拉电阻电平匹配,广泛应用于连接 EEPROM、温度传感器、RTC、OLED 显示屏、音频编解码器 等外设,是嵌入式系统中最常用的低速通信总线之一。

🔍 核心定位

  • I2C ≠ UART/SPI,而是共享总线型、地址可寻的“局域网”

  • 最高通信速率:

    • 标准模式:100 kbps
    • 快速模式:400 kbps
    • 快速模式+:1 Mbps(STM32H7 支持)
  • 支持 DMA 传输超时检测SMBus/PMBus 兼容

  • 可配置为 主模式(Master)或 从模式(Slave)


1.1 I2C 核心特性(STM32H750VBT6)

特性参数说明应用场景
通信速率最高 1 Mbps(快速模式+)支持时钟动态调节高速传感器
地址模式7-bit / 10-bit兼容大多数 I2C 设备多设备挂载
数据方向发送 / 接收支持读写切换EEPROM 读写
DMA 支持✅(TX/RX)零 CPU 开销传输大数据块传输
超时机制BUSTIMEOUT防止总线死锁增强系统可靠性
SMBus/PMBus支持协议级控制电源管理、电池监控
多 I2C 实例I2C1、I2C2、I2C3、I2C4可独立配置为主/从多设备系统
时钟源APB1(100 MHz)通过 PRESC 分频生成 SCL精确波特率控制

📌 STM32H750VBT6 专属优势

  • 支持时钟延展(Clock Stretching):从设备可拉低 SCL 要求延时
  • 硬件 CRC 生成:SMBus 模式下自动生成 PEC 校验
  • 双地址模式:可响应两个从地址(OAR2
  • 噪声滤波:可配置数字滤波器抑制毛刺
  • 与 RTC/ADC 协同:可用于传感器数据采集系统

1.2 I2C 工作原理详解

1.2.1 通信协议时序
MasterSlaveSTART (SDA↓ while SCL=H)7-bit Address + R/W bitACK (SDA↓)Data ByteACKData ByteACK/NACKalt[写操作][读操作]STOP (SDA↑ while SCL=H)MasterSlave
  • 关键信号定义

    • START:SDA 从高到低,SCL 为高
    • STOP:SDA 从低到高,SCL 为高
    • ACK:第 9 个时钟周期,SDA 被拉低
    • NACK:SDA 保持高电平
  • 7-bit 地址格式

    [ D7 D6 D5 D4 D3 D2 D1 | R/W ]地址 (7-bit)     | 0=Write, 1=Read
    
1.2.2 通信模式
模式说明典型应用
主发送MCU 发送数据到从设备写 EEPROM、配置寄存器
主接收MCU 从从设备读取数据读传感器数据
从发送MCU 作为从设备发送多主通信、桥接器
从接收MCU 接收主设备数据命令接收、配置更新
1.2.3 波特率生成
  • I2C 波特率由 TIMINGR 寄存器控制,计算复杂:

    • 包含 SCL 高/低电平时间数据建立/保持时间
  • 公式(简化):

    在这里插入图片描述

  • 推荐使用 STM32CubeMX 自动生成 TIMINGR 值


1.3 关键寄存器操作

1.3.1 I2C 主要寄存器
寄存器关键位域功能说明
CR1PE, TXIE, RXIE, NACKIE, GCEN, SBC使能、中断控制PE=1 启用 I2C
CR2SADD[9:0], RD_WRN, NBYTES, RELOAD, START, STOP, AUTOEND目标地址、方向、数据量写此寄存器触发传输
TIMINGRPRESC, SCLL, SCLH, SDADEL, SCLDEL时序配置决定波特率
ISRTXIS, RXNE, TC, TCR, BERR, ARLO状态标志必须在 ISR 中读取
TXDRTXDATA[7:0]发送数据寄存器写入即启动发送
RXDRRXDATA[7:0]接收数据寄存器只读
ICRBERRCF, ARLOCF清除错误标志写 1 清除
1.3.2 配置步骤(I2C1 主模式,400 kbps)
// 1. 使能 I2C1 时钟和 GPIO 时钟
RCC->APB1LENR |= RCC_APB1LENR_I2C1EN;
RCC->AHB4ENR  |= RCC_AHB4ENR_GPIOBEN;// 2. 配置 PB6= SCL, PB7= SDA 为开漏复用
GPIOB->MODER   |= GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1;
GPIOB->OTYPER  |= GPIO_OTYPER_OT6 | GPIO_OTYPER_OT7; // 开漏
GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEED6 | GPIO_OSPEEDER_OSPEED7; // 高速
GPIOB->PUPDR   |= GPIO_PUPDR_PUPD6_0 | GPIO_PUPDR_PUPD7_0; // 上拉
GPIOB->AFR[0]  |= 4 << 24 | 4 << 28; // PB6/PB7 = AF4 (I2C1)// 3. 配置 TIMINGR(400 kbps,PCLK1=100MHz)
I2C1->TIMINGR = (1 << 28)  // PRESC = 1| (0x13 << 20) // SCLDEL = 19| (0x10 << 16) // SDADEL = 16| (0x3 << 8)   // SCLH = 3| (0x9 << 0);  // SCLL = 9// 4. 使能 I2C
I2C1->CR1 |= I2C_CR1_PE;
1.3.3 HAL 库简化操作
I2C_HandleTypeDef hi2c1;hi2c1.Instance = I2C1;
hi2c1.Init.Timing = 0x10707DBC; // 400 kHz(由 CubeMX 生成)
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;HAL_I2C_Init(&hi2c1);

1.4 I2C vs SPI vs UART 对比

特性I2CSPIUART
线数23–4(CS可多)2
速率1 Mbps(max)10+ Mbps1–10 Mbps
寻址✅(7/10-bit)❌(靠CS)
主从数量多主多从单主多从点对点
硬件复杂度
抗干扰能力
典型应用传感器、EEPROMFlash、SD卡调试、GPS

💡 选型建议

  • 多设备、低速 → I2C
  • 高速、大数据 → SPI
  • 点对点、调试 → UART

2. I2C使用示例-STM32IDE

2.1 STM32Cube配置

在这里插入图片描述

2.2 用户代码

#include "i2c.h"
#include "delay.h"/*** @brief       初始化IIC* @param       无* @retval      无*/
void iic_init(void)
{GPIO_InitTypeDef gpio_init_struct;IIC_SCL_GPIO_CLK_ENABLE();  /* SCL引脚时钟使能 */IIC_SDA_GPIO_CLK_ENABLE();  /* SDA引脚时钟使能 */gpio_init_struct.Pin = IIC_SCL_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;        /* 推挽输出 */gpio_init_struct.Pull = GPIO_PULLUP;                /* 上拉 */gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 快速 */HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct);/* SCL */gpio_init_struct.Pin = IIC_SDA_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;        /* 推挽输出 */HAL_GPIO_Init(IIC_SDA_GPIO_PORT, &gpio_init_struct);/* SDA *//* SDA引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */iic_stop();     /* 停止总线上所有设备 */
}/*** @brief       IIC延时函数,用于控制IIC读写速度* @param       无* @retval      无*/
static void iic_delay(void)
{delay_us(2);    /* 2us的延时, 读写速度在250Khz以内 */
}/*** @brief       产生IIC起始信号* @param       无* @retval      无*/
void iic_start(void)
{IIC_SDA(1);IIC_SCL(1);iic_delay();IIC_SDA(0);     /* START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 */iic_delay();IIC_SCL(0);     /* 钳住I2C总线,准备发送或接收数据 */iic_delay();
}/*** @brief       产生IIC停止信号* @param       无* @retval      无*/
void iic_stop(void)
{IIC_SDA(0);     /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */iic_delay();IIC_SCL(1);iic_delay();IIC_SDA(1);     /* 发送I2C总线结束信号 */iic_delay();
}/*** @brief       等待应答信号到来* @param       无* @retval      1,接收应答失败*              0,接收应答成功*/
uint8_t iic_wait_ack(void)
{uint8_t waittime = 0;uint8_t rack = 0;IIC_SDA(1);     /* 主机释放SDA线(此时外部器件可以拉低SDA线) */iic_delay();IIC_SCL(1);     /* SCL=1, 此时从机可以返回ACK */iic_delay();while (IIC_READ_SDA)    /* 等待应答 */{waittime++;if (waittime > 250){iic_stop();rack = 1;break;}}IIC_SCL(0);     /* SCL=0, 结束ACK检查 */iic_delay();return rack;
}/*** @brief       产生ACK应答* @param       无* @retval      无*/
void iic_ack(void)
{IIC_SDA(0);     /* SCL 0 -> 1  时 SDA = 0,表示应答 */iic_delay();IIC_SCL(1);     /* 产生一个时钟 */iic_delay();IIC_SCL(0);iic_delay();IIC_SDA(1);     /* 主机释放SDA线 */iic_delay();
}/*** @brief       不产生ACK应答* @param       无* @retval      无*/
void iic_nack(void)
{IIC_SDA(1);     /* SCL 0 -> 1  时 SDA = 1,表示不应答 */iic_delay();IIC_SCL(1);     /* 产生一个时钟 */iic_delay();IIC_SCL(0);iic_delay();
}/*** @brief       IIC发送一个字节* @param       data: 要发送的数据* @retval      无*/
void iic_send_byte(uint8_t data)
{uint8_t t;for (t = 0; t < 8; t++){IIC_SDA((data & 0x80) >> 7);    /* 高位先发送 */iic_delay();IIC_SCL(1);iic_delay();IIC_SCL(0);data <<= 1;     /* 左移1位,用于下一次发送 */}IIC_SDA(1);         /* 发送完成, 主机释放SDA线 */
}/*** @brief       IIC读取一个字节* @param       ack:  ack=1时,发送ack; ack=0时,发送nack* @retval      接收到的数据*/
uint8_t iic_read_byte(uint8_t ack)
{uint8_t i, receive = 0;for (i = 0; i < 8; i++ )    /* 接收1个字节数据 */{receive <<= 1;  /* 高位先输出,所以先收到的数据位要左移 */IIC_SCL(1);iic_delay();if (IIC_READ_SDA){receive++;}IIC_SCL(0);iic_delay();}if (!ack){iic_nack();     /* 发送nACK */}else{iic_ack();      /* 发送ACK */}return receive;
}
#include "i2c.h"
#include "eeprom.h"
#include "delay.h"/*** @brief       初始化IIC接口* @param       无* @retval      无*/
void at24cxx_init(void)
{iic_init();
}/*** @brief       在AT24CXX指定地址读出一个数据* @param       readaddr: 开始读数的地址* @retval      读到的数据*/
uint8_t at24cxx_read_one_byte(uint16_t addr)
{uint8_t temp = 0;iic_start();                /* 发送起始信号 *//* 根据不同的24CXX型号, 发送高位地址* 1, 24C16以上的型号, 分2个字节发送地址* 2, 24C16及以下的型号, 分1个低字节地址 + 占用器件地址的bit1~bit3位 用于表示高位地址, 最多11位地址*    对于24C01/02, 其器件地址格式(8bit)为: 1  0  1  0  A2  A1  A0  R/W*    对于24C04,    其器件地址格式(8bit)为: 1  0  1  0  A2  A1  a8  R/W*    对于24C08,    其器件地址格式(8bit)为: 1  0  1  0  A2  a9  a8  R/W*    对于24C16,    其器件地址格式(8bit)为: 1  0  1  0  a10 a9  a8  R/W*    R/W      : 读/写控制位 0,表示写; 1,表示读;*    A0/A1/A2 : 对应器件的1,2,3引脚(只有24C01/02/04/8有这些脚)*    a8/a9/a10: 对应存储整列的高位地址, 11bit地址最多可以表示2048个位置,可以寻址24C16及以内的型号*/if (EE_TYPE > AT24C16)      /* 24C16以上的型号, 分2个字节发送地址 */{iic_send_byte(0XA0);    /* 发送写命令, IIC规定最低位是0, 表示写入 */iic_wait_ack();         /* 每次发送完一个字节,都要等待ACK */iic_send_byte(addr >> 8);/* 发送高字节地址 */}else{iic_send_byte(0XA0 + ((addr >> 8) << 1));   /* 发送器件 0XA0 + 高位a8/a9/a10地址,写数据 */}iic_wait_ack();             /* 每次发送完一个字节,都要等待ACK */iic_send_byte(addr % 256);  /* 发送低位地址 */iic_wait_ack();             /* 等待ACK, 此时地址发送完成了 */iic_start();                /* 重新发送起始信号 */iic_send_byte(0XA1);        /* 进入接收模式, IIC规定最低位是0, 表示读取 */iic_wait_ack();             /* 每次发送完一个字节,都要等待ACK */temp = iic_read_byte(0);    /* 接收一个字节数据 */iic_stop();                 /* 产生一个停止条件 */return temp;
}/*** @brief       在AT24CXX指定地址写入一个数据* @param       addr: 写入数据的目的地址* @param       data: 要写入的数据* @retval      无*/
void at24cxx_write_one_byte(uint16_t addr, uint8_t data)
{/* 原理说明见:at24cxx_read_one_byte函数, 本函数完全类似 */iic_start();                /* 发送起始信号 */if (EE_TYPE > AT24C16)      /* 24C16以上的型号, 分2个字节发送地址 */{iic_send_byte(0XA0);    /* 发送写命令, IIC规定最低位是0, 表示写入 */iic_wait_ack();         /* 每次发送完一个字节,都要等待ACK */iic_send_byte(addr >> 8);/* 发送高字节地址 */}else{iic_send_byte(0XA0 + ((addr >> 8) << 1));   /* 发送器件 0XA0 + 高位a8/a9/a10地址,写数据 */}iic_wait_ack();             /* 每次发送完一个字节,都要等待ACK */iic_send_byte(addr % 256);  /* 发送低位地址 */iic_wait_ack();             /* 等待ACK, 此时地址发送完成了 *//* 因为写数据的时候,不需要进入接收模式了,所以这里不用重新发送起始信号了 */iic_send_byte(data);        /* 发送1字节 */iic_wait_ack();             /* 等待ACK */iic_stop();                 /* 产生一个停止条件 */delay_ms(10);               /* 注意: EEPROM 写入比较慢,必须等到10ms后再写下一个字节 */
}/*** @brief       检查AT24CXX是否正常*   @note      检测原理: 在器件的末地址写如0X55, 然后再读取, 如果读取值为0X55*              则表示检测正常. 否则,则表示检测失败.** @param       无* @retval      检测结果*              0: 检测成功*              1: 检测失败*/
uint8_t at24cxx_check(void)
{uint8_t temp;uint16_t addr = EE_TYPE;temp = at24cxx_read_one_byte(addr); /* 避免每次开机都写AT24CXX */if (temp == 0X55)   /* 读取数据正常 */{return 0;}else    /* 排除第一次初始化的情况 */{at24cxx_write_one_byte(addr, 0X55); /* 先写入数据 */temp = at24cxx_read_one_byte(255);  /* 再读取数据 */if (temp == 0X55)return 0;}return 1;
}/*** @brief       在AT24CXX里面的指定地址开始读出指定个数的数据* @param       addr    : 开始读出的地址 对24c02为0~255* @param       pbuf    : 数据数组首地址* @param       datalen : 要读出数据的个数* @retval      无*/
void at24cxx_read(uint16_t addr, uint8_t *pbuf, uint16_t datalen)
{while (datalen--){*pbuf++ = at24cxx_read_one_byte(addr++);}
}/*** @brief       在AT24CXX里面的指定地址开始写入指定个数的数据* @param       addr    : 开始写入的地址 对24c02为0~255* @param       pbuf    : 数据数组首地址* @param       datalen : 要写入数据的个数* @retval      无*/
void at24cxx_write(uint16_t addr, uint8_t *pbuf, uint16_t datalen)
{while (datalen--){at24cxx_write_one_byte(addr, *pbuf);addr++;pbuf++;}
}
#include "main.h"
#include "eeprom.h"
#include "bsp_init.h"
#include "stdio.h" // For printf function
#include "string.h"void SystemClock_Config(void);
void PeriphCommonClock_Config(void);
static void MPU_Config(void);// 要写入的字符串
const uint8_t text_buf[] = {"STM32 I2C"};
#define text_size sizeof(text_buf)int main(void)
{uint8_t key_value;uint8_t temp_data[text_size];MPU_Config();HAL_Init();SystemClock_Config();PeriphCommonClock_Config();bsp_init();at24cxx_init();while(at24cxx_check()) // 监测不到24c02{HAL_GPIO_TogglePin(LED_RED_Port, LED_RED_Pin);}printf("24C02 Ready!!!\r\n");while (1){key_value = key_scan(0);if(key_value == KEY0_PRES) // 写入数据{printf("Start Write 24C02....\r\n");fflush(stdout);at24cxx_write(0, (uint8_t*)text_buf, text_size);HAL_Delay(5); // 确保写入完成// 验证写入的数据uint8_t verify_data[text_size];at24cxx_read(0, verify_data, text_size);if(memcmp(text_buf, verify_data, text_size) == 0) {printf("24C02 Write Verified OK!\r\n");} else {printf("24C02 Write Verification Failed!\r\n");}fflush(stdout);HAL_GPIO_WritePin(LED_GREEN_Port, LED_GREEN_Pin, RESET);}if(key_value == KEY1_PRES) // 读取数据{printf("Start Read 24C02....\r\n");at24cxx_read(0, temp_data, text_size);printf("The Data Readed Is:  %s", (char*)temp_data);}HAL_Delay(10);}
}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Supply configuration update enable*/HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);/** Configure the main internal regulator output voltage*/__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.HSIState = RCC_HSI_DIV1;RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;RCC_OscInitStruct.LSIState = RCC_LSI_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLM = 2;RCC_OscInitStruct.PLL.PLLN = 240;RCC_OscInitStruct.PLL.PLLP = 2;RCC_OscInitStruct.PLL.PLLQ = 2;RCC_OscInitStruct.PLL.PLLR = 2;RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;RCC_OscInitStruct.PLL.PLLFRACN = 0;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_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK){Error_Handler();}
}/*** @brief Peripherals Common Clock Configuration* @retval None*/
void PeriphCommonClock_Config(void)
{RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};/** Initializes the peripherals clock*/PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_CKPER;PeriphClkInitStruct.CkperClockSelection = RCC_CLKPSOURCE_HSI;if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){Error_Handler();}
}/* USER CODE BEGIN 4 *//* USER CODE END 4 *//* MPU Configuration */void MPU_Config(void)
{MPU_Region_InitTypeDef MPU_InitStruct = {0};/* Disables the MPU */HAL_MPU_Disable();/** Initializes and configures the Region and the memory to be protected*/MPU_InitStruct.Enable = MPU_REGION_ENABLE;MPU_InitStruct.Number = MPU_REGION_NUMBER0;MPU_InitStruct.BaseAddress = 0x0;MPU_InitStruct.Size = MPU_REGION_SIZE_4GB;MPU_InitStruct.SubRegionDisable = 0x87;MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS;MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;HAL_MPU_ConfigRegion(&MPU_InitStruct);/* Enables the MPU */HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);}/*** @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 */

3. I2C相关函数总结(HAL库)

3.1 初始化与配置

  • 核心配置流程(五步关键操作):

    1. 使能时钟(I2C + GPIO)
    2. 配置GPIO复用功能(SCL/SDA)
    3. 初始化I2C参数(时钟/寻址模式等)
    4. 启动I2C外设
    5. 配置NVIC中断(若使用中断模式)
  • HAL_I2C_Init(I2C_HandleTypeDef *hi2c)
    基础配置示例(I2C1 100kHz标准模式):

    // 1. 使能I2C和GPIO时钟
    __HAL_RCC_I2C1_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();// 2. 配置GPIO(PB6=SCL, PB7=SDA)
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;           // 开漏输出
    GPIO_InitStruct.Pull = GPIO_PULLUP;               // 上拉电阻
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;        // 复用功能
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);// 3. 配置I2C参数
    hi2c1.Instance = I2C1;
    hi2c1.Init.Timing = 0x10805E89;                   // 100kHz时序
    hi2c1.Init.OwnAddress1 = 0x00;                    // 无自身地址
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; // 7位寻址
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; // 禁用双地址
    hi2c1.Init.OwnAddress2 = 0x00;
    hi2c1.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; // 禁用广播
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;     // 启用时钟延展
    HAL_I2C_Init(&hi2c1);
    
  • I2C_InitTypeDef 结构体成员说明

    成员说明关键选项H750特殊说明
    Timing时序配置32位配置值需精确计算
    AddressingMode寻址模式7BIT, 10BIT7位最常用
    DualAddressMode双地址ENABLE, DISABLE从机模式用
    GeneralCallMode广播模式ENABLE, DISABLE从机响应0x00
    NoStretchMode时钟延展ENABLE, DISABLE影响从机兼容性
  • 时序参数计算(核心!):

    • 必须使用STM32CubeMX生成精确Timing

    • 关键参数

      模式SCL频率典型Timing值H750时钟
      Standard100kHz0x10805E89PCLK1=100MHz
      Fast400kHz0x00702991PCLK1=100MHz
      Fast+1MHz0x00300B29PCLK1=100MHz
  • GPIO配置要点

    // I2C引脚必须配置为开漏+上拉
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;      // 开漏模式
    GPIO_InitStruct.Pull = GPIO_PULLUP;          // 外部或内部上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;   // 复用功能选择
    

3.2 I2C操作核心函数

  • 主模式操作

    函数原型特点应用场景
    HAL_I2C_Master_Transmit()(hi2c, Addr, *Data, Size, Timeout)阻塞式发送短数据发送
    HAL_I2C_Master_Receive()(hi2c, Addr, *Data, Size, Timeout)阻塞式接收读取传感器
    HAL_I2C_Master_Transmit_IT()(hi2c, Addr, *Data, Size)中断发送非阻塞
    HAL_I2C_Master_Receive_IT()(hi2c, Addr, *Data, Size)中断接收
    HAL_I2C_Master_Transmit_DMA()(hi2c, Addr, *Data, Size)DMA发送大数据量
    HAL_I2C_Master_Receive_DMA()(hi2c, Addr, *Data, Size)DMA接收
  • 从模式操作

    // 配置自身地址
    hi2c1.Init.OwnAddress1 = 0x30;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    HAL_I2C_Init(&hi2c1);// 启动从机接收(中断模式)
    HAL_I2C_Slave_Receive_IT(&hi2c1, rx_buffer, BUFFER_SIZE);// 启动从机发送
    HAL_I2C_Slave_Transmit_IT(&hi2c1, tx_buffer, BUFFER_SIZE);
    
  • 复合操作(读写寄存器)

    // 写寄存器地址+数据
    uint8_t data[2] = {REGISTER_ADDR, DATA_VALUE};
    HAL_I2C_Master_Transmit(&hi2c1, SLAVE_ADDR<<1, data, 2, 100);// 读寄存器(带停止和重新开始)
    HAL_I2C_Master_Transmit(&hi2c1, SLAVE_ADDR<<1, &reg_addr, 1, 100);
    HAL_I2C_Master_Receive(&hi2c1, (SLAVE_ADDR<<1)|0x01, &read_data, 1, 100);// 或使用复合函数
    HAL_I2C_Mem_Write(&hi2c1, SLAVE_ADDR<<1, REG_ADDR, I2C_MEMADD_SIZE_8BIT, &data, 1, 100);
    HAL_I2C_Mem_Read(&hi2c1, SLAVE_ADDR<<1, REG_ADDR, I2C_MEMADD_SIZE_8BIT, &data, 1, 100);
    
  • 状态检查

    // 检查总线是否空闲
    if (HAL_I2C_GetState(&hi2c1) == HAL_I2C_STATE_READY) {// 可以开始新传输
    }// 检查错误
    if (HAL_I2C_GetError(&hi2c1) & HAL_I2C_ERROR_AF) {// 应答错误
    }
    

3.3 高级功能与特性

  • DMA模式传输

    // 启用DMA传输
    __HAL_LINKDMA(&hi2c1, hdmatx, hdma_i2c1_tx);
    __HAL_LINKDMA(&hi2c1, hdmarx, hdma_i2c1_rx);// DMA传输(非阻塞)
    HAL_I2C_Master_Transmit_DMA(&hi2c1, SLAVE_ADDR<<1, tx_buffer, BUFFER_SIZE);// 传输完成回调
    void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
    {if(hi2c == &hi2c1) {transmission_complete = 1;}
    }
    
  • 多主系统处理

    // 检测总线冲突
    if (__HAL_I2C_GET_FLAG(&hi2c1, I2C_FLAG_BUSY)) {// 总线被占用HAL_Delay(1);// 重试机制
    }
    
  • 时钟延展控制

    // 启用时钟延展(从机可拉低SCL)
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;// 禁用时钟延展(提高速度,但兼容性差)
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_ENABLE;
    
  • SMBus模式(系统管理总线):

    // 启用SMBus功能
    HAL_I2CEx_EnableSMBusAlert(&hi2c1);  // 使能报警
    HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE);
    

3.4 使用示例(完整流程)

3.4.1 示例1:I2C主模式读写EEPROM
#define EEPROM_ADDR     0x50
#define EEPROM_PAGE_SIZE 32
uint8_t eeprom_buffer[EEPROM_PAGE_SIZE];// 1. 初始化I2C
void I2C_Init(void)
{__HAL_RCC_I2C1_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();// 配置GPIO (PB8=SCL, PB9=SDA)GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9;GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);// 配置I2Chi2c1.Instance = I2C1;hi2c1.Init.Timing = 0x10805E89;  // 100kHzhi2c1.Init.OwnAddress1 = 0;hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;hi2c1.Init.OwnAddress2 = 0;hi2c1.Init.OwnAddress2Masks = I2C_OA2_NOMASK;hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;HAL_I2C_Init(&hi2c1);
}// 2. 写EEPROM页
HAL_StatusTypeDef EEPROM_Write_Page(uint8_t page_addr, uint8_t *data, uint8_t size)
{uint8_t tx_buffer[EEPROM_PAGE_SIZE + 1];// 第1字节为内存地址tx_buffer[0] = page_addr * EEPROM_PAGE_SIZE;memcpy(&tx_buffer[1], data, size);// 发送数据return HAL_I2C_Master_Transmit(&hi2c1, EEPROM_ADDR << 1, tx_buffer, size + 1, 100);
}// 3. 读EEPROM
HAL_StatusTypeDef EEPROM_Read(uint16_t mem_addr, uint8_t *data, uint8_t size)
{// 先发送内存地址uint8_t addr_byte = (uint8_t)mem_addr;HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(&hi2c1,EEPROM_ADDR << 1,&addr_byte,1,100);if (status != HAL_OK) return status;// 接收数据return HAL_I2C_Master_Receive(&hi2c1,(EEPROM_ADDR << 1) | 0x01,data,size,100);
}// 4. 使用示例
void EEPROM_Example(void)
{I2C_Init();// 写数据for(int i = 0; i < EEPROM_PAGE_SIZE; i++) {eeprom_buffer[i] = i;}EEPROM_Write_Page(0, eeprom_buffer, EEPROM_PAGE_SIZE);// 等待写完成(典型5ms)HAL_Delay(10);// 读数据EEPROM_Read(0, eeprom_buffer, EEPROM_PAGE_SIZE);// 验证for(int i = 0; i < EEPROM_PAGE_SIZE; i++) {if(eeprom_buffer[i] != i) {error_handler();}}
}
3.4.2 示例2:I2C从模式实现
#define SLAVE_ADDR      0x30
#define RX_BUFFER_SIZE  64
uint8_t slave_rx_buffer[RX_BUFFER_SIZE];
uint8_t slave_tx_buffer[RX_BUFFER_SIZE] = "Hello Master!";
volatile uint8_t slave_ready = 0;// 1. 配置从机
void I2C_Slave_Init(void)
{__HAL_RCC_I2C1_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();// 配置GPIO (同上)// 配置I2C为从机hi2c1.Instance = I2C1;hi2c1.Init.Timing = 0x10805E89;hi2c1.Init.OwnAddress1 = SLAVE_ADDR;hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;// 其他参数同主模式HAL_I2C_Init(&hi2c1);// 配置NVICHAL_NVIC_SetPriority(I2C1_EV_IRQn, 1, 0);HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);HAL_NVIC_SetPriority(I2C1_ER_IRQn, 1, 0);HAL_NVIC_EnableIRQ(I2C1_ER_IRQn);// 启动从机接收HAL_I2C_Slave_Receive_IT(&hi2c1, slave_rx_buffer, RX_BUFFER_SIZE);
}// 2. 事件中断服务函数
void I2C1_EV_IRQHandler(void)
{HAL_I2C_EV_IRQHandler(&hi2c1);
}// 3. 错误中断服务函数
void I2C1_ER_IRQHandler(void)
{HAL_I2C_ER_IRQHandler(&hi2c1);
}// 4. 回调函数
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
{if(hi2c == &hi2c1) {// 接收完成slave_ready = 1;// 启动下一次接收HAL_I2C_Slave_Receive_IT(hi2c, slave_rx_buffer, RX_BUFFER_SIZE);// 如果接收到特定命令,准备发送响应if(slave_rx_buffer[0] == 0xFF) {HAL_I2C_Slave_Transmit_IT(hi2c, slave_tx_buffer, 12);}}
}void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c)
{if(hi2c == &hi2c1) {// 发送完成HAL_I2C_Slave_Receive_IT(hi2c, slave_rx_buffer, RX_BUFFER_SIZE);}
}

4. 关键注意事项

  1. 上拉电阻选择
  • 典型值:4.7kΩ

  • 计算公式

    R_pullup > (VDD - VIL) / 3mA
    
  • 高速模式:需减小电阻值(如2.2kΩ)

  1. 时序参数陷阱
  • 必须使用CubeMX生成Timing
  • 手动计算极易出错
  • 不同PCLK1频率需要不同Timing
  1. 地址格式
  • 7位地址:实际传输时左移1位

    // 7位地址0x50 → 传输0xA0(写)或0xA1(读)
    HAL_I2C_Master_Transmit(&hi2c1, 0x50<<1, data, size, timeout);
    
  • 10位地址:使用I2C_ADDRESSINGMODE_10BIT

  1. 总线锁定处理
 // I2C总线锁定恢复程序void I2C_Recovery(void){// 9个时钟脉冲for(int i = 0; i < 9; i++) {HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);HAL_Delay(1);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);HAL_Delay(1);}// 生成停止条件HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);HAL_Delay(1);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);HAL_Delay(1);}
  1. DMA传输陷阱
  • 必须链接DMA句柄

    __HAL_LINKDMA(&hi2c1, hdmatx, hdma_i2c1_tx);
    
  • Cache一致性

    SCB_CleanDCache_by_Addr((uint32_t*)tx_buffer, size);
    SCB_InvalidateDCache_by_Addr((uint32_t*)rx_buffer, size);
    

4.1 H750特有优化技巧

场景解决方案性能提升实现要点
高速通信400kHz Fast Mode速度×4Timing=0x00702991
长距离传输增强上拉可靠性↑总线电容<400pF
多设备系统地址扫描调试方便遍历0x08-0x77
低功耗STOP模式唤醒功耗↓从机模式响应

避坑指南

  1. H750 I2C架构
  • 支持SMBus Alert和PEC校验
  • 可配置数字滤波器抑制噪声
  1. GPIO复用选择
// I2C1可映射到不同引脚
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;  // PB6/PB7// 或
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;  // PC6/PC7
  1. 中断优先级
  • I2C事件中断(EV)优先级 高于 错误中断(ER)
  • 否则错误可能无法及时处理
  1. 生产环境测试
  • 使用逻辑分析仪验证时序
  • 测试最坏情况下的总线负载
  • 验证从机的时钟延展兼容性

4.2 I2C模式性能对比

模式SCL频率Timing配置典型应用
Standard100kHz0x10805E89传感器、EEPROM
Fast400kHz0x00702991高速数据采集
Fast+1MHz0x00300B29视频传输

重要提示

  • I2C是最常用的串行总线,但时序要求严格
  • 上拉电阻是可靠通信的关键
  • 从机模式需要完善的中断处理机制
  • DMA模式可释放CPU资源用于大数据传输

文中代码下载:https://github.com/hazy1k/STM32H7-Quick-Start-Guide-CubeIDE/tree/main/2.code


文章转载自:

http://HQ6lh0xu.wxrbL.cn
http://blSHuJ9A.wxrbL.cn
http://DqmkniSs.wxrbL.cn
http://6yPgM9R7.wxrbL.cn
http://bjSXHXw3.wxrbL.cn
http://NG89LNgE.wxrbL.cn
http://FDfLrvUj.wxrbL.cn
http://r38L47HN.wxrbL.cn
http://KrnuhEpc.wxrbL.cn
http://eD9YJOWH.wxrbL.cn
http://uCbS4arn.wxrbL.cn
http://JG7BFNC5.wxrbL.cn
http://4CE7dv7E.wxrbL.cn
http://C9Up4czh.wxrbL.cn
http://0gk6NRpX.wxrbL.cn
http://XKN6099c.wxrbL.cn
http://pGwRRMZv.wxrbL.cn
http://ZdBbMuM0.wxrbL.cn
http://auE7lyMK.wxrbL.cn
http://DVhVhBs4.wxrbL.cn
http://Y4kBBg9k.wxrbL.cn
http://bmo9LNDZ.wxrbL.cn
http://B9fjYoFg.wxrbL.cn
http://iKs0EbaK.wxrbL.cn
http://JosJwxLw.wxrbL.cn
http://YbG3U07k.wxrbL.cn
http://a1hqQlzv.wxrbL.cn
http://F6cANm3S.wxrbL.cn
http://DlxQzYxx.wxrbL.cn
http://SOIrvDyy.wxrbL.cn
http://www.dtcms.com/a/377472.html

相关文章:

  • 计算机网络---物理层
  • 【freemarker】创建html页面
  • 【华为OD】区块链文件转储系统
  • sprintf不是像printf一样的打印函数吗
  • Js 图片加载完成 与 图片缓存加载的区别
  • 汽车动力电池管理系统(BMS):电动汽车的“智能大脑”
  • n8n add npm module 發生 Module ‘ioredis‘ is disallowed,getaddrinfo EAI_AGAIN
  • 性能——day3
  • 安卓学习 之 SeekBar(音视频播放进度条)
  • CRMEB标准版PHP订单列表功能详解与优化技巧
  • Linux基础知识(五)
  • [数据结构——lesson7.队列]
  • 在算法竞赛中怎样正确的分析一个程序的时间复杂度
  • ES6笔记4
  • 实现一个动态顺序表(C++)
  • 10 分钟上手 ECharts:从“能跑”到“生产级”的完整踩坑之旅
  • Decode Global新官网上线披露核心数据
  • 【Redis】Scan 命令使用教程:高效遍历海量数据
  • 深度解析:抗辐射电源芯片 ASP4644S2B 在空间环境中的单粒子效应表现
  • 多链冷换仓攻略:如何在 Solana、BSC 与 Base 隐身管理资产
  • 【博弈论和SG函数 | 那忘算10】巴什博奕 尼姆博弈及其变种 威佐夫博弈(附例题)
  • Cubemx+Fatfs(解决挂载Fatfs失败的问题)
  • UVa1063/LA3807 Marble Game
  • leetcode LCR 170.交易逆序对的总数
  • 【学习笔记】Https证书如何升级到国密
  • 期权行权期限一般多久?
  • 0基础Java学习过程记录——枚举、注解
  • 【C++】C++ 内存管理
  • C++ STL之list的使用
  • Midjourney绘画创作入门操作创作(宣传创意)