回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

Beetle 树莓派RP2350------初学者视角的试用第三步:桌面emo,打工人的摸鱼神器 简单

头像 蠍蠍蠍蠍蠍 2025.06.05 44 2

实在是不好意思难为情,一天输出三篇,虽然之前都把内容都准备完全了,今天潜下心思来把这段时间制作的内容都整理一下,下面来看看我们这次要做点什么吧!

image.png

结合上次对于oled的FLAPPYBIRD游戏制作,这次我们在基础上增加两个舵机,让屏幕作为机器人表情的输出部分进行设计,配合传感器实现触摸后的交互~

元件清单:

1.开发板:Beetle RP2350 x1

image.png

2.90度舵机: sg90 x2

image.png

3.oled屏幕:ssd1316

image.png

4.触摸传感器:

image.png

5.舵机云台底座

image.png

第二步:接线,研究了好久,还是没有搞懂接线图的绘制,暂时还是试用文字的方式给与说明哈!!!

1. Beetle RP2350 与 OLED 的接线

  • VCC:将 OLED 的 VCC 引脚连接到 Beetle RP2350 的 3.3V 电源引脚,为 OLED 提供工作电压。
  • GND:把 OLED 的 GND 引脚连接到 Beetle RP2350 的 GND 引脚,确保共地。
  • SCL:OLED 的 SCL 引脚连接到 Beetle RP2350 的 D5 引脚,用于 I2C 通信中的时钟信号传输。
  • SDA:OLED 的 SDA 引脚连接到 Beetle RP2350 的 D4 引脚,用于 I2C 通信中的数据传输。

2. Beetle RP2350 与舵机的接线

  • 水平舵机:水平舵机的控制信号线连接到 Beetle RP2350 的 D8 引脚(对应代码中的 HORIZONTAL_PIN)。舵机的电源正极(通常为红色线)连接到合适的电源,舵机的电源负极(通常为棕色或黑色线)连接到 GND。
  • 垂直舵机:垂直舵机的控制信号线连接到 Beetle RP2350 的 D9 引脚。同样,其电源正极连接到 5V 电源,电源负极连接到 GND。

3. Beetle RP2350 与按钮的接线

按钮的一端连接到 Beetle RP2350 的 D1 引脚,按钮的另一端连接到 GND。同时,由于代码中使用了内部上拉电阻,Beetle RP2350 会自动将该引脚拉高,当按钮按下时,引脚电平被拉低,从而检测到按钮的按下动作。

image.png

第三步:程序准备

驱动部分除了试用U8G2库驱动oled外,还需要试用servo库来驱动我们两个90度的舵机

安装方法如下:

image.png

在库内搜索并安装servo库后即可

第四步,开始我们的代码书写!

1. 库引入与引脚定义

  • 库引入:引入 Servo.h 库用于控制舵机,U8g2lib.h 库用于驱动 OLED 显示屏。
  • 引脚定义:定义了水平舵机控制引脚 HORIZONTAL_PIN 为 8,垂直舵机控制引脚 VERTICAL_PIN 为 9,按钮引脚 BUTTON_PIN 为 1

代码
#include <Servo.h>
#include <U8g2lib.h>

#define HORIZONTAL_PIN  8
#define VERTICAL_PIN    9
#define BUTTON_PIN      1

2. 全局变量声明

  • 对象声明:创建 u8g2 对象用于 OLED 显示控制,horizontalServo 和 verticalServo 对象分别控制水平和垂直舵机。
  • 基础角度定义:设置舵机的基础角度 BASE_HORIZ 和 BASE_VERT。
  • 表情枚举:定义 Emotion 枚举类型表示不同表情,并初始化当前表情为 NEUTRAL。
  • 动作状态变量:用于跟踪表情动作执行状态、待机动画状态、动作步骤、上次动作时间和待机动画时间等。
  • 眼睛位置参数:定义在 OLED 上绘制眼睛的位置和半径参数。

代码
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 0x3C);
Servo horizontalServo, verticalServo;
const int BASE_HORIZ = 40;
const int BASE_VERT = 90;
enum Emotion {NEUTRAL, HAPPY, SAD, SURPRISED, ANGRY, LOVE, COUNT};
Emotion currentEmotion = NEUTRAL;
bool isPerforming = false;
bool isIdleAnimating = true;
int actionStep = 0;
unsigned long lastActionTime = 0;
unsigned long lastIdleTime = 0;
int idleAnimationPhase = 0;
const int EYE_SPACING = 40;
const int EYE_Y = 30;
const int EYE_RADIUS = 8;

3. setup 函数

  • 初始化串口:设置串口波特率为 9600,用于调试输出。
  • 舵机与 OLED 初始化:将舵机连接到对应的引脚,并初始化 OLED 显示屏。
  • 按钮引脚设置:将按钮引脚设置为输入模式并启用内部上拉电阻。
  • 系统初始化:调用 resetToBase 函数将舵机复位到基础状态,调用 showEmotion 函数显示中性表情,并通过串口打印提示信息

代码
void setup() {
    Serial.begin(9600);
    horizontalServo.attach(HORIZONTAL_PIN);
    verticalServo.attach(VERTICAL_PIN);
    u8g2.begin();
    pinMode(BUTTON_PIN, INPUT_PULLUP);
    resetToBase();
    showEmotion(NEUTRAL);
    Serial.println("系统启动完成");
    Serial.println("按下按钮触发随机表情");
}

4. loop 函数

  • 按钮检测:检测按钮是否按下,通过软件防抖处理避免误触发。若按钮按下,调用 triggerRandomEmotion 函数触发随机表情,并等待按钮释放。
  • 动作执行:如果正在执行表情动作(isPerforming 为 true),调用 performAction 函数执行相应表情动作序列;否则,每 3 秒执行一次待机动画,并显示中性表情

代码
void loop() {
    if (digitalRead(BUTTON_PIN) == LOW) {
        delay(50); 
        if (digitalRead(BUTTON_PIN) == LOW) {
            triggerRandomEmotion();
            while (digitalRead(BUTTON_PIN) == LOW) delay(10);
        }
    }

    if (isPerforming) {
        performAction();  
    } else {
        if (millis() - lastIdleTime > 3000) {
            performIdleAnimation();
            lastIdleTime = millis();
        }
        showEmotion(NEUTRAL);
    }
}

5. 表情动作相关函数

  • performAction 函数:根据当前表情,按照一定时间间隔调用相应的表情动作函数。时间间隔由 getActionDelay 函数返回。
  • performHappyAction 函数:实现开心表情的动作序列,包括抬头和左右旋转,完成后恢复到基础状态并结束动作执行
  • 类似地,performSadAction、performSurprisedAction、performAngryAction 和 performLoveAction 函数分别实现悲伤、惊讶、生气和爱心表情的动作序列。

代码
void performAction() {
    if (millis() - lastActionTime > getActionDelay()) {
        lastActionTime = millis();
        switch(currentEmotion) {
            case HAPPY: performHappyAction(); break;
            case SAD: performSadAction(); break;
            case SURPRISED: performSurprisedAction(); break;
            case ANGRY: performAngryAction(); break;
            case LOVE: performLoveAction(); break;
            default: isPerforming = false; 
        }
    }
}
    switch(actionStep) {
        case 0: setPosition(BASE_HORIZ, 100); break;  
        case 1: setPosition(BASE_HORIZ-30, 100); break;  
        case 2: setPosition(BASE_HORIZ+30, 100); break;  
        case 3: setPosition(BASE_HORIZ-30, 100); break;  
        case 4: setPosition(BASE_HORIZ+30, 100); break;  
        case 5: setPosition(BASE_HORIZ-30, 100); break;  
        case 6: setPosition(BASE_HORIZ+30, 100); break;  
        case 7: resetToBase(); isPerforming = false; break;  
    }
    actionStep++;
    if (actionStep > 7) actionStep = 0;
}

6. 待机动画函数

  • performIdleAnimation 函数:在非表情动作执行期间,每 3 秒随机小幅度移动舵机位置,模拟待机动画,包含三个阶段,完成后回到基础位置。

代码
void performIdleAnimation() {
    if (!isPerforming && isIdleAnimating) {
        switch(idleAnimationPhase) {
            case 0: setPosition(BASE_HORIZ + random(-10, 10), BASE_VERT + random(-10, 10)); break;
            case 1: setPosition(BASE_HORIZ + random(-15, 15), BASE_VERT + random(-5, 5)); break;
            case 2: setPosition(BASE_HORIZ, BASE_VERT); break;
        }
        idleAnimationPhase = (idleAnimationPhase + 1) % 3;
    }
}

7. 辅助函数

  • getActionDelay 函数:根据当前表情返回不同的动作延迟时间,使不同表情的动作速度不同。
  • setPosition 函数:限制舵机角度在 0 到 180 度之间,设置舵机位置,并更新 OLED 上的表情显示。
  • showEmotion 函数:在 OLED 上绘制表情,先绘制基础眼睛,再根据不同表情绘制嘴巴、眉毛等特征。
  • resetToBase 函数:将舵机位置设置为基础角度,表情设置为中性,动作步骤清零。
  • triggerRandomEmotion 函数:随机选择一种表情(排除中性表情),设置为当前表情,开始执行表情动作,并通过串口打印触发的表情名称。
  • printEmotionName 函数:根据表情枚举值通过串口打印对应的表情名称。

    下面是整段程序的内容:

代码
#include <Servo.h>
#include <U8g2lib.h>

// 定义舵机控制引脚和按键引脚
#define HORIZONTAL_PIN  8
#define VERTICAL_PIN    9
#define BUTTON_PIN      1

// OLED初始化
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 0x3C);

// 舵机对象
Servo horizontalServo, verticalServo;

// 基础状态角度
const int BASE_HORIZ = 40;
const int BASE_VERT = 90;

// 表情枚举
enum Emotion {NEUTRAL, HAPPY, SAD, SURPRISED, ANGRY, LOVE, COUNT};
Emotion currentEmotion = NEUTRAL;

// 动作状态
bool isPerforming = false;      // 是否正在执行表情动作
bool isIdleAnimating = true;    // 是否执行待机动画
int actionStep = 0;             // 当前动作步骤
unsigned long lastActionTime = 0;  // 上次动作时间
unsigned long lastIdleTime = 0;    // 上次待机动画时间
int idleAnimationPhase = 0;        // 待机动画阶段

// 眼睛位置参数
const int EYE_SPACING = 40;
const int EYE_Y = 30;
const int EYE_RADIUS = 8;

void setup() {
    Serial.begin(9600);
    horizontalServo.attach(HORIZONTAL_PIN);
    verticalServo.attach(VERTICAL_PIN);
    u8g2.begin();

    // 设置按键引脚为输入模式(使用内部上拉电阻)
    pinMode(BUTTON_PIN, INPUT_PULLUP);

    // 初始化基础状态
    resetToBase();
    showEmotion(NEUTRAL);
    Serial.println("系统启动完成");
    Serial.println("按下按钮触发随机表情");
}

void loop() {
    // 检测按键按下(带软件防抖)
    if (digitalRead(BUTTON_PIN) == LOW) {
        delay(50); // 防抖延时
        if (digitalRead(BUTTON_PIN) == LOW) {
            triggerRandomEmotion();
            // 等待按键释放
            while (digitalRead(BUTTON_PIN) == LOW) delay(10);
        }
    }

    if (isPerforming) {
        performAction();  // 执行当前表情的动作序列
    } else {
        // 执行待机动画
        if (millis() - lastIdleTime > 3000) {
            performIdleAnimation();
            lastIdleTime = millis();
        }

        // 默认显示基础表情
        showEmotion(NEUTRAL);
    }
}

// 执行动作序列
void performAction() {
    if (millis() - lastActionTime > getActionDelay()) {
        lastActionTime = millis();

        switch(currentEmotion) {
            case HAPPY:
                performHappyAction();
                break;
            case SAD:
                performSadAction();
                break;
            case SURPRISED:
                performSurprisedAction();
                break;
            case ANGRY:
                performAngryAction();
                break;
            case LOVE:
                performLoveAction();
                break;
            default:
                isPerforming = false;  // 其他表情直接结束
        }
    }
}

// 开心表情动作:抬头左右旋转三次
void performHappyAction() {
    switch(actionStep) {
        case 0: setPosition(BASE_HORIZ, 100); break;  // 抬头
        case 1: setPosition(BASE_HORIZ-30, 100); break;  // 左转
        case 2: setPosition(BASE_HORIZ+30, 100); break;  // 右转
        case 3: setPosition(BASE_HORIZ-30, 100); break;  // 左转
        case 4: setPosition(BASE_HORIZ+30, 100); break;  // 右转
        case 5: setPosition(BASE_HORIZ-30, 100); break;  // 左转
        case 6: setPosition(BASE_HORIZ+30, 100); break;  // 右转
        case 7: resetToBase(); isPerforming = false; break;  // 恢复基础状态
    }
    actionStep++;
    if (actionStep > 7) actionStep = 0;
}

// 悲伤表情动作:慢慢抬头,然后慢慢低头
void performSadAction() {
    switch(actionStep) {
        case 0: setPosition(BASE_HORIZ, 100); break;  // 慢慢抬头
        case 1: setPosition(BASE_HORIZ, 120); break;  // 继续抬头
        case 2: delay(500); break;                     // 暂停
        case 3: setPosition(BASE_HORIZ, 80); break;   // 慢慢低头
        case 4: setPosition(BASE_HORIZ, 60); break;   // 继续低头
        case 5: resetToBase(); isPerforming = false; break;
    }
    actionStep++;
}

// 生气表情动作:快速左右转动
void performAngryAction() {
    switch(actionStep) {
        case 0: setPosition(BASE_HORIZ, 100); break;  // 抬头
        case 1: setPosition(BASE_HORIZ-20, 100); break;  // 快速左转
        case 2: setPosition(BASE_HORIZ+20, 100); break;  // 快速右转
        case 3: setPosition(BASE_HORIZ-20, 100); break;  // 快速左转
        case 4: setPosition(BASE_HORIZ+20, 100); break;  // 快速右转
        case 5: setPosition(BASE_HORIZ-20, 100); break;  // 快速左转
        case 6: setPosition(BASE_HORIZ+20, 100); break;  // 快速右转
        case 7: resetToBase(); isPerforming = false; break;
    }
    actionStep++;
}

// 惊讶表情动作
void performSurprisedAction() {
    switch(actionStep) {
        case 0: setPosition(BASE_HORIZ+30, 100); break;  // 右转
        case 1: setPosition(BASE_HORIZ-30, 100); break;  // 左转
        case 2: setPosition(BASE_HORIZ, 120); break;     // 抬头
        case 3: resetToBase(); isPerforming = false; break;
    }
    actionStep++;
}

// 爱心表情动作
void performLoveAction() {
    switch(actionStep) {
        case 0: setPosition(BASE_HORIZ, 120); break;   // 抬头
        case 1: setPosition(BASE_HORIZ+20, 120); break; // 右转
        case 2: setPosition(BASE_HORIZ-20, 120); break; // 左转
        case 3: resetToBase(); isPerforming = false; break;
    }
    actionStep++;
}

// 待机动画 - 小幅度随机移动
void performIdleAnimation() {
    if (!isPerforming && isIdleAnimating) {
        switch(idleAnimationPhase) {
            case 0:
                setPosition(BASE_HORIZ + random(-10, 10), BASE_VERT + random(-10, 10));
                break;
            case 1:
                setPosition(BASE_HORIZ + random(-15, 15), BASE_VERT + random(-5, 5));
                break;
            case 2:
                setPosition(BASE_HORIZ, BASE_VERT); // 回到中心位置
                break;
        }
        idleAnimationPhase = (idleAnimationPhase + 1) % 3;
    }
}

// 获取动作延迟时间(毫秒)
unsigned long getActionDelay() {
    if (currentEmotion == HAPPY) return 300;  // 开心表情动作较快
    if (currentEmotion == SAD) return 400;    // 悲伤表情动作较慢
    if (currentEmotion == ANGRY) return 200;  // 生气表情快速转动
    return 500;  // 其他表情动作中等速度
}

// 设置舵机位置
void setPosition(int h, int v) {
    // 限制舵机运动范围
    h = constrain(h, 0, 180);
    v = constrain(v, 0, 180);

    horizontalServo.write(h);
    verticalServo.write(v);
    showEmotion(currentEmotion);  // 更新表情显示
}

// 显示表情 - 使用实心圆作为眼睛
void showEmotion(Emotion emo) {
    u8g2.firstPage();
    do {
        u8g2.clearBuffer();

        // 绘制两个基础眼睛(实心圆)
        u8g2.drawDisc(64 - EYE_SPACING/2, EYE_Y, EYE_RADIUS); // 左眼
        u8g2.drawDisc(64 + EYE_SPACING/2, EYE_Y, EYE_RADIUS); // 右眼

        // 根据表情修改眼睛和添加嘴巴
        switch(emo) {
            case NEUTRAL: // 中性表情
                // 直线嘴巴
                u8g2.drawHLine(64 - 20, 50, 40);
                break;

            case HAPPY:   // 开心表情
                // 向上弯曲的嘴巴(笑脸)
                u8g2.drawDisc(64 - 20, 50, 5, U8G2_DRAW_UPPER_RIGHT);
                u8g2.drawDisc(64 + 20, 50, 5, U8G2_DRAW_UPPER_LEFT);
                u8g2.drawHLine(64 - 15, 50, 30);
                break;

            case SAD:     // 悲伤表情
                // 向下弯曲的嘴巴(哭脸)
                u8g2.drawDisc(64 - 20, 55, 5, U8G2_DRAW_LOWER_RIGHT);
                u8g2.drawDisc(64 + 20, 55, 5, U8G2_DRAW_LOWER_LEFT);
                u8g2.drawHLine(64 - 15, 55, 30);
                break;

            case SURPRISED: // 惊讶表情
                // 大圆嘴巴
                u8g2.drawCircle(64, 55, 10);
                // 小瞳孔
                u8g2.drawDisc(64 - EYE_SPACING/2, EYE_Y, 3);
                u8g2.drawDisc(64 + EYE_SPACING/2, EYE_Y, 3);
                break;

            case ANGRY:   // 生气表情
                // 倒V形嘴巴
                u8g2.drawLine(64 - 15, 50, 64, 60);
                u8g2.drawLine(64, 60, 64 + 15, 50);
                // 斜线眉毛
                u8g2.drawLine(64 - EYE_SPACING/2 - 10, EYE_Y - 15, 64 - EYE_SPACING/2 + 5, EYE_Y - 5);
                u8g2.drawLine(64 + EYE_SPACING/2 - 5, EYE_Y - 5, 64 + EYE_SPACING/2 + 10, EYE_Y - 15);
                break;

            case LOVE:    // 爱心表情
                // 绘制爱心
                u8g2.drawDisc(64, 55, 5, U8G2_DRAW_UPPER_LEFT | U8G2_DRAW_UPPER_RIGHT);
                u8g2.drawTriangle(59, 55, 69, 55, 64, 65);
                // 心形眼睛
                u8g2.drawDisc(64 - EYE_SPACING/2, EYE_Y, 3);
                u8g2.drawDisc(64 + EYE_SPACING/2, EYE_Y, 3);
                break;
        }

    } while(u8g2.nextPage());
}

// 复位到基础状态
void resetToBase() {
    setPosition(BASE_HORIZ, BASE_VERT);
    currentEmotion = NEUTRAL;
    actionStep = 0;
}

// 触发随机表情
void triggerRandomEmotion() {
    // 随机选择一个表情(排除NEUTRAL)
    Emotion newEmotion = static_cast<Emotion>(random(1, COUNT));

    currentEmotion = newEmotion;
    isPerforming = true;
    actionStep = 0;
    lastActionTime = millis();

    Serial.print("触发表情: ");
    printEmotionName(newEmotion);
}

// 打印表情名称
void printEmotionName(Emotion emo) {
    switch(emo) {
        case HAPPY: Serial.println("开心"); break;
        case SAD: Serial.println("悲伤"); break;
        case SURPRISED: Serial.println("惊讶"); break;
        case ANGRY: Serial.println("生气"); break;
        case LOVE: Serial.println("爱心"); break;
        default: Serial.println("中性");
    }
}

成功制作了一个桌面的摸鱼神器!!!


当然接下来就是针对这个小机器人动作和表情的调整,主要在提高动作的交互感受,这里我选择增大舵机的旋转速度来达到这个目的,同时再增加一些表情!

废话不多说 直接上代码

代码
#include <Servo.h>
#include <U8g2lib.h>

// 定义舵机控制引脚和按键引脚
#define HORIZONTAL_PIN  8
#define VERTICAL_PIN    9
#define BUTTON_PIN      1


// OLED初始化
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 0x3C);

// 舵机对象
Servo horizontalServo, verticalServo;

// 基础状态角度
const int BASE_HORIZ = 40;
const int BASE_VERT = 90;

// 垂直舵机动力补偿参数
const int VERTICAL_POWER_BOOST = 5;  // 垂直舵机额外角度补偿

// 表情枚举 - 增加新表情
enum Emotion {NEUTRAL, HAPPY, SAD, SURPRISED, ANGRY, LOVE, CONFUSED, EXCITED, SLEEPY, COUNT};
Emotion currentEmotion = NEUTRAL;

// 动作状态
bool isPerforming = false;      // 是否正在执行表情动作
bool isIdleAnimating = true;    // 是否执行待机动画
int actionStep = 0;             // 当前动作步骤
unsigned long lastActionTime = 0;  // 上次动作时间
unsigned long lastIdleStart = 0;   // 上次待机动画开始时间
unsigned long nextIdleTime = 0;    // 下次待机动画时间
int idleAnimationType = 0;         // 待机动画类型
unsigned long lastAngleReport = 0; // 上次角度报告时间

// 待机动画参数
const unsigned long IDLE_INTERVAL = 5000; // 待机动画间隔(毫秒)

// 眼睛位置参数
const int EYE_SPACING = 40;
const int EYE_Y = 30;
const int EYE_RADIUS = 8;

void setup() {
  Serial.begin(9600);
  horizontalServo.attach(HORIZONTAL_PIN);
  verticalServo.attach(VERTICAL_PIN);
  u8g2.begin();
  
  // 设置按键引脚为输入模式(使用内部上拉电阻)
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  
  // 初始化基础状态
  resetToBase();
  showEmotion(NEUTRAL);
  Serial.println("系统启动完成");
  Serial.println("按下D3按钮触发随机表情");
  
  // 随机种子
  randomSeed(analogRead(0));
  
  // 初始化待机动画计时
  nextIdleTime = millis() + IDLE_INTERVAL;
}

void loop() {
  // 检测按键按下(带软件防抖)
  if (digitalRead(BUTTON_PIN) == LOW) {
    delay(50); // 防抖延时
    if (digitalRead(BUTTON_PIN) == LOW) {
      triggerRandomEmotion();
      // 等待按键释放
      while (digitalRead(BUTTON_PIN) == LOW) delay(10);
    }
  }
  
  if (isPerforming) {
    performAction();  // 执行当前表情的动作序列
  } else {
    // 检查是否应该执行待机动画
    if (isIdleAnimating && millis() >= nextIdleTime) {
      performIdleAnimation();
    }
    
    // 默认显示基础表情
    showEmotion(NEUTRAL);
  }
  
  // 每500ms报告一次舵机角度
  if (millis() - lastAngleReport > 500) {
    reportServoAngles();
    lastAngleReport = millis();
  }
}

// 报告舵机角度到串口
void reportServoAngles() {
  int hAngle = horizontalServo.read();
  int vAngle = verticalServo.read();
  
  Serial.print("舵机角度: H=");
  Serial.print(hAngle);
  Serial.print("°, V=");
  Serial.print(vAngle);
  Serial.print("° | 表情: ");
  printEmotionName(currentEmotion);
  
  // 添加角度状态指示
  if (vAngle < BASE_VERT - 15) Serial.println(" (低头)");
  else if (vAngle > BASE_VERT + 15) Serial.println(" (抬头)");
  else if (hAngle < BASE_HORIZ - 15) Serial.println(" (左转)");
  else if (hAngle > BASE_HORIZ + 15) Serial.println(" (右转)");
  else Serial.println(" (中立)");
}

// 执行动作序列
void performAction() {
  if (millis() - lastActionTime > getActionDelay()) {
    lastActionTime = millis();
    
    // 报告当前动作步骤
    Serial.print("动作步骤: ");
    Serial.print(actionStep);
    Serial.print(" | ");
    
    switch(currentEmotion) {
      case HAPPY:
        Serial.println("执行开心动作");
        performHappyAction();
        break;
      case SAD:
        Serial.println("执行悲伤动作");
        performSadAction();
        break;
      case SURPRISED:
        Serial.println("执行惊讶动作");
        performSurprisedAction();
        break;
      case ANGRY:
        Serial.println("执行生气动作");
        performAngryAction();
        break;
      case LOVE:
        Serial.println("执行爱心动作");
        performLoveAction();
        break;
      case CONFUSED:
        Serial.println("执行困惑动作");
        performConfusedAction();
        break;
      case EXCITED:
        Serial.println("执行兴奋动作");
        performExcitedAction();
        break;
      case SLEEPY:
        Serial.println("执行困倦动作");
        performSleepyAction();
        break;
      default:
        isPerforming = false;  // 其他表情直接结束
    }
  }
}

// ================ 表情动作序列 - 增强版 ================

// 开心表情动作:更丰富的点头和摇摆
void performHappyAction() {
  switch(actionStep) {
    case 0: 
      Serial.println("快速抬头");
      setPosition(BASE_HORIZ, 100);
      delay(200);
      break;
    case 1: 
      Serial.println("左转");
      setPosition(BASE_HORIZ-25, 100);
      delay(250);
      break;
    case 2: 
      Serial.println("右转");
      setPosition(BASE_HORIZ+25, 100);
      delay(250);
      break;
    case 3: 
      Serial.println("左转");
      setPosition(BASE_HORIZ-20, 100);
      delay(200);
      break;
    case 4: 
      Serial.println("右转");
      setPosition(BASE_HORIZ+20, 100);
      delay(200);
      break;
    case 5: 
      Serial.println("快速点头");
      setPosition(BASE_HORIZ, 120);
      delay(150);
      setPosition(BASE_HORIZ, 100);
      delay(150);
      break;
    case 6: 
      Serial.println("点头两次");
      setPosition(BASE_HORIZ, 120);
      delay(100);
      setPosition(BASE_HORIZ, 100);
      delay(100);
      setPosition(BASE_HORIZ, 120);
      delay(100);
      setPosition(BASE_HORIZ, 100);
      delay(100);
      break;
    case 7: 
      Serial.println("小幅度摇摆");
      setPosition(BASE_HORIZ-10, 100);
      delay(150);
      setPosition(BASE_HORIZ+10, 100);
      delay(150);
      setPosition(BASE_HORIZ-10, 100);
      delay(150);
      setPosition(BASE_HORIZ+10, 100);
      delay(150);
      break;
    case 8: 
      Serial.println("恢复基础状态");
      resetToBase(); 
      isPerforming = false; 
      break;
  }
  actionStep++;
}

// 悲伤表情动作:更深的低头和颤抖
void performSadAction() {
  switch(actionStep) {
    case 0: 
      Serial.println("缓慢低头");
      setPosition(BASE_HORIZ, 70);
      delay(800);
      break;
    case 1: 
      Serial.println("轻微左移颤抖");
      setPosition(BASE_HORIZ-5, 70);
      delay(200);
      setPosition(BASE_HORIZ+5, 70);
      delay(200);
      setPosition(BASE_HORIZ-5, 70);
      delay(200);
      setPosition(BASE_HORIZ+5, 70);
      delay(200);
      break;
    case 2: 
      Serial.println("更低低头");
      setPosition(BASE_HORIZ, 60);
      delay(800);
      break;
    case 3: 
      Serial.println("颤抖");
      setPosition(BASE_HORIZ-3, 60);
      delay(150);
      setPosition(BASE_HORIZ+3, 60);
      delay(150);
      setPosition(BASE_HORIZ-3, 60);
      delay(150);
      setPosition(BASE_HORIZ+3, 60);
      delay(150);
      break;
    case 4: 
      Serial.println("最深深低头");
      setPosition(BASE_HORIZ, 50);
      delay(1000);
      break;
    case 5: 
      Serial.println("缓慢抬头");
      setPosition(BASE_HORIZ, 70);
      delay(600);
      break;
    case 6: 
      Serial.println("恢复基础状态");
      resetToBase(); 
      isPerforming = false; 
      break;
  }
  actionStep++;
}

// 生气表情动作:更剧烈的抖动
void performAngryAction() {
  switch(actionStep) {
    case 0: 
      Serial.println("快速抬头");
      setPosition(BASE_HORIZ, 100);
      delay(200);
      break;
    case 1: 
      Serial.println("剧烈左转");
      setPosition(BASE_HORIZ-40, 100);
      delay(150);
      break;
    case 2: 
      Serial.println("剧烈右转");
      setPosition(BASE_HORIZ+40, 100);
      delay(150);
      break;
    case 3: 
      Serial.println("快速左右抖动");
      setPosition(BASE_HORIZ-30, 100);
      delay(100);
      setPosition(BASE_HORIZ+30, 100);
      delay(100);
      setPosition(BASE_HORIZ-30, 100);
      delay(100);
      setPosition(BASE_HORIZ+30, 100);
      delay(100);
      break;
    case 4: 
      Serial.println("上下抖动");
      setPosition(BASE_HORIZ, 80);
      delay(120);
      setPosition(BASE_HORIZ, 100);
      delay(120);
      setPosition(BASE_HORIZ, 80);
      delay(120);
      setPosition(BASE_HORIZ, 100);
      delay(120);
      break;
    case 5: 
      Serial.println("组合抖动");
      setPosition(BASE_HORIZ-20, 80);
      delay(100);
      setPosition(BASE_HORIZ+20, 100);
      delay(100);
      setPosition(BASE_HORIZ-20, 80);
      delay(100);
      setPosition(BASE_HORIZ+20, 100);
      delay(100);
      break;
    case 6: 
      Serial.println("恢复基础状态");
      resetToBase(); 
      isPerforming = false; 
      break;
  }
  actionStep++;
}

// 惊讶表情动作:更夸张的反应
void performSurprisedAction() {
  switch(actionStep) {
    case 0: 
      Serial.println("快速抬头");
      setPosition(BASE_HORIZ, 120);
      delay(300);
      break;
    case 1: 
      Serial.println("向后仰");
      setPosition(BASE_HORIZ, 130);
      delay(300);
      break;
    case 2: 
      Serial.println("左右扫视");
      setPosition(BASE_HORIZ-40, 120);
      delay(250);
      setPosition(BASE_HORIZ+40, 120);
      delay(250);
      setPosition(BASE_HORIZ-30, 120);
      delay(200);
      setPosition(BASE_HORIZ+30, 120);
      delay(200);
      break;
    case 3: 
      Serial.println("快速小幅度左右");
      setPosition(BASE_HORIZ-10, 120);
      delay(100);
      setPosition(BASE_HORIZ+10, 120);
      delay(100);
      setPosition(BASE_HORIZ-10, 120);
      delay(100);
      setPosition(BASE_HORIZ+10, 120);
      delay(100);
      break;
    case 4: 
      Serial.println("恢复基础状态");
      resetToBase(); 
      isPerforming = false; 
      break;
  }
  actionStep++;
}

// 爱心表情动作:更完整的8字形
void performLoveAction() {
  switch(actionStep) {
    case 0: 
      Serial.println("抬头");
      setPosition(BASE_HORIZ, 100);
      delay(400);
      break;
    case 1: 
      Serial.println("右上");
      setPosition(BASE_HORIZ+30, 110);
      delay(400);
      break;
    case 2: 
      Serial.println("右下");
      setPosition(BASE_HORIZ+20, 120);
      delay(400);
      break;
    case 3: 
      Serial.println("左上");
      setPosition(BASE_HORIZ-30, 110);
      delay(400);
      break;
    case 4: 
      Serial.println("左下");
      setPosition(BASE_HORIZ-20, 120);
      delay(400);
      break;
    case 5: 
      Serial.println("右上");
      setPosition(BASE_HORIZ+30, 110);
      delay(400);
      break;
    case 6: 
      Serial.println("右下");
      setPosition(BASE_HORIZ+20, 120);
      delay(400);
      break;
    case 7: 
      Serial.println("左上");
      setPosition(BASE_HORIZ-30, 110);
      delay(400);
      break;
    case 8: 
      Serial.println("左下");
      setPosition(BASE_HORIZ-20, 120);
      delay(400);
      break;
    case 9: 
      Serial.println("恢复基础状态");
      resetToBase(); 
      isPerforming = false; 
      break;
  }
  actionStep++;
}

// 新增:困惑表情动作
void performConfusedAction() {
  switch(actionStep) {
    case 0: 
      Serial.println("轻微左倾");
      setPosition(BASE_HORIZ-15, 95);
      delay(500);
      break;
    case 1: 
      Serial.println("轻微右倾");
      setPosition(BASE_HORIZ+15, 95);
      delay(500);
      break;
    case 2: 
      Serial.println("轻微低头");
      setPosition(BASE_HORIZ, 80);
      delay(400);
      break;
    case 3: 
      Serial.println("轻微抬头");
      setPosition(BASE_HORIZ, 100);
      delay(400);
      break;
    case 4: 
      Serial.println("小幅度左右摆动");
      setPosition(BASE_HORIZ-10, 90);
      delay(200);
      setPosition(BASE_HORIZ+10, 90);
      delay(200);
      setPosition(BASE_HORIZ-10, 90);
      delay(200);
      setPosition(BASE_HORIZ+10, 90);
      delay(200);
      break;
    case 5: 
      Serial.println("恢复基础状态");
      resetToBase(); 
      isPerforming = false; 
      break;
  }
  actionStep++;
}

// 新增:兴奋表情动作
void performExcitedAction() {
  switch(actionStep) {
    case 0: 
      Serial.println("快速抬头");
      setPosition(BASE_HORIZ, 110);
      delay(150);
      break;
    case 1: 
      Serial.println("快速低头");
      setPosition(BASE_HORIZ, 80);
      delay(150);
      break;
    case 2: 
      Serial.println("快速抬头");
      setPosition(BASE_HORIZ, 110);
      delay(150);
      break;
    case 3: 
      Serial.println("快速左右");
      setPosition(BASE_HORIZ-30, 100);
      delay(100);
      setPosition(BASE_HORIZ+30, 100);
      delay(100);
      setPosition(BASE_HORIZ-30, 100);
      delay(100);
      setPosition(BASE_HORIZ+30, 100);
      delay(100);
      break;
    case 4: 
      Serial.println("八字形移动");
      setPosition(BASE_HORIZ+20, 110);
      delay(200);
      setPosition(BASE_HORIZ, 90);
      delay(200);
      setPosition(BASE_HORIZ-20, 110);
      delay(200);
      setPosition(BASE_HORIZ, 90);
      delay(200);
      break;
    case 5: 
      Serial.println("快速点头三次");
      for (int i = 0; i < 3; i++) {
        setPosition(BASE_HORIZ, 120);
        delay(80);
        setPosition(BASE_HORIZ, 100);
        delay(80);
      }
      break;
    case 6: 
      Serial.println("恢复基础状态");
      resetToBase(); 
      isPerforming = false; 
      break;
  }
  actionStep++;
}

// 新增:困倦表情动作
void performSleepyAction() {
  switch(actionStep) {
    case 0: 
      Serial.println("缓慢低头");
      setPosition(BASE_HORIZ, 70);
      delay(800);
      break;
    case 1: 
      Serial.println("轻微左右摇摆");
      setPosition(BASE_HORIZ-5, 70);
      delay(400);
      setPosition(BASE_HORIZ+5, 70);
      delay(400);
      break;
    case 2: 
      Serial.println("点头一次");
      setPosition(BASE_HORIZ, 80);
      delay(300);
      setPosition(BASE_HORIZ, 70);
      delay(300);
      break;
    case 3: 
      Serial.println("更深的低头");
      setPosition(BASE_HORIZ, 60);
      delay(1000);
      break;
    case 4: 
      Serial.println("轻微抬头");
      setPosition(BASE_HORIZ, 70);
      delay(600);
      break;
    case 5: 
      Serial.println("恢复基础状态");
      resetToBase(); 
      isPerforming = false; 
      break;
  }
  actionStep++;
}

// 待机动画 - 多种自然动作(修复触发逻辑)
void performIdleAnimation() {
  if (!isPerforming && isIdleAnimating) {
    Serial.print("待机动画: ");
    lastIdleStart = millis(); // 记录动画开始时间
    
    switch(idleAnimationType) {
      case 0: 
        Serial.println("轻微点头");
        setPosition(BASE_HORIZ, BASE_VERT-10);
        delay(300);
        setPosition(BASE_HORIZ, BASE_VERT);
        delay(300);
        break;
        
      case 1: 
        Serial.println("左右扫视");
        setPosition(BASE_HORIZ-15, BASE_VERT);
        delay(400);
        setPosition(BASE_HORIZ+15, BASE_VERT);
        delay(400);
        setPosition(BASE_HORIZ, BASE_VERT);
        delay(400);
        break;
        
      case 2: 
        Serial.println("小幅度8字形");
        setPosition(BASE_HORIZ+5, BASE_VERT-5);
        delay(300);
        setPosition(BASE_HORIZ-5, BASE_VERT+5);
        delay(300);
        setPosition(BASE_HORIZ-5, BASE_VERT-5);
        delay(300);
        setPosition(BASE_HORIZ+5, BASE_VERT+5);
        delay(300);
        setPosition(BASE_HORIZ, BASE_VERT);
        delay(300);
        break;

      case 3: 
        Serial.println("缓慢环顾");
        setPosition(BASE_HORIZ-20, BASE_VERT);
        delay(600);
        setPosition(BASE_HORIZ+20, BASE_VERT);
        delay(600);
        setPosition(BASE_HORIZ, BASE_VERT);
        delay(600);
        break;
    }
    
    // 随机选择下一个待机动画类型
    idleAnimationType = random(0, 4);
    
    // 设置下次待机动画时间(当前时间 + 间隔)
    nextIdleTime = millis() + IDLE_INTERVAL;
  }
}

// 获取动作延迟时间(毫秒)
unsigned long getActionDelay() {
  if (currentEmotion == HAPPY) return 400;  // 开心表情动作节奏
  if (currentEmotion == SAD) return 600;    // 悲伤表情动作较慢
  if (currentEmotion == ANGRY) return 300;  // 生气表情快速
  if (currentEmotion == EXCITED) return 200; // 兴奋表情非常快
  return 500;  // 其他表情动作中等速度
}

// 设置舵机位置(直接设置)
void setPosition(int h, int v) {
  // 限制舵机运动范围
  h = constrain(h, 0, 180);
  v = constrain(v, 0, 180);
  
  // 应用位置设置
  horizontalServo.write(h);
  
  // 垂直舵机增加补偿和保持力
  if (v > verticalServo.read()) {
    v = constrain(v + VERTICAL_POWER_BOOST, 0, 180);
  }
  verticalServo.write(v);
  
  // 增强保持力
  delay(5);
  verticalServo.write(v);
  
  // 显示当前表情
  showEmotion(currentEmotion);
  
  // 报告位置设置
  Serial.print("设置位置: H=");
  Serial.print(h);
  Serial.print("°, V=");
  Serial.print(v);
  Serial.println("°");
}

// 复位到基础状态
void resetToBase() {
  Serial.println("复位到基础状态");
  setPosition(BASE_HORIZ, BASE_VERT);
  currentEmotion = NEUTRAL;
  actionStep = 0;
  isPerforming = false;
  delay(500); // 确保复位完成
}

// 显示表情 - 使用实心圆作为眼睛
void showEmotion(Emotion emo) {
  u8g2.firstPage();
  do {
    u8g2.clearBuffer();
    
    // 绘制两个基础眼睛(实心圆)
    u8g2.drawDisc(64 - EYE_SPACING/2, EYE_Y, EYE_RADIUS); // 左眼
    u8g2.drawDisc(64 + EYE_SPACING/2, EYE_Y, EYE_RADIUS); // 右眼
    
    // 根据表情修改眼睛和添加嘴巴
    switch(emo) {
      case NEUTRAL: // 中性表情
        // 直线嘴巴
        u8g2.drawHLine(64 - 20, 50, 40);
        // 小瞳孔
        u8g2.setDrawColor(0);
        u8g2.drawDisc(64 - EYE_SPACING/2, EYE_Y, EYE_RADIUS-3);
        u8g2.drawDisc(64 + EYE_SPACING/2, EYE_Y, EYE_RADIUS-3);
        u8g2.setDrawColor(1);
        break;
        
      case HAPPY:   // 开心表情
        // 向上弯曲的嘴巴(笑脸)
        u8g2.drawDisc(64 - 20, 50, 5, U8G2_DRAW_UPPER_RIGHT);
        u8g2.drawDisc(64 + 20, 50, 5, U8G2_DRAW_UPPER_LEFT);
        u8g2.drawHLine(64 - 15, 50, 30);
        // 眯眼效果
        u8g2.setDrawColor(0);
        u8g2.drawBox(64 - EYE_SPACING/2 - EYE_RADIUS, EYE_Y - 2, EYE_RADIUS*2, 4);
        u8g2.drawBox(64 + EYE_SPACING/2 - EYE_RADIUS, EYE_Y - 2, EYE_RADIUS*2, 4);
        u8g2.setDrawColor(1);
        break;
        
      case SAD:     // 悲伤表情
        // 向下弯曲的嘴巴(哭脸)
        u8g2.drawDisc(64 - 20, 55, 5, U8G2_DRAW_LOWER_RIGHT);
        u8g2.drawDisc(64 + 20, 55, 5, U8G2_DRAW_LOWER_LEFT);
        u8g2.drawHLine(64 - 15, 55, 30);
        // 眼泪效果
        u8g2.drawDisc(64 - EYE_SPACING/2, EYE_Y + 10, 2);
        u8g2.drawDisc(64 + EYE_SPACING/2, EYE_Y + 10, 2);
        break;
        
      case SURPRISED: // 惊讶表情
        // 大圆嘴巴
        u8g2.drawCircle(64, 55, 10);
        // 大眼睛(瞳孔缩小)
        u8g2.setDrawColor(0);
        u8g2.drawDisc(64 - EYE_SPACING/2, EYE_Y, 4);
        u8g2.drawDisc(64 + EYE_SPACING/2, EYE_Y, 4);
        u8g2.setDrawColor(1);
        break;
        
      case ANGRY:   // 生气表情
        // 倒V形嘴巴
        u8g2.drawLine(64 - 15, 50, 64, 60);
        u8g2.drawLine(64, 60, 64 + 15, 50);
        // 斜线眉毛
        u8g2.drawLine(64 - EYE_SPACING/2 - 12, EYE_Y - 15, 64 - EYE_SPACING/2 + 3, EYE_Y - 5);
        u8g2.drawLine(64 + EYE_SPACING/2 - 3, EYE_Y - 5, 64 + EYE_SPACING/2 + 12, EYE_Y - 15);
        // 小瞳孔
        u8g2.setDrawColor(0);
        u8g2.drawDisc(64 - EYE_SPACING/2, EYE_Y, 3);
        u8g2.drawDisc(64 + EYE_SPACING/2, EYE_Y, 3);
        u8g2.setDrawColor(1);
        break;
        
      case LOVE:    // 爱心表情
        // 绘制爱心
        u8g2.drawDisc(64, 55, 5, U8G2_DRAW_UPPER_LEFT | U8G2_DRAW_UPPER_RIGHT);
        u8g2.drawTriangle(59, 55, 69, 55, 64, 65);
        // 心形眼睛
        u8g2.setDrawColor(0);
        u8g2.drawDisc(64 - EYE_SPACING/2, EYE_Y, 5);
        u8g2.drawDisc(64 + EYE_SPACING/2, EYE_Y, 5);
        u8g2.setDrawColor(1);
        u8g2.drawDisc(64 - EYE_SPACING/2, EYE_Y, 3);
        u8g2.drawDisc(64 + EYE_SPACING/2, EYE_Y, 3);
        break;
        
      case CONFUSED: // 困惑表情
        // 弯曲眉毛
        u8g2.drawLine(64 - EYE_SPACING/2 - 10, EYE_Y - 10, 64 - EYE_SPACING/2, EYE_Y - 5);
        u8g2.drawLine(64 + EYE_SPACING/2, EYE_Y - 5, 64 + EYE_SPACING/2 + 10, EYE_Y - 10);
        // 歪嘴
        u8g2.drawLine(64 - 20, 50, 64, 55);
        u8g2.drawLine(64, 55, 64 + 20, 50);
        break;
        
      case EXCITED: // 兴奋表情
        // 大笑嘴巴
        u8g2.drawDisc(64, 55, 15, U8G2_DRAW_LOWER_LEFT | U8G2_DRAW_LOWER_RIGHT);
        // 星星眼睛
        u8g2.setDrawColor(0);
        u8g2.drawBox(64 - EYE_SPACING/2 - 3, EYE_Y - 3, 6, 6);
        u8g2.drawBox(64 + EYE_SPACING/2 - 3, EYE_Y - 3, 6, 6);
        u8g2.setDrawColor(1);
        u8g2.drawLine(64 - EYE_SPACING/2 - 5, EYE_Y, 64 - EYE_SPACING/2 + 5, EYE_Y);
        u8g2.drawLine(64 - EYE_SPACING/2, EYE_Y - 5, 64 - EYE_SPACING/2, EYE_Y + 5);
        u8g2.drawLine(64 + EYE_SPACING/2 - 5, EYE_Y, 64 + EYE_SPACING/2 + 5, EYE_Y);
        u8g2.drawLine(64 + EYE_SPACING/2, EYE_Y - 5, 64 + EYE_SPACING/2, EYE_Y + 5);
        break;
        
      case SLEEPY: // 困倦表情
        // 闭眼
        u8g2.setDrawColor(0);
        u8g2.drawHLine(64 - EYE_SPACING/2 - EYE_RADIUS, EYE_Y, EYE_RADIUS*2);
        u8g2.drawHLine(64 + EYE_SPACING/2 - EYE_RADIUS, EYE_Y, EYE_RADIUS*2);
        u8g2.setDrawColor(1);
        // Zzz嘴巴
        u8g2.drawLine(64 - 15, 55, 64 - 5, 55);
        u8g2.drawLine(64 - 5, 55, 64, 60);
        u8g2.drawLine(64, 60, 64 + 5, 60);
        u8g2.drawLine(64 + 5, 60, 64 + 15, 55);
        break;
    }
    
  } while(u8g2.nextPage());
}

// 触发随机表情
void triggerRandomEmotion() {
  // 随机选择一个表情(排除NEUTRAL)
  Emotion newEmotion = static_cast<Emotion>(random(1, COUNT));
  
  currentEmotion = newEmotion;
  isPerforming = true;
  actionStep = 0;
  lastActionTime = millis();
  
  // 重置待机动画计时
  nextIdleTime = millis() + IDLE_INTERVAL;
  
  Serial.print("触发表情: ");
  printEmotionName(newEmotion);
}

// 打印表情名称
void printEmotionName(Emotion emo) {
  switch(emo) {
    case HAPPY: Serial.println("开心"); break;
    case SAD: Serial.println("悲伤"); break;
    case SURPRISED: Serial.println("惊讶"); break;
    case ANGRY: Serial.println("生气"); break;
    case LOVE: Serial.println("爱心"); break;
    case CONFUSED: Serial.println("困惑"); break;
    case EXCITED: Serial.println("兴奋"); break;
    case SLEEPY: Serial.println("困倦"); break;
    default: Serial.println("中性");
  }
}

beetle rp2350的体积还是很小的,下一步,继续研究,想办法把面包板不要了集成线路,将开发板和电源都安在云台的底座上,优化外观!

评论

user-avatar
  • 小高……

    小高……2025.06.05

    第二排第二排

    0
    • ROCK-PENG

      ROCK-PENG2025.06.05

      沙发沙发

      0