在快节奏的生活中,一只可爱的桌面宠物或许是解压的最佳伙伴。市面上的电子宠物大多功能单一、不可编程,而专业机器人平台又价格高昂、门槛过高。本项目基于深圳大夏龙雀科技 BT36 蓝牙模块与 STM32F103C6T6A 主控,仅用面包板 + 杜邦线 + 4个SG90舵机 + 一块OLED屏 + 一节18650电池,就打造出一只可蓝牙遥控、会做表情、能动作卖萌的桌面宠物机器人。配套开发了微信小程序作为控制端,实现了从硬件到软件的全栈设计。


一、项目背景

1.1 创作初衷

一直想做一个既有技术含量又充满趣味的DIY项目。工业类项目虽然硬核但缺乏传播力,纯玩具类又显得技术深度不足。最终选择了"桌面宠物机器人"这个方向——

  • 趣味性强:可爱的外形、多种表情、跳舞卖萌,天然适合展示分享;

  • 技术覆盖全:涉及嵌入式开发、蓝牙通信、PWM舵机控制、I2C显示驱动、微信小程序开发,技术栈完整;

  • 门槛低可复制:面包板搭建、耗材便宜、结构简单,读者跟着教程就能复刻;

1.2 核心功能

功能模块

具体功能

运动控制

前进 / 后退 / 左转 / 右转 / 停止

表情系统

开心 / 困困 / 生气 / 惊讶 四种表情

炫彩灯效

呼吸灯 / 彩虹灯 / 闪烁灯 / 常亮灯

组合动作

跳舞 / 打招呼 / 卖萌

控制方式

微信小程序 + 蓝牙遥控器双模式

供电方式

单节18650锂电池,移动便携


二、整体架构

桌面宠物机器人采用 "应用层(小程序、遥控)— 通信层(BT36蓝牙)— 控制层(STM32)— 执行层(舵机、OLED、LED)" 四层架构:

2.1 工作原理

  1. 蓝牙连接:大夏龙雀 BT36 模块上电后自动广播,手机微信小程序或蓝牙遥控器搜索并建立 BLE 连接;

  2. 指令下发:用户在小程序端点击方向键、表情按钮或灯效按钮,指令通过 BLE 透传至 BT36;

  3. UART通信:BT36 通过 UART 将指令发送给 STM32F103C6T6A 主控;

  4. 指令解析:STM32 解析指令格式,执行对应的舵机运动、OLED表情切换或RGB灯效变换;

  5. 状态回复:执行完成后通过蓝牙回复 ACK,小程序端更新状态显示。

2.2 硬件选型

模块

型号

接口

数量

说明

主控

STM32F103C6T6A

-

×1

LQFP-48封装,资源充足

蓝牙

大夏龙雀 BT36

UART

×1

BLE5.3,主从一体,AT指令配置

显示

0.96" OLED

I2C

×1

128×64分辨率,SSD1306驱动

舵机

SG90 微型舵机

PWM

×4

180°旋转,重量轻,适合桌面级

灯光

RGB LED(共阴)

PWM

×1

三原色混合,实现炫彩效果

电源

18650 锂电池

-

×1

3.7V/2000mAh以上,续航持久

搭建

面包板 + 杜邦线

-

若干

免焊接,快速原型验证


三、硬件设计

3.1 硬件连接总览

全部电路通过面包板 + 杜邦线搭建,无需PCB,零基础也能快速上手。连接示意图如下

3.2 引脚分配

// STM32F103C6T6A 引脚分配

#define SERVO_LEFT_LEG_PIN    GPIO_Pin_0   // PA0 - 左腿
#define SERVO_RIGHT_LEG_PIN   GPIO_Pin_1   // PA1 - 右腿
#define SERVO_LEFT_FOOT_PIN   GPIO_Pin_2   // PA2 - 左前足
#define SERVO_RIGHT_FOOT_PIN  GPIO_Pin_3   // PA3 - 右前足

#define OLED_SDA_PIN          GPIO_Pin_7   // PB7 - I2C SDA
#define OLED_SCL_PIN          GPIO_Pin_6   // PB6 - I2C SCL

#define BT_TX_PIN             GPIO_Pin_9   // PA9 - USART1 TX
#define BT_RX_PIN             GPIO_Pin_10  // PA10 - USART1 RX

#define LED_R_PIN             GPIO_Pin_8   // PA8 - TIM1 CH1
#define LED_G_PIN             GPIO_Pin_9   // PB9 - TIM4 CH4
#define LED_B_PIN             GPIO_Pin_8   // PB8 - TIM4 CH3

3.3 舵机驱动原理

SG90舵机的驱动信号是20ms周期的PWM波,高电平宽度从0.5ms到2.5ms对应0°到180°:

高电平宽度

对应角度

0.5ms

1.0ms

45°

1.5ms

90°(中位)

2.0ms

135°

2.5ms

180°

通过STM32定时器PWM输出,动态调整CCR寄存器即可控制舵机角度。四只舵机分别控制两条后腿和两只前足,通过协调不同的角度组合实现前进、后退、转向等步态。

3.4 电源方案

单节18650锂电池(3.7V标称电压)直接为整个系统供电:

  • STM32F103C6T6A:工作电压2.0~3.6V,核心板自带的LDO即可实现MCU供电

  • SG90舵机:工作电压4.8V典型,3.7V也能工作但扭矩稍小,桌面宠物重量轻完全够用

  • BT36蓝牙模块:支持3.3~5V宽电压,3.7V直接供电

  • OLED显示屏:3.3V供电,由STM32的3.3V引脚

我们使用的锂电池充放电方案是TP4056,直接粘贴在面包板上,方便使用Type-C接口直接对18650锂电池进行充电。


四、机械结构设计

桌面宠物采用4舵机方案,结构简单、动作灵活:

4.1 四舵机布局

  • 后腿 ×2:左右各一只,负责主要的行走动力输出,摆动范围约60°

  • 前足 ×2:左右各一只,负责辅助支撑和姿态调整,使动作更自然可爱

4.2 步态原理

通过四只舵机的角度协调变化实现不同运动:

前进步态

  1. 左前足抬起 → 右腿后蹬 → 身体前倾

  2. 右前足抬起 → 左腿后蹬 → 身体继续前进

  3. 交替循环,实现"蹒跚学步"的可爱效果

转向原理

  • 左转:右腿加速前摆,左腿减速或小幅后摆,身体向左偏转

  • 右转:左腿加速前摆,右腿减速或小幅后摆,身体向右偏转

4.3 结构材质

我们使用的刚好是面包板,无需任何焊接即可实现电路的快速搭建,可以用:

建议先用简单材料验证步态,满意后再做精致外壳。


五、OLED表情系统

小小的OLED屏幕是宠物的"灵魂之窗",通过绘制不同表情赋予宠物性格:

5.1 四种基础表情

表情

触发场景

视觉效果

开心

正常待机 / 收到指令执行成功

弯弯笑眼 + 上扬嘴角

困困

长时间无操作 / 夜晚模式

眯成一条缝 + Zzz

生气

连续撞墙 / 被频繁打扰

倒八字眉 + 嘴角下撇

惊讶

首次连接 / 收到新指令

圆眼睛 + O型嘴

5.2 表情绘制实现

使用SSD1306驱动的0.96寸OLED(128×64),通过I2C通信:

// 表情数据结构
typedef struct {
    const uint8_t *face_data;  // 表情位图数据
    uint8_t width;             // 表情宽度
    uint8_t height;            // 表情高度
} Expression_t;

// 表情枚举
typedef enum {
    EXP_HAPPY = 0,    // 开心
    EXP_SLEEPY = 1,   // 困困
    EXP_ANGRY = 2,    // 生气
    EXP_SURPRISE = 3  // 惊讶
} Expression_Type;

// 切换表情函数
void OLED_ShowExpression(Expression_Type exp)
{
    // 清屏
    OLED_Clear();
    
    // 在屏幕中央绘制表情
    uint8_t x = (128 - expressions[exp].width) / 2;
    uint8_t y = (64 - expressions[exp].height) / 2;
    
    OLED_DrawBMP(x, y, expressions[exp].width, expressions[exp].height,
                 expressions[exp].face_data);
    
    // 可附加装饰:脸颊红晕等
    if(exp == EXP_HAPPY) {
        OLED_DrawCircle(30, 40, 5, 1);  // 左脸颊
        OLED_DrawCircle(98, 40, 5, 1);  // 右脸颊
    }
    
    OLED_Refresh();
}

表情图案可以用 LCD Image Converter 或类似工具将图片转换为C数组,直接嵌入代码中。


六、RGB炫彩灯效

RGB LED是宠物的"氛围担当",四种灯效模式随心切换:

6.1 四种灯效模式

模式

效果描述

适用场景

呼吸灯

PWM渐变亮灭,温柔如呼吸

待机状态,安静优雅

彩虹灯

RGB三色循环渐变,炫彩夺目

跳舞时,活泼热闹

闪烁灯

快速闪烁,活泼俏皮

开心/惊讶时

常亮灯

稳定单色光,柔和护眼

夜灯模式,温暖陪伴

6.2 PWM混色原理

三路独立PWM,每路8位分辨率(0~255),可混合出约1677万种颜色:

// RGB颜色设置函数
void RGB_SetColor(uint8_t r, uint8_t g, uint8_t b)
{
    // 直接修改CCR寄存器,0~255映射到0~ARR
    TIM_SetCompare1(TIM1, r);    // R通道
    TIM_SetCompare3(TIM4, g);    // G通道
    TIM_SetCompare4(TIM4, b);    // B通道
}

// 呼吸灯效果
void LED_Breathing_Effect(void)
{
    static uint8_t brightness = 0;
    static uint8_t direction = 1;
    
    if(direction) {
        brightness++;
        if(brightness >= 200) direction = 0;
    } else {
        brightness--;
        if(brightness == 0) direction = 1;
    }
    
    RGB_SetColor(brightness, brightness / 2, 0);  // 暖橙色呼吸
}

七、蓝牙通信协议

通信核心是深圳大夏龙雀科技 BT36 蓝牙模块,这是整个项目的无线桥梁。

7.1 BT36 模块特性

BT36 是大夏龙雀出品的高性价比 BLE 5.3 模块,采用 DIALOG DA14535 芯片,主从一体设计。在本项目中,它的优势体现在:

  • BLE 5.3 协议:连接速度快、功耗低、抗干扰能力强

  • 主从一体:既可作从机被手机连接,也可作主机连接遥控器

  • AT指令丰富:支持修改设备名、波特率、UUID、发射功率等参数,响应迅速(<10ms)

  • 宽电压工作:3.3V~5V,直接接锂电池,无需额外电平转换

  • UART透传:透明传输模式,开发简单,像操作串口一样使用蓝牙

  • 通信距离远:空旷环境下可达数十米,桌面场景绰绰有余

在实际开发中,BT36的稳定性令人印象深刻——电机PWM信号产生的电磁干扰对蓝牙通信几乎没有影响,连接稳定不掉线,这对于面包板搭建的原型项目来说非常关键。

7.2 通信协议设计

采用简洁易解析的文本指令协议,便于调试和扩展:

7.3 串口解析实现

// 蓝牙数据接收缓冲区
#define BT_RX_BUF_SIZE  64
uint8_t bt_rx_buf[BT_RX_BUF_SIZE];
uint8_t bt_rx_index = 0;

// USART1 中断接收(BT36连接)
void USART1_IRQHandler(void)
{
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        uint8_t ch = USART_ReceiveData(USART1);
        
        if(ch == '\n' || bt_rx_index >= BT_RX_BUF_SIZE - 1) {
            bt_rx_buf[bt_rx_index] = '\0';
            // 完整指令,解析处理
            BT_Parse_Command((char*)bt_rx_buf);
            bt_rx_index = 0;
        } else if(ch != '\r') {
            bt_rx_buf[bt_rx_index++] = ch;
        }
    }
}

// 指令解析函数
void BT_Parse_Command(char *cmd)
{
    if(strncmp(cmd, "FWD", 3) == 0) {
        Servo_WalkForward();
        OLED_ShowExpression(EXP_HAPPY);
        BT_SendString("OK\r\n");
    }
    else if(strncmp(cmd, "BACK", 4) == 0) {
        Servo_WalkBackward();
        BT_SendString("OK\r\n");
    }
    else if(strncmp(cmd, "LEFT", 4) == 0) {
        Servo_TurnLeft();
        BT_SendString("OK\r\n");
    }
    else if(strncmp(cmd, "RIGHT", 5) == 0) {
        Servo_TurnRight();
        BT_SendString("OK\r\n");
    }
    else if(strncmp(cmd, "STOP", 4) == 0) {
        Servo_Stop();
        BT_SendString("OK\r\n");
    }
    else if(strncmp(cmd, "EXP:", 4) == 0) {
        uint8_t exp_num = cmd[4] - '0';
        if(exp_num <= 3) {
            OLED_ShowExpression(exp_num);
            BT_SendString("OK\r\n");
        }
    }
    else if(strncmp(cmd, "LED:", 4) == 0) {
        uint8_t led_num = cmd[4] - '0';
        if(led_num <= 3) {
            LED_SetMode(led_num);
            BT_SendString("OK\r\n");
        }
    }
    else if(strncmp(cmd, "ACT:", 4) == 0) {
        Action_Execute(cmd + 4);
        BT_SendString("OK\r\n");
    }
    else {
        BT_SendString("ERR\r\n");
    }
}

八、系统控制流程

8.1 主循环结构

int main(void)
{
    // 系统初始化
    SystemInit();
    Servo_Init();          // 舵机PWM初始化
    OLED_Init();           // OLED初始化
    RGB_Init();            // RGB灯PWM初始化
    USART1_Init(9600);     // 蓝牙串口初始化(BT36默认9600)
    
    // 开机动画
    OLED_ShowExpression(EXP_HAPPY);
    LED_SetMode(LED_BREATHING);
    Servo_WaveHello();     // 开机打招呼
    
    while(1)
    {
        // 检查是否收到蓝牙指令
        if(bt_cmd_ready) {
            BT_Parse_Command(bt_cmd_buffer);
            bt_cmd_ready = 0;
        }
        
        // LED灯效任务
        LED_Task_Handler();
        
        // 待机检测
        if(HAL_GetTick() - last_cmd_time > 10000) {
            if(current_expression != EXP_SLEEPY) {
                OLED_ShowExpression(EXP_SLEEPY);
                LED_SetMode(LED_BREATHING);  // 柔和呼吸
            }
        }
    }
}

8.2 组合动作实现

组合动作是让宠物"活起来"的关键,通过编排一系列舵机角度序列实现:

// 跳舞动作序列
void Action_Dance(void)
{
    OLED_ShowExpression(EXP_HAPPY);
    LED_SetMode(LED_RAINBOW);  // 彩虹灯效
    
    // 左右摇摆 + 前足摆动
    for(int i = 0; i < 4; i++) {
        Servo_SetAngle(SERVO_LEFT_LEG, 60 + i * 15);
        Servo_SetAngle(SERVO_RIGHT_LEG, 120 - i * 15);
        Delay_Ms(200);
    }
    for(int i = 0; i < 4; i++) {
        Servo_SetAngle(SERVO_LEFT_LEG, 120 - i * 15);
        Servo_SetAngle(SERVO_RIGHT_LEG, 60 + i * 15);
        Delay_Ms(200);
    }
    
    // 前足上下摆动
    for(int i = 0; i < 3; i++) {
        Servo_SetAngle(SERVO_LEFT_FOOT, 45);
        Servo_SetAngle(SERVO_RIGHT_FOOT, 45);
        Delay_Ms(150);
        Servo_SetAngle(SERVO_LEFT_FOOT, 135);
        Servo_SetAngle(SERVO_RIGHT_FOOT, 135);
        Delay_Ms(150);
    }
    
    Servo_Stop();
    LED_SetMode(LED_BREATHING);
}

// 打招呼动作
void Action_Hello(void)
{
    OLED_ShowExpression(EXP_HAPPY);
    
    // 右前足抬起挥动
    for(int i = 0; i < 3; i++) {
        Servo_SetAngle(SERVO_RIGHT_FOOT, 30);
        Delay_Ms(200);
        Servo_SetAngle(SERVO_RIGHT_FOOT, 60);
        Delay_Ms(200);
    }
    
    Servo_Stop();
}

九、微信小程序控制端

为了让宠物的控制更便捷、作品更完整,开发了配套的微信小程序控制端,通过 BLE 直接连接 BT36 模块:

9.1 小程序功能架构

小程序包含三个主要页面:

页面

功能

控制页

方向控制 + 表情切换 + 灯效选择 + 快捷动作

设置页

舵机角度微调、响应速度设置、自动休眠时间

关于页

作品介绍、版本信息、教程链接

9.2 BLE连接核心代码

// 小程序端 - 蓝牙连接与控制
const DEVICE_NAME = 'DX-BT36';  // BT36默认设备名
const SERVICE_UUID = '0000FFE0-0000-1000-8000-00805F9B34FB';
const CHAR_UUID = '0000FFE1-0000-1000-8000-00805F9B34FB';

let deviceId = null;
let connected = false;

// 搜索并连接设备
function connectDevice() {
    wx.startBluetoothDevicesDiscovery({
        services: [SERVICE_UUID],
        success: (res) => {
            console.log('开始搜索设备');
            
            wx.onBluetoothDeviceFound((devices) => {
                const device = devices.devices.find(d => 
                    d.name === DEVICE_NAME || d.localName === DEVICE_NAME
                );
                if(device) {
                    wx.stopBluetoothDevicesDiscovery();
                    createConnection(device.deviceId);
                }
            });
        }
    });
}

// 建立连接
function createConnection(id) {
    wx.createBLEConnection({
        deviceId: id,
        success: () => {
            deviceId = id;
            connected = true;
            wx.showToast({ title: '连接成功', icon: 'success' });
            
            // 监听特征值变化
            wx.notifyBLECharacteristicValueChange({
                deviceId,
                serviceId: SERVICE_UUID,
                characteristicId: CHAR_UUID,
                success: () => {
                    wx.onBLECharacteristicValueChange((res) => {
                        const data = ab2str(res.value);
                        console.log('收到:', data);
                    });
                }
            });
        }
    });
}

// 发送指令
function sendCommand(cmd) {
    if(!connected) {
        wx.showToast({ title: '请先连接设备', icon: 'none' });
        return;
    }
    
    const buffer = str2ab(cmd + '\r\n');
    wx.writeBLECharacteristicValue({
        deviceId,
        serviceId: SERVICE_UUID,
        characteristicId: CHAR_UUID,
        value: buffer,
        success: () => console.log('发送:', cmd)
    });
}

// 工具函数:字符串转ArrayBuffer
function str2ab(str) {
    const buffer = new ArrayBuffer(str.length);
    const view = new Uint8Array(buffer);
    for(let i = 0; i < str.length; i++) {
        view[i] = str.charCodeAt(i);
    }
    return buffer;
}

function ab2str(buffer) {
    return String.fromCharCode.apply(null, new Uint8Array(buffer));
}

// 方向控制按钮
const control = {
    forward: () => sendCommand('FWD'),
    backward: () => sendCommand('BACK'),
    turnLeft: () => sendCommand('LEFT'),
    turnRight: () => sendCommand('RIGHT'),
    stop: () => sendCommand('STOP'),
};

// 表情切换
const expressions = {
    happy: () => sendCommand('EXP:0'),
    sleepy: () => sendCommand('EXP:1'),
    angry: () => sendCommand('EXP:2'),
    surprise: () => sendCommand('EXP:3'),
};

// 灯效切换
const ledModes = {
    breathing: () => sendCommand('LED:0'),
    rainbow: () => sendCommand('LED:1'),
    blink: () => sendCommand('LED:2'),
    solid: () => sendCommand('LED:3'),
};

// 快捷动作
const actions = {
    dance: () => sendCommand('ACT:DANCE'),
    hello: () => sendCommand('ACT:HELLO'),
    cute: () => sendCommand('ACT:CUTE'),
};

9.3 小程序界面设计要点

  • 方向控制区:D-pad方向键布局,中间键为停止,符合游戏手柄使用习惯

  • 表情切换区:4个表情按钮横向排列,点击即切换,状态反馈清晰

  • 灯效选择区:4种灯效彩色按钮,视觉上即传达灯光氛围

  • 快捷动作区:跳舞/打招呼/卖萌三个大按钮,一键触发组合动作

  • 状态栏:顶部实时显示连接状态,已连接显示绿色圆点


十、项目总结

这款桌面宠物机器人虽然只用面包板搭建,但麻雀虽小五脏俱全,从硬件到软件实现了完整的全栈设计:

10.1 项目亮点

  • 全栈自主设计:从电路搭建、嵌入式程序到微信小程序,端到端完整实现,技术覆盖面广

  • BT36深度适配:充分发挥大夏龙雀 BT36 蓝牙模块的性能优势,BLE5.3稳定连接、AT指令灵活配置、宽电压适配

  • 面包板入门友好:无需PCB、无需焊接,所有耗材易购,降低了复刻门槛,适合教学和推广

  • 趣味性与传播性:可爱的表情、灵动的动作、微信小程序控制,天然适合短视频和社交分享

  • 扩展性强:可以不断添加新表情、新动作、新传感器(触摸/声音/光线),持续迭代

10.2 成本清单

物料

单价(参考)

数量

小计

STM32F103C6T6A 最小系统板

约15元

×1

15元

大夏龙雀 BT36 蓝牙模块

约20元

×1

20元

SG90 微型舵机

约5元

×4

20元

0.96" OLED 显示屏

约8元

×1

8元

RGB LED + 电阻

约1元

×1

1元

18650 锂电池 + 电池盒

约10元

×1

10元

面包板 + 杜邦线

约10元

若干

10元

总计

-

-

约84元

Logo

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

更多推荐