You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32中I2C DMA读取PCF8574导致GPIO翻转频率降低排查

问题分析与解决方案

核心原因

  1. HAL_Delay的阻塞特性与SysTick抢占:HAL_Delay依赖SysTick定时器实现阻塞延时,当高优先级的I2C/DMA中断触发时,SysTick中断会被抢占,导致实际延时变长,引脚翻转频率降低。
  2. I2C回调中的阻塞操作:HAL_I2C_MasterRxCpltCallback中直接调用LCD打印函数,这类函数通常包含阻塞式I2C传输,会占用大量CPU时间,打断主循环的延时节奏。
  3. 中断优先级配置不合理:DMA和I2C中断被设置为最高优先级(优先级0),会抢占所有其他中断(包括SysTick),进一步加剧延时不准的问题。

解决方案

方案1:定时器中断实现引脚翻转(推荐)

放弃主循环的阻塞延时,改用定时器定时中断触发引脚翻转,彻底避免阻塞,确保20Hz频率不受其他操作干扰。

步骤1:添加定时器初始化代码

在MX_GPIO_Init后新增TIM2初始化(假设系统时钟为72MHz):

/* USER CODE BEGIN 2 */
static void MX_TIM2_Init(void)
{
  TIM_HandleTypeDef htim2;

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 7199; // 72MHz ÷ (7199+1) = 10kHz
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 249; // 10kHz ÷ (249+1) = 40Hz,每两次中断翻转一次,最终20Hz
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }

  // 使能定时器更新中断
  HAL_TIM_Base_Start_IT(&htim2);
}

在main函数的外设初始化段调用该函数:

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_I2C1_Init();
MX_TIM2_Init(); // 新增定时器初始化

步骤2:实现定时器中断回调

在USER CODE BEGIN 0中添加:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM2)
  {
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_10); // 修正为目标引脚PB10
  }
}

步骤3:简化主循环

移除主循环中的翻转和延时操作,改为处理LCD打印等非实时任务:

while (1)
{
  // 仅在数据变化时更新LCD,减少操作次数
  static uint8_t last_data = 0;
  if(data[0] != last_data)
  {
    lcdPrintMap_I2C(&lcd, 0, 0, "data[0] = ");
    lcdPrint_I2C(&lcd, IntegerToString(data[0]));
    last_data = data[0];
  }
}

方案2:优化回调与中断优先级(无需定时器)

如果不想引入定时器,可通过以下方式缓解问题:

  1. 剥离回调中的阻塞操作:将LCD打印移至主循环,仅在回调中标记数据更新:
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) {
    if(hi2c->Instance == I2C1){
        static uint8_t data_updated = 0;
        data_updated = 1; // 标记数据已更新
        HAL_I2C_Master_Receive_DMA(&hi2c1, 69, data, 1);
    }
}

主循环中处理打印:

while (1)
{
    static uint8_t last_data = 0;
    static uint8_t data_updated = 0;
    if(data_updated)
    {
        if(data[0] != last_data)
        {
            lcdPrintMap_I2C(&lcd, 0, 0, "data[0] = ");
            lcdPrint_I2C(&lcd, IntegerToString(data[0]));
            last_data = data[0];
        }
        data_updated = 0;
    }
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_10); // 修正为PB10
    HAL_Delay(25);
}
  1. 调整中断优先级:降低DMA/I2C中断优先级,确保SysTick中断能正常执行:
static void MX_DMA_Init(void)
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 2, 0); // 降低优先级至2
  HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
  /* DMA1_Channel7_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 2, 0); // 同步降低优先级
  HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn);
}

额外修正点

  • 原代码中翻转的是GPIO_PIN_0,需统一改为GPIO_PIN_10,并在MX_GPIO_Init中添加PB10的输出初始化配置。
  • 若LCD驱动使用阻塞式I2C,建议改为DMA发送,进一步减少CPU占用。

内容的提问来源于stack exchange,提问作者JIDAN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 23:19:52