立创EDA开源项目:基于STM32F103的智能桌宠3.0设计与实现全解析

最近在立创开源平台上看到一个特别有意思的项目——一个能跟你互动、会生气、能遥控的桌面小机器人,也就是这个“桌宠3.0”。很多朋友看了演示视频都觉得好玩,但觉得涉及硬件、软件、结构,不知道从何下手。作为一个做过不少类似项目的嵌入式工程师,我决定把这个项目的设计思路、代码逻辑和组装要点掰开揉碎了讲清楚,带你从零开始复现这个有趣的智能桌宠。

这个项目本质上是一个集成了多种交互方式的桌面机器人。它的大脑是STM32F103C8T6单片机,通过语音模块、蓝牙遥控、红外感应和OLED表情屏,实现了一个有“情绪”、能互动、可玩游戏的智能伙伴。无论你是想学习STM32的综合应用,还是想做一个独一无二的桌面摆件,这个教程都能给你提供一条清晰的路径。

1. 项目核心功能与硬件选型

在动手之前,咱们先搞清楚这个小家伙到底能干什么,以及为了实现这些功能,需要哪些硬件来支撑。

1.1 四大核心模式

桌宠3.0主要工作在四种模式下,就像一个多面手:

  1. 互动模式:这是它的默认状态。在这个模式下,桌宠有自己的“情绪系统”。你可以通过语音命令(比如“前进”、“后退”)与它互动,互动会让它心情变好。如果长时间没人理它,它的“怒气值”会积累,心情变差,最差的时候甚至会“小发雷霆”(表现为快速移动和摇头)。它正面还有两个光电管(类似红外对管),感应到障碍物会后退,如果左右两侧被快速交替触发,它还会进入“晕眩”状态。

  2. 游戏模式:进入猜数字游戏。你可以通过语音告诉它一个数字范围(比如1到100),然后它心里想一个数,你通过语音猜,它会告诉你猜大了还是小了。游戏过程有音效和OLED画面反馈。

  3. 遥控模式:通过一个蓝牙手柄(摇杆)进行精细控制。左边摇杆控制头部舵机的上下左右转动,右边摇杆控制底部两个电机的行进(前后左右)。这个模式需要先进行一个“解锁”手势操作,并且有自动上锁机制(8秒无操作或蓝牙断开3秒后自动退出)。

  4. 充电/坞站模式:按下特定按键,桌宠进入静止状态,OLED屏幕显示累计工作时长。同时,它背部的USB HUB(拓展坞)和音频解码模块可以当作电脑的扩展接口和MP3播放器使用。

1.2 硬件清单与选型思路

要实现这么多功能,硬件选型是关键。原作者的选择兼顾了性价比和易用性,非常适合学习和复现。

模块/芯片 型号/规格 在项目中的作用 选型备注
主控MCU STM32F103C8T6 (蓝桥杯/核心板) 系统大脑,负责逻辑控制、通信、PWM输出等 经典“蓝桥杯”芯片,资源丰富(72MHz主频,64KB Flash,20KB RAM),社区资料多,上手容易。
语音识别模块 天问ASRPRO 识别特定语音指令,通过串口发送指令码给STM32 离线语音模块,无需联网,识别率高,配置简单,通过图形化工具训练指令即可。
运动执行器 N20直流减速电机 (2个) 驱动两个主动轮,实现前进、后退、转向 选择一分钟30转的型号,扭矩足够推动小桌宠。需要配合电机驱动模块使用。
电机驱动 基于MT3608的升压驱动模块 将电池电压升压至6V为电机供电,并提供方向控制 MT3608是升压芯片,这里用来给电机提供更高电压以获得更好动力。注意需要能支持PWM调速和方向控制的驱动板。
头部舵机 MG90S (金属齿轮) 或 SG90 控制头部OLED屏幕的俯仰和旋转 MG90S扭矩更大更耐用,SG90更便宜。根据预算选择。
显示模块 0.96寸OLED (I2C接口,4针) 显示桌宠的8档情绪表情、游戏界面、环境数据等 I2C接口节省IO,显示效果清晰。注意是四针(VCC, GND, SCL, SDA)的型号。
电源管理 IP5306充电管理芯片 管理锂电池充电,并升压至5V为系统供电 集成度高,支持2A充电和2.4A放电,自带电量显示功能,非常方便。
蓝牙模块 MX-02A (贴片款) 接收来自蓝牙手柄的遥控指令 需要选择主从一体的模块,并务必在焊接前完成主从配对
环境传感器 DHT11 (温湿度) + 光敏电阻 + LM393比较器 检测环境温湿度和光照强度 DHT11数字输出,光敏电阻通过LM393比较器后输出数字信号,简化了STM32的ADC读取。
拓展与音频 SL2.1A USB HUB芯片 + 杰里AD162A音频解码芯片 提供4个USB-A接口和TF卡MP3播放功能 这两个模块都是“即插即用”型,STM32只需供电,不参与控制,属于功能拓展。
结构件 3D打印/亚克力外壳、万向轮、铜柱、螺丝等 固定所有电子元件,构成桌宠的物理身体 原作者提供了立创EDA的面板设计文件,可以直接在嘉立创下单打印亚克力板。

硬件连接核心思路:STM32是中心枢纽。语音模块(USART3)、蓝牙模块(USART1)、OLED(I2C)与它通信;它通过GPIO和PWM(TIM2, TIM3)控制电机、舵机、读取传感器;IP5306为整个系统供电。

2. 软件架构与核心代码解析

光有硬件只是一堆零件,让桌宠“活”起来的灵魂是软件。我们来看看STM32里的程序是怎么组织的。

2.1 程序主框架:状态机驱动

整个程序的核心是一个多模式状态机。用一个全局变量 mode 来标识当前处于哪种模式。

int mode=1; // 模式  1-互动 2-游戏 3-手柄控制  4-自由模式

void main(void) {
    // 硬件初始化(串口、定时器、GPIO、ADC等)
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_USART3_UART_Init();
    MX_TIM2_Init(); // PWM for motor
    MX_TIM3_Init(); // PWM for servo
    MX_ADC1_Init();
    MX_I2C1_Init(); // for OLED
    // ... 其他初始化

    while (1) {
        switch(mode) {
            case 1: // 互动模式
                interact_mode();
                break;
            case 2: // 游戏模式
                game_mode();
                break;
            case 3: // 遥控模式
                remote_control_mode();
                break;
            case 4: // 自由模式(可能用于调试)
                free_mode();
                break;
        }
        // 一些全局的、与模式无关的定时任务放在这里
        update_face_animation(); // 更新表情
        check_battery(); // 检查电量
    }
}

这种结构非常清晰,每个模式独立成函数,互不干扰。模式之间的切换由外部事件触发,比如收到蓝牙的特定指令、按键按下或者定时器超时。

2.2 情绪系统与互动逻辑

这是互动模式的精髓。情绪值 mood 范围是0-8,0代表最开心,8代表最愤怒。

int mood=3; // 初始心情为3(平静)
int inter_time=0; // 无互动时间计数器
int anger=0; // 临时怒气值,用于触发小动作

// 在定时器中断中(每0.1秒触发一次)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if(htim->Instance == TIM4) {
        if(mode == 1) { // 仅在互动模式下
            inter_time++; // 无互动时间增加

            if(inter_time >= 3000) { // 3000 * 0.1s = 300秒 = 5分钟
                mood_bad(); // 心情变差一级
                inter_time = 0; // 重置计时器
            }
        }
    }
}

// 心情变好函数(例如检测到语音指令或按键)
void mood_good() {
    inter_time = 0; // 有互动,清零无互动计时
    if(mood > 0) { // 不能超过最开心值
        mood--;
    }
}

// 心情变差函数(由定时器或特定坏指令触发)
void mood_bad() {
    if(mood < 8) { // 不能超过最愤怒值
        mood++;
    }
}

face() 函数根据当前的 mood 值,在OLED上显示对应的表情图片(BMP0到BMP11)。当 mood 达到8(最愤怒)时,feel() 函数中的互动逻辑会触发“小发雷霆”的动作序列。

注意:情绪变化是渐进的,并且有“一键快乐”按键(对应代码中某个按键检测)可以直接调用 mood_good() 来改善心情,这个设计增加了可玩性。

2.3 串口通信:与语音和蓝牙模块对话

STM32通过两个串口分别与天问ASRPRO语音模块和MX-02A蓝牙模块通信。

  • USART3 - 语音模块:语音模块识别到关键词后,会发送单个字符(如 ‘A’, ‘B’, ‘C’…)给STM32。STM32在串口中断回调函数 HAL_UART_RxCpltCallback 中解析这些字符,转换为对应的 state(动作指令)或 mode(模式切换)。

    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
        if(huart->Instance == USART3) { // 来自语音模块
            if(rx == 'A') { state = 11; } // 唤醒指令
            else if(rx == 'B') { state = 0; } // 动作指令:左右看
            else if(rx == 'C') { state = 1; } // 动作指令:前进
            // ... 其他指令解析
            else if(rx == 'Q') { mode = 2; } // 切换到游戏模式
            else if(rx == 'R') { mode = 1; } // 切换回互动模式
            // 数字'1'-'9'用于游戏模式下的猜数字
            HAL_UART_Receive_DMA(&huart3, &rx, 1); // 重新开启DMA接收
        }
        // ... 处理USART1(蓝牙)的代码
    }
    
  • USART1 - 蓝牙模块:蓝牙手柄发送的是摇杆的状态编码。例如,左摇杆的X轴有三个状态:左、中、右,分别用字符’A’, ‘B’, ‘C’表示。STM32解析后更新 left_x, left_y, right_x, right_y 这些变量,然后在 control_blue() 函数中将其转化为具体的舵机角度和电机PWM值。

2.4 运动控制:PWM与GPIO的配合

运动控制分为两部分:头部舵机和底部电机。

头部舵机控制: 使用TIM3产生两路PWM信号,分别控制底座旋转舵机(Channel1)和头部俯仰舵机(Channel2)。body(base, head) 函数负责限制角度范围并设置PWM占空比。

void body(int base, int head) {
    // 限制舵机角度范围,防止损坏
    if(base >= 2500) { base = 2500; }
    else if(base <= 500) { base = 500; }
    if(head >= 2500) { head = 2500; }
    else if(head < 1200) { head = 1200; }

    // 设置PWM比较值,控制舵机角度
    __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, base); // 底部旋转
    __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_2, head); // 头部俯仰
}
// 注释解释了PWM值对应的角度:
// 底部: 2500-500 (对应一个方向的极限到另一个方向极限)
// 头部: 2500-低头, 1200-最顶, 1500-超级仰头, 1900-正常对人, 2250-平视

底部电机控制: 使用TIM2产生两路PWM(Channel3, Channel4)进行调速,同时用两个GPIO引脚(PA1, PA4)控制电机的方向(正转/反转)。

void left_wheel(int turn_left, int speed_left) {
    turn_left_A = turn_left; // 记录方向状态
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, turn_left); // 设置方向
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, speed_left); // 设置速度PWM
}
// right_wheel 函数同理

// 封装好的动作函数
void go() { // 前进
    left_wheel(0, 2500); // 方向0,全速
    right_wheel(0, 2500);
}
void back() { // 后退
    left_wheel(1, 2500); // 方向1,全速
    right_wheel(1, 2500);
}
void stop() { // 停止
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, 0); // PWM置0
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, 0);
    // 同时将方向引脚置低,确保电机刹车或自由停止
}

3. 动手组装与调试要点

代码理解了,硬件齐了,最后一步就是把它们拼装起来并让它们正常工作。这里有几个关键步骤和容易踩坑的地方。

3.1 核心步骤与技巧

  1. 焊接与固定

    • 先配对,后焊接蓝牙模块MX-02A一定要在焊接前完成主从配对! 按照原作者提供的流程图,用USB转TTL模块连接两个蓝牙,使用AT指令设置名称、角色并配对连接。配对成功后再焊接到主板上。
    • 模块化安装:建议先分别测试各个模块(OLED显示、舵机转动、电机正反转、语音识别、蓝牙通信),确保每个部分单独工作正常。
    • 走线与固定:舵机和屏幕的线缆比较集中,可以用绕线管或热缩管捆扎整齐。电机驱动板、电池等较重的部件,在亚克力板内部可以用热熔胶或螺丝+铜柱牢固固定,防止移动导致线缆脱落。
  2. 电源与充电

    • IP5306充电:原作者特别提醒,不要使用快充充电头给IP5306充电,否则可能无法充电。使用普通的5V/2A充电器即可。
    • 电机供电:N20电机工作电压是6V,所以需要通过MT3608升压模块从电池电压升压得到。确保电机驱动模块的电源输入接的是升压后的6V,而不是系统主电源的5V。
  3. 结构组装

    • 亚克力外壳文件在立创EDA工程里的 Panel_1,可以直接在嘉立创的“面板打印”服务下单制作。
    • 底部的万向轮(从动轮)和两个N20电机(主动轮)要呈三角形分布,保证稳定。
    • 头部舵机需要先与OLED屏幕支架固定好,再整体安装到身体上,注意线缆要从舵机转轴中间穿过,避免缠绕。

3.2 调试与常见问题

  • 程序烧录:使用ST-Link或USB转TTL(需接BOOT0)给STM32F103C8T6核心板烧录程序。确保工程中正确配置了时钟、引脚和外设。
  • 舵机不动:检查PWM频率是否为50Hz(周期20ms),以及脉宽是否在500-2500us的合理范围内。用 body(1500, 1900) 这样的函数测试,看舵机是否归中。
  • 电机不转或只震动
    1. 检查电机驱动板的供电(是否为6V)和使能信号。
    2. 检查STM32输出的方向控制GPIO电平是否正确。
    3. 检查PWM输出是否使能,并且占空比是否大于0。可以用万用表测量电机驱动板输出端电压。
  • 蓝牙无法控制
    1. 首要确认:蓝牙模块是否已正确配对?这是最常见的问题。
    2. 检查STM32的USART1波特率是否与蓝牙模块设置的波特率一致(通常是9600或115200)。
    3. 在串口助手中监听USART1,看是否能收到手柄发送的原始数据(‘A’, ‘B’, ‘C’…),以确定问题出在蓝牙链路还是STM32解析。
  • 语音无反应
    1. 检查天问ASRPRO模块是否已训练好指令,并正确烧录了语音模型。
    2. 检查USART3连接是否正确,STM32是否能进入串口接收中断。
    3. 在代码中,收到语音指令后可以点亮一个LED或通过串口打印调试信息,来确认指令是否被正确接收。

这个项目麻雀虽小,五脏俱全,涵盖了嵌入式开发中MCU控制、传感器应用、通信协议、PWM电机控制、状态机设计等多个核心知识点。通过复现它,你不仅能收获一个有趣的桌面伙伴,更能系统地锻炼嵌入式软硬件综合开发能力。遇到问题别怕,对照原理图、代码和这个教程,一步步排查,调试的过程本身就是最好的学习。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐