一.项目介绍
本项目基于MAX78000FTHR开发板实现一个简单的录音笔,开发板自带麦克风和SD卡插槽,通过I2S模块获取麦克风原始音频数据,经mp3编码后写入SD卡文件中保存。实现通过开发板上面的按键实现开始录音和停止录音功能,按下sw1开始录音,按下sw2停止录音。
项目起因是在技术公众号看到了电子森林基于MAX78000智能应用比赛,看了MAX78000开发板的硬件资源非常丰富,于是想通过这次机会,学习一些跟更接近底层硬件操作相关的知识,扩宽一下自己的知识技能,因为以前接触的跟底层相关的知识比较小,上手可能较慢,因此先选择了一个比较简单的录音笔项目,该项目使用到了开发板GPIO,IS2音频,SD文件操作相关内容,内容不算多,但跟硬件相关性还是非常大,可以达到学习嵌入式硬件目的。MAX78000开发板最主要的功能还是带了CNN神经网络模块,等后期熟悉更多知识后再把这块用起来,达到资源利用最大化。
二.项目设计思路
本项目主要分为以下3个模块:
1.通过按键控制录音开始和结束,led灯显示当前状态,红色代表正在录音,绿色停止录音,开始后创建保存音频文件,结束后关闭文件
2.获取开发板麦克风原始数据
3.将原始音频数据通过MP3编码保存到SD中
流程图:
功能实现
按键控制:
主要通过开发板上面的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暂未实现,后面更新电脑再来深入研究