完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
先看一下官方手册是怎么说的:
小结: 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后停止播放。循环。 |
|
|
|
只有小组成员才能发言,加入小组>>
3322 浏览 9 评论
3000 浏览 16 评论
3497 浏览 1 评论
9070 浏览 16 评论
4090 浏览 18 评论
1191浏览 3评论
613浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
603浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2341浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1899浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-28 23:39 , Processed in 0.926254 second(s), Total 46, Slave 37 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (威廉希尔官方网站 图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号