在这里插入图片描述
Arduino BLDC 之“观众互动导览机器人(语音交互 + 实时情绪表达)”,本质上是“语音/视觉/触摸多模态感知 → 对话意图与情绪状态估计 → 表情、动作、语音合成、底盘运动联合输出 → BLDC FOC 柔顺执行”的具身交互闭环:语音交互负责“听懂观众要什么”,情绪表达负责“让机器人看起来像有态度、有温度地回应”,BLDC 则负责把点头、转头、靠近、后退、差速摆动等社交动作做得平滑、安静、可重复。 从工程实现看,这类系统通常采用“云-边-端”或“上位机 + MCU”的分层架构:上位机/ESP32-S3 处理语音识别、LLM 对话、情绪推理;Arduino/下位 MCU 负责 BLDC FOC 控制、关节角度跟踪和底盘运动执行。

主要特点

  1. 语音交互链路通常是“唤醒 → 识别 → 理解 → 生成 → 合成 → 播放”
    导览机器人需要先通过麦克风阵列或数字麦克风采集语音,再进行唤醒词检测、ASR 语音识别、意图理解或 LLM 对话生成,最后通过 TTS 语音合成输出回答。 在 Arduino 生态中,Uno/Nano 通常不足以独立完成完整 ASR/LLM,更常见的是由 ESP32-S3、树莓派或上位机承担语音与对话,Arduino/下位机通过串口、I2C 或 CAN 接收“表情/动作/底盘指令”。
  2. 情绪表达需要“多通道协同”,不能只靠屏幕表情
    实时情绪表达通常由多个通道共同完成:头部/眼睛/嘴部关节角度、底盘姿态、灯光颜色、语音语调、屏幕表情和动作节奏。BLDC 在这里的价值不只是驱动轮子,还可以驱动头部云台、眼睛机构、身体摆动关节或差速底盘。FOC 电流环能让点头、转头、靠近等动作更柔顺,避免舵机式“咔哒”动作破坏互动氛围。
  3. 情绪状态机比“直接映射”更稳定
    如果每次检测到情绪就立刻切换动作,机器人会显得抽搐、夸张、不可信。更合理的做法是维护一个内部情绪状态,例如平静、好奇、欢迎、困惑、安抚、兴奋,并根据语音内容、观众距离、交互轮次和当前状态进行平滑过渡。 例如观众刚靠近时进入“欢迎”,提问未识别时进入“困惑”,连续多次失败时进入“安抚并引导换一种说法”。
  4. BLDC 差速/关节运动可承载“非语言社交信号”
    导览机器人的情绪不只来自语音内容,也来自身体语言。BLDC 底盘可以通过缓慢靠近表达欢迎,通过原地小幅前后晃动表达兴奋,通过缓慢偏航表达“正在思考”,通过后退和降低速度表达“给你空间”。头部或眼部 BLDC 关节则可以配合语音节奏做微幅点头、注视跟随和视线偏移,增强“在场感”。
  5. 实时性需要分层调度
    语音识别和 LLM 响应通常在百毫秒到秒级,情绪状态更新在几十到几百毫秒级,而 BLDC FOC 电流环需要在千赫兹到万赫兹级运行。因此应把高频电机控制放在下位机或独立任务中,避免语音处理阻塞电机闭环。 否则机器人说话时可能出现动作卡顿、底盘抖动或表情延迟。
  6. 隐私与可解释反馈很重要
    导览机器人常处于公共空间,会持续采集语音甚至图像。应明确告知观众机器人正在录音/识别,并提供物理静音开关、摄像头遮挡结构或本地处理优先策略。情绪表达也不应过度拟人化,避免让观众误以为机器人具有真实情感或医疗判断能力。

应用场景
博物馆/美术馆导览:观众提问展品信息,机器人通过语音回答,并配合转头、点头、屏幕表情和缓慢靠近增强讲解氛围。
景区/园区智能导游:在人流较多的入口、岔路口提供路线推荐、景点介绍和情绪化欢迎动作。
商场/展厅导购:识别观众停留和提问意图,引导至目标展位,同时用灯光、表情和底盘动作吸引注意。
校园/企业展厅接待:作为固定点位或移动接待终端,回答参观者常见问题,并在等待时保持自然微动作。
儿童科普互动:通过问答、表情和肢体动作提升参与感,但需限制互动强度,避免拥挤和误触。
科研/创客教学平台:用于验证语音交互、情绪状态机、BLDC 柔顺控制和多模态反馈协同。

需要注意的事项
不要让 Arduino 单独承担语音识别和 LLM 推理
Uno/Nano 的算力和内存不足以稳定运行 ASR、LLM 和 FOC 闭环。更合理的是 ESP32-S3/树莓派/上位机负责语音与对话,Arduino/下位 MCU 负责 BLDC 控制和传感器采集。
语音链路延迟会直接影响情绪表达节奏
如果 ASR + LLM + TTS 总延迟过高,机器人点头、表情和回答会不同步,观众容易觉得“反应迟钝”。应设置超时降级策略,例如延迟过高时先播放“正在思考”的表情和微动作,再输出完整回答。
情绪表达必须设置强度和频率上限
过度夸张的摇头、快速点头、频繁靠近会让观众不适,尤其对儿童和老人更需谨慎。应限制关节角速度、底盘接近速度和表情切换频率,并保持动作平滑。
BLDC 关节需要柔顺控制,而非硬位置跳变
情绪动作如果直接给目标角度,容易产生冲击。应使用 S 型速度规划、加速度限制或阻抗/力矩限幅,使头部转动、底盘靠近和肢体摆动更自然。
公共环境噪声会显著降低语音识别率
展馆、商场和景区背景噪声复杂,单麦克风效果通常不稳定。建议使用麦克风阵列、近场拾音、噪声抑制和关键词唤醒,并在识别失败时提供明确的二次引导。
底盘靠近观众时必须保留安全距离
情绪化“靠近”不能变成压迫式逼近。应设置最小安全距离、最大接近速度和急停条件,并在观众后退时立即停止跟随。
情绪识别结果只能作为交互参考,不能作为诊断依据
即使系统判断观众“不开心”“焦虑”或“困惑”,也应避免输出心理诊断、医疗建议或过度干预话术。导览机器人更适合提供陪伴式回应、信息引导和人工服务转接。
需要设计离线/弱网降级模式
展馆网络可能不稳定。若云端 ASR/LLM 不可用,应切换到本地关键词指令、预置讲解词或固定导览流程,避免机器人完全沉默。
多机器人同时部署时要避免互相干扰
若多台导览机器人共享同一空间,应区分唤醒词、语音输出方向、蓝牙/WiFi 信道和底盘避障策略,防止同时响应同一位观众或互相遮挡路径。

在这里插入图片描述
1、离线语音指令 + 多关节 BLDC 情绪动作映射(基础级)
场景说明:面向博物馆展厅的固定点位导览机器人。观众通过唤醒词和语音指令与机器人互动,机器人根据指令内容执行对应的多关节动作(点头、挥手、摇头)和 LED 表情。采用 LD3320 离线语音模块,响应延迟在 100ms 级别。

#include <SimpleFOC.h>
#include <Wire.h>

// ===== 多关节 BLDC(颈部 + 左臂 + 右臂)=====
BLDCMotor neckMotor(7);
BLDCMotor armLeftMotor(7);
BLDCMotor armRightMotor(7);
BLDCDriver3PWM neckDriver(9, 10, 11, 8);
BLDCDriver3PWM armLeftDriver(3, 5, 6, 4);
BLDCDriver3PWM armRightDriver(44, 45, 46, 43);

// ===== 离线语音模块(LD3320 简化接口)=====
// 实际使用需引用相应语音库
#define VOICE_CMD_NONE    0
#define VOICE_CMD_HELLO   1
#define VOICE_CMD_INTRO   2
#define VOICE_CMD_QUESTION 3
#define VOICE_CMD_GOODBYE 4

int voiceCmd = VOICE_CMD_NONE;

// ===== 情绪状态与动作映射 =====
enum EmotionState {
  EMOTION_NEUTRAL,
  EMOTION_HAPPY,
  EMOTION_CURIOUS,
  EMOTION_SAD
};

EmotionState currentEmotion = EMOTION_NEUTRAL;

// ===== LED 表情(简化:数字引脚模拟 PWM 亮度)=====
const int ledEyeLeft = 22;
const int ledEyeRight = 23;
const int ledMouth = 24;

void setup() {
  Serial.begin(115200);

  // 初始化 BLDC
  neckMotor.init(); neckMotor.initFOC();
  armLeftMotor.init(); armLeftMotor.initFOC();
  armRightMotor.init(); armRightMotor.initFOC();

  neckMotor.controller = MotionControlType::angle;
  armLeftMotor.controller = MotionControlType::angle;
  armRightMotor.controller = MotionControlType::angle;

  // 初始化语音模块(简化:实际调用 voiceRec.begin() 等)
  initVoiceModule();

  pinMode(ledEyeLeft, OUTPUT);
  pinMode(ledEyeRight, OUTPUT);
  pinMode(ledMouth, OUTPUT);

  Serial.println("导览机器人就绪");
}

void loop() {
  // ===== 1. 感知:语音指令 =====
  voiceCmd = checkVoiceCommand();

  // ===== 2. 调度:情绪映射 =====
  mapCommandToEmotion(voiceCmd);

  // ===== 3. 执行:BLDC 动作 + LED 表情 =====
  executeEmotionAction(currentEmotion);

  // FOC 闭环
  neckMotor.loopFOC();
  armLeftMotor.loopFOC();
  armRightMotor.loopFOC();

  delay(20);
}

void initVoiceModule() {
  // 实际使用 LD3320 库:
  // voiceRec.begin(10);
  // voiceRec.addCommand("你好", VOICE_CMD_HELLO);
  // voiceRec.addCommand("介绍", VOICE_CMD_INTRO);
  // voiceRec.addCommand("问题", VOICE_CMD_QUESTION);
  // voiceRec.addCommand("再见", VOICE_CMD_GOODBYE);
  // voiceRec.start();
}

int checkVoiceCommand() {
  // 实际调用 voiceRec.checkCommand()
  // 此处模拟:根据串口输入返回
  if (Serial.available()) {
    char c = Serial.read();
    if (c == '1') return VOICE_CMD_HELLO;
    if (c == '2') return VOICE_CMD_INTRO;
    if (c == '3') return VOICE_CMD_QUESTION;
    if (c == '4') return VOICE_CMD_GOODBYE;
  }
  return VOICE_CMD_NONE;
}

void mapCommandToEmotion(int cmd) {
  switch (cmd) {
    case VOICE_CMD_HELLO:
      currentEmotion = EMOTION_HAPPY;
      break;
    case VOICE_CMD_INTRO:
      currentEmotion = EMOTION_CURIOUS;
      break;
    case VOICE_CMD_QUESTION:
      currentEmotion = EMOTION_CURIOUS;
      break;
    case VOICE_CMD_GOODBYE:
      currentEmotion = EMOTION_SAD;
      break;
    default:
      // 保持当前情绪
      break;
  }
}

void executeEmotionAction(EmotionState emotion) {
  switch (emotion) {
    case EMOTION_HAPPY:
      // 开心:颈部点头 + 双臂抬起挥动 + 眼睛高亮
      neckMotor.move(0.15f);
      armLeftMotor.move(0.5f);
      armRightMotor.move(0.5f);
      analogWrite(ledEyeLeft, 255);
      analogWrite(ledEyeRight, 255);
      analogWrite(ledMouth, 200);
      break;

    case EMOTION_CURIOUS:
      // 好奇:颈部微侧 + 单臂抬起 + 眼睛中等亮度
      neckMotor.move(0.1f);
      armLeftMotor.move(0.3f);
      armRightMotor.move(0.0f);
      analogWrite(ledEyeLeft, 180);
      analogWrite(ledEyeRight, 180);
      analogWrite(ledMouth, 100);
      break;

    case EMOTION_SAD:
      // 悲伤:颈部低垂 + 双臂下垂 + 眼睛暗
      neckMotor.move(-0.1f);
      armLeftMotor.move(-0.2f);
      armRightMotor.move(-0.2f);
      analogWrite(ledEyeLeft, 50);
      analogWrite(ledEyeRight, 50);
      analogWrite(ledMouth, 30);
      break;

    case EMOTION_NEUTRAL:
    default:
      // 中性:复位
      neckMotor.move(0);
      armLeftMotor.move(0);
      armRightMotor.move(0);
      analogWrite(ledEyeLeft, 120);
      analogWrite(ledEyeRight, 120);
      analogWrite(ledMouth, 60);
      break;
  }
}

代码要点:离线语音模块的 checkCommand() 返回指令编号,通过 mapCommandToEmotion() 映射为情绪状态,再由 executeEmotionAction() 驱动 BLDC 关节和 LED。BLDC 使用角度控制模式,FOC 确保动作平滑无抖动。LED 亮度用 analogWrite 模拟情绪强度。

2、多模态融合(语音 + 触摸)+ 自适应情绪权重(进阶级)
场景说明:导览机器人不仅响应语音,还通过触摸传感器感知观众互动意图。触摸被赋予最高权重——轻拍和长时间抚摸对应不同的情绪反馈。语音、触摸、环境三模态加权融合后输出最终情绪状态,驱动 BLDC 动作。

#include <SimpleFOC.h>
#include <Wire.h>

// ===== 多关节 BLDC =====
BLDCMotor neckMotor(7);
BLDCMotor torsoMotor(7);
BLDCMotor armLeftMotor(7);
BLDCMotor armRightMotor(7);

BLDCDriver3PWM neckDriver(9, 10, 11, 8);
BLDCDriver3PWM torsoDriver(3, 5, 6, 4);
BLDCDriver3PWM armLeftDriver(44, 45, 46, 43);
BLDCDriver3PWM armRightDriver(22, 23, 24, 21);

// ===== 触摸传感器 =====
const int touchPins[3] = {A0, A1, A2};  // 头部、左侧、右侧
int touchState[3] = {0, 0, 0};

// ===== 多模态评分 =====
float touchScore = 0.5f;
float voiceScore = 0.5f;
float faceScore = 0.5f;  // 简化:模拟表情评分

// ===== 情绪状态 =====
enum EmotionState {
  EMOTION_HAPPY,
  EMOTION_NEUTRAL,
  EMOTION_UNHAPPY,
  EMOTION_ANGRY
};

EmotionState currentEmotion = EMOTION_NEUTRAL;

// ===== 触摸时长追踪 =====
unsigned long touchStartTime[3] = {0, 0, 0};
int lastTouchIndex = -1;

void setup() {
  Serial.begin(115200);

  // BLDC 初始化
  neckMotor.init(); neckMotor.initFOC();
  torsoMotor.init(); torsoMotor.initFOC();
  armLeftMotor.init(); armLeftMotor.initFOC();
  armRightMotor.init(); armRightMotor.initFOC();

  neckMotor.controller = MotionControlType::angle;
  torsoMotor.controller = MotionControlType::angle;
  armLeftMotor.controller = MotionControlType::angle;
  armRightMotor.controller = MotionControlType::angle;

  // 触摸引脚
  for (int i = 0; i < 3; i++) {
    pinMode(touchPins[i], INPUT);
  }

  Serial.println("多模态导览机器人就绪");
}

void loop() {
  // ===== 1. 感知:触摸检测 =====
  int touchIdx = detectTouch();
  float touchIntent = evaluateTouchIntent(touchIdx);

  // ===== 2. 感知:语音(简化模拟)=====
  float voiceIntent = simulateVoiceIntent();

  // ===== 3. 调度:三模态加权融合 =====
  String emotion = multiModalFusion(touchIntent, voiceIntent, faceScore);
  mapEmotionString(emotion);

  // ===== 4. 执行:多关节协同动作 =====
  executeMultiJointAction(currentEmotion);

  // FOC 闭环
  neckMotor.loopFOC();
  torsoMotor.loopFOC();
  armLeftMotor.loopFOC();
  armRightMotor.loopFOC();

  delay(30);
}

int detectTouch() {
  for (int i = 0; i < 3; i++) {
    if (digitalRead(touchPins[i]) == HIGH) {
      if (lastTouchIndex != i) {
        // 新触摸开始
        touchStartTime[i] = millis();
        lastTouchIndex = i;
      }
      touchState[i] = 1;
      return i;
    } else {
      touchState[i] = 0;
    }
  }
  lastTouchIndex = -1;
  return -1;
}

float evaluateTouchIntent(int touchIndex) {
  if (touchIndex < 0) return 0.5f;  // 无触摸,中性

  unsigned long duration = millis() - touchStartTime[touchIndex];

  if (touchIndex == 0) {  // 头部触摸
    if (duration > 3000) return 0.9f;  // 长时间抚摸 → 亲近/开心
    else return 0.7f;                   // 短时轻拍 → 友好
  } else if (touchIndex == 1) {  // 左侧身体
    return 0.8f;  // 寻求帮助/互动
  } else if (touchIndex == 2) {  // 右侧身体
    return 0.3f;  // 可能的拒绝或低落
  }
  return 0.5f;
}

float simulateVoiceIntent() {
  // 实际调用离线语音模块获取情绪评分
  // 简化:返回中性偏积极
  return 0.6f;
}

String multiModalFusion(float touch, float voice, float face) {
  // 自适应权重:触摸优先(主动交互权重 0.4)
  float fusedScore = (touch * 0.4f) + (face * 0.3f) + (voice * 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 mapEmotionString(String emotion) {
  if (emotion == "happy") currentEmotion = EMOTION_HAPPY;
  else if (emotion == "neutral") currentEmotion = EMOTION_NEUTRAL;
  else if (emotion == "unhappy") currentEmotion = EMOTION_UNHAPPY;
  else currentEmotion = EMOTION_ANGRY;
}

void executeMultiJointAction(EmotionState emotion) {
  switch (emotion) {
    case EMOTION_HAPPY:
      // 开心:颈部点头 + 躯干前倾 + 手臂挥动
      neckMotor.move(0.15f);
      torsoMotor.move(0.1f);
      armLeftMotor.move(0.6f);
      armRightMotor.move(0.6f);
      Serial.println("太棒啦!我们一起做游戏吧~");
      break;

    case EMOTION_NEUTRAL:
      neckMotor.move(0);
      torsoMotor.move(0);
      armLeftMotor.move(0);
      armRightMotor.move(0);
      Serial.println("今天过得怎么样?有什么想和我聊的吗~");
      break;

    case EMOTION_UNHAPPY:
      // 不开心:低头 + 微侧 + 手臂轻拍安抚
      neckMotor.move(-0.1f);
      torsoMotor.move(-0.05f);
      armLeftMotor.move(0.2f);
      armRightMotor.move(0.2f);
      Serial.println("没关系,我在这里陪着你~");
      break;

    case EMOTION_ANGRY:
      // 生气:后仰 + 双臂收紧
      neckMotor.move(-0.15f);
      torsoMotor.move(-0.1f);
      armLeftMotor.move(-0.1f);
      armRightMotor.move(-0.1f);
      Serial.println("我需要安静一下...");
      break;
  }
}

代码要点:evaluateTouchIntent() 根据触摸位置和持续时长计算意图评分——长时间抚摸头部得分最高(亲近),触摸右侧身体得分最低。multiModalFusion() 赋予触摸最高权重(0.4),因为触摸是观众最主动的交互意图表达。多关节动作覆盖颈部、躯干和双臂,形成“全身情绪语言”。

3、分层架构——高频 FOC + 中频情绪推理 + 低频叙事状态机(工业级)
场景说明:面向科技馆的长时间运行导览机器人。采用分层式硬实时情感闭环架构:高频层(中断)运行 BLDC FOC 电流环保障动作平滑,中频层(任务)进行传感器采集与轻量情绪推理,低频层(主循环)处理状态机更新与叙事逻辑。情绪表达通过 BLDC 的“柔顺力矩控制”实现——悲伤时动作带有“迟疑”,兴奋时动作轻快。

#include <SimpleFOC.h>
#include <Wire.h>

// ===== 多关节 BLDC =====
BLDCMotor neckMotor(7);
BLDCMotor torsoMotor(7);
BLDCMotor armLeftMotor(7);
BLDCMotor armRightMotor(7);

BLDCDriver3PWM neckDriver(9, 10, 11, 8);
BLDCDriver3PWM torsoDriver(3, 5, 6, 4);
BLDCDriver3PWM armLeftDriver(44, 45, 46, 43);
BLDCDriver3PWM armRightDriver(22, 23, 24, 21);

// ===== 情感状态机 =====
enum InteractionState {
  STATE_NEUTRAL,
  STATE_ENGAGED,      // 观众表现出兴趣
  STATE_EXPLAINING,   // 讲解中
  STATE_CONCERNED,    // 观众困惑/低落
  STATE_FAREWELL      // 告别
};

InteractionState currentState = STATE_NEUTRAL;

// ===== 情绪强度(影响动作幅度和速度)=====
float emotionIntensity = 0.5f;

// ===== 交互历史(简化 EEPROM 模拟)=====
struct InteractionHistory {
  int visitCount;
  bool lastVisitHappy;
  float lastTouchScore;
} history = {0, false, 0.5f};

// ===== 分层控制变量 =====
unsigned long lastMidLayer = 0;
unsigned long lastLowLayer = 0;
const unsigned long MID_LAYER_PERIOD = 100;  // 10Hz 情绪推理
const unsigned long LOW_LAYER_PERIOD = 500;  // 2Hz 状态机更新

// ===== 当前感知数据 =====
float currentTouchScore = 0.5f;
float currentVoiceScore = 0.5f;
String currentVoiceCmd = "";

void setup() {
  Serial.begin(115200);

  // BLDC 初始化(力矩控制模式,用于柔顺情绪表达)
  neckMotor.init(); neckMotor.initFOC();
  torsoMotor.init(); torsoMotor.initFOC();
  armLeftMotor.init(); armLeftMotor.initFOC();
  armRightMotor.init(); armRightMotor.initFOC();

  neckMotor.controller = MotionControlType::torque;
  torsoMotor.controller = MotionControlType::torque;
  armLeftMotor.controller = MotionControlType::torque;
  armRightMotor.controller = MotionControlType::torque;

  // 设置力矩限制(安全)
  neckMotor.torque_controller = TorqueControlType::voltage;
  neckMotor.voltage_limit = 6;
  torsoMotor.voltage_limit = 6;
  armLeftMotor.voltage_limit = 4;
  armRightMotor.voltage_limit = 4;

  Serial.println("分层导览机器人就绪");
  Serial.println("状态: 中性");
}

void loop() {
  // ===== 高频层:FOC 电流环(由 SimpleFOC 内部定时器保障)=====
  neckMotor.loopFOC();
  torsoMotor.loopFOC();
  armLeftMotor.loopFOC();
  armRightMotor.loopFOC();

  unsigned long now = millis();

  // ===== 中频层:情绪推理(10Hz)=====
  if (now - lastMidLayer >= MID_LAYER_PERIOD) {
    lastMidLayer = now;
    midLayerEmotionInference();
  }

  // ===== 低频层:状态机与叙事(2Hz)=====
  if (now - lastLowLayer >= LOW_LAYER_PERIOD) {
    lastLowLayer = now;
    lowLayerStateMachine();
  }

  // ===== 持续执行:柔顺情绪动作 =====
  executeContinuousEmotion();
}

void midLayerEmotionInference() {
  // 简化的多模态情绪推理
  // 实际可接入轻量 CNN 或关键词/语调分析

  // 读取触摸(模拟)
  currentTouchScore = simulateTouchInput();

  // 读取语音(模拟)
  currentVoiceScore = simulateVoiceInput();

  // 融合情绪评分
  float fusedScore = currentTouchScore * 0.5f + currentVoiceScore * 0.5f;

  // 根据评分调整情绪强度
  emotionIntensity = constrain(fusedScore, 0.2f, 1.0f);

  // 串口输出(调试)
  static int cnt = 0;
  if (++cnt % 10 == 0) {
    Serial.print("情绪强度: ");
    Serial.println(emotionIntensity, 2);
  }
}

void lowLayerStateMachine() {
  // 状态转换逻辑
  switch (currentState) {
    case STATE_NEUTRAL:
      if (emotionIntensity > 0.7f) {
        currentState = STATE_ENGAGED;
        Serial.println("状态 → 参与");
      }
      break;

    case STATE_ENGAGED:
      if (currentVoiceCmd == "介绍") {
        currentState = STATE_EXPLAINING;
        Serial.println("状态 → 讲解");
      } else if (emotionIntensity < 0.3f) {
        currentState = STATE_CONCERNED;
        Serial.println("状态 → 关心");
      }
      break;

    case STATE_EXPLAINING:
      if (currentVoiceCmd == "再见") {
        currentState = STATE_FAREWELL;
        Serial.println("状态 → 告别");
      }
      break;

    case STATE_CONCERNED:
      if (emotionIntensity > 0.6f) {
        currentState = STATE_ENGAGED;
        Serial.println("状态 → 参与");
      }
      break;

    case STATE_FAREWELL:
      if (emotionIntensity > 0.5f) {
        currentState = STATE_NEUTRAL;
        Serial.println("状态 → 中性");
      }
      break;
  }
}

void executeContinuousEmotion() {
  // 基于情绪强度和状态的连续力矩输出
  // 核心思路:情绪强度影响力矩大小和动作“柔顺度”

  float baseTorque = emotionIntensity * 0.5f;

  switch (currentState) {
    case STATE_ENGAGED:
      // 参与:轻快动作,力矩适中
      neckMotor.move(baseTorque * 0.3f);
      armLeftMotor.move(baseTorque * 0.6f);
      armRightMotor.move(baseTorque * 0.6f);
      torsoMotor.move(baseTorque * 0.1f);
      break;

    case STATE_EXPLAINING:
      // 讲解:稳定点头,力矩平缓
      neckMotor.move(sin(millis() / 500.0f) * baseTorque * 0.2f);
      armLeftMotor.move(baseTorque * 0.3f);
      armRightMotor.move(-baseTorque * 0.2f);
      torsoMotor.move(0);
      break;

    case STATE_CONCERNED:
      // 关心:轻柔,力矩偏小,动作缓慢
      neckMotor.move(-baseTorque * 0.15f);
      armLeftMotor.move(baseTorque * 0.2f);
      armRightMotor.move(baseTorque * 0.2f);
      torsoMotor.move(-baseTorque * 0.05f);
      break;

    case STATE_FAREWELL:
      // 告别:挥手动作
      armLeftMotor.move(sin(millis() / 300.0f) * baseTorque * 0.5f);
      armRightMotor.move(sin(millis() / 300.0f + PI) * baseTorque * 0.5f);
      neckMotor.move(baseTorque * 0.1f);
      torsoMotor.move(0);
      break;

    case STATE_NEUTRAL:
    default:
      neckMotor.move(0);
      torsoMotor.move(0);
      armLeftMotor.move(0);
      armRightMotor.move(0);
      break;
  }
}

float simulateTouchInput() {
  // 模拟触摸输入
  static unsigned long lastSim = 0;
  if (millis() - lastSim > 3000) {
    lastSim = millis();
    return 0.5f + (random(0, 100) / 100.0f) * 0.4f;
  }
  return currentTouchScore;
}

float simulateVoiceInput() {
  // 模拟语音输入
  static unsigned long lastSim = 0;
  if (millis() - lastSim > 5000) {
    lastSim = millis();
    int r = random(0, 4);
    if (r == 0) { currentVoiceCmd = "介绍"; return 0.8f; }
    if (r == 1) { currentVoiceCmd = "再见"; return 0.4f; }
    return 0.6f;
  }
  return currentVoiceScore;
}

代码要点:分层架构将 FOC 高频闭环、情绪推理中频更新、状态机低频决策分离,确保底层运动不被上层逻辑阻塞。BLDC 使用力矩控制模式而非角度模式——情绪强度直接影响输出力矩大小,实现“轻柔”或“轻快”的动作质感。executeContinuousEmotion() 在每次主循环中根据当前状态持续输出力矩,而非一次性位置指令。

要点解读

  1. 离线语音是导览场景“实时性 + 隐私性”的最优解

导览机器人在博物馆、科技馆等场所运行时,网络环境不可靠,且观众的语音数据涉及隐私。离线语音模块(如 LD3320)在本地方案中具有不可替代的优势:响应延迟在 100ms 级别,无网络依赖,语音数据完全在本地闭环处理。这使得机器人可以在嘈杂的展厅中稳定识别“你好”“介绍”“再见”等关键词,同时避免云端传输的隐私风险。

  1. 多模态加权融合的核心是“意图优先级”而非简单平均

观众触摸机器人头部,其交互意图的主动性远高于机器人“看到”观众微笑。案例二中触摸权重(0.4)高于表情(0.3)和语音(0.3),且触摸位置和时长影响评分——长时间抚摸头部得 0.9 分(亲近),短时轻拍得 0.7 分(友好),触摸右侧身体仅得 0.3 分。这种“意图优先级”设计使机器人能区分“观众在主动互动”与“观众只是路过看了一眼”。

  1. 情绪表达的本质是“运动质感”的调制,而非简单的位置指令

让 BLDC 从 A 点转到 B 点很容易,但让这个转动“看起来悲伤”或“看起来兴奋”需要调控力矩和速度曲线。案例三采用力矩控制模式:悲伤时输出力矩偏小、动作缓慢“迟疑”,兴奋时力矩增大、动作轻快。学术研究也验证了这一方向——情感调制运动(Affect-aware motion scaling)使机器人的手势在“情感兼容性”评分上比规则基线提升了 21%。

  1. 分层架构是 Arduino 平台同时保障“动作平滑”和“交互智能”的唯一可行方案

Arduino 无法在单一循环中同时完成 1kHz 级的 FOC 电流环、100Hz 级的传感器采集和情绪推理、以及状态机逻辑。案例三的三层架构将时间敏感度不同的任务分离:FOC 由 SimpleFOC 内部定时器保障(高频层),情绪推理每 100ms 执行(中频层),状态机每 500ms 更新(低频层)。这种分离确保了即使情绪推理偶尔耗时较长,BLDC 的 FOC 闭环也不会被阻塞。

  1. 交互历史存储使机器人从“刺激-反应”升级为“有记忆的陪伴”

导览机器人如果对每位观众都给出完全相同的反应,会显得机械。通过 EEPROM 或外部 Flash 存储短期交互历史(如“上次这位观众抚摸头部后情绪好转”),机器人可以在下次交互时做出更个性化的响应。在案例三中,InteractionHistory 结构体记录了访问次数、上次是否开心、上次触摸评分,状态机在转换时可参考这些历史数据。这种“上下文感知”是导览机器人从“工具”升级为“陪伴者”的关键一步。

在这里插入图片描述
4、基础语音交互+表情情绪表达
适用场景:展馆、博物馆等单体导览场景,通过语音指令触发导览动作,搭配LED表情模块实时反馈情绪状态。

#include <SimpleFOC.h>
#include <Arduino_LSD.h> // LED点阵屏
SimpleFOCMotor2 leftWheel = SimpleFOCMotor2(2,3,4,A0,A1);
SimpleFOCMotor2 rightWheel = SimpleFOCMotor2(5,6,7,A2,A3);
LSD lsd(12, 13, 11); // 12x12 LED点阵
int currentEmotion = 0; // 0:微笑 1:惊讶 2:疑惑 3:开心

// 语音指令映射:不同指令触发不同情绪
void processVoiceCommand(String command) {
  command.toLowerCase();
  if (command.indexOf("你好") != -1) {
    currentEmotion = 3; // 开心
    navigateTo(100, 0); // 向前移动打招呼
  } else if (command.indexOf("讲解") != -1) {
    currentEmotion = 0; // 微笑
    navigateTo(0, 50); // 原地旋转讲解
  } else if (command.indexOf("不知道") != -1) {
    currentEmotion = 2; // 疑惑
    stopMove();
  } else if (command.indexOf("危险") != -1) {
    currentEmotion = 1; // 惊讶
    stopMove();
  }
}

// 情绪展示:动态LED表情
void showEmotion() {
  lsd.clear();
  switch (currentEmotion) {
    case 0: // 微笑
      for(int i=1; i<=8; i++) lsd.setPixel(i, 6, 1);
      break;
    case 1: // 惊讶
      lsd.setPixel(4, 4, 1); lsd.setPixel(8, 4, 1); // 睁大眼
      break;
    case 2: // 疑惑
      lsd.setPixel(4, 4, 1); lsd.setPixel(8, 6, 1); // 歪头疑惑
      break;
    case 3: // 开心
      lsd.setPixel(3, 4, 1); lsd.setPixel(9, 4, 1); // 弯眼
      break;
  }
  lsd.draw();
}

// 基础移动控制
void navigateTo(int forward, int turn) {
  float vL = forward - turn * 0.5f;
  float vR = forward + turn * 0.5f;
  leftWheel.setVelocity(constrain(vL, -80, 80));
  rightWheel.setVelocity(constrain(vR, -80, 80));
}
void stopMove() {
  leftWheel.setVelocity(0);
  rightWheel.setVelocity(0);
}

void setup() {
  Serial.begin(9600);
  leftWheel.init(); rightWheel.init();
  leftWheel.controller = FOC_VELOCITY;
  rightWheel.controller = FOC_VELOCITY;
  lsd.begin();
}

void loop() {
  String voiceCmd = readVoiceCommand(); // 语音识别接口
  if (voiceCmd != "") processVoiceCommand(voiceCmd);
  showEmotion();
  delay(10);
}

6、语音情绪感知+多感官融合表达
适用场景:接待、展会等场景,通过用户语音语调识别情绪,结合动作、语音、表情多维度反馈。

#include <SimpleFOC.h>
#include <OLEDDisplay.h>
#include <EmotionDriver.h>
SimpleFOCMotor2 leftWheel = SimpleFOCMotor2(2,3,4,A0,A1);
SimpleFOCMotor2 rightWheel = SimpleFOCMotor2(5,6,7,A2,A3);
SimpleFOCMotor2 neckJoint = SimpleFOCMotor2(8,9,10,A4,A5); // 颈部关节
OLEDDisplay oled(128, 64);
EmotionDriver emotionDriver; // 情绪表达驱动
int userEmotion = 0; // 识别到的用户情绪:0:平静 1:兴奋 2:烦躁

// 语音情绪识别:基于语调、音量判断
int detectUserEmotion() {
  int volume = getVoiceVolume(); // 音量
  float pitch = getVoicePitch(); // 音调
  if (volume > 70 && pitch > 0.5f) return 1; // 兴奋
  else if (volume > 80 && pitch < 0.3f) return 2; // 烦躁
  else return 0; // 平静
}

// 多感官融合情绪表达
void expressEmotion() {
  switch (userEmotion) {
    case 1: // 用户兴奋→机器人愉悦反馈
      emotionDriver.setLEDColor(255, 255, 0); // 黄灯
      oled.setText("开心为您服务");
      neckJoint.set(0.2f, 50); // 抬头动作
      leftWheel.setVelocity(60); rightWheel.setVelocity(60); // 轻快移动
      break;
    case 2: // 用户烦躁→安抚反馈
      emotionDriver.setLEDColor(0, 0, 255); // 蓝灯
      oled.setText("请稍等,马上处理");
      neckJoint.set(-0.1f, 30); // 低头安抚
      leftWheel.setVelocity(0); rightWheel.setVelocity(0); // 停止干扰
      break;
    case 0: // 平静→常规反馈
      emotionDriver.setLEDColor(0, 255, 0); // 绿灯
      oled.setText("您好,我是导览机器人");
      neckJoint.set(0, 30); // 正常姿态
      break;
  }
}

void setup() {
  Serial.begin(9600);
  leftWheel.init(); rightWheel.init(); neckJoint.init();
  oled.init();
  emotionDriver.init();
}

void loop() {
  userEmotion = detectUserEmotion();
  expressEmotion();
  delay(20);
}

6、多人互动+群体情绪响应
适用场景:场馆入口、广场等多人场景,识别人群情绪与人数,调整导览策略与情绪表达。

#include <SimpleFOC.h>
#include <PIRSensor.h>
#include <HeartbeatModule.h>
SimpleFOCMotor2 leftWheel = SimpleFOCMotor2(2,3,4,A0,A1);
SimpleFOCMotor2 rightWheel = SimpleFOCMotor2(5,6,7,A2,A3);
PIRSensor pir(A0); // 人体红外传感器
HeartbeatModule heartbeat; // 心跳灯
int crowdCount = 0;
int crowdEmotion = 0;

// 人群数量与情绪识别(简化版,实际可接摄像头)
void detectCrowd() {
  crowdCount = pir.countPeople();
  // 多传感器融合判断情绪:人数+声音+移动速度
  int soundLevel = getSoundLevel();
  if (crowdCount > 3) crowdEmotion = (soundLevel > 60) ? 1 : 0;
  else if (crowdCount == 1) crowdEmotion = 2;
  else crowdEmotion = 3;
}

// 群体场景情绪响应
void responseToCrowd() {
  switch (crowdEmotion) {
    case 1: // 人群热闹→热情导览
      heartbeat.setSpeed(200); // 快闪心跳灯
      leftWheel.setVelocity(50); rightWheel.setVelocity(50); // 双向移动吸引注意
      break;
    case 2: // 单人→一对一贴心服务
      heartbeat.setSpeed(80); // 慢闪
      stopMove(); // 靠近用户
      break;
    case 3: // 无人→待机状态
      heartbeat.setSpeed(30); // 常亮微闪
      leftWheel.setVelocity(0); rightWheel.setVelocity(0);
      break;
    case 0: // 人群平静→常规导览
      heartbeat.setSpeed(100);
      leftWheel.setVelocity(40); rightWheel.setVelocity(40);
      break;
  }
}

void setup() {
  Serial.begin(9600);
  leftWheel.init(); rightWheel.init();
  heartbeat.init();
}

void loop() {
  detectCrowd();
  responseToCrowd();
  delay(50);
}

要点解读
语音交互需低延迟且容错,适配导览场景
语音识别需优化响应速度(≤1s),支持模糊匹配与中断识别,避免因环境噪音导致识别失败;同时设置默认应答逻辑,无有效指令时主动引导用户提问,提升互动体验。

情绪表达需多维度融合,提升互动自然度
单一表情反馈不足以传递情绪,需结合LED、动作、语音多通道输出:兴奋时搭配快节奏灯光、轻快动作,烦躁时搭配安抚语调与安静姿态,让用户直观感知机器人的情绪状态。

情绪识别需适配场景,避免误判
不同场景下情绪判断标准不同:接待场景需识别用户兴奋/烦躁,导览场景需关注兴趣度;算法需预留校准空间,结合环境参数(噪音、光线)动态调整识别阈值,减少误判。

运动控制需与情绪表达解耦,保证稳定性
情绪表达速度、动作幅度需独立于底盘移动控制,避免情绪驱动的运动影响导览稳定性;同时BLDC电机需设置速度上限,防止情绪触发的动作过快导致设备损坏。

需具备异常状态处理,保障互动安全
语音模块故障、情绪识别失效时,机器人需自动进入待机模式,停止移动并保持基础表情;同时设置紧急停止逻辑,遇到用户冲突或设备异常时立刻停机,避免影响馆内秩序与安全。

请注意:以上案例仅作为思路拓展的参考示例,不保证完全正确、适配所有场景或可直接编译运行。由于硬件平台、实际使用场景、Arduino 版本的差异,均可能影响代码的适配性与使用方法的选择。在实际编程开发时,请务必根据自身硬件配置、使用场景及具体功能需求进行针对性调整,并通过多次实测验证效果;同时需确保硬件接线正确,充分了解所用传感器、执行器等设备的技术规范与核心特性。对于涉及硬件操作的代码,使用前务必核对引脚定义、电平参数等关键信息的准确性与安全性,避免因参数错误导致硬件损坏或运行异常。
在这里插入图片描述

Logo

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

更多推荐