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

STM32调用sprintf后程序卡死,启动DMA后问题复现求排查

STM32启动ADC DMA后sprintf/snprintf调用导致程序卡死的排查与解决

问题核心

启动ADC DMA后调用sprintf或snprintf直接导致程序卡死,DMA启动前该函数可正常运行;已确认头文件齐全、目标缓冲区大小足够,通过LED测试定位卡死点在函数调用处。怀疑问题与uint16_t数组强制转换为uint32_t*传递给HAL_ADC_Start_DMA有关。

可能原因

  1. 内存对齐与类型转换错误
    STM32 ADC DMA传输对目标缓冲区的地址对齐有严格要求:若ADC配置为16位采样,DMA传输宽度为半字(16位),此时uint16_t数组默认满足半字对齐,但直接将uint16_t*强制转为uint32_t*会让DMA控制器以32位宽度访问内存,引发未定义的内存访问行为,进而干扰后续标准库函数的执行。

  2. 总线优先级冲突
    DMA控制器的总线优先级若高于内核,会抢占内核访问栈内存的权限,而sprintf等标准库函数依赖栈内存的连续、原子访问,总线抢占会导致函数执行过程中内存访问异常,最终卡死。

  3. 标准库实现的内存依赖
    部分Newlib nano版本的printf系列函数依赖特定的内存操作逻辑,若DMA正在访问的内存区域(或总线)与标准库的内存操作产生冲突,会触发硬故障或总线故障。

解决方案

1. 修正DMA缓冲区的类型与地址传递

  • 若ADC配置为16位采样,保持uint16_t类型的缓冲区,调用HAL_ADC_Start_DMA时使用明确的地址转换,避免直接强制转换数组指针:
    uint16_t values[4];
    // 传递数组首元素的地址,确保DMA按正确宽度访问
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&values[0], 4);
    
  • 若ADC确实需要32位数据对齐,直接定义uint32_t values[4],无需类型转换。

2. 调整DMA总线优先级

在MX_DMA_Init函数中,降低DMA通道的优先级,确保内核总线访问优先级高于DMA:

hdma_adc1.Instance = DMA1_Channel1;
hdma_adc1.Init.Priority = DMA_PRIORITY_LOW; // 改为低优先级
HAL_DMA_Init(&hdma_adc1);

3. 优化字符串长度计算与标准库调用

  • 避免使用log10等浮点运算计算字符串长度,直接用strlen更可靠且避免浮点库带来的潜在问题:
    int size = strlen(str);
    HAL_UART_Transmit(&hlpuart1, (uint8_t*)str, size, 100);
    
  • 若问题仍存在,尝试使用轻量级的自定义打印函数替代标准库sprintf,减少复杂内存操作:
    void my_printf(char* buf, int num) {
        int len = sprintf(buf, "TEMP_ADC: %d\n", num);
    }
    

4. 调试定位具体异常

使用调试器查看卡死时的内核寄存器:

  • 检查SCB->HFSR寄存器,确认是否触发HardFault
  • 查看SCB->BFAR寄存器,获取总线故障的错误地址,定位具体的内存访问冲突点

修正后的示例代码

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  PeriphCommonClock_Config();

  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_TIM2_Init();
  MX_TIM16_Init();
  MX_MEMORYMAP_Init();
  MX_LPUART1_UART_Init();
  MX_RF_Init();

  /* USER CODE BEGIN 2 */
  char str[100];
  sprintf(str, "TEMP_ADC: %d\n", 50);
  int size = strlen(str);
  HAL_UART_Transmit(&hlpuart1, (uint8_t*)str, size, 100);
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, SET);

  uint16_t values[4];
  // 修正DMA地址传递方式
  HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&values[0], 4);

  uint16_t temp = 0;
  /* USER CODE END 2 */

  while (1)
  {
      HAL_Delay(1000);
      // DMA运行时测试sprintf
      sprintf(str, "Loop count: %d\n", temp++);
      size = strlen(str);
      HAL_UART_Transmit(&hlpuart1, (uint8_t*)str, size, 100);
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 12:25:27