OpenEdv-开源电子网

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

《STM32H7R7开发指南 V1.1 》第六十章 录音机实验

[复制链接]

1349

主题

1365

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5760
金钱
5760
注册时间
2019-5-8
在线时间
1568 小时
发表于 9 小时前 | 显示全部楼层 |阅读模式
第六十章 录音机实验

1)实验平台:正点原子STM32H7R7开发板

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

3)购买链接: https://detail.tmall.com/item.htm?id=820823382459

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

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

6)正点原子STM32开发板技术交流群:756580169


2.jpg

3.png

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


60.1 SAI录音简介
本章涉及的知识点基本上在上一章都有介绍。本章要实现WAV录音,还是和上一章一样,要了解:WAV文件格式、ES8388和SAI接口。WAV文件格式,我们在上一章已经做了详细介绍了,这里就不作介绍了。
STM32H7R7开发板将板载的一个MIC分别接入到了ES8388的2个差分输入通道(LIP/LIN和RIP/RIN,原理图见:图59.2.1)。代码上,我们采用立体声WAV录音,不过,左右声道的音源都是一样的,录音出来的WAV文件,听起来就是个单声道效果。
关于ES8388的录音设置(即ADC的相关设置),我们在上一章的介绍已经顺带都介绍了,所以本章我们就不再介绍ES8388的寄存器了,想了解读者可以参考第59章的介绍,或者参考本例程源代码和ES8388的pdf数据手册理解。
上一章我们向大家介绍了STM32H7R7的SAI放音,通过上一章的了解,我们知道:STM32H7R7 SAI的全双工通信,需要用到SAI的两个子模块(SAI_A和SAI_B),一个工作在主模式,产生FS、SCK和MCLK,一个工作在从模式,通过SD引脚接收数据。
本章我们必须向ES8388提供WS(FS),CK(SCK)和MCK(MCLK)等时钟,同时又要录音,所以只能使用全双工模式。工作在主模式的SAI子模块循环发送数据0X0000,给ES8388,以产生CK、WS和MCK等信号,工作在从模式的SAI子模块,则接收来自ES8388的ADC数据(ADCDAT),并保存到SD卡,实现录音。
本章我们将同时使用SAI的两个子模块,以实现录音功能,SAI的相关寄存器,我们在上一章已经介绍的差不多了,这里就不再进行寄存器介绍,大家可以参考《STM32H7Rx参考手册_V6(英文版).pdf》第56.6小节。
最后,我们看看要通过STM32H7R7的SAI,驱动ES8388实现WAV录音的简要步骤,如下:
1)初始化ES8388
这个过程就是前面所讲的ES8388 MIC录音配置步骤,让ES8388的ADC以及其模拟部分工作起来。
2)初始化SAI_A和SAI_B
本章要用到SAI的全双工模式,所以,SAI_A和SAI_B都需要配置,其中SAI_A配置为主模式,SAI_B设置为从模式,且与SAI_A同步。他们的其他配置(协议、时钟电平特性、slot相关参数)基本一样,只是一个是发送一个是接收,且都要使能DMA。同时,还需要设置音频采样率,不过这个只需要设置SAI_A的即可,还是通过上一章介绍的查表法设置。
3)设置发送和接收DMA
放音和录音都是采用GPDMA传输数据的,本章放音其实就是个幌子,不过也得设置GPDMA(使用GPDMA1的通道0),配置同上一章一模一样,不过不需要开启GPDMA传输完成中断。对于录音,则使用的是GPDMA1的通道1实现的GPDMA数据接收,我们需要配置GPDMA1,本章将使用双缓冲循环模式,外设和存储器都是16位宽,并开启传输完成中断(方便接收数据)。
4)编写接收通道DMA传输完成中断服务函数
为了方便接收音频数据,我们使用DMA传输完成中断,每当一个缓冲接数据满了,硬件自动切换为下一个缓冲,同时进入中断服务函数,将已满缓冲的数据写入SD卡的wav文件。过程如图60.1.1所示:


第六十章 录音机实验1523.png
图60.1.1 DMA双缓冲接收音频数据流框图

5)创建WAV文件,并保存wav头
前面4步完成,其实就可以开始读取音频数据了,不过在录音之前,我们需要先在创建一个新的文件,并写入wav头,然后才能开始写入我们读取到的的PCM音频数据。
6)开启DMA传输,接收数据
然后,我们就只需要开启DMA传输,然后及时将SAI_B读到的数据写入到SD卡之前新建的wav文件里面,就可以实现录音了。
7)计算整个文件大小,重新保存wav头并关闭文件
在结束录音的时候,我们必须知道本次录音的大小(数据大小和整个文件大小),然后更新wav头,重新写入文件,最后因为FATFS,在文件创建之后,必须调用f_close,文件才会真正体现在文件系统里面,否则是不会写入的!所以最后还需要调用f_close,以保存文件。


60.2 硬件设计

1. 例程功能
本章实验功能简介:开机后,先初始化各外设,然后检测字库是否存在,如果检测无问题,再检测SD卡根目录是否存在RECORDER文件夹,如果不存在则创建,如果创建失败,则报错。在找到SD卡的RECORDER文件夹后,即进入录音模式(包括配置ES8388和SAI等),此时可以在耳机(或喇叭)听到采集到的音频。KEY0用于开始/暂停录音,KEY1用于保存并停止录音,KEY_UP用于播放最近一次的录音。
当我们按下KEY0的时候,可以在屏幕上看到录音文件的名字、码率以及录音时间等,然后通过KEY1可以保存该文件,同时停止录音(文件名和时间也都将清零),在完成一段录音后,我们可以通过按KEY_UP按键,来试听刚刚的录音。LED0用于提示程序正在运行,LED1用于提示是否处于暂停录音状态。

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


60.3 程序设计

60.3.1 程序解析
1. recorder驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,RECORDER的驱动主要包括两个文件:recorder.c和recorder.h。
音乐播放器实验中我们已经学过配置ES8388的方法,我们在recoder.c编写函数配置ES8388工作在PCM录音模式,我们编写代码如下:

  1. /**
  2. * @brief       进入PCM 录音模式
  3. * [url=home.php?mod=space&uid=271674]@param[/url]       无
  4. * @retval      无
  5. */
  6. void recoder_enter_rec_mode(void)
  7. {
  8. SAI1_TX_DMACx->CCR &= ~DMA_IT_TC;
  9. /* 关闭传输完成中断(这里不用中断送数据) (如果在这里不关闭dma就会卡在清空数据过程)*/
  10.     sai1_tx_callback = NULL;

  11.     es8388_adda_cfg(0, 1);          /* 开启ADC */
  12.     es8388_input_cfg(0);            /* 开启输入通道(通道1,MIC所在通道) */
  13.     es8388_mic_gain(8);             /* MIC增益设置为最大 */
  14.     es8388_alc_ctrl(3, 4, 4);       /* 开启立体声ALC控制,以提高录音音量 */
  15.     es8388_output_cfg(0, 0);        /* 关闭通道1和2的输出 */
  16.     es8388_spkvol_set(0);           /* 关闭喇叭. */
  17.     es8388_sai_cfg(0, 3);           /* 飞利浦标准,16位数据长度 */
  18.    
  19. sai1_saib_init(SAI_MODEMASTER_TX, SAI_CLOCKSTROBING_RISINGEDGE,
  20. SAI_DATASIZE_16);   /* SAI1 Block B,主发送,16位数据 */
  21. sai1_saia_init(SAI_MODESLAVE_RX, SAI_CLOCKSTROBING_RISINGEDGE,
  22. SAI_DATASIZE_16);   /* SAI1 Block A从模式接收,16位 */

  23.     sai1_samplerate_set(REC_SAMPLERATE);    /* 设置采样率 */

  24. sai1_tx_dma_init((uint8_t *)&SAI_PLAY_BUF[0], (uint8_t *)&SAI_PLAY_BUF[1],
  25. 1, 1);   /* 配置TX DMA,16位 */
  26.     SAI1_TX_DMACx->CCR |= DMA_IT_TC;
  27. sai1_rx_dma_init(p_sai_recbuf1, p_sai_recbuf2, REC_SAI_RX_DMA_BUF_SIZE / 2,
  28. 1);     /* 配置RX DMA */

  29. sai1_rx_callback = recoder_sai_dma_rx_callback;     
  30. /* 初始化回调函数指sai_rx_callback */

  31.     sai1_play_start();           /* 开始SAI数据发送(主机) */
  32.     sai1_rec_start();            /* 开始SAI数据接收(从机) */

  33.     recoder_remindmsg_show(0);
  34. }
复制代码
该函数就是用我们前面介绍的方法,激活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_SAMPLERATE;/* 采样速率 */
  11.         wavhead->fmt.ByteRate=wavhead->fmt.SampleRate*4;
  12.         /* 字节速率=采样率*通道数*(ADC位数/8) */
  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.     DIR recdir;                                 /* 目录 */
  15.     FIL *f_rec = 0;                             /* 录音文件 */
  16.    
  17.     uint8_t *pdatabuf;                          /* 数据缓存指针 */
  18.     uint8_t *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_SAI_RX_FIFO_SIZE; i++)
  31.     {
  32.         p_sai_recfifo_buf[i] = mymalloc(SRAMIN, REC_SAI_RX_DMA_BUF_SIZE);
  33. /* SAI录音FIFO内存申请 */

  34.         if (p_sai_recfifo_buf[i] == NULL)
  35.         {
  36.             break;  /* 申请失败 */
  37.         }
  38.     }

  39. p_sai_recbuf1 = mymalloc(SRAMIN, REC_SAI_RX_DMA_BUF_SIZE);         
  40. /* SAI录音内存1申请 */
  41. p_sai_recbuf2 = mymalloc(SRAMIN, REC_SAI_RX_DMA_BUF_SIZE);         
  42. /* SAI录音内存2申请 */
  43. f_rec = (FIL *)mymalloc(SRAMIN, sizeof(FIL));                       
  44. /* 开辟FIL字节的内存区域 */
  45. wavhead = (__WaveHeader *)mymalloc(SRAMIN, sizeof(__WaveHeader));   
  46. /* 开辟__WaveHeader字节的内存区域 */
  47. pname = mymalloc(SRAMIN, 30);   
  48. /* 申请30个字节内存,文件名类似"0:RECORDER/REC00001.wav" */

  49.     if (!p_sai_recbuf2 || !f_rec || !wavhead || !pname)
  50.     {
  51.         rval = 1;
  52.     }

  53.     if (rval == 0)
  54.     {
  55.         recoder_enter_rec_mode();/* 进入录音模式,此时耳机可以听到咪头采集到的音频 */
  56.         pname[0] = 0;            /* pname没有任何文件名 */

  57.         while (rval == 0)
  58.         {
  59.             key = key_scan(0);

  60.             switch (key)
  61.             {
  62.                 case KEY1_PRES:                  /* STOP&SAVE */
  63.                     if (g_rec_sta & 0x80)        /* 有录音 */
  64.                     {
  65.                         g_rec_sta = 0;                           /* 关闭录音 */
  66.                         wavhead->riff.ChunkSize = g_wav_size + 36;
  67. /* 整个文件的大小-8; */
  68.                         wavhead->data.ChunkSize = g_wav_size;    /* 数据大小 */
  69.                         f_lseek(f_rec, 0);                        
  70. /* 偏移到文件头. */
  71.                         f_write(f_rec, (const void *)wavhead,
  72. sizeof(__WaveHeader), &bw); /* 写入头数据 */
  73.                         f_close(f_rec);
  74.                         g_wav_size = 0;
  75.                     }

  76.                     g_rec_sta = 0;
  77.                     recsec = 0;
  78.                     LED1(1);                      /* 关闭DS1 */
  79.                     lcd_fill(30, 190, lcddev.width, lcddev.height, WHITE);
  80. /* 清除显示,清除之前显示的录音文件名 */
  81.                     break;

  82.                 case KEY0_PRES:                   /* REC/PAUSE */
  83.                     if (g_rec_sta & 0x01)         /* 如果是暂停,继续录音 */
  84.                     {
  85.                         g_rec_sta &= 0xFE;        /* 取消暂停 */
  86.                     }
  87.                     else if (g_rec_sta & 0x80)    /* 已经在录音了,暂停 */
  88.                     {
  89.                         g_rec_sta |= 0x01;        /* 暂停 */
  90.                     }
  91.                     else    /* 还没开始录音 */
  92.                     {
  93.                         recsec = 0;
  94.                         recoder_new_pathname(pname);    /* 得到新的名字 */
  95.                         text_show_string(30, 190, lcddev.width, 16, "录制:",
  96. 16, 0, RED);
  97.                         text_show_string(30 + 40, 190, lcddev.width, 16,
  98. (char *)pname + 11, 16, 0, RED); /* 显示当前录音文件名字 */
  99.                         recoder_wav_init(wavhead);      /* 初始化wav数据 */
  100.                         res = f_open(f_rec, (const TCHAR *)pname,
  101. FA_CREATE_ALWAYS | FA_WRITE);

  102.                         if (res)                        /* 文件创建失败 */
  103.                         {
  104.                             g_rec_sta = 0;              /* 创建文件失败,不能录音 */
  105.                             rval = 0xFE;                /* 提示是否存在SD卡 */
  106.                         }
  107.                         else
  108.                         {
  109.                             res = f_write(f_rec, (const void *)wavhead,
  110.                             sizeof(__WaveHeader), &bw); /* 写入头数据 */
  111.                             recoder_msg_show(0, 0);
  112.                             g_rec_sta |= 0x80;          /* 开始录音 */
  113.                         }
  114.                     }

  115.                     if (g_rec_sta & 0x01)
  116.                     {
  117.                         LED1(0);            /* 提示正在暂停 */
  118.                     }
  119.                     else
  120.                     {
  121.                         LED1(1);
  122.                     }
  123.                     break;

  124.                 case WKUP_PRES:             /* 播放最近一段录音 */
  125.                     if (g_rec_sta != 0x80)  /* 没有在录音 */
  126.                     {
  127.                         if (pname[0])       /* 如果按键被按下,且pname不为空 */
  128.                         {
  129.                             text_show_string(30, 190, lcddev.width, 16, "播放:",
  130. 16, 0, RED);
  131.                             text_show_string(30 + 40, 190, lcddev.width, 16,
  132.           (char *)pname + 11, 16, 0, RED);
  133. /* 显示当播放的文件名字 */
  134.                             recoder_enter_play_mode();  /* 进入播放模式 */
  135.                             audio_play_song(pname);     /* 播放pname */
  136.                             lcd_fill(30, 190, lcddev.width, lcddev.height,
  137.                            WHITE); /* 清除显示,清除之前显示的录音文件名 */
  138.                             recoder_enter_rec_mode();   /* 重新进入录音模式 */
  139.                         }
  140.                     }
  141.                     break;
  142.             }

  143.             if (recoder_sai_fifo_read(&pdatabuf))   
  144. /* 读取一次数据, 读到数据了, 写入文件 */
  145.             {
  146.                 res = f_write(f_rec, pdatabuf, REC_SAI_RX_DMA_BUF_SIZE, &bw);
  147.                 /* 写入文件 */

  148.                 if (res)
  149.                 {
  150.                     printf("write error:%d\r\n", res);
  151.                 }

  152.                 g_wav_size += REC_SAI_RX_DMA_BUF_SIZE;  /* WAV数据大小增加 */
  153.             }
  154.             else
  155.             {
  156.                 delay_ms(5);
  157.             }
  158.             
  159.             timecnt++;

  160.             if ((timecnt % 20) == 0)
  161.             {
  162.                 LED0_TOGGLE();                               /* LED0闪烁 */
  163.             }

  164.             if (recsec != (g_wav_size / wavhead->fmt.ByteRate))   
  165. /* 录音时间显示 */
  166.             {
  167.                 LED1_TOGGLE();  /* LED1闪烁 */
  168.                 recsec = g_wav_size / wavhead->fmt.ByteRate;  /* 录音时间 */
  169.                 recoder_msg_show(recsec, wavhead->fmt.SampleRate *
  170. wavhead->fmt.NumOfChannels * wavhead->fmt.BitsPerSample);
  171. /* 显示码率 */
  172.             }
  173.         }
  174.     }
  175.    
  176.     for (i = 0; i < REC_SAI_RX_FIFO_SIZE; i++)
  177.     {
  178.         myfree(SRAMIN, p_sai_recfifo_buf[i]);   /* 录音FIFO内存释放 */
  179.     }
  180.    
  181.     myfree(SRAMIN, p_sai_recbuf1);  /* 释放内存 */
  182.     myfree(SRAMIN, p_sai_recbuf2);  /* 释放内存 */
  183.     myfree(SRAMIN, f_rec);          /* 释放内存 */
  184.     myfree(SRAMIN, wavhead);        /* 释放内存 */
  185.     myfree(SRAMIN, pname);          /* 释放内存 */
  186. }
复制代码

2. main.c代码
由于我们把大部分功能已经在wav_recoder()中实现了,main函数进行必要的外设初始化,显示相关的数据信息后,调用该接口即可实现我们需要的录音机功能了,最后我们在main.c中实现代码如下:

  1. int main(void)
  2. {
  3.     sys_mpu_config();                   /* 配置MPU */
  4.     sys_cache_enable();                 /* 使能Cache */
  5.     HAL_Init();                         /* 初始化HAL库 */
  6.     sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
  7.     delay_init(600);                    /* 初始化延时 */
  8.     usart_init(115200);                 /* 初始化串口 */
  9.     led_init();                         /* 初始化LED */
  10.     key_init();                         /* 初始化按键 */
  11.     hyperram_init();                    /* 初始化HyperRAM */
  12.     lcd_init();                         /* 初始化LCD */
  13.     my_mem_init(SRAMIN);                /* 初始化AXI-SRAM1~4内存池 */
  14.     my_mem_init(SRAMEX);                /* 初始化XSPI2 HyperRAM内存池 */
  15.     my_mem_init(SRAM12);                /* 初始化AHB-SRAM1~2内存池 */
  16.     my_mem_init(SRAMDTCM);              /* 初始化DTCM内存池 */
  17.     my_mem_init(SRAMITCM);              /* 初始化ITCM内存池 */
  18.     exfuns_init();                      /* 为exfuns申请内存 */
  19.     f_mount(fs[0], "0:", 1);            /* 挂载SD卡 */
  20.     f_mount(fs[1], "1:", 1);            /* 挂载NOR Flash */
  21.     f_mount(fs[2], "2:", 1);            /* 挂载NAND Flash */
  22.    
  23.     /* 初始化SD卡 */
  24.     while (sd_init() != 0)
  25.     {
  26.         lcd_show_string(30, 30, 200, 16, 16, "SD Card Error!", RED);
  27.         delay_ms(500);
  28.         lcd_show_string(30, 30, 200, 16, 16, "Please Check! ", RED);
  29.         delay_ms(500);
  30.     }
  31.     lcd_fill(30, 30, 30 + 200, 30 + 16, WHITE);
  32.    
  33.     /* 检查字库 */
  34.     while (fonts_init() != 0)
  35.     {
  36.         lcd_show_string(30, 30, 200, 16, 16, "Font Error!  ", RED);
  37.         delay_ms(500);
  38.         lcd_show_string(30, 30, 200, 16, 16, "Please Check!", RED);
  39.         delay_ms(500);
  40.     }
  41.     lcd_fill(30, 30, 30 + 200, 30 + 16, WHITE);
  42.    
  43.     text_show_string(30, 50, 200, 16, "正点原子STM32开发板",16,0, RED);
  44.     text_show_string(30, 70, 200, 16, "录音机实验", 16, 0, RED);
  45.     text_show_string(30, 90, 200, 16, "正点原子@ALIENTEK", 16, 0, RED);
  46.    
  47.     es8388_init();                      /* ES8388初始化 */
  48.     es8388_hpvol_set(25);               /* 设置耳机音量 */
  49.     es8388_spkvol_set(25);              /* 设置喇叭音量 */
  50.    
  51.     while (1)
  52.     {
  53.         wav_recorder();
  54.     }
  55. }
复制代码
可以看到main函数与音乐播放器实验十分类似,封装好了APP,main函数会精简很多。

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

第六十章 录音机实验15119.png
图60.4.1 录音机实验界面

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

第六十章 录音机实验15189.png
图60.4.2 录音进行中

在录音的时候按下KEY0则执行暂停/继续录音的切换,通过LED0指示录音暂停。通过按下KEY1,可以停止当前录音,并保存录音文件。在完成一次录音文件保存之后,我们可以通过按KEY_UP按键,来实现播放这个录音文件(即播放最近一次的录音文件),实现试听。
我们可以把录音完成的wav文件放到电脑上,可以通过一些播放软件播放并查看详细的音频编码信息,本例程使用的是KMPlayer播放,查看到的信息如图60.4.3所示:


第六十章 录音机实验15414.png
图60.4.3 录音文件属性

这和我们程序设计时的效果一样,通过电脑端的播放器可以直接播放我们所录的音频。经实测,效果还是非常不错的。
回复

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

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

GMT+8, 2026-6-11 19:18

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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