《移动式面粉发酵监测容器的设计》

2026-09-221

作品说明

一、背景分析与设计要求

摘要:本作品面向中学生物"微生物呼吸作用"实验,用行空板 M10 配合 SCI 采集模块,把 pH、二氧化碳、空气温湿度、酒精浓度五路参数集成进一个"笔盒大小"的抽拉式容器里,做到边发酵边监测、数据自动上云。容器内部用激光切割的木板与亚克力板拼成100×100×117mm 的方形发酵仓,顶部扣一块 3D 打印的绿色杯盖(104×104mm,厚 26mm,盖下凸台开 25 个通气孔),仓内埋设 pH 电极、MQ-3 酒精传感器与温湿度探头,盖面嵌行空板屏幕实时显示五路数值,并印出二维码供手机扫码进入 SIoT 平台看折线图。另一路 USB 摄像头把发酵画面转成 base64 数据流推到云平台,实现远程"看画面 + 看曲线"。整机材料清单中 6 项标价合计 1200 元。2025 年 2 月 12 日的一次 3 小时实测中,容器完整记录了酒精值 250→619、CO₂ 873→2333ppm、温度 17.06→21.01℃ 的发酵全过程,验证了"低成本、多参数、可远程"的设计目标。

关键词:面粉发酵监测 物联网(SIoT) 多传感器融合 激光切割 跨学科实践

1.1 作品背景

面粉发酵是食品工业里一道重要的生产环节,通常指小麦等谷物在缺氧条件下进行无氧呼吸的过程——酵母菌把糖类分解,产生二氧化碳和酒精。这个过程在初中生物"微生物的呼吸作用"、高中生物"细胞呼吸"里都是核心实验,学生往往要在课堂上亲自动手做一遍。

但在真实的课堂和家庭场景里,传统做法一直绕不开三个痛点:

  • 数据采集粗放:判断发酵是否到位,主要靠肉眼观察面团体积膨胀、用鼻子闻酒精味,对温度、湿度、气体浓度这些关键参数没有量化记录,学生拿不到可以画图、可以比较的数字;
  • 条件控制困难:自然环境里温湿度波动剧烈,实验过程中很难把条件稳定下来,同一个班不同小组的结果差异极大,很难做"控制变量"式的对照探究;
  • 过程不可逆:发酵一旦开始就无法回退,靠手工定时记录容易漏掉关键节点,事后想回看"第 40 分钟发生了什么",往往只能凭印象描述。

《义务教育生物学课程标准》明确要求培养学生"科学探究"与"跨学科实践"素养,其中就包括"能够使用简单的仪器设备采集数据、分析数据并得出结论"。本项目正是基于这一要求,把物联网技术和传感器技术引入课堂实验,让学生从"看面团"升级为"读数据"。

1.2 作品主体结构

作品整体分为两个功能部分,这也是方案迭代后的最终形态:

区域构成作用
面粉收集区激光切割木板 + 亚克力板拼成的方形盒子(100×100×117mm)放置待发酵的面粉;内部安装 MQ-3 酒精传感器、pH 电极与数字温湿度传感器;仓壁与盖板凸台上设置通气孔,便于气体交换也便于采集气体数据
数据处理区顶部的 3D 打印杯盖(104×104mm)与嵌在盖面上的行空板 M10行空板负责采集、运算与显示;屏幕实时刷新五路参数,二维码供手机扫码进入 SIoT 查看历史折线图;外接摄像头用于远程查看发酵画面

两个区域上下叠合、干湿分离:下面是装料与传感的"湿区",上面是电路与显示的"干区"。这样既保证了电子部件的安全,又让加料、取料、观察变得简单——拎起盖子就能添面粉,扣上盖子就能继续跑数据。

1.3 设计要求

1.3.1 实现功能要求

  1. 实时显示酸碱程度(pH 值);
  2. 获取空气情况(CO₂ 浓度)与温湿度;
  3. 行空板对采集到的数据进行分析处理,并给出当天发酵状况的判断;
  4. 获取酒精浓度。

1.3.2 工程结构要求

  1. 结构合理,不影响正常使用与放置;
  2. 结构牢固,不易脱落、损坏;
  3. 结构便携,方便移动与使用。

设计目标:围绕上面两组要求,团队给自己定下了四条硬指标——① 体积控制在"笔盒"级别,单人可携带;② 一次能容纳足够量的面粉,不因容器太小而影响发酵效果;③ 五路参数同框显示,数据可以远程访问;④ 材料尽量用学校现有的激光切割机与 3D 打印机加工,控制成本。

二、设计思维导图

整个项目的推理链条可以拆成"需求—选型—结构—验证—落地"五条支线。下图把这条思路整理成一棵放射状的思维导图:外围四个方框是四个维度的思考,中间一列自左向右依次是"背景需求 → 方案选型 → 结构设计 → 应用落地",纵向主干则是信号"输入 → 处理 → 输出 → 拓展"的完整通路。

背景需求温湿度波动难控肉眼判断不可量化方案选型五路参数 + 摄像头行空板 + SCI 采集结构设计激光切割 + 3D 打印笔盒大小·干湿分离应用落地家庭烘焙 / 食品工坊课堂分组探究实验面粉发酵监测容器104×104×143mm · 1200 元 · 5 路参数输入pH / CO₂ / 温湿度 / 酒精 4 类传感器经 SCI 采集处理行空板 M10 每轮读 5 个变量并刷新界面输出TFT 屏实时数值 + 二维码 + SIoT 折线图拓展摄像头远程看画面 / 预留称重模块接口

三、硬件选用与连接

作品的硬件走的是"一块主控 + 一个采集中枢 + 四类探头"的路线。行空板 M10 负责运算、显示和联网;SCI 采集模块作为传感器中枢,把 pH 计、空气质量传感器、温湿度传感器统一挂在 I²C 总线上;酒精传感器因为是模拟量输出,直接接在行空板的模拟引脚上;摄像头走 USB。整套接线只有 I²C 四线 + 一根模拟线 + 一根 USB 线,这也是作品能做到"笔盒大小"的原因。

3.1 硬件清单

下表是作品申报材料中的完整硬件清单。需要说明的是:MQ-3 酒精传感器在原始清单里只列了名称与功能说明,未填写数量与单价;其余 6 项均已标价,合计 1200 元

序号名称型号接口数量单价(元)功能说明单位
1行空板M101399主控、运算、显示、联网
2SCI 采集模块Gravity SCII²C1269传感器中枢,扩展 3 路传感器接口
3数字温湿度传感器SHT31-FI²C169Temp_Air—环境温度;Humi_Air—环境湿度℃ / %RH
4空气质量传感器ENS160I²C1129TVOC—总挥发性有机化合物;eCO₂—CO₂ 浓度;AQI—空气质量指数ppb / ppm / 等级
5pH 计Gravity pH V2模拟(经 SCI)1249PH_Water—酸碱度
6酒精传感器MQ-3模拟(接 P22)1未标价ALcohol—酒精浓度(模拟量)
7摄像头FIT0892USB185百万级 720P USB 广角摄像头(140°)
合计7 件1200(6 项标价之和)

清单里第 5 项 pH 计标注接口为"—",实际上 pH 计是通过 SCI 采集模块的模拟端口 ePort1 接入的(见 5.3 节的引脚实测),这里按源码实际连接方式标注。

3.2 硬件连接图

下图是作品的硬件连接示意。可以看到信号是"传感器 → SCI 采集模块 → 行空板"的汇流结构:pH 计、ENS160、SHT31 分别接在 SCI 采集模块的三路接口上,再由 SCI 采集模块用 I²C 总线把数据汇总给行空板;MQ-3 酒精传感器单独走一条模拟线到行空板;摄像头用 USB 直连。

图 3-1 硬件连接图:pH 计经转接板接入 SCI 采集模块,ENS160 与 SHT31 并联在采集模块上,采集模块再以 I²C 与行空板通信;酒精传感器走模拟口,摄像头走 USB

图 3-1 硬件连接图:pH 计经转接板接入 SCI 采集模块,ENS160 与 SHT31 并联在采集模块上,采集模块再以 I²C 与行空板通信;酒精传感器走模拟口,摄像头走 USB

3.3 核心模块与参数

模块型号关键参数在本作品中的角色
主控行空板 M10国产 4 核 1.2GHz CPU、512MB DDR3 内存 + 16GB eMMC;内置 Debian 系统,支持 2.4G Wi-Fi;板载 2.8 寸 240×320 TFT 彩屏、按键、麦克风、光线/加速度传感器与蜂鸣器;Type-C 供电(5V),工作电压 3.3V,最大工作电流 2000mA运算与显示核心;直接跑 Python 程序,屏幕充当设备人机界面
采集中枢Gravity SCI 采集模块I²C 通信,本作品使用地址 0x21;板载 OLED 显示屏,可现场读数;提供多路 ePort 接口,自带按键与拨码开关把 pH 计、ENS160、SHT31 三个传感器统一挂到一条 I²C 总线上,既省引脚又简化接线
温湿度SHT31-F湿度 0—100%RH;温度 −40—125℃;工作电压 2.15—5.5V;PTFE 防水膜(IP67);I²C 地址出厂默认 0x45提供发酵环境温度与湿度两个参数,是判断酵母活性的基础数据
空气质量ENS160直接输出 TVOC、eCO₂、AQI 三类数据;预热时间小于 3 分钟提供 CO₂ 浓度,用于观察酵母无氧呼吸释放二氧化碳的进程
酸碱度Gravity 可插拔 pH 计 V2可插拔式 pH 电极,配自动清洗机构,可有效防止面团粘附监测发酵体系的酸碱变化,为"是否过度发酵"提供参考
酒精MQ-3 半导体酒精传感器半导体气敏原理,需预热约 30 秒完成校准;输出模拟电压监测乙醇浓度——面粉无氧呼吸的直接产物,是判断发酵进程的主参数
视觉FIT0892 摄像头百万级 720P,USB 广角 140°拍摄发酵画面并转 base64 上传,远端可以直观看到面团状态

 

3.4 引脚分配

下面这张表是最终定稿的引脚分配,全部来自 Mind+ 图形化程序与导出的 Python 源码,两者互相印证后确认:

信号传感器接入位置读取方式源码中的关键字
酸碱度pH 计 V2SCI 采集模块 ePort1I²C(模块地址 0x21)PH_Water
二氧化碳ENS160SCI 采集模块 ePort2I²C(模块地址 0x21)ECO2
环境温度SHT31SCI 采集模块 ePort3I²C(模块地址 0x21)Temp_Air
环境湿度SHT31SCI 采集模块 ePort3I²C(模块地址 0x21)Humi_Air
酒精浓度MQ-3行空板 P22模拟量(ADC 0—4095)Pin.P22 / ANALOG
画面FIT0892 摄像头行空板 USB 口OpenCV 取帧(设备号 −1 自动)VideoCapture

接线口诀可以记成一句话:"pH 在一、CO₂ 在二、温湿度在三,酒精单独上 P22,画面走 USB"

接线注意:SHT31 出厂默认 I²C 地址为 0x45,如果同一总线接了两颗同址传感器需要拨动板载开关切换地址;本作品 SHT31 与 ENS160 地址不冲突,因此保持默认。另外 MQ-3 是半导体气敏元件,上电后需要约 30 秒预热,所以程序里把首轮数据视作基线、不宜直接采信。

四、结构安装与制作过程

结构是这件作品最花功夫的地方。团队最初的设想是做成"一体化"——上方一个倒面粉的漏斗,下方是传感器与处理中心,行空板的屏幕朝上显示数据。但画完图纸、切出样件之后发现一体化的问题很多:加料必须整体掀开,面粉容易洒进电路区,防水和防尘都很难处理,取料也不方便。最终方案改成"上下分体、干湿分离":下面是可以独立拿取的发酵仓,上面是集成了屏幕、电路与摄像头的盖体。

4.1 外观与结构设计

4.1.1 整体造型

最终定稿的造型是一个立方体:透明亚克力箱体保证学生能直接看到面团体积的变化,顶部一块绿色 3D 打印盖板把电路、屏幕与传感器接口全部收纳在里面,一根 pH 电极从盖面穿入、垂直悬在仓内,既不容易碰伤电极,也不影响加料。整个外形没有多余的装饰,一切以实用为本——这也是团队在"创新设计说明"里写下的原话。

图 4-1 透视效果图:透明箱体 + 绿色 3D 打印盖板,盖面嵌入行空板屏幕,pH 电极从盖面穿入仓内(图中屏幕数值为建模阶段的示意值)

图 4-1 透视效果图:透明箱体 + 绿色 3D 打印盖板,盖面嵌入行空板屏幕,pH 电极从盖面穿入仓内(图中屏幕数值为建模阶段的示意值)

4.1.2 主体尺寸

作品的正立面与侧立面尺寸完全一致,说明发酵仓是一个正方形截面的立方体。所有尺寸均按 SketchUp 设计图纸标注实测:

部位尺寸说明
发酵仓(亚克力箱体)100 × 100 × 117 mm透明亚克力板围合,可直观看到面团体积变化
盖板(3D 打印)104 × 104 mm,厚 26 mm比箱体四周各外扩 2mm,形成扣边
盖下凸台60 × 60 mm,高 54 mm插入箱体内部定位,凸台面上开 5×5 = 25 个通气孔
屏幕窗口52 × 71 mm盖面为行空板 2.8 寸屏预留的开窗
电极过孔⌀20 mm位于盖面一角,供 pH 电极穿入,兼作气压平衡孔
整机外形104 × 104 × 143 mm盖板 26mm + 箱体 117mm,接近一个笔盒的体积
图 4-2 正立面尺寸图:箱体 100×117mm,盖板厚 26mm,整机高 143mm

图 4-2 正立面尺寸图:箱体 100×117mm,盖板厚 26mm,整机高 143mm

图 4-3 侧立面尺寸图:与正立面尺寸一致,确认发酵仓为正方形截面

图 4-3 侧立面尺寸图:与正立面尺寸一致,确认发酵仓为正方形截面

尺寸核对说明:项目论文在描述结构时写作"透明亚克力观察窗 10×10×15cm、厚度 5mm",这是对整体外形的近似表述;本节的 100×100×117mm、143mm 等数值均取自SketchUp 设计图纸上的实测标注,以图纸为准。图纸未标注板厚,文中不作推测。

4.1.3 杯盖结构

杯盖是整个作品的结构核心,它同时承担四件事:固定行空板、固定传感器、留出气体交换通道、封闭电路区。盖板用 PLA 材料 3D 打印(切片软件 Bambu Studio),设计上分两层:上层是 104×104mm、厚 26mm 的盖板本体;下层是 60×60mm、高 54mm 的凸台,凸台正好卡进 100×100mm 的箱口完成定位。

凸台面上呈 5 行 × 5 列排布了 25 个通气孔——这是解决"密封与透气矛盾"的关键设计:孔开在下方的凸台上,既能保证仓内气体与传感器充分接触,又因为孔朝向仓内、上方还有盖板遮挡,粉尘不易直接落到电路上。

图 4-4 杯盖正立面尺寸图:盖板宽 104mm、厚 26mm;下方凸台宽 60mm、高 54mm,台面开设 25 个通气孔

图 4-4 杯盖正立面尺寸图:盖板宽 104mm、厚 26mm;下方凸台宽 60mm、高 54mm,台面开设 25 个通气孔

4.1.4 盖面布局

盖面布局要同时满足"看得清"和"够得着"两个要求。行空板屏幕居中偏右嵌装,留出的 52×71mm 窗口正好对应 2.8 寸屏的可视区;pH 电极的过孔安排在盖面一角,避开屏幕与按键;屏幕界面下方还印有"观察折线图请登录 SIoT"与二维码,手机一扫就能进数据看板。

图 4-5 杯盖顶视尺寸图:盖板 104×104mm,一角设 ⌀20mm 过孔

图 4-5 杯盖顶视尺寸图:盖板 104×104mm,一角设 ⌀20mm 过孔

图 4-6 顶视布局图:屏幕窗口 52×71mm,界面显示

图 4-6 顶视布局图:屏幕窗口 52×71mm,界面显示"发酵监测分析"标题与五路参数,并给出 SIoT 访问二维码(图中数值为建模阶段示意值)

4.2 制作过程

4.2.1 方案讨论与结构定型

制作从"怎么装"开始。团队先拿纸笔与建模软件反复推演了两个方案:一体化漏斗式与上下分体式。讨论中大家发现,一体化方案在"加面粉"这一步就已经很不方便——必须掀开整块盖板,而盖板上连着屏幕和所有传感器线,动作稍大就可能扯断杜邦线。这个结论促成了最终的分体方案。

 

4.2.2 程序设计与调试

结构确定后,程序与结构是并行推进的。程序主体在 Mind+ 里用图形化积木搭建,再导出为 Python 代码烧进行空板。这样做的好处是:积木层级把"初始化 → 采集 → 显示 → 上传"的流程摆得清清楚楚,出了问题能一眼看出卡在哪一块积木上。

 

4.2.3 切割与组装

外壳的加工走的是"激光切割 + 热熔胶拼装"路线:先绘制激光切割图纸,用激光切割机把木板与亚克力板切成指定外形,再把切好的材料用热熔胶组合拼装成箱体;3D 打印的杯盖单独加工完成后与箱体扣合。传感器与行空板的位置在拼装过程中反复调整——这部分的难点不是切割精度,而是"哪里能固定、哪里够得着"。

 

4.2.4 联调与排错

通电联调时最常碰到的是接触不良与读数异常。团队的做法是逐路排查:先单独验证 SCI 采集模块能否正常初始化(程序中有一个"重复执行直到初始化成功"的等待循环,如果传感器没接好,程序就会一直卡在这里),再逐路确认 pH、CO₂、温湿度、酒精四类数据是否都能读到合理值,最后才打开联网与上传功能。

 

4.3 迭代改进

从第一版样机到最终成品,结构上做了三轮明确的改进,每一轮都是被真实问题逼出来的:

轮次问题改进措施效果
第一轮一体式设计加料不便,面粉易洒入电路区,防水防尘都难处理把整体拆成"面粉收集区 + 数据处理区"上下分体加料只需取下盖体,电路与物料彻底分离,干湿分区
第二轮仓内气体不流通,CO₂ 与酒精读数反应迟钝在盖下凸台面上开 5×5 通气孔,并在仓体上沿留出气隙气体交换顺畅,CO₂ 从 873ppm 逐步升至 2333ppm 的变化被完整记录下来
第三轮内部传感器位置互相"打架",硬件固定位难以兼顾更换长短不同的杜邦线,重新调整各传感器的固定位置走线不再互相顶托,整体更美观,盖体扣合也更顺畅

4.4 成品与实测

最终成品体积接近一个笔盒,单人拎起即可搬动。实验时把配好的面团放入仓内,用毛巾包住箱体保温,扣上盖子即可开始记录。

图 4-11 成品正面:绿色盖板扣在透明箱体上,盖面可见行空板屏幕

图 4-11 成品正面:绿色盖板扣在透明箱体上,盖面可见行空板屏幕

图 4-12 发酵仓内部:白色面团置于透明仓体底部,周边留有气体交换空间

图 4-12 发酵仓内部:白色面团置于透明仓体底部,周边留有气体交换空间

图 4-13 发酵前的状态:面团放入仓内,设备尚未加盖

图 4-13 发酵前的状态:面团放入仓内,设备尚未加盖

图 4-14 用毛巾包裹箱体保温:发酵需要相对稳定的温度环境

图 4-14 用毛巾包裹箱体保温:发酵需要相对稳定的温度环境

图 4-15 包裹就位后的设备:屏幕上数据持续刷新,箱体保持保温状态

图 4-15 包裹就位后的设备:屏幕上数据持续刷新,箱体保持保温状态

实验配方与注意事项:160 克面粉 + 100 克温水 + 3 克酵母 + 5 克糖,可根据实际情况等比例调整。本次实验中 pH 电极没有浸入面团内部,而是留在仓内空气中,因此 pH 读数不能代表面团的真实酸碱度(详见 5.8 节说明)。

五、编程流程分析

程序采用 Python 编写(运行在行空板内置的 Debian 系统上),主体结构是"一次初始化 + 双循环并行":主循环负责采集、显示与上传,另一个线程专门负责刷新屏幕上的日期与时间。这样的分工让时间显示始终是秒级的,不会因为采集周期而卡顿。

5.1 程序总体流程

下面这张流程图是作品在项目材料中给出的真实流程,把程序的执行顺序完整画了出来:程序启动后先导入库、初始化 GUI 与板子、连接 SIoT 服务器并启动网络循环;随后在屏幕上绘制初始文本;接着进入传感器初始化环节,这里有一个"初始化成功?"的判断——失败就打印错误信息并等待 1 秒后重试,成功才继续往下走进主循环。

 

5.2 界面设计

行空板的 2.8 寸屏幕被划分成固定的几行区域,所有显示对象在程序启动时一次性创建,之后每轮只更新文本内容。这种"一次创建、多次刷新"的做法比每轮重建对象要省资源得多。

图 5-2 界面设计积木:11 个显示对象在启动阶段一次性创建,包含标题、日期时间、五路参数、二维码入口与提示文字

图 5-2 界面设计积木:11 个显示对象在启动阶段一次性创建,包含标题、日期时间、五路参数、二维码入口与提示文字

下表是按积木参数整理的界面对象清单,坐标与字号均取自程序中真实设定:

对象名初始文本XY字号颜色作用
cc面粉发酵监测45518黑色界面主标题
Date(空)253514黑色显示日期,由线程每秒刷新
Time(空)1303514黑色显示时间,由线程每秒刷新
PHPH:206516蓝色 #0000FF酸碱度读数
ALcohol酒精:209016蓝色 #0000FF酒精浓度读数
CO2CO2:2011516蓝色 #0000FF二氧化碳读数(单位 ppm)
Temp温度:2014016黑色环境温度读数(单位 ℃)
Humi温度:2016516黑色环境湿度读数(单位 %RH)
login(二维码)80190边长 80扫码进入 SIoT 数据看板
soumin观察折线图请登录SIoT2526513蓝色 #0000FF二维码的文字引导
IP联网提示文字1528510蓝色 #0000FF屏幕底部的联网信息行

源码细节:Humi 对象的初始文本在程序中误写成了"温度:",但每轮刷新时会用"湿度:"覆盖,所以实际显示是正确的;IP 对象用于在屏幕底部提示联网信息,其中包含网络凭据,本文在展示时已隐去具体内容。

5.3 数据采集

每个采集周期里,程序会把四类传感器的读数分别存进五个变量,再把这些变量拼接进界面文本。这一段的积木非常直观地体现了"引脚 → 变量"的对应关系:

图 5-3 数据采集积木:pH 取自 SCI 采集模块 Port1,CO₂ 取自 Port2,温度与湿度取自 Port3;酒精走行空板 P22 模拟口

图 5-3 数据采集积木:pH 取自 SCI 采集模块 Port1,CO₂ 取自 Port2,温度与湿度取自 Port3;酒精走行空板 P22 模拟口

变量名数据来源取值方式
酸碱度SCI 采集模块 Port1读取 PH_Water 键值
酒精行空板 P22读取模拟量(ADC 0—4095)
co2SCI 采集模块 Port2读取 ECO2 键值
温度SCI 采集模块 Port3读取 Temp_Air 键值
湿度SCI 采集模块 Port3读取 Humi_Air 键值

可以看出温度与湿度来自同一路 Port3——SHT31 本身就把两个参数量放在一颗芯片里,所以一次读取就能拿到两组数据。而酒精传感器输出的是连续电压,程序读取的是 0—4095 的原始 ADC 值,这也是实验记录里酒精数值是 250、619 这类整数而非物理量的原因。

5.4 摄像头初始化与画面推流

摄像头是作品的"眼睛",程序在初始化阶段就要确认它被正确识别:先创建 VideoCapture 对象并打开设备,再用一个"重复执行直到初始化完成"的等待循环防止摄像头还没就绪就开始取帧。

图 5-4 摄像头初始化积木:创建 VideoCapture 对象、打开设备,并等待直到初始化完成才继续

图 5-4 摄像头初始化积木:创建 VideoCapture 对象、打开设备,并等待直到初始化完成才继续

取到画面之后,程序并不直接上传原始图像,而是先压缩再编码:先用 OpenCV 把 BGR 转成 RGB,再用 PIL 存成 JPEG 字节流,最后用 base64 编码成一个纯文本字符串,加上 data:image/png;base64, 前缀后发布到 siot/摄像头 主题。这样做的意义在于:MQTT 是文本协议,只有把二进制图像转成文本,才能借用同一条数据通道把"数值 + 画面"一起送上去。

5.5 MQTT 通信设计

作品使用 SIoT 平台作为数据中转。程序启动时会订阅一批主题,每个采集周期再向对应主题发布数据——订阅是为了能收到平台下发的控制指令,发布则是把本地数据送上去。

图 5-5 MQTT 订阅积木:程序启动时一次性订阅 8 个主题,覆盖五路参数、两条组合数据与摄像头画面

图 5-5 MQTT 订阅积木:程序启动时一次性订阅 8 个主题,覆盖五路参数、两条组合数据与摄像头画面

图 5-6 数据发布积木:每轮把五路参数逐个发布,同时额外发布

图 5-6 数据发布积木:每轮把五路参数逐个发布,同时额外发布"CO₂+酒精"与"温度+湿度"两条组合数据

主题设计如下,其中带"组合"字样的两条是为折线图对比分析准备的:把两个相关的参量拼成一条 数值,数值 格式的消息,在 SIoT 的折线图里就能同图对比两条曲线。

主题数据内容用途
siot/PH酸碱度酸碱度单参数记录
siot/ALcohol酒精酒精浓度单参数记录
siot/CO2co2二氧化碳单参数记录
siot/Temp温度温度单参数记录
siot/Humi湿度湿度单参数记录
siot/CO2ALC"co2,酒精" 组合字符串同图对比二氧化碳与酒精的变化趋势
siot/TempHUMI"温度,湿度" 组合字符串同图对比温度与湿度的变化趋势
siot/摄像头base64 编码的 JPEG 图像远程查看发酵画面

本文展示的程序与截图中,SIoT 服务器的地址、账号与密码一律以占位符形式呈现。SIoT 是开源物联网平台,学校部署在内网环境中使用,实际部署时请把地址、账号、密码换成自己服务器的配置。

5.6 时间刷新线程

屏幕上的日期与时间需要每秒都走,但如果放在主循环里做,采集与上传就会被迫拖慢。团队的做法是把它单独放到一个线程里:线程一旦启动就进入无限循环,每秒拼接一次"年-月-日"和"时:分:秒",刷新两个文本对象后休眠 1 秒。

图 5-7 时间线程积木:线程启动后每秒刷新日期与时间,与主循环并行运行

图 5-7 时间线程积木:线程启动后每秒刷新日期与时间,与主循环并行运行

这是本程序里唯一一处"多线程"设计,也是程序结构上最值得学习的一点——把"对时间精度要求高"的任务和"对时间精度要求低"的任务分开,互不干扰。

5.7 主程序结构

把各个模块按执行顺序串起来,就得到主程序的完整骨架。下图把"启动一次、并行两路"的结构画了出来——初始化段只执行一次,随后主循环与时间线程并行运行,两条线都把自己的数据送到 SIoT 平台:

初始化(启动阶段只执行一次)GUI → Board → SIoT 连接 → P22 模拟口 → 摄像头就绪 → 11 个界面对象 → SCI 自检(0x21)→ 订阅 8 个主题① 主循环 采集 + 上传 + 推流抓取一帧画面 → 转成 base64 文本发布到 siot/摄像头调用 mqtt_msg():读 5 路参数刷新屏幕 5 个文本对象发布 5 路 + 2 条组合数据 → 等待 1 秒② 时间线程 thread1 每秒刷新读取 年 / 月 / 日 并拼成日期读取 时 : 分 : 秒刷新 Date / Time 两个对象等待 1 秒与主循环并行运行,互不阻塞SIoT 物联网平台五路参数曲线 + 两条组合对比曲线 + 摄像头画面手机扫码或电脑端均可查看

这一步的设计要点在于"分工":主循环负责周期性采集与上传,时间线程负责每秒刷新时钟。如果把刷新时钟也塞进主循环,采集周期就会被拖慢;反过来,如果让主循环去等时钟,数据上传就会出现长时间空档。两条线并行,各管一摊,程序才稳。下图是初始化段的积木:

图 5-8 初始化段积木:SCI 采集模块初始化(直到成功)→ pinpong 板型自动识别 → MQTT 初始化 → 启动时间线程 → 进入主循环

图 5-8 初始化段积木:SCI 采集模块初始化(直到成功)→ pinpong 板型自动识别 → MQTT 初始化 → 启动时间线程 → 进入主循环

图 5-9 程序积木全图:可看到界面设计、初始化、MQTT 订阅与发布、时间线程各段的位置关系

图 5-9 程序积木全图:可看到界面设计、初始化、MQTT 订阅与发布、时间线程各段的位置关系

需要特别说明的是:主循环里除了采集数据,还有一段"抓帧并发布画面"的逻辑——如果摄像头有下一帧就抓取并转码上传,没有则打印提示。这意味着画面推流与数据上传是同一个循环里交替进行的。

程序参数差异提示:导出的 Python 代码中主循环的等待时间为 1 秒,而 Mind+ 图形化积木里该处设置的是 5 秒。两者是不同版本程序的参数,本文按图形化积木版本说明程序结构,实际运行以板上烧录版本为准。

5.8 程序运行效果与发酵数据

程序跑通之后,团队做了一次完整的发酵实测:2025 年 2 月 12 日上午 9 时 49 分装入面团,用毛巾包裹保温,连续监测到中午 13 时,历时约 3 小时 10 分钟。下面六张图是不同时刻的行空板屏幕实拍,可以看到五路参数随着发酵推进的变化:

图 5-10 09:49:07 实验起始:酒精 250、CO₂ 873ppm、温度 17.06℃、湿度 72.47%RH

图 5-10 09:49:07 实验起始:酒精 250、CO₂ 873ppm、温度 17.06℃、湿度 72.47%RH

图 5-11 09:59:44 酵母启动:温度升到 19.07℃,CO₂ 升至 1337ppm

图 5-11 09:59:44 酵母启动:温度升到 19.07℃,CO₂ 升至 1337ppm

图 5-12 10:50:20 发酵旺盛:CO₂ 1815ppm,湿度升至 81.95%RH

图 5-12 10:50:20 发酵旺盛:CO₂ 1815ppm,湿度升至 81.95%RH

图 5-13 11:23:00 持续产气:CO₂ 1944ppm,酒精 424

图 5-13 11:23:00 持续产气:CO₂ 1944ppm,酒精 424

图 5-14 11:51:22 酒精峰值:达到 619,为本次实验的最高值

图 5-14 11:51:22 酒精峰值:达到 619,为本次实验的最高值

图 5-15 12:59:24 趋于平稳:CO₂ 2322ppm,温度稳定在 20.94℃

图 5-15 12:59:24 趋于平稳:CO₂ 2322ppm,温度稳定在 20.94℃

把各时间点的记录整理成表,发酵过程的全貌就清楚了:

时间ALC(酒精)CO₂(ppm)TEMP(℃)HUMI(%RH)PH
09:4925087317.0672.4714.47(无效)
09:59295133719.0786.3014.31
10:50391181520.7781.9514.29
11:23424194420.9581.1414.44
11:51619209520.9483.0314.31
12:33595228721.0186.6214.38
12:59601232220.9488.4914.41
13:00593233320.9288.4814.48

这些数据同步上传到 SIoT 平台后,可以导出成折线图做趋势分析:

图 5-16 酒精(ALC)折线图:从 250 起步,11:51 达到峰值 619 后回落并趋稳

图 5-16 酒精(ALC)折线图:从 250 起步,11:51 达到峰值 619 后回落并趋稳

图 5-17 二氧化碳折线图:全程单调上升,873ppm → 2333ppm,是本次实验中最典型的发酵信号

图 5-17 二氧化碳折线图:全程单调上升,873ppm → 2333ppm,是本次实验中最典型的发酵信号

图 5-18 温度折线图:起始 17.06℃,前 20 分钟内快速升至 20.77℃,之后稳定在 20.9—21.0℃

图 5-18 温度折线图:起始 17.06℃,前 20 分钟内快速升至 20.77℃,之后稳定在 20.9—21.0℃

图 5-19 湿度折线图:从 72.47%RH 升至 88.48%RH,与面团产气产水的过程相吻合

图 5-19 湿度折线图:从 72.47%RH 升至 88.48%RH,与面团产气产水的过程相吻合

从数据里能读出三个明确的结论:

  1. 酒精与二氧化碳同向上升,印证了无氧呼吸。CO₂ 从 873ppm 增长到 2333ppm,酒精值从 250 涨到峰值 619,两条曲线虽然量纲不同,但上升段高度重合——这正是酵母菌无氧呼吸"生成酒精和二氧化碳"的直接证据,比课本上的一句话描述要有说服力得多;
  2. 温度稳定说明保温措施有效。整个 3 小时里,温度只在前 20 分钟从 17.06℃ 跃升到 20.77℃,之后就一直稳定在 20.9—21.0℃ 的窄区间内,说明毛巾包裹确实起到了保温作用,也为"控制变量"提供了条件;
  3. 发酵存在明显的阶段特征。11:51 前后酒精值出现峰值后回落(619→595→601→593),而 CO₂ 仍在缓慢上升,说明此时产气仍在继续、但酒精积累已接近上限。如果能结合称重数据推断面团失重率,就能进一步判断发酵终点。

关于 pH 数据:本次实验中 pH 读数在 14.29—14.48 之间小幅波动,明显超出 0—14 的常规 pH 量程,属于无效数据。项目材料中对此有明确说明——实验时 pH 电极没有浸入面团,而是留在仓内空气中,因此这个读数不能代表面团的真实酸碱度。pH 通道在硬件与程序上均已打通,后续实验中只需把电极插入面团即可获得有效读数。

为了验证远程访问功能,团队还在数据平台上查看了实时监控曲线。下面这组截图是 SIoT 平台的消息监控界面,横轴为真实时间戳,可以确认数据确实按周期上报到了平台:

图 5-20 SIoT 平台 [siot/Temp] 消息监控界面:横轴为真实时间戳,曲线持续上报

图 5-20 SIoT 平台 [siot/Temp] 消息监控界面:横轴为真实时间戳,曲线持续上报

图 5-21 SIoT 平台 [siot/Humi] 消息监控界面

图 5-21 SIoT 平台 [siot/Humi] 消息监控界面

图 5-22 SIoT 平台 [siot/CO2] 消息监控界面

图 5-22 SIoT 平台 [siot/CO2] 消息监控界面

图 5-23 SIoT 平台 [siot/ALcohol] 消息监控界面

图 5-23 SIoT 平台 [siot/ALcohol] 消息监控界面

图 5-24 SIoT 平台 [siot/PH] 消息监控界面

图 5-24 SIoT 平台 [siot/PH] 消息监控界面

截图批次说明:上面 5 张平台截图的时间戳为 2025-02-09,是设备联调阶段的记录,与 2 月 12 日的正式发酵实验不是同一批次,因此曲线上的数值区间与 5.8 节的数据表并不对应。这里保留它们是为了证明"数据能够稳定上报到平台"这一功能本身。

六、参考代码

下面是由 Mind+ 图形化程序导出的 Python 源码(运行环境 Python 3.6 + 行空板 Debian 系统),已逐行加上中文注释。需要特别说明的是:源码中的 SIoT 服务器地址、账号、密码、客户端 ID 以及屏幕底部提示的 Wi-Fi 名称与密码,在本文中一律以占位符形式呈现,实际部署时请替换为自己服务器的配置。

# -*- coding: UTF-8 -*-
# MindPlus
# Python
import cv2                                  # OpenCV:摄像头取帧
import time                                 # 时间模块:延时、时间格式化
import siot                                 # SIoT 物联网库:MQTT 通信
import datetime                             # 日期时间模块
from unihiker import GUI                    # 行空板屏幕 GUI 库
from pinpong.board import Board             # pinpong 板卡库
from pinpong.board import Board, Pin        # 引脚对象
from pinpong.extension.unihiker import *    # 行空板板载资源扩展
import base64                               # base64 编码:把图像转成文本
from io import BytesIO                      # 内存字节流:JPEG 编码中转
from PIL import Image                       # 图像处理
import sys
# 把 SCI 采集模块的第三方扩展库加进搜索路径
sys.path.append("/root/mindplus/.lib/thirdExtension/liliang-gravitysci-thirdex")
from dfrobot_rp2040_sci import *            # SCI 采集模块驱动

# ================= 配置区:部署时请改为自己的服务器参数 =================
SIOT_SERVER = "0.0.0.0"                     # SIoT 服务器地址(占位符)
SIOT_PORT   = 1883                          # MQTT 端口
SIOT_USER   = "****"                        # 平台账号(占位符)
SIOT_PWD    = "****"                        # 平台密码(占位符)
SIOT_CLIENT = "****"                        # 客户端 ID(占位符)
WIFI_SSID   = "****"                        # 屏幕底部提示的 Wi-Fi 名称(占位符)
WIFI_PWD    = "****"                        # 屏幕底部提示的 Wi-Fi 密码(占位符)


# 自定义函数
def MQTT():
    """启动阶段一次性订阅全部主题:五路参数 + 两条组合数据 + 摄像头画面"""
    siot.getsubscribe(topic="siot/PH")          # 订阅酸碱度
    siot.getsubscribe(topic="siot/ALcohol")     # 订阅酒精浓度
    siot.getsubscribe(topic="siot/CO2")         # 订阅二氧化碳
    siot.getsubscribe(topic="siot/Temp")        # 订阅温度
    siot.getsubscribe(topic="siot/Humi")        # 订阅湿度
    siot.getsubscribe(topic="siot/CO2ALC")      # 订阅"CO2+酒精"组合数据
    siot.getsubscribe(topic="siot/TempHUMI")    # 订阅"温度+湿度"组合数据
    siot.getsubscribe(topic="siot/摄像头")       # 订阅摄像头画面


def mqtt_msg():
    """单个采集周期:读 5 路传感器 → 刷新屏幕 → 发布到 SIoT"""
    # ---- 1. 采集:四类传感器共读 5 个参量 ----
    SuanJianDu = SCI1.get_value1(SCI1.ePort1, "PH_Water")   # pH 值,取自采集模块 Port1
    JiuJing = p_p22_analog.read_analog()                    # 酒精原始 ADC 值(0-4095),P22 模拟口
    co2 = SCI1.get_value1(SCI1.ePort2, "ECO2")              # 二氧化碳,取自 Port2
    WenDu = SCI1.get_value1(SCI1.ePort3, "Temp_Air")        # 空气温度,取自 Port3
    ShiDu = SCI1.get_value1(SCI1.ePort3, "Humi_Air")        # 空气湿度,同取 Port3

    # ---- 2. 显示:把读数拼进界面文本并刷新 ----
    PH.config(text=(str("P H:   ") + str(SuanJianDu)))                     # 酸碱度
    ALcohol.config(text=(str("酒精:") + str(JiuJing)))                     # 酒精
    CO2.config(text=(str("CO2:  ") + str((str(co2) + str("  ppm")))))      # CO2,带 ppm 单位
    Temp.config(text=(str("温度:") + str((str(WenDu) + str("  ℃")))))      # 温度,带 ℃ 单位
    Humi.config(text=(str("湿度:") + str((str(ShiDu) + str("  %RH")))))    # 湿度,带 %RH 单位

    # ---- 3. 上传:逐个参数发布到对应主题 ----
    siot.publish_save(topic="siot/PH", data=SuanJianDu)         # 酸碱度
    siot.publish_save(topic="siot/Temp", data=WenDu)            # 温度
    siot.publish_save(topic="siot/Humi", data=ShiDu)            # 湿度
    siot.publish_save(topic="siot/CO2", data=co2)               # 二氧化碳
    siot.publish_save(topic="siot/ALcohol", data=JiuJing)       # 酒精
    # 组合数据:拼成"值1,值2",便于在平台上同图对比两条曲线
    siot.publish_save(topic="siot/CO2ALC",
                      data=(str(co2) + str((str(",") + str(JiuJing)))))
    siot.publish_save(topic="siot/TempHUMI",
                      data=(str(WenDu) + str((str(",") + str(ShiDu)))))


# 事件回调函数
def u_thread1_function():
    """时间刷新线程:每秒更新一次屏幕上的日期与时间"""
    while True:
        # 拼接"年-月-日"(源码把三个 get_time 结果逐层嵌套,此处等效果表达)
        RiQi = (str(datetime.datetime.now().year) + str((str("-") + str((
            str(datetime.datetime.now().month) + str((str("-") + str(
                datetime.datetime.now().day))))))))
        ShiJian = time.strftime("%H:%M:%S")     # 取"时:分:秒"
        Date.config(text=RiQi)                  # 刷新日期文本
        Time.config(text=ShiJian)               # 刷新时间文本
        time.sleep(1)                           # 每秒一次


# ================= 主程序 =================
u_gui = GUI()                               # 创建 GUI 对象
Board().begin()                             # 板卡初始化(自动识别板型)
# 连接 SIoT 平台(地址/账号/密码/客户端 ID 见配置区占位符)
siot.init(client_id=SIOT_CLIENT, server=SIOT_SERVER, port=SIOT_PORT,
          user=SIOT_USER, password=SIOT_PWD)
siot.connect()                              # 建立 MQTT 连接
siot.loop()                                 # 启动网络循环(后台持续收发)
p_p22_analog = Pin(Pin.P22, Pin.ANALOG)     # 把 P22 设为模拟输入(酒精传感器)


def frame2base64(frame):
    """把一帧图像转成 base64 文本:BGR→RGB→JPEG→base64"""
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)   # OpenCV 默认 BGR,先转 RGB
    img = Image.fromarray(frame)                     # 转成 PIL 图像对象
    output_buffer = BytesIO()                        # 建一个内存缓冲区
    img.save(output_buffer, format='JPEG')           # 存成 JPEG 写入缓冲
    byte_data = output_buffer.getvalue()             # 取出字节数据
    base64_data = base64.b64encode(byte_data)        # 编码为 base64
    return base64_data                               # 返回 base64 文本


def base642base64(frame):
    """给 base64 文本补上 data URI 前缀,方便前端直接渲染"""
    data = str('data:image/png;base64,')             # 前缀
    base64data = str(frame2base64(frame))            # 取 base64 文本
    framedata = base64data[2:(len(base64data) - 1)]  # 去掉 b' 与结尾的 '
    base642base64_data = data + str(framedata)       # 拼接
    return base642base64_data


vd = cv2.VideoCapture()                     # 创建摄像头对象
vd.open(-1)                                 # 打开设备(-1 表示自动选择)
while not (vd.isOpened()):                  # 直到摄像头就绪才继续
    print("摄像头初始化中......")
print("摄像头初始化完成")

# ---- 创建界面对象(一次创建,后续只改文本)----
cc = u_gui.draw_text(text="面粉发酵监测", x=45, y=5, font_size=18, color="#000000")
Date = u_gui.draw_text(text="       ", x=25, y=35, font_size=14, color="#000000")
Time = u_gui.draw_text(text="        ", x=130, y=35, font_size=14, color="#000000")
PH = u_gui.draw_text(text="PH:", x=20, y=65, font_size=16, color="#0000FF")
ALcohol = u_gui.draw_text(text="酒精:", x=20, y=90, font_size=16, color="#0000FF")
CO2 = u_gui.draw_text(text="CO2:", x=20, y=115, font_size=16, color="#0000FF")
Temp = u_gui.draw_text(text="温度:", x=20, y=140, font_size=16, color="#000000")
Humi = u_gui.draw_text(text="湿度:", x=20, y=165, font_size=16, color="#000000")
# 二维码:扫码即可进入 SIoT 数据看板(地址见配置区占位符)
login = u_gui.draw_qr_code(text="http://" + SIOT_SERVER + ":8080", x=80, y=190, w=80)
soumin = u_gui.draw_text(text="观察折线图请登录SIOT", x=25, y=265, font_size=13, color="#0000FF")
# 屏幕底部联网提示行:原程序在此处显示 Wi-Fi 名称与密码,本文以占位符变量呈现
WIFI_HINT = "Wi-Fi:" + WIFI_SSID + "    密码:" + WIFI_PWD
IP = u_gui.draw_text(text=WIFI_HINT, x=15, y=285, font_size=10, color="#0000FF")

# ---- SCI 采集模块初始化:失败就重试 ----
SCI1 = DFRobot_RP2040_SCI_IIC(addr=0x21)    # I²C 地址 0x21
while SCI1.begin() != 0:                    # 返回非 0 表示初始化失败
    print("Initialization Sensor Universal Adapter Board failed.")
    time.sleep(1)                           # 等 1 秒后重试
print("Initialization Sensor Universal Adapter Board done.")

MQTT()                                      # 订阅全部主题
print("MQTT初始化完成")
thread1 = u_gui.start_thread(u_thread1_function)   # 启动时间刷新线程

# ---- 主循环:推流画面 + 采集上传 ----
while True:
    if vd.grab():                                       # 摄像头有下一帧
        ret, grab = vd.read()                           # 读出该帧
        siot.publish(topic="siot/摄像头",
                     data=base642base64(grab))          # 转 base64 后上传
    else:
        print("没有下一帧")                              # 没帧则提示
    mqtt_msg()                                          # 采集 5 路并上传
    time.sleep(1)                                       # 每轮间隔 1 秒(积木版为 5 秒)

代码阅读要点:① 五个变量名(酸碱度、酒精、co2、温度、湿度)对应五路数据;② siot.publish_savesiot.publish 的区别在于前者会把数据同时存入平台数据库(这也是能导出折线图的原因);③ 摄像头取帧与数据采集在同一个循环里,靠 vd.grab() 的返回值判断是否有新帧。

七、作品创新点

  1. 物联网功能:把作品采集并处理好的数据发送到 SIoT 服务器,可供远程访问与操作,便于后期对数据进行分析处理、得出结论。屏幕上的二维码让学生用手机扫一下就能看到折线图,不需要任何额外软件。
  2. 简单便携:整个作品设计为"笔盒"大小(104×104×143mm),便于单人携带;数据由行空板程序自动分析处理,使用者无需进行过多操作,扣上盖子就开始记录,这在课堂分组实验场景中非常关键。
  3. 立体化分析:如果只检测一个酒精浓度去判断面粉发酵情况,显然是比较片面的。作品在检测酒精浓度的同时增加了 pH 计、空气质量传感器与温湿度传感器,检测结果更加立体合理,便于学生进行分组探究分析。
  4. 外观合理:没有使用过多的装饰性设计,完全以实用性为本——透明箱体保证能看见面团、绿色盖板把电路藏起来、盖下凸台开孔兼顾透气与防尘。
  5. 视觉远程查看:使用 USB 摄像头对发酵过程进行拍摄,画面转成 base64 文本后借助同一条 MQTT 通道上传,便于远程查看发酵进度并进行管理。

值得单独一提的是第 5 条的设计巧思:MQTT 本身是文本协议,无法直接传图像。团队用"JPEG 压缩 + base64 编码"把图像变成字符串,复用了已有的数据通道,没有额外增加服务器或协议——这对中学生项目来说是一个相当务实的工程取舍。

八、项目应用拓展

这件作品的价值不限于一次演示实验。把它放到不同的场景里,还能延伸出多种用法:

应用场景使用方式价值
中学生物课堂分组实验每组一套设备,同时跑发酵实验,数据实时汇总到同一平台把"看面团膨胀"变成"读数据曲线",为控制变量与对照实验提供量化依据
跨学科项目式学习生物讲呼吸作用、物理讲传感器原理、信息技术讲物联网协议,共用一套设备同一件作品串起三大学科知识点,是典型的多学科融合载体
家庭烘焙与食品加工把容器放在面团旁边,随时掌握发酵进度与温度减少"发过头"或"发不起来"的情况,让经验判断有了数据支撑
食品工坊与小型生产线多台设备并联使用,统一查看各批次发酵状态在不高的成本下实现多点位监控,便于标准化管理
功能扩展:称重监测利用作品预留的 I²C 接口扩展称重模块,记录面团质量损失率质量损失率是发酵强度的另一个重要指标,可与 CO₂ 数据互相印证
功能扩展:发酵终点预测基于已积累的历史数据训练简单模型,自动判断发酵是否完成把"什么时候发好"这个老师傅的经验问题,变成可以计算的问题

团队在项目材料中也提到,后续会参考市面上的相似商品,在现有基础上增加远程遥控、蓝牙等功能。而 3D 打印模型文件采用 PLA 材料,学校可以自行加工容器外壳,这也意味着作品具备被复制和推广的基础——一套图纸、一份代码,其他学校就能做出自己的版本。

 

硬件清单

创作许可协议

本项目采用 CC BY(署名) 进行许可。

所需材料/产品

加载全部

    行空板M10(UNIHIKER)Python教学主控板 - 蘑菇云科创教育

    行空板M10(UNIHIKER)Python教学主控板 - 蘑菇云科创教育

    行空板M10(UNIHIKER)Python教学主控板 - 蘑菇云科创教育

    Gravity: SCI采集模块(兼容micro:bit、掌控板和行空板)- 蘑菇云科创教育

    Gravity: SCI采集模块(兼容micro:bit、掌控板和行空板)- 蘑菇云科创教育

    Gravity: SCI采集模块(兼容micro:bit、掌控板和行空板)- 蘑菇云科创教育

    百万级720PUSB广角摄像头(140°)

    百万级720PUSB广角摄像头(140°)

    百万级720PUSB广角摄像头(140°)

相关推荐

评论(0)
- 没有更多了 -

创作许可协议

本项目采用 CC BY(署名) 进行许可。

相关推荐

所需材料/产品

行空板M10(UNIHIKER)Python教学主控板 - 蘑菇云科创教育

行空板M10(UNIHIKER)Python教学主控板 - 蘑菇云科创教育

行空板M10(UNIHIKER)Python教学主控板 - 蘑菇云科创教育

Gravity: SCI采集模块(兼容micro:bit、掌控板和行空板)- 蘑菇云科创教育

Gravity: SCI采集模块(兼容micro:bit、掌控板和行空板)- 蘑菇云科创教育

Gravity: SCI采集模块(兼容micro:bit、掌控板和行空板)- 蘑菇云科创教育

百万级720PUSB广角摄像头(140°)

百万级720PUSB广角摄像头(140°)

百万级720PUSB广角摄像头(140°)

加载全部