基于MAX78000制作录音笔
该项目使用了MAX78000FTHR开发板,实现了录音笔的设计,它的主要功能为:通过MAX78000FTHR开发板的麦克风获取音频,再编译成MP3格式保存到SD卡中。
标签
嵌入式系统
心叶飞
更新2024-01-10
194

一.项目介绍

      

      本项目基于MAX78000FTHR开发板实现一个简单的录音笔,开发板自带麦克风和SD卡插槽,通过I2S模块获取麦克风原始音频数据,经mp3编码后写入SD卡文件中保存。实现通过开发板上面的按键实现开始录音和停止录音功能,按下sw1开始录音,按下sw2停止录音。

      项目起因是在技术公众号看到了电子森林基于MAX78000智能应用比赛,看了MAX78000开发板的硬件资源非常丰富,于是想通过这次机会,学习一些跟更接近底层硬件操作相关的知识,扩宽一下自己的知识技能,因为以前接触的跟底层相关的知识比较小,上手可能较慢,因此先选择了一个比较简单的录音笔项目,该项目使用到了开发板GPIO,IS2音频,SD文件操作相关内容,内容不算多,但跟硬件相关性还是非常大,可以达到学习嵌入式硬件目的。MAX78000开发板最主要的功能还是带了CNN神经网络模块,等后期熟悉更多知识后再把这块用起来,达到资源利用最大化。

二.项目设计思路

      本项目主要分为以下3个模块:

      1.通过按键控制录音开始和结束,led灯显示当前状态,红色代表正在录音,绿色停止录音,开始后创建保存音频文件,结束后关闭文件

      2.获取开发板麦克风原始数据

      3.将原始音频数据通过MP3编码保存到SD中

      流程图:

FirLAqD6up5DJB8juIZyYCJ-kIr4

      功能实现

      按键控制:

      主要通过开发板上面的sw1实现开始录音功能,sw2实现停止录音功能,按键按下后系统会记录当前的录音状态,如果没有按键开始录音,直接按结束录音不会处理。按下sw1后,通过f_open函数创建并打开音频保存文件,并把当前系统状态置为录音状态,通过LED_Off(LED2);LED_On(LED1);切换LED灯状态为红色。当按下SW2的时候,通过f_close()关闭文件,通过LED_Off(LED1);LED_On(LED2);切换LED灯状态至绿色;

    //定义GPIO按键配置变量
mxc_gpio_cfg_t gpio_key1;
mxc_gpio_cfg_t gpio_key2;

//配置sw1和sw2
gpio_key1.port = MXC_GPIO_PORT_KEY_1;
gpio_key1.mask = MXC_GPIO_PIN_KEY_1;
gpio_key1.pad = MXC_GPIO_PAD_PULL_UP;
gpio_key1.func = MXC_GPIO_FUNC_IN;
gpio_key1.vssel = MXC_GPIO_VSSEL_VDDIOH;
MXC_GPIO_Config(&gpio_key1);
MXC_GPIO_RegisterCallback(&gpio_key1, gpio_isr_key1, NULL);
MXC_GPIO_IntConfig(&gpio_key1, MXC_GPIO_INT_FALLING);
MXC_GPIO_EnableInt(gpio_key1.port, gpio_key1.mask);
NVIC_EnableIRQ(MXC_GPIO_GET_IRQ(MXC_GPIO_GET_IDX(MXC_GPIO_PORT_KEY_1)));

gpio_key2.port = MXC_GPIO_PORT_KEY_2;
gpio_key2.mask = MXC_GPIO_PIN_KEY_2;
gpio_key2.pad = MXC_GPIO_PAD_PULL_UP;
gpio_key2.func = MXC_GPIO_FUNC_IN;
gpio_key2.vssel = MXC_GPIO_VSSEL_VDDIOH;
MXC_GPIO_Config(&gpio_key2);
MXC_GPIO_RegisterCallback(&gpio_key2, gpio_isr_key2, NULL);
MXC_GPIO_IntConfig(&gpio_key2, MXC_GPIO_INT_FALLING);
MXC_GPIO_EnableInt(gpio_key2.port, gpio_key2.mask);
NVIC_EnableIRQ(MXC_GPIO_GET_IRQ(MXC_GPIO_GET_IDX(MXC_GPIO_PORT_KEY_2)));

//在key1中断函数中指定key值为button1
static void gpio_isr_key1(void *cbdata)
{
g_key_vol = KEY_BUTTON_1;
}

//在key2中断函数中指定key值为button2
static void gpio_isr_key2(void *cbdata)
{
g_key_vol = KEY_BUTTON_2;
}

printf("KEY_BUTTON_1 pressed!\n");

snprintf(pcm_file_name, sizeof(pcm_file_name), "recording%04d.pcm", fileCount);
snprintf(mp3_file_name, sizeof(mp3_file_name), "recording%04d.mp3", fileCount);

if ((err = f_open(&file, (const TCHAR *)pcm_file_name, FA_CREATE_ALWAYS | FA_WRITE)) != FR_OK) {
printf("Error opening file: %s\n", get_ff_errors(err));
}

g_cur_state = 1;
printf("begin get i2s data\n");
clean_key();
fileCount++;
LED_Off(LED2);
LED_On(LED1);

if(g_cur_state)
{
if ((err = f_close(&file)) != FR_OK) {
printf("Error closing file: %s\n", get_ff_errors(err));
}

//删除pcm文件
if ((err = f_stat((const TCHAR *)pcm_file_name, &fno)) == FR_NO_FILE) {
printf("File or directory doesn't exist\n");
}

if ((err = f_unlink(pcm_file_name)) != FR_OK) {
printf("Error deleting file\n");
}
}

g_cur_state = 0;
printf("end get i2s data\n");
clean_key();
LED_Off(LED1);
LED_On(LED2);

      

      通过I2S模块获取麦克风音频原始数据,在github上找到一个轻量级的编码库tinymp3,只需要通过shine_initialise和shine_samples_per_pass初始化好编码器,后面直接调用相关编码函数把pcm音频数据编码成mp3即可。

//初始化编码器参数
shine_set_config_mpeg_defaults(&(config.mpeg));
config.wave.samplerate = 8000;
config.wave.channels = 1;
config.mpeg.mode = MONO;
config.mpeg.bitr = 16;
/* Initiate encoder */
encode_config = shine_initialise(&config);
samples_per_pass = shine_samples_per_pass(encode_config) * config.wave.channels * 2;

//初始化I2S
/* Initialize microphone on the Featherboard */
if (max20303_init(MXC_I2C1) != E_NO_ERROR) {
printf("Unable to initialize I2C interface to commonicate with PMIC!\n");
while (1) {}
}

if (max20303_mic_power(1) != E_NO_ERROR) {
printf("Unable to turn on microphone!\n");
while (1) {}
}
MXC_Delay(MXC_DELAY_MSEC(200));

memset(dev_rx_buffer, 0, sizeof(dev_rx_buffer));

mxc_i2s_req_t req;
/* Configure I2S interface parameters */
req.wordSize = MXC_I2S_DATASIZE_WORD;
req.sampleSize = MXC_I2S_SAMPLESIZE_THIRTYTWO;
req.justify = MXC_I2S_MSB_JUSTIFY;
req.wsPolarity = MXC_I2S_POL_NORMAL;
req.channelMode = MXC_I2S_INTERNAL_SCK_WS_0;
/* Get only left channel data from on-board microphone. Right channel samples are zeros */
req.stereoMode = MXC_I2S_MONO_LEFT_CH;
req.bitOrder = MXC_I2S_MSB_FIRST;
/* I2S clock = 12.288MHz / (2*(req.clkdiv + 1)) = 1.024 MHz */
/* I2S sample rate = 1.024 MHz/64 = 16kHz */
req.clkdiv = 5;
req.rawData = NULL;
req.txData = NULL;
req.rxData = dev_rx_buffer;
req.length = I2S_RX_BUFFER_SIZE;

if ((err = MXC_I2S_Init(&req)) != E_NO_ERROR) {
printf("\nError in I2S_Init: %d\n", err);

while (1) {}
}

/* Set I2S RX FIFO threshold to generate interrupt */
MXC_I2S_SetRXThreshold(4);
MXC_NVIC_SetVector(I2S_IRQn, i2s_isr);
NVIC_EnableIRQ(I2S_IRQn);
/* Enable RX FIFO Threshold Interrupt */
MXC_I2S_EnableInt(MXC_F_I2S_INTEN_RX_THD_CH0);
MXC_I2S_RXEnable();

//产生中断数据后获取I2S数据大小
uint32_t get_i2s_recv_size()
{
uint32_t rx_size = 0;
rx_size = MXC_I2S->dmach0 >> MXC_F_I2S_DMACH0_RX_LVL_POS;
return rx_size;
}

//获取fifo数据
int32_t get_i2s_cur_data()
{
return ((int32_t)MXC_I2S->fifoch0) >> 14;
}

 

      通过i2s_flag判断当前音频缓存里面是否有数据,如果有数据,则循环读取数据,然后进行编码保存到文件,最后通过hine_encode_buffer_interleaved进行音频编码

while (g_cur_state)
{
if(get_i2s_flag() == 0)
break;

clear_i2s_flag();

rx_size = get_i2s_recv_size();

while (rx_size--) {
sample = get_i2s_cur_data();

temp = sample >> 14;

sample = HPF((int16_t)temp);

(uint8_t)((sample)*SAMPLE_SCALE_FACTOR / 256);

serialMicBuff[serialMicBufIndex++] = (sample)*SAMPLE_SCALE_FACTOR / 256;

if(serialMicBufIndex == SAMPLE_SIZE-1)
{
serialMicBufIndex = 0;
if ((err = f_write(&file, (TCHAR*)serialMicBuff, SAMPLE_SIZE, &bytes_written_n)) != FR_OK) {
printf("Error writing file: %s\n", get_ff_errors(err));
}
printf("%d bytes written to file!\n", bytes_written_n);
}
}
}


read_sum_len += read_pcm_len;
encode_data = shine_encode_buffer_interleaved(encode_cfg, (int16_t *)pcm_message, &written);

if ((err = f_write(&mp3_file, (TCHAR*)encode_data, written, &bytes_written_n)) != FR_OK) {
printf("Error writing file: %s\n", get_ff_errors(err));
}
sum_len += written;

if ((err = f_read(&pcm_file, &pcm_message, pre_frm_num, &read_pcm_len)) != FR_OK) {
printf("Error reading file: %s\n", get_ff_errors(err));
}

 

      把保存数据到SD卡这部分,首先需要初始化SD部件,定义FF_ERRORS数组变量的值,在出现错误的时候可以查看错误状态,在使用SD卡前,首先需要通过mount()函数挂载SD卡,后面主要就是通过f_open/f_close/f_write进行数据存储操作

    //初始化SD卡操作错误信息
FF_ERRORS[0] = "FR_OK";
FF_ERRORS[1] = "FR_DISK_ERR";
FF_ERRORS[2] = "FR_INT_ERR";
FF_ERRORS[3] = "FR_NOT_READY";
FF_ERRORS[4] = "FR_NO_FILE";
FF_ERRORS[5] = "FR_NO_PATH";
FF_ERRORS[6] = "FR_INVLAID_NAME";
FF_ERRORS[7] = "FR_DENIED";
FF_ERRORS[8] = "FR_EXIST";
FF_ERRORS[9] = "FR_INVALID_OBJECT";
FF_ERRORS[10] = "FR_WRITE_PROTECTED";
FF_ERRORS[11] = "FR_INVALID_DRIVE";
FF_ERRORS[12] = "FR_NOT_ENABLED";
FF_ERRORS[13] = "FR_NO_FILESYSTEM";
FF_ERRORS[14] = "FR_MKFS_ABORTED";
FF_ERRORS[15] = "FR_TIMEOUT";
FF_ERRORS[16] = "FR_LOCKED";
FF_ERRORS[17] = "FR_NOT_ENOUGH_CORE";
FF_ERRORS[18] = "FR_TOO_MANY_OPEN_FILES";
FF_ERRORS[19] = "FR_INVALID_PARAMETER";

// On the MAX78000FTHR board, P0.12 will be pulled low when a card is inserted.
mxc_gpio_cfg_t cardDetect;
cardDetect.port = MXC_GPIO0;
cardDetect.mask = MXC_GPIO_PIN_12;
cardDetect.func = MXC_GPIO_FUNC_IN;
cardDetect.pad = MXC_GPIO_PAD_NONE;
cardDetect.vssel = MXC_GPIO_VSSEL_VDDIOH;

//初始化SD卡模块
MXC_GPIO_Config(&cardDetect);

// Exit function if card is already inserted
if (MXC_GPIO_InGet(MXC_GPIO0, MXC_GPIO_PIN_12) == 0) {
return;
}

printf("Insert SD card to continue.\n");

while (MXC_GPIO_InGet(MXC_GPIO0, MXC_GPIO_PIN_12) != 0) {
// Spin waiting for card to be inserted.
}

//挂载SD卡
int mount()
{
fs = &fs_obj;

if ((err = f_mount(fs, "", 1)) != FR_OK) { //Mount the default drive to fs now
printf("Error opening SD card: %s\n", FF_ERRORS[err]);
f_mount(NULL, "", 0);
} else {
printf("SD card mounted.\n");
mounted = 1;
}

f_getcwd(cwd, sizeof(cwd)); //Set the Current working directory

return err;
}

//创建将要写入数据文件并打开
if ((err = f_open(&file, (const TCHAR *)filename, FA_CREATE_ALWAYS | FA_WRITE)) != FR_OK) {
printf("Error opening file: %s\n", get_ff_errors(err));
}

//向文件内写入编码后的数据
if ((err = f_write(&file, (TCHAR*)buf_start, I2S_RX_BUFFER_SIZE, &bytes_written_n)) != FR_OK) {
printf("Error writing file: %s\n", get_ff_errors(err));
}

      以上就是项目整体代码实现过程,流程很简单,主要就是结合开发板相关硬件资源,再通过软件编码实现音频数据编码,最后保存到SD卡中,录音结果可以能完把SD卡中的MP3拿出来使用。

三.实现结果展示

   结果如视频所示,可以通过按下sw1按键开始录音,录音过程中led灯亮红色,按下sw2按键结束录音,led灯变成绿色,录音结束后把SD卡取下来,用USB读卡器插到电脑上,用播放器可以正常播放录音MP3录音文件。

四.遇到的主要难题及解决方法

   a.在录音的过程中,发现写入获取到的原始录音文件有问题,不能直接使用编译,拿到电脑端也不能正常播放,经参考音频示例,通过添加滤波函数解决数据问题

temp = sample >> 14;

sample = HPF((int16_t)temp);

(uint8_t)((sample)*SAMPLE_SCALE_FACTOR / 256);

int16_t HPF(int16_t input)
{
int16_t Acc, output;
int32_t tmp;

/* a 1st order IIR high pass filter (100 Hz cutoff frequency) */
/* y(n)=x(n)-x(n-1)+A*y(n-1) and A =.995*2^15 */

x0 = input;

tmp = (Coeff * y1);
Acc = (int16_t)((tmp + (1 << 14)) >> 15);
y0 = x0 - x1 + Acc;

/* Clipping */
if (y0 > 32767) {
y0 = 32767;
}

if (y0 < -32768) {
y0 = -32768;
}

/* Update filter state */
y1 = y0;
x1 = x0;

output = (int16_t)y0;

return (output);
}

b.音频写入文件的时候,因为编码需要的时间比较长,所以最好不要在数据读取里面进行大量编码操作,因为这样可能造成数据丢失,后面通过录制完成后再编码

c.本计划通过max78000实现ai语音识别,但是需要用到GPU,目前电脑不带GPU暂未实现,后面更新电脑再来深入研究

附件下载
my_proj.rar
团队介绍
个人
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号