OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2492|回复: 0

《STM32F407 探索者开发指南》第五十六章 录音机实验

[复制链接]

1140

主题

1152

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4895
金钱
4895
注册时间
2019-5-8
在线时间
1248 小时
发表于 2023-9-12 17:51:43 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-9-12 17:50 编辑

第五十六章 录音机实验
1)实验平台:正点原子探索者STM32F407开发板

2) 章节摘自【正点原子】STM32F407开发指南 V1.1


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)STM32技术交流QQ群:151941872

155537nfqovl2gg9faaol9.png

155537c2odj87vz1z9vj6l.jpg

上一章,我们实现了一个简单的音乐播放器,本章我们将在上一章的基础上,继续用ES8388实现一个简单的录音机,录制WAV格式的录音。
56.1 I2S录音简介
56.2 硬件设计
56.3 软件设计
56.4 下载验证

56.1 I2S录音简介
本章涉及的知识点基本上在上一章都有介绍。本章要实现WAV录音,还是和上一章一样,要了解:WAV文件格式、ES8388和I2S接口。WAV文件格式,我们在上一章已经做了详细介绍了,这里就不作介绍了。

ALIENTEK探索者STM32F407开发板将板载的一个MIC分别接入到了ES8388的2个差分输入通道(LIP/LIN和RIP/RIN,原理图见:图54.2.1)。代码上,我们采用立体声WAV录音,不过,左右声道的音源都是一样的,录音出来的WAV文件,听起来就是个单声道效果。

关于ES8388的录音设置(即ADC的相关设置),我们在上一站的介绍已经顺带都介绍了,所以本章我们就不再介绍ES8388的寄存器了,想了解读者可以参考第54章的介绍,或者参考本例程源代码和ES8388的pdf数据手册理解。

上一章我们向大家介绍了STM32F407的I2S放音,通过上一章的了解,我们知道:STM32F407的全双工需要用到扩展的I2Sx_ext(x=2/3),和I2Sx组成全双工I2S。在全双工模式下,I2Sx向I2Sx_ext提供CK和WS时钟信号。

本章我们必须向ES8388提供WS(FS),CK(SCK)和MCK(MCLK)等时钟,同时又要录音,所以只能使用全双工模式。工作在主模式的主I2Sx循环发送数据0X0000,给ES8388,以产生CK、WS和MCK等信号,工作在从模式的从I2Sx_ext,则接收来自ES8388的ADC数据(ADCDAT),并保存到SD卡,实现录音。

本章我们主要通过STM32F407的I2S,驱动ES8388实现WAV录音的简要步骤,如下:

1)初始化ES8388
这个过程就是前面所讲的ES8388 MIC录音配置步骤,让ES8388的ADC以及其模拟部分工作起来。

2)初始化I2S2I2S2_ext
本章要用到I2S2的全双工模式,所以,I2S2和I2S2_ext都需要配置,其中I2S2配置为主模式,I2S2_ext设置为从模式。他们的其他配置(协议、时钟电平特性、slot相关参数)基本一样,只是一个是发送一个是接收,且都要使能DMA。同时,还需要设置音频采样率,不过这个只需要设置I2S2的即可,还是通过上一章介绍的查表法设置。

3)设置发送和接收DMA
放音和录音都是采用DMA传输数据的,本章放音其实就是个幌子,不过也得设置DMA(使用DMA1数据流4的通道0),配置同上一章一模一样,不过不需要开启DMA传输完成中断。对于录音,则使用的是DMA1数据流3的通道3实现的DMA数据接收,我们需要配置DMA1的数据流3,本章将DMA1数据流3设置为:双缓冲循环模式,外设和存储器都是16位宽,并开启传输完成中断(方便接收数据)。

4)编写接收通道DMA传输完成中断服务函数
为了方便接收音频数据,我们使用DMA传输完成中断,每当一个缓冲接数据满了,硬件自动切换为下一个缓冲,同时进入中断服务函数,将已满缓冲的数据写入SD卡的wav文件。过程如图56.1.1所示:
image002.png
图56.1.1 DMA双缓冲接收音频数据流框图

5)创建WAV文件,并保存wav
前面4步完成,其实就可以开始读取音频数据了,不过在录音之前,我们需要先在创建一个新的文件,并写入wav头,然后才能开始写入我们读取到的的PCM音频数据。

6)开启DMA传输,接收数据
然后,我们就只需要开启DMA传输,然后及时将I2S2_ext读到的数据写入到SD卡之前新建的wav文件里面,就可以实现录音了。

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

56.2 硬件设计
1. 例程功能
本章实验功能简介:开机后,先初始化各外设,然后检测字库是否存在,如果检测无问题,再检测SD卡根目录是否存在RECORDER文件夹,如果不存在则创建,如果创建失败,则报错。在找到SD卡的RECORDER文件夹后,即进入录音模式(包括配置ES8388和I2S等),此时可以在耳机(或喇叭)听到采集到的音频。KEY0用于开始/暂停录音,KEY2用于保存并停止录音,KEY_UP用于播放最近一次的录音。

当我们按下KEY0的时候,可以在屏幕上看到录音文件的名字、码率以及录音时间等,然后通过KEY2可以保存该文件,同时停止录音(文件名和时间也都将清零),在完成一段录音后,我们可以通过按KEY_UP按键,来试听刚刚的录音。LED0用于提示程序正在运行,LED1用于提示是否处于暂停录音状态。

2. 硬件资源
本实验,大家需要准备1个microSD/SD卡(在里面新建一个MUSIC文件夹,并存放一些歌曲在MUSIC文件夹下)和一个耳机(非必备),分别插入SD卡接口和耳机接口,然后下载本实验就可以实现录音机的效果。实验用到的硬件资源如下:
1)LED灯
LED0 – PF9
LED1 – PF10
2)独立按键
KEY0    - PE4
KEY1    - PE3
KEY2    - PE2
KEY_UP  - PA0 (程序中的宏名:WK_UP)
3)串口1 (PA9/PA10连接在板载USB转串口芯片CH340上面)
4)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
5)SD卡:通过SDIO连接
6)NOR FLASH(SPI FLASH芯片,连接在SPI1上)
7)I2S,驱动ES8388芯片
8)开发板板载的咪头或自备麦克风输入
9)喇叭或耳机
录音机实验与上一章(音乐播放器实验)用到的硬件资源基本一样,我们这里就不重复介绍了,有差异的是这次我们用到板载的咪头用于信号输入,也可以通过3.5mm的音频接口通过LINE_IN接入麦克风输入录音音源。

56.3 程序设计
56.3.1 程序流程图
程序的设计流程如下:
QQ截图20230912174911.png
图56.3.1.1 录音机实验程序流程图

我们通过板载的按键控制录音的开始和停止,检测到录音开始后在录音目录下随机生成一个wav后缀的文件名并写入文件头信息,通过ES8388的录音模式不断采集声音信息并定入文件,录音结束后,我们保存文件并修改对应的文件头信息以便文件能被解码。最后,我们设计了用KEY_UP按键来播放上一次录音的文件,以便查看录音效果。

56.3.2 程序解析
1. recorder驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,RECORDER的驱动主要包括两个文件:recorder.c和recorder.h。

音乐播放器实验中我们已经学过配置ES8388的方法,我们在recoder.c编写函数配置ES8388工作在PCM录音模式,我们编写代码如下:
  1. /**
  2. *@brief        进入PCM 录音模式
  3. *@param        无
  4. *@retval       无
  5. */
  6. voidrecoder_enter_rec_mode(void)
  7. {
  8.     /* 关闭传输完成中断(这里不用中断送数据) (如果在这里不关闭dma就会卡在清空数据过程)*/
  9.    I2S_TX_DMASx->CR &= ~(1 << 4);
  10.    es8388_adda_cfg(0, 1);       /* 开启ADC */
  11.    es8388_input_cfg(0);         /* 开启输入通道(通道1,MIC所在通道) */
  12.    es8388_mic_gain(8);          /* MIC增益设置为最大 */
  13.    es8388_alc_ctrl(3, 4, 4);   /* 开启立体声ALC控制,以提高录音音量 */
  14.    es8388_output_cfg(0, 0);    /* 关闭通道1和2的输出 */
  15.    es8388_spkvol_set(0);        /* 关闭喇叭. */
  16.    es8388_i2s_cfg(0, 3);        /* 飞利浦标准,16位数据长度 */
  17.    
  18.    i2s_init(I2S_STANDARD_PHILIPS, I2S_MODE_MASTER_TX, I2S_CPOL_LOW,
  19.               I2S_DATAFORMAT_16B);   /* 飞利浦标准,主机发送,时钟低电平有效,16位帧长度 */
  20.    i2sext_init(I2S_STANDARD_PHILIPS, I2S_MODE_SLAVE_RX, I2S_CPOL_LOW,
  21.               I2S_DATAFORMAT_16B);   /* 飞利浦标准,从机接收,时钟低电平有效,16位帧长度 */
  22.    i2s_samplerate_set(REC_SAMPLERATE); /* 设置采样率 */
  23.     /* 配置TX DMA */
  24.    i2s_tx_dma_init((uint8_t *)&I2S_PLAY_BUF[0], (uint8_t *)&I2S_PLAY_BUF[1],1);
  25.    I2S_TX_DMASx->CR &= ~(1 << 4);       /* 关闭传输完成中断(这里不用中断送数据) */
  26.     /* 配置RX DMA */
  27.    i2sext_rx_dma_init(p_i2s_recbuf1, p_i2s_recbuf2, REC_I2S_RX_DMA_BUF_SIZE/2);
  28.    i2s_rx_callback = rec_i2s_dma_rx_callback; /*回调函数指wav_i2s_dma_callback*/
  29.    i2s_play_start();   /* 开始I2S数据发送(主机) */
  30.    i2s_rec_start();     /* 开始I2S数据接收(从机) */
  31.    recoder_remindmsg_show(0);
  32. }
复制代码
该函数就是用我们前面介绍的方法,激活ES8388的PCM模式,本章,我们使用的是44.1Khz采样率,16位单声道线性PCM模式,以及发送与接收DMA配置。

由于最后要把录音写入到文件,这里需要准备wav的文件头,为方便,我们定义了一个__WaveHeader结构体来定义文件头的数据字节,这个结构体包含了前面提到的wav文件的数据结构块:
  1. typedef __PACKED_STRUCT
  2. {
  3.     ChunkRIFF riff;               /* riff块 */
  4.     ChunkFMT fmt;                 /* fmt块 */
  5.     //ChunkFACT fact;             /*fact块 线性PCM,没有这个结构体 */
  6.     ChunkDATA data;               /* data块 */
  7. } __WaveHeader;
复制代码
我们定义一个recoder_wav_init()函数方便初始化文件信息,代码如下:
  1. void recoder_wav_init(__WaveHeader *wavhead)
  2. {
  3.     wavhead->riff.ChunkID = 0X46464952;     /* "RIFF"*/
  4.    wavhead->riff.ChunkSize = 0;              /* 还未确定, 最后需要计算 */
  5.     wavhead->riff.Format = 0X45564157;       /*"WAVE" */
  6.     wavhead->fmt.ChunkID = 0X20746D66;       /* "fmt" */
  7.     wavhead->fmt.ChunkSize = 16;             /* 大小为16个字节 */
  8.     wavhead->fmt.AudioFormat = 0x01;     /* 0x01, 表示PCM; 0x00, 表示IMA ADPCM; */
  9.     wavhead->fmt.NumOfChannels = 2;          /* 双声道 */
  10.     wavhead->fmt.SampleRate = REC_SAMPLEREATE;    /*采样速率 */
  11. /* 字节速率=采样率*通道数*(ADC位数/8) */
  12. wavhead->fmt.ByteRate = wavhead->fmt.SampleRate * 4;
  13.     wavhead->fmt.BlockAlign = 4;             /* 块大小=通道数*(ADC位数/8) */
  14.     wavhead->fmt.BitsPerSample = 16;         /* 16位PCM */
  15.     wavhead->data.ChunkID = 0X61746164;     /*"data" */
  16.     wavhead->data.ChunkSize = 0;             /* 数据大小, 还需要计算 */
  17. }
复制代码
录音完成我们还要重新计算录音文件的大小写入文件头,以保证音频文件能正常被解析。我们把这些数据直接按顺序写入文件即可完成录音操作,结合文件操作和按键功能定义,我们用wav_recoder()函数实现录音过程,代码如下:
  1. /**
  2. *@brief        WAV录音
  3. *@param        无
  4. *@retval       无
  5. */
  6. void wav_recorder(void)
  7. {
  8.     uint8_t res, i;
  9.     uint8_t key;
  10.     uint8_t rval = 0;
  11.     uint32_t bw;
  12.    
  13.    __WaveHeader *wavhead = 0;
  14.     DIRrecdir;               /* 目录 */
  15.     FIL*f_rec = 0;          /* 录音文件 */
  16.    
  17.     uint8_t *pdatabuf;       /* 数据缓存指针 */
  18.     char *pname = 0;
  19.     uint8_t timecnt = 0;    /* 计时器 */
  20.     uint32_t recsec = 0;    /* 录音时间 */
  21.     while (f_opendir(&recdir, "0:/RECORDER"))   /* 打开录音文件夹 */
  22.     {
  23.        lcd_show_string(30, 230, 240, 16, 16, "RECORDER文件夹错误!", RED);
  24.        delay_ms(200);
  25.        lcd_fill(30, 230, 240, 246, WHITE);      /* 清除显示 */
  26.        delay_ms(200);
  27.        f_mkdir("0:/RECORDER");     /* 创建该目录 */
  28.     }
  29.     /* 申请内存 */
  30.     for (i = 0; i < REC_I2S_RX_FIFO_SIZE; i++)
  31.     {
  32.        /* I2S录音FIFO内存申请 */
  33.        p_i2s_recfifo_buf = mymalloc(SRAMIN, REC_I2S_RX_DMA_BUF_SIZE);
  34.        if (p_i2s_recfifo_buf == NULL)
  35.        {
  36.            break;  /* 申请失败 */
  37.        }
  38.     }
  39.    
  40.    p_i2s_recbuf1 = mymalloc(SRAMIN,REC_I2S_RX_DMA_BUF_SIZE);/*I2S录音内存1申请*/
  41.     p_i2s_recbuf2 = mymalloc(SRAMIN,REC_I2S_RX_DMA_BUF_SIZE);/*I2S录音内存2申请*/
  42.    f_rec = (FIL *)mymalloc(SRAMIN, sizeof(FIL));        /* 开辟FIL字节的内存区域 */
  43.    
  44.     /* 开辟__WaveHeader字节的内存区域 */
  45.    wavhead = (__WaveHeader *)mymalloc(SRAMIN, sizeof(__WaveHeader));
  46.    pname = mymalloc(SRAMIN, 30);   /* 申请30个字节内存,文件名类似 REC00001.wav"*/
  47.     if (!p_i2s_recbuf2 || !f_rec || !wavhead || !pname)rval = 1;
  48.     if(rval==0)
  49.     {
  50.        recoder_enter_rec_mode();    /* 进入录音模式,此时耳机可以听到咪头采集到的音频 */
  51.        pname[0]=0;                    /* pname没有任何文件名 */
  52.        while (rval == 0)
  53.        {
  54.            key = key_scan(0);
  55.            switch (key)
  56.            {
  57.                 case KEY2_PRES: /*STOP&SAVE */
  58.                     if (g_rec_sta & 0x80) /* 有录音 */
  59.                     {
  60.                         g_rec_sta = 0;      /* 关闭录音 */
  61.                        /* 整个文件的大小-8; */
  62.                         wavhead->riff.ChunkSize = g_wav_size + 36;
  63.                         wavhead->data.ChunkSize = g_wav_size;    /* 数据大小 */
  64.                         f_lseek(f_rec, 0);  /* 偏移到文件头. */
  65.                         f_write(f_rec, (const void *)wavhead, sizeof(__WaveHeader),
  66.                                   &bw);       /* 写入头数据 */
  67.                         f_close(f_rec);
  68.                         g_wav_size = 0;
  69.                     }
  70.                     g_rec_sta = 0;
  71.                     recsec = 0;
  72.                     LED1(1);    /* 关闭DS1 */
  73.                     /* 清除显示,清除之前显示的录音文件名 */
  74.                     lcd_fill(30, 190, lcddev.width, lcddev.height, WHITE);
  75.                     break;
  76.                 case KEY0_PRES: /* REC/PAUSE*/
  77.                     if (g_rec_sta & 0x01)           /* 如果是暂停,继续录音 */
  78.                     {
  79.                         g_rec_sta &= 0xFE;          /* 取消暂停 */
  80.                     }
  81.                     else if (g_rec_sta & 0x80)     /* 已经在录音了,暂停 */
  82.                     {
  83.                         g_rec_sta |= 0x01;          /* 暂停 */
  84.                     }
  85.                     else    /* 还没开始录音 */
  86.                     {
  87.                         recsec = 0;
  88.                         recoder_new_pathname(pname);       /* 得到新的名字 */
  89.                         text_show_string(30, 190,lcddev.width,16,"录制:",16,0,RED);
  90.                         text_show_string(30 + 40, 190, lcddev.width, 16, pname +
  91.                                              11, 16, 0, RED); /* 显示当前录音文件名字 */
  92.                         recoder_wav_init(wavhead);          /* 初始化wav数据 */
  93.                         res = f_open(f_rec, (const TCHAR *)pname, FA_CREATE_ALWAYS
  94.                                                                          | FA_WRITE);
  95.                        if (res)              /* 文件创建失败 */
  96.                         {
  97.                             g_rec_sta = 0;  /* 创建文件失败,不能录音 */
  98.                             rval = 0xFE;    /* 提示是否存在SD卡 */
  99.                         }
  100.                         else
  101.                         {
  102.                             res = f_write(f_rec, (const void *)wavhead,
  103.                                              sizeof(__WaveHeader), &bw); /* 写入头数据 */
  104.                             recoder_msg_show(0, 0);
  105.                             g_rec_sta |= 0x80;    /* 开始录音 */
  106.                         }
  107.                     }
  108.                     if (g_rec_sta & 0x01)
  109.                     {
  110.                         LED1(0);   /* 提示正在暂停 */
  111.                     }
  112.                     else
  113.                     {
  114.                         LED1(1);
  115.                     }
  116.                     break;
  117.                 case WKUP_PRES:   /* 播放最近一段录音 */
  118.                     if (g_rec_sta != 0x80)     /* 没有在录音 */
  119.                     {
  120.                         if (pname[0])           /* 如果按键被按下,且pname不为空 */
  121.                         {
  122.                             text_show_string(30, 190, lcddev.width, 16, "播放:", 16,
  123.                                                  0, RED);
  124.                             text_show_string(30 + 40, 190, lcddev.width, 16,
  125.                               (char*)pname + 11, 16, 0, RED); /* 显示当播放的文件名字 */
  126.                            recoder_enter_play_mode();      /* 进入播放模式 */
  127.                             audio_play_song(pname);         /* 播放pname */
  128.                             /* 清除显示,清除之前显示的录音文件名 */
  129.                             lcd_fill(30, 190, lcddev.width, lcddev.height, WHITE);
  130.                            recoder_enter_rec_mode();       /* 重新进入录音模式 */
  131.                         }
  132.                     }
  133.                     break;
  134.            }
  135.            if (recoder_i2s_fifo_read(&pdatabuf))  /*读取一次数据,读到数据了,写入文件*/
  136.            {
  137.                 /* 写入文件 */
  138.                 res = f_write(f_rec, pdatabuf, REC_I2S_RX_DMA_BUF_SIZE, &bw);
  139.                 if (res)
  140.                 {
  141.                     printf("writeerror:%d\r\n", res);
  142.                 }
  143.                 g_wav_size +=REC_I2S_RX_DMA_BUF_SIZE;  /* WAV数据大小增加 */
  144.            }
  145.            else
  146.            {
  147.                 delay_ms(5);
  148.            }
  149.            
  150.            timecnt++;
  151.            if ((timecnt % 20) == 0)
  152.            {
  153.                 LED0_TOGGLE();    /* LED0闪烁 */
  154.            }
  155.            if (recsec != (g_wav_size / wavhead->fmt.ByteRate))   /* 录音时间显示 */
  156.            {
  157.                 LED1_TOGGLE();     /* LED0闪烁 */
  158.                 recsec = g_wav_size / wavhead->fmt.ByteRate;       /* 录音时间 */
  159.                 recoder_msg_show(recsec, wavhead->fmt.SampleRate *
  160.                                               wavhead->fmt.NumOfChannels *
  161.                                               wavhead->fmt.BitsPerSample); /* 显示码率 */
  162.            }
  163.        }
  164.     }
  165.    
  166.     for (i = 0; i < REC_I2S_RX_FIFO_SIZE; i++)
  167.     {
  168.        myfree(SRAMIN, p_i2s_recfifo_buf);    /* 录音FIFO内存释放 */
  169.     }
  170.    
  171.    myfree(SRAMIN, p_i2s_recbuf1);  /* 释放内存 */
  172.    myfree(SRAMIN, p_i2s_recbuf2);  /* 释放内存 */
  173.    myfree(SRAMIN, f_rec);           /* 释放内存 */
  174.    myfree(SRAMIN, wavhead);         /* 释放内存 */
  175.    myfree(SRAMIN, pname);           /* 释放内存 */
  176. }
复制代码
2. main.c代码
由于我们把大部分功能已经在wav_recoder()中实现了,main函数进行必要的外设初始化,显示相关的数据信息后,调用该接口即可实现我们需要的录音机功能了,最后我们在main.c中实现代码如下:
  1. int main(void)
  2. {
  3.     HAL_Init();                                  /* 初始化HAL库 */
  4.    sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟,168Mhz */
  5.    delay_init(168);                           /* 延时初始化 */
  6.     usart_init(115200);                         /* 串口初始化为115200 */
  7.     usmart_dev.init(84);                       /* 初始化USMART */
  8.     led_init();                                   /* 初始化LED */
  9.     lcd_init();                                   /* 初始化LCD */
  10.     key_init();                                   /* 初始化按键 */
  11.     sram_init();                                 /* SRAM初始化 */
  12.     norflash_init();                             /* 初始化NORFLASH */
  13.     my_mem_init(SRAMIN);                       /* 初始化内部SRAM内存池 */
  14.     my_mem_init(SRAMCCM);                       /* 初始化内部SRAMCCM内存池 */
  15.     my_mem_init(SRAMEX);                       /* 初始化外部SRAM内存池 */
  16.     while (fonts_init())                       /* 检查字库 */
  17.     {
  18.        lcd_show_string(30, 150, 200, 16, 16, "SD Card Error!", RED);
  19.         delay_ms(500);
  20.        lcd_show_string(30, 150, 200, 16, 16, "PleaseCheck! ", RED);
  21.         delay_ms(500);
  22.         LED0_TOGGLE();                         /* LED0闪烁 */
  23.     }
  24.     exfuns_init();                              /* 为fatfs相关变量申请内存 */
  25.     f_mount(fs[0], "0:", 1);                  /* 挂载SD卡 */
  26.     f_mount(fs[1], "1:", 1);                  /* 挂载FLASH */
  27.     while (fonts_init())                       /* 检查字库 */
  28.     {
  29.        lcd_show_string(30, 50, 200, 16, 16, "FontError!", RED);
  30.         delay_ms(200);
  31.         lcd_fill(30, 50, 240, 66, WHITE);   /* 清除显示 */
  32.         delay_ms(200);
  33.     }
  34.    text_show_string(30, 30, 200, 16, "正点原子STM32开发板", 16, 0, RED);
  35.    text_show_string(30, 50, 200, 16, "WAV录音机 实验", 16, 0, RED);
  36.    text_show_string(30, 70, 200, 16, "2021年11月16日", 16, 0, RED);
  37.    es8388_init();                /* ES8388初始化 */
  38.    es8388_adda_cfg(1, 0);       /* 开启DAC关闭ADC */
  39.    es8388_hpvol_set(25);        /* 设置耳机音量 */
  40.    es8388_spkvol_set(30);       /* 设置喇叭音量 */
  41.     while (1)
  42.     {
  43.         wav_recoder();           /* 录音 */
  44.     }
  45. }
复制代码
可以看到main函数与音乐播放器实验十分类似,封装好了APP,main函数会精简很多。

56.4 下载验证
在代码编译成功之后,我们下载代码到正点原子探索者STM32F407开发板上,先初始化各外设,然后检测字库是否存在,如果检测无问题,再检测SD卡根目录是否存在RECORDER文件夹,如果不存在则创建,如果创建失败,则报错。在找到SD卡的RECORDER文件夹后,即进入录音模式(包括配置ES8388和I2S等),此时可以在耳机(或喇叭)听到采集到的音频。KEY0用于开始/暂停录音,KEY2用于保存并停止录音,KEY_UP用于播放最近一次的录音。     
image005.png
图56.4.1 录音机实验界面

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

在录音的时候按下KEY0则执行暂停/继续录音的切换,通过LED0指示录音暂停。通过按下KEY2,可以停止当前录音,并保存录音文件。在完成一次录音文件保存之后,我们可以通过按KEY_UP按键,来实现播放这个录音文件(即播放最近一次的录音文件),实现试听。

我们可以把录音完成的wav文件放到电脑上,可以通过一些播放软件播放并查看详细的音频编码信息,本例程使用的是KMPlayer播放,查看到的信息如图56.4.3所示:
image009.png
图56.4.3 录音文件属性

这和我们程序设计时的效果一样,通过电脑端的播放器可以直接播放我们所录的音频。经实测,效果还是非常不错的。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-2-24 06:13

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表