【欧时RS x Arduino】04 AI控制风扇
# 背景经过前面的铺垫,熟悉了AI手势识别,并且在MCU侧实现了电机档位控制,现在只需要把两者结合起来就可以了。
目的是:Arduino Uno Q 通过 USB Hub 接入 USB 摄像头,运行 Edge Impulse 本地识别手势,然后通过 Bridge 发送消息通知 MCU 侧控制电机,例如启动、停止、切换档位。
# 模型介绍
这个 hand\-gestures 模型来自 https://studio\.edgeimpulse\.com/public/842271/live 这个项目。这是一个轻量级的视觉项目,摄像头目标识别4种手势:
- nestral fist
- thumbs\-up \(good\)
- V\-sign \(peace\)
- open hand \(five\)
!(//m.qjumbo.com/forum/data/attachment/forum/202611/01/011754hk497clg8zc1tz84.png)
# 流程介绍
首先,给程序添加一个 Brick,如下图所示,video\_object\_detection,对应的模型选择 hand\-gestures
!(//m.qjumbo.com/forum/data/attachment/forum/202611/01/011806dcjcm6s1ut519sss.png)
## 主流程
- Linux 侧初始化摄像头、手势识别模型、网页界面和通信服务
- 摄像头持续采集画面,由本地模型进行推理,输出手势类别以及置信度
- 识别回调将结果推送到网页,同时保存最新识别结果,供主循环读取
- 主循环对识别结果进行置信度过滤和连续确认,再将手势映射为风扇档位:neut 对应停转,good/peace/five 分别对应1/2/3档
- 只有识别结果满足确认条件、目标档位发生变化切达到指令发送间隔时,Linux 才通过 Bridge 下发 fan\_set 指令;否则继续识别,风扇保持原档位
- MCU接收到指令后,将档位限制在0\~3范围内,再转换为对应的PWM值,通过 TB6612FNG 驱动模块控制风扇停止或调整转速
!(//m.qjumbo.com/forum/data/attachment/forum/202611/01/011816f0rdohdrp05htt5d.png)
## 交互图
- Linux 侧的识别模块首先将手势类别、置信度和事件序号交给档位决策模块
- 决策模块完成连续确认、档位去重以及发送节流后,调用 Bridge\.notify\("fan\_set", level\) 将目标档位发送给MCU
- MCU通过已注册的 cmd\_fan\_set\(\) 回调接受指令,并调用电机控制函数,将档位转换为PWM输出。PWM值依次分别是 \{0, 120, 170, 255\},对应停转以及3个运行档位,最终由驱动模块作用于电机
整个交互过程中,图像采集、AI推理和控制决策都在 Linux 侧完成,MCU 负责确定性的硬件控制。两侧传递的是档位数值,无需传输图像。 notify 不等待执行回执,因此该交互表示指令下发和执行路径,不包含实际转速反馈。
!(//m.qjumbo.com/forum/data/attachment/forum/202611/01/011825dpfpz2fq8j93ofyl.png)
# 关键代码
## 初始化摄像头与识别器
```Plain
camera = Camera(
resolution=CAMERA_RESOLUTION,
fps=CAMERA_FPS,
)
detector = VideoObjectDetection(
camera=camera,
confidence=GESTURE_CONFIDENCE,
debounce_sec=GESTURE_DEBOUNCE_S,
camera_preview=False,
)
gestures.attach(detector)
```
Linux 侧采集摄像头画面,使用工程配置的 hand\-gestures 模型进行本**理;置信度门槛和回调间隔由配置控制。
## 接收识别结果
```Python
def handler(detections):
best = None
for label, items in (detections or {}).items():
confidence = (
float(items.get("confidence", 0.0))
if items else 0.0
)
if best is None or confidence > best:
best = (label, confidence)
if best is not None:
self._record(best, best)
detector.on_detect_all(handler)
```
回调从识别结果中选出置信度最高的类别,并保存到“最新结果”槽位,交给主循环处理。
## 手势映射为风扇档位
```Python
GESTURE_TO_LEVEL = {
"neut":0,# 握拳:停转
"good":1,# 大拇指:1档
"peace": 2,# 剪刀手:2档
"five":3,# 张开手掌:3档
}
```
采用绝对档位映射,同一手势始终对应同一档位。
## 连续确认和档位去重
```Python
level = GESTURE_TO_LEVEL.get(label)
if level is None:
return
required = (
NEUT_CONFIRM_COUNT
if label == "neut"
else GESTURE_CONFIRM_COUNT
)
if self._candidate is not None and level == self._candidate:
self._candidate = (level, self._candidate + 1)
else:
self._candidate = (level, 1)
if self._candidate < required:
return
if level == self._current_level:
return
self._pending = (level, label, confidence)
```
同一目标档位达到确认次数后才进入带发送状态;档位未变化则不重复发送。完整实现还包含 neut 专用置信度门槛、确认过期和发送间隔控制。
## 通过Bridge下发指令
```Python
class FanController:
def set_level(self, level):
level = max(LEVEL_STOP, min(LEVEL_MAX, int(level)))
Bridge.notify(RPC_FAN_SET, level)
return level
```
将档位限制到合法范围,通过 fan\_set 通知 MCU。 notify\(\) 不等待执行结果。
## MCU 注册并处理指令
```Plain
// 初始化时注册
Bridge.provide_safe(RPC_FAN_SET, cmd_fan_set);
void cmd_fan_set(int level)
{
motor_set_level(level);
}
```
MCU 暴露 fan\_set\(\) 接口,收到 Linux 指令后执行电机控制函数。 provide\_safe\(\) 让回调在安全的主循环上下文执行。
## 档位转换为 PWM
```Plain
const int DUTY_FOR_LEVEL = {
0, 120, 170, 255
};
void motor_set_level(int level)
{
if (level < 0)
level = 0;
if (level > FAN_LEVEL_MAX)
level = FAN_LEVEL_MAX;
analogWrite(MOTOR_PIN_PWMA, DUTY_FOR_LEVEL);
matrix_display_set_level(level);
}
```
档位 0\~3 分别转换为对应 PWM,经 TB6612FNG 驱动风扇。最后一行同步更新点阵侠士,不参与电机调速。
# 主循环持续驱动
```Plain
def loop():
service.tick() # 心跳与通信检测
reporter.tick() # MCU启动回执
control_tick() # AI模式的阈值更新与手势决策
time.sleep(LOOP_SLEEP_S)
App.run(user_loop=loop)
```
识别回调负责提供结果,主循环持续完成控制决策,两者分工执行。
# 实物演示
!(//m.qjumbo.com/forum/data/attachment/forum/202611/01/011902hohbh7a2x2d3bkxc.png)
!(//m.qjumbo.com/forum/data/attachment/forum/202611/01/011907jvb8rbh9egg9z8m9.png)
!(//m.qjumbo.com/forum/data/attachment/forum/202611/01/011911doa4fl4p4c96l7f0.png)
!(//m.qjumbo.com/forum/data/attachment/forum/202611/01/011916clun9j4524i52njj.png)
视频演示
<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=117496715679056&bvid=BV15RHT6NEJV&cid=43022814171&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
页:
[1]