genvex

  • 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的都功能挂饰开发 源码

  • 发表了主题帖: 【Follow me第二季第1期】+ CPE基于VS-CODE PIO的多功能挂饰开发(完整项目) 

    本帖最后由 genvex 于 2024-9-3 10:57 编辑       本项目将包括以下内容: 入门任务、开发环境搭建   VS-CODE -platformIO 编程环境(有坑),入门任务很高阶。 任务一、炸街灯光秀 多种炫酷灯光效果随意切换 任务二、环境温度和光线强度监测    光温效果立竿见影 任务三、谜之接近检测 红外传感器的逆袭  进阶任务、不倒翁优雅实现 Atan2关键函数深度解析 创意任务、精彩挂饰 灯光秀+midi音乐播放器(助攻生日趴)    项目简介    Circuit Playground Express(下文简称CPE)基于ATSAMD21微控制器,采用32位ARM® Cortex®-M0+内核。ATSAMD21采用先进的电源管理技术,电流消耗极低。它可以由USB、“AAA”电池组或Lipoly电池供电。可通过内置USB快速连接进行编程,无需专用电缆或适配器。非常适合用于电子产品和编程,能够让创意充分发挥。    本项目基于CPE的功能挂饰开发,包括编程环境搭建、多个任务的实现等内容。在编程环境搭建中,通过解决初始代码编译报错问题,最终在VSCODE的platformIO平台上成功为CPE新建项目。在任务实现方面,完成了炸街灯光秀、环境温度和光线强度监测、谜之接近检测、不倒翁优雅实现以及精彩挂饰等任务,如利用灯珠实现多种灯光效果切换、监测环境数据并在灯珠上显示、通过红外脉冲判断物体接近、感受重心变化实现不倒翁功能以及融合midi播放和灯光秀等。 全部任务内容在一个套代码中实现,无需分次上传。    如果不想看后面详细展开看以下精简版内容也可以获取本文主要核心内容:   本项基于VS - CODE PIO的Circuit Playground Express(CPE)多功能挂饰开发项目, 具体内容包括: 1. 项目简介: • CPE基于ATSAMD21微控制器,采用32位ARM Cortex - M0 +内核,可由多种方式供电,适合电子产品和编程。 • 项目包括编程环境搭建和多个任务的实现,全部任务内容在一个套代码中实现。 2. 编程环境搭建: • CPE支持多种编程环境,本项目选择在VSCODE的platformIO平台上搭建,但初始代码编译报错。 • 通过对比Seeed Xiao ATSAMD21项目,发现是vscode - PIO提供的Adafruit TinyUSB Library有问题,最终通过替换Seeed的TinyUSB驱动解决。 • CPE在该编程环境下编译上传速度快。 3. 任务实现: • 灯光秀:利用CPE上的10颗灯珠实现多种炫酷灯光效果切换,通过函数数组和“RainbowCycleDemo”类实现灯光模式切换。 • 环境温度和光线强度监测:光线强度传感器连接到模拟引脚A8,温度传感器使用NTC热敏电阻器,通过复杂的计算公式获取温度,将光照强度和温度数值映射到灯珠上进行简易显示。 • 接近检测:通过sendIRPulse()函数发送红外脉冲,根据A10的模拟量判断物体接近,实现距离检测和相应的显示及警报功能。 • 进阶任务、不倒翁实现:用CPE感受重心变化,通过螺丝孔和龟苓膏塑料盖等制作不倒翁,核心关键代码中使用atan2函数计算极角,解决了atan在x值接近0时不稳定的问题。 • 创意任务、精彩挂饰:实现midi音乐播放器和灯光秀的融合,对midi音频数据序列进行解码,根据音符字符串播放对应的音符。 4. 心得体会: • 项目开始觉得简单,实际很有趣,得益于充分的调研和CPE的配置提升。 • 用类把所有任务内容用一套代码实现,按键B实现功能切换,按键A实现功能内部模式切换,通过时间判断实现midi播放和灯光秀同时进行。 • CPE背后Adafruit团队的板卡支持库和案例通俗易懂。 5. 不足之处:篇幅有限,不能呈现实验过程细节,可下载代码包中的免编译固件进行体验测试。 代码包中有firmware.bin固件,可双击reset键进入bootloader模式,将firmware.bin文件放在新弹出的虚拟优盘上验证功能。 参考资料包括多个相关网站链接。   谢谢,阅读!     一、编程环境搭建    CPE当前支持通过Microsoft MakeCode,circuitpython,Arduino等编程环境开发。 MakeCode适合儿童编程启蒙课程,circuitpython是Adafruit公司主打的编程生态,非常完善和强大,很多功能都封装好,入门学习比较顺滑。Arduino 依仗全球的创客生态,只有你没想到的,没有你做不到的。 因为Circuit Playground Express采用的ATSAMD21微控制器也是一款成熟的产品了,跟国内的ESP32同台竞技好多年,在VSCODE上的platformIO平台上肯定有支持。PIO的底层还是Arduino,但是继承了VSCODE所有资产,做一些复杂的内容也可以从容应对。 根据Circuit Playground Express关键词就可以在PIO上新建一个项目。       如果不出意外,肯定出意外了。 (见下图)初始代码编译测试就报错,百思不得其姐,差点就放弃了。经过几天休养,灵光一动,采用同样芯片的Seeed Xiao ATSAMD21肯定是好用的吧,这么多人用,CPE 是几乎没有人用PIO来玩的,所以有bug也没人发现。于是新建了基于Seeed Xiao ATSAMD21项目,编译顺利通过,但demo代码上传到CPE,却没有反应,这也正常呀,Xiao的引脚跟CPE的管脚设置是不同的。 但给到了一个出路,就是Xiao的支持环境是正常的。     后来根据报错提示,最终确认是vscode -PIO 提供的Adafruit TinyUSB Library有问题(可能是一些低级的错误)用Xiao的支持库所以把Seeed的TinyUSB 驱动拿过来放到对应的目录上,借尸还魂,可以完美解决。还想去给PIO提个意见那啥的,算了,作业都还没完成。 (替换文件夹在附件链接细找) 不足1秒就完成编译上传了,ESP32什么时候享受过这样的待遇,太快了。   任务一、炸街灯光秀     CPE上有10颗灯珠,开始的时候对它还是挺嫌弃的,怎么不搞12颗呢,后来发现就这么10颗灯珠就可以玩出花来了,可以做出很多炫酷的灯效。 下面先来看看灯光秀。    先用一个函数数组灯光效果函数放进去,就像有彩虹灯效、旋转灯效、闪光灯等函数) 构造了“RainbowCycleDemo”类,这个类继承自“Demo”类。“loop”方法能在程序的主循环中频繁被调用。然后,根据“mode”的值,从“ligthList”数组中选择对应的灯光类型函数并调用它,从而实现不同灯光效果的切换展示。“modePress”方法则最终被按键A调用,实现了灯光模式内循环切换。   任务二、环境温度和光线强度监测       光线强度传感器连接到模拟引脚A8。在Arduino编程环境取值在0和1023之间,对于大多数室内光线水平来说,大约300的读数很常见。 温度传感器使用NTC热敏电阻器(村田NCP15XH103F03RC)。虽然它不是全集成温度传感器,但具有线性输出,但可以根据模拟引脚#A9的模拟电压轻松计算出温度。就是数值计算起来比较复杂。温度计算公式: float Adafruit_CircuitPlayground::temperature(void) {   // Thermistor test   double reading;   reading = analogRead(CPLAY_THERMISTORPIN);   // Serial.print("Thermistor reading: "); Serial.println(reading);   // convert the value to resistance   reading = ((1023.0 * SERIESRESISTOR) / reading);   reading -= SERIESRESISTOR;   // Serial.print("Thermistor resistance: "); Serial.println(reading);   double steinhart;   steinhart = reading / THERMISTORNOMINAL;          // (R/Ro)   steinhart = log(steinhart);                       // ln(R/Ro)   steinhart /= BCOEFFICIENT;                        // 1/B * ln(R/Ro)   steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To)   steinhart = 1.0 / steinhart;                      // Invert   steinhart -= 273.15;                              // convert to C   return steinhart; }      我们需要做的主要工作是将光照强度和温度数值映射到灯珠上进行简易显示。      首先,通过CircuitPlayground.lightSensor()获取当前的光照强度值,并存储在变量light中。然后,使用lerp的线性插值函数,把光照量程映射 9号灯珠(初始值)和 5号灯珠(结束值),灯光颜色选择为蓝色。     根据温度设置左侧像素点颜色:类似地,通过CircuitPlayground.temperature()获取当前温度值,并存储在变量tempC中。再次使用lerp函数根据温度值tempC、温度量程映射到映射5号灯珠(初始值)和 0号灯珠(结束值),温度演示颜色选择红色。   任务三、谜之接近检测     void sendIRPulse() {     for (int i = 0; i < 32; i++)     {         digitalWrite(irTransmitterPin, HIGH);         delayMicroseconds(13);         digitalWrite(irTransmitterPin, LOW);         delayMicroseconds(13);     } } void displayDistance(int distance) { int ledCount = map(distance, minDistance, safeDistance, 0, 9); // 将距离值映射到0-10的LED数量 Serial.print("Distance: "); Serial.print(distance); Serial.print(", LED Count: "); Serial.println(ledCount); for (int i = 0; i < 10; i++) { if (i < ledCount) { CircuitPlayground.setPixelColor(i, 0, 255, 0); } else { CircuitPlayground.setPixelColor(i, 0); } } } void checkForIntrusion(int distance) { if (distance > SAFE_DISTANCE) { Serial.println("Intrusion detected!"); playAlertTone(); } } inline void looping() { if (millis() - timeElapsed > 300) { sendIRPulse(); // CircuitPlayground.irSend.send(MY_PROTOCOL,MY_POWER,MY_BITS); distance = analogRead(irReceiverPin); // 读取红外传感器的值 if (firstFlag) { minDistance = distance; firstFlag = false; safeDistance = minDistance + 50; } Serial.print("Received distance: "); Serial.println(distance); displayDistance(distance); checkForIntrusion(distance); timeElapsed = millis(); } }       这个实验成果与否就取决于这个函数,在读取说明书上的接近传感器引脚的数值之前必须进行的操作,尝试过其他发射方式不成功。 sendIRPulse()函数每次调用时生成一系列32个红外脉冲,每个脉冲持续26微秒,具有50%的占空比(13微秒高电平,13微秒低电平)。通过红外脉冲实现测距的原理主要基于红外线的发射和接收之间的时间差。这种方法通常称为脉冲时间飞行法(Time of Flight, ToF)。然而这里我们需要根据A10的模拟量来大约的判断有物体接近,这不是一种很常见的用法,都是玩具,我觉得也不一定要这么较真呀。 (彩蛋,当我们把手靠近CPE的时候,蜂鸣器会发出biubiu的声音,还会喊“danger,danger”)      进阶任务、不倒翁优雅实现    不倒翁的意思是用CPE来感受重心的变化,利用10颗灯珠通过重心极角指映射到对应点亮灯珠。   不倒翁制作过程    得益于塑壳背后别有用心的预留了一个M4还是多少的螺丝孔,这个穿孔所用螺丝比较容易获取,例如相机底座的固定螺丝也是同款的。在龟苓膏塑料盖中心开一孔让螺丝穿过,边缘也开一个小孔让电池线头穿过,不要太靠塑料盖边缘,这样不美观。然后电池舱藏在龟苓膏罐里头,用些纸皮固定,由于电池比较重,固定电池的位置比其他地方重,造成了重心偏离中心的情况,正好符合不倒翁的工作模式。       virtual void loop() {      static float X, Y, Z;   X = CircuitPlayground.motionX();   Y = CircuitPlayground.motionY();   Z = CircuitPlayground.motionZ();   static int old_x, old_y;   /*滤波*/ //一阶低通滤波器,减少灯光因为加速度传感器的漂移产出的闪烁。   X = old_x * 0.6 + X * 0.4;   old_x = X;   Y = old_y * 0.6 + Y * 0.4;   old_y = Y;   Serial.print("X: "); // 左右倾斜是 x   Serial.print(X);   Serial.print("  Y: "); // 前后倾斜是 y   Serial.print(Y);   Serial.print("  Z: "); // 水平轴向;   Serial.println(Z); // atan2函数返回的角度范围是-π到π,通过乘以180/PI转换为度,并加上180,使得角度范围变为0到360。 // map函数用于将角度映射到LED索引,确保角度和LED索引之间的对应关系是线性的。   float theta = atan2(Y, X) * 180 / PI + 180; // originally the value range is -pi - pi, add 180 for easy understand.   // in order to understand the relationship between the angle and lights ,we need to watch the change of the r and thera.   Serial.print("theta:");   Serial.println(theta);   int led_point2 = map(theta, 0, 360, 7, 17); // the zero point the Led 7, in order to map the numbers of the leds,7+10 =17 ,return the zero point.   CircuitPlayground.clearPixels();            // Set all pixel colors to 'off'   uint32_t offset = millis() / 100;//等过的渐变速度以100毫秒为单位发生变化。   for (int i = 0; i < 3; i++)   {     CircuitPlayground.strip.setPixelColor((led_point2 + i) % 10, CircuitPlayground.colorWheel((((led_point2 + i) * 256 / 10) + offset) & 255));   }   CircuitPlayground.strip.show();   delay(10);   }   核心关键代码    float theta = atan2(y_,x_ )*180/PI+180;  // 计算极角,还原到0-360便于理解。在完成这个实验,我认为最精彩的部分莫过于使用了使用 atan2这个函数可以完美解决atan在x值接近0时不稳定的现象,数学还得好好学。 atan2是功能解析: 一、明确的参数顺序和结果范围 参数顺序明确:atan2(y, x)接收两个参数,先指定纵坐标的值y,再指定横坐标的值x。这种明确的参数顺序使得在调用函数时不容易出错,并且在复杂的计算中更容易理解和追踪参数的来源。 结果范围清晰:该函数返回值的范围是从 -π 到 π。这个固定的范围使得结果具有确定性,方便进行后续的角度计算和处理。无论是在平面几何、图形绘制还是物理模拟等领域,都能准确地确定角度,避免了因角度范围不明确而导致的错误。 二、处理特殊情况准确 处理零值情况:当 x 为零且 y 为正时,atan2 返回 π/2;当 x 为零且 y 为负时,返回 -π/2。这种准确的处理方式在涉及到垂直方向的角度计算时非常有用,避免了因分母为零而产生的计算错误。 处理坐标轴上的情况:当 y 为零时,根据 x 的正负返回 0 或 ±π。这使得在处理与坐标轴平行的情况时,能够准确地确定角度,为图形绘制、向量分析等提供了准确的角度信息。 三、在二维向量和坐标计算中表现出色 二维向量的角度计算:在处理二维向量时,atan2 可以方便地计算向量与 x 轴正方向之间的夹角。这在计算机图形学中非常重要,例如在绘制图形时确定物体的方向、进行旋转操作等。通过 atan2 可以快速准确地计算出向量的角度,从而实现各种图形变换和动画效果。       2. 坐标转换和定位:在地理信息系统、游戏开发等领域,经常需要进行坐标转换和定位。atan2 可以用于计算两点之间的方位角,从而确定物体在平面上的相对位置。例如,在游戏中可以根据玩家和目标的坐标计算出玩家面向目标的角度,以便进行角色的转向和攻击。   难点说明:      int led_point2 = map(theta, 0, 360, 7-17); // 为什么映射都(7-17)?因为0度的时候正好是7号的位置,后面超过10点的灯珠我们用取余的方法,让它们又回到了正常的位置,这是没有实验现象辅助会有点难理解。 创意任务、精彩挂饰      实现非阻塞的播放功能和灯光秀功能的完美融合。用一套的非常规播放逻辑来实现midi音符解码。这里提供了几首midi曲目,有我最爱的《我爱和我的祖国》(又红又专),还有这次特别制作的生日歌,可以在小伙伴生日的时候掏出来,活跃气氛,没有蜡烛,灯光秀顶上,临时救个场也不是不可以的。         Midi 曲目的数据格式: const char *my_people_my_country = "#,#,A#5,A#5,A#5,A#5,C6,C6,C6,...#”   每个音符使用逗号进行分隔,接下来,函数会根据设定的节拍时间(音调的播放持续时间)判断是否到达了一个节拍点。如果到达了节拍点,函数会解析字符串中的音符,并播放对应的音符。 音符的识别使用了map容器来快速根据音符字符串查询到对应的音符频率数值,具体实现方式查看源码。 std::map<String, int> tones = { {"C0", 16}, {"C#0", 17}, {"D0", 18},,,,{"A#9", 14917}, {"B9", 15804}};    解析字符串中的音符的逻辑是函数会移动指针到下一个逗号的位置,以准备播放下一个音符。如果还有逗号存在,函数会继续播放下一个音符, 如再未检测到逗号,表明播放结束,歌曲播放结束后会检测是否循环播放。 可用于转换 onlinesequencer.net schematic format 的乐谱 页:onlinesequencer.net     心得体会      本次任务开始的时候觉得项目太简单了,可能学不到啥东西,后来发现这个东西还是非常有趣的。 主要得益于在采购阶段做好了充分的调研工作,买买买我是专业的,给CPE精心配置了一个水晶外壳(塑料的),立马提升了一个档次,灯光秀就更加出彩了,然后花了巨资购买了一条粗犷的挂绳(还是一条typeC充电线)来搭配CPE,这样就可以用一种另类的方式出街啦,成为这条街最亮D仔。 另外,在代码方面,用类把所有以上任务内容用一套代码实现,按键B实现功能切换,按键A实现功能内部模式切换。代码思路是非阻塞式运行(拒绝使用delay),因为简单尝试了freeRTos的移植,好像问题多多,折中通过时间判断完成了midi播放和灯光秀同时进行。 任务中也不乏亮点例如不倒翁功能的实现。   总体来说,任务还是比较轻松愉悦的,这里主要功劳还是CPE背后Adafruit团队的板卡支持库和案例做得非常通俗易懂,没有八股文代码那么晦涩难懂(我没有说idf哈),所以一个板卡成功与否,除了硬件性能,板卡的支持库也是非常重要的。   该项目给今年的夏天留下浓重的色彩,怀着不舍之情,结束了这次活动的学习,让我继续砥砺前行,迎接更加猛烈的风雨!       不足之处就是篇幅有限不能把实验过程的细节一一呈现,喜欢的朋友可以下载代码包中的免编译固件,进行体验测试。    代码包中有firmware.bin 固件,可以验证功能。  而已双击reset键进入bootloader模式,报firmware.bin 文件放在新弹出的虚拟优盘上。  代码包   参考资料: 信息地址 https://github.com/adafruit/Adafruit_CircuitPlayground https://wiki.seeedstudio.com/Seeeduino-XIAO/ https://learn.adafruit.com/adafruit-circuit-playground-express/overview https://learn.adafruit.com/circuit-playground-hourglass/basic-hourglass https://learn.adafruit.com/circuit-playground-bluefruit-brake-light https://learn.adafruit.com/circuit-playground-bike-light

最近访客

< 1/2 >

统计信息

已有11人来访过

  • 芯积分:96
  • 好友:--
  • 主题:8
  • 回复:18

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言