6621Cx PWM音乐播放范例

wen sir · 367次点击 · 11个月前


PWM音乐播放范例 

验证平台: 6621CxC

附件代码放置 :\...\SVN3420\examples ,编译可运行

示例 音源为PCM压缩后的数据,压缩率为1:4。(不提供压缩工具)


image.png


image.png

image.png



#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();
}


ble_app_central_audio_1CH_PWM播放-202312182853.rar
被收藏 0  ∙  0 赞  
加入收藏
点赞
1 回复  
善言善语 (您需要 登录 后才能回复 没有账号 ?)

请先登录网站