- 2025-03-13
-
发表了主题帖:
【2024 DigiKey大赛参与奖】开箱帖--m5stack_core3Se
特么的我也中了一个参与奖,其中过程比较锻炼,有个安慰奖也行吧,
还是关注esp32系列的产品,继续粉m5stack,碍于经费预算不多,只能买个阉割版。
迅速刷入 小智固件就可以开心地玩耍啦
- 2024-12-30
-
加入了学习《基于 Arduino?Nano RP2040 Connect?开发板的机器学习系列示例应用》,观看 基于 ArduinoNano RP2040 Connect开发板的机器学习系列示例应用
-
发表了主题帖:
【得捷电子Follow Me第二季第4期】Nano RP2040 Connect 的机器学习实...
本帖最后由 genvex 于 2024-12-30 23:58 编辑
【得捷电子Follow Me第二季第4期】基于Arduino Nano RP2040 Connect 的机器学习实践(汇总)
欢迎大家来到得捷与eeworld联合举办的Follow me第二集的都最终季,这一次我们玩的一个加强版的RP2040,这个板子集成了WIFI,加速度传感器,麦克风,覆盖机器学习项目所需要的硬件支撑。在本次活动中,同时配了一个NANO专用grove接口的扩展板,开拓了NANO和外部世界连接的窗口,同时还购买了一片经济实惠使用oled屏幕,用于展示输出的数据和结果,简直不能再完美了。
项目内容目录
一、编程环境搭建
二、任务1:LVGL点屏点灯
三、任务2:LVGL开机动画及水平仪UI 设计
四、任务3:风吹乱了我的秀发(声音数据+灯光展示)
五、进阶任务:FFT声音动态灯光秀
六、进阶任务:TinyML运动姿势机器学习实践
一、编程环境搭建
官方的例程都提供了Arduino环境的参考代码,Arduino的IDE不适用大型项目管理,虽然本次活动的任务也足够简单,本着把简单问题复杂化的做法,还是在VSCODE开展了全面编码工作。
鉴于本次的项目任务相对分散,没有将所有任务合成一个项目,但是完成了将所有工作都放置在一套依赖体系里面,调试起来非常友好和方便。对项目的工程文件platformio.ini 作了以下规划和调整,请仔细阅读下图,对你定有参考意义。
二、任务1:LVGL点屏点灯
开篇即开挂。完成了基于 Arduino Nano RP2040 Connect 开发板的 LVGL 图形界面示例,主要实现了在 SSD1306 OLED 显示屏上显示中英文混合文本。通常LVGL在彩色LCD屏幕上使用较多,在oled上运行LVGL的案例可谓少见。一来觉得黑白的oled颜色非黑即白没有什么可玩性,二来实现起来的难度不小。 经过长期的摸索学习,终于形成了一套在oled屏上运行LVGL的方法。经过多次验证本套代码适用于esp32系列和树莓派rp2040系列开发板,其他的开发板应该也可以。 该项目得以实施的关键在于LovyanGFX 为oled屏幕提供基于灰阶的刷屏函数,理论上可以把黑色到白色细分为256个等级,使得oled不再是非黑即白,呈现了一度颜色梯度增加了观赏性。
这里留一个问题:中文显示是怎么实现的,可以将你的答案放在讨论区。
static void lvgl_begin(void)
{
// 初始化 OLED 显示屏
oled.init();
// 初始化 LVGL
lv_init();
// 配置显示缓冲区
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, DISP_BUF_SIZE);
// 设置显示驱动参数
static lv_disp_drv_t disp_drv;
disp_drv.hor_res = LVGL_HOR_RES; // 128 像素宽
disp_drv.ver_res = LVGL_VER_RES; // 64 像素高
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &disp_buf;
// 创建并显示文本标签
lv_obj_t* label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "你好 世界!\nHello DigiKey\n & EEWorld! ");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}
三、任务2:LVGL开机动画及水平仪UI 设计
开机动画足够炫酷,开机启动的时候是三个弧形在动态旋转,之后博主的头像就逐渐显现,然后进入水平仪UI.这套动画方案是从乐鑫开机动画学来的,在他的基础上,任意增加渐变显示图片和文字的动画,如果你没看明白或者你也想学可以给我留言说说哪里不明白的地方。
原创的水平仪设计,用一个圆环作为一个黑色小球的运行轨道,黑色小球可以根据加速数值的变化计算倾斜的角度,可以用做量角器。 UI的设计没少花功夫,特别是如何修饰圆形上的组件,欢迎你仔细品鉴。
(水平仪的UI核心代码)
void MpuView::create()
{
/** screen */
lv_obj_t *root = lv_obj_create(NULL);
lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_size(root, LV_HOR_RES, LV_VER_RES);
lv_obj_center(root);
lv_obj_set_style_pad_all(root, 0, 0);
lv_obj_set_style_bg_color(root, lv_color_black(), 0);
lv_obj_set_style_bg_opa(root, LV_OPA_100, 0);
ui.root = root;
// lv_obj_set_flex_grow(ui.city, 1);
static lv_style_t style;
lv_style_init(&style);
/*Make a gradient*/
lv_style_set_bg_color(&style, lv_color_black());
// lv_style_set_border_opa(&style, LV_OPA_100);
lv_style_set_bg_opa(&style, LV_OPA_100);
lv_obj_t *chart = lv_chart_create(root);
// lv_obj_remove_style_all(game_station);
lv_obj_set_size(chart, LV_PCT(100), LV_PCT(80));
lv_obj_align(chart, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_add_style(chart, &style, 0);
lv_obj_set_style_border_color(chart, lv_color_black(), 0);
lv_obj_set_style_radius(chart, 0, 0);
lv_chart_set_axis_tick(chart, LV_CHART_AXIS_PRIMARY_Y, 5, 3, 5, 2, true, 10);
lv_chart_set_point_count(chart, 100);
lv_chart_set_div_line_count(chart, 0, 0);
ui.chart = chart;
lv_obj_add_flag(chart, LV_OBJ_FLAG_HIDDEN);
lv_chart_set_type(chart, LV_CHART_TYPE_LINE); /*Show lines and points too*/
lv_obj_set_style_size(chart, 0, LV_PART_INDICATOR);
/*Add two data series*/
ser1 = lv_chart_add_series(chart, lv_color_white(), LV_CHART_AXIS_PRIMARY_Y);
ser2 = lv_chart_add_series(chart, lv_color_white(), LV_CHART_AXIS_SECONDARY_Y);
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, -1, 1);
lv_chart_set_range(chart, LV_CHART_AXIS_SECONDARY_Y, -1, 1);
lv_obj_t *labelaccX = lv_label_create(root);
lv_obj_set_size(labelaccX, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_label_set_text_fmt(labelaccX, "X:%.2f,Y:%.2f", 0.25, 0.25);
lv_obj_align(labelaccX, LV_ALIGN_TOP_LEFT, 0, 0);
lv_obj_set_style_text_color(labelaccX, lv_color_white(), 0);
lv_obj_set_style_text_font(labelaccX, &lv_font_montserrat_16, 0);
ui.labelaccX = labelaccX;
lv_obj_t *arc = lv_arc_create(root);
lv_obj_set_size(arc, 60, 60);
lv_obj_clear_flag(arc, LV_OBJ_FLAG_CLICKABLE);
// 改变进度条颜色
// lv_obj_set_style_bg_color(arc, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLUE); // 轨道颜色为蓝色
// lv_obj_set_style_local_bg_color(arc, LV_OBJ_PART_INDICATOR, LV_STATE_DEFAULT, LV_COLOR_GREEN); // 进度条颜色为绿色
// lv_obj_set_style_border_width(arc, 5, 0); // 设置进度条的宽度
lv_obj_set_style_arc_width(arc, 5, LV_PART_MAIN|LV_PART_INDICATOR);
lv_obj_set_style_arc_color(arc, lv_color_white(), LV_PART_MAIN|LV_PART_INDICATOR); // arc color ,not the value color
lv_arc_set_rotation(arc, 270);
lv_arc_set_bg_angles(arc, 0, 360);
lv_arc_set_angles(arc, 0, 40);
lv_obj_center(arc);
ui.arc = arc;
}
四、任务3:风吹乱了我的秀发(声音数据+灯光展示)
根据作业任务要求“学习PDM麦克风技术知识,调试PDM麦克风,通过串口打印收音数据和音频波形。” ,我把麦克风的数据采集了,然后再oled上展示出来,同时串口也可以看到麦克风数据,打开串口查看工具就可以看大波形。
外界声音较小的时候,在oled的上的波形是一条平稳的支线,当你向它吹一口气,线条就顿时凌乱起来。 具体实现方案请查看代码。
五、进阶任务:FFT声音动态灯光秀
根据作业要求“通过RGB LED不同颜色、亮度显示PDM麦克风收到的声音大小;”,难度不算大,因此,在这里加大了难度,使用FFT组件分析采集到的声音数据,然后将频谱数据划分3个部分,就叫低频、中频、高频。它非常灵敏地侦测到外界声音的变化,灯光熠熠生辉,甚是有趣。 难度在于对FFT理解,FFT函数的应用实践,这里也不作过多地解析,反正关于FFT的故事还在继续。
(核心代码)
short sampleBuffer[SAMPLES];
volatile int samplesRead;
// FFT 对象
// ArduinoFFT FFT = ArduinoFFT();
ArduinoFFT<double> FFT = ArduinoFFT<double>();
// 频谱数据
double vReal[SAMPLES];
double vImag[SAMPLES];
// PDM 数据回调函数
void onPDMdata() {
int bytesAvailable = PDM.available();
PDM.read(sampleBuffer, bytesAvailable);
samplesRead = bytesAvailable / 2; // 每个样本 2 字节
}
// 计算频带强度
double calculateFrequencyBand(double* data, int start, int end) {
double sum = 0;
for (int i = start; i < end; i++) {
sum += data[i];
}
return sum;
}
// 设置 LED 亮度
void setLEDs(double lowFreq, double midFreq, double highFreq) {
// 将强度映射到 0-255 范围
int lowBrightness = map(lowFreq, 0, 10000, 0, 255);
int midBrightness = map(midFreq, 0, 10000, 0, 255);
int highBrightness = map(highFreq, 0, 10000, 0, 255);
// 限制亮度范围
lowBrightness = constrain(lowBrightness, 0, 255);
midBrightness = constrain(midBrightness, 0, 255);
highBrightness = constrain(highBrightness, 0, 255);
// 设置 LED 的亮度
analogWrite(LEDR,lowBrightness);
analogWrite(LEDG, midBrightness);
analogWrite(LEDB,highBrightness);
}
void setup() {
// 初始化串口
Serial.begin(115200);
oled.begin();
oled.setRotation(0);
// 配置 LED 引脚为输出
pinMode(LEDR, OUTPUT); // 设置红色 LED 引脚为输出
pinMode(LEDG, OUTPUT); // 设置绿色 LED 引脚为输出
pinMode(LEDB, OUTPUT); // 设置蓝色 LED 引脚为输出
// 配置数据接收回调函数
PDM.onReceive(onPDMdata);
// 初始化 PDM 麦克风:
// - 一个通道(单声道模式)
// - 20 kHz 采样率
if (!PDM.begin(channels, SAMPLING_FREQUENCY)) {
Serial.println("无法启动 PDM!");
while (1); // 启动失败时停止程序
}
}
void loop() {
// 如果读取到新的样本
if (samplesRead) {
// 将样本数据复制到 vReal
for (int i = 0; i < SAMPLES; i++) {
vReal[i] = sampleBuffer[i];
vImag[i] = 0;
}
// 执行 FFT
FFT.windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.compute(vReal, vImag, SAMPLES, FFT_FORWARD);
FFT.complexToMagnitude(vReal, vImag, SAMPLES);
// 计算低频、中频和高频的强度
double lowFreq = calculateFrequencyBand(vReal, 0, SAMPLES / 3); // 0-2kHz
double midFreq = calculateFrequencyBand(vReal, SAMPLES / 3, SAMPLES*2 / 3); // 2-4kHz
double highFreq = calculateFrequencyBand(vReal, SAMPLES *2/ 3, SAMPLES ); // 4-8kHz
// 控制 LED 亮度
setLEDs(lowFreq, midFreq, highFreq);
// 重置样本计数
samplesRead = 0;
}
}
六、进阶任务:TinyML运动姿势机器学习实践
本项目包含两个相关示例:数据采集器 (5_TinyML_dataCollector)模型推理 (5_TinyMLimu_inferencing) , 使用Edge Impulse训练的模型进行推理,基于加速度传感器数据进行手势识别。
从头完成一个机器学习项目步骤真的很多,工作也很琐碎,劳动量也不少,例如数据采集部分是一个很费时费力的环节,反正就是“大力出奇迹”吧,数据也多理论上预测会更精确,也不排除一开始的方向就是错的。 来来来,先别走,我们一起把这个流程过一遍,有个感性-性感认识也行。
(1)连接Edge Impulse:
Edge Impulse 是一个领先的边缘人工智能开发平台,支持多种数据类型,如加速度计数据、音频、图像等,也可收集来自各种传感器硬件、公共数据集等的数据。提供创建脉冲(Impulse)的功能,包含处理模块(如频谱分析)和学习模块(如分类、回归、异常检测等),可自定义构建模型。具有边缘优化神经(Edge Optimised Neural, EON)编译程序,编译出的神经网络推论模型能减少内存使用和储存空间。运用数字信号处理区块(DSP Block)进行声音推论前的前置处理,可加快推论速度并提高精准度。支持多种开发板,方便记录和上传数据集。
数据采集环节需要解决一个关键问题是怎么把数据投喂给这个网络平台,这个平台还在国外。 据介绍数据上传的方式也有几种,例如可以把数据保存下来,然后以数据包的方式上传。另外,最直观和推荐的方式是在线采集。 Edge Implus为数据采集提供了一套方案,具体如下:
创建项目:在Edge Impulse新建项目并选类型。
安装及运行工具:在电脑装Edge Impulse CLI,在终端等运行sudo edge - impulse - data - forwarder启动。
安装最新 LTS 版本:
choco install nodejs-lts
npm install -g edge-impulse-cli --force
npm install -g edge-impulse-cli
npm cache clean --force
npm install -g edge-impulse-cli
把edge-impulse-cli安装成功就已经成功一半了,后面的就按提示继续进行就可以了。
- 设备连接:用CLI连接设备与Edge Impulse,登录、选项目、命名传感器和设备。
- 数据采集:选“3 axes”传感器,命名标签,改采样长度后开始采样,按指定动作移动设备并保持。
- 数据分割:点击“Split Sample”,添加并分割数据段。
- 重复采集:重复采集和分割步骤,用不同名称标记不同运动数据。
(2) 模型构建与测试:
- 设置频谱特征:设置并保存频谱特征参数,生成特征。
- 训练模型:点击NN Classifier开始训练,选Unoptimized (float32)。
- 模型测试:点击Model testing并点击Classify all进行测试,准确率低可增加训练集和采样时间。
(3) 部署与验证:
- 构建库文件:在Edge Impulse点击Deployment,选Arduino Library构建并下载.ZIP文件,通过Arduino IDE添加该库。
晕了没有,如果没有晕还想继续的话,点这个链接:
https://wiki.seeedstudio.com/XIAO-RP2040-EI/
这个教程有些老,但步骤大差不差的,主要是跑通后,还需要多次练习才行,检测的精度才会有所提升。
总结:
本次项目完满成功,取得多项重大进展:
基于vscode-pio的多项目demo文档管理方式,这种方式很适合开发板的配套Demo使用。
继续推广lvgl在单色屏上的应用,效果还不错,今后还会继续使用,希望更多人用起来,最好把本篇文章和本人的名号也引用一下,多谢了。
使用了FFT技术对声音进行频谱处理并映射到三色灯上。
最后,完成了基于Edgde Impluse的TinyML的动作识别机器学习实践。 这个内容是参加本次活动的主要目的。 虽然主板上已经集成了机器学习的算法,简单的调个包就可以出来结果,结果精度也不错,但是那些都是黑匣子,自己没有主动权。
最后的最后:
参加活动最开心的时候是功能探索的过程,最痛苦的时候是写报告。
虽然主办方给的题目都是“点灯题目”,简单完成任务是可以,但肯定少学到很多东西,参加活动不是真的为了嫖板子,主要是利用交作业的压力,把压力化解为学习的动力,更能驱动自己学习到更多的东西。
预祝大家在新的一年里学习进步,身体健康,万事如意!
- 2024-11-29
-
加入了学习《基于ESP32-S3-LCD-EV-Board的物联网多功能平台》,观看 基于ESP32-S3-LCD-EV-Board的物联网多功能平台
- 2024-11-28
-
上传了资料:
基于ESP32-S3-LCD-EV-Board的物联网多功能平台代码
-
加入了学习《2024创意大赛-基于ESP32-S3-LCD-EV-Board的物联网多功能平台》,观看 2024创意大赛esp32S3ev-board
-
加入了学习《直播回放: DigiKey FollowMe 第二季 第4期 Arduino Nano RP2040 Connect 任务讲解》,观看 Arduino Nano RP2040 Connect 任务讲解
-
发表了主题帖:
【2024 DigiKey 创意大赛】基于ESP32-S3-LCD-EV-Board的物联网多功能平台
基于ESP32-S3-LCD-EV-Board的物联网多功能平台:
变色龙UI框架与LVGL游戏移植实践
作者:genvex
一、作品简介
故事过于恢宏庞大以至于不知从何讲起,说多了多眼泪,是不?
剧情需要还是讲些朋友们喜闻乐见的东西吧,我用 ESP32-S3-LCD-EV-Board 做一个lvgl的游戏合集,把目前B站流传的几个成功的lvgl游戏移植到了ev-Board上,游戏包括:羊了羊、消消乐、打砖块、植物大战僵尸等等,载体是乐鑫新推出的UI-框架(esp-brookesia) ,这个框架类似手机APP的运行界面和方式,每个程序独立管理互不影响,效果如下图所示,这种样式的框架也是所看好的发展趋势,因为随着mcu的频率不断的提升,例如乐鑫的P4 芯片的推出,无法阻挡着创客们往更加生动UI交互方向发展。因为程序可以控制进退,游戏运行起来还算流畅,这是在Arduino+pio上面所不能达到的效果,除了有游戏合集外,还专门制作了一个扩展板,这个扩展板简单地扩展了2个I2C的grove接口,一个UART的grove 接口,一个I2C的qwiic接口。grove制式的接口是M5stack和Seeed两大国内嵌入式系统创客生态的巨头,他们所生产的传感器都是基于grove接口的,那么这样EV-Board通过这样的方式就可以无缝连接地使用这两家公司所以千计的各种各样的传感器了,另外,qwiic接口则是国外嵌入系统巨无霸公司Adafruit公司所采用的传感器连接方式,作为嵌入式系统创客的鼻祖,她们家的传感器丰富至极,那么这么来一板在手,天下我有,想想都是开心呀。 故事梗概就这么多,下面我来分解动作,先解析这个框架的运行方式,然后再到单独没个程序的移植开发,坐好扶稳啦。
二、系统框图
项目以“esp32S3_ev_board”(开发板)为核心硬件,为后续的扩展和功能实现提供了物理支撑。量身定制了一块扩展板,能够扩展开发板的功能,例如增加更多的接口或者特定的硬件模块。传感器可以通过灵活的组装方式结合到ev_board上,使得ev_board成为了连接Grove传感器生态的终端,构成了一个完整的、基于Grove传感器的生态系统。成为了名副其实的 “物联网平台”,项目依托 “VSCode”(Visual Studio Code)和 “IDF”(ESP - IDF,即Espressif IoT Development Framework)编程环境,实现多种物联网功能的应用及展示。
三、各部分功能说明
(1)UI-框架(esp-brookesia)运行方式
ESP-Brookesia 是一个面向物联网设备的人机交互开发框架,基于 LVGL 构建,旨在简化用户在不同尺寸与形状屏幕上的 UI 设计及应用开发流程,该框架内置多种标准化系统 UI 和应用管理机制,允许用户灵活地修改样式、添加或删除应用 UI。ESP-Brookesia已实现所有系统 UI 统一的核心逻辑,包括 App 管理、样式表管理、事件管理等。封装了系统 UI 的通用控件,包括状态栏、导航栏、手势等。
1. Status Bar
Status Bar 用于显示时间、电量、WiFi 状态以及 App 指定图标,位于屏幕顶部,最多 6 张状态图标,并且支持自适应缩放,允许使用不同于样式表大小的图片。允许设置系统时间,格式为 HH:MM AM/PM。允许设置电量状态,包含百分比和状态图标。允许设置 WiFi 连接状态,包含状态图标。(目前时间是可以随网络同步,WIFI和电量只是装饰,因为就没有电池)
2. App Launcher
App Launcher 用于显示所有已安装 App 的图标,运行效果如下图所示,位于屏幕中间。每个 App 用一个图标展示,并且支持自适应缩放,允许使用不同于样式表大小的图片(你的图标小了,它会帮你放大,所有你想图标那么粗蛮,可以适当的留够透明的空白边距)。多页显示:通过左右滑动切换页面。页面指示器位于控件的底部,指示当前页面的位置。跟你手中的手机一毛一样。
3. Navigation Bar
提供导航按键,位于屏幕底部,导航按键:提供 "后退"、"主页"、"概览屏幕" 三种按键,通过点击控制界面切换。动态调整:支持通过样式表参数调整按键位置顺序。
4. Recents Screen
Recents Screen 用于显示正在运行中的 App,位于屏幕中间。可以显示剩余和总存储空间大小。显示当前后台 App 的 GUI 截图,并且支持自适应缩放。通过左右滑动切换 App,上下滑动清理后台 App。一键清理:支持一键清理所有后台 App。
(2)Brookesia程序移植方法
首先你得搭建好IDF编程环境(IDF编程环境不在本编内容讨论范围,反正每个人的感受和经历不一样,就像大家的成长路线不一样。),然后在乐鑫github,拉取esp-brookesia库内容,库包里面包含了Arduino和IDF的example,经过测试针对EV_broad,Arduino环境的屏幕驱动得不够完善,会出现屏幕诡异的划出边界,可能跟屏幕的触摸驱动和其他传感器共用I2C数据接口,它们之间会互相影响),在IDF环境下,这种情况可以有效地避免,为了追求极致流畅效果还是强忍痛苦,把这个坑继续挖下去。 针对性的我们打开esp_brookesia_phone_s3_lcd_ev_board文件夹,进行探索性测试。程序里面已经配置好了ev_board的屏幕驱动等硬件的基础实施,我们专注于我们的上层建筑即可。成功跑通代码后,我们就可以体验新鲜出炉的phone-UI, 但是一直寄人篱下也不是办法, 我们需要把这个文件拷贝出去,简历自己的项目,但从这里迁移出去后,依赖关系就发生了变化,也是一顿恶整呀。
通过修改main文件夹里的CMakeLists.txt文件(具体修改方案参见附件资料),最终完成的文件系统布局如下图所示。app里面放的些自己搭建的应用和附加增值驱动,phone里面放着的才是brookesia的APP程序系统。
第一步:框架修改
根据example里面的3个app(simple_conf,complex_conf,squareline)的运作方式来移植我们现有的lvgl程序。 顾名思义,simple_conf的设置方法最简单,后续所有APP都按这个套路进行改造,complex_conf的设置更详细些,自然修改的功能更加个性化,而squareline提供一个移植squareline项目的参考。 在simple_conf 文件里我们对应地把四个配置文件分别是: phone_app_simple_conf.hpp,phone_app_simple_conf.cpp,phone_app_simple_conf_main.h,phone_app_simple_conf_main.c 对应的修改成自己程序的名字,如flygame_conf_XXX.XXX,这样在对应的文件中引用也要修改,那么我们最好使用全部替换的手法来处理,避免无休止的报错,影响心情。
第二部:内容移植
而同级别目录下的UI文件夹放置我们的lvgl程序。这些lvgl需要归纳为一个入口函数来加载lvgl图形界面内容。 lvgl程序我们在别的地方测试好了再往这里搬,不适合在这个地方开启原始的建造工程,但是移植的工作难点却变成了修复一些由于小问题,消耗了大量时间。
第三步:修改图标
最后的步骤是修改APP在主界面显示的图标,在前面移植过程中采用默认图标即可,最后项目定型后统一更换,参见“APP图标修改指引”(修改桌面墙纸,在后面在补充)。
小结:
以上就是移植1个APP所需要开展的工作,开始的时候,觉得很繁琐,多整几个之后,这种“症状”得到缓解,成就感慢慢地飙升,于是有了前面的剧情,我把“大老虎爱3D”的lvgl最新杰作都搬了过来,这里感谢B站up主”大老虎爱3D”的无私奉献,他开创了使用lvgl开发小游戏的先河,他在lvgl动画上(animation控件)已达到了出神入化的境界,因此,一般的mcu也被折腾得上气不接下气,我们共同期待大老虎可以出品更多大众喜爱的小游戏,让我们lvgl朋友圈更加繁荣昌盛。
另外,这种移植方式是是最简单无脑的,但是文件嵌套有些多,是肯定可以继续优化的。按照,以上套路还移植了一些有用好玩的小程序,例如从esp32P4demo里拿过来的计算器小程序就优化得很成功,可以说是这些程序中最实用的一个,值得深入学习挖掘。
APP文件系统推荐布局指引
移植工作目录指引
APP图标修改指引
(3)扩展板设计:
精彩过后回到我们的主线任务,回应本次大赛的“万物互联”主题上,本次任务为ev_board做了一个扩展板,由于ev_board上的管脚资源几乎被RGB屏幕挤占得一个不剩了,还是在加了管脚扩充IC的情况下。在ev_board上11pin双排的扩充管脚上,能用的只有I2C和一路UART(跟USB接口共用)可以使用了。 所以,本次设计的扩展板就从扩展引脚引出I2C和UART接口,接口制式如前所述(grove&qwiic),加了一个bme280温度度传感器。 下图是扩展板设计图(资源一并放在附件包里头)。由于前期收藏了很多M5Stack的传感器,例如声音、光照、温湿度、二氧化碳、热成像、MIDI驱动器,这些传感器大多都是通过I2C和esp32连接的,因此这些传感器全都可以蹭上ev_board的高清屏幕。 日后可以慢慢的把这些传感器都排上用场。 这次一同购买的Adafruit 公司的SGP30 传感器是使用QWIIC接口连接的,但由于跟ev_board内部的I2C原件地址冲突,没有成功驱动,也是巧了。
PCB图案寓意:PCB布局上的卡通图案看似简单其实一点也不复杂,正面有个卡通熊猫,相传熊猫能够保佑我们的程序猿的代码稳定不bug。还有乐鑫的Logo 致敬一下这个伟大的公司。顶部的一些小图案代表了该扩展板集成了相应的硬件功能,紧扣“万物互联”的主题。背面除了些小元件之外,巧妙地利用穿孔构造了一只猫的形状,猫在人们心目中代表“灵动”与“智慧”,跟我们程序猿大型交友社区的图案相似,也是巧合了。
实物对照图
(4)程序主界面
到了最后一部分了,完成了一个多功能的控制面板,主要工作内容是ev_board的example的智能面板提取了一个页面来完成联网、天气预报、时钟、BME280温湿度传感器的数据展示。 构建 bme280_sensor类完成传感器的初始化、定时数据更新、获取当前数据等操作。
(1)BMX280 传感器的业务逻辑
使用了 `BMX280Sensor` 这个结构体来封装 BME280(或 BMX280)传感器的初始化、数据更新、数据获取等功能。整体设计考虑了传感器的初始化、数据更新以及线程安全的问题,并将数据更新和获取逻辑封装成了多个函数和任务,使得系统可以高效、稳定地工作。
1. 传感器初始化:通过 `BMX280Sensor_create` 创建传感器对象,并进行初始化配置,包括 I2C 配置和传感器初始化。
2. 数据更新:通过一个 FreeRTOS 任务(`updateTask`)每秒定时更新传感器数据,读取传感器的温度、湿度和气压值,并通过互斥锁确保数据更新的线程安全。
3. 数据获取:通过 `BMX280Sensor_getData` 和 `BMX280Sensor_getCurrentInfo` 获取当前的传感器数据。
下面将逐个分析代码中的业务逻辑。
1. 传感器创建与初始化
BMX280Sensor* BMX280Sensor_create(i2c_port_t port, bool initialized) {
BMX280Sensor* sensor = (BMX280Sensor*) malloc(sizeof(BMX280Sensor));
if (sensor == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for BMX280Sensor");
return NULL;
}
sensor->i2c_port = port;
sensor->bmx280 = NULL;
sensor->dataMutex = xSemaphoreCreateMutex();
if (sensor->dataMutex == NULL) {
ESP_LOGE(TAG, "Failed to create mutex");
free(sensor);
return NULL;
}
if (initialized) {
i2c_config_t i2c_cfg = {
.mode = I2C_MODE_MASTER,
.sda_io_num = BSP_I2C_SDA_R16,
.scl_io_num = BSP_I2C_SCL_R16,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000
};
ESP_ERROR_CHECK(i2c_param_config(port, &i2c_cfg));
ESP_ERROR_CHECK(i2c_driver_install(port, I2C_MODE_MASTER, 0, 0, 0));
}
if (BMX280Sensor_init(sensor) != ESP_OK) {
ESP_LOGE(TAG, "Sensor initialization failed");
BMX280Sensor_destroy(sensor);
return NULL;
}
BMX280Sensor_startUpdating(sensor); // 启动数据更新任务
return sensor;
}
首先使用 `malloc` 动态分配内存来创建 `BMX280Sensor` 对象。 I2C 配置:如果 `initialized` 为 `true`,则初始化 I2C 总线。但因为传感器跟屏幕的共用I2C,在板卡的bsp中已经驱动好I2C了,所以这里的初始化只有在测试阶段需要用到。 为了保持库类的完整性,继续保留了这些内容。 调用 `BMX280Sensor_init` 函数初始化传感器,如果初始化失败则销毁并释放传感器对象。初始化完成后,调用 `BMX280Sensor_startUpdating` 启动数据更新任务(通过任务创建)。通过 `xSemaphoreCreateMutex` 创建互斥锁,保证数据读取和写入的线程安全。
2. 销毁传感器对象
```c
void BMX280Sensor_destroy(BMX280Sensor* sensor) {
if (sensor != NULL) {
if (sensor->dataMutex) {
vSemaphoreDelete(sensor->dataMutex);
}
free(sensor);
}
}
```
- 资源释放:销毁传感器对象时,首先释放互斥锁,然后释放分配的内存。
3. 启动数据更新任务
void updateTask(void* param) {
// 将传入的参数(指针)转换为 BMX280Sensor 类型
BMX280Sensor* sensor = (BMX280Sensor*) param;
// 无限循环,定期更新传感器数据
while (1) {
// 定义一个用于存储传感器数据的结构体
SensorData_t data;
// 设置传感器为强制模式 (BMX280_MODE_FORCE),此模式下传感器会一次性进行测量并返回结果
if (bmx280_setMode(sensor->bmx280, BMX280_MODE_FORCE) == ESP_OK) {
// 在传感器采样时,任务延时 1 毫秒,并检查传感器是否完成采样
// 当采样完成后,跳出此循环
do {
vTaskDelay(pdMS_TO_TICKS(1)); // 延时 1 毫秒,等待传感器完成采样
} while (bmx280_isSampling(sensor->bmx280)); // 判断传感器是否在采样中
// 采样完成,读取传感器的数据 (温度、压力、湿度)
if (bmx280_readoutFloat(sensor->bmx280, &data.temperature, &data.pressure, &data.humidity) == ESP_OK) {
// 获取互斥锁,确保当前任务独占传感器数据
if (xSemaphoreTake(sensor->dataMutex, portMAX_DELAY)) {
// 将获取到的传感器数据存储到 BMX280Sensor 对象的 currentData 字段中
sensor->currentData = data;
// 释放互斥锁,允许其他任务访问传感器数据
xSemaphoreGive(sensor->dataMutex);
}
}
}
}
}
```c
void BMX280Sensor_startUpdating(BMX280Sensor* sensor) {
xTaskCreate(updateTask, "bmx280_update_task", 2048, sensor, 5, NULL);
}
```
数据更新:这个任务每 1 秒钟更新一次传感器数据。流程如下:
设置强制模式:使用 `bmx280_setMode(sensor->bmx280, BMX280_MODE_FORCE)` 设置传感器进入强制模式(每次读取时采样一次)。
等待采样完成:使用 `vTaskDelay(pdMS_TO_TICKS(1))` 等待 1 毫秒,循环检测 `bmx280_isSampling`,确保数据采样完成。
读取数据:使用 `bmx280_readoutFloat` 读取温度、湿度和气压数据。
数据保护:通过互斥锁 `xSemaphoreTake` 和 `xSemaphoreGive` 来保护共享数据 `currentData`,确保数据的安全性。
5. 获取当前传感器数据
```c
esp_err_t BMX280Sensor_getData(BMX280Sensor* sensor, SensorData_t* data) {
if (xSemaphoreTake(sensor->dataMutex, portMAX_DELAY)) {
*data = sensor->currentData;
xSemaphoreGive(sensor->dataMutex);
return ESP_OK;
}
return ESP_FAIL;
}
```
```c
esp_err_t BMX280Sensor_getCurrentInfo(BMX280Sensor* sensor, SensorData_t* info) {
if (info == NULL) {
ESP_LOGE(TAG, "Invalid argument: info is NULL");
return ESP_ERR_INVALID_ARG;
}
if (sensor->currentData.temperature != 0 || sensor->currentData.pressure != 0 || sensor->currentData.humidity != 0) {
memcpy(info, &sensor->currentData, sizeof(SensorData_t));
return ESP_OK;
}
return ESP_FAIL;
}
```
提供了两个传感器数据获取的函数,可以任选其一,其中加了检查数据有效性,这两个函数是对外联系的窗口,数据的更新自动在后台完成。在天气页面只需要调用获取数据函数即可,实现了底层与上层的轻度解耦。
6.函数调用
最后在时钟主页的lvgl版面设计中调用获取数据函数,与天气预报、时钟代码一起打包,按照APP的生成方法,建立一个独立的APP。
static void update_bme_data(void *arg)
{
// 定义存储传感器数据的结构体
SensorData_t data;
// 获取传感器数据
esp_err_t err = BMX280Sensor_getData(sensor, &data);
// 检查是否成功获取数据
if (err == ESP_OK)
{
// 输出到控制台调试信息
printf("Temperature: %.2f °C\n", data.temperature);
printf("Pressure: %.2f hPa\n", data.pressure / 1000); // 转换为 hPa 后输出
printf("Humidity: %.2f %%\n", data.humidity);
// 格式化并更新温度标签
char temp_str[32];
snprintf(temp_str, sizeof(temp_str), "%.2f℃", data.temperature); // 格式化温度数据并加入单位
lv_label_set_text(bme_temp_label, temp_str); // 设置温度标签的文本
// 格式化并更新湿度标签
snprintf(temp_str, sizeof(temp_str), "%.2f%%", data.humidity); // 格式化湿度数据并加入单位
lv_label_set_text(bme_humi_label, temp_str); // 设置湿度标签的文本
// 格式化并更新气压标签(将Pa转换为hPa)
snprintf(temp_str, sizeof(temp_str), "%.1f", data.pressure / 1000); // 格式化压力数据并转换为 hPa
lv_label_set_text(bme_press_label, temp_str); // 设置气压标签的文本
}
else
{
// 获取数据失败,输出错误信息
printf("Failed to get sensor data.\n");
}
}
PS:最后一个小问题,如何更换壁纸
跟其他电子产品一样,大家都热衷于更换壁纸,brookesia的墙纸是放在依赖里面的,根据自己屏幕的尺寸,找到对应的图片资源替换成自己的墙纸即可。
修改墙纸
components\esp-brookesia\src\systems\phone\stylesheets\480_480\assets\wallpaer
四、作品源码
项目完整代码地址:
https://download.eeworld.com.cn/detail/genvex/635160
扩展板PCB资料地址:
https://download.eeworld.com.cn/detail/genvex/635123
项目开箱贴:
https://bbs.eeworld.com.cn/thread-1296131-1-1.html
五、作品功能演示视频
https://training.eeworld.com.cn/video/42202
六、项目总结
1. 乐鑫UI框架的应用
本项目采用ESP - Brookesia框架,基于LVGL构建,简化了不同尺寸与形状屏幕的UI设计及应用开发流程,实现了系统UI统一的核心逻辑,该框架类似手机APP运行界面和方式,每个程序独立管理互不影响,在mcu频率提升背景下,极有可能物联网设备UI交互发方向,至少在今后的几年会成为比较流行的UI运行方式。
2. 多合一游戏移植
将B站流传的多个成功lvgl游戏(如羊了羊、消消乐、打砖块、植物大战僵尸等)移植到ESP32 - S3 - LCD - EV - Board上,为该平台增添了娱乐功能,丰富了其应用场景,在物联网设备上实现多种游戏功能的整合,充分体现了IDF框架的优越性,这些包含大量动画的游戏基本都可以正常运行起来,在其他环境单次只能跑通一个游戏。@老虎爱3D 大佬从睡梦中惊醒,头一回有人把的全部心血给一个锅端了。
3. 创意扩展板设计
针对EV - Board管脚资源紧张情况,设计扩展板从有限可用管脚引出特定接口(I2C和UART接口,采用grove&qwiic接口制式),并添加BME280温度传感器。这一设计使平台能无缝连接M5Stack和Seeed等公司的众多传感器,同时尝试连接国外Adafruit公司传感器,极大拓展了平台的感知能力和外设兼容性,仅以本次活动为契机,开拓了万物互联的可能性。
4. 多功能主界面设计
在程序主界面实现了联网、天气预报、时钟、BME280温湿度传感器数据展示等多功能整合。通过构建特定类和函数来管理传感器数据,在数据更新和获取过程中考虑线程安全问题,将数据更新逻辑封装成任务并利用互斥锁保护共享数据,同时实现了底层与上层的轻度解耦,方便功能调用和扩展,实现了多模块功能融合与优化。
通过这些创新实践,ESP32-S3-LCD-EV-Board不仅提升了用户体验,还增强了物联网设备的功能性和互动性,展示了其在多功能物联网解决方案平台构建中的潜力和应用前景。由于收到板卡比起其他同学几乎晚了半个月,在一个月追星赶月勤奋学习,在其他传感还没有用起来,后面有空了继续搞起来。
鸣谢:
最后感谢梅尧大佬在扩展板pcb设计的指导与帮助。
感谢重阳老师在bme280驱动与功能融合反面提供的指导和帮助。
感谢羊毛党一众党员不分昼夜的技术支撑。
感谢主办方提供深入学习的机会,不离不弃地督促我的学习进展@lightxixi ,让我把这个IDF项目硬啃下来了。
七、其他
在wifi_setting.c文件里修改自己的wifi信息,没有Bme280传感器可能无法开机,在IDF5.1-IDF5.3(不包括5.3)下运行。
- 2024-11-26
-
上传了资料:
基于ESP32-S3-LCD-EV-Board的物联网多功能平台扩展板
-
上传了资料:
基于ESP32-S3-LCD-EV-Board的物联网多功能平台
- 2024-11-05
-
回复了主题帖:
【2024 DigiKey 创意大赛】+功能
流弊流弊
- 2024-10-25
-
回复了主题帖:
【获奖名单】艾迈斯欧司朗高效能源存储:工业级数据采集前端集成电路产品分享 直播
祝贺我中了30软妹币
- 2024-10-15
-
发表了主题帖:
【2024 DigiKey 创意大赛】物料开箱--跑通所有官方例程
本帖最后由 genvex 于 2024-10-15 11:27 编辑
ESP32S3_EV_BOARD开箱
最后时刻搭上来年度创意大赛的末班车,拿下来心心念念的 ESP32S3_EV_BOARD。鉴于上一年的翻车事件,今年低调很多,再也不敢开那些天马行空的题目了,低调的做一些物联网项目,目标是力保不翻车。
虽然迟到了一个月,基础学习不能少。 EV_BOARD开发板上乐鑫官方推出为S3性能背书的产品,展示了S3在物联网、人机交互、视频语音方面的潜力。这些相关的Demo在官方网站上可以轻松获取,但全部正常跑通的人不多,原因是Espressif IDE更新频次很高,,而且版本众多,现在一个月可能有有一个新版本号了,然后Demo制作当时的版本已经是半年以前的了,然后版本的前后兼容性不一致,有些程序使用新版的支持库也是可以正常运行的,但多数情况下,是会出现各种问题的,一般人不好解决,入手板卡两周时间都在跑demo,这两天才把所有Demo成功运行,迈出了重要的一步。
一、板卡固定螺丝升级
板卡原来的固定螺丝是塑料的,而且上层屏幕,为了方便拆卸,甚至4颗螺丝位,有两个是空的,导致板卡比较松垮,在我这里这种情况是不允许发生的。 塑料M3柱子全更换成铜柱(1cm),M3螺丝更换成不锈钢内6角螺丝。 这样下来整个板卡拿在手上就比较稳当了
二、跑通86box_smart_panel 例程
该例程是EV—BOARD最为完善的例程,而且完美适配480*480屏幕。其他的demo是位800*480屏幕共用或不兼用的demo. 基本上跑通这个demo认真学习这个案例的话,叫做对ev_board的性能有所了解了吧。
三、 usb_camera_lcd
为了跑这个案例重新购买了扩展接口和摄像头,学习的诚意也是够够的。
四、小结:
其他的案例就不一一展示了,踩坑的苦痛只有经历过的人才能感受。 把目前的主流的几个支持库版本都测试过了,得出结论是5.1.2和5.2.0 版本运行比较顺畅,其他版本能够编译并能上传运行,就是触摸不准,出现莫名其妙的现象,如有知道原因的朋友,请赐教。
(请继续关注,看看楼主今年会不会翻车)
联排别墅!
- 2024-09-15
-
加入了学习《FollowMe 第二季:2 - Arduino UNO R4 Wi-Fi 及任务讲解》,观看 Arduino UNO R4 Wi-Fi 及任务讲解
- 2024-09-14
-
回复了主题帖:
【2024 DigiKey创意大赛】+我的自动浇水装置+开箱贴
霸总浇花系统
- 2024-09-12
-
回复了主题帖:
【Follow me第二季第2期】+ 基础任务【驱动LED矩阵+DAC正弦波+放大信号+ADC数据采集】
服气,没有点动手能力还完成不了这个任务
-
回复了主题帖:
【Follow me第二季第2期】扩展任务二:通过外部SHT40温湿度传感器,上传温湿度到HA.
- 2024-09-01
-
加入了学习《【Follow me第二季第1期】+ CPE基于VS-CODE PIO的都功能挂饰开发》,观看 【Follow me第二季第1期】+ CPE基于VS-CODE PIO的都功能挂饰开发
-
加入了学习《【Follow me第二季第1期】全部任务演示短视频》,观看 【Follow me第二季第1期】任务演示视频
-
上传了资料:
【Follow me第二季第1期】+ CPE基于VS-CODE PIO的都功能挂饰开发 源码