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

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

2.90度舵机: sg90 x2

3.oled屏幕:ssd1316

4.触摸传感器:

5.舵机云台底座

第二步:接线,研究了好久,还是没有搞懂接线图的绘制,暂时还是试用文字的方式给与说明哈!!!
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 会自动将该引脚拉高,当按钮按下时,引脚电平被拉低,从而检测到按钮的按下动作。

第三步:程序准备
驱动部分除了试用U8G2库驱动oled外,还需要试用servo库来驱动我们两个90度的舵机
安装方法如下:

在库内搜索并安装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的体积还是很小的,下一步,继续研究,想办法把面包板不要了集成线路,将开发板和电源都安在云台的底座上,优化外观!
小高……2025.06.05
第二排第二排
ROCK-PENG2025.06.05
沙发沙发