STM32H7S78-DK 开发板是具备无敌多媒体功能的超级开发板。
为什么这么讲呢,他具备数字麦克风、I2S音频编解码、SD卡及Flash存储、5英寸高清晰TFT LCD、WIFI扩展卡。
这些功能集合到一块,可以发挥无穷想象,既可以做一个数码相册、也可以做一个网络收音机、或者是像小爱同学一样的智能音箱,总之玩法多样,潜力无穷。
本篇我们来体验最基本的音频播放功能,即播放存储在Flash上的流行音乐。
大致逻辑是这样的:
但问题是,整个流程涉及Flash读取、音频文件解析(Wav)、I2S(DMA方式)、WM8904驱动……
短时间内如何掌握这么多复杂高深的内容,从而完成我们一个简单的任务:播放流行音乐
经过一段时间的摸索,办法来了:利用官方的BSP(board support package,板级支持包)
这里面有我们需要的一切函数,我们想要实现什么功能,直接设置好参数调用就好了,无需彻底搞懂复杂的过程,一切以目标驱动模式完成。
好了,话不多说,开始我们的流行音乐之旅吧。
1. 在CubeIDE中导入官方例程BSP。
2. 熟读README.md,详细介绍了这个BSP都有啥演示程序,以及如何使用。
3. 最关键的环节:烧录音乐文件(bin)到Flash存储中。
### <b>How to use it ?</b>
- Use STM32CubeProgrammer, available on www.st.com or any other in system programming
tool to load "BSP/Binary/audio_sample_tdm.bin" file to the external QSPI flash
at the address 0x700A0000.
- Open the STM32CubeProgrammer tool
- Select the octoSPI external flash loader "MX66UW1G45G_STM32H7S78-DK"
- From Erasing & Programming menu, browse and open the output binary file relative to this example
- Load the file into the external QSPI flash using "Start Programming" at the address APPLICATION_ADDRESS (0x700A0000)
他这里提供了一段实例音乐audio_sample_tdm.bin(大小512KB),我们将他替换为我们想要播放的流行音乐。
你肯定纳闷,上哪去搞bin格式的音频。
首先下载一首mp3,其次去在线mp3转wav格式网站,这种很多。
比如:
https://www.aconvert.com/cn/audio/mp3-to-wav/
比特率和采样率参考:
得到WAV格式后,直接将.wav后缀改为.bin(简单粗暴有木有,其实wav就是最原始的i2s数字音频文件)
WAV为微软公司(Microsoft)开发的一种声音文件格式,它符合RIFF(Resource Interchange File Format)文件规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持,该格式也支持MSADPCM,CCITT A LAW等多种压缩运算法,支持多种音频数字,取样频率和声道。标准格式化的WAV文件采样频率为44100Hz,采样比特为16bit,因此标准的(这里说标准,只是一种广泛采用的波形音频方案)WAV文件和CD音频格式一样,也是44.1KHz的取样频率,16位量化数字,在声音文件质量和CD音频相差无几。
将我们的流行音乐miaozhen.bin(大约10.9MB)通过STM32CubeProgrammer烧录到开发板的Flash中。
第一步,连接开发板,选择Flash驱动:MX66UW1G45G_STM32H7S78-DK。
第二部:选择我们的bin格式音频,输入起始地址(0x700A0000),开始烧录。
4. 对官方支持包BSP大做手术,实现我们的功能:播放流行音乐
main.c中可以看到,BSP总共6个功能,由用户按钮选择,我们简单干脆,只要audio play功能。
所以把该程序块直接屏蔽,仅改为:AudioPlay_demo();
来到main.h中,查看对音频文件的定义:
/* The Audio file is flashed with STM32CubeProgrammer @ flash address = AUDIO_SRC_FILE_ADDRESS */
#define AUDIO_SRC_FILE_ADDRESS 0x700A0000 /* Audio file address in flash */
#define AUDIO_FILE_SIZE 0xafc880
AUDIO_SRC_FILE_ADDRESS好理解,就是刚才我们写入Flash的地址:0x700A0000。
AUDIO_FILE_SIZE就是该段音频的文件大小:
用文件管理器可看到,该bin文件为11,520,128 bit,换算为16进制为:0xAFC880,也就是上面的AUDIO_FILE_SIZE
这里一定要对上,否则在循环播放的过程中会错乱。
最后来到播放音乐的核心 audio_play.c文件中:
我们可以仔细阅读各部分的功能,多余的也不要了。
最核心的while循环中,仅保留
AUDIO_Process();
#include "main.h"
#include <stdio.h>
/** @addtogroup STM32H7RSxx_HAL_Examples
* @{
*/
/** @addtogroup BSP
* @{
*/
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/*Since SysTick is set to 1ms (unless to set it quicker) */
/* to run up to 48khz, a buffer around 1000 (or more) is requested*/
/* to run up to 96khz, a buffer around 2000 (or more) is requested*/
#define AUDIO_DEFAULT_VOLUME 90
/* Audio file size and start address are defined here since the audio file is
stored in Flash memory as a constant table of 16-bit data */
#define AUDIO_START_OFFSET_ADDRESS 0 /* Offset relative to audio file header size */
#define AUDIO_BUFFER_SIZE 2048
/* Audio file size and start address are defined here since the audio file is
stored in Flash memory as a constant table of 16-bit data */
#define AUDIO_START_OFFSET_ADDRESS 0 /* Offset relative to audio file header size */
/* Private typedef -----------------------------------------------------------*/
typedef enum {
BUFFER_OFFSET_NONE = 0,
BUFFER_OFFSET_HALF,
BUFFER_OFFSET_FULL,
}BUFFER_StateTypeDef;
typedef struct {
uint8_t buff[AUDIO_BUFFER_SIZE];
uint32_t fptr;
BUFFER_StateTypeDef state;
uint32_t AudioFileSize;
uint32_t *SrcAddress;
}AUDIO_BufferTypeDef;
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
ALIGN_32BYTES (static AUDIO_BufferTypeDef buffer_ctl);
__IO uint32_t audio_state;
__IO uint32_t uwVolume = 20;
__IO uint32_t uwPauseEnabledStatus = 0;
uint32_t AudioFreq[8] = {96000, 48000, 44100, 32000, 22050, 16000, 11025, 8000};
BSP_AUDIO_Init_t AudioPlayInit;
/* Private function prototypes -----------------------------------------------*/
static void Audio_SetHint(uint32_t Index);
static uint32_t GetData(void *pdata, uint32_t offset, uint8_t *pbuf, uint32_t NbrOfData);
AUDIO_ErrorTypeDef AUDIO_Start(uint32_t *psrc_address, uint32_t file_size);
extern TS_Init_t hTS;
/* Private functions ---------------------------------------------------------*/
/**
* [url=home.php?mod=space&uid=159083]@brief[/url] Audio Play demo
* @param None
* @retval None
*/
void AudioPlay_demo(void)
{
uint32_t *AudioFreq_ptr;
uint32_t y_size,x_size;
uint32_t CurrentTickDetect =0;
uint32_t LastTSTick = 0;
uint32_t TS_Available = 1;
uint16_t x1, y1;
TS_State_t TS_State;
uint8_t VolStr[256] = {0};
uint8_t FreqStr[256] = {0};
int32_t ts_status = BSP_ERROR_NONE;
int32_t nor_status = BSP_ERROR_NONE;
Point Points2[] = {{226, 196}, {265, 223}, {226, 248}};
BSP_XSPI_NOR_Init_t NorInit;
BSP_LCD_GetXSize(0, &x_size);
BSP_LCD_GetYSize(0, &y_size);
AudioFreq_ptr = &AudioFreq[1]; /*48K*/
UserButtonPressed = 0;
uwPauseEnabledStatus = 1; /* 0 when audio is running, 1 when Pause is on */
uwVolume = 90;
Audio_SetHint(0);
UTIL_LCD_SetFont(&Font20);
UserButtonPressed = 0;
/* External Nor Flash initialization to get audio file from */
NorInit.InterfaceMode = MX66UW1G45G_OPI_MODE;
NorInit.TransferRate = MX66UW1G45G_DTR_TRANSFER;
if (BSP_XSPI_NOR_Init(0, &NorInit) != BSP_ERROR_NONE)
{
nor_status = BSP_ERROR_NO_INIT;
}
else if (BSP_XSPI_NOR_EnableMemoryMappedMode(0) != BSP_ERROR_NONE)
{
nor_status = BSP_ERROR_PERIPH_FAILURE;
}
if (nor_status != BSP_ERROR_NONE)
{
UTIL_LCD_SetBackColor(UTIL_LCD_COLOR_WHITE);
UTIL_LCD_SetTextColor(UTIL_LCD_COLOR_RED);
UTIL_LCD_DisplayStringAt(0, y_size - 95, (uint8_t *)"ERROR", CENTER_MODE);
UTIL_LCD_DisplayStringAt(0, y_size - 80, (uint8_t *)"NOR flash XSPI cannot be initialized", CENTER_MODE);
}
hTS.Width = x_size;
hTS.Height = y_size;
hTS.Orientation = TS_SWAP_NONE;
hTS.Accuracy = 10;
/* Touchscreen initialization */
ts_status = BSP_TS_Init(0, &hTS);
if (ts_status != BSP_ERROR_NONE)
{
UTIL_LCD_SetBackColor(UTIL_LCD_COLOR_WHITE);
UTIL_LCD_SetTextColor(UTIL_LCD_COLOR_RED);
UTIL_LCD_DisplayStringAt(0, y_size - 95, (uint8_t *)"ERROR", CENTER_MODE);
UTIL_LCD_DisplayStringAt(0, y_size - 80, (uint8_t *)"Touch Screen cannot be initialized", CENTER_MODE);
}
AudioPlayInit.Device = AUDIO_OUT_HEADPHONE;
AudioPlayInit.ChannelsNbr = 2;
AudioPlayInit.SampleRate = *AudioFreq_ptr;
AudioPlayInit.BitsPerSample = AUDIO_RESOLUTION_16B;
AudioPlayInit.Volume = uwVolume;
if(BSP_AUDIO_OUT_Init(0, &AudioPlayInit) != 0)
{
UTIL_LCD_SetBackColor(UTIL_LCD_COLOR_WHITE);
UTIL_LCD_SetTextColor(UTIL_LCD_COLOR_RED);
UTIL_LCD_DisplayStringAt(0, y_size - 95, (uint8_t *)" AUDIO CODEC FAIL ", CENTER_MODE);
UTIL_LCD_DisplayStringAt(0, y_size - 80, (uint8_t *)" Try to reset board ", CENTER_MODE);
}
/*
Start playing the file from a circular buffer, once the DMA is enabled, it is
always in running state. Application has to fill the buffer with the audio data
using Transfer complete and/or half transfer complete interrupts callbacks
(BSP_AUDIO_OUT_TransferComplete_CallBack() or BSP_AUDIO_OUT_HalfTransfer_CallBack()...
*/
AUDIO_Start((uint32_t *)AUDIO_SRC_FILE_ADDRESS + PLAY_HEADER, (uint32_t)AUDIO_FILE_SIZE);
while (1)
{
AUDIO_Process();
}
}
AUDIO_Start((uint32_t *)AUDIO_SRC_FILE_ADDRESS + PLAY_HEADER, (uint32_t)AUDIO_FILE_SIZE);
就是音频播放的开始。
uint8_t AUDIO_Process(void)就是从Flash存储中读取音乐文件。
AUDIO_Process();就是音乐循环反复播放音乐文件,这里用到了双缓冲DMA。
/**
* @brief Gets Data from storage unit.
* @param None
* @retval None
*/
static uint32_t GetData(void *pdata, uint32_t offset, uint8_t *pbuf, uint32_t NbrOfData)
{
uint8_t *lptr = pdata;
uint32_t ReadDataNbr;
ReadDataNbr = 0;
while(((offset + ReadDataNbr) < buffer_ctl.AudioFileSize) && (ReadDataNbr < NbrOfData))
{
pbuf[ReadDataNbr]= lptr [offset + ReadDataNbr];
ReadDataNbr++;
}
return ReadDataNbr;
}
/**
* @brief Manages Audio process.
* @param None
* @retval Audio error
*/
uint8_t AUDIO_Process(void)
{
uint32_t bytesread;
AUDIO_ErrorTypeDef error_state = AUDIO_ERROR_NONE;
switch(audio_state)
{
case AUDIO_OUT_STATE_PLAYING:
if(buffer_ctl.fptr >= buffer_ctl.AudioFileSize)
{
/* Play audio sample again ... */
buffer_ctl.fptr = 0;
error_state = AUDIO_ERROR_EOF;
}
/* 1st half buffer played; so fill it and continue playing from bottom*/
if(buffer_ctl.state == BUFFER_OFFSET_HALF)
{
bytesread = GetData((void *)buffer_ctl.SrcAddress,
buffer_ctl.fptr,
&buffer_ctl.buff[0],
AUDIO_BUFFER_SIZE /2);
if( bytesread >0)
{
buffer_ctl.state = BUFFER_OFFSET_NONE;
buffer_ctl.fptr += bytesread;
}
}
/* 2nd half buffer played; so fill it and continue playing from top */
if(buffer_ctl.state == BUFFER_OFFSET_FULL)
{
bytesread = GetData((void *)buffer_ctl.SrcAddress,
buffer_ctl.fptr,
&buffer_ctl.buff[AUDIO_BUFFER_SIZE /2],
AUDIO_BUFFER_SIZE /2);
if( bytesread > 0)
{
buffer_ctl.state = BUFFER_OFFSET_NONE;
buffer_ctl.fptr += bytesread;
}
}
break;
default:
error_state = AUDIO_ERROR_NOTREADY;
break;
}
return (uint8_t) error_state;
}
最后我们插上3.5mm耳机,烧录程序,就能听到流行音乐《秒针》响起,一起尽情摇摆吧!
STM32播放流行音乐