本帖最后由 coslight 于 2022-12-3 09:23 编辑
通过I2S播放flash中的音乐文件 本测试是在前一个测试的基础上,利用shell终端和ymodem将音乐文件存储到spi flash中,然后通过shell 终端启动I2S来播放存储在flash中的音乐文件。
1、文件制作
由于这款控制器是CM0内核,处理能力有限,所以只能播放WAV或PCM这类不需要解压缩操作的音频文件,这里选择采用PCM文件,因为处理起来相当的简单。
PCM(Pulse Code Modulation)编码,即通过脉冲编码调制方法生成数字音频数据的技术或格式,是一种无损编码格式,是音频模拟信号数字化的一种方法,需要经过采样、量化和编码过程,以实现音频模拟信号数字化。简单说,PCM文件中存储的就是AD量化后的音频数据,通常为16位的有符号数,我们也不用做处理,读出这些文件直接通过I2S送给DA输出就可以了。当然还是有一些特殊性的,不过这里不详细讨论了,感兴趣的同学可以上网查找一下。
这里我们还需要两个软件,一个为ffmpeg,一个可以将mp3文件转换为我们需要的pcm格式的文件。另一个是CoolEdit,这个工具软件可以播放音频文件,也可以剪辑音频文件,贼方便。
1)Mp3转PCM
我们利用ffmpeg 实现这个功能,前面我们说,控制器能力有限,我们这里也尽量简化PCM文件的格式,稍有音质损失也是可以接受的。所以选择为单通道,8K采样率的输出格式,我们可以采用下面的命令,
.\ffmpeg -i 音乐名称.mp3 -f s16le -ar 8000 -ac 1 -acodec pcm_s16le 输出音频名称.pcm
通常一首3分钟左右的mp3,转换为单通道8K采样率的pcm文件通常在3M字节左右的文件,这个对于我们来说太大了,我们必须要剪裁一下才可以。
2)PCM文件剪裁
打开Cooledit工具软件,载入刚才转换好的PCM文件,这里要注意,因为这个PCM文件中是没有格式的,所以我们需要选择好格式,如下图所示。
打开音频文件后,我们可以试听,也可以剪裁,由于我们的flash一共才1M字节,所以我们必须剪裁到整首歌的绝大部份,直到小于1M为止,最好不要超过900K了,不一会儿用串口ymodem下载的时间也是超级长。
2、通过I2S来播放音乐文件
2.1 硬件接口
通过硬件原理图可见,I2S接口功占用5个IO口,分别为PC2,PC4,PC5,PC6,PC7。由于PC4,5,7三个IO口和SLCD的驱动引脚复用了,所以这里必须注意,需要将SW1和SW2的开关拨到右侧,使能I2S的连接。
2.2 I2S接口初始化
为了简化实现过程,我这里选择采用中断方式来完成,因此I2S接口的配置为:
/* I2S. */
#define BOARD_I2S_PORT SPI1
#define BOARD_I2S_FREQ CLOCK_APB2_FREQ
#define BOARD_I2S_SAMPLE_RATE 8000u
#define BOARD_I2S_DATA_WIDTH I2S_DataWidth_16b
#define BOARD_I2S_PROTOCOL I2S_Protocol_PHILIPS
#define BOARD_I2S_CPOL I2S_Polarity_0
#define BOARD_I2S_IRQHandler SPI1_IRQHandler
#define BOARD_I2S_IRQn SPI1_IRQn
因为我们的PCM文件为单通道,8K采样率,16位采样宽度,因此,配置参数如上。
初始化I2S为主机发送模式,产生MCLK信号。
/* setup I2S master module. */
I2S_Master_Init_Type i2s_master_init;
i2s_master_init.ClockFreqHz = BOARD_I2S_FREQ;
i2s_master_init.SampleRate = BOARD_I2S_SAMPLE_RATE;
i2s_master_init.DataWidth = BOARD_I2S_DATA_WIDTH;
i2s_master_init.Protocol = BOARD_I2S_PROTOCOL;
i2s_master_init.EnableMCLK = true;
i2s_master_init.Polarity = BOARD_I2S_CPOL;
i2s_master_init.XferMode = I2S_XferMode_TxOnly;
I2S_InitMaster(BOARD_I2S_PORT, &i2s_master_init);
/* Enable I2S tx buffer empty interrupt. */
I2S_EnableInterrupts(BOARD_I2S_PORT, I2S_INT_TX_EMPTY, true);
NVIC_EnableIRQ(BOARD_I2S_IRQn);
2.3 I2S中断函数
I2S中断中判断当前的传输通道,让后切换到下一个通道去传输数据,实际是先左通道,在右通道,如此交替下去。
由于是单通道,所以读取一次数据分别传递给左右两个通道,相当于让他们发出相同的声音。
所在右通道传输完成的时候,我们从flash中读取两个字节,构成一个16位的音频数据,所以数据指针每次都是加2的,直到文件尾部,关闭I2S,结束播放。
/* I2S interrupt handler. */
void BOARD_I2S_IRQHandler(void)
{
static int16_t data = 0;
/* Get interrupt status. */
uint32_t flag = I2S_GetInterruptStatus(BOARD_I2S_PORT);
if (0u != (flag & I2S_INT_TX_EMPTY) )
{
if (I2S_Channel_Left != I2S_GetXferChannel(BOARD_I2S_PORT) )
{
app_i2s_put_data_left(data); /* Send right data, buffer put left data to prepare next send. */
}
else
{
app_i2s_put_data_right(data); /* Send left data, buffer put right data to prepare next send. */
sfud_read(sfud_get_device(SFUD_SPI_DEVICE_INDEX), FlashDestination + APP_SFUD_PAGE_SIZE, 2, (uint8_t *)&data);
FlashDestination+=2;
if(FlashDestination >= musicl)
{
I2S_Enable(BOARD_I2S_PORT, false);
}
if((FlashDestination % 1000 ) < 500)
{
GPIO_WriteBit(BOARD_LED1_GPIO_PORT, BOARD_LED1_GPIO_PIN, 1u);
GPIO_WriteBit(BOARD_LED2_GPIO_PORT, BOARD_LED2_GPIO_PIN, 0u);
}
else
{
GPIO_WriteBit(BOARD_LED1_GPIO_PORT, BOARD_LED1_GPIO_PIN, 0u);
GPIO_WriteBit(BOARD_LED2_GPIO_PORT, BOARD_LED2_GPIO_PIN, 1u);
}
}
}
/* Clear interrupt status. */
I2S_ClearInterruptStatus(BOARD_I2S_PORT, flag);
}
3)shell的使用
建立一个shell命令,用来启动音乐播放。
int play(int argc, char *argv[])
{
uint8_t musiclen[10];
uint8_t i;
short int *data_ptr = (short int *)tab_1024;
shellPrint(&shell, "play music in spiflash\n\r");
sfud_err err;
FlashDestination = 0;
/* sfud read. */
err = sfud_read(sfud_get_device(SFUD_SPI_DEVICE_INDEX), 128, 10, musiclen);
for(i=0;i<10;i++)
{
if(musiclen[i] == 0xff)
{
musiclen[i] = '\0';
}
}
Str2Int(musiclen, &musicl);
if(musicl ==0 & musicl > 1024*1024 - 256)
{
shellPrint(&shell, "music lenth error %d\n\r", musicl);
return -1;
}
GPIO_WriteBit(BOARD_LED0_GPIO_PORT, BOARD_LED0_GPIO_PIN, 0); /* led on. */
I2S_Enable(BOARD_I2S_PORT, true);
while(musicl > FlashDestination)
{
}
GPIO_WriteBit(BOARD_LED0_GPIO_PORT, BOARD_LED0_GPIO_PIN, 1); /* led on. */
return 0;
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), play, play, play a music in flash);
3、测试运行效果
在shell终端输入play命令后,回车执行,我将在扬声器中听到我们播放的音乐。
视频
代码:
shell+ymodem+i2s.rar
(42.79 KB)
|