PPPH 发表于 2022-5-12 14:11:10

STM32使用PWM播放WAV音频,但flash不够大,求思路

因为flash不够大,无法把音频数据一下子都存进去,现在想做个数组,一边对应成PWM的占空比,一边来转换,但是没有具体的操作思路,本人小白求各位大佬指点一下

PPPH 发表于 2022-5-12 14:15:53

音频存在SD卡中,STM32F407把SD卡中的数据读取到数组中

孤独飞行 发表于 2022-5-12 17:25:46

用PWM做DA,应该对音质没什么期望吧{:lol:}

mainbp 发表于 2022-5-12 20:22:44

两个定时器,一个控制幅度,一个控制频率。就记得这么多了。

armstrong 发表于 2022-5-12 22:11:13

这个比较容易的,STM32F030C8T6都可以实现。
PWM用DMA来传送占空比到CCR1和CCR2实现立体声,占空比缓冲区用双缓冲;
即DMA传buffer1时,后台即可准备好buffer2;而后播放buffer2时,后台再准备buffer1,如此交替下去。
所谓后台准备buffer,就是从SD卡或者Flash读取声音数据,也即占空比。

armstrong 发表于 2022-5-12 22:16:31

本帖最后由 armstrong 于 2022-5-12 22:20 编辑

下面是我在STM32F030C8T6上的实现代码,供参考。
硬件资源:TIM16CH1,DMA1_Channel3,GPIO_Pin_8,如果要双声道,多配置一路TIM16CH2和对应GPIO即可。
为节省存储和传输资源,我实现的采样率是11025hz,样本精度是8bits。

////////////////////////////////////////////////////////////////////////////////
#define AUDIO_MUTE_LEN(118)   /* 118个样本大约播放10毫秒 */

static uint8_t const audio_mute = {
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128,128,128,
128,128,128,128,128,128
};

static volatile struct {
const uint8_t *buf_addr1;
uint32_t buf_len1;

const uint8_t *buf_addr2;
uint32_t buf_len2;

} audio_dev = { .buf_len1 = 0, .buf_len2 = 0 };

void HWU_AudioOpen(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

/**************************************************************************/
/* Timer16用作音频输出,管脚为PB8(AF2=TIM16_CH1) */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_2);

/* 开启Timer16的系统时钟,并初始化Timer16 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM16, ENABLE);

/* 关闭Timer计数器 */
TIM16->CR1 = TIM_CR1_ARPE|TIM_CR1_URS;
TIM16->CR2 = 0;
/* 关闭Timer中断 */
TIM16->DIER = 0;
TIM16->SR   = 0;
/* 预分频使CK_CNT=12MHZ,同时配置RCR=3且ARR=255,
   就可以得到11718.75HZ脉冲,
   与标准音频采样频率11025HZ近似 */
TIM16->PSC = (4-1);
/* 每个音频脉冲输出4次 */
TIM16->RCR = (4-1);
/* 配置PWM脉宽 */
TIM16->ARR = (uint16_t)(256-1);

/* 配置比较器工作在PWM方式,并配置其电平逻辑 */
TIM16->CCMR1 = ((6<<4)|TIM_CCMR1_OC1PE|TIM_CCMR1_OC1FE|(0<<0));
TIM16->CCER= ((0<<3)|(0<<1)|TIM_CCER_CC1E);
TIM16->BDTR= (TIM_BDTR_MOE|TIM_BDTR_AOE|(0<<12));
/* 配置PWM的有效电平脉宽 */
TIM16->CCR1 = (uint16_t)128;
TIM16->EGR= TIM_EGR_UG; /* 手动更新设定值 */
/* 配置Timer模块的DMA功能 */
TIM16->DCR= TIM_DMABurstLength_1Transfer|TIM_DMABase_CCR1;
TIM16->DIER = TIM_DIER_UDE; /* 使能DMA请求 */
TIM16->CR1 |= TIM_CR1_CEN;
/* 配置DMA模块,用于Timer */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* 注意:要使SYSCFG_CFGR1寄存器起作用,必须开启SYSCFG模块时钟! */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
SYSCFG_DMAChannelRemapConfig(SYSCFG_DMARemap_TIM16, DISABLE);
/* 使用DMA1通道3响应TIM16_UP的传输请求 */
DMA1->IFCR = DMA_IFCR_CGIF3;
DMA1_Channel3->CCR   = 0;
/* 源端口8位宽,目的端口16位宽,NDT是指源端口读取次数 */
DMA1_Channel3->CCR|= ((3<<12)|(1<<8)|DMA_CCR_MINC|DMA_CCR_DIR);
DMA1_Channel3->CCR|= (DMA_CCR_TEIE|DMA_CCR_TCIE);
DMA1_Channel3->CPAR= (uint32_t)&TIM16->DMAR;
DMA1_Channel3->CMAR= (uint32_t)audio_mute;
DMA1_Channel3->CNDTR = (uint16_t)AUDIO_MUTE_LEN;
DMA1_Channel3->CCR|= DMA_CCR_EN;
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
}

int HWU_AudioPlay(uint8_t const stream[], int num)
{
if(audio_dev.buf_len1 == 0){
    audio_dev.buf_addr1 = stream;
    audio_dev.buf_len1 = num;
    return num;
}else if(!audio_dev.buf_len2){
    audio_dev.buf_addr2 = stream;
    audio_dev.buf_len2 = num;
    return num;
}
return 0;
}

void HWU_AudioClose(void)
{
TIM16->CR1 &= ~TIM_CR1_CEN;
NVIC_DisableIRQ(DMA1_Channel2_3_IRQn);
DMA1_Channel3->CCR &= ~DMA_CCR_EN;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM16, DISABLE);
}

/* PWM音频引擎控制中断 */
void TIM16_IRQHandler(void)
{
TIM16->SR = 0;
}

/* PWM音频引擎输出中断 */
void DMA1_Channel2_3_IRQHandler(void)
{
uint32_t isr = DMA1->ISR;

if(isr & DMA_ISR_GIF3){

    DMA1->IFCR = DMA_IFCR_CGIF3;

    if(isr & DMA_ISR_TEIF3){
      /* A transfer error (TE) occurred */
      DMA1_Channel3->CCR &= ~DMA_CCR_EN;
      DMA1_Channel3->CMAR= (uint32_t)audio_mute;
      DMA1_Channel3->CNDTR = (uint16_t)AUDIO_MUTE_LEN;
      DMA1_Channel3->CCR |= DMA_CCR_EN;
    }

    if(isr & DMA_ISR_TCIF3){
      /* A transfer complete (TC) event occurred */
      DMA1_Channel3->CCR &= ~DMA_CCR_EN;
      if(audio_dev.buf_len1){
      DMA1_Channel3->CMAR= (uint32_t)audio_dev.buf_addr1;
      DMA1_Channel3->CNDTR = (uint16_t)audio_dev.buf_len1;
      audio_dev.buf_len1 = 0;
      }else if(audio_dev.buf_len2){
      DMA1_Channel3->CMAR= (uint32_t)audio_dev.buf_addr2;
      DMA1_Channel3->CNDTR = (uint16_t)audio_dev.buf_len2;
      audio_dev.buf_len2 = 0;
      }else{
      DMA1_Channel3->CMAR= (uint32_t)audio_mute;
      DMA1_Channel3->CNDTR = (uint16_t)AUDIO_MUTE_LEN;
      }
      DMA1_Channel3->CCR |= DMA_CCR_EN;
    }
}
}

zbhrose1 发表于 2022-5-12 22:43:36

学习下,谢谢分享

tomzbj 发表于 2022-5-12 23:54:32

elm-Chan的网站上找呗,他用8k Flash, 1k ram的attiny85实现过,从sd卡读取wav播放。

dog 发表于 2022-5-13 14:04:33

MARK, PWM播放音频

qinxg 发表于 2022-5-16 11:05:51

1. 语音可以用ADPCM压缩
2. PWM 1, 2路差分输出, 用一个电机驱动当功放.
3. 差分: 信号>0时,PWM1输出,PWM2为0; <0时则反之.
4. PWM DMA做双缓冲区.STM32 DMA可以设置为一次传递1,2路数据
5. 16Khz时, 人耳几乎听不出DAC, PWM语音的区别

armok. 发表于 2022-7-17 23:26:01

“STM32使用PWM播放WAV音频”标题不合格。(注意:主题发出24小时后就不能修改帖子)已经错过编辑时间,你是VIP++已经帮你修改。

帖子标题必须能充分说明帖子的内容。如你要问AVR的ADC如何才能测量得比较准确,“AVR的ADC如何消除干扰测量得比较准确?”是合格的标题。不合格举例:
    1:小女子冰天雪地裸体跪求解决方法
    2:救命啊...
    3:高手请出招,一个无法解决的AVR问题
    4:一个困扰学习单片机初学者,惊动单片机开发者的难题
    5:AVR的ADC测量   (点评:你到底是问问题,或是有技术心得与大家分享?)

zyw19987 发表于 2022-7-19 15:13:46

两个定时器,一个定时器输出最够高频方波,一个定时器以16k的速度从外挂SPI flash读取WAV数据移位控制前一个定时器占空比。好像是这样做的。STM8Z做过录放音,音质不亚于128b的MP3
页: [1]
查看完整版本: STM32使用PWM播放WAV音频,但flash不够大,求思路