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

STM32 I2S DMA TxHalfCplt回调仅触发一次问题排查

STM32 I2S DMA双缓冲TxHalfCplt回调仅触发一次问题排查

问题现象

  • 采用STM32 I2S DMA双缓冲方案播放固定格式WAV文件,启动传输后HAL_I2S_TxHalfCpltCallback仅执行一次就停止触发,仅HAL_I2S_TxCpltCallback能持续正常工作
  • 缓冲区前半部分数据无法被更新,残留旧内容通过DAC播放,后半部分可正常填充新数据
  • UART调试仅能捕获第一次TxHalfCpltCallback的输出日志,后续无相关触发记录
  • 已在CMSIS V2 RTOS环境中将音频任务优先级设为最高

环境说明

  • 硬件:STM32系列芯片(带I2S、DMA外设)
  • 软件框架:HAL库 + CMSIS V2 RTOS
  • 音频方案:I2S DMA双缓冲循环播放

代码片段

任务代码

uint32_t task_stat = STOP_TASK;
uint32_t StartAddr = 0x00000; // 音频数据起始地址
uint32_t EndAddr = 0x4FFFF;   // 音频数据结束地址
uint32_t currentAddr = 0x00000;
uint32_t flashOffsetHalfBuffer = (AUDIO_BUFF_SIZE / 2) * sizeof(uint16_t); // 半缓冲字节数
uint32_t flashOffsetFullBuffer = AUDIO_BUFF_SIZE * sizeof(uint16_t);       // 全缓冲字节数

/* 无限循环 */
for(;;)
{
    if(osMessageQueueGet(test_TaskQueueHandle, &task_stat, NULL, osWaitForever) == osOK){
        printf("TEST STARTED\r\n");
        // 预填充全缓冲区
        initAudioData(StartAddr, dacData);
        currentAddr = StartAddr + flashOffsetFullBuffer;
        FLAG_I2S_DATA_READY = 1;
        
        // 启动DMA传输
        HAL_I2S_Transmit_DMA(&hi2s1, (uint16_t *)dacData, DMA_MAX(AUDIO_BUFF_SIZE / 2));

    } else {
        printf("ERR: TASK HANDLE MSG QT\r\n");
    }
    while (task_stat == RUN_TASK) {

        if( FLAG_I2S_DATA_READY == 0 ){
            if((currentAddr + flashOffsetHalfBuffer) <= EndAddr){
                if(processAudioData(currentAddr, dacPtr) == STAT_OK){
                    currentAddr += flashOffsetHalfBuffer;
                    FLAG_I2S_DATA_READY = 1;
                }

            } else {
                // 循环播放,重置地址到起始位置
                currentAddr = StartAddr;
                if(processAudioData(currentAddr, dacPtr) == STAT_OK){
                    currentAddr += flashOffsetHalfBuffer;
                    FLAG_I2S_DATA_READY = 1;
                }
            }
        }

        if(osMessageQueueGet(test_TaskQueueHandle, &task_stat, NULL, 1) == osOK){
            printf("TASK PAUSE\r\n");
            HAL_I2S_DMAStop(&hi2s1);
            currentAddr = 0x00000;
            task_stat = STOP_TASK;
        }
    }

    osDelay(1);
}

回调函数

void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s){
    dacPtr = &dacData[AUDIO_BUFF_SIZE/2];
    FLAG_I2S_DATA_READY = 0;
    FLAG_FIRST_HALF_RELOAD = 0;
    FLAG_SECOND_HALF_RELOAD = 1;
    // printf("C\r\n");
}

void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s){
    dacPtr = &dacData[0];
    FLAG_I2S_DATA_READY = 0;
    FLAG_FIRST_HALF_RELOAD = 1;
    FLAG_SECOND_HALF_RELOAD = 0;
    // printf("H\r\n");
}

全局定义

// DMA状态标志
volatile uint8_t FLAG_I2S_DATA_READY = 1;
volatile uint8_t FLAG_FIRST_HALF_RELOAD = 0;
volatile uint8_t FLAG_SECOND_HALF_RELOAD = 0;

// 音频DMA缓冲区
uint16_t dacData[AUDIO_BUFF_SIZE];
// 缓冲区指针
static volatile uint16_t *dacPtr = &dacData[0];

问题排查与修复方案

1. DMA传输长度参数错误

当前启动DMA时传入的长度是AUDIO_BUFF_SIZE / 2,这仅为半缓冲区的数据项数。双缓冲方案要求DMA传输整个缓冲区的长度,才能在循环模式下交替触发半完成、完成回调。

修正代码:

// 传入全缓冲区的数据项数(uint16_t类型)
HAL_I2S_Transmit_DMA(&hi2s1, (uint16_t *)dacData, AUDIO_BUFF_SIZE);

注:确保AUDIO_BUFF_SIZE是缓冲区中uint16_t类型数据的总个数,而非字节数。

2. 未开启DMA循环模式

若DMA配置为Normal模式,传输完成一次后会自动停止,无法持续触发回调。必须将DMA Tx通道设置为循环模式(Circular Mode)。

  • CubeMX配置:找到I2S对应的DMA Tx通道,将Mode设置为Circular
  • 代码层面确认:
hdma_spi1_tx.Init.Mode = DMA_CIRCULAR; // 确保此项为循环模式

3. 全局标志的竞态问题

RTOS环境下,中断回调(上下文)与任务上下文同时操作全局标志,容易出现竞态逻辑错误。建议用RTOS信号量替代全局标志,实现安全同步。

修正步骤:

  • 定义信号量:
osSemaphoreId_t audioSemaphore;
  • 初始化信号量(任务创建前执行):
audioSemaphore = osSemaphoreNew(1, 1, NULL);
  • 修改回调函数:
void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s){
    dacPtr = &dacData[AUDIO_BUFF_SIZE/2];
    osSemaphoreRelease(audioSemaphore);
}

void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s){
    dacPtr = &dacData[0];
    osSemaphoreRelease(audioSemaphore);
}
  • 修改任务循环逻辑:
while (task_stat == RUN_TASK) {
    // 等待回调触发的信号量
    if(osSemaphoreAcquire(audioSemaphore, osWaitForever) == osOK){
        if((currentAddr + flashOffsetHalfBuffer) <= EndAddr){
            if(processAudioData(currentAddr, dacPtr) == STAT_OK){
                currentAddr += flashOffsetHalfBuffer;
            }
        } else {
            currentAddr = StartAddr;
            if(processAudioData(currentAddr, dacPtr) == STAT_OK){
                currentAddr += flashOffsetHalfBuffer;
            }
        }
    }

    // 处理暂停消息
    if(osMessageQueueGet(test_TaskQueueHandle, &task_stat, NULL, 1) == osOK){
        printf("TASK PAUSE\r\n");
        HAL_I2S_DMAStop(&hi2s1);
        currentAddr = 0x00000;
        task_stat = STOP_TASK;
    }
}

4. 数据宽度匹配检查

确认I2S的数据宽度配置(如16位)与DMA的数据宽度、缓冲区类型(uint16_t)完全匹配,避免因数据位宽不匹配导致DMA计数错误,进而引发回调触发异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 08:23:12