单片机学习小组
直播中

刘超

7年用户 1447经验值
私信 关注

什么是双缓冲区模式?

什么是双缓冲区模式?

回帖(1)

江根磊

2022-2-28 14:00:08
先看一下官方手册是怎么说的:






小结:

1、首先STM32F4系列芯片的DMA1 和 DMA2是支持双DMA缓存区模式的,通过将 DMA_SxCR 寄存器中的 DBM 位置 1,即可使能双缓冲区模式。将自动使能循环模式(DMA_SxCR 中的 CIRC 位位的状态是“无 关”),即DMA发送模式设置无关。

2、由于是双缓存区只有一个缓存区在工作,故可以在一个缓存区工作的时候填充另一个缓存区数据。

3、在缓存区在工作的时候,不支持切换缓存区基地址,否则硬件会使TEIF = 1,中止数据传输。比如DMA_SxM0AR = buf0正在工作,此时你让DMA_SxM0AR = buf1,即出错。

4、可以通过当 DMA_SxCR 寄存器中的 CT 位的值判读哪个缓存区正在工作。如果是做串口DMA接收数据空闲中断,则必须用到(题外话)。

5、双缓存模式的源和目标不支持内存到内存模式。



但是官方HAL库的HAL_DMA_Init()函数不支持双缓冲区模式,却给出了HAL_DMAEx_MultiBufferStart()和HAL_DMAEx_MultiBufferStart_IT()这两个设置双缓存区模式的函数。在轮询模式下使用HAL_DMA_MultiBufferStart()函数或在中断模式下使用HAL_DMA_MultiBufferStart_IT()启动多缓冲区传输。

但是官方又没有给出这两个函数的具体用法,故只能靠自己作了。



故如果要使用DMA双缓存区功能就要自己仿写HAL实现双DMA缓存区功能,由于这里我用的是I2S,即实现HAL_I2S_Transmit_DMA自定义版本,这个版本内部调用的HAL_DMAEx_MultiBufferStart_IT()而非HAL_DMA_Start_IT(),并且还要实现XferCpltCallback()、XferM1CpltCallback()或者XferErrorCallback()这三个回调函数,最后还要DMA_HandleTypeDef指向这三个回调函数。



讲了这么多,其实很简单。






硬件:某原子STM32F407ZG + WM8978




STM32CubeIDE:


配置I2C控制WM8978寄存器。





I2S配置。使用的音频还是双声道8KHz_16bit数据,文件还是存储在MCU的内部flash。DMA数据模式我还是配置为Normal,反正和它无关。DMA的数据宽度为什么是Half Word,因为我选的音频数据是16bits和16bit为一帧。





生成代码即可。



代码:


读取内部flash的音频文件。datas.h文件存放在音频文件。

WAV_FileInit()初始化文件长度和数据地址。

WAV_FileRead()读取size大小数据,返回值为0表示文件已经读完了。

#include
#include
#include
#include "datas.h"

#define        BUFFER_SIZE                                        1024

static uint32_t DataLength = 0;
static uint8_t *DataAddress = NULL;
uint16_t I2S_Buf0[BUFFER_SIZE] = { 0 };
uint16_t I2S_Buf1[BUFFER_SIZE] = { 0 };


void WAV_FileInit(void)
{
        DataLength = sizeof(data) - 0x2c;
        DataAddress = (uint8_t*) (data + 0x2c);
}

uint32_t WAV_FileRead(uint8_t *buf, uint32_t size)
{
        uint32_t Playing_End = 0;

        if (DataLength >= size)
        {
                memcpy(buf, DataAddress, size);
                DataLength -= size;
                DataAddress += size;
                Playing_End = 1;
        }
        else
        {
                memcpy(buf, DataAddress, DataLength);
                Playing_End = 0;
        }

        return Playing_End;
}


首先先看一下HAL_DMAEx_MultiBufferStart_IT()这个函数源码。红框处发现如果用户没实现下面三个回调函数的话,这个函数使用出错。





那么先实现这三个回调函数。

void (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);

这个回调函数是第1个缓存区Buffer0发送完成后中断会调用回调函数,此时可以去填充该函数下一轮的数据。

void (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma);  

这个回调函数是第2个缓存区Buffer1发送完成后中断会调用回调函数,此时可以去填充该函数下一轮的数据。

void  (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);

这个回调函数是传输数据出错的回调函数。出错原因上面有小结,认真看,谢谢~@。



void (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);       // DMA 第1个缓存区半传输完成回调

void (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);  // DMA 第2个缓存区半传输完成回调

void (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma);           // DMA 传输中止回调

其余的回调函数我用不上,让DMA_HandleTypeDef hdma_spi2_tx 对应的函数指针 = NULL。

static void DMAEx_XferCpltCallback(struct __DMA_HandleTypeDef *hdma)
{
        if (WAV_FileRead((uint8_t*) I2S_Buf0, sizeof(I2S_Buf0)) == 0)
        {
                Audio_Player_Stop();
        }
}

static void DMAEx_XferM1CpltCallback(struct __DMA_HandleTypeDef *hdma)
{
        if (WAV_FileRead((uint8_t*) I2S_Buf1, sizeof(I2S_Buf1)) == 0)
        {
                Audio_Player_Stop();
        }
}

static void DMAEx_XferErrorCallback(struct __DMA_HandleTypeDef *hdma)
{

}


先复制一份HAL库的HAL_I2S_Transmit_DMA()源码,函数修改为HAL_I2S_Transmit_DMAEx(),如下:

HAL_StatusTypeDef HAL_I2S_Transmit_DMAEx(I2S_HandleTypeDef *hi2s, uint16_t *FirstBuffer, uint16_t *SecondBuffer, uint16_t Size)
{
        uint32_t tmpreg_cfgr;

        if ((FirstBuffer == NULL) || (SecondBuffer == NULL) || (Size == 0U))
        {
                return HAL_ERROR;
        }

        /* Process Locked */
        __HAL_LOCK(hi2s);

        if (hi2s->State != HAL_I2S_STATE_READY)
        {
                __HAL_UNLOCK(hi2s);
                return HAL_BUSY;
        }

        /* Set state and reset error code */
        hi2s->State = HAL_I2S_STATE_BUSY_TX;
        hi2s->ErrorCode = HAL_I2S_ERROR_NONE;
        hi2s->pTxBuffPtr = FirstBuffer;

        tmpreg_cfgr = hi2s->Instance->I2SCFGR
                        & (SPI_I2SCFGR_DATLEN | SPI_I2SCFGR_CHLEN);

        if ((tmpreg_cfgr == I2S_DATAFORMAT_24B)
                        || (tmpreg_cfgr == I2S_DATAFORMAT_32B))
        {
                hi2s->TxXferSize = (Size << 1U);
                hi2s->TxXferCount = (Size << 1U);
        }
        else
        {
                hi2s->TxXferSize = Size;
                hi2s->TxXferCount = Size;
        }

        /* Set the I2S Tx DMA Half transfer complete callback */
        hi2s->hdmatx->XferHalfCpltCallback = NULL;
        hi2s->hdmatx->XferM1HalfCpltCallback = NULL;

        /* Set the I2S Tx DMA transfer complete callback */
        hi2s->hdmatx->XferCpltCallback = DMAEx_XferCpltCallback;
        hi2s->hdmatx->XferM1CpltCallback = DMAEx_XferM1CpltCallback;

        /* Set the DMA error callback */
        hi2s->hdmatx->XferErrorCallback = DMAEx_XferErrorCallback;

        /* Set the DMA abort callback */
        hi2s->hdmatx->XferAbortCallback = NULL;

        /* Enable the Tx DMA Stream/Channel */
        if (HAL_OK != HAL_DMAEx_MultiBufferStart_IT(hi2s->hdmatx, (uint32_t) FirstBuffer, (uint32_t) &hi2s->Instance->DR, (uint32_t) SecondBuffer,        hi2s->TxXferSize))
        {
                /* Update SPI error code */
                SET_BIT(hi2s->ErrorCode, HAL_I2S_ERROR_DMA);
                hi2s->State = HAL_I2S_STATE_READY;

                __HAL_UNLOCK(hi2s);
                return HAL_ERROR;
        }

        /* Check if the I2S is already enabled */
        if (HAL_IS_BIT_CLR(hi2s->Instance->I2SCFGR, SPI_I2SCFGR_I2SE))
        {
                /* Enable I2S peripheral */
                __HAL_I2S_ENABLE(hi2s);
        }

        /* Check if the I2S Tx request is already enabled */
        if (HAL_IS_BIT_CLR(hi2s->Instance->CR2, SPI_CR2_TXDMAEN))
        {
                /* Enable Tx DMA Request */
                SET_BIT(hi2s->Instance->CR2, SPI_CR2_TXDMAEN);
        }

        __HAL_UNLOCK(hi2s);
        return HAL_OK;
}


剩下就是初始化WM8978芯片 和 功能逻辑代码。

#define        WM8978_ADDRESS                                0x1A
#define        WM8978_WIRTE_ADDRESS                (WM8978_ADDRESS << 1 | 0)

extern I2C_HandleTypeDef hi2c1;
extern I2S_HandleTypeDef hi2s2;
extern DMA_HandleTypeDef hdma_spi2_tx;

HAL_StatusTypeDef WM8978_Register_Wirter(uint8_t reg_addr, uint16_t data)
{
        uint8_t pData[10] =        { 0 };

        pData[0] = (reg_addr << 1) | ((data >> 8) & 0x01);
        pData[1] = data & 0xFF;
        return HAL_I2C_Master_Transmit(&hi2c1, WM8978_WIRTE_ADDRESS, pData, 2, 1000);
}


void Audio_Player_Init(void)
{
        WM8978_Register_Wirter(0, 0);                // 软复位
        WM8978_Register_Wirter(1, 0x0F);        // 模拟放大器使能, 使能输出输入缓存区
        WM8978_Register_Wirter(3, 0x7F);        // 使能左右声道和LROUT2
        WM8978_Register_Wirter(4, 0x10);        // I2S 16bit
        WM8978_Register_Wirter(6,0);                // MCU提供时钟
        WM8978_Register_Wirter(10, 0x08);        // 输出音质最好
        WM8978_Register_Wirter(43, 0x10);        // ROUT2反相
        WM8978_Register_Wirter(54,30);                // 设置LOUT2左声道音量
        WM8978_Register_Wirter(55,30|(1<<8));        // 设置ROUT2右声道音量, 更新左右声道音量
}

void Audio_Player_Start(void)
{
        WAV_FileInit();
        WAV_FileRead((uint8_t*) I2S_Buf0, sizeof(I2S_Buf0));
        WAV_FileRead((uint8_t*) I2S_Buf1, sizeof(I2S_Buf1));
        HAL_I2S_Transmit_DMAEx(&hi2s2, I2S_Buf0, I2S_Buf1, BUFFER_SIZE);
}

void Audio_Player_Pause(void)
{
        HAL_I2S_DMAPause(&hi2s2);
}

void Audio_Player_Resume(void)
{
        HAL_I2S_DMAResume(&hi2s2);
}

void Audio_Player_Stop(void)
{
        WAV_FileInit();
        HAL_I2S_DMAStop(&hi2s2);
}


主函数:

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_I2C1_Init();
  MX_I2S2_Init();
  MX_USART1_UART_Init();

  printf("Sudarootrn");
  HAL_Delay(1000);
  Audio_Player_Init();

  while (1)
  {
          printf("开始播放rn");
          Audio_Player_Start();
          HAL_Delay(5000);
          printf("暂停播放rn");
          Audio_Player_Pause();
          HAL_Delay(3000);
          printf("继续播放rn");
          Audio_Player_Resume();
          HAL_Delay(10000);
          printf("停止播放rn");
          Audio_Player_Stop();
          HAL_Delay(5000);
  }
}
现象:播放5s音频,暂停3s后继续播放。10s后停止播放。循环。



举报

更多回帖

发帖
×
20
完善资料,
赚取积分