1 前言
本章介绍使用i2s示例驱动max97357播放音乐。
2 前期准备
需要将wav文件中获取音频采样数据,参见文章:【python】将wav文件转为c
获取到音源的数组,注意音频位深,本文使用16bit的音源,最终生成uint16_t的数组。
PS:由于内存有限,所以只能保存1s左右的音源,后续再看看通过spi加载sd卡能不能实现整段音频数据读取。
3 电路
测试时注意i2s三个信号上拉电阻,我是上拉10k电阻到3.3v。
4 代码
main.c
/*******************************************************************************
*
* COPYRIGHT(c) 2020, China Mobile IOT
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of China Mobile IOT nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*******************************************************************************/
/**
* @file main.c
* @author CMIOT Firmware Team
* @version v1.0.0
*
* @copyright Copyright (c) 2020, CMIOT. All rights reserved.
*/
#include <main.h>
#include "log.h"
//#include "voice.h"
#include "music2.h"
#define BufferSize (32)
DMA_InitType DMA_InitStructure;
GPIO_InitType GPIO_InitStructure;
I2S_InitType I2S_InitStructure;
bool status_wait_uart = false;
const uint16_t I2S_MASTER_Buffer_Tx[BufferSize] = {
0x0102, 0x0304, 0x0506, 0x0708, 0x090A, 0x0B0C, 0x0D0E, 0x0F10,
0x1112, 0x1314, 0x1516, 0x1718, 0x191A, 0x1B1C, 0x1D1E, 0x1F20,
0x2122, 0x2324, 0x2526, 0x2728, 0x292A, 0x2B2C, 0x2D2E, 0x2F30,
0x3132, 0x3334, 0x3536, 0x3738, 0x393A, 0x3B3C, 0x3D3E, 0x3F40};
const uint16_t sin_wav[BufferSize] = {
0x0000, 0x1869, 0x2fec, 0x45ae,
0x5a78, 0x6a13, 0x75ca, 0x7d2e,
0x7ffe, 0x7daa, 0x76b9, 0x6b6c,
0x5a9d, 0x47b6, 0x322c, 0x1acc,
0x0034, 0xe7cb, 0xd044, 0xba7e,
0xa5ad, 0x960a, 0x8a4a, 0x82dd,
0x8002, 0x824c, 0x8934, 0x9478,
0xa53e, 0xb81f, 0xcda4, 0xe501
};
// dma buffer num bigger, noise obvious
#define DMA_BUFF_TX_MAX 16 //16 is a suitable value
uint16_t dma_buff_tx[DMA_BUFF_TX_MAX]={
};
uint16_t I2S_SLAVE_Buffer_Rx[BufferSize];
volatile Status TransferStatus = FAILED;
ErrorStatus HSEStartUpStatus;
void RCC_Configuration(void);
void GPIO_Configuration(void);
Status Buffercmp(uint16_t* pBuffer1, uint16_t* pBuffer2, uint16_t BufferLength);
void Delay(__IO uint32_t nCount);
/* add*/
uint32_t wav_point=0; // dma get voice sample num
uint32_t num = 0; // i2s send num
#define TEST_NUM 1
// 1. sinwav; 2. music; 3. dma tx
/**
* @brief Main function.
*/
int main(void)
{
/*!< At this stage the microcontroller clock setting is already configured,
this is done through SystemInit() function which is called from startup
file (startup_cm32m4xxr.s) before to branch to application main.
To reconfigure the default setting of SystemInit() function, refer to
system_cm32m4xxr.c file
*/
log_init();
log_info("I2S_DMA test start\r\n");
// init dma tx buffer
memset(dma_buff_tx,0x0000,DMA_BUFF_TX_MAX);
memcpy(dma_buff_tx,music2,DMA_BUFF_TX_MAX);
/* System clocks configuration ---------------------------------------------*/
RCC_Configuration();
/* GPIO configuration ------------------------------------------------------*/
GPIO_Configuration();
/* Deinitializes the SPI2 and SPI3 peripheral registers --------------------*/
SPI_I2S_DeInit(I2S_SLAVE);
SPI_I2S_DeInit(I2S_MASTER);
/* I2S_SLAVE_Rx_DMA_Channel configuration ---------------------------------------------*/
DMA_DeInit(I2S_SLAVE_Rx_DMA_Channel);
DMA_InitStructure.PeriphAddr = (uint32_t)I2S_SLAVE_DR_Base;
DMA_InitStructure.MemAddr = (uint32_t)I2S_SLAVE_Buffer_Rx;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC;
DMA_InitStructure.BufSize = BufferSize;
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEM_INC_ENABLE;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_HALFWORD;
DMA_InitStructure.MemDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL;
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH;
DMA_InitStructure.Mem2Mem = DMA_M2M_DISABLE;
DMA_Init(I2S_SLAVE_Rx_DMA_Channel, &DMA_InitStructure);
/* I2S_MASTER_Tx_DMA_Channel configuration ---------------------------------------------*/
DMA_DeInit(I2S_MASTER_Tx_DMA_Channel);
DMA_InitStructure.PeriphAddr = (uint32_t)I2S_MASTER_DR_Base;
DMA_InitStructure.MemAddr = (uint32_t)dma_buff_tx;
DMA_InitStructure.BufSize = DMA_BUFF_TX_MAX;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
DMA_Init(I2S_MASTER_Tx_DMA_Channel, &DMA_InitStructure);
/* I2S peripheral configuration */
I2S_InitStructure.Standard = I2S_STD_PHILLIPS;
I2S_InitStructure.DataFormat = I2S_DATA_FMT_16BITS;//_EXTENDED;
I2S_InitStructure.MCLKEnable = I2S_MCLK_DISABLE;
I2S_InitStructure.AudioFrequency = I2S_AUDIO_FREQ_48K;
// I2S_InitStructure.AudioFrequency = I2S_AUDIO_FREQ_48K*16*2;
I2S_InitStructure.CLKPOL = I2S_CLKPOL_LOW;
/* I2S3 Master Transmitter to I2S2 Slave Receiver communication ------------*/
/* I2S3 configuration */
I2S_InitStructure.I2sMode = I2S_MODE_MASTER_TX;
I2S_Init(I2S_MASTER, &I2S_InitStructure);
/* I2S2 configuration */
I2S_InitStructure.I2sMode = I2S_MODE_SlAVE_RX;
I2S_Init(I2S_SLAVE, &I2S_InitStructure);
/* Enable I2S_SLAVE Rx request */
SPI_I2S_EnableDma(I2S_SLAVE, SPI_I2S_DMA_RX, ENABLE);
/* Enable I2S_MASTER Tx request */
SPI_I2S_EnableDma(I2S_MASTER, SPI_I2S_DMA_TX, ENABLE);
/* Enable the I2S2 */
I2S_Enable(I2S_SLAVE, ENABLE);
/* Enable the I2S3 */
I2S_Enable(I2S_MASTER, ENABLE);
/* Enable DMA2 Channel2 */
DMA_EnableChannel(I2S_MASTER_Tx_DMA_Channel, ENABLE);
/* Enable DMA1 Channel4 */
DMA_EnableChannel(I2S_SLAVE_Rx_DMA_Channel, ENABLE);
// test start
while(1)
{
switch_test();
}
printf("end send wav data to speaker\r\n");
// printf("start receive wav data from mic\r\n");
// while (!DMA_GetFlagStatus(I2S_SLAVE_Rx_DMA_FLAG, DMA1))
// ;
while (1)
{
}
}
/**
* @brief Configures the different system clocks.
*/
void RCC_Configuration(void)
{
/* Enable peripheral clocks --------------------------------------------------*/
/* Enable I2S_SLAVE DMA clock */
RCC_EnableAHBPeriphClk(I2S_SLAVE_DMA_CLK, ENABLE);
RCC_EnableAHBPeriphClk(I2S_MASTER_DMA_CLK, ENABLE);
/* GPIOB, GPIOC, GPIOD and AFIO clocks enable */
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB | RCC_APB2_PERIPH_GPIOC | RCC_APB2_PERIPH_GPIOD | RCC_APB2_PERIPH_AFIO, ENABLE);
/* SPI2 and SPI3 clocks enable */
RCC_EnableAPB1PeriphClk(I2S_SLAVE_CLK | I2S_MASTER_CLK, ENABLE);
}
/**
* @brief Configures the different GPIO ports.
*/
void GPIO_Configuration(void)
{
GPIO_InitType GPIO_InitStructure;
/* Disable the JTAG interface and enable the SWJ interface
This operation is not necessary for Connectivity Line devices since
SPI3 I/Os can be remapped on other GPIO pins */
// GPIO_ConfigPinRemap(GPIO_RMP_SW_JTAG_SW_ENABLE, ENABLE);
GPIO_ConfigPinRemap(GPIO_RMP1_SPI3, ENABLE);
/* Configure SPI2 pins: WS, CK and SD ---------------------------------*/
GPIO_InitStructure.Pin = I2S_SLAVE_PIN_WS | I2S_SLAVE_PIN_CK | I2S_SLAVE_PIN_SD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* Configure SPI3 pins: CK and SD ------------------------------------*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.Pin = I2S_MASTER_PIN_CK | I2S_MASTER_PIN_SD;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* Configure SPI3 pins: WS -------------------------------------------*/
GPIO_InitStructure.Pin = I2S_MASTER_PIN_WS;
GPIO_Init(GPIOD, &GPIO_InitStructure);
}
/**
* @brief Compares two buffers.
* @param pBuffer1, pBuffer2: buffers to be compared.
* @param BufferLength buffer's length
* @return PASSED: pBuffer1 identical to pBuffer2
* FAILED: pBuffer1 differs from pBuffer2
*/
Status Buffercmp(uint16_t* pBuffer1, uint16_t* pBuffer2, uint16_t BufferLength)
{
while (BufferLength--)
{
if (*pBuffer1 != *pBuffer2)
{
return FAILED;
}
pBuffer1++;
pBuffer2++;
}
return PASSED;
}
/**
* @brief Inserts a delay time.
* @param nCount specifies the delay time length.
*/
void Delay(__IO uint32_t nCount)
{
/* Decrement nCount value */
for (; nCount != 0; nCount--)
;
}
/* ---------------------- add ------------------------*/
void print_uart_wait()
{
if(status_wait_uart == true)
{
status_wait_uart = false;
printf("i2s transfer waiting......\r\n");
}
}
void print_uart_wait_end()
{
status_wait_uart = true;
printf("end i2s wait.\r\n");
}
void myI2s_tx(uint8_t i2s_id, uint16_t * data, uint16_t len_set)
{
// ?
uint16_t len_data = sizeof(data)/sizeof(uint16_t);
printf("sizeof(data) = %d\r\n",sizeof(data));
bool str_data = len_set>=len_data;
// if send overside strlen, just send strlen
uint16_t len = str_data ? len_data:len_set;
printf("strlen = %d, send len = %d, send>strlen = %s, len = %d\r\n",len_data,len_set,
str_data?"true":"false",len);
if(str_data == true)
{
printf("send data len overside str_len.\r\n");
}
else
{
printf("send data len = %d.\r\n",len_set);
}
myI2s_senddata(i2s_id,data,len);
}
void myI2s_senddata(uint8_t i2s_id, uint16_t* data, uint16_t len_set)
{
uint16_t * temp_value = data;
uint16_t temp_len = len_set;
printf("len_set = %d, strvalue = [%s]\r\n",temp_len,(char * )data);
// choose i2s
SPI_Module* i2s = SPI2;
uint16_t flag = SPI_I2S_TE_FLAG;
switch(i2s_id)
{
case 2:
i2s = SPI2;
break;
case 3:
i2s = SPI3;
break;
default:
printf("use default uart5\r\n");
i2s = SPI2;
break;
}
uint16_t i=0;
while(temp_len > i)
{
// printf("i = %d, value = %04x\r\n",i,*temp_value);
/* Wait the Tx buffer to be empty */
// while (SPI_I2S_GetStatus(i2s, flag) == RESET)
// {
// print_uart_wait();
// }
// print_uart_wait_end();
/* Send a data from SPI3 */
SPI_I2S_TransmitData(i2s, *temp_value);
printf("i = %d\n",i);
i++;
temp_value++;
}
return;
}
void reset_dma_tx()
{
// print_dma();
DMA_EnableChannel(I2S_MASTER_Tx_DMA_Channel, DISABLE);
// printf("has send i2s3 tx\r\n");
memset(dma_buff_tx,0x0000,DMA_BUFF_TX_MAX);
memcpy(dma_buff_tx,music2 + wav_point,DMA_BUFF_TX_MAX);
wav_point+=DMA_BUFF_TX_MAX;
if(wav_point >= MUSIC_VALUE_MAX)
wav_point=0;
// printf("wav_point=%d\r\n",wav_point);
// printf("dma_buff_tx = %04x\r\n",dma_buff_tx[0]);
DMA_SetCurrDataCounter(I2S_MASTER_Tx_DMA_Channel,DMA_BUFF_TX_MAX);
// printf("dma count = %d\r\n",DMA_GetCurrDataCounter(I2S_MASTER_Tx_DMA_Channel));
DMA_ClearFlag(I2S_MASTER_Tx_DMA_FLAG, DMA2);
DMA_EnableChannel(I2S_MASTER_Tx_DMA_Channel, ENABLE);
}
void switch_test()
{
switch(TEST_NUM)
{
case 1:
test1_sinwav();
break;
case 2:
test2_music();
break;
case 3:
test3_dma_tx();
break;
default:
break;
}
}
void test1_sinwav()
{
/* test 1: sinwav*/
// printf("test1_sinwav\r\n");
SPI_I2S_TransmitData(I2S_MASTER, sin_wav[num]);
num++;
if(num == 32)
num = 0;
// printf("data = %04x\r\n",I2S_MASTER_Buffer_Tx_t[num]);
}
void test2_music()
{
/* test 2: music1 (I2S SEND)*/
// printf("test2_music\r\n");
SPI_I2S_TransmitData(I2S_MASTER, music2[num]);
num++;
if(num == MUSIC_VALUE_MAX)
num = 0;
// Delay(500);
// printf("data[%d] = %04x\r\n",num,music1[num]);
// printf("data[%d]\r\n",num);
/* Wait for I2S TX buffer empty */
while (SPI_I2S_GetStatus(I2S_MASTER, SPI_I2S_TE_FLAG) == RESET)
;
}
void test3_dma_tx()
{
/* test 3: music1 (DMA SEND)*/
// printf("test3_dma_tx\r\n");
/* Wait for DMA2 channel2 transfer complete */
while (!DMA_GetFlagStatus(I2S_MASTER_Tx_DMA_FLAG, DMA2))
;
if(DMA_GetFlagStatus(I2S_MASTER_Tx_DMA_FLAG, DMA2) == SET)
reset_dma_tx();
}
music2.h
#define MUSIC_VALUE_MAX 77715
const uint16_t music2[] = {
0x06AB,0x052B,0x03E6,0x0353,0x0631,0x0966,0x0874,0x0C2A,
0x1615,0x16D6,0x1360,0x18C3,0x1D52,0x18C8,0x11B8,0x0ED2,
0x0E65,0x0E30,0x1073,0x14AA,0x1767,0x1CE3,0x284B,0x30F5,
0x31D9,0x2FBF,0x317E,0x3488,0x31AC,0x2A56,0x22A7,0x22AB,
//省略。。。
};
5 结果
代码中有test_num,1为播放正弦波,2为音乐,3为通过dma播放。
1,2都是调用SPI_I2S_TransmitData实现播放,3是通过写入dma数组播放。
注意:播放代码中不要添加printf函数,否则会无法出声。
6 后续
- 由于内存有限,音源wav只能设置1s左右,后续看看spi读卡是否可以支持完整播放。
- 播放结果来看,好像是加速了,不清楚哪里设置异常。