搜索
bottom↓
回复: 0

《DNK210使用指南 -SDK版 V1.0》第二十四章 音频录制实验

[复制链接]

出0入234汤圆

发表于 4 小时前 | 显示全部楼层 |阅读模式
2.jpg
1)实验平台:正点原子DNK210开发板
2)购买链接:https://detail.tmall.com/item.htm?id=782801398750
3)全套实验源码+手册+视频下载地址:http://openedv.com/thread-348335-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子手把手教你学DNK210快速入门视频教程:https://www.bilibili.com/video/BV1kD421G7fu
6)正点原子FPGA交流群:132780729
1.png
3.png

第二十四章 音频录制实验


       本章将介绍如何使用Kendryte K210的I2S功能,并将麦克风输入的声音数据以wav格式保存在SD卡中。通过学习本章内容,读者将掌握利用SDK编程技术实现wav音频文件的编码并保存的方法。
       本章分为如下几个小节:
       24.1 I2S录音简介
       24.2 硬件设计
       24.3 程序设计
       24.4 运行验证

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

       24.2 硬件设计

       24.2.1 例程功能

       1. 开机后,先初始化各外设,然后检测SD卡是否存在,如果不存在,则报错。在找到SD卡的RECORDER文件夹后,即进入录音模式(配置I2S和开启中断),此时LCD模块显示进入音频录制实验以及控制信息。KEY0用于开始录音,KEY1用于暂停或开启录音,KEY2用于保存并停止录音。
       当我们按下KEY0的时候,可以在屏幕上看到录音时间,然后通过KEY2可以保存该文件,同时停止录音(文件名和时间也都将清零),按下KEY0开始录音的途中我们也可以通过KEY1暂停录音,再次按下KEY1会继续录音,直至按下KEY2可以保存该文件并停止录音。

       24.2.2 硬件资源

       1. 数字麦克风
              IIS_SDIN - IO30
              IIS_BCK - IO32
              IIS_LRCK - IO33

       24.2.3 原理图
       本章实验内容,需要获取板载数字麦克风的音频数据,然后使用WAV编码后保存到文件系统中。
       DNK210开发板上的数字麦克风的连接原理图,如下所示:

第二十四章 音频录制实验741.png
图24.2.3.1 数字功放NS4168连接原理图

       24.3 程序设计

       24.3.1 mic驱动代码
       本实验是通过I2S将数据输入到数字功放实现音频的播放,数字功放也需要相应的驱动代码进行配置,驱动源码包括两个文件:mic.c和mic.h,我们先介绍mic.h文件的内容。
  1. /*****************************HARDWARE-PIN*********************************/
  2. /* 硬件IO口,与原理图对应 */
  3. #define PIN_SPK_CTRL           (21)

  4. #define PIN_MIC_SDIN           (30)
  5. #define PIN_MIC_BCK            (32)
  6. #define PIN_MIC_WS             (33)

  7. /*****************************SOFTWARE-GPIO********************************/
  8. /* 软件GPIO口,与程序对应 */
  9. #define SPK_CTRL_GPIONUM       (0)

  10. /*****************************FUNC-GPIO************************************/
  11. /* GPIO口的功能,绑定到硬件IO口 */
  12. #define FUNC_SPK_CTRL           (FUNC_GPIO0 + SPK_CTRL_GPIONUM)

  13. #define FUNC_MIC_WS           FUNC_I2S0_WS
  14. #define FUNC_MIC_SDIN         FUNC_I2S0_IN_D1
  15. #define FUNC_MIC_BCK          FUNC_I2S0_SCLK
复制代码
       这个是驱动引脚功能的绑定的宏,除了数据引脚不同外,其他和扬声器的引脚是相同的,也就是说DNK210开发板的音频播放和音频录制两个功能不可以同时使用,只能分步进行。
       下面看mic.c文件代码。
  1. /**
  2. * @brief       麦克风引脚初始化,绑定GPIO功能
  3. * @param       无
  4. * @retval      返回值 : 无
  5. */
  6. void mic_i2s_hardware_init(void)
  7. {
  8.     /* I2S 初始化 */
  9.     gpio_init();    /* 使能GPIO的时钟 */

  10.     /* mic */
  11.     fpioa_set_function(PIN_MIC_WS,   FUNC_MIC_WS);
  12.     fpioa_set_function(PIN_MIC_SDIN, FUNC_MIC_SDIN);
  13.     fpioa_set_function(PIN_MIC_BCK,  FUNC_MIC_BCK);

  14.     fpioa_set_function(PIN_SPK_CTRL, FUNC_SPK_CTRL);
  15.     gpio_set_drive_mode(SPK_CTRL_GPIONUM, GPIO_DM_OUTPUT);  /*输出模式*/
  16.     gpio_set_pin(SPK_CTRL_GPIONUM, GPIO_PV_LOW); /*输出为低,使能麦克风输入*/
  17. }
复制代码
       麦克风的初始化代码和扬声器的也相识,不同的是数据引脚绑定的I2S输入功能,用于数据的接收,控制引脚拉低,使能数字功放输入。

       24.3.2 recoder代码
       recoder程序文件主要用于实现音频录制功能,驱动源码包括两个文件:recoder.c和recoder.h,我们先看下recoder.h文件。
  1. #define REC_I2S_RX_DMA_BUF_SIZE     4096        /* 定义RX DMA 数组大小 */
  2. #define REC_I2S_RX_FIFO_SIZE        10          /* 定义接收FIFO大小 */
  3. #define MIC_GAIN                    5    /* 麦克风增益值,可以根据实际调大录音的音量 */
  4. #define REC_SAMPLERATE              16000       /* 采样率,44.1Khz */
复制代码
       这四个宏分别是设置音频存放的缓存区大小、接收FIFO的数量、麦克风增益以及设置采样率。
       下面看看recoder.c里面的几个函数,代码如下:
  1. /**
  2. * @brief       进入PCM 录音模式
  3. * @param       无
  4. * @retval      无
  5. */
  6. void recoder_enter_rec_mode(void)
  7. {
  8.     /* I2S设备0初始化为接收模式 */
  9.     i2s_init(I2S_DEVICE_0, I2S_RECEIVER, 0x0C);

  10.     /* 通道参数设置 */
  11.     i2s_rx_channel_config(
  12.         I2S_DEVICE_0, /* I2S设备0 */
  13.         I2S_CHANNEL_1, /* 通道1 */
  14.         RESOLUTION_16_BIT, /* 接收数据16bit */
  15.         SCLK_CYCLES_32, /* 单个数据时钟为32 */
  16.         TRIGGER_LEVEL_4, /* FIFO深度为4 */

  17.         STANDARD_MODE); /* 标准模式 */
  18.     /* 设置采样率 */
  19.     i2s_set_sample_rate(I2S_DEVICE_0, REC_SAMPLERATE);

  20.     /* 设置DMA中断回调 */
  21.     dmac_set_irq(DMAC_CHANNEL1, i2s_receive_dma_cb, NULL, 4);
  22. }
复制代码
       该函数用于初始化I2S外设,并将I2S配置为接收模式,设置采样率和DMA中断回调函数,我们使用的采样率为44.1Khz。
  1. /**
  2. * @brief       初始化WAV头
  3. * @param       wavhead : wav文件头指针
  4. * @retval      无
  5. */
  6. void recoder_wav_init(__WaveHeader *wavhead)
  7. {
  8.     wavhead->riff.ChunkID = 0X46464952;     /* RIFF" */
  9.     wavhead->riff.ChunkSize = 0;            /* 还未确定,最后需要计算 */
  10.     wavhead->riff.Format = 0X45564157;      /* "WAVE" */
  11.     wavhead->fmt.ChunkID = 0X20746D66;      /* "fmt " */
  12.     wavhead->fmt.ChunkSize = 16;            /* 大小为16个字节 */
  13.     wavhead->fmt.AudioFormat = 0X01;        /* 0X01,表示PCM;0X01,表示IMA ADPCM */
  14.     wavhead->fmt.NumOfChannels = 2;         /* 双声道 */
  15.     wavhead->fmt.SampleRate = REC_SAMPLERATE;               /* 采样速率 */
  16.     wavhead->fmt.ByteRate = wavhead->fmt.SampleRate * 4;    /* 字节速率=采样率*通道数*(ADC位数/8) */
  17.     wavhead->fmt.BlockAlign = 4;            /* 块大小=通道数*(ADC位数/8) */
  18.     wavhead->fmt.BitsPerSample = 16;        /* 16位PCM */
  19.     wavhead->data.ChunkID = 0X61746164;     /* "data" */
  20.     wavhead->data.ChunkSize = 0;            /* 数据大小,还需要计算 */
  21. }
复制代码
       recoder_wav_init()函数方便初始化wav文件信息。
  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.     char datashow[15];

  13.     __WaveHeader *wavhead = 0;
  14.     DIR recdir;             /* 目录 */
  15.     FIL *f_rec = 0;         /* 录音文件 */

  16.     uint8_t *pdatabuf;      /* 数据缓存指针 */
  17.     uint8_t *pname = 0;
  18.     uint32_t recsec = 0;    /* 录音时间 */

  19.     while (f_opendir(&recdir, "0:/RECORDER"))   /* 打开录音文件夹 */
  20.     {
  21.         msleep(200);
  22.         f_mkdir("0:/RECORDER");                 /* 创建该目录 */
  23.     }
  24.     /* 申请内存 */
  25.     for (i = 0; i < REC_I2S_RX_FIFO_SIZE; i++)
  26.     {
  27.         p_i2s_recfifo_buf = iomem_malloc(REC_I2S_RX_DMA_BUF_SIZE);
  28. /* I2S录音FIFO内存申请 */

  29. <span style="font-style: italic;"><span style="font-style: normal;">        if (p_i2s_recfifo_buf == NULL)
  30.         {
  31.             break;  /* 申请失败 */
  32.         }
  33.     }

  34.     p_i2s_recbuf1 = iomem_malloc(REC_I2S_RX_DMA_BUF_SIZE / 2);
  35. p_i2s_recbuf2 = iomem_malloc(REC_I2S_RX_DMA_BUF_SIZE / 2);
  36. /* I2S录音内存申请 */
  37.     rx_buf = iomem_malloc(REC_I2S_RX_DMA_BUF_SIZE / 2);
  38.     f_rec = (FIL *)iomem_malloc(sizeof(FIL));        /* 开辟FIL字节的内存区域 */
  39. wavhead = (__WaveHeader *)iomem_malloc(sizeof(__WaveHeader));
  40. /* 开辟__WaveHeader字节的内存区域 */
  41. pname = iomem_malloc(30);   
  42. /* 申请30个字节内存,类似"0:RECORDER/REC00001.wav" */
  43. if (!p_i2s_recbuf2 || !f_rec || !wavhead || !pname)rval = 1;   
  44. /* 任意一项失败, 则失败 */

  45.     if (rval == 0)
  46.     {
  47.         recoder_enter_rec_mode();   /* 进入录音模式 */
  48.         pname[0] = 0;               /* pname没有任何文件名 */

  49.         while (rval == 0)
  50.         {
  51.             key = key_scan(0);
  52.             switch (key)
  53.             {
  54.                case KEY0_PRES:     /* 开始录音 */
  55.                     recsec = 0;
  56.                     recoder_new_pathname(pname);    /* 得到新的名字 */
  57.                     recoder_wav_init(wavhead);      /* 初始化wav数据 */
  58.                     res = f_open(f_rec, (const TCHAR *)pname, FA_CREATE_ALWAYS |
  59. FA_WRITE);

  60.                     if (res)            /* 文件创建失败 */
  61.                     {
  62.                         g_rec_sta = 0;  /* 创建文件失败,不能录音 */
  63.                         rval = 0XFE;    /* 提示是否存在SD卡 */
  64.                     }
  65.                     else
  66.                     {
  67.                         res = f_write(f_rec, (const void *)wavhead,
  68. sizeof(__WaveHeader), &bw);  /* 写入头数据 */
  69.                         g_rec_sta |= 0X80;  /* 开始录音 */
  70.                         /* I2S通过DMA接收数据,保存到rx_buf中 */
  71.                         i2s_receive_data_dma(I2S_DEVICE_0, p_i2s_recbuf1,
  72. REC_I2S_RX_DMA_BUF_SIZE, DMAC_CHANNEL1);
  73.                     }
  74.                     break;

  75.                 case KEY1_PRES: /*REC/PAUSE */
  76.                     if (g_rec_sta & 0X01)           /* 原来是暂停,继续录音 */
  77.                     {
  78.                         g_rec_sta &= 0XFE;          /* 取消暂停 */
  79.                         /* I2S通过DMA接收数据,保存到rx_buf中 */
  80.                         i2s_receive_data_dma(I2S_DEVICE_0, p_i2s_recbuf1,
  81. REC_I2S_RX_DMA_BUF_SIZE, DMAC_CHANNEL1);
  82.                     }
  83.                     else if (g_rec_sta & 0X80)      /* 已经在录音了,暂停 */
  84.                     {
  85.                         g_rec_sta |= 0X01;          /* 暂停 */
  86.                     }
  87.                     else    /* 还没开始录音 */
  88.                     {
  89.                         recsec = 0;
  90.                         recoder_new_pathname(pname);    /* 得到新的名字 */
  91.                         recoder_wav_init(wavhead);      /* 初始化wav数据 */
  92.                         res = f_open(f_rec, (const TCHAR *)pname,
  93. FA_CREATE_ALWAYS | FA_WRITE);

  94.                         if (res)            /* 文件创建失败 */
  95.                         {
  96.                             g_rec_sta = 0;  /* 创建文件失败,不能录音 */
  97.                             rval = 0XFE;    /* 提示是否存在SD卡 */
  98.                         }
  99.                         else
  100.                         {
  101.                             res = f_write(f_rec, (const void *)wavhead,
  102. sizeof(__WaveHeader), &bw); /* 写入头数据 */
  103.                             g_rec_sta |= 0X80;  /* 开始录音 */
  104.                             /* I2S通过DMA接收数据,保存到rx_buf中 */
  105.                             i2s_receive_data_dma(I2S_DEVICE_0, p_i2s_recbuf1,
  106. REC_I2S_RX_DMA_BUF_SIZE, DMAC_CHANNEL1);
  107.                         }
  108.                     }
  109.                     key = 0;
  110.                     break;
  111.                 case KEY2_PRES:     /* STOP&SAVE */
  112.                     if (g_rec_sta & 0X80)   /* 有录音 */
  113.                     {
  114.                         g_rec_sta = 0;      /* 关闭录音 */
  115.                         wavhead->riff.ChunkSize = g_wav_size + 36;  
  116. /* 整个文件的大小-8; */
  117.                         wavhead->data.ChunkSize = g_wav_size;       /* 数据大小 */
  118.                         f_lseek(f_rec, 0);                     /* 偏移到文件头. */
  119.                         f_write(f_rec, (const void *)wavhead,
  120. sizeof(__WaveHeader), &bw);   /* 写入头数据 */
  121.                         f_close(f_rec);
  122.                         g_wav_size = 0;
  123.                     }

  124.                     g_rec_sta = 0;
  125.                     recsec = 0;
  126.                     g_index = 0;
  127.                     key = 0;
  128.                     break;
  129.             }

  130.             if (recoder_i2s_fifo_read(&pdatabuf))/*读取一次数据,读到数据了,写入文件*/
  131.             {
  132.                 res = f_write(f_rec, pdatabuf, REC_I2S_RX_DMA_BUF_SIZE, &bw);
  133. /* 写入文件 */
  134.                 if (res)
  135.                 {
  136.                     printf("write error:%d\r\n", res);
  137.                 }

  138.                 g_wav_size += REC_I2S_RX_DMA_BUF_SIZE;  /* WAV数据大小增加 */
  139.             }
  140.             else
  141.             {
  142.                 msleep(1);
  143.             }

  144.             if (recsec != (g_wav_size / wavhead->fmt.ByteRate)) /* 录音时间显示 */
  145.             {
  146.                 recsec = g_wav_size / wavhead->fmt.ByteRate;    /* 录音时间 */
  147.                 sprintf((char *)datashow, "time:%02d:%02d", (uint16_t)(recsec /
  148. 60), (uint16_t)(recsec % 60));

  149.                 for (size_t i = 0; i < 320 * 16; i++)
  150.                 {
  151.                     lcd_gram</span><span style="font-style: normal;"> = 0xFFFF;
  152.                 }
  153.                 draw_string_rgb565_image(lcd_gram, 320, 240, 10, 0,
  154. (char *)datashow, BLUE);
  155.                 lcd_draw_picture(0, 70, 320, 16, (uint16_t *)lcd_gram);
  156.             }
  157.         }
  158.     }
  159.    
  160.     for (i = 0; i < REC_I2S_RX_FIFO_SIZE; i++)
  161.     {
  162.         iomem_free(p_i2s_recfifo_buf</span><span style="font-style: normal;">);   /* SAI录音FIFO内存释放 */
  163.     }
  164.    
  165.     iomem_free(p_i2s_recbuf1);  /* 释放内存 */
  166.     iomem_free(p_i2s_recbuf2);  /* 释放内存 */
  167.     iomem_free(f_rec);          /* 释放内存 */
  168.     iomem_free(wavhead);        /* 释放内存 */

  169.     iomem_free(pname);          /* 释放内存 */
  170. }</span></span>
复制代码
       wav_recorder函数是我们实现录音功能的主要函数,它首先是申请数个缓存区,然后将I2S按顺序存入这些缓存区中,再一个一个写入到SD卡保存,我们通过相应的按键控制录音的开始、暂停与继续、停止并保存录音文件等操作,录音完成我们还要重新计算录音文件的大小并写入wav文件头,以保证音频文件能正常被解析。

       24.3.3 main.c代码
       main.c中的代码如下所示:
  1. int main(void)
  2. {
  3.     FRESULT res;
  4.     FATFS fs;
  5.    
  6.     sysctl_pll_set_freq(SYSCTL_PLL0, 800000000);
  7.     sysctl_pll_set_freq(SYSCTL_PLL1, 400000000);
  8.     sysctl_pll_set_freq(SYSCTL_PLL2, 45158400);
  9.     sysctl_set_power_mode(SYSCTL_POWER_BANK6, SYSCTL_POWER_V18);
  10.     sysctl_set_power_mode(SYSCTL_POWER_BANK7, SYSCTL_POWER_V18);
  11.     sysctl_set_spi0_dvp_data(1);

  12.     plic_init();
  13.     sysctl_enable_irq();
  14.     dmac_init();

  15.     key_init();              /* 按键初始化 */
  16.     mic_i2s_hardware_init(); /* 麦克风初始化 */
  17.     lcd_init();                             /* 初始化LCD */
  18.     lcd_set_direction(DIR_YX_LRUD);
  19.     lcd_draw_string(10, 10, "RECODER ", RED);
  20.     lcd_draw_string(10, 30, "KEY0:START ", RED);
  21.     lcd_draw_string(10, 50, "KEY1:REC/PAUSE KEY2:STOP&SAVE", RED);

  22.     /* 初始化SD卡*/
  23.     if (sd_init() != 0)
  24.     {
  25.         printf("SD card initialization failed!\n");
  26.         while (1);
  27.     }
  28.     printf("SD card initialization succeed!\n");

  29.     /* Filesystem mount SD card */
  30.     res = f_mount(&fs, _T("0:"), 1);
  31.     if (res != FR_OK)
  32.     {
  33.         printf("SD card mount failed! Error code: %d\n", res);
  34.         while (1);
  35.     }
  36.     printf("SD card mount succeed!\n");

  37.     while (1)
  38.     {
  39.         wav_recorder();  /* 开始录音 */
  40.     }
  41. }
复制代码
       main函数代码具体流程大致是:首先完成系统级和用户级初始化工作,完成LCD、按键、麦克风、SD卡等初始化,然后挂载SD卡,挂载成功后进入录音函数,LCD模块显示按键控制相关信息。

       24.4 运行验证
       将DNK210开发板连接到电脑主机,通过VSCode将固件烧录到开发板中,程序启动后进入录音模式,我们按下KEY0控制DNK210开发板开始录音,可以看到LCD显示录音时间信息,LCD显示的内容如图24.4.1所示:

第二十四章 音频录制实验12423.png
图24.4.1音频录制中运行效果图

       录音完成后我们可以将SD卡插入读取器在电脑中播放录音。

阿莫论坛20周年了!感谢大家的支持与爱护!!

你熬了10碗粥,别人一桶水倒进去,淘走90碗,剩下10碗给你,你看似没亏,其实你那10碗已经没有之前的裹腹了,人家的一桶水换90碗,继续卖。说白了,通货膨胀就是,你的钱是挣来的,他的钱是印来的,掺和在一起,你的钱就贬值了。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-10-16 18:17

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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