项目介绍
该项目使用CrowPanel ESP32 Display 4.3英寸HMI开发板,实现了音频信号播放及分析,主要功能为接收音频信号,显示波形和频谱图。
上位机部分:
使用python编写,PC上通过自带Micphone采集输入的音频信号,并显示波形,频谱;
再通过WiFi传输采集到的音频信号到4.3英寸显示终端上。
显示终端部分:
对接收到的音频信号做频谱分析,在屏幕上可以显示:
音频信号波形
音频信号的频谱
信号的参数 - 幅度值、频率范围
可以通过界面来选择只显示波形、只显示频谱、二者都显示
硬件介绍
使用了CrowPanel ESP32 Display 4.3英寸HMI开发板。Elecrow ESP32 4.3英寸显示屏是一款功能强大的HMI触摸屏,具有480*272分辨率的LCD显示屏。它使用ESP32-S3-WROOM-1-N4R2模组作为主控处理器,具有双核32位 LX6微处理器,集成WiFi和蓝牙功能,主频高达240MHz,提供强大的性能和多功能的应用,适用于物联网应用设备等场景。
项目设计
方案框图
设计思路
首先是上位机部分,使用python是有几大优点:
简单:Python是一种代表简单主义思想的语言。阅读一个良好的Python程序就感觉像是在读英语一样。它让使用者能够专注于解决问题而不是去搞明白语言本身。
解释性:一个用编译性语言比如C或C++写的程序可以从源文件(即C或C++语言)转换到一个计算机使用的语言(二进制代码,即0和1)。这个过程通过编译器和不同的标记、选项完成。
丰富的库:Python标准库确实很庞大。它可以帮助处理各种工作,包括正则表达式、文档生成、单元测试、线程、数据库、网页浏览器、CGI、FTP、电子邮件、XML、XML-RPC、HTML、WAV文件、密码系统、GUI(图形用户界面)、Tk和其他与系统有关的操作。这被称作Python的“功能齐全”理念。除了标准库以外,还有许多其他高质量的库,如wxPython、Twisted和Python图像库等等。
基于以上几点优点,我可以快速开发调试上位机。
接着是显示终端:
CrowPanel ESP32 Display 4.3英寸HMI开发板支持Arduino IDE、Espressif IDF、PlatformIO、MicroPython等多种开发环境,基于快速开发和调试的需求我使用PlatformIO在Arduino框架下开发,图形界面使用LVGL框架。
软件设计
上位机
流程图
关键代码
# 从音频流中读取音频数据
data = stream.read(CHUNK)
# 将二进制音频数据转换为 numpy 数组
audio_data = np.frombuffer(data, dtype=np.int16)
# 更新时域波形图的数据
line_time.set_ydata(abs(audio_data))
audio_data_abs = abs(audio_data)
# 对音频数据进行快速傅里叶变换
fft_data = np.fft.fft(audio_data)
# 计算频谱的幅度
fft_magnitude = np.abs(fft_data[:CHUNK // 2])
# 计算频谱的频率
frequencies = np.fft.fftfreq(CHUNK, 1 / RATE)[:CHUNK // 2]
# 更新频谱图的数据
line_freq.set_xdata(frequencies)
line_freq.set_ydata(fft_magnitude)
# 将时域波形数据转换为字节流
time_data_bytes = audio_data_abs.tobytes()
# 遍历所有客户端套接字,发送数据并在结尾添加换行符
for client in clients:
client.sendall(time_data_bytes)
显示终端
流程图
关键代码
//主要功能界面创建
void create_screen_autopage() {
void *flowState = getFlowState(0, 1);
lv_obj_t *obj = lv_obj_create(0);
objects.autopage = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, 480, 272);
lv_obj_set_style_bg_color(obj, lv_color_hex(0xffffffff), LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_btn_create(parent_obj);
objects.obj2 = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, 50, 50);
lv_obj_add_event_cb(obj, event_handler_cb_autopage_obj2, LV_EVENT_ALL, flowState);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_img_create(parent_obj);
lv_obj_set_pos(obj, -11, -5);
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_img_set_src(obj, &img_returnpic);
}
}
}
{
// p_line
lv_obj_t *obj = lv_line_create(parent_obj);
static lv_point_t line_points[] = {
{ 0, 0 },
{ 50, 50 },
{ 100, 0 },
{ 150, 50 },
{ 200, 0 }
};
lv_line_set_points(obj, line_points, 5);
lv_line_set_y_invert(obj, true);
objects.p_line = obj;
lv_obj_set_pos(obj, 5, 95);
lv_obj_set_size(obj, LV_PCT(96), LV_PCT(20));
lv_obj_set_style_line_color(obj, lv_color_hex(0xffff0000), LV_PART_MAIN | LV_STATE_DEFAULT);
}
{
// f_line
lv_obj_t *obj = lv_line_create(parent_obj);
static lv_point_t line_points[] = {
{ 0, 0 },
{ 50, 50 },
{ 100, 0 },
{ 150, 50 },
{ 200, 0 }
};
lv_line_set_points(obj, line_points, 5);
lv_line_set_y_invert(obj, true);
objects.f_line = obj;
lv_obj_set_pos(obj, 5, 164);
lv_obj_set_size(obj, LV_PCT(96), LV_PCT(40));
lv_obj_set_style_line_color(obj, lv_color_hex(0xff0000ff), LV_PART_MAIN | LV_STATE_DEFAULT);
}
{
// p_switch
lv_obj_t *obj = lv_switch_create(parent_obj);
objects.p_switch = obj;
lv_obj_set_pos(obj, 166, 5);
lv_obj_set_size(obj, 50, 25);
lv_obj_add_event_cb(obj, event_handler_cb_autopage_p_switch, LV_EVENT_ALL, flowState);
lv_obj_add_state(obj, LV_STATE_CHECKED);
}
{
// f_switch
lv_obj_t *obj = lv_switch_create(parent_obj);
objects.f_switch = obj;
lv_obj_set_pos(obj, 396, 6);
lv_obj_set_size(obj, 50, 25);
lv_obj_add_event_cb(obj, event_handler_cb_autopage_f_switch, LV_EVENT_ALL, flowState);
lv_obj_add_state(obj, LV_STATE_CHECKED);
}
{
lv_obj_t *obj = lv_label_create(parent_obj);
lv_obj_set_pos(obj, 76, 10);
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_label_set_text(obj, "Waveform");
}
{
lv_obj_t *obj = lv_label_create(parent_obj);
lv_obj_set_pos(obj, 231, 10);
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_label_set_text(obj, "Frequency spectrum");
}
{
lv_obj_t *obj = lv_label_create(parent_obj);
lv_obj_set_pos(obj, 235, 42);
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_label_set_text(obj, "Max magnitude:");
}
{
lv_obj_t *obj = lv_label_create(parent_obj);
lv_obj_set_pos(obj, 271, 69);
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_label_set_text(obj, "Frequency:");
}
{
// var_mag
lv_obj_t *obj = lv_label_create(parent_obj);
objects.var_mag = obj;
lv_obj_set_pos(obj, 364, 42);
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_label_set_text(obj, "0");
}
{
// var_frz
lv_obj_t *obj = lv_label_create(parent_obj);
objects.var_frz = obj;
lv_obj_set_pos(obj, 364, 69);
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_label_set_text(obj, "0");
}
}
}
//数据接收
if (client.connected() || client.available()) //如果已连接或有收到的未读取的数据
{
if (client.available()) //如果有数据可读取
{
uint8_t line[2048];
client.read(line,2048);
}
}
//时域数据显示
for (int i = 0; i < 1024; i++)
{
p_line_points[i].y = line[i*2 + 1];
p_line_points[i].y <<= 8;
p_line_points[i].y |= line[i*2];
if (p_line_points[i].y > temp0.int16Value)
{
temp1 = p_line_points[i].y;
}
p_line_points[i].y = p_line_points[i].y/50;
}
lv_label_set_text_fmt(objects.var_mag,"%d",temp1);
功能展示
上位机效果图
同时显示
显示频域
显示时域
感言
感谢电子森林和CrowPanel举办这次活动,我在这次活动中收获颇丰,不仅是对音频数据方面的加深了了解,更对Arduino,FFT,LVGL这些框架和库熟悉应用。对我以后的工作学习提供了可以参考的案例和实践的经验。