【花雕学编程】Arduino BLDC 之基于环境感知的主动情绪响应机器人(导览陪伴场景)

基于Arduino与BLDC(无刷直流电机)构建的“环境感知主动情绪响应机器人”,是一套将多模态环境感知、轻量化AI推理与BLDC拟人化执行深度融合的嵌入式情感计算架构。该方案突破了传统Arduino仅能执行简单逻辑的局限,通过边缘侧情绪识别与BLDC柔顺运动控制,赋予机器人在导览陪伴场景中“察言观色”与“情感表达”的能力。
主要特点
- 边缘侧轻量化多模态感知与融合
区别于依赖云端计算的方案,该系统将情绪识别下沉至Arduino边缘端,实现低延迟与高隐私保护。系统通过搭载微型摄像头(如OV7670或ESP32-CAM)采集图像,运行基于TensorFlow Lite Micro训练的轻量化卷积神经网络(CNN),提取面部表情特征(如嘴角上扬、眉毛下垂);通过麦克风阵列采集语音,结合开源语音识别库进行关键词提取和语调分析(如音调高低、语速快慢),判断情绪状态(兴奋、悲伤);通过压力传感器或触摸传感器感知用户的互动力度(如轻拍、紧抱),作为情绪识别的辅助输入。在ESP32等具备DSP指令集的芯片上,采用决策级或特征级融合策略,实时判断用户的情绪状态。 - 基于FOC的BLDC拟人化情绪表达
在此系统中,BLDC电机不再是简单的动力源,而是情绪表达的“执行器官”。通过FOC(磁场定向控制)精确控制转矩电流(Iq),实现力矩柔顺控制。例如,在识别到用户悲伤时,机器人头部的转向不再是刚性的匀速运动,而是带有“迟疑”和“轻柔”的变加速运动,模拟人类的关切姿态;识别到兴奋时,则可通过差速扭矩实现小幅度的原地“雀跃”。此外,BLDC电机的PWM频率可与语音合成器联动,在机器人发声时让头部电机产生微幅振动,模拟人类发声时的生理特征。 - 基于状态机的交互叙事引擎
系统在Arduino上构建有限状态机(FSM),管理复杂的交互逻辑。定义“中性”、“快乐”、“悲伤”、“警惕”等状态,例如连续检测到用户笑声且语调高昂,则从“中性”迁移至“快乐”状态,触发预设的“陪伴玩耍”行为序列。同时,利用EEPROM或外部Flash存储短期交互历史(如“上次用户抚摸头部后情绪好转”),实现基于上下文的个性化响应,而非简单的刺激-反应。 - 分层式硬实时情感闭环
系统构建“感知-决策-执行-反馈”的硬实时闭环:高频层(中断)运行BLDC FOC电流环(10-20kHz),确保拟人化动作的平滑性;中频层(任务)进行传感器数据采集与轻量化情绪推理(100-500Hz);低频层(主循环)处理状态机更新与叙事逻辑(50-100Hz)。这种分层架构确保了在复杂交互中,底层运动控制不被上层逻辑阻塞。
应用场景
- 博物馆与科技展馆智能导览
在博物馆或科技馆中,机器人通过人脸识别判断参观者的兴趣程度与情绪状态。当检测到参观者驻足凝视某展品且表情专注时,机器人主动靠近并启动讲解;当检测到参观者面露困惑时,机器人可放慢语速、配合头部微倾等肢体语言进行补充说明,提升导览体验的互动性与亲和力。 - 儿童孤独症干预与陪伴
作为辅助治疗工具,为孤独症谱系障碍(ASD)儿童提供可预测、非压迫性的社交互动。机器人通过表情识别判断儿童是否焦虑,通过缓慢、重复的BLDC驱动摆动动作(如左右摇晃)提供安抚;通过识别儿童的笑脸,触发奖励性的灯光和音乐,强化正向社交行为。 - 老年人情感陪护与认知训练
在养老机构或居家场景中,机器人通过语音交互识别老人的孤独感,主动靠近(BLDC驱动底盘平滑移动)并播放老人喜爱的音乐;通过表情识别监测老人是否长时间情绪低落,自动通知监护人。同时,机器人可作为认知训练辅助工具,通过互动游戏帮助老人维持认知能力。 - 商业综合体迎宾与导购
在商场、酒店大堂等场所,机器人通过视觉与听觉感知主动迎接顾客,根据顾客的面部表情与语音语调判断其需求紧迫性。对于表情轻松的顾客,机器人以活泼的姿态引导;对于表情焦急的顾客,机器人则快速、直接地提供路径指引,提升服务效率与顾客满意度。
注意事项
- 硬件选型与算力瓶颈(首要挑战)
AVR架构(Arduino Uno)完全不可行。情绪识别需要运行轻量级神经网络(如MobileNetV1 8-bit量化版),强烈推荐ESP32-S3(双核,支持向量指令)或STM32H7系列(带Cache和AI加速器)。若需运行更复杂的模型,可采用“上位机(树莓派/Jetson)推理+Arduino底层BLDC控制”的分布式架构。 - 数据隐私与合规性
由于系统涉及持续的面部图像采集与语音监听,在面向儿童或老人的应用中,必须严格遵守数据隐私法规。建议采用纯边缘端推理,不将原始音视频数据上传云端,并在硬件或软件层面提供明确的隐私指示灯与物理断电开关。 - BLDC驱动的EMC与信号隔离
BLDC电机的大电流PWM驱动信号极易干扰摄像头、麦克风等敏感传感器的信号。在硬件设计上,必须将电机动力线与传感器信号线严格分开布线,并做好屏蔽处理;建议采用隔离电源(如DC-DC模块)为控制电路供电,防止电机启停时的电压波动导致主控板复位或传感器数据失真。 - 情绪识别的鲁棒性与误判处理
在实际环境中,光线变化、背景噪声、多人同时说话等因素都会影响情绪识别的准确率。必须在软件中加入多帧投票机制或置信度阈值过滤,避免因单次误判触发不恰当的情绪反馈。同时,应设计“降级策略”——当识别置信度低于阈值时,机器人回归中性状态,避免做出令人困惑的行为。 - 拟人化动作的自然度调优
BLDC的FOC控制虽然能实现柔顺运动,但“情绪映射”的参数整定需要大量实验。例如,“悲伤”状态下的头部下垂速度、“快乐”状态下的身体晃动幅度,都需要通过真人动作捕捉数据或心理学研究进行标定,避免动作过于夸张或过于僵硬,破坏陪伴的真实感。

1、触摸意图识别与情绪评分
此案例聚焦于主动交互感知。通过多个触摸传感器检测用户交互的位置和时长,将“轻拍头部”“抚摸手臂”等行为映射为不同的情绪评分,作为后续多模态融合的最高权重输入。
#include <SimpleFOC.h>
// ===== 触摸传感器配置 =====
#define TOUCH_HEAD A0
#define TOUCH_LEFT A1
#define TOUCH_RIGHT A2
// 三路触摸状态
int touchState[3] = {0, 0, 0};
float touchScore = 0.5; // 默认中性
// 触摸检测
int detectTouch() {
touchState[0] = analogRead(TOUCH_HEAD) > 500 ? 1 : 0;
touchState[1] = analogRead(TOUCH_LEFT) > 500 ? 1 : 0;
touchState[2] = analogRead(TOUCH_RIGHT) > 500 ? 1 : 0;
for (int i = 0; i < 3; i++) {
if (touchState[i] == 1) return i;
}
return -1; // 无触摸
}
// ===== 触摸意图评分(触摸优先级最高)=====
float evaluateTouchIntent(int touchIndex, int touchDuration) {
if (touchIndex == 0) { // 头部触摸
if (touchDuration > 3000) return 0.8f; // 长时间→亲近
else return 0.6f; // 短时→友好
} else if (touchIndex == 1) { // 左侧身体触摸
return 0.7f; // 寻求帮助
} else if (touchIndex == 2) { // 右侧身体触摸
return 0.3f; // 可能的拒绝或低落
}
return 0.5f;
}
void setup() {
Serial.begin(115200);
Serial.println("触摸意图识别系统就绪");
}
void loop() {
static unsigned long touchStart = 0;
int touchIndex = detectTouch();
int touchDuration = 0;
// 记录触摸时长
if (touchIndex != -1 && touchStart == 0) {
touchStart = millis();
}
if (touchIndex != -1) {
touchDuration = millis() - touchStart;
// 持续触摸时更新评分
touchScore = evaluateTouchIntent(touchIndex, touchDuration);
} else {
if (touchStart != 0) {
// 触摸结束,保持评分一小段时间后衰减
touchStart = 0;
}
// 缓慢衰减回中性
touchScore = touchScore * 0.95f + 0.5f * 0.05f;
}
Serial.print("触摸: "); Serial.print(touchIndex);
Serial.print(" | 时长: "); Serial.print(touchDuration);
Serial.print(" | 评分: "); Serial.println(touchScore);
delay(50);
}
核心逻辑:触摸传感器检测用户交互意图(轻拍、长时间抚摸等)并赋予最高权重。头部触摸通常代表亲近意图,左侧身体触摸可能表示寻求帮助,右侧身体触摸则可能表示拒绝或低落。触摸时长也被纳入评分——长时间抚摸比短时轻拍传递更强烈的情感意图。
2、多模态情绪融合决策
此案例实现触摸+表情+语音的三模态加权融合。触摸评分作为主动交互信号获得最高权重,表情和语音作为辅助模态。融合后的情绪评分映射为“开心/中性/不开心/愤怒”四种情绪状态,为BLDC动作执行提供决策依据。
// ===== 多模态融合权重 =====
float faceScore = 0.5; // 表情情绪分(来自视觉模块)
float voiceScore = 0.5; // 语音情绪分(来自语音模块)
float touchScore = 0.5; // 触摸情绪分(来自案例一)
String currentEmotion = "neutral";
// ===== 三模态加权融合 =====
String multiModalFusion() {
// 自适应权重:触摸优先(主动交互权重0.4),其次表情(0.3),最后语音(0.3)
float fusedScore = (touchScore * 0.4f) + (faceScore * 0.3f) + (voiceScore * 0.3f);
if (fusedScore >= 0.7f) return "happy";
else if (fusedScore >= 0.5f) return "neutral";
else if (fusedScore >= 0.3f) return "unhappy";
else return "angry";
}
// ===== 模拟表情和语音输入(实际项目需接入视觉/语音模块)=====
void updateFaceScore() {
// 实际项目中:通过摄像头捕捉面部表情,计算情绪分
// 简化:随机波动模拟
faceScore = 0.5f + (random(-20, 20) / 100.0f);
faceScore = constrain(faceScore, 0, 1);
}
void updateVoiceScore() {
// 实际项目中:通过麦克风提取语速、语调等声学特征
// 简化:基于音量模拟
voiceScore = 0.5f + (random(-15, 15) / 100.0f);
voiceScore = constrain(voiceScore, 0, 1);
}
void setup() {
Serial.begin(115200);
Serial.println("多模态情绪融合系统就绪");
}
void loop() {
// 1. 更新各模态评分
updateFaceScore();
updateVoiceScore();
// touchScore 由案例一提供
// 2. 三模态融合
currentEmotion = multiModalFusion();
// 3. 输出决策结果
Serial.print("触摸:"); Serial.print(touchScore);
Serial.print(" 表情:"); Serial.print(faceScore);
Serial.print(" 语音:"); Serial.print(voiceScore);
Serial.print(" → 情绪:"); Serial.println(currentEmotion);
// 4. 根据情绪驱动BLDC动作(案例三实现)
// executeMultiJointAction(currentEmotion);
delay(100);
}
核心逻辑:融合公式 fusedScore = touchScore × 0.4 + faceScore × 0.3 + voiceScore × 0.3 体现了“主动交互优先”的设计哲学。用户主动触摸机器人时,意图最明确、最强烈,因此权重最高。表情和语音作为被动感知信号,权重较低,但能在用户未触摸时提供情绪背景。
3、情绪驱动的BLDC多关节协同表达
此案例将情绪决策转化为BLDC多关节的协同动作。不同情绪对应不同的姿态序列:开心时点头+前倾+挥手,不开心时低头+微侧+轻拍安抚。BLDC配合FOC算法实现平滑、无顿挫的动作表达。
#include <SimpleFOC.h>
// ===== 多关节BLDC定义 =====
BLDCMotor neckJoint = BLDCMotor(7); // 颈部
BLDCMotor torsoJoint = BLDCMotor(8); // 躯干
BLDCMotor armJoint = BLDCMotor(9); // 手臂
BLDCDriver3PWM neckDriver = BLDCDriver3PWM(3, 4, 5);
BLDCDriver3PWM torsoDriver = BLDCDriver3PWM(6, 7, 8);
BLDCDriver3PWM armDriver = BLDCDriver3PWM(9, 10, 11);
// 语音合成串口(可选)
// SoftwareSerial ttsSerial(12, 13);
// ===== 情绪驱动的多关节动作 =====
void executeMultiJointAction(String emotion) {
if (emotion == "happy") {
// 开心:颈部点头15° + 躯干前倾10° + 手臂抬起挥动
neckJoint.moveTo(15);
torsoJoint.moveTo(10);
armJoint.moveTo(150);
// ttsSerial.println("太棒啦!我们一起做游戏吧~");
Serial.println("执行动作:开心 - 点头+前倾+挥手");
} else if (emotion == "neutral") {
// 中性:恢复自然姿态
neckJoint.moveTo(0);
torsoJoint.moveTo(0);
armJoint.moveTo(90);
Serial.println("执行动作:中性 - 自然姿态");
} else if (emotion == "unhappy") {
// 不开心:颈部低头10° + 躯干微侧 + 手臂轻拍安抚
neckJoint.moveTo(-10);
torsoJoint.moveTo(-5);
// 手臂轻拍3次
for (int i = 0; i < 3; i++) {
armJoint.moveTo(45);
delay(200);
armJoint.moveTo(60);
delay(200);
}
Serial.println("执行动作:不开心 - 低头+轻拍安抚");
} else if (emotion == "angry") {
// 愤怒:颈部摇头 + 躯干后仰 + 手臂放下
for (int i = 0; i < 4; i++) {
neckJoint.moveTo(10);
delay(150);
neckJoint.moveTo(-10);
delay(150);
}
torsoJoint.moveTo(-15);
armJoint.moveTo(30);
Serial.println("执行动作:愤怒 - 摇头+后仰");
}
}
void setup() {
Serial.begin(115200);
// 初始化BLDC关节(位置控制模式)
neckJoint.linkDriver(&neckDriver);
torsoJoint.linkDriver(&torsoDriver);
armJoint.linkDriver(&armDriver);
neckJoint.init(); torsoJoint.init(); armJoint.init();
neckJoint.controller = MotionControlType::angle;
torsoJoint.controller = MotionControlType::angle;
armJoint.controller = MotionControlType::angle;
Serial.println("情绪表达系统就绪");
}
void loop() {
// 模拟从案例二获取的情绪决策
// 实际项目中:String emotion = multiModalFusion();
String emotion = "happy"; // 测试用
// 执行情绪动作
executeMultiJointAction(emotion);
// FOC更新(需在循环中持续调用)
neckJoint.loopFOC();
torsoJoint.loopFOC();
armJoint.loopFOC();
delay(2000); // 动作间隔
}
核心逻辑:不同情绪对应不同的关节姿态序列。BLDC配合FOC算法使动作极其平滑——开心时的点头是缓慢渐进的,不开心时的轻拍是柔和有节奏的,愤怒时的摇头是快速但仍有控制的。这种动作的“质感”直接决定了情感表达的真实感,而BLDC的低转矩脉动特性是实现这一点的硬件基础。
要点解读
- 触摸交互是“主动情绪响应”的核心触发信号
在导览陪伴场景中,用户主动触摸机器人是最强烈、最明确的交互意图。触摸位置和时长携带丰富的情感信息:头部长时间抚摸表示亲近,左侧轻拍表示寻求帮助,右侧触摸则可能表示拒绝。触摸评分应作为多模态融合中的最高权重输入,因为它是用户“主动发起”的,而非机器人被动感知的。
- 多模态加权融合的关键是“自适应权重”而非固定权重
触摸、表情、语音三种模态的可靠性不同。触摸在用户主动交互时最可靠,但在无人接触时无信号;表情依赖光照和摄像头质量;语音受环境噪声影响。融合策略应采用“动态权重”——当触摸信号存在时提高其权重,触摸信号消失时回退到表情和语音的融合。
- BLDC+FOC是实现“有温度”情绪表达的执行基础
情绪表达的本质是“动作质感”。步进电机的顿挫感会让机器人的“点头”像机械臂抓取,而有刷电机的噪声会让“安抚轻拍”变得刺耳。BLDC配合FOC算法提供的平滑转矩输出和低噪音特性,使机器人的动作具有“生物感”——开心时的点头是柔和的,愤怒时的摇头是有力度但仍有控制的。
- 情绪决策与动作执行需要解耦,便于独立调试
案例二(情绪融合)和案例三(动作执行)应保持接口清晰:融合模块输出标准化的情绪字符串(“happy”/“neutral”/“unhappy”/“angry”),动作模块根据字符串调用预定义的动作序列。这种解耦使调试时可以独立验证融合逻辑(串口打印情绪)和动作效果(手动传入情绪字符串),降低系统复杂度。
- 主动情绪响应需要“防误触”和“情绪惯性”机制
触摸传感器容易因振动或电磁干扰产生误触发。工程上需要设置“持续检测阈值”——连续多个控制周期检测到触摸信号才确认有效。同时,情绪状态应有“惯性”——从开心切换到愤怒不应是瞬时跳变,而应通过中间状态平滑过渡,否则机器人的动作会显得突兀、不自然。

4、导览路径识别+基础情绪反馈程序(核心:自主导览+基础互动)
该案例聚焦导览场景的核心需求——自主跟踪预订路径并输出对应情绪,是环境感知与情绪响应的基础实现,适用于固定路线的展厅、园区导览场景。
核心逻辑
环境感知:红外循迹模块识别地面黑白路径,超声波检测前方障碍物;
运动控制:基于路径偏移量调整双轮速度,实现稳定循迹;
情绪响应:正常循迹时输出“愉悦”灯光(暖黄色呼吸灯),避障时输出“警示”灯光(红色闪烁),提升导览的亲切感。
// 导览路径识别+基础情绪反馈程序
#include <DallasTemperature.h> // 可选,温度检测扩展用
// ===== 硬件引脚定义 =====
// 循迹模块引脚
#define LEFT_TRACKING_PIN 2 // 左侧红外循迹
#define RIGHT_TRACKING_PIN 3 // 右侧红外循迹
// 电机控制引脚
#define LEFT_MOTOR_PWM 9
#define LEFT_MOTOR_DIR 10
#define RIGHT_MOTOR_PWM 11
#define RIGHT_MOTOR_DIR 12// 超声波引脚
#define SONAR_TRIG 18
#define SONAR_ECHO 19
// 情绪输出引脚
#define EMOTION_LED_PIN 8 // 情绪灯带(WS2812需接数字引脚)
#define BUZZER_PIN 7
// ===== 核心参数 =====
const int TRACKING_ERROR_THRESHOLD = 100; // 路径偏移阈值,用于判断偏离
const int COLLISION_DISTANCE = 20; // 避障距离阈值(cm)
const int NORMAL_SPEED = 70; // 正常循迹速度(PWM值0~255)
const int TURN_SPEED = 40; // 转向速度
// ===== 状态变量 =====
bool trackingState = false; // 循迹状态:true=正常循迹 false=偏离
bool collision = false; // 障碍物状态bool emotionState = 0; // 情绪状态:0=愉悦 1=警示
long lastEmotionUpdate = 0; // 情绪更新计时
const long EMOTION_INTERVAL = 500; // 情绪灯更新间隔(ms)
// ===== 函数声明 =====
void readTracking();
bool checkCollision();
void controlMotor();
void updateEmotion();
void setEmotionLed(uint8_t r, uint8_t g, uint8_t b);
void setup() {
Serial.begin(115200);
// 循迹引脚初始化
pinMode(LEFT_TRACKING_PIN, INPUT);
pinMode(RIGHT_TRACKING_PIN, INPUT);
// 电机引脚初始化
pinMode(LEFT_MOTOR_PWM, OUTPUT);
pinMode(LEFT_MOTOR_DIR, OUTPUT);
pinMode(RIGHT_MOTOR_PWM, OUTPUT);
pinMode(RIGHT_MOTOR_DIR, OUTPUT);
// 超声波引脚初始化
pinMode(SONAR_TRIG, OUTPUT);
pinMode(SONAR_ECHO, INPUT);
// 情绪外设初始化
pinMode(EMOTION_LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
// 初始化情绪灯
setEmotionLed(255, 170, 0); // 初始暖黄色
Serial.println("导览机器人初始化完成");
}
void loop() {
unsigned long currentTime = millis();
// 1. 环境感知:读取循迹与障碍物状态
readTracking();bool collision = checkCollision();
// 2. 运动控制:跟随路径+避障
controlMotor();
// 3. 情绪响应:定时更新情绪表达
if (currentTime - lastEmotionUpdate > EMOTION_INTERVAL) {
updateEmotion(trackingState, collision);
lastEmotionUpdate = currentTime;
}
// 4. 调试输出
Serial.print("循迹状态:"); Serial.print(trackingState?"正常":"偏离");
Serial.print(" 避障状态:"); Serial.print(collision?"有障碍":"正常");
Serial.print(" 情绪:"); Serial.println(emotionState?"警示":"愉悦");
delay(50);
}
// 读取循迹状态并判断是否偏离
void readTracking() {
bool leftTrack = digitalRead(LEFT_TRACKING_PIN) == LOW; // 红外检测到黑线返回LOW
bool rightTrack = digitalRead(RIGHT_TRACKING_PIN) == LOW;
// 双路均检测到黑线:沿路径直行
if (leftTrack && rightTrack) {
trackingState = true;
}
// 仅单路检测到黑线:偏离路径,需调整
else if (leftTrack && !rightTrack) {
trackingState = false;
} else if (!leftTrack && rightTrack) {
trackingState = false;
}
// 均未检测到:路径丢失,停车预警
else {
trackingState = false;
}
}
// 检测前方障碍物
bool checkCollision() {
digitalWrite(SONAR_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(SONAR_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(SONAR_TRIG, LOW);
long duration = pulseIn(SONAR_ECHO, HIGH);
int distance = (duration * 0.0343) / 2; // 距离cm
return distance < COLLISION_DISTANCE;
}
// 电机控制:根据循迹与避障状态调整
void controlMotor() {
if (collision) {
// 有障碍:停车
analogWrite(LEFT_MOTOR_PWM, 0);
analogWrite(RIGHT_MOTOR_PWM, 0);
return;
}
bool leftTrack = digitalRead(LEFT_TRACKING_PIN) == LOW;
bool rightTrack = digitalRead(RIGHT_TRACKING_PIN) == LOW;
if (leftTrack && rightTrack) {
// 沿路径直行:双轮同速
analogWrite(LEFT_MOTOR_PWM, NORMAL_SPEED);
analogWrite(RIGHT_MOTOR_PWM, NORMAL_SPEED);
} else if (leftTrack && !rightTrack) {
// 偏右:左转修正
analogWrite(LEFT_MOTOR_PWM, TURN_SPEED);
analogWrite(RIGHT_MOTOR_PWM, NORMAL_SPEED);
} else if (!leftTrack && rightTrack) {
// 偏左:右转修正
analogWrite(LEFT_MOTOR_PWM, NORMAL_SPEED);
analogWrite(RIGHT_MOTOR_PWM, TURN_SPEED);
} else {
// 路径丢失:缓慢前进寻找路径
analogWrite(LEFT_MOTOR_PWM, TURN_SPEED);
analogWrite(RIGHT_MOTOR_PWM, TURN_SPEED);
}
}
// 情绪响应:根据状态切换情情绪表达
void updateEmotion(bool tracking, bool coll) {
if (coll) {
emotionState = 1;
// 警示:红色闪烁+蜂鸣器短响
setEmotionLed(255, 0, 0);
digitalWrite(BUZZER_PIN, HIGH);
delay(50);
digitalWrite(BUZZER_PIN, LOW);
} else if (!tracking) {
emotionState = 1;
// 路径丢失:橙色呼吸(轻微警示)
setEmotionLed(255, 130, 0);
digitalWrite(BUZZER_PIN, LOW);
} else {
emotionState = 0;
// 正常循迹:暖黄色呼吸(愉悦)
setEmotionLed(255, 170, 0);
digitalWrite(BUZZER_PIN, LOW);
}
}
// WS2812灯颜色设置(需兼容Adafruit_NeoPixel库,此处为简化写法)
// 实际应用可引入Adafruit_NeoPixel库实现精准色彩控制
void setEmotionLed(uint8_t r, uint8_t g, uint8_t b) {
// 简化:通过模拟PWM输出基础颜色,完整效果需搭配灯带驱动
analogWrite(EMOTION_LED_PIN, r);
}
程序优化方向
引入Adafruit_NeoPixel库,实现WS2812灯带的丰富灯效(如呼吸、渐变、闪烁),让情绪表达更生动;
加入编码器测速,实现更精准的循迹速度控制,避免因速度波动导致的情绪状态异常;
支持多路径切换,根据不同导览路线切换对应的情绪主题色,适配多场景导览。
5、人员互动感知+主动情绪响应程序(核心:人机互动+情绪识别)
该案例聚焦导览中的陪伴属性,通过感知人员动作与互动请求,主动输出对应的情绪反馈,适用于展会、病房、养老等需要人机互动的导览陪伴场景。
核心逻辑
环境感知:超声波检测人员距离,红外手势模块识别“跟随/停止”请求;
互动响应:根据人员的互动指令调整机器人动作(跟随/停留/靠近);
情绪响应:针对“跟随”输出绿色在指示灯(热情),“停止”输出蓝色指示灯(耐心),无互动时输出紫色指示灯(待命),若人员靠近则主动靠近并柔声提示。
// 人员互动感知+主动情绪响应程序—陪伴导览场景
#include <Adafruit_NeoPixel.h> // WS2812灯带驱动
// ===== 硬件引脚定义 =====
// 距离与手势感知
#define SONAR_TRIG 18
#define SONAR_ECHO 19
#define GESTURE_PIN 4 // 红外手势传感器(简化:检测挥手/停止动作)
// 电机控制
#define LEFT_MOTOR_PWM 9
#define LEFT_MOTOR_DIR 10
#define RIGHT_MOTOR_PWM 11
#define RIGHT_MOTOR_DIR 12
// 情绪外设
#define EMOTION_LED_PIN 6
#define SERVO_PIN 5 // 舵机(点头互动)
#define BUZZER_PIN 7 #define SPEAKER_PIN A0 // 语音提示输出引脚(可外接功放)
// Adafruit_NeoPixel初始化
#define LED_COUNT 8
Adafruit_NeoPixel strip(LED_COUNT, EMOTION_LED_PIN, NEO_GRB + NEO_KHZ800);
// ===== 核心参数 =====
const int FOLLOW_DISTANCE = 80; // 跟随距离(cm)
const int NEAR_DISTANCE = 30; // 靠近提示距离(cm)
const int MOVE_SPEED = 40; // 跟随移动速度
const int IDLE_SPEED = 0; // 待机速度
// ===== 状态变量 =====
int personDistance = 0; // 人员距离bool gestureCommand; // 手势指令:true=跟随 false=暂停跟随
bool following = false; // 是否处于跟随状态
int emotionType; // 情绪类型:0=待命 1=跟随 2=暂停 3=靠近提示
long lastGestureUpdate = 0; // 手势检测计时
const long GESTURE_INTERVAL = 200;
// ===== 函数声明 =====
int measureDistance();
bool readGesture();
void controlRobot();
void updateEmotion();
void setEmotionEffect(uint32_t color, bool blink);
void setup() {
Serial.begin(115200);
// 传感器初始化
pinMode(SONAR_TRIG, OUTPUT);
pinMode(SONAR_ECHO, INPUT);
pinMode(GESTURE_PIN, INPUT);
// 电机初始化
pinMode(LEFT_MOTOR_PWM, OUTPUT);
pinMode(LEFT_MOTOR_DIR, OUTPUT);
pinMode(RIGHT_MOTOR_PWM, OUTPUT);
pinMode(RIGHT_MOTOR_DIR, OUTPUT);
// 情绪外设初始化
strip.begin();
strip.show();
pinMode(SERVO_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(SPEAKER_PIN, OUTPUT);
Serial.println("互动陪伴机器人初始化完成");
}
void loop() {
unsigned long currentTime = millis();
// 1. 环境感知:获取人员距离与手势指令
personDistance = measureDistance();
if (currentTime - lastGestureUpdate > GESTURE_INTERVAL) {
gestureCommand = readGesture();
following = gestureCommand || (personDistance < FOLLOW_DISTANCE && following);
lastGestureUpdate = currentTime;
}
// 2. 动作控制:跟随/停留/靠近
controlRobot();
// 3. 情绪响应:根据状态输出情绪
updateEmotion();
// 4. 调试输出
Serial.print("人员距离:"); Serial.print(personDistance);
Serial.print("cm 手势:"); Serial.print(gestureCommand? "跟随":"暂停");
Serial.print(" 情绪:"); Serial.println(emotionType);
delay(50);
}
// 距离测量
int measureDistance() {
digitalWrite(SONAR_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(SONAR_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(SONAR_TRIG, LOW);
long duration = pulseIn(SONAR_ECHO, HIGH);
return (duration * 0.0343) / 2;
}
// 读取手势指令(简化:检测引脚电平,实际搭配专用手势传感器更精准)
bool readGesture() {
// 简化逻辑:引脚高电平=挥手跟随,低电平=停止
return digitalRead(GESTURE_PIN) == HIGH;
}
// 机器人动作控制
void controlRobot() {
if (!following) {
// 非跟随状态:停车待命
analogWrite(LEFT_MOTOR_PWM, IDLE_SPEED);
analogWrite(RIGHT_MOTOR_PWM, IDLE_SPEED);
return;
}
// 距离过近:减速停止,避免碰撞
if (personDistance < NEAR_DISTANCE) {
analogWrite(LEFT_MOTOR_PWM, 0);
analogWrite(RIGHT_MOTOR_PWM, 0);
} else if (personDistance > FOLLOW_DISTANCE +20) {
// 距离过远:加速靠近
analogWrite(LEFT_MOTOR_PWM, MOVE_SPEED);
analogWrite(RIGHT_MOTOR_PWM, MOVE_SPEED);
} else {
// 距离合适:匀速跟随
analogWrite(LEFT_MOTOR_PWM, MOVE_SPEED - 5);
analogWrite(RIGHT_MOTOR_PWM, MOVE_SPEED - 5);
}
}
// 情绪响应更新
void updateEmotion() {
if (personDistance < NEAR_DISTANCE) {
emotionType = 3;
// 靠近提示:绿色呼吸+舵机点头+柔和提示音
setEmotionEffect(0X00FF00, false);
analogWrite(SERVO_PIN, 1500); // 舵机点头角度
playTone(880, 100); // 柔和提示音
} else if (following) {
emotionType = 1;
// 跟随状态:绿色跑马灯(热情)
setEmotionEffect(0X00FF00, false);
analogWrite(SERVO_PIN, 1000); // 舵机复位
digitalWrite(BUZZER_PIN, LOW);
} else if (gestureCommand == false && following) {
emotionType = 2;
// 暂停跟随:蓝色呼吸灯(耐心)
setEmotionEffect(0X0000FF, false);
analogWrite(SERVO_PIN, 1500); // 舵机点头回应
digitalWrite(BUZZER_PIN, LOW);
} else {
emotionType = 0;
// 待命状态:紫色呼吸(安静)
setEmotionEffect(0X8800FF, false);
analogWrite(SERVO_PIN, 900); // 舵机复位
digitalWrite(BUZZER_PIN, LOW);
}
}
// 情绪灯效设置(基于NeoPixel库)
void setEmotionEffect(uint32_t color, bool blink) {
if (blink) {
// 闪烁效果:间隔亮灯
if (millis() % 200 < 100) {
for (int i = 0; i < LED_COUNT; i++) {
strip.setPixelColor(i, color);
}
strip.show();
} else {
strip.show();
strip.clear();
strip.show();
}
} else {
// 呼吸效果:全灯渐变
for (int i = 0; i < LED_COUNT; i++) {
strip.setPixelColor(i, color);
}
strip.show();
}
}
// 简易音调播放(需外接功放模块)
void playTone(int frequency, int duration) {
tone(BUZZER_PIN, frequency, duration);
}
程序优化方向
引入手势识别传感器(如APDS9960),实现更精准的挥手、指向等互动指令识别;
加入语音识别模块,支持语音指令(如“请跟我来”“停下休息”),强化陪伴属性;
基于人员位置实现自主避让,当人员挡住路径时,机器人主动绕行并输出“抱歉”的情绪表达,提升交互友好度。
6、导览异常应对+情绪安抚程序(核心:异常感知+情绪安抚)
该案例聚焦导览过程中的突发异常场景,当检测到环境危险、设备异常等情况时,主动输出安抚或警示情绪,适用于医院、实验室、危险品存储区等对安全与情绪感知要求高的导览场景。
核心逻辑
异常感知:检测环境异常(温度过高、烟雾浓度超标)、设备异常(电机电流过大、路径长时间丢失);
分级应对:轻微异常时安抚情绪(黄色温和灯),严重异常时警示(红色闪烁+蜂鸣器);
情绪安抚:异常时输出“稳定”的情绪表达(如黄色呼吸灯、舵机缓慢摆动),缓解访客紧张感,同时明确警示风险。
// 导览异常应对+情绪安抚程序—安全导览场景#include <Adafruit_NeoPixel.h>
#include <DallasTemperature.h>
// ===== 硬件引脚定义 =====
// 异常感知
#define SMOKE_PIN A0 // 烟雾传感器(MQ-2,模拟量输出)
#define TEMP_PIN 2 // DS18B20温度传感器
#define MOTOR_CURRENT_PIN A1 // 电机电流采样
// 电机与跟随控制
#define LEFT_MOTOR_PWM 9
#define LEFT_MOTOR_DIR 10
#define RIGHT_MOTOR_PWM 11
#define RIGHT_MOTOR_DIR 12
// 情绪外设
#define EMOTION_LED_PIN 6#define SERVO_PIN 5
#define BUZZER_PIN 7
// 温度传感器初始化
#define ONE_WIRE_BUS 3
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);#define LED_COUNT 8
Adafruit_NeoPixel strip(LED_COUNT, EMOTION_LED_PIN, NEO_GRB + NEO_KHZ800);
// ===== 异常阈值 =====
const float SMOKE_DANGER_THRESHOLD = 300; // 烟雾浓度阈值(模拟量,需校准)
const float TEMP_DANGER_THRESHOLD = 45.0; // 环境温度阈值(℃)
const int MOTOR_CURRENT_THRESHOLD = 200; // 电机电流阈值(ADC值)
const int PATH_LOST_TIME = 3000; // 路径丢失超时阈值(ms)
// ===== 状态变量 =====
float smokeValue = 0; // 烟雾浓度值
float tempValue = 0; // 环境温度
int motorCurrent = 0; // 电机电流
bool dangerState = false; // 是否为危险状态(严重异常)
bool warningState = false; // 是否为预警状态(轻微异常)
long pathLostStart = 0; // 路径丢失计时
int emotionType; // 情绪类型:0=正常 1=轻微预警 2=危险警示
// ===== 函数声明 =====
void readEnvironment();
void checkAnomaly();
void emergencyControl();
void updateEmotion();
void setEmotionEffect(uint32_t color, bool blink, bool gradient);
void setup() {
Serial.begin(115200);
sensors.begin();
strip.begin();
// 传感器引脚初始化
pinMode(SMOKE_PIN, INPUT);
pinMode(MOTOR_CURRENT_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(SERVO_PIN, OUTPUT);
// 电机初始化
pinMode(LEFT_MOTOR_PWM, OUTPUT);
pinMode(LEFT_MOTOR_DIR, OUTPUT);
pinMode(RIGHT_MOTOR_PWM, OUTPUT);
pinMode(RIGHT_MOTOR_DIR, OUTPUT);
Serial.println("安全导览机器人初始化完成");
}
void loop() {
// 1. 环境感知:读取所有异常相关数据
readEnvironment();
// 2. 异常检测:判断是否存在异常及等级
checkAnomaly();
// 3. 应急控制:异常时减速/停车
emergencyControl();
//4. 情绪响应:输出对应安抚/警示情绪
updateEmotion();
//5. 调试输出
Serial.print("烟雾:"); Serial.print(smokeValue);
Serial.print(" 温度:"); Serial.print(tempValue);
Serial.print("℃ 电流:"); Serial.print(motorCurrent);
Serial.print(" 状态:"); Serial.print(dangerState ? "危险" : (warningState ? "预警" : "正常"));
Serial.println();
delay(100);
}
// 读取环境传感器数据
void readEnvironment() {
sensors.requestTemperatures();
tempValue = sensors.getTempCByIndex(0);
smokeValue = analogRead(SMOKE_PIN);
motorCurrent = analogRead(MOTOR_CURRENT_PIN);
}
// 异常检测与分级
void checkAnomaly() {
dangerState = false;
warningState = false;
// 严重异常判定:任意参数超危险阈值
if (smokeValue > SMOKE_DANGER_THRESHOLD ||
tempValue > TEMP_DANGER_THRESHOLD ||
motorCurrent > MOTOR_CURRENT_THRESHOLD) {
dangerState = true;
return;
}
// 轻微异常判定:参数接近阈值
if (smokeValue > SMOKE_DANGER_THRESHOLD * 0.7 ||
tempValue > (TEMP_DANGER_THRESHOLD -5.0)) {
warningState = true;
}
}
// 应急运动控制
void emergencyControl() {
if (dangerState) {
// 严重异常:立即停车
analogWrite(LEFT_MOTOR_PWM, 0);
analogWrite(RIGHT_MOTOR_PWM, 0);
} else if (warningState) {
// 轻微异常:减速行驶
analogWrite(LEFT_MOTOR_PWM, 30);
analogWrite(RIGHT_MOTOR_PWM, 30);
} else {
// 正常行驶
analogWrite(LEFT_MOTOR_PWM, 60);
analogWrite(RIGHT_MOTOR_PWM, 60);
}
}
// 情绪响应更新
void updateEmotion() {
if (dangerState) {
emotionType = 2;
// 危险警示:红色闪烁+蜂鸣器长响+舵机剧烈摆动(警示但不刺耳,需调节)
setEmotionEffect(0XFF0000, true, false);
digitalWrite(BUZZER_PIN, HIGH);
// 舵机缓慢摆动,避免过于突兀模拟安抚动作
for (int angle = 900; angle <= 2100; angle += 100) {
analogWrite(SERVO_PIN, angle);
delay(30);
}
for (int angle = 2100; angle >= 900; angle -= 100) {
analogWrite(SERVO_PIN, angle);
delay(30);
}
} else if (warningState) {
emotionType = 1;
emotionType = 1;
// 轻微预警:黄色呼吸灯(温和安抚)+蜂鸣器间歇响
setEmotionEffect(0XFFAA00, false, false);
if (millis() % 500 < 250) {
digitalWrite(BUZZER_PIN, HIGH);
} else {
digitalWrite(BUZZER_PIN, LOW);
}
analogWrite(SERVO_PIN, 1200); // 舵机保持中间位置,稳定姿态
} else {
emotionType = 0;
// 正常:绿色渐变灯(舒适)
setEmotionEffect(0X00FF00, false, true);
digitalWrite(BUZZER_PIN, LOW);
analogWrite(SERVO_PIN, 1000);
}
}
// 情绪灯效设置(支持闪烁、呼吸、渐变)
void setEmotionEffect(uint32_t color, bool blink, bool gradient) {
if (blink) {
// 闪烁效果
if (millis() % 150 < 75) {
for (int i = 0; i < LED_COUNT; i++) {
strip.setPixelColor(i, color);
}
} else {
strip.clear();
}
} else if (gradient) {
// 呼吸效果:亮度渐变
uint8_t brightness = (sin(millis() * 0.001) * 127) + 128;
for (int i = 0; i < LED_COUNT; i++) {
strip.setPixelColor(i, color);
}
strip.setBrightness(brightness);
} else {
// 呼吸效果:恒亮度
for (int i = 0; i < LED_COUNT; i++) {
strip.setPixelColor(i, color);
}
strip.setBrightness(200);
}
strip.show();
}
程序优化方向
加入多类异常检测(如湿度、光照),适配更多环境场景;
实现异常分级的情绪缓冲,当异常从预警转为危险时,情绪表达逐步过渡(如黄色→红色),避免情绪切换过于突兀,增强安抚感;
异常时自动上报异常信息至后台管理系统,实现远程监控,同时机器人的情绪表达与后台指令联动,适配远程导览管理需求。
要点解读
要点1:环境感知的多维度融合——情绪响应的前提
环境感知是情绪响应的触发源,需实现多维度数据融合,而非依赖单一传感器,需把握三点:
感知覆盖全面:同时覆盖路径、人员、环境、设备四类核心数据,既要识别导览路径,也要感知人员互动、环境异常与设备状态,避免情绪表达脱离场景;
阈值合理校准:所有感知阈值需结合现场环境校准(如烟雾阈值需适配场景的空气质量,距离阈值需匹配机器人移动速度),避免误触发导致情绪表达混乱;
采样同步协调:多传感器采样需保持一定同步性,避免因时间差导致的状态判断误差(如距离测量与手势识别间隔过大,导致跟随逻辑冲突)。
要点2:情绪表达的场景适配性——导览陪伴的核心
情绪表达需贴合导览陪伴的属性,既要考虑情绪的真实性,也要兼顾场景的实用性,关键把握:
情绪状态分级:将情绪按场景需求分为“愉悦/热情/耐心/警示/安抚”等等级,每个等级对应明确的外设输出(灯光颜色、灯效、声音、舵机动作),避免表达模糊;
情绪表达柔和化:导览陪伴场景以服务为核心,警示情绪避免过于刺眼(如红色闪烁频率不宜过高)、声音避免过于刺耳(蜂鸣器频率控制在舒适范围),安抚情绪侧重温和感(如暖色调、柔和声音);
情绪与动作联动:情绪输出需与机器人动作同步(如停车时同步输出警示情绪,跟随时同步输出热情情绪),让访客感知到情绪与行为的一致性。
要点3:情绪响应的实时性与稳定性——交互体验的保障
情绪响应需兼顾实时性与稳定性,避免延迟过高或频繁切换,关键要点:
响应延迟可控:环境感知、运动控制、情绪响应的循环周期需控制在100ms以内,确保异常或互动发生后,情绪能及时输出,不影响体验;
状态去抖处理:对感知数据进行滤波与去抖(如距离测量多次采样取平均、异常状态连续触发才判定),避免因瞬时干扰导致情绪频繁切换;
异常情绪优先:当异常发生时,情绪表达需优先于常规情绪(如警示情绪覆盖愉悦情绪),确保安全相关信息能第一时间传递给访客。
要点4:外设输出的硬件兼容性与效果可控性
情绪表达依赖灯带、舵机、蜂鸣器等外设,需匹配硬件特性并控制输出效果:
外设选型匹配:WS2812灯带需使用标准的数字引脚与驱动库,舵机需选择适合机器人负载的小扭矩舵机,蜂鸣器需搭配功放模块实现清晰的声音输出,避免硬件不兼容导致情绪表达失效;
灯效控制精准:基于NeoPixel等成熟库实现灯效控制,明确区分纯色、闪烁、呼吸、渐变等效果,每种效果对应固定的情绪状态,避免模糊表达;
能耗与续航兼顾:情绪外设的输出强度需适配机器人续航,如待机时采用低亮度的呼吸灯,异常警示时可适当提高强度,平衡情绪效果与续航能力。
要点5:场景扩展的灵活性与可维护性
程序需具备强扩展性,便于适配不同导览陪伴场景,同时降低后续维护成本:
模块化设计:将感知、控制、情绪响应拆分为独立函数,后续新增传感器或情绪类型时,只需修改对应模块,无需重构整体代码(如新增光照传感器只需添加读取函数与异常判定逻辑);
参数可调设计:所有阈值、速度、情绪效果等参数均集中定义,支持串口或参数配置文件动态调整,无需修改代码即可适配不同场景;
调试输出完善:保留串口调试信息,实时输出感知数据、状态与情绪类型,便于后续排查情绪响应异常或感知误差,提升维护效率。
请注意:以上案例仅作为思路拓展的参考示例,不保证完全正确、适配所有场景或可直接编译运行。由于硬件平台、实际使用场景、Arduino 版本的差异,均可能影响代码的适配性与使用方法的选择。在实际编程开发时,请务必根据自身硬件配置、使用场景及具体功能需求进行针对性调整,并通过多次实测验证效果;同时需确保硬件接线正确,充分了解所用传感器、执行器等设备的技术规范与核心特性。对于涉及硬件操作的代码,使用前务必核对引脚定义、电平参数等关键信息的准确性与安全性,避免因参数错误导致硬件损坏或运行异常。

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)