PWM音乐播放范例 验证平台: 6621CxC 附件代码放置 :\...\SVN3420\examples ,编译可运行 示例 音源为PCM压缩后的数据,压缩率为1:4。(不提供压缩工具) #include "rwip_config.h" // RW SW configuration #include "arch.h" // architectural platform definitions #include <stdlib.h> // standard lib functions #include <stddef.h> // standard definitions #include <stdint.h> // standard integer definition #include <stdbool.h> // boolean definition #include <stdio.h> #include <string.h> #include "rwip.h" // RW SW initialization #include "dbg.h" #include "peripheral.h" #include "sysdump.h" #include "app.h" #include "adpcm.h" #include "internal_flash_data.h" #include "my_audio.h" /* 定义DMA“乒乓”缓冲区 */ /* {...}, {...}, {...} */ #define nMs 20 // 20ms voice buffer #define AUDIO_BUFF_BLOCK_SIZE (nMs * 16 * 2) //定义DMA_PWM 缓存字节大小 #define BLOCK_NUM 3 // block counter define, more than 3 block //DMA Buffer必须4字节对齐 CO_ALIGN(4) uint16_t Audio_PCM_Buffer[BLOCK_NUM][AUDIO_BUFF_BLOCK_SIZE / 2] = {0}; //short int uint8_t block_index = 0; static dma_fifo_t ccr_dma_fifo; HS_DMA_CH_Type *p_dma_channel = NULL; #define PWM_Timer HS_TIM1 /* 当前正在播放的文件头,本Demo中,是固定值,直接指定 */ #pragma pack(1) typedef struct { uint32_t addr; //地址 uint32_t length; //长度 } SoundInfo_stu_t; #pragma pack() /* 语音信息 */ static SoundInfo_stu_t soundInfo; /* 当前播放的语音文件,剩余的(还未播放的)数据 */ // static uint32_t audioRemainderDataSize; static int32_t audioRemainderDataSize = 0; /* 缓存大小 */ static uint16_t m_buffer_Block_Size = 0; /*所有数据播放结束标志*/ uint8_t audioStopflag = 0; uint8_t audio_irq_flag = 0; void PWM_audio_play(); /*-------------------- define play status *--------------------*/ typedef enum { ePlay_statu_init = (0), ePlay_statu_idle = (1), ePlay_statu_playing = (2), ePlay_statu_stop = (3), }; uint8_t m_play_status = ePlay_statu_init; void Audio_Init(void) { //参考src\internal_flash_data.h soundInfo.addr = 24; soundInfo.length = 0x6A52; //bytes audioRemainderDataSize = soundInfo.length; m_buffer_Block_Size = AUDIO_BUFF_BLOCK_SIZE; ADPCM_Init(); } /** * @brief 从Flash/SRAM中读取数据 * * @param data * @param len * @param param */ void flash_audio_read(void *data, uint32_t len, uint32_t param) { //本例只从SRAM中读取 memcpy(data, &SOUND_DATA[param], len); //如果需要从Flash中读取,需要调用SF相应的接口 } /** * @brief 读取音乐PCM数据,并填充我DMA Buffer中 * * @param pBuf:解析后的数据 * @param flashAddr: ADPCM数据地址 * @param readSize: 读取数据的字节数 */ void Audio_ReadAdpcmAndDecode(uint8_t *pBuf, uint32_t flashAddr, uint16_t readSize) { uint16_t i; int16_t sound; uint8_t *p_audioDataBufferTemp = malloc(readSize); if (p_audioDataBufferTemp == NULL) { printf("audioDataBuffer malloc error\r\n"); return; } flash_audio_read(p_audioDataBufferTemp, readSize, flashAddr); for (i = 0; i < readSize; i++) { //低4Bit 还原2个Byte sound = ADPCM_Decode(p_audioDataBufferTemp[i] & 0x0f); *pBuf++ = (uint8_t)(sound & 0x00ff); *pBuf++ = (uint8_t)((sound >> 8)); //高4Bit 还原2个Byte sound = ADPCM_Decode(p_audioDataBufferTemp[i] >> 4); *pBuf++ = (uint8_t)(sound & 0x00ff); *pBuf++ = (uint8_t)((sound >> 8)); } free(p_audioDataBufferTemp); } /** * @brief * * @param data * @param len * @param param * @return ** void */ void Audio_Hal_Write(void *data, uint32_t len, uint32_t param) { uint16_t i; int16_t *pData = (int16_t *)data; len /= 2; for (i = 0; i < len; i++) // 128 { /* 输入范围 缩放系数 缩放后值 偏移值 偏移后值 PWM周期 占空比 -32768 0.03 -983.04 1000 16.96 2000 0.85% 0 0.03 0 1000 1000 2000 50.00% 32768 0.03 983.04 1000 1983.04 2000 99.15% */ #define PWM_Period_CNT 2000 // THE PWM Period Counter #define scale_X 0.03 // scale_x= PWM_Period_CNT/2/32768 /*原数据进行适当缩放,确保能完全在PWM范围内,x0.03 相当于除 34,即缩小34倍*/ Audio_PCM_Buffer[block_index][i] = pData[i] / 34 + (PWM_Period_CNT / 2); } block_index++; if (block_index >= BLOCK_NUM) { block_index = 0; } } uint8_t sAudioDataBuffer[AUDIO_BUFF_BLOCK_SIZE] = {0}; /** * @brief 加载语音数据 * * @note */ void Audio_DataLoading(void) { // log_debug("read_data_Size %d\r\n",read_data_Size); int32_t read_data_Size = audioRemainderDataSize; memset(&sAudioDataBuffer, 0, sizeof(sAudioDataBuffer)); //为什么只读m_buffer_Block_Size/4, 因为普通PCM压缩是按1:4进行压缩 //填完一个m_buffer_Block_Size 缓冲,只需要读其1/4 //如果数据1:1没压缩,那此处只需要按m_buffer_Block_Size读即可。 if (read_data_Size >= (m_buffer_Block_Size / 4)) { read_data_Size = m_buffer_Block_Size / 4 ; } Audio_ReadAdpcmAndDecode(sAudioDataBuffer, soundInfo.addr + soundInfo.length - audioRemainderDataSize, read_data_Size); Audio_Hal_Write(sAudioDataBuffer, m_buffer_Block_Size, 0); /* 记录剩余数据大小 */ audioRemainderDataSize -= read_data_Size; //未读取的数据大小 // if(audioRemainderDataSize == 0) if (audioRemainderDataSize <= 0) { audioStopflag = 1; } } #define evt_load_data (KE_EVENT_USR_FIRST+3) static co_timer_t file_buffer_timer; static void dma_cb1(dma_status_t status, uint32_t cur_src_addr, uint32_t cur_dst_addr, uint32_t xfer_size) { // use to test fill buffer time audio_irq_flag = 1; //标志产生了BLOCK中断,此时需要加载一个Block数据到Buffer ke_event_set(evt_load_data); gpio_toggle(BITMASK(LED_RED_PIN)); //翻转一下标记 } /** * @brief evt_load_data_callback evt_load_data_callback事件响应中加载数据,如果任务比较多,有可能不能及时加载,导致断续 * 可以改用一个硬件定时器,间隔几ms检测是否需要加载来加载 */ void evt_load_data_callback(void) { gpio_write(BITMASK(LED_GREEN_PIN), GPIO_LOW); // test pin #if 0 //Audio_DataLoading(); #else //此处调用 PWM_audio_play ,case ePlay_statu_playing, 读取数据,结束时,关闭PWM PWM_audio_play(); #endif gpio_write(BITMASK(LED_GREEN_PIN), GPIO_HIGH); // test pin ke_event_clear(evt_load_data); } typedef struct { uint32_t pin_index; pinmux_t pin_function; gpio_direction_t io_direct; pmu_pin_mode_t io_mode; gpio_trigger_type_t int_trigger_type; pmu_pin_wakeup_type_t wakeup_type; gpio_level_t default_level; } gpio_setting_t; void gpios_init(gpio_setting_t *gpio_setting) { uint32_t pin_idx; uint32_t Bitmask; pin_idx = gpio_setting->pin_index; pinmux_config(pin_idx, gpio_setting->pin_function); } /* GPIO初始化配置 */ static void AudioHalGPIO_Init(void) { int i; gpio_setting_t PWM_Buzzer_IOs[] = { {PWM_PIN, PINMUX_TIMER1_IO_0_CFG, GPIO_OUTPUT, PMU_PIN_MODE_PP, GPIO_TRIGGER_DISABLE, PMU_PIN_WAKEUP_DISABLE, GPIO_LOW}, }; for (i = 0; i < sizeof(PWM_Buzzer_IOs) / sizeof(gpio_setting_t); i++) { gpios_init(&PWM_Buzzer_IOs[i]); } } typedef struct { /// DMA fifo buffer bool use_fifo; void *buffer; uint32_t buffer_len; dma_llip_t *block_llip; uint32_t block_num; /// Absolute address of the TIM register to access __IO uint32_t *tim_addr; /// number of registers under updating uint32_t num; // DMA burst length /// TIM DMA request source: TIM_DIER_UDE, TIM_DIER_CC1DE, ..., TIM_DIER_TDE uint32_t req; /// DMA event callback dma_callback_t callback; } timer_dma_config_t; DMA_DECLARE_LLIP(dma_block_llip, BLOCK_NUM); /*-------------------- PWM Dma config... *--------------------*/ timer_dma_config_t pwm_dma_cfg1 = { .use_fifo = true, //使用链表,能保持最好的连续性 .buffer = &Audio_PCM_Buffer[0], //缓冲地址 .buffer_len = sizeof(Audio_PCM_Buffer), //总长度(bytes) .block_llip = dma_block_llip, .block_num = BLOCK_NUM, //DMA 缓冲分块数据 .tim_addr = &PWM_Timer->CCR[0], .num = 1, //每次操作数据 .req = (1 << 8), //DMA触发源 .callback = dma_cb1, //每个Block的数据发送完毕,会产生一个中断 }; /** * @brief timer_dma_config 配置 * * @param tim * @param dma * @param config * @return HS_DMA_CH_Type* */ static HS_DMA_CH_Type *timer_dma_config(HS_TIM_Type *tim, HS_DMA_CH_Type *dma, const timer_dma_config_t *config) { dma_id_t dma_id; if (dma == NULL) dma = dma_allocate(); dma_id = tim == HS_TIM0 ? TIMER0_DMA_ID : (tim == HS_TIM1 ? TIMER1_DMA_ID : TIMER2_DMA_ID); if (dma) { bool res; dma_config_t m_dma_config; m_dma_config.slave_id = dma_id; m_dma_config.direction = DMA_MEM_TO_DEV; m_dma_config.src_addr_width = DMA_SLAVE_BUSWIDTH_16BITS; m_dma_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_16BITS; m_dma_config.src_burst = DMA_BURST_LEN_1UNITS; m_dma_config.dst_burst = DMA_BURST_LEN_1UNITS; m_dma_config.dev_flow_ctrl = false; m_dma_config.priority = 0; m_dma_config.callback = config->callback; m_dma_config.lli.enable = true; m_dma_config.lli.use_fifo = config->use_fifo; m_dma_config.lli.src_addr = (uint32_t)config->buffer; m_dma_config.lli.dst_addr = (uint32_t)&tim->DMAR; m_dma_config.lli.block_num = config->block_num; m_dma_config.lli.block_len = config->buffer_len / config->block_num; m_dma_config.lli.llip = config->block_llip; res = dma_config(dma, &m_dma_config); if (!res) { dma_release(dma); return NULL; } tim->DCR = ((config->num - 1) << 8) | ((uint32_t)config->tim_addr - (uint32_t)tim) / 4; tim->DIER |= config->req; } return dma; } static void AudioHalTimer_Init(void) { const tim_config_t pwm_cfg = { .mode = TIM_PWM_MODE, .config.pwm = { // 64M 4000 16Khz .count_freq = 64 * 1000 * 1000, //定时器x模块输入时钟 .period_count = 2000, //PWM 频率 F = count_freq/period_count = 64Mhz/2000 = 32Khz .channel = { {true /*enable*/, {TIM_PWM_POL_HIGH2LOW, 0}}, //CH0 为PWM 输出通道 {false, {TIM_PWM_POL_HIGH2LOW, 5000}},// 其它通道未使用 {false, {TIM_PWM_POL_HIGH2LOW, 5000}},// 其它通道未使用 {false, {TIM_PWM_POL_HIGH2LOW, 5000}},// 其它通道未使用 }, .callback = NULL, }, }; tim_init(); tim_config(PWM_Timer, &pwm_cfg); // NVIC_SetPriority(TIM1_IRQn, IRQ_PRIORITY_HIGH); // RCR=0 波形为 ...[50%][40%][30%]... // RCR=1 即每个周期的波形会重复多1次 如,[50%][50%][40%][40%][30%][30%]... PWM_Timer->RCR = 0x1; PWM_Timer->CNT = 0x0; // CNT 初始值 为0 PWM_Timer->CCR[0] = 0; } static void AudioHalDMA_Init(void) { dma_fifo_init(&ccr_dma_fifo, Audio_PCM_Buffer, sizeof(Audio_PCM_Buffer)); dma_init(); p_dma_channel = timer_dma_config(PWM_Timer, p_dma_channel, &pwm_dma_cfg1); // NVIC_SetPriority(DMA0_IRQn, IRQ_PRIORITY_HIGH); } void AudioHal_Init(void) { /* GPIO初始化配置 */ AudioHalGPIO_Init(); /* Timer初始化配置 */ AudioHalTimer_Init(); /* DMA初始化配置 */ AudioHalDMA_Init(); Audio_Init(); } void PWM_audio_play() { switch (m_play_status) { case ePlay_statu_idle: //状态机状态为空闲时,开始播放 log_debug("\nePlay_statu_idle"); ke_event_callback_set(evt_load_data, evt_load_data_callback);//注册填充Buffer事件 Audio_DataLoading();//预加载Block 0 数据 Audio_DataLoading();//预加载Block 1 数据 gpio_write(BITMASK(LED_RED_PIN), GPIO_LOW); // test pin gpio_write(BITMASK(LED_GREEN_PIN), GPIO_LOW); // test pin gpio_write(BITMASK(LED_RED_PIN), GPIO_HIGH); // test pin gpio_write(BITMASK(LED_GREEN_PIN), GPIO_HIGH); // test pin dma_start_with_lli(p_dma_channel);//使能DMA及链表 tim_stop(PWM_Timer); PWM_Timer->CNT = 0; /*timer starts running*/ PWM_Timer->CR1 |= TIM_CR1_CEN; /*timer starts running*/ m_play_status = ePlay_statu_playing; // Timer init // tim_config(HS_TIM0, &timer_cfg); // start timer 0 too file dma data tim_start(HS_TIM0); // start timer 0 too file dma data break; case ePlay_statu_playing: if (audioStopflag == 0) { //状态机状态为正在播放时,且数据还未加载完,继续加载填充数据 if (audio_irq_flag) { audio_irq_flag = 0; Audio_DataLoading(); } } else if (audioStopflag) { //数据加载完毕时, 切换状态至停止,下个Block中断,会停止播放 m_play_status = ePlay_statu_stop; } break; case ePlay_statu_stop: //停止PWM输出,并释放资源,恢复默认IO及各标志至默认状态。 tim_stop(PWM_Timer); if (NULL != p_dma_channel) { dma_stop(p_dma_channel); dma_release(p_dma_channel); p_dma_channel = NULL; } m_play_status = ePlay_statu_init; audioStopflag = 0; Audio_Init(); AudioHal_Init(); block_index = 0; log_debug("\r\nePlay_statu_stop"); break; default: break; } } /** * @brief * */ void Audio_test(void) { PWM_audio_play(); } |