开发板的基本信息:gd32f450zkt6;使用的音频D/A转换器芯片:CS4344。
查看CS4344芯片的数据手册,该音频转换芯片引脚介绍如下:
查看mcu的数据手册,使用I2S1接口,具体使用的引脚是PC1,PC7,PB9,PC6。引脚介绍如下:
官方设计的电路原理图如下:
实际的电路如下:
程序设计基本流程:
I2S的引脚功能配置(配置I2S外设功能)
开启中断配置
读取音频文件
发送音频数据
官方代码分析:
配置中断优先级分组,允许中断请求
nvic_priority_group_set(NVIC_PRIGROUP_PRE0_SUB4);
nvic_irq_enable(SPI1_IRQn, 0, 1);
音频文件播放功能函数
i2s_audio_play()
具体分析音频文件播放功能,步骤如下:
1. 判断是否是wave audio文件,同时得到数据的起始位置datastartaddr 。
/*!
\brief wave audio file parsing function
\param[in] none
\param[out] none
\retval errorcode_enum
*/
errorcode_enum codec_wave_parsing(void)
{
uint32_t temp = 0;
uint32_t extraformatbytes = 0;
/* initialize the headertab index variable */
headertab_index = 0;
/* read chunkid, must be 'riff' */
if(CHUNKID != read_unit(4, bigendian)) {
return(UNVALID_RIFF_ID);
}
/* read the file length */
wave_struct.riffchunksize = read_unit(4, littleendian);
/* read the file format, must be 'wave' */
if(FILEFORMAT != read_unit(4, bigendian)) {
return(UNVALID_WAVE_FORMAT);
}
/* read the format chunk, must be 'fmt' */
if(FORMATID != read_unit(4, bigendian)) {
return(UNVALID_FORMATCHUNK_ID);
}
/* read the size of the 'fmt' data, must be 0x10 */
if(FORMATCHUNKSIZE != read_unit(4, littleendian)) {
extraformatbytes = 1;
}
/* read the audio format, must be 0x01 (pcm) */
wave_struct.formattag = read_unit(2, littleendian);
if(WAVE_FORMAT_PCM != wave_struct.formattag) {
return(UNSUPPORETD_FORMATTAG);
}
/* read the number of channels: 0x02->stereo 0x01->mono */
wave_struct.numchannels = read_unit(2, littleendian);
/* read the sample rate */
wave_struct.samplerate = read_unit(4, littleendian);
/* update the i2s_audiofreq value according to the .wav file sample rate */
if((wave_struct.samplerate < 8000) || (wave_struct.samplerate > 192000)) {
return(UNSUPPORETD_SAMPLE_RATE);
} else {
i2saudiofreq = wave_struct.samplerate;
}
/* read the byte rate */
wave_struct.byterate = read_unit(4, littleendian);
/* read the block alignment */
wave_struct.blockalign = read_unit(2, littleendian);
/* read the number of bits per sample */
wave_struct.bitspersample = read_unit(2, littleendian);
if(BITS_PER_SAMPLE_16 != wave_struct.bitspersample) {
return(UNSUPPORETD_BITS_PER_SAMPLE);
}
/* if there are extra format bytes, these bytes will be defined in "fact chunk" */
if(1 == extraformatbytes) {
/* read th extra format bytes, must be 0x00 */
if(0x00 != read_unit(2, littleendian)) {
return(UNSUPPORETD_EXTRAFORMATBYTES);
}
/* read the fact chunk, must be 'fact' */
if(FACTID != read_unit(4, bigendian)) {
return(UNVALID_FACTCHUNK_ID);
}
/* read fact chunk data size */
temp = read_unit(4, littleendian);
/* set the index to start reading just after the header end */
headertab_index += temp;
}
/* read the data chunk, must be 'data' */
if(DATAID != read_unit(4, bigendian)) {
return(UNVALID_DATACHUNK_ID);
}
/* read the number of sample data */
wave_struct.datasize = read_unit(4, littleendian);
/* set the data pointer at the beginning of the effective audio data */
datastartaddr += headertab_index;
return(VALID_WAVE_FILE);
}
2. 文件格式正确,进行I2S配置
/*!
\brief configure the I2S peripheral
\param[in] none
\param[out] none
\retval none
*/
void i2s_config()
{
/* enable the GPIO clock */
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_GPIOC);
/* enable I2S1 clock */
rcu_periph_clock_enable(RCU_SPI1);
/* I2S1_MCK(PC6), I2S1_CK(PC7), I2S1_WS(PB9), I2S1_SD(PC1) GPIO pin configuration */
gpio_af_set(GPIOC, GPIO_AF_5, GPIO_PIN_6 | GPIO_PIN_7);
gpio_af_set(GPIOC, GPIO_AF_7, GPIO_PIN_1);
gpio_af_set(GPIOB, GPIO_AF_5, GPIO_PIN_9);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1 | GPIO_PIN_6 | GPIO_PIN_7);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1 | GPIO_PIN_6 | GPIO_PIN_7);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
spi_i2s_deinit(SPI1);
/* I2S1 peripheral configuration */
i2s_psc_config(SPI1, i2saudiofreq, I2S_FRAMEFORMAT_DT16B_CH16B, I2S_MCLKOUTPUT);
i2s_init(SPI1, I2S_MODE_MASTERTX, I2S_STANDARD, I2S_CKPL_HIGH);
/* enable the I2S1 peripheral */
i2s_enable(SPI1);
}
3. 中断使能
spi_i2s_interrupt_enable(SPI1, SPI_I2S_INT_TBE);
接下来看下中断函数中的处理,进行音频数据发送
/*!
\brief this function handles SPI1 exception
\param[in] none
\param[out] none
\retval none
*/
void SPI1_IRQHandler(void)
{
if(SET == spi_i2s_interrupt_flag_get(SPI1, SPI_I2S_INT_TBE))
/* send data */
{
i2s_audio_data_send();
}
}
音频发送函数的具体实现,如下:
/*!
\brief send audio data
\param[in] none
\param[out] none
\retval none
*/
void i2s_audio_data_send(void)
{
/* send the data read from the memory */
spi_i2s_data_transmit(SPI1, read_half_word(audiodataindex + datastartaddr));
/* increment the index */
audiodataindex += (uint32_t)wave_struct.numchannels ;
}
audiodataindex 每次加1,(因为wave_struct.numchannels的值是1),也就是每次读取音频数据的地址会加1。
注意read_half_word()函数,对read_half_word()函数进行查看,如下:
/*!
\brief read half word
\param[in] offset : audio data index
\param[out] none
\retval audio data
*/
uint16_t read_half_word(uint32_t offset)
{
static uint32_t monovar = 0, tmpvar = 0;
if((AUDIOFILEADDRESS + offset) >= AUDIOFILEADDRESSEND) {
spi_i2s_interrupt_disable(SPI1, SPI_I2S_INT_TBE);
audiodataindex = 0;
}
/* test if the left channel is to be sent */
if(0 == monovar) {
tmpvar = (*(__IO uint16_t *)(AUDIOFILEADDRESS + offset));
/* increment the mono variable only if the file is in mono format */
if(CHANNEL_MONO == wave_struct.numchannels)
/* increment the monovar variable */
{
monovar++;
}
/* return the read value */
return tmpvar;
/* right channel to be sent in mono format */
} else {
/* reset the monovar variable */
monovar = 0;
/* return the previous read data in mono format */
return tmpvar;
}
}
整个的流程到此结束。
将程序下载到开发板,插上耳机即可收听到播放的音频。
————————————————
版权声明:本文为CSDN博主「嵌入式学习和实践」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_46158019/article/details/130377276
|
|