精华内容
下载资源
问答
  • STM32录音机设计 .zip

    2021-07-09 21:52:17
    基于STM32f429 +ADC+DAC+SRAM录音机,可回放 需要麦克风和扬声器
  • 本电路模块是基于STM32和VS1003的电路设计图,不提供软件程序
  • 基于STM32录音机设计(STM32F103+VS1053B)

    千次阅读 多人点赞 2021-06-09 19:06:35
    MCU: STM32F103C8T6 开发软件: Keil5 音频模块: VS1053B 录音文件存储设备: SD卡,采用SPI协议驱动 显示屏: SPI接口的0.96寸OLED 代码风格: 采用寄存器编程,代码简洁、执行效率高、注释到位、移植方便。 项目...

    一、环境介绍

    MCU:  STM32F103C8T6

    开发软件:  Keil5

    音频模块:  VS1053B

    录音文件存储设备:  SD卡,采用SPI协议驱动

    显示屏:  SPI接口的0.96寸OLED

    代码风格:  采用寄存器编程,代码简洁、执行效率高、注释到位、移植方便。

    项目完整源代码下载地址(下载即可编译运行测试):  https://download.csdn.net/download/xiaolong1126626497/19520781

    二、功能介绍

    这是基于STM32F103C8T6设计的录音机功能,支持的功能如下:

    1.  按下按键1启动自动录音,默认为5秒录音一次,录音完毕自动保存在SD指定目录下。文件名称采用当前时间命名;音频文件格式采用WAV格式存储。

    2.  按下按键2启动手动录音,按键按下之后开始录音,再次按下结束录音,录音完毕之后,文件也是一样的保存在SD卡里。

    3. SD卡文件系统采用FAT32格式,STM32移植了FATFS开源文件系统对SD卡进行读写操作。

    4. OLED显示屏用于显示当前录音机的状态:  空闲、录音、回放等状态。

    5. 按下按键3,启动自动回放功能。自动扫描目录,按顺序播放录音文件。

    技术介绍:

    1.  SD卡采用SPI协议驱动,因为对速度没有很高要求,SPI协议已经完全满足;如果要更高的速度,可以采用SDIO协议。

    2.  音频模块采用VS1053B,这个芯片支持IIS和SPI协议。我这里采用的是SPI协议驱动,SPI比较简单,代码也好移植,可以很方便的移植到其他单片机上运行。VS1053功能比较强大,支持录音、解码播放。

    3.  文件系统采用的是FATFS文件系统,这个文件系统功能比较完善,使用免费,支持FAT16、FAT32等格式。底层也比较好适配移植。当前,除了FATFS以外,还有很多其他的嵌入式文件系统可以选择,移植都大同小异。

    4. OLED显示屏是0.96寸的。采用SPI协议驱动,主要是显示一些状态,SPI刷屏比较快,这款OLED也支持IIC接口。

    5. VS1053模块上没有喇叭设备,可以适应耳机或者音箱听回放的录音。

     

    硬件与STM32的接线说明:

    OLED显示屏:
    D0----SCK-----PB14
    D1----MOSI----PB13
    RES—复位(低电平有效)—PB12
    DC---数据和命令控制管脚—PB1
    CS---片选引脚-----PA7

    VS1053:
    #define VS1053_DREQ     PAin(11)      //DREQ  数据请求
    #define VS1053_RESET    PAout(12)   //RST   硬件复位--低电平有效
    #define VS1053_XCS      PAout(13)      //XCS   片选--低电平有效
    #define VS1053_XDCS     PAout(14)      //XDCS  用于数据片选、字节同步
    #define VS1053_SCLK     PAout(15)
    #define VS1053_OUTPUT   PBout(3)
    #define VS1053_INPUT    PBin(4)

    SD卡接口:
    5V----5V
    GND---GND
    SPI1_MOSI---PA7
    SPI1_MISO---PA6
    SPI1_CS---PA4
    SPI1_SCK--PA5
     

    三、使用的相关硬件

    STM32F103C8T6系统板: 

    OLED显示屏: 

    VS1053: 

    SD卡卡槽: 

     

     

    四、操作说明

    开发板有一个复位键和一个K0按键。

    程序下载:

     

    程序支持三种模式:

    因为开发板只有一个K0按键,所以三种模式都是通过一个按键进行切换的。

    一个按键是通过按下的计数方式进行切换的,切换的顺序是自动录音模式、手动录音模式、回放模式。

    (1)自动录音模式:按下一次按键后,进入自动录音模式,自动录音模式下,录音5秒自动退出,退出后自动启动播放状态,就是播放刚才5秒录制的音频,播放过程中按下按键可以退出播放状态。

    (2)手动录音模式:第二次按下K0按键后,进入手动录音模式,手动录音模式下,可以长时间录音,如果要结束录音,按下K0按键即可结束;结束后自动启动播放状态,就是播放刚才录制的音频,播放过程中按下按键可以退出播放状态。

    (3)回放模式:第三次按下K0按键后,进入回放模式,自动扫描wav目录,进行顺序播放音频文件。

        播放过程中可以按下K0按键退出回放模式。
    每次录音后的文件是存放在SD卡根目录下的wav目录下。
    每个状态都会在OLED显示屏上显示
    也会同时通过串口打印到串口调试助手终端。

    五、SD卡上存放的文件

    SD卡上有两个目录:font目录和wav目录。
    font目录下存放16x16字库文件。
    wav目录下存放录音的音频文件。
     

    六、部分源码

    6.1 VS1053.c   这是VS1053的驱动代码

    #include "vs1053b.h"	
    
    /*
    函数功能:移植接口--SPI时序读写一个字节
    函数参数:data:要写入的数据
    返 回 值:读到的数据
    */
    u8 VS1053_SPI_ReadWriteByte(u8 tx_data)
    {			  	 
    	u8 rx_data=0;				 
      u8 i;
      for(i=0;i<8;i++)
    	{
    		VS1053_SCLK=0;  
    		if(tx_data&0x80){VS1053_OUTPUT=1;}
    		else {VS1053_OUTPUT=0;}
    		tx_data<<=1;	
    		VS1053_SCLK=1;
    		rx_data<<=1;
    		if(VS1053_INPUT)rx_data|=0x01;
    	}
    	return rx_data; 
    }
    
    
    /*
    函数功能:初始化VS1053的IO口	 
    */
    void VS1053_Init(void)
    {
    	 RCC->APB2ENR|=1<<0;
    	 AFIO->MAPR&=~(0x7<<24);  //释放PA13/14/15
    	 AFIO->MAPR|=0x4<<24;
    	
    	 RCC->APB2ENR|=1<<2;
    	 RCC->APB2ENR|=1<<3;
    	 
    	 GPIOA->CRH&=0x00000FFF;
    	 GPIOA->CRH|=0x33338000;
    	  
    	 GPIOB->CRL&=0xFFF00FFF;
    	 GPIOB->CRL|=0x00083000;
    	
    	 VS1053_SCLK=1;
    	 VS1053_XCS=1;
         VS1053_RESET=1;
    
    }	
    
    
    /*
    函数功能:软复位VS10XX
    */
    void VS1053_SoftReset(void)
    {	 
    	u8 retry=0;  				   
    	while(VS1053_DREQ==0); 							//等待软件复位结束	   
    	VS1053_SPI_ReadWriteByte(0Xff);			//启动传输
    	retry=0;
    	while(VS1053_ReadReg(SPI_MODE)!=0x0800)	// 软件复位,新模式  
    	{
    		VS1053_WriteCmd(SPI_MODE,0x0804);		// 软件复位,新模式	    
    		DelayMs(2);//等待至少1.35ms 
    		if(retry++>100)break; 	  
    	}	
    	while(VS1053_DREQ==0);//等待软件复位结束	 
    	retry=0;
    	while(VS1053_ReadReg(SPI_CLOCKF)!=0X9800)//设置VS10XX的时钟,3倍频 ,1.5xADD 
    	{
    		VS1053_WriteCmd(SPI_CLOCKF,0X9800);	//设置VS10XX的时钟,3倍频 ,1.5xADD
    		if(retry++>100)break; 	    
    	}	 
    	DelayMs(20);
    } 
    
    
    /*
    函数 功 能:硬复位MP3
    函数返回值:1:复位失败;0:复位成功	
    */
    u8 VS1053_Reset(void)
    {
    	u8 retry=0;
    	VS1053_RESET=0;
    	DelayMs(20);
    	VS1053_XDCS=1;//取消数据传输
    	VS1053_XCS=1; //取消数据传输
    	VS1053_RESET=1;	   
    	while(VS1053_DREQ==0&&retry<200)//等待DREQ为高
    	{
    		retry++;
    		DelayUs(50);
    	}
    	DelayMs(20);	
    	if(retry>=200)return 1;
    	else return 0;	    		 
    }
    
    
    /*
    函数功能:向VS10XX写命令
    函数参数:
    				address:命令地址
    				data   :命令数据
    */
    void VS1053_WriteCmd(u8 address,u16 data)
    {  
    	while(VS1053_DREQ==0);	//等待空闲		   	   
    	VS1053_XDCS=1; 	 
    	VS1053_XCS=0; 	 
    	VS1053_SPI_ReadWriteByte(VS_WRITE_COMMAND);//发送VS10XX的写命令
    	VS1053_SPI_ReadWriteByte(address); 	//地址
    	VS1053_SPI_ReadWriteByte(data>>8); 	//发送高八位
    	VS1053_SPI_ReadWriteByte(data);	 		//第八位
    	VS1053_XCS=1;            
    } 
    
    
    /*
    函数参数:向VS1053写数据
    函数参数:data:要写入的数据
    */
    void VS1053_WriteData(u8 data)
    {
    	VS1053_XDCS=0;   
    	VS1053_SPI_ReadWriteByte(data);
    	VS1053_XDCS=1;      
    }
    
    
    /*
    函数功能:读VS1053的寄存器 
    函数参数:address:寄存器地址
    返回值:读到的值
    */
    u16 VS1053_ReadReg(u8 address)
    { 
    	u16 temp=0;   	
      while(VS1053_DREQ==0);//非等待空闲状态   	
    	VS1053_XDCS=1;       
    	VS1053_XCS=0;        
    	VS1053_SPI_ReadWriteByte(VS_READ_COMMAND);//发送VS10XX的读命令
    	VS1053_SPI_ReadWriteByte(address);       	//地址
    	temp=VS1053_SPI_ReadWriteByte(0xff); 		  //读取高字节
    	temp=temp<<8;
    	temp+=VS1053_SPI_ReadWriteByte(0xff); 		//读取低字节
    	VS1053_XCS=1;      
       return temp; 
    }  
    
    
    /*
    函数功能:读取VS1053的RAM
    函数参数:addr:RAM地址
    返 回 值:读到的值
    */
    u16 VS1053_ReadRAM(u16 addr) 
    { 
    	u16 res;			   	  
     	VS1053_WriteCmd(SPI_WRAMADDR, addr); 
    	res=VS1053_ReadReg(SPI_WRAM);  
     	return res;
    } 
    
    
    /*
    函数功能:写VS1053的RAM
    函数参数:
    				addr:RAM地址
    				val:要写入的值 
    */
    void VS1053_WriteRAM(u16 addr,u16 val) 
    {  		   	  
     	VS1053_WriteCmd(SPI_WRAMADDR,addr);	//写RAM地址 
    	while(VS1053_DREQ==0); 							//等待空闲	   
    	VS1053_WriteCmd(SPI_WRAM,val); 			//写RAM值 
    } 
    
    
    /*
    函数参数:发送一次音频数据,固定为32字节
    返 回 值:0,发送成功
    				  1,本次数据未成功发送   
    */ 
    u8 VS1053_SendMusicData(u8* buf)
    {
    	u8 n;
    	if(VS1053_DREQ!=0)  //送数据给VS10XX
    	{			   	 
    		VS1053_XDCS=0;  
        for(n=0;n<32;n++)
    		{
    			VS1053_SPI_ReadWriteByte(buf[n]);	 			
    		}
    		VS1053_XDCS=1;     				   
    	}else return 1;
    	return 0;//成功发送了
    }
    
    
    /*
    函数参数:发送一次音频数据,固定为32字节
    返 回 值:0,发送成功
    				  1,本次数据未成功发送   
    */ 
    void VS1053_SendMusicByte(u8 data)
    {
    	u8 n;
    	while(VS1053_DREQ==0){}	   	 
    	VS1053_XDCS=0;  
    	VS1053_SPI_ReadWriteByte(data);	 			
    	VS1053_XDCS=1;     				   
    }
    
    
    /*
    函数功能:设定VS1053播放的音量
    函数参数:volx:音量大小(0~254)
    */
    void VS1053_SetVol(u8 volx)
    {
        u16 volt=0; 			      //暂存音量值
        volt=254-volx;			    //取反一下,得到最大值,表示最大的表示 
    	  volt<<=8;
        volt+=254-volx;					//得到音量设置后大小
        VS1053_WriteCmd(SPI_VOL,volt);//设音量 
    }
    
    
    /*--------------------------------------录音功能-----------------------------------------------------*/
    
    
    /*
    函数功能:vs10xx装载patch
    函数参数:
    				patch:patch首地址
    				len  :patch长度
    */
    void VS1053_LoadPatch(u16 *patch,u16 len) 
    {
    	u16 i; 
    	u16 addr, n, val; 	  			   
    	for(i=0;i<len;) 
    	{ 
    		addr = patch[i++]; 
    		n    = patch[i++]; 
    		if(n & 0x8000U) //RLE run, replicate n samples 
    		{ 
    			n  &= 0x7FFF; 
    			val = patch[i++]; 
    			while(n--)VS1053_WriteCmd(addr, val);  
    		}
    		else //copy run, copy n sample 
    		{ 
    			while(n--) 
    			{ 
    				val = patch[i++]; 
    				VS1053_WriteCmd(addr, val); 
    			} 
    		} 
    	} 	
    }
    
    
    /*
    函数参数:初始化WAV头
    */
    void VS1053_RecoderWavInit(__WaveHeader* wavhead) //初始化WAV头			   
    {
    	wavhead->riff.ChunkID=0X46464952;	//"RIFF"
    	wavhead->riff.ChunkSize=0;				//还未确定,最后需要计算
    	wavhead->riff.Format=0X45564157; 	//"WAVE"
    	wavhead->fmt.ChunkID=0X20746D66; 	//"fmt "
    	wavhead->fmt.ChunkSize=16; 				//大小为16个字节
    	wavhead->fmt.AudioFormat=0X01; 		//0X01,表示PCM;0X01,表示IMA ADPCM
     	wavhead->fmt.NumOfChannels=1;			//单声道
     	wavhead->fmt.SampleRate=8000;			//8Khz采样率 采样速率
     	wavhead->fmt.ByteRate=wavhead->fmt.SampleRate*2;//16位,即2个字节
     	wavhead->fmt.BlockAlign=2;				//块大小,2个字节为一个块
     	wavhead->fmt.BitsPerSample=16;		//16位PCM
      wavhead->data.ChunkID=0X61746164;	//"data"
     	wavhead->data.ChunkSize=0;				//数据大小,还需要计算  
    }
    
    //VS1053的WAV录音有bug,这个plugin可以修正这个问题 							    
    const u16 VS1053_WavPlugin[40]=/* Compressed plugin */ 
    { 
    		0x0007, 0x0001, 0x8010, 0x0006, 0x001c, 0x3e12, 0xb817, 0x3e14, /* 0 */ 
    		0xf812, 0x3e01, 0xb811, 0x0007, 0x9717, 0x0020, 0xffd2, 0x0030, /* 8 */ 
    		0x11d1, 0x3111, 0x8024, 0x3704, 0xc024, 0x3b81, 0x8024, 0x3101, /* 10 */ 
    		0x8024, 0x3b81, 0x8024, 0x3f04, 0xc024, 0x2808, 0x4800, 0x36f1, /* 18 */ 
    		0x9811, 0x0007, 0x0001, 0x8028, 0x0006, 0x0002, 0x2a00, 0x040e,  
    }; 
    
    
    /*
    函数功能:激活PCM 录音模式
    函数参数:
    				agc:0,自动增益
            1024相当于1倍
            512相当于0.5倍
            最大值65535=64倍		  
    */
    void VS1053_RecoderInit(u16 agc)
    {
    	//如果是IMA ADPCM,采样率计算公式如下:
     	//采样率=CLKI/256*d;	
    	//假设d=0,并2倍频,外部晶振为12.288M.那么Fc=(2*12288000)/256*6=16Khz
    	//如果是线性PCM,采样率直接就写采样值 
      VS1053_WriteCmd(SPI_BASS,0x0000);    
     	VS1053_WriteCmd(SPI_AICTRL0,8000);	//设置采样率,设置为8Khz
     	VS1053_WriteCmd(SPI_AICTRL1,agc);		//设置增益,0,自动增益.1024相当于1倍,512相当于0.5倍,最大值65535=64倍	
     	VS1053_WriteCmd(SPI_AICTRL2,0);		  //设置增益最大值,0,代表最大值65536=64X
     	VS1053_WriteCmd(SPI_AICTRL3,6);		  //左通道(MIC单声道输入)
    	VS1053_WriteCmd(SPI_CLOCKF,0X2000);	//设置VS10XX的时钟,MULT:2倍频;ADD:不允许;CLK:12.288Mhz
    	VS1053_WriteCmd(SPI_MODE,0x1804);		//MIC,录音激活    
     	DelayMs(5);					//等待至少1.35ms 
     	VS1053_LoadPatch((u16*)VS1053_WavPlugin,40);//VS1053的WAV录音需要patch
    }
    
    

     

    6.2   SD.c  这是SD卡的驱动代码

    #include "sdcard.h"			   
    static u8  SD_Type=0;  //存放SD卡的类型
    
    /*
    函数功能:SD卡底层接口,通过SPI时序向SD卡读写一个字节
    函数参数:data是要写入的数据
    返 回 值:读到的数据
    */
    u8 SDCardReadWriteOneByte(u8 DataTx)
    {		 
        u16 cnt=0;				 
        while((SPI1->SR&1<<1)==0)		 //等待发送区空--等待发送缓冲为空	
        {
          cnt++;
          if(cnt>=65530)return 0; 	  //超时退出  u16=2个字节
        }	
        SPI1->DR=DataTx;	 	  		      //发送一个byte 
        cnt=0;
        while((SPI1->SR&1<<0)==0) 		//等待接收完一个byte   
        {
          cnt++;
          if(cnt>=65530)return 0;	   //超时退出
        }	  						    
        return SPI1->DR;          		//返回收到的数据
    }
    
    
    /*
    函数功能:底层SD卡接口初始化
    SPI1接口---SD卡接线原理
    5V----5V
    GND---GND
    SPI1_MOSI---PA7
    SPI1_MISO---PA6
    SPI1_CS---PA4
    SPI1_SCK--PA5
    */
    void SDCardSpiInit(void)
    {
      /*1. 开启时钟*/
     	RCC->APB2ENR|=1<<2;		    //使能PORTA时钟
      
      /*2. 配置GPIO口模式*/
      GPIOA->CRL&=0x0000FFFF;
      GPIOA->CRL|=0xB8B30000;
      
      /*3. 上拉*/
      GPIOA->ODR|=1<<4;
    	
    		/*SPI1基本配置*/
    	RCC->APB2ENR|=1<<12;    //开启SPI1时钟
    	RCC->APB2RSTR|=1<<12;
    	RCC->APB2RSTR&=~(1<<12);
    	
    	SPI1->CR1=0X0; 		//清空寄存器
    	SPI1->CR1|=0<<15; //选择“双线双向”模式
    	SPI1->CR1|=0<<11; //使用8位数据帧格式进行发送/接收;
    	SPI1->CR1|=0<<10; //全双工(发送和接收);
    	SPI1->CR1|=1<<9;  //启用软件从设备管理
    	SPI1->CR1|=1<<8;  //NSS
    	SPI1->CR1|=0<<7;  //帧格式,先发送高位
    	SPI1->CR1|=0x0<<3;//当总线频率为36MHZ时,SPI速度为18MHZ,高速。
    	SPI1->CR1|=1<<2;  //配置为主设备
    	SPI1->CR1|=1<<1;  //空闲状态时, SCK保持高电平。
    	SPI1->CR1|=1<<0;  //数据采样从第二个时钟边沿开始。
    	SPI1->CR1|=1<<6;  //开启SPI设备。
    }
    
    /*
    函数功能:取消选择,释放SPI总线
    */
    void SDCardCancelCS(void)
    {
    	SDCARD_CS=1;
     	SDCardReadWriteOneByte(0xff);//提供额外的8个时钟
    }
    
    
    /*
    函数 功 能:选择sd卡,并且等待卡准备OK
    函数返回值:0,成功;1,失败;
    */
    u8 SDCardSelectCS(void)
    {
    	SDCARD_CS=0;
    	if(SDCardWaitBusy()==0)return 0;//等待成功
    	SDCardCancelCS();
    	return 1;//等待失败
    }
    
    
    /*
    函数 功 能:等待卡准备好
    函数返回值:0,准备好了;其他,错误代码
    */
    u8 SDCardWaitBusy(void)
    {
    	u32 t=0;
    	do
    	{
    		if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK
    		t++;		  
    	}while(t<0xFFFFFF);//等待 
    	return 1;
    }
    
    
    /*
    函数功能:等待SD卡回应
    函数参数:
    					Response:要得到的回应值
    返 回 值:
    					0,成功得到了该回应值
    					其他,得到回应值失败
    */
    u8 SDCardGetAck(u8 Response)
    {
    	u16 Count=0xFFFF;//等待次数	   						  
    	while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应  	  
    	if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回应失败   
    	else return SDCard_RESPONSE_NO_ERROR;//正确回应
    }
    
    
    /*
    函数功能:从sd卡读取一个数据包的内容
    函数参数:
    				buf:数据缓存区
    				len:要读取的数据长度.
    返回值:
    			0,成功;其他,失败;	
    */
    u8 SDCardRecvData(u8*buf,u16 len)
    {			  	  
    	if(SDCardGetAck(0xFE))return 1;//等待SD卡发回数据起始令牌0xFE
        while(len--)//开始接收数据
        {
            *buf=SDCardReadWriteOneByte(0xFF);
            buf++;
        }
        //下面是2个伪CRC(dummy CRC)
        SDCardReadWriteOneByte(0xFF);
        SDCardReadWriteOneByte(0xFF);									  					    
        return 0;//读取成功
    }
    
    
    /*
    函数功能:向sd卡写入一个数据包的内容 512字节
    函数参数:
    					buf 数据缓存区
    					cmd 指令
    返 回 值:0表示成功;其他值表示失败;
    */
    u8 SDCardSendData(u8*buf,u8 cmd)
    {	
    	u16 t;		  	  
    	if(SDCardWaitBusy())return 1;  //等待准备失效
    	SDCardReadWriteOneByte(cmd);
    	if(cmd!=0XFD)//不是结束指令
    	{
    		for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,减少函数传参时间
    	    SDCardReadWriteOneByte(0xFF); //忽略crc
    	    SDCardReadWriteOneByte(0xFF);
    		  t=SDCardReadWriteOneByte(0xFF); //接收响应
    		if((t&0x1F)!=0x05)return 2;   //响应错误									  					    
    	}						 									  					    
        return 0;//写入成功
    }
    
    
    
    /*
    函数功能:向SD卡发送一个命令
    函数参数:
    				u8 cmd   命令 
    				u32 arg  命令参数
    				u8 crc   crc校验值	
    返回值:SD卡返回的响应
    */												  
    u8 SendSDCardCmd(u8 cmd, u32 arg, u8 crc)
    {
    	u8 r1;	
    	u8 Retry=0; 
    		
    	SDCardCancelCS();               //取消上次片选
    	if(SDCardSelectCS())return 0XFF;//片选失效 
    	//发送数据
    	SDCardReadWriteOneByte(cmd | 0x40);//分别写入命令
    	SDCardReadWriteOneByte(arg >> 24);
    	SDCardReadWriteOneByte(arg >> 16);
    	SDCardReadWriteOneByte(arg >> 8);
    	SDCardReadWriteOneByte(arg);	  
    	SDCardReadWriteOneByte(crc); 
    	if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading
    	Retry=0X1F;
    	do
    	{
    		r1=SDCardReadWriteOneByte(0xFF);
    	}while((r1&0X80) && Retry--);	  //等待响应,或超时退出
       return r1;	//返回状态值
    }	
    
    
    
    /*
    函数功能:获取SD卡的CID信息,包括制造商信息
    函数参数:u8 *cid_data(存放CID的内存,至少16Byte)	  
    返 回 值:
    					0:成功,1:错误				
    */
    u8 GetSDCardCISDCardOutnfo(u8 *cid_data)
    {
        u8 r1;	   
        //发SDCard_CMD10命令,读CID
        r1=SendSDCardCmd(SDCard_CMD10,0,0x01);
        if(r1==0x00)
    	  {
    			r1=SDCardRecvData(cid_data,16);//接收16个字节的数据	 
        }
    	SDCardCancelCS();//取消片选
    	if(r1)return 1;
    	else return 0;
    }	
    
    
    /*
    函数说明:
    					获取SD卡的CSD信息,包括容量和速度信息
    函数参数:
    					u8 *cid_data(存放CID的内存,至少16Byte)	    
    返 回 值:
    					0:成功,1:错误	
    */
    u8 GetSDCardCSSDCardOutnfo(u8 *csd_data)
    {
    	u8 r1;	 
    	r1=SendSDCardCmd(SDCard_CMD9,0,0x01);    //发SDCard_CMD9命令,读CSD
    	if(r1==0)
    	{
    		r1=SDCardRecvData(csd_data, 16);//接收16个字节的数据 
    	}
    	SDCardCancelCS();//取消片选
    	if(r1)return 1;
    	else return 0;
    }  
    
    
    /*
    函数功能:获取SD卡的总扇区数(扇区数)   
    返 回 值:
    				0表示容量检测出错,其他值表示SD卡的容量(扇区数/512字节)
    说   明:
    				每扇区的字节数必为512字节,如果不是512字节,则初始化不能通过.	
    */
    u32 GetSDCardSectorCount(void)
    {
        u8 csd[16];
        u32 Capacity;  
    	  u16 csize;  					    
        if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0;	//取CSD信息,如果期间出错,返回0
        if((csd[0]&0xC0)==0x40)  //SDHC卡,按照下面方式计算
        {	
    			csize = csd[9] + ((u16)csd[8] << 8) + 1;
    			Capacity = (u32)csize << 10;//得到扇区数	 		   
        }
        return Capacity;
    }
    
    
    
    /*
    函数功能: 初始化SD卡
    返 回 值: 非0表示初始化失败!
    */
    u8 SDCardDeviceInit(void)
    {
      u8 r1;      // 存放SD卡的返回值
      u16 retry;  // 用来进行超时计数
      u8 buf[4];  
    	u16 i;
    	SDCardSpiInit();		//初始化底层IO口
    	
     	for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //发送最少74个脉冲
    	retry=20;
    	do
    	{
    		r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//进入IDLE状态 闲置
    	}while((r1!=0X01) && retry--);
     	SD_Type=0;   //默认无卡
    	if(r1==0X01)
    	{
    		if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1)  //SD V2.0
    		{
    			for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);
    			if(buf[2]==0X01&&buf[3]==0XAA)    //卡是否支持2.7~3.6V
    			{
    				retry=0XFFFE;
    				do
    				{
    					SendSDCardCmd(SDCard_CMD55,0,0X01);	    //发送SDCard_CMD55
    					r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//发送SDCard_CMD41
    				}while(r1&&retry--);
    				if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
    				{
    					for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值
    					if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC;    //检查CCS
    					else SD_Type=SDCard_TYPE_V2;   
    				}
    			}
    		}
    	}
    	SDCardCancelCS();       //取消片选
    	if(SD_Type)return 0;  //初始化成功返回0
    	else if(r1)return r1; //返回值错误值	   
    	return 0xaa;          //其他错误
    }
    
    
    /*
    函数功能:读SD卡
    函数参数:
    				buf:数据缓存区
    				sector:扇区
    				cnt:扇区数
    返回值:
    				0,ok;其他,失败.
    说  明:
    				SD卡一个扇区大小512字节
    */
    u8 SDCardReadData(u8*buf,u32 sector,u32 cnt)
    {
    	u8 r1;
    	if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//转换为字节地址
    	if(cnt==1)
    	{
    		r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//读命令
    		if(r1==0)												  //指令发送成功
    		{
    			r1=SDCardRecvData(buf,512);			//接收512个字节	   
    		}
    	}else
    	{
    		r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//连续读命令
    		do
    		{
    			r1=SDCardRecvData(buf,512);//接收512个字节	 
    			buf+=512;  
    		}while(--cnt && r1==0); 	
    		SendSDCardCmd(SDCard_CMD12,0,0X01);	//发送停止命令
    	}   
    	SDCardCancelCS();//取消片选
    	return r1;//
    }
    
    /*
    函数功能:向SD卡写数据
    函数参数:
    				buf:数据缓存区
    				sector:起始扇区
    				cnt:扇区数
    返回值:
    				0,ok;其他,失败.
    说  明:
    				SD卡一个扇区大小512字节
    */
    u8 SDCardWriteData(u8*buf,u32 sector,u32 cnt)
    {
    	u8 r1;
    	if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//转换为字节地址
    	if(cnt==1)
    	{
    		r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//读命令
    		if(r1==0)//指令发送成功
    		{
    			r1=SDCardSendData(buf,0xFE);//写512个字节	   
    		}
    	}
    	else
    	{
    		if(SD_Type!=SDCard_TYPE_MMC)
    		{
    			SendSDCardCmd(SDCard_CMD55,0,0X01);	
    			SendSDCardCmd(SDCard_CMD23,cnt,0X01);//发送指令	
    		}
     		r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//连续读命令
    		if(r1==0)
    		{
    			do
    			{
    				r1=SDCardSendData(buf,0xFC);//接收512个字节	 
    				buf+=512;  
    			}while(--cnt && r1==0);
    			r1=SDCardSendData(0,0xFD);//接收512个字节 
    		}
    	}   
    	SDCardCancelCS();//取消片选
    	return r1;//
    }	
    
    

     

     

    展开全文
  • 文中设计完成了数字录音模块和STM32外围配置的硬件电路;PCB布局布线;编写加密录音笔软件,实现了数字录音笔的数据加密与数据存储功能。
  • STM32录音机程序,亲测可用。内有详细的注释和文档。很好的学习资料。
  • 文中设计完成了数字录音模块和STM32外围配置的硬件电路;PCB布局布线;编写加密录音笔软件,实现了数字录音笔的数据加密与数据存储功能。
  • 这是基于STM32录音机设计完整源代码+演示视频+使用说明文档。 音频芯片采用:VS1053B OLED显示屏: 采用0.96寸SPI接口显示屏 MCU: STM32F103C8T6
  • STM32STM32F4开发板简介

    千次阅读 2020-08-17 11:08:27
    ALIENTEK 探索者 STM32F4 开发板,资源十分丰富,并把 STM32F407的内部资源发挥到了极致,基本所有 STM32F407 的内部资源,都可以在此开发板上验证,同时扩充丰富的接口和功能模块,整个开发板显得十分大气 02. STM...

    00. 目录

    01. STM32F4开发板的资源图

    在这里插入图片描述

    ALIENTEK 探索者 STM32F4 开发板,资源十分丰富,并把 STM32F407的内部资源发挥到了极致,基本所有 STM32F407 的内部资源,都可以在此开发板上验证,同时扩充丰富的接口和功能模块,整个开发板显得十分大气

    02. STM32F4开发板板载资源

    ALIENTEK 探索者 STM32F4 开发板板载资源如下:

    ◆ CPU:STM32F407ZGT6,LQFP144,FLASH:1024K,SRAM:192K;
    ◆ 外扩 SRAM:XM8A51216,1M 字节
    ◆ 外扩 SPI FLASH:W25Q128,16M 字节
    ◆ 1 个电源指示灯(蓝色)
    ◆ 2 个状态指示灯(DS0:红色,DS1:绿色)
    ◆ 1 个红外接收头,并配备一款小巧的红外遥控器
    ◆ 1 个 EEPROM 芯片,24C02,容量 256 字节
    ◆ 1 个六轴(陀螺仪+加速度)传感器芯片,MPU6050
    ◆ 1 个高性能音频编解码芯片,WM8978
    ◆ 1 个 2.4G 无线模块接口,支持 NRF24L01 无线模块
    ◆ 1 路 CAN 接口,采用 TJA1050 芯片
    ◆ 1 路 485 接口,采用 SP3485 芯片
    ◆ 2 路 RS232 串口(一公一母)接口,采用 SP3232 芯片
    ◆ 1 路单总线接口,支持 DS18B20/DHT11 等单总线传感器
    ◆ 1 个 ATK 模块接口,支持 ALIENTEK 蓝牙/GPS 模块
    ◆ 1 个光敏传感器
    ◆ 1 个标准的 2.4/2.8/3.5/4.3/7 寸 LCD 接口,支持电阻/电容触摸屏
    ◆ 1 个摄像头模块接口
    ◆ 1 个 OLED 模块接口
    ◆ 1 个 USB 串口,可用于程序下载和代码调试(USMART 调试)
    ◆ 1 个 USB SLAVE 接口,用于 USB 从机通信
    ◆ 1 个 USB HOST(OTG)接口,用于 USB 主机通信
    ◆ 1 个有源蜂鸣器
    ◆ 1 个 RS232/RS485 选择接口
    ◆ 1 个 RS232/模块选择接口
    ◆ 1 个 CAN/USB 选择接口
    ◆ 1 个串口选择接口
    ◆ 1 个 SD 卡接口(在板子背面)
    ◆ 1 个百兆以太网接口(RJ45)
    ◆ 1 个标准的 JTAG/SWD 调试下载口
    ◆ 1 个录音头(MIC/咪头)
    ◆ 1 路立体声音频输出接口
    ◆ 1 路立体声录音输入接口
    ◆ 1 路扬声器输出接口,可接 1W 左右小喇叭
    ◆ 1 组多功能端口(DAC/ADC/PWM DAC/AUDIO IN/TPAD)
    ◆ 1 组 5V 电源供应/接入口
    ◆ 1 组 3.3V 电源供应/接入口
    ◆ 1 个参考电压设置接口
    ◆ 1 个直流电源输入接口(输入电压范围:DC6~16V)
    ◆ 1 个启动模式选择配置接口

    ◆ 1 个 RTC 后备电池座,并带电池
    ◆ 1 个复位按钮,可用于复位 MCU 和 LCD
    ◆ 4 个功能按钮,其中 KEY_UP(即 WK_UP)兼具唤醒功能
    ◆ 1 个电容触摸按键
    ◆ 1 个电源开关,控制整个板的电源
    ◆ 独创的一键下载功能
    ◆ 除晶振占用的 IO 口外,其余所有 IO 口全部引出

    03. STM32F4部分资源说明

    3.1 JTAG/SWD

    ALIENTEK 探索者 STM32F4 开发板板载的 20 针标准 JTAG 调试口(JTAG),该 JTAG口直接可以和 ULINKJLINK 或者 STLINK 等调试器(仿真器)连接,同时由于 STM32 支持SWD 调试,这个 JTAG 口也可以用 SWD 模式来连接。用标准的 JTAG 调试,需要占用 5 个 IO 口,有些时候,可能造成 IO 口不够用,而用 SWD则只需要 2 个 IO 口,大大节约了 IO 数量,但他们达到的效果是一样的,所以我们 强烈建议仿用真器使用 SWD 模式!

    3.2 STM32F407ZGT6

    开发板的核心芯片(U4),型号为:STM32F407ZGT6。该芯片集成 FPU 和 DSP 指令,并具有 192KB SRAM、1024KB FLASH、12 个 16 位定时器、2 个 32 位定时器、2 个 DMA 控制器(共16个通道)、3个SPI、2个全双工I2S、3个IIC、6个串口、2个USB(支持HOST /SLAVE)、2 个 CAN、3 个 12 位 ADC、2 个 12 位 DAC、1 个 RTC(带日历功能)、1 个 SDIO 接口、1 个FSMC 接口、1 个 10/100M 以太网 MAC 控制器、1 个摄像头接口、1 个硬件随机数生成器、以及 112 个通用 IO 口等。

    04. 附录

    6.1 【STM32】STM32系列教程汇总

    网址:【STM32】STM32系列教程汇总

    05. 声明

    该教程参考了正点原子的《STM32 F4 开发指南》

    展开全文
  • 使用stm32f1做的音乐播放器
  • 这是stm32f103驱动vs1003b芯片的范例,上位机使用c#写的,全部代码开源, 采用的是PL2302usb转串口模块,还包含了写adpcm文件头的软件,是一套完整的录音程序。 所用模块 1.stm32f103rct6 arm芯片开发板 2.pl2303...
  • 硬件——STM32 , 录音

    千次阅读 2018-03-01 14:34:00
    战舰V3的录音程序解析 上一章,我们实现了一个简单的音乐播放器,本章我们将在上一章的基础上,实现一个简单的录音机,实现WAV录音。本章分为如下几个部: 50.1 WAV简介 50.2 硬件设计 50.3 软件设计 50.4 下载...

    战舰V3的录音程序解析

    上一章,我们实现了一个简单的音乐播放器,本章我们将在上一章的基础上,实现一个简单的录音机,实现WAV录音。本章分为如下几个部:

    50.1 WAV简介

    50.2 硬件设计

    50.3 软件设计

    50.4 下载验证

     

    50.1 WAV简介 

     

    WAVWAVE文件,WAV是计算机领域最常用的数字化声音文件格式之一,它是微软专门为Windows系统定义的波形文件格式(Waveform Audio),由于其扩展名为"*.wav"它符合RIFF(Resource Interchange File Format)文件规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持,该格式也支持MSADPCMCCITT A LAW等多种压缩运算法,支持多种音频数字,取样频率和声道,标准格式化的WAV文件和CD格式一样,也是44.1K的取样频率,16位量化数字,因此在声音文件质量和CD相差无几!

     

    ALIENTEK战舰STM32开发板板载的VS1053支持2种格式的WAV录音:PCM格式或者IMA ADPCM格式,其中PCM脉冲编码调制最基本的WAVE文件格式,这种文件直接存储采样的声音数据没有经过任何的压缩。而IAM ADPCM则是使用了压缩算法,压缩比率为4:1

     

    本章,我们主要讨论PCM,因为这个最简单。我们将利用VS1053实现16位,8Khz采样率的单声道WAV录音(PCM格式)。要想实现WAV录音得先了解一下WAV文件的格式,WAVE文件是由若干个Chunk组成的。按照在文件中的出现位置包括:RIFF WAVE Chunk Format Chunk Fact Chunk(可选) Data Chunk。每个Chunk由块标识符、数据大小和数据三部分组成,如图50.1.1所示:

    其中标识符由4ASCII码构成,数据大小则标出紧跟其后的数据的长度(单位为字节),注意这个长度不包含块标识符和数据大小的长度,即不包含最前面的8个字节。所以实际Chunk的大小为数据大小加8

     

    首先,我们来看看RIFF块(RIFF WAVE Chunk),该块以“RIFF作为标示,紧跟wav文件大小(该大小是wav文件的总大小-8),然后数据段为“WAVE”,表示是wav文件。RIFF块的Chunk结构如下:

     

    //RIFF

     

    typedef __packed struct

     

    {

     

        u32 ChunkID;             //chunk id;这里固定为"RIFF",0X46464952

     

        u32 ChunkSize ;            //集合大小;文件总大小-8

     

        u32 Format;                //格式;WAVE,0X45564157

     

    }ChunkRIFF ;

    接着,我们看看Format块(Format Chunk),该块以“fmt ”作为标示(注意有个空格!),一般情况下,该段的大小为16个字节,但是有些软件生成的wav格式,该部分可能有18个字节,含有2个字节的附加信息。Format块的Chunk结构如下:

     

    //fmt

     

    typedef __packed struct

     

    {

     

        u32 ChunkID;             //chunk id;这里固定为"fmt ",0X20746D66

     

        u32 ChunkSize ;            //子集合大小(不包括IDSize);这里为:20.

     

        u16 AudioFormat;        //音频格式;0X10,表示线性PCM;0X11表示IMA ADPCM

     

           u16 NumOfChannels;    //通道数量;1,表示单声道;2,表示双声道;

     

           u32 SampleRate;           //采样率;0X1F40,表示8Khz

     

           u32 ByteRate;               //字节速率;

     

           u16 BlockAlign;            //块对齐(字节);

     

           u16 BitsPerSample;              //单个采样数据大小;4ADPCM,设置为4

     

    }ChunkFMT; 

    接下来,我们再看看Fact块(Fact Chunk),该块为可选块,以“fact”作为标示,不是每个WAV文件都有,在非PCM格式的文件中,一般会在Format结构后面加入一个Fact块,该块Chunk结构如下:

     

    //fact

     

    typedef __packed struct

     

    {

     

        u32 ChunkID;                    //chunk id;这里固定为"fact",0X74636166;

     

        u32 ChunkSize ;                 //子集合大小(不包括IDSize);这里为:4.

     

        u32 DataFactSize;               //数据转换为PCM格式后的大小

     

    }ChunkFACT;

    DataFactSize是这个Chunk中最重要的数据,如果这是某种压缩格式的声音文件,那么从这里就可以知道他解压缩后的大小。对于解压时的计算会有很大的好处!不过本章我们使用的是PCM格式,所以不存在这个块。

    最后,我们来看看数据块(Data Chunk),该块是真正保存wav数据的地方,以“data'作为该Chunk的标示。然后是数据的大小。紧接着就是wav数据。根据Format Chunk中的声道数以及采样bit数,wav数据的bit位置可以分成如表50.1.1所示的几种形式:

    本章,我们采用的是16位,单声道,所以每个取样为2个字节,低字节在前,高字节在后。数据块的Chunk结构如下:

    //data

    typedef __packed struct

    {

        u32 ChunkID;             //chunk id;这里固定为"data",0X61746164

        u32 ChunkSize ;            //子集合大小(不包括IDSize);文件大小-60.

    }ChunkDATA;

    通过以上学习,我们对WAVE文件有了个大概了解。接下来,我们看看如何使用VS1053实现WAVPCM格式)录音。

    激活PCM录音

    VS1053激活PCM录音需要设置的寄存器和相关位如表50.1.2所示:

    通过设置SCI_MODE寄存器的21214位,来激活PCM录音,SCI_MODE的各位描述见表49.1.4(也可以参考VS1053的数据手册)SCI_AICTRL0寄存器用于设置采样率,我们本章用的是8K的采样率,所以设置这个值为8000即可。SCI_AICTRL1寄存器用于设置AGC1024相当于数字增加1,这里建议大家设置AGC44*1024)左右比较合适。SCI_AICTRL2用于设置自动AGC的时候的最大值,当设置为0的时候表示最大64(65536),这个大家按自己的需要设置即可。最后,SCI_AICTRL3,我们本章用到的是咪头线性PCM单声道录音,所以设置该寄存器值为6

    通过这几个寄存器的设置,我们就激活VS1053PCM录音了。不过,VS1053PCM录音有一个小BUG,必须通过加载patch才能解决,如果不加载patch,那么VS1053是不输出PCM数据的,VLSI提供了我们这个patch,只需要通过软件加载即可。

     

    读取PCM数据

     

    在激活了PCM录音之后,SCI_HDAT0SCI_HDAT1有了新的功能。VS1053PCM采样缓冲区由102416位数据组成,如果SCI_HDAT1大于0,则说明可以从SCI_HDAT0读取至少SCI_HDAT116位数据,如果数据没有被及时读取,那么将溢出,并返回空的状态。

     

    注意,如果SCI_HDAT1896,最好等待缓冲区溢出,以免数据混叠。所以,对我们来说,只需要判断SCI_HDAT1的值非零,然后从SCI_HDAT0读取对应长度的数据,即完成一次数据读取,以此循环,即可实现PCM数据的持续采集。

     

    最后,我们看看本章实现WAV录音需要经过哪些步骤:

     

    1)设置VS1053 PCM采样参数

     

    这一步,我们要设置PCM的格式(线性PCM)、采样率(8K)、位数(16位)、通道数(单声道)等重要参数,同时还要选择采样通道(咪头),还包括AGC设置等。可以说这里的设置直接决定了我们wav文件的性质。

     

    2)激活VS1053PCM模式,加载patch

     

    通过激活VS1053PCM格式,让其开始PCM数据采集,同时,由于VS1053BUG,我们需要加载patch,以实现正常的PCM数据接收。

     

    3)创建WAV文件,并保存wav

     

    在前两部设置成功之后,我们即可正常的从SCI_HDAT0读取我们需要的PCM数据了,不过在这之前,我们需要先在创建一个新的文件,并写入wav头,然后才能开始写入我们的PCM数据。

     

    4)读取PCM数据

     

    经过前面几步的处理,这一步就比较简单了,只需要不停的从SCI_HDAT0读取数据,然后存入wav文件即可,不过这里我们还需要做文件大小统计,在最后的时候写入wav头里面。

     

    5)计算整个文件大小,重新保存wav头并关闭文件

     

    在结束录音的时候,我们必须知道本次录音的大小(数据大小和整个文件大小),然后更新wav头,重新写入文件,最后因为FATFS,在文件创建之后,必须调用f_close,文件才会真正体现在文件系统里面,否则是不会写入的!所以最后还需要调用f_close,以保存文件。

     

    50.2 硬件设计

     

    本章实验功能简介:开机的时候先检测字库,然后初始化VS1053,进行RAM测试和正弦测试,之后,检测SD卡根目录是否存在RECORDER文件夹,如果不存在则创建,如果创建失败,则报错。在找到SD卡的RECORDER文件夹后,即设置VS1053进入录音模式,此时可以在耳机听到VS1053采集的音频。KEY0用于开始/暂停录音,KEY2用于保存并停止录音,WK_UP用于AGC增加、KEY1用于AGC减小,TPAD用于播放最近一次的录音。当我们按下KEY0的时候,可以在屏幕上看到录音文件的名字,以及录音时间,然后通过KEY2可以保存该文件,同时停止录音(文件名和时间也都将清零),在完成一个录音后,我们可以通过按TPAD按键,来试听刚刚的录音。DS0用于提示程序正在运行,DS1用于指示当前是否处于录音暂停状态。

     

    本实验用到的资源如下:

     

    1)  指示灯DS0DS1

     

    2)  五个按键(WK_UP/KEY0/KEY1/KEY2/TPAD

     

    3)  串口

     

    4)  TFTLCD模块

     

    5)  SD

     

    6)  SPI FLASH

     

    7)  VS1053

     

    8)  74HC4052

     

    9)  TDA1308

     

    本章用到的硬件资源同上一章基本一样,就多了一个TPAD按键,用于播放最近一次录音。

     

    本实验,大家需要准备1SD卡和一个耳机,分别插入SD卡接口和耳机接口,然后下载本实验就可以实现一个简单的录音机了。

     

    50.3 软件设计

     

    打开上一章的工程,首先在APP文件夹下面新建recorder.crecorder.h两个文件,然后将recorder.c加入到工程的APP组下。

     

    因为recorder.c代码比较多,我们这里仅介绍其中的三个函数,首先是设置VS1053进入PCM模式的函数:recoder_enter_rec_mode,该函数代码如下:

     

    //进入PCM 录音模式

     

    //agc:0,自动增益.1024相当于1,512相当于0.5,最大值65535=64            

     

    void recoder_enter_rec_mode(u16 agc)

     

    {

     

           //如果是IMA ADPCM,采样率计算公式如下:

     

          //采样率=CLKI/256*d; 

     

           //假设d=0,2倍频,外部晶振为12.288M.那么Fc=(2*12288000)/256*6=16Khz

     

           //如果是线性PCM,采样率直接就写采样值

     

         VS_WR_Cmd(SPI_BASS,0x0000);   

     

          VS_WR_Cmd(SPI_AICTRL0,8000);   //设置采样率,设置为8Khz

     

          VS_WR_Cmd(SPI_AICTRL1,agc);            

     

    //设置增益,0,自动增益.1024相当于1,512相当于0.5,最大值65535=64 

     

          VS_WR_Cmd(SPI_AICTRL2,0);         //设置增益最大值,0,代表最大值65536=64X

     

          VS_WR_Cmd(SPI_AICTRL3,6);         //左通道(MIC单声道输入)

     

           VS_WR_Cmd(SPI_CLOCKF,0X2000);      

     

    //设置VS10XX的时钟,MULT:2倍频;ADD:不允许;CLK:12.288Mhz

     

           VS_WR_Cmd(SPI_MODE,0x1804);    //MIC,录音激活   

     

          delay_ms(5);                                      //等待至少1.35ms

     

          VS_Load_Patch((u16*)wav_plugin,40);//VS1053WAV录音需要patch

     

    }

    该函数就是用我们前面介绍的方法,激活VS1053PCM模式,本章,我们使用的是8Khz采样率,16位单声道线性PCM模式,AGC通过函数参数设置。最后加载patch(用于修复VS1053录音BUG)。

    第二个函数是初始化wav头的函数:recoder_wav_init,该函数代码如下:

    //初始化WAV.

    void recoder_wav_init(__WaveHeader* wavhead) //初始化WAV                  

    {

           wavhead->riff.ChunkID=0X46464952;        //"RIFF"

           wavhead->riff.ChunkSize=0;                      //还未确定,最后需要计算

           wavhead->riff.Format=0X45564157;          //"WAVE"

           wavhead->fmt.ChunkID=0X20746D66;      //"fmt "

           wavhead->fmt.ChunkSize=16;                   //大小为16个字节

           wavhead->fmt.AudioFormat=0X01;           //0X01,表示PCM;0X01,表示IMA ADPCM

          wavhead->fmt.NumOfChannels=1;             //单声道

          wavhead->fmt.SampleRate=8000;               //8Khz采样率 采样速率

          wavhead->fmt.ByteRate=wavhead->fmt.SampleRate*2;//16,2个字节

          wavhead->fmt.BlockAlign=2;                     //块大小,2个字节为一个块

          wavhead->fmt.BitsPerSample=16;                     //16PCM

         wavhead->data.ChunkID=0X61746164;       //"data"

          wavhead->data.ChunkSize=0;                     //数据大小,还需要计算 

    }

    该函数初始化wav头的绝大部分数据,这里我们设置了该wav文件为8Khz采样率,16位线性PCM格式,另外由于录音还未真正开始,所以文件大小和数据大小都还是未知的,要等录音结束才能知道。该函数__WaveHeader结构体就是由前面介绍的三个Chunk组成,结构为:

    //wav

    typedef __packed struct

    {

           ChunkRIFF riff;     //riff

           ChunkFMT fmt;  //fmt

           //ChunkFACT fact; //fact线性PCM,没有这个结构体 

           ChunkDATA data;   //data        

    }__WaveHeader;

    最后,我们介绍recoder_play函数,是录音机实现的主循环函数,该函数代码如下:

    //录音机

    //所有录音文件,均保存在SDRECORDER文件夹内.

    u8 recoder_play(void)

    {

           u8 res, key, rval=0;

           __WaveHeader *wavhead=0;

           u32 sectorsize=0;u16 w; u16 idx=0;    

           FIL* f_rec=0;                             //文件               

          DIR recdir;                               //目录

           u8 *recbuf;                                 //数据内存    

           u8 rec_sta=0;                              //录音状态

                                                            //[7]:0,没有录音;1,有录音;

                                                            //[6:1]:保留

                                                            //[0]:0,正在录音;1,暂停录音;

          u8 *pname=0;

           u8 timecnt=0;                             //计时器  

           u32 recsec=0;                              //录音时间

          u8 recagc=4;                               //默认增益为4

          while(f_opendir(&recdir,"0:/RECORDER"))//打开录音文件夹

          {    

                  Show_Str(60,230,240,16,"RECORDER文件夹错误!",16,0); delay_ms(200);          

                  LCD_Fill(60,230,240,246,WHITE); delay_ms(200);          //清除显示    

                  f_mkdir("0:/RECORDER");//创建该目录  

           }

          f_rec=(FIL *)mymalloc(SRAMIN,sizeof(FIL));   //开辟FIL字节的内存区域

           if(f_rec==NULL)rval=1;       //申请失败

          wavhead=(__WaveHeader*)mymalloc(SRAMIN,sizeof(__WaveHeader));

    //开辟__WaveHeader字节的内存区域

           if(wavhead==NULL)rval=1;

           recbuf=mymalloc(SRAMIN,512);      

           if(recbuf==NULL)rval=1;                     

           pname=mymalloc(SRAMIN,30); 

    //申请30个字节内存,存放路径+名字,类似"0:RECORDER/REC00001.wav"

           if(pname==NULL)rval=1;

          if(rval==0)                                                      //内存申请OK

           {     

                 recoder_enter_rec_mode(1024*recagc);                           

                while(VS_RD_Reg(SPI_HDAT1)>>8);        //等到buf 较为空闲再开始 

                 recoder_show_time(recsec);                        //显示时间

                  recoder_show_agc(recagc);                         //显示agc

                  pname[0]=0;                                             //pname没有任何文件名            

               while(rval==0)

                  {

                         key=KEY_Scan(0);

                         switch(key)

                         {           

                                case KEY_LEFT:   //STOP&SAVE

                                       if(rec_sta&0X80)//有录音

                                       {

                                              wavhead->riff.ChunkSize=sectorsize*512+36;     //文件大小-8;

                                            wavhead->data.ChunkSize=sectorsize*512;         //数据大小

                                              f_lseek(f_rec,0);                                               //偏移到文件头.

                                             f_write(f_rec,(const void*)wavhead,sizeof(__WaveHeader),

    &bw);//写入头数据

                                              f_close(f_rec); sectorsize=0;

                                       }

                                       rec_sta=0; recsec=0; LED1=1; //关闭DS1         

                                       LCD_Fill(60,230,240,246,WHITE);    

    //清除显示,清除之前显示的录音文件名        

                                       recoder_show_time(recsec);          //显示时间

                                       break;    

                                case KEY_RIGHT: //REC/PAUSE

                                       if(rec_sta&0X01)//原来是暂停,继续录音

                                       {

                                              rec_sta&=0XFE;//取消暂停

                                       }else if(rec_sta&0X80)//已经在录音了,暂停

                                       {

                                              rec_sta|=0X01;       //暂停

                                       }else                            //还没开始录音

                                       {

                                             rec_sta|=0X80;       //开始录音           

                                              recoder_new_pathname(pname);                 //得到新的名字

                                              Show_Str(60,230,240,16,pname+11,16,0);  //显示录音文件名字

                                             recoder_wav_init(wavhead);                       //初始化wav数据

                                             res=f_open(f_rec,(const TCHAR*)pname,FA_CREATE_ALWAYS

    |FA_WRITE);

                                              if(res)//文件创建失败

                                              {

                                                     rec_sta=0;       //创建文件失败,不能录音

                                                     rval=0XFE;    //提示是否存在SD

                                              }else res=f_write(f_rec,(const void*)wavhead,

    sizeof(__WaveHeader),&bw);//写入头数据

                                      }

                                       if(rec_sta&0X01)LED1=0;    //提示正在暂停

                                       else LED1=1;

                                       break;

                                case KEY_UP:       //AGC+  

                                case KEY_DOWN: //AGC-

                                       if(key==KEY_UP)recagc++;

                                       else if(recagc)recagc--;

                                       if(recagc>15)recagc=15;       //范围限定为0~15,自动AGC.其他AGC倍数

                                       recoder_show_agc(recagc);

                                       VS_WR_Cmd(SPI_AICTRL1,1024*recagc);      

    //设置增益,0,自动增益.1024相当于1,512相当于0.5

                                       break;

                         }

                         if(rec_sta==0X80)//已经在录音了

                         {

                               w=VS_RD_Reg(SPI_HDAT1);    

                                if((w>=256)&&(w<896))

                                {

                                      idx=0;                                

                                      while(idx<512)     //一次读取512字节

                                       {    

                                             w=VS_RD_Reg(SPI_HDAT0);                                   

                                             recbuf[idx++]=w&0XFF; recbuf[idx++]=w>>8;                     

                                       }                   

                                      res=f_write(f_rec,recbuf,512,&bw);//写入文件

                                       if(res) break;//写入出错.      

                                       sectorsize++;//扇区数增加1,约为32ms     

                                }                  

                         }else//没有开始录音,则检测TPAD按键

                         {                                                       

                                if(TPAD_Scan(0)&&pname[0])//如果触摸按键被按下,pname不为空

                                {                         

                                       Show_Str(60,230,240,16,"播放:",16,0);               

                                       Show_Str(60+40,230,240,16,pname+11,16,0);//显示播放的文件名字  

                                       rec_play_wav(pname);                        播放pname

                                       LCD_Fill(60,230,240,246,WHITE);     /清除之前显示的录音文件名 

                                       recoder_enter_rec_mode(1024*recagc);       //重新进入录音模式    

                                     while(VS_RD_Reg(SPI_HDAT1)>>8); //等到buf 较为空闲再开始 

                                      recoder_show_time(recsec);                 //显示时间

                                       recoder_show_agc(recagc);                  //显示agc

                               }

                                delay_ms(5); timecnt++;

                                if((timecnt%20)==0)LED0=!LED0;//DS0闪烁

                         }

                        if(recsec!=(sectorsize*4/125))//录音时间显示

                         {       

                                LED0=!LED0;//DS0闪烁

                                recsec=sectorsize*4/125;

                                recoder_show_time(recsec);//显示时间

                         }

                  }                                   

           }                                               

           myfree(SRAMIN,wavhead); myfree(SRAMIN,recbuf);              

          myfree(SRAMIN,f_rec);       myfree(SRAMIN,pname);

           return rval;

    }

    该函数实现了我们在硬件设计时介绍的功能,我们就不详细介绍了。recorder.c的其他代码和recorder.h的代码我们这里就不再贴出了,请大家参考光盘本实验的源码。保存recorder.c,最后,我们在test.c里面修改main函数如下:

    int main(void)

    {           

         Stm32_Clock_Init(9);    //系统时钟设置

           delay_init(72);                     //延时初始化

           uart_init(72,9600);       //串口1初始化      

           LCD_Init();                  //初始化液晶

           LED_Init();           //LED初始化

           KEY_Init();                  //按键初始化

           TPAD_Init(72);             //初始化触摸按键   

           Audiosel_Init();            //初始化音源选择

           usmart_dev.init(72);      //usmart初始化    

          mem_init(SRAMIN);     //初始化内部内存池    

          VS_Init();       

          exfuns_init();                //fatfs相关变量申请内存 

          f_mount(0,fs[0]);         //挂载SD

          f_mount(1,fs[1]);         //挂载FLASH.

           POINT_COLOR=RED;     

          while(font_init())         //检查字库

           {        

                  LCD_ShowString(60,50,200,16,16,"Font Error!"); delay_ms(200);                         

                  LCD_Fill(60,50,240,66,WHITE);//清除显示          

           }

          Show_Str(60,50,200,16,"战舰 STM32开发板",16,0);                                       

           Show_Str(60,70,200,16,"WAV录音机实验",16,0);                                     

           Show_Str(60,90,200,16,"广州星翼电子",16,0);                                   

           Show_Str(60,110,200,16,"2012920",16,0);

           Show_Str(60,130,200,16,"KEY0:REC/PAUSE",16,0);

           Show_Str(60,150,200,16,"KEY2:STOP&SAVE",16,0);

           Show_Str(60,170,200,16,"KEY_UP:AGC+ KEY1:AGC-",16,0);

           Show_Str(60,190,200,16,"TPADlay The File",16,0);

           while(1)

           {

                  Audiosel_Set(0);   //MP3通道

                  Show_Str(60,210,200,16,"存储器测试...",16,0);

                  VS_Ram_Test();        

                  Show_Str(60,210,200,16,"正弦波测试...",16,0);              

                 VS_Sine_Test();       

                  Show_Str(60,210,200,16,"<<WAV录音机>>",16,0);

                  recoder_play();

           }                                                                                                     

    }

        该函数代码同上一章的main函数代码几乎一样,只是我们这里增加了TPAD初始化,然后修改了一些显示内容,其他两者就都差不多了,我们就不再细说了。

    至此,本实验的软件设计部分结束。

     

    50.4 下载验证

     

    在代码编译成功之后,我们下载代码到ALIENTEK战舰STM32开发板上,程序先检测字库,然后对VS1053进行RAM测试和正弦测试,之后检测SD卡的RECORDER文件夹,一切顺利通过之后,激活VS1053PCM录音模式,得到,如图50.4.1所示:

     


    50.4.1 录音机界面

     

    此时,我们按下KEY0就开始录音了,此时看到屏幕显示录音文件的名字以及录音时长,如图50.4.2所示:

     


    50.4.2 录音进行中

     

           在录音的时候按下KEY0则执行暂停/继续录音的切换,通过DS1指示录音暂停,按WK_UPKEY1可以调节AGCAGC越大,越灵敏,不过不建议设置太大,因为这可能导致失真。通过按下KEY2,可以停止当前录音,并保存录音文件。在完成一次录音文件保存之后,我们可以通过按TPAD按键,来实现播放这个录音文件(即播放最近一次的录音文件),实现试听。

     

           我们将开发板的录音文件放到电脑上面,可以通过属性查看录音文件的属性,如图50.4.3所示:

     


    50.4.3 录音文件属性

     

           这和我们预期的效果一样,通过电脑端的播放器(winamp/千千静听等)可以直接播放我们所录的音频。经实测,效果还是非常不错的。

     

     

     

     

     

     

     

     

     

     

     

     

     

    转载于:https://www.cnblogs.com/chulin/p/7908498.html

    展开全文
  • 使用STM32作为控制器,并且驱动VS1053实现录音机的设计,同时相关的状态通过LCD显示
  • 关于录音程序的编写: 我的思路是改写原子的程序,原子的程序需要借助VS1053 (这是一个语音芯片)来实现录音, 麦克风连接在VS1053 (这是一个语音芯片)芯片上,麦克风采集的声音电压会...,用stm32从那个语音芯片的...

    详细的wav头文件解析,有例子:http://www.cnblogs.com/chulin/p/8918957.html

     

    关于录音程序的编写:

    我的思路是改写原子的程序,原子的程序需要借助VS1053 (这是一个语音芯片)来实现录音,

    麦克风连接在VS1053 (这是一个语音芯片)芯片上,麦克风采集的声音电压会经过语音芯片处理存入语音芯片特定的寄存器中

    ,用stm32从那个语音芯片的寄存器里读音频数据,并通过FATFS文件系统制作WAV文件头,把数据存入文件头下就可以了.即:

    生成最终的WAV文件.  一个文件其实就是由一个文件头和数据组成, 机器通过解读文件头,来按照文件头指定的方式读取数据,

     

     

    我现在的工作是去掉这颗语音芯片,,用ADC来替代语音芯片的功能,,麦克风接在ADC的引脚上,,把声音电压采集出来,存到数组

    或链表中,,,完成WAV文件的制作

     

    步骤:

    1,制作出一个音符的WAV文件,不通过ADC,直接用数组建出来, 调整音量大小

    2,通过ADC进行声音采样,涉及到  "存"  和  "采"  两个过程,单片机每次只能做一件事,如何协调这两个的关系..

     

     

    下面是用UltraEdit软件,打开WAV文件之后的截图:

     

        

     

     

    分析一下上面两张图,,,找不同

    代码部分WAV文件头如下::

    //wav头
    typedef __packed struct   //关于结构体我用的还是很不熟练,,,,需要提升这部分,,,,这个是结构体的定义,定义在了.h文件中,,,,
                                 //上面一大堆也是结构体,,,  其实这个结构体的元素也都是结构体,,,是结构体套结构体的...    
    {
        ChunkRIFF riff;    //riff块
        ChunkFMT fmt;      //fmt块
        //ChunkFACT fact;    //fact块 线性PCM,没有这个结构体,此次试验我们也不打算考虑这部分..   
        ChunkDATA data;    //data块        
    }__WaveHeader;

     

    把上面WAV头的每个元素找出来

    每个元素都是chunk结构:即

     

     //RIFF块
    typedef __packed struct
    {
        u32 ChunkID;                     //chunk id;这里固定为"RIFF",即0X46464952   (这个就是4bytes,,即4个ASCII码,,,bytes,,,,1byte=8bit  也就是一个字节是8位)  查看图片上的前四个:52  49  46  46
        u32 ChunkSize ;               //集合大小;文件总大小-8         这部分也是占固定的4bytes  0X24  DA  04 00 =317988约310K       0X24   84  03  00=230436约225K
        u32 Format;                     //格式;WAVE,即0X45 56 41 57
    }ChunkRIFF ;

     

     

    //fmt块
    typedef __packed struct
    {
        u32 ChunkID;               //chunk id;这里固定为"fmt ",即0X20746D66     66 6D  74  20
        u32 ChunkSize ;               //子集合大小(不包括ID和Size);这里为: 接下来4个字节是:     10 00 00 00 在这里表示 01,

                                               //也就是16个字节  :01 00 02 00 80 BB 00 00 00 EE 02 00 04 00 10 00

                                              //红色标注的数字,分别分配到下面:


        u16 AudioFormat;          //音频格式;0X01,表示线性PCM;0X11表示IMA ADPCM     01 00 pcm格式
        u16 NumOfChannels;        //通道数量;1,表示单声道;2,表示双声道;   02 00  双声道
        u32 SampleRate;            //采样率;0X1F40,表示8Khz  80 BB 00 00=4800                           这个是采样率,能不能根据采样率,来计算定时器需要如何设定 如果我们设定的采样率是8000Hz即8KHz,,,那么也就是每秒8000次,,,,也就是0.000125s采集一次...125us一次中断采集....剩下就是配置定时器中断了
        u32 ByteRate;            //字节速率;   00 EE 02 00=750
        u16 BlockAlign;            //块对齐(字节);  04 00
        u16 BitsPerSample;        //单个采样数据大小;4位ADPCM,设置为4   10 00=16
    //    u16 ByteExtraData;        //附加的数据字节;2个; 线性PCM,没有这个参数
    //    u16 ExtraData;            //附加的数据,单个采样数据块大小;0X1F9:505字节  线性PCM,没有这个参数
    }ChunkFMT;   

     

     

    最后一张图:网上得来:http://blog.csdn.net/gwhcsdn/article/details/70964342

     

     根据上图的实例分析:

     

     

    正点原子文档的主要部分在:recorder.c中

     

     

     

    参考的<<STM32开发指南V1.0库函数版本>>

     

    ALIENTEK 战舰 STM32 开发板板载的 VS1053 (这是一个语音芯片)支持 2 种格式的 WAV 录音:

    PCM 格式或者 IMA ADPCM 格式, 其中 PCM(脉冲编码调制) 是最基本的 WAVE 文件格式,这种文件直
    接存储采样的声音数据没有经过任何的压缩。而 IAM ADPCM 则是使用了压缩算法,压缩比率
    4:1 .

     

    本章,我们主要讨论 PCM,因为这个最简单。我们将利用 VS1053 实现 16 位, 8Khz
    样率的单声道 WAV 录音(PCM 格式)要想实现 WAV 录音得先了解一下 WAV 文件的格式,
    WAVE 文件是由若干个 Chunk 组成的。按照在文件中的出现位置包括: RIFF WAVE Chunk
    Format ChunkFact Chunk(可选)Data Chunk。每个 Chunk 由块标识符、数据大小和数
    据三部分组成,如图 50.1.1 所示:

     

    上图是每个chunk块都要遵守的结构.

    其中块标识符由 4 ASCII 码构成,数据大小则标出紧跟其后的数据的长度(单位为字节),
    注意这个长度不包含块标识符和数据大小的长度,即不包含最前面的 8 个字节。所以实际 Chunk
    的大小为数据大小加 8

     

     

     

     

     

     

     

    通过以上学习,我们对 WAVE 文件有了个大概了解。接下来,我们看看如何使用 VS1053

    实现 WAVPCM 格式)录音。

     

     

     

     

     

     

    转载于:https://www.cnblogs.com/chulin/p/8022036.html

    展开全文
  • 目录01、STM32F4开发板的资源图02、STM32F4开发板板载资源03、STM32F4部分资源说明3.1、JTAG/SWD3.2、STM32F407ZGT604、声明 01、STM32F4开发板的资源图 F4开发板图示如下: ALIENTEK 探索者 STM32F4 开发板,资源...
  •  系统使用基于ARM内核的STM32处理器执行加密算法和系统的协作控制。采用TEA加密算法对接收到的音频数据进行实时加密,加密后的语音数据存储到外部SD卡中,并可将语音文件通过USB接口传输到计算机进行解密处理。文中...
  • mp3播放,wav录音存入SD卡,FATFS R0.08,hc595驱动4位数码管,定时扫描按键,4x4矩阵键盘,
  • 基于STM32和Speex编解码的录音设计与实现.pdf
  • STM32F103_WM8978_录音及播放参考例程,可以将录音直接存储至SD卡中,也可以将SD卡的音频播放处理啊,压缩包中有模块WM8978的详细资料,可以参考
  • 目录 说明 CubeMX配置 Pinout Clock Configuration ...CubeMX版本是4.21.0(STM32Cube V1.0),这个关系不大。 CubeMX配置 Pinout 这里没有配置用于连接ADC的IIS和IIC接口,只用于demo,USB声卡最
  • 1、资源太大,上传不进CSDN,故此为百度网盘下载链接。...SW4STM32 ARM项目的优化设置为 “无”,以避免在录音机模块上出现问题 未提供MDK-ARM 项目 ST 的emWin演示了 STM32F412ZG发现 SW4STM32 没有提供项目
  • 前言: 最近在做毕设的过程中,需要用到录音播放方面的知识。所以,在借鉴了正点原子例程之后,成功修改出了16k录音+播放的代码。...keil5,stm32F103ZET6战舰开发板、蓝牙模块,ST-LINK烧录器 (开发板上的U...
  • STM32F1和STM32F4 区别

    万次阅读 2016-05-12 09:01:45
    STM32F4相对于STM32F1的改进不只一点点,为了便于初学者了解,我们比对相关资料将改进点进行了汇总。 STM32F1和STM32F4 区别 (安富莱整理)  F1采用Crotex M3内核,F4采用Crotex M4内核。  F1最高主频 72M
  • STM32F4的资料大部分都在这里: http://www.stmcu.org/download/index.php?act=ziliao&id=150 根据个人的理解对这些资料作了一些规律,后期可能会增加一些个人的思维导图以增强理解。 stm32库函数开发指南 stm32F...
  • 正点原子:STM32F103(战舰)、STM32F407(探索者)、STM32F103(MINI)原理图和PCB
  • STM32F1 ADC采集录音存SD卡

    千次阅读 2020-03-30 17:56:36
    目录前言TP79录音功能解析STM32如何得到8K采样率例子WAV音频文件解析PCM格式WAV格式c语言中wav数据结构的构成wav文件格式实例分析WAVE_HEADERWAVE_FMTWAVE_DATADATA 前言 更多技术整理,欢迎关注本人博客...
  • AN3997_使用STM32F4DISCOVERY进行音频播放和录音
  • 这是帮一个同学做毕设做的,基本要求如下(有些指标看看就好,实际当然不需要,哈哈): (1)放大器1的增益为46dB,放大器2的增益为40dB,增益均可调;(2)带通滤波器:通带为300Hz~3.4kHz ;...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 653
精华内容 261
关键字:

stm32录音