CH582M,PWM模拟DAC实现WAV播放,FATFS文件

Posted kedvellek

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CH582M,PWM模拟DAC实现WAV播放,FATFS文件相关的知识,希望对你有一定的参考价值。

一、 

利用PWM、RC电路、TCB8002D音频功率放大器。通过调制PWM的占空比输出wav音频。

不是通过调整(一开始方向搞错)

  1. 频率(调整音调)
  2. 通过delay_ms延时函数来实现四分之一音符、二分之一音符、全音符。
  3. 占空比调整音量

如何用单片机的PWM演奏一首歌曲 - 知乎

二、

首先配置输出PWM输出频率,源文件为44.1KHz的采样频率,所以PWM配置输出为不小于44KHz(影响播放速度,小了播放慢、大了播放快)。

    SetSysClock(CLK_SOURCE_PLL_60MHz);  //系统时钟
 
    GPIOB_ModeCfg(GPIO_Pin_5, GPIO_ModeOut_PP_5mA);  //使能TCB8002D音频功率放大器
    GPIOB_SetBits(GPIO_Pin_5);

    GPIOB_ModeCfg(GPIO_Pin_0, GPIO_ModeOut_PP_5mA);  // 配置PWM输出
    PWMX_CLKCfg(5);                                 // cycle = 4/Fsys  分频
    PWMX_CycleCfg(PWMX_Cycle_256);                // 周期 = 64*cycle  重复计数值

我们可以通过GoldWave将源音频文件转换为44.1KHz8Bit的wav,再理由WinHex获取C数组形式的数据。

#define maxSize sizeof(wavdat)


const unsigned wavdat[] = 
    0x75, 0x74, 0x73, 0x72, 0x71, 0x70, 0x6F, 0x6F, 0x70, 0x71, 0x72, 0x74, 0x76, 0x78, 0x79, 0x79,
    0x7A, 0x7A, 0x7A, 0x79, 0x79, 0x7A, 0x7A, 0x7B, 0x7B, 0x7B, 0x7B, 0x7C, 0x7D, 0x7F, 0x80, 0x81,
    0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x81, 0x82, 0x83, 0x84, 0x84, 0x84, 0x84, 0x83, 0x82, 0x81,
    0x80, 0x80, 0x81, 0x83, 0x85, 0x87, 0x89, 0x8A, 0x8C, 0x8D, 0x8F, 0x8F, 0x8F, 0x8E, 0x8D, 0x8B,
    0x8A, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x87, 0x85, 0x83, 0x81, 0x80, 0x7E, 0x7D, 0x7D, 0x7C,
    0x7C, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7C, 0x7C, 0x7D, 0x7D, 0x7F, 0x80, 0x80, 0x81, 0x81,
    0x81, 0x81, 0x81, 0x80, 0x80, 0x7F, 0x7F, 0x7E, 0x7E, 0x7D, 0x7D, 0x7C, 0x7C, 0x7C, 0x7D, 0x7E,
    0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x80, 0x81, 0x82, 0x84, 0x86, 0x87, 0x89, 0x8B, 0x8C,
    0x8C, 0x8D, 0x8D, 0x8C, 0x8C, 0x8D, 0x8D, 0x8D, 0x8D, 0x8C, 0x8C, 0x8A, 0x89, 0x87, 0x87, 0x86,
    0x86, 0x85, 0x85, 0x84, 0x83, 0x83, 0x82, 0x82, 0x82, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80,
    0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x7F, 0x7F, 0x7E, 0x7F, 0x7F, 0x7F, 0x80, 0x7F,
    0x7F, 0x7E, 0x7D, 0x7B, 0x7A, 0x79, 0x77, 0x76, 0x75, 0x75, 0x76, 0x77, 0x79, 0x7B, 0x7C, 0x7E,
    0x7E, 0x7F, 0x7E, 0x7F, 0x7F, 0x7F, 0x80, 0x80, 0x80, 0x81, 0x81, 0x82, 0x83, 0x85, 0x86, 0x87,
...........

读取数据信息

    while(1)
       PWMX_ACTOUT(CH_PWM6,(wavdat[wavIndex]), Low_Level, ENABLE);  // 25% 占空比
       if(++wavIndex>=maxSize)wavIndex=0;

    

参考文章: 

使用PWM实现语音播放_嵌入式Linux,的博客-CSDN博客

stm32基于pwm语音播报设计 - 百度文库

STM32F103使用TIM DMA DAC实现播放WAV音乐_liqiang420795936的博客-CSDN博客_stm32驱动喇叭播放音乐c语言 输出音频 单片机,单片机播放WAV格式音频的理解_张溪梦 Simon的博客-CSDN博客TTS(1)单片机播放WAV语音,有原理,有代码_乘简的博客-CSDN博客_wav播放原理基于PWM控制的声音播放的实现_天上人间555的博客-CSDN博客_pwm 语音

三、 读取TF卡内WAV音频文件播放音乐

前面需要注意的是不能只单一个PWM直接扔数据給他,音频文件为44.1KHz所以要固定频率更新数据,利用定时器每隔 (44.1 kHz)产生一次中断来更新数据,而不是直接扔数据主频8Mhz直接扔数据一下会扔很多数据。

1:FATFS基础知识(23.2.1):文件系统FATFS的移植教程_sunshine_SGP的博客-CSDN博客_fatfs移植

2:SD卡的读取

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "tftlcd.h"
#include "key.h"
#include "malloc.h" 
#include "sd.h"
#include "flash.h"
#include "ff.h" 
#include "fatfs_app.h"
#include "diskio.h"
#include "integer.h"
#include "string.h"

	
	
FATFS fsobject;
FIL SDFile ;

uint8_t  readBuf[512];
uint8_t  writeBuf[512];
BYTE work[_MAX_SS];
char *fileName = "test.txt";
uint32_t writeLen;
uint32_t readLen;

int main(void)

	FRESULT  retSD ;

	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组 分2组
	LED_Init();
	USART1_Init(115200);
	TFTLCD_Init();			//LCD初始化
	// 挂载
	retSD = f_mount(&fsobject, "0:", 1);
	switch(retSD)
	
		case FR_NO_FILESYSTEM:  // FM_FAT32
					
						retSD = f_mkfs("0:",0,sizeof(work));
						if(retSD!=FR_OK)
						
							printf("格式化失败\\r\\n");
							while(1);
						
					
					break;
		case FR_OK:
			printf("挂载成功\\r\\n");
			break;
		default:
			while(1);
	
	
	// 写
	retSD = f_open(&SDFile,fileName, FA_CREATE_ALWAYS | FA_WRITE);
	if(retSD!=FR_OK)
	
		printf("打开失败\\r\\n");
		while(1);
	
	printf("打开成功\\r\\n");
	sprintf((char *)writeBuf,"%s","abc123今天天气还可以---FATFStest\\r\\n");
	retSD = f_write(&SDFile,writeBuf, strlen((const char *)writeBuf), &writeLen);
	if(retSD!=FR_OK)
	
		printf("写失败\\r\\n");
		while(1);
	
	printf("写成功\\r\\n");
	f_close(&SDFile);
	
	
	// 读
	retSD = f_open(&SDFile,fileName, FA_OPEN_EXISTING | FA_READ);
	if(retSD!=FR_OK)
	
		while(1);
	
	printf("打开成功\\r\\n");
	
	retSD = f_read(&SDFile,readBuf, 50, &readLen);
	if(retSD!=FR_OK)
	
		while(1);
	
	printf("读到的数据为%s\\r\\n",readBuf);
	LCD_ShowString(10,180,tftlcd_data.width,tftlcd_data.height,16,readBuf); 
	f_close(&SDFile);
	
	// 卸载
	f_mount(NULL, "0:", 1);
	while (1)
	
	

 

 

 

3.1挂载FATFS文件管理系统

通过文件管理系统读取我们需要的wav文件里的数据。d移植自:

【CH579开发板】+分享TF卡的FATFS例子 - - 21ic电子技术开发论坛

/******************SD卡初始化***********************/
    if(MSD_Init())
    
        printf("error!\\n");
    else
    
        MSD_GetCardInfo(&cardinfo);
        printf("OK\\n");

        if(MSD_ReadSingleBlock(0,buf) == 0) //读取MBR扇数据打
        
            printf("TF Card Sector Data:\\n");
            p = buf;
            for(i=0; i!=512/16; i++)
            
                for(j=0; j!=16; j++)
                
                    printf("%02X ",*p++);
                
                printf("\\n");
            
        
/******************FATFFS***********************/
        f_mount(&fs,"0:",1); //挂载SD卡

3.2配置TIM定时器

配置定时器,在定时器中断设置更新数据的标志位,定时器到了就更新PWM占空数据。

/******************定时器初始化***********************/
TMR0_TimerInit(FREQ_SYS / 44100);         // 设置定时时间
TMR0_ITCfg(ENABLE, TMR0_3_IT_CYC_END); // 开启中断
PFIC_EnableIRQ(TMR0_IRQn);

/******************定时中断***********************/
void TMR0_IRQHandler(void) // TMR0 定时中断

    if(TMR0_GetITFlag(TMR0_3_IT_CYC_END))
    
        TMR0_ClearITFlag(TMR0_3_IT_CYC_END); // 清除中断标志
        GPIOB_InverseBits(GPIO_Pin_1);
        capFlag = 1;
    

3.3读取数据播放音乐

一次读写512个字节数或512倍的数据到缓冲区,tf卡读写操作时每次是按块来读写,最小块是512个字节,文件系统读取TF卡数据是一次读一个块到文件系统的缓存区。

注意点:WAV格式是有符号的整型,所以要将源数据进行转换成无符号整型加上0x80后转换为u8类型。

int readdata(void )

    FATFS fs;   //逻辑磁盘工作区
    FIL file;   //文件
    FRESULT res;//状态变量
    UINT br;    /* 文件读/写字节计数 */
    u8 table[1024];
    int32_t i=0 ;
    res=f_open(&file,"0:/1.wav",FA_OPEN_EXISTING|FA_READ);//
    if(res)   //打开文件错误
    
        printf("open file error.\\r\\n");
        return 0;
    
    f_read (&file,table,sizeof(table),&br);          //读取文件到buf
    while(1) //f_eof - 测试一个文件是否到达文件末尾
    
        if(capFlag ==1)
        
            capFlag=0;
            PWMX_ACTOUT(CH_PWM6,(255-((u8)(table[i]+0x80))), High_Level, ENABLE);
            i++;
        
       if(i>=sizeof(table))
       
           f_read (&file,table,sizeof(table),&br);
           i=0;
       
       // if(res||br==0) f_close(&file); /*  文件结束错误 */
    
//    printf("文件大小%d\\n",f_size(&file));//获取文件大小

    f_close(&file);       //关闭文件
    return 0;

参考文章:

STM32CubeMX学习笔记(27)——FatFs文件系统使用(操作SD卡)_Leung_ManWah的博客-CSDN博客_fatfs格式化sd卡FatFs 的用户层API接口应用简单介绍(基于STM32F1)_喜暖知寒的博客-CSDN博客

STM32基于 FatFs R0.14b&SD Card 的MP3音乐播放器(也算是FatFs的简单应用了吧)_喜暖知寒的博客-CSDN博客_stm32播放mp3

STM32PWM DAC基本原理(实验:PWM实现DAC)

虽然STM32F103ZET6具有内部DAC,但是也仅仅只有两条DAC通道,并且STM32还有其他的很多型号是没有DAC的。通常情况下,采用专用的D/A芯片来实现,但是这样就会带来成本的增加。

不过STM32所有的芯片都有PWM输出,并且PWM输出通道很多,资源丰富。因此,我们可以使用PWM+简单的RC滤波来实现DAC的输出从而节省成本。

 

PWM DAC
PWM DAC的构成原理
PWM本质上其实就是是一种周期一定,而高低电平占空比可调的方波。实际电路的典型PWM波形,如下图所示:

 技术分享图片

 

针对PWM的波形进行以下分析:

高电平阶段:计数器当前值从0-CCRx阶段(总时间=CCRx*每两个计数之间的间隔时间);
低电平阶段:计数器当前值从CCRx-ARR-1阶段(总时间=(ARR-1-CCRx)*每两个计数之间的间隔时间)。
如果PWM内容如果不太懂,可以参考链接:【STM32】通用定时器的PWM输出(实例:PWM输出)。

根据PWM的波形,可以用分段函数来进行表示:

 技术分享图片

 

其中:T是STM32中计数脉冲的基本周期,也就是STM32定时器的计数频率的倒数;N是PWM波一个周期的计数脉冲个数,也就是STM32的ARR-1的值;n是PWM波一个周期中高电平的计数脉冲个数,也就是STM32的CCRx的值;VH和VL分别是PWM波的高低电平电压值;k为谐波次数;t为时间。

我们将分段函数①式展开成傅里叶级数,得到公式②:

 技术分享图片

 

从②式可以看出,式中第1个方括弧为直流分量,第2项为1次谐波分量,第3项为大于1次的高次谐波分量。

式②中的直流分量与n成线性关系,并随着n从0到N,直流分量从VL到VL+VH之间变化。而STM32的DAC功能也就是电压输出,这正是电压输出的DAC所需要的。

因此,如果能把式②中除直流分量外的谐波过滤掉,则可以得到从PWM波到电压输出DAC的转换,即:PWM波可以通过一个低通滤波器进行解调。式②中的第2项的幅度和相角与n有关,频率为1/(NT),其实就是PWM的输出频率。该频率是设计低通滤波器的依据。如果能把1次谐波很好过滤掉,则高次谐波就应该基本不存在了。

 

PWM DAC的具体实现
通过上面的了解,我们可以得到PWM DAC的分辨率,计算公式如下:

分辨率=log2(N)

这里假设n的最小变化为1,当N=256的时候,分辨率就是8位。而STM32的定时器都是16位的,可以很容易得到更高的分辨率,分辨率越高,速度就越慢。不过我们在本章要设计的DAC分辨率为8位。

在8位分辨条件下,我们一般要求1次谐波对输出电压的影响不要超过1个位的精度,也就是3.3/256=0.01289V。假设VH为3.3V,VL为0V,那么一次谐波的最大值是2*3.3/π=2.1V,这就要求我们的RC滤波电路提供至少-20lg(2.1/0.01289)=-44dB的衰减。

STM32的定时器最快的计数频率是72Mhz,8为分辨率的时候,PWM频率为72M/256=281.25Khz。如果是1阶RC滤波,则要求截止频率为1.77Khz,如果为2阶RC滤波,则要求截止频率为22.34Khz。

二阶RC滤波截止频率计算公式为:

f=1/2πRC

以上公式要求R55=R56=R,C63=C64=C(R55*C63=R56*C64=RC)。根据这个公式,我们计算出图25.1.2的截止频率为:33.8Khz超过了22.34Khz,这个和我们前面提到的要求有点出入,原因是该电路我们还需要用作PWM DAC音频输出,而音频信号带宽是22.05Khz,为了让音频信号能够通过该低通滤波,同时为了标准化参数选取,所以确定了这样的参数。实测精度在0.5LSB以内。

 

PWM DAC实例
硬件连接
单片机:STM32F103ZET6
硬件资源:指示灯DS0,WK_UP和KEY1按键,ADC,PWM DAC
具体的硬件连接的图如下所示:

 技术分享图片

 

STM32控制程序
//设置输出电压
//vol:0~330,代表0~3.3V
void PWM_DAC_Set(u16 vol)
{
float temp=vol;
temp/=100;
temp=temp*256/3.3;
TIM_SetCompare1(TIM1,temp);
}
int main(void)
{     
u16 adcx;
float temp;
u8 t=0;     
u16 pwmval=0;
u8 key;
delay_init();    //延时函数初始化    
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200);    //串口初始化为115200
KEY_Init();    //KEY初始化
LED_Init();    //LED端口初始化
usmart_dev.init(72);    //初始化USMART    
LCD_Init();    //LCD初始化
Adc_Init();    //ADC初始化
TIM1_PWM_Init(255,0);    //TIM1 PWM初始化, Fpwm=72M/256=281.25Khz.
TIM_SetCompare1(TIM1,100);//初始值为0    


POINT_COLOR=RED;//设置字体为红色 
LCD_ShowString(60,50,200,16,16,"WarShip STM32");    
LCD_ShowString(60,70,200,16,16,"PWM DAC TEST");    
LCD_ShowString(60,90,200,16,16,"[email protected]");
LCD_ShowString(60,110,200,16,16,"2015/1/15");    
LCD_ShowString(60,130,200,16,16,"WK_UP:+ KEY1:-");    
//显示提示信息    
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,150,200,16,16,"PWM VAL:");    
LCD_ShowString(60,170,200,16,16,"DAC VOL:0.000V");    
LCD_ShowString(60,190,200,16,16,"ADC VOL:0.000V");

TIM_SetCompare1(TIM1,pwmval);//初始值    
while(1)
{
t++;
key=KEY_Scan(0);    
if(key==WKUP_PRES)
{    
if(pwmval<250)pwmval+=10;
TIM_SetCompare1(TIM1,pwmval); //输出
}else if(key==KEY1_PRES)    
{
if(pwmval>10)pwmval-=10;
else pwmval=0;
TIM_SetCompare1(TIM1,pwmval); //输出
}     
if(t==10||key==KEY1_PRES||key==WKUP_PRES) //WKUP/KEY1按下了,或者定时时间到了
{    
adcx=TIM_GetCapture1(TIM1);
LCD_ShowxNum(124,150,adcx,4,16,0); //显示DAC寄存器值
temp=(float)adcx*(3.3/256);    //得到DAC电压值
adcx=temp;
LCD_ShowxNum(124,170,temp,1,16,0); //显示电压值整数部分
temp-=adcx;
temp*=1000;
LCD_ShowxNum(140,170,temp,3,16,0x80); //显示电压值的小数部分
adcx=Get_Adc_Average(ADC_Channel_1,20); //得到ADC转换值    
temp=(float)adcx*(3.3/4096);    //得到ADC电压值
adcx=temp;
LCD_ShowxNum(124,190,temp,1,16,0); //显示电压值整数部分
temp-=adcx;
temp*=1000;
LCD_ShowxNum(140,190,temp,3,16,0x80); //显示电压值的小数部分
t=0;
LED0=!LED0;    
}    
delay_ms(10);    

}

 

---------------------
作者:Yngz_Miao
来源:CSDN
原文:https://blog.csdn.net/qq_38410730/article/details/80113841
版权声明:本文为博主原创文章,转载请附上博文链接!














以上是关于CH582M,PWM模拟DAC实现WAV播放,FATFS文件的主要内容,如果未能解决你的问题,请参考以下文章

STM32 M4 Cortex WAV 播放器 SD 卡 DMA DAC

STM32之DAC音频播放

WM8978和VS1053B的区别

stm32h7的ADC和DAC问题笔记

stm32h7的ADC和DAC问题笔记

DAC+DMA+TIM实现音频播放问题记录