STM32CubeIDE Audio播放音频 WM8978 + I2S + DMA双缓存模式

随言:

 

STM32CubeIDE Audio播放音频,DAC + TIM + DMA https://blog.csdn.net/sudaroot/article/details/106337717
STM32CubeIDE Audio播放音频,PWM + TIM https://blog.csdn.net/sudaroot/article/details/106373388
STM32CbueIDE Audio播放音频 WM8978 + I2S https://blog.csdn.net/sudaroot/article/details/106528371
STM32CubeIDE Audio播放音频 WM8978 + I2S + DMA双缓存模式 https://blog.csdn.net/sudaroot/article/details/106660351
STM32CubeIDE USB Audio声卡 WM8978 + I2S https://blog.csdn.net/sudaroot/article/details/106834893​​​​​​​

 

本来没想写这篇DMA双缓存文章的,不过想想还是记录一下吧,毕竟自己是健忘患者。

实现DMA双缓存的方式有两种,其一是下面文章这种;还有就是利用单DMA传输 + DMA半传输完成回调函数XferHalfCpltCallback更新数据(这种方式就不提及了,挺简单的)。

这篇《STM32CbueIDE Audio播放音频 WM8978 + I2S》:https://blog.csdn.net/sudaroot/article/details/106528371

 

先看一下官方手册是怎么说的:

STM32CubeIDE Audio播放音频 WM8978 + I2S + DMA双缓存模式_第1张图片

STM32CubeIDE Audio播放音频 WM8978 + I2S + DMA双缓存模式_第2张图片

小结:

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寄存器。

STM32CubeIDE Audio播放音频 WM8978 + I2S + DMA双缓存模式_第3张图片

 

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

STM32CubeIDE Audio播放音频 WM8978 + I2S + DMA双缓存模式_第4张图片

STM32CubeIDE Audio播放音频 WM8978 + I2S + DMA双缓存模式_第5张图片

生成代码即可。

 

代码:

读取内部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()这个函数源码。红框处发现如果用户没实现下面三个回调函数的话,这个函数使用出错。

STM32CubeIDE Audio播放音频 WM8978 + I2S + DMA双缓存模式_第6张图片

 

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

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);	// 使能左右声道和L\ROUT2
	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("Sudaroot\r\n");
  HAL_Delay(1000);
  Audio_Player_Init();

  while (1)
  {
	  printf("开始播放\r\n");
	  Audio_Player_Start();
	  HAL_Delay(5000);
	  printf("暂停播放\r\n");
	  Audio_Player_Pause();
	  HAL_Delay(3000);
	  printf("继续播放\r\n");
	  Audio_Player_Resume();
	  HAL_Delay(10000);
	  printf("停止播放\r\n");
	  Audio_Player_Stop();
	  HAL_Delay(5000);
  }
}

现象:播放5s音频,暂停3s后继续播放。10s后停止播放。循环。

源码:https://download.csdn.net/download/sudaroot/12510712

 

全篇完。

 

本人是一个嵌入式未入门小白,博客仅仅代表我个人主观见解方便记录成长笔记。 若有与大神大大见解有冲突,我坚信大神大大见解是对的,我的是错的。 若无法下载源码,可私聊私发。 感谢~!

你可能感兴趣的:(STM32,STM32,Audio,DMA双缓存,STM32,I2S,WM8978,STM32,声音)