任务介绍
本项目实现了硬禾科技2025寒假练CrowPanel HMI开发板的任务2,使用官方提供的CrowPanel ESP32 Display 4.3英寸HMI开发板,实现了基于ESP32 UDP通信和数字信号处理的音频信号分析。
硬件平台
首先介绍本次用到的开发板:CrowPanel ESP32 Display,这是一块基于ESP32 S3微控制器的HMI触摸屏开发板,因此它的主体就是一块4.3寸的LCD屏,并且集成了比较丰富的外设接口,例如扩展GPIO、串口、TF卡槽、USB接口、喇叭以及电池接口,在软件方面,官方已经适配好了Arduino IDE、Espressif IDF、PlatformIO和MicroPython多种开发平台,上手开发会很方便。本次我会使用这块CrowPanel开发板,做一个基于ESP32 UDP通信和数字信号处理的音频信号分析实践。
- 主控设备:CrowPanel ESP32 Display开发板
- 搭载双核Xtensa® 32位LX6微处理器
- 集成WiFi/蓝牙无线通信模块
- 480x272分辨率RGB显示屏
- 电阻式触摸屏交互界面
- 音频采集端:PC端麦克风设备
- 16位采样精度
- 8kHz采样频率
- 256样本/帧的传输单元
任务分析与实现
这次主办方出了三种任务,
- 任务一是手写识别显示,在屏幕上手写数字,识别结果并在灯板上显示;
- 任务二是音频信号播放及分析,电脑上采集音频,发送给开发板进行波形显示与信号分析;
- 任务三是自由命题。
这次我选择了任务二。
方案框图:
一、上位机采集层
- 音频流捕获
- 基于PyAudio库实现实时音频采集
- 采用Int16格式原始采样数据
- 256样本/帧的缓冲区配置
- 数据传输模块
- UDP协议无线传输
- 192.168.1.7:5005目标地址配置
- 数据归一化处理(32位浮点转换)
二、下位机处理层
- 通信接口
- WiFi STA模式连接
- UDP服务端(5005端口)
- 数据完整性校验机制
- 信号处理核心
- 双模式显示支持:时域波形/频域谱图
- FFT算法实现(ArduinoFFT库)
- 汉明窗滤波预处理
- 动态频率范围检测(0-4kHz)
- 人机交互界面
- LVGL图形库驱动
- 实时刷新图表(48ms周期)
- 触摸按钮模式切换功能
- 振幅/频段信息显示面板
技术亮点
- 实时音频处理:8kHz采样率下实现<100ms端到端延迟
- 双模显示系统:一键切换时域波形与频域谱图
- 动态频率检测:自动识别有效频段范围
- 多任务架构:FreeRTOS实现显示刷新与数据处理的任务分离
- 自适应缩放:根据信号强度动态调整显示范围
代码详解
下位机整体软件流程图:
本次项目涉及到信号采集、处理与显示几个关键部分,接下来结合相关代码来进行讲解:
一、信号采集模块(Upper.py)
def audio_callback(in_data, frame_count, time_info, status):
audio_data = np.frombuffer(in_data, dtype=np.int16)
audio_data = audio_data.astype(np.float32) / 32768.0 # 归一化处理
sock.sendto(audio_data.tobytes(), (UDP_IP, UDP_PORT))
return (in_data, pyaudio.paContinue)
- 采集参数配置
- 采样格式:16位整型(paInt16)
- 采样率:8kHz(满足语音频段需求)
- 帧缓冲区:256样本/帧(平衡实时性与延迟)
- 通道数:单声道(CHANNELS=1)
- 数据处理流程
- 类型转换:将原始int16数据转换为float32
- 归一化处理:除以32768.0映射到[-1,1]范围
- 网络封装:通过tobytes()转换为二进制流
- 传输协议设计
- UDP协议:采用无连接传输降低延迟
- 目标地址:192.168.1.7:5005(ESP32接收端)
- 数据包大小:256
二、信号处理模块(main.cpp)
1. 数据接收层
int packetSize = Udp.parsePacket();
if (packetSize == sizeof(audioBuffer)) {
Udp.read((char*)audioBuffer, sizeof(audioBuffer));
}
- 完整性校验:验证数据包长度是否为256*4=1024字节
- 错误处理:打印长度不匹配警告信息
- 缓冲区管理:使用全局audioBuffer数组存储浮点数据
2. FFT处理核心
cpp// 应用汉明窗口
FFT.windowing(vReal, 256, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
// 计算FFT
FFT.compute(vReal, vImag, 256, FFT_FORWARD);
// 计算幅度谱
FFT.complexToMagnitude(vReal, vImag, 256);
关键技术点:
- 窗函数:采用汉明窗减少频谱泄漏
- 复数处理:分离实部虚部进行计算
- 频率分辨率:8000/256=31.25Hz
- 有效频段:仅显示0-4kHz(128个频点)
3. 动态频率检测
for (int i = 0; i < 128; i++) {
if (vReal[i] > threshold) {
// 寻找最大/最小有效幅度
}
}
double frequencyResolution = samplingFrequency / 256.0;
- 噪声阈值:设定2.0幅度阈值过滤背景噪声
- 极值追踪:记录最大/最小幅度对应的频点
- 频率计算:索引值×31.25Hz得到实际频率
三、显示模块设计
1. 图形框架初始化
lv_disp_draw_buf_init(&draw_buf, disp_draw_buf, NULL, 480*272/8);
lv_disp_drv_register(&disp_drv);
lv_chart_create(lv_scr_act());
- 显示缓冲:分配480*272/8≈16KB显存
- 刷新模式:LV_CHART_UPDATE_MODE_SHIFT(滚动显示)
- 坐标系:Y轴动态调整范围(波形模式±1000,频谱模式0-1000)
2. 双模式显示控制
void btn_event_cb(lv_event_t * e) {
displaySpectrum = !displaySpectrum;
lv_label_set_text(info_label, ...);
}
- 模式切换:通过触摸按钮触发displaySpectrum标志
- 视图重构:
lv_chart_set_point_count(chart, displaySpectrum?128:256);
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, ...);
3. 实时渲染优化
xTaskCreatePinnedToCore(lvgl_task, "LVGL Task", 4096, NULL, 1, NULL, 0);
void lvgl_task() {
lv_timer_handler();
vTaskDelay(48); // 约20FPS
}
- 多核架构:显示任务运行在核心0,数据处理在核心1
- 刷新周期:48ms(符合人眼视觉暂留特性)
- 数据同步:通过audioBufferMutex信号量保护共享缓冲
四、系统性能优化
- 内存管理
- 静态分配:预定义audioBuffer[256]避免动态分配
- 数据类型:采用float32平衡精度与计算效率
- 实时性保障
- UDP协议:无连接特性降低传输延迟
- FFT优化:使用ArduinoFFT库的模板化实现
- 任务优先级:LVGL任务设置为较高优先级(1级)
- 用户体验增强
- 自动缩放:根据信号幅度动态调整Y轴范围
- 视觉反馈:信息面板显示实时幅值/频率范围
- 触摸响应:非阻塞式事件处理(LV_EVENT_CLICKED)
上位机整体软件流程图:
效果展示
音频波形显示
时域波形
频域波形
遇到的难题与解决办法
数据包较大时数据接收不到
当音频数据包超过1024字节时,下位机出现以下异常:
- 数据接收不完整(payload长度不匹配)
- 系统日志提示"Read mismatch"错误
原因分析
- UDP分片丢失:
- ESP32默认MTU为1500字节
- 实际有效载荷:1472字节(扣除IP/UDP头)
- 原始数据包大小:256采样点 × 4字节 = 1024字节(接近临界值)
- WiFi堆栈限制:
// 原始接收代码
int n = Udp.read((char*)audioBuffer, sizeof(audioBuffer));
// 当网络波动时可能无法完整读取
因此需要调整UDP参数与缓冲区大小,实现完整的数据接收。同时为了提高响应的实时性,尽量使用点数较低的FFT计算(本次使用256).
活动感想
本项目完整实现了从音频采集到频谱分析的全链路开发,让我深入实践了嵌入式系统中的实时信号处理技术。通过UDP协议优化和多线程优化,锻炼了在资源受限环境下开发实时系统的能力。特别感谢硬禾科技提供的高质量开发平台,其完善的周边生态显著降低了嵌入式图形界面开发的门槛。
感谢硬禾学堂举办的寒假练活动,祝硬禾的活动越办越好!