本篇文章介绍使用RVB2601开发板自带的麦克风采集周围的声音,记录单位时间内的最大值,用以表示环境噪声水平。为了更直观显示,在OLED显示屏上设计了一个动感光条,能够随着声音节奏跳动。
1、准备工作
开始动工之前首先是查阅资料,做准备工作。作为推出有一段时间的单片机系统,网上早有大神做过声音方面的试验。我的本次试验就是先学习了“我爱下载”同学的《麦克风录音测试》文章,整明白声音采集原理后开始动手的,原贴链接如下。
2、硬件原理
首先介绍一下硬件原理。RVB2601 通过 I2S 和 I2C 总线连接 ES7210 ADC 芯片,实现硅麦的音频信号采样。原理图如下。
图1、音频采样原理
CH2601采用I2C接口完成ES7210的配置,采用I2S接口读取ES7210的转换数据,其接口如图所示。
图2、硬件接口
3、软件设计
软件驱动设计使用了CODEC编解码库。CODEC在这里指的是同时具有D/A(数字讯号转换成模拟讯号)和A/D(模拟讯号转换成数字讯号)转换功能的编解码器,播放音乐的时候用到的是D/A转换功能。在录音的时候用到的是A/D转换功能。具体的详细介绍和使用方法见如下官方链接。
在接口中,D/A指的是输出通道,A/D指的是输入通道。本次实验主要使用AD的输入通道。用到的CODEC的CSI接口如下所示:
图3、软件接口
4、代码编写
下载官方例程ch2601_ft_demo,截取其中录音部分代码。因为本次应用只需要声音幅值,对其代码大幅删减改造,将缓存缩小,只保留一个采样周期就行,每次采样完成,计算这个周期内的音频数据最大值,这个结果就作为要输出的实时音量数据。可以通过串口打印输出,也可以传递给其他任务用于显示。音频采样代码如下。
#include <stdlib.h>
#include <aos/aos.h>
#include "board_config.h"
#include "drv/codec.h"
#include "drv/dma.h"
#include <drv/ringbuffer.h>
#include "snd_get.h"
csi_codec_t codec;
csi_codec_input_t codec_input_ch;
csi_dma_ch_t dma_ch_input_handle;
#define INPUT_BUF_SIZE 2048
uint8_t input_buf[INPUT_BUF_SIZE];
ringbuffer_t input_ring_buffer;
volatile uint32_t cb_input_transfer_flag = 0;
int16_t sound_buff[512];
volatile int16_t sound_max=0;
static void codec_input_event_cb_fun(csi_codec_input_t *i2s, csi_codec_event_t event, void *arg)
{
if (event == CODEC_EVENT_PERIOD_READ_COMPLETE) {
cb_input_transfer_flag += 1;
}
}
void cmd_ft_mic_handler(uint32_t channel_status)
{
csi_error_t ret;
csi_codec_input_config_t input_config;
ret = csi_codec_init(&codec, 0);
if (ret != CSI_OK) {
printf("csi_codec_init error\n");
return ;
}
codec_input_ch.ring_buf = &input_ring_buffer;
csi_codec_input_open(&codec, &codec_input_ch, 0);
/* input ch config */
csi_codec_input_attach_callback(&codec_input_ch, codec_input_event_cb_fun, NULL);
input_config.bit_width = 16;
input_config.sample_rate = 8000;
input_config.buffer = input_buf;
input_config.buffer_size = INPUT_BUF_SIZE;
input_config.period = 1024;
input_config.mode = CODEC_INPUT_DIFFERENCE;
csi_codec_input_config(&codec_input_ch, &input_config);
csi_codec_input_analog_gain(&codec_input_ch, 0xbf);
csi_codec_input_link_dma(&codec_input_ch, &dma_ch_input_handle);
printf("start sound get\n");
csi_codec_input_start(&codec_input_ch);
while (1)
{
if (cb_input_transfer_flag)
{
csi_codec_input_read_async(&codec_input_ch, sound_buff, 1024);
cb_input_transfer_flag = 0U;
sound_max=0;
int ii=0;
while(ii < 512)
{
if(sound_max < sound_buff[ii])
{
sound_max = sound_buff[ii];
}
ii++;
}
// printf("max sound:%d\n",sound_max);
}
aos_msleep(10);
}
return;
}
下图是用串口打印输出的实时音量数据,我对着麦克风用不同音量说话,可以看到,实际数据最小在30左右,最大为32767,平均底噪在50左右。
图4、串口输出音量
5、动感光条实现
从串口数据看还不是很直观,我改造了一下显示程序,将音量变化情况实时显示到OLED屏上。
由于大部分时间都是晚上测试这个板子,看着这个OLED屏幕全亮时还是比较刺眼的,为了能看起来更舒服一点,也增加屏幕寿命,减少功耗,我对显示底层程序做了一点改动,将颜色取反了一下,就看起来舒服多了。具体是修改oled.c这个文件里面的画点函数。代码如下。
void oled_draw_point(uint8_t r, uint8_t c, uint8_t t)
{
if (t) {
CLR_BIT(g_oled_ram[r / 8][c], ((r % 8)));
} else {
SET_BIT(g_oled_ram[r / 8][c], (r % 8));
}
// if (t) {
// SET_BIT(g_oled_ram[r / 8][c], ((r % 8)));
// } else {
// CLR_BIT(g_oled_ram[r / 8][c], (r % 8));
// }
}
然后设计动感光条代码,思路就是按照套娃方法,先画一个大一点的正色矩形,用于显示边框;然后画一个小一圈的反色矩形做衬底;最后将量化后的音量值作为参数,传入后作为长度,用于画第三层正色矩形,就实现了光条设计。连续调用的时候,随着传入的数值变化,光条长短在变化,就实现了动态光条设计,具体代码如下。
//num_p = 1...100
void oled_draw_bar(uint8_t num_p)
{
unsigned char i, j, k;
for (i = 54; i < 64; i++) {
for (j = 0; j < 128; j++) {
oled_draw_point(i, j, 0);
}
}
for (i = 55; i < 63; i++) {
for (j = 1; j < 127; j++) {
oled_draw_point(i, j, 1);
}
}
k = num_p * 124 / 100;
for (i = 56; i < 62; i++) {
for (j = 2; j < k; j++) {
oled_draw_point(i, j, 0);
}
}
oled_reflesh();
}
传入不同数值时,光条动态效果如下。
图5、动态光条效果
然后在主函数里面创建一个任务,将动态光条和声音采样关联起来,就可实现随音乐跳动的炫动光条效果了。代码如下。
/*
* Copyright (C) 2019-2020 Alibaba Group Holding Limited
*/
#include <stdlib.h>
#include <string.h>
#include <aos/aos.h>
#include "aos/cli.h"
#include "main.h"
#include "app_init.h"
#include "oled.h"
#include "snd_get.h"
#define TAG "app"
static void snd_get_task(void *arg);
extern int16_t sound_max;
int main(void)
{
int i=0;
board_yoc_init();
aos_task_new("snd", snd_get_task, NULL, 10 * 1024);
LOGD(TAG, "%s\n", aos_get_app_version());
oled_init();
while (1)
{
aos_msleep(100);
i = sound_max /20;
i++;
if(i>100) i=100;
printf("%d\n",i);
oled_draw_bar( i);
}
return 0;
}
static void snd_get_task(void *arg)
{
cmd_ft_mic_handler(0);
}
最终的音乐光条效果如下视频。音频采集到的数值比较大,为了在普通音量下效果比较明显。我做了一点简单处理,但是声音很大时光柱会显示饱和。要想更好的效果,可以对原始数据进行一阶高通数字滤波,只截取高频波动部分显示,就能够适应更宽的音量范围。
图6、音乐光条
最后,我将声音采集代码移植到我上一篇文章介绍的显示界面中,从下面视频可以看出实时响应效果有些滞后,这个是LVGL的刷新不够造成的。但是在我预期的应用中,影响不大,因为我预计一秒钟以上才上传一次平均值,对实时性要求不高。
图7、在LVGL中显示音量
本次试验主要是从定性的角度进行声音采集和显示,没有定量校准,如果后面能搞到分贝仪,可以尝试对声音采集部分进行校准,就能比较准确显示音量分贝值了。
引用: soso 发表于 2022-4-18 10:11 加油加油
我昨天更新了使用固件,现在下载不了程序了,可能要翻车了。
引用: lugl4313820 发表于 2022-4-17 19:41 你离项目实现越来越近了。我碰到的,就一步一坚难,是不是上帝给我磨难吧
好事多磨,多研究肯定能成功。而且你的文章含金量挺高的,给大家帮助也很多。
引用: lugl4313820 发表于 2022-4-18 10:34 我昨天更新了使用固件,现在下载不了程序了,可能要翻车了。
提交工单吧,他们服务还是很到位的