STM32中I2C DMA读取PCF8574导致GPIO翻转频率降低排查
问题分析与解决方案
核心原因
HAL_Delay的阻塞特性与SysTick抢占:HAL_Delay依赖SysTick定时器实现阻塞延时,当高优先级的I2C/DMA中断触发时,SysTick中断会被抢占,导致实际延时变长,引脚翻转频率降低。- I2C回调中的阻塞操作:
HAL_I2C_MasterRxCpltCallback中直接调用LCD打印函数,这类函数通常包含阻塞式I2C传输,会占用大量CPU时间,打断主循环的延时节奏。 - 中断优先级配置不合理: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:优化回调与中断优先级(无需定时器)
如果不想引入定时器,可通过以下方式缓解问题:
- 剥离回调中的阻塞操作:将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); }
- 调整中断优先级:降低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
相关产品推荐
相关产品推荐

