1. 项目概述:当贪吃蛇“听懂”你的指令
几年前,我第一次带学生用Micro:bit做项目,大部分作品都离不开那个小小的5x5 LED点阵和两个实体按键。直到有一次,一个孩子问我:“老师,能不能让这个小板子‘听’我说话来控制它?” 这个问题一下子点醒了我。传统的交互,无论是按键、光线还是倾斜感应,都建立在“设备主动感知”的基础上。而“语音”,则是我们人类最自然、最直接的输出方式。将语音识别与Micro:bit结合,不仅仅是增加一个传感器,更是将人机交互的主动权部分交还给了用户,让冰冷的硬件有了“对话”的可能。
“语音贪吃蛇”这个项目,正是这种思路的一个绝佳实践。它脱胎于经典的贪吃蛇游戏,但核心玩法从“用手按”升级为“用嘴说”。你不再需要去记忆哪个按键对应上下左右,只需要对着麦克风清晰地说出“上”、“下”、“左”、“右”,屏幕上的小蛇就会应声而动。这个项目看似简单,却巧妙地串联了离线语音识别模块的接入、串口通信的数据解析、游戏状态机的逻辑设计以及Micro:bit的图形化编程(如Mind+)或Python代码编程等多个核心技能点。
它非常适合作为嵌入式入门、物联网交互或STEAM教育的一个综合性案例。无论你是教育工作者想设计一堂生动的创客课,还是编程爱好者想给自己的Micro:bit开发板增添点新玩法,亦或是家长想和孩子一起完成一个有趣的亲子项目,这个“语音贪吃蛇”都能提供一个从硬件连接到软件逻辑的完整闭环体验。接下来,我将以最详尽的步骤,带你从零开始,复现这个会“听话”的贪吃蛇。
2. 核心硬件选型与连接方案
实现语音控制,核心在于为Micro:bit找一个可靠的“耳朵”。Micro:bit本身没有麦克风,因此我们必须借助外部模块。
2.1 为什么选择离线语音识别模块?
市面上常见的语音方案主要有两类:在线语音识别(如科大讯飞、百度AI的SDK)和离线语音识别模块。对于Micro:bit这类资源有限、且通常运行在无网络环境(如课堂、工作坊)的嵌入式设备,离线语音识别模块是几乎唯一的选择。
在线方案虽然识别率高、词库大,但严重依赖网络和云端服务器,会引入延迟、增加系统复杂性(需要Wi-Fi模块),且涉及隐私和数据安全问题。而离线模块,如常见的LD3320、SYN7318、ASRPRO等芯片方案,其所有识别计算都在本地完成,响应速度极快(通常在毫秒级),无需网络,即插即用,非常适合这种简单的、固定指令集的交互场景。
注意:不要被“离线”二字误导,认为其能力很弱。对于“上、下、左、右、开始、停止”这类数量有限、发音清晰的指令,经过针对性训练的离线模块识别准确率可以做到非常高,完全满足项目需求。
2.2 模块选型与对比
这里我推荐两款经过大量项目验证、性价比极高的模块,你可以根据手头资源任选其一。
方案一:Gravity-I2C语音识别模块(基于LD3320)这款模块是DFRobot出品的,最大优点是接口简单。它采用I2C通信,只需要连接Micro:bit的SDA(P20)、SCL(P19)、VCC和GND四根线。编程时,直接调用其提供的图形化积木或Python库函数,就可以读取识别结果,非常省心。缺点是自定义词条可能需要通过上位机软件烧录,灵活性稍逊。
方案二:通用串口(UART)语音识别模块市面上很多蓝色或绿色的小板子,上面标有“Voice Recognition Module”,通常采用UART(串口)通信。你需要连接模块的TX到Micro:bit的RX(默认为P0),模块的RX到Micro:bit的TX(默认为P1),以及VCC和GND。这种模块的优点是价格通常更便宜,并且可以通过串口指令实时添加、删除和设置识别词条,灵活性极高。我们后续的讲解将以这种更通用的串口模块为例。
硬件连接清单:
- Micro:bit主板 x1
- 串口语音识别模块 x1(支持3.3V逻辑电平)
- 面包板 x1(可选,方便连接)
- 杜邦线(母对母)若干
连接示意图:
语音模块 VCC -> Micro:bit 3.3V 语音模块 GND -> Micro:bit GND 语音模块 TX -> Micro:bit P0 (RX) 语音模块 RX -> Micro:bit P1 (TX)实操心得:务必确认你的语音模块是3.3V电平!绝大多数Micro:bit的GPIO口只能耐受3.3V,连接5V模块可能会损坏主板。如果模块只有5V电平,则需要一个逻辑电平转换器(如TXS0108E)。
2.3 语音模块的初始配置
模块上电后,通常会有指示灯闪烁。很多模块支持通过串口发送特定指令进行配置。我们需要做两件事:
设置识别词条:告诉模块我们需要识别哪几个词。每个词会对应一个编号(ID)。例如,我们可以设置:
- ID1: “上”
- ID2: “下”
- ID3: “左”
- ID4: “右”
- ID5: “开始”
- ID6: “停止”
设置模块工作模式:一般为“循环识别”模式,即模块持续监听,一旦识别到已设置的词条,就通过串口输出对应的ID号。
你可以使用电脑上的串口调试助手(如Arduino IDE自带的串口监视器、Putty等),连接模块的串口(可能需要一个USB转TTL工具),发送指令进行配置。常用指令格式如AA 55 07 01 00 01 B4(添加ID1),具体指令集需要查阅你所购买模块的数据手册。
避坑指南:很多新手在这一步会卡住,因为指令是十六进制格式,且需要严格的校验和。一个更简单的方法是,寻找模块卖家提供的专用配置软件(通常是一个.exe文件),通过图形化界面点击设置,软件会自动生成并发送指令,成功率几乎100%。
3. 游戏逻辑设计与编程环境搭建
硬件连接好后,我们的大脑——程序,就需要开始设计了。这里我以Mind+(基于Scratch3.0的图形化编程软件)和MicroPython两种方式分别讲解,你可以根据熟悉程度选择。
3.1 贪吃蛇游戏状态机分解
无论用哪种语言,游戏的核心逻辑都是一个“状态机”。我们需要明确程序在不同状态下该做什么:
待机状态:游戏未开始。LED点阵显示一个静态的蛇头(一个光点)和食物(一个光点)。在此状态下,程序持续检测语音指令。只有当识别到“开始”指令时,才进入运行状态。
运行状态:游戏进行中。小蛇根据当前方向(初始为右)自动移动。在此状态下,程序需要同时做几件事:
- 持续监听语音指令,更新方向变量(但禁止直接反向,比如不能从“上”立刻变为“下”)。
- 计算蛇头的新位置。
- 碰撞检测:判断蛇头是否撞墙(超出5x5范围)或撞到自己身体。
- 吃食物检测:判断蛇头是否与食物位置重合。如果吃到,蛇身长度加1,在随机空位置生成新食物,分数增加。
- 画面渲染:在LED点阵上清除旧画面,绘制新的蛇身和食物。
- 控制游戏速度:通过
time.sleep()或延时积木来控制蛇的移动速度,从而调节游戏难度。
结束状态:当发生碰撞时,游戏结束。点阵可以显示一个“哭脸”图案或者闪烁“GAME OVER”。此时监听“开始”指令,可以重置游戏,回到待机状态。
3.2 Mind+(图形化)编程详解
Mind+对初学者非常友好,它封装了语音识别模块的通信细节。
第一步:软件与扩展设置
- 打开Mind+,切换到“实时模式”。
- 连接Micro:bit,选择正确的串口。
- 在“扩展”中心,添加“主控板”下的“Micro:bit”,以及“传感器”下的“语音识别模块”(如果找不到,可能需要手动导入用户库,搜索Gravity或通用语音识别模块的积木库)。
第二步:核心积木逻辑搭建
- 初始化:当绿旗被点击,设置LED点阵亮度,初始化蛇身列表(一个包含坐标的列表)、食物位置、方向变量、游戏状态变量。
- 语音监听循环:建立一个“重复执行”积木。在里面放入“读取语音识别结果”积木。这个积木会返回一个数字(对应我们设置的ID)。接着用“如果...那么...”积木进行判断。
依次判断ID1到ID6,并更新对应的变量或状态(如“开始”指令将游戏状态设为“运行”)。如果 (识别结果) = 1 那么 将 [方向] 设为 [上] - 游戏主循环:另一个“重复执行”积木,但它的执行条件应基于“游戏状态”变量。只有当状态为“运行”时,才执行移动、碰撞检测、绘制等逻辑。
- 移动:根据方向变量,计算蛇头新坐标。例如,方向为“右”,则新蛇头X坐标加1。
- 吃食物:判断新蛇头坐标是否等于食物坐标。如果相等,则不删除蛇尾(实现增长),并随机生成新食物(确保不在蛇身上)。
- 移动(未吃到食物):将新蛇头坐标插入蛇身列表最前面,并删除蛇身列表的最后一项(蛇尾坐标)。
- 碰撞检测:判断新蛇头是否X/Y坐标超出0-4的范围,或者是否已经存在于蛇身列表中(撞到自己)。
- 绘制:清除屏幕,用一个“重复执行”积木遍历蛇身列表的每个坐标,使用“点亮XY”积木绘制。再单独点亮食物坐标。
注意事项:Mind+中操作列表(数组)可能不如代码直观。务必理清“在列表头部插入新项”和“删除列表末尾项”这两个操作,这是贪吃蛇移动的核心。图形化编程的优势是直观,但当逻辑复杂时,积木堆叠会很长,需要耐心梳理。
3.3 MicroPython(代码)编程实现
对于追求更灵活控制和深入理解的同学,MicroPython是更好的选择。这里给出核心代码框架和解释。
第一步:开发环境你可以使用Mu Editor、Thonny或VSCode+Micro:bit插件。将编写好的.py文件刷入Micro:bit即可。
第二步:核心代码解析
from microbit import * import random import speech # 1. 初始化 snake = [[2, 2]] # 蛇身,初始为一个点 food = [random.randint(0, 4), random.randint(0, 4)] # 食物位置 direction = “right” # 初始方向 game_active = False # 游戏状态 score = 0 # 2. 串口初始化(连接语音模块) uart.init(baudrate=9600, bits=8, parity=None, stop=1, tx=pin1, rx=pin0) # 使用P1(TX), P0(RX) def draw(): """在点阵上绘制蛇和食物""" display.clear() for segment in snake: if 0 <= segment[0] <= 4 and 0 <= segment[1] <= 4: display.set_pixel(segment[0], segment[1], 9) # 蛇身亮度 if 0 <= food[0] <= 4 and 0 <= food[1] <= 4: display.set_pixel(food[0], food[1], 5) # 食物亮度 def generate_food(): """在非蛇身的位置生成新食物""" global food while True: new_food = [random.randint(0, 4), random.randint(0, 4)] if new_food not in snake: food = new_food break # 3. 主循环 while True: # --- 语音指令处理 --- if uart.any(): # 如果串口有数据 incoming = uart.read() # 读取数据 # 假设模块发送单个字节的ID(1-6) if incoming == b'\x01': if direction != “down”: direction = “up” elif incoming == b'\x02': if direction != “up”: direction = “down” elif incoming == b'\x03': if direction != “right”: direction = “left” elif incoming == b'\x04': if direction != “left”: direction = “right” elif incoming == b'\x05': game_active = True display.show(Image.HAPPY) # 开始游戏提示 sleep(500) elif incoming == b'\x06': game_active = False display.show(Image.NO) # 停止游戏提示 # --- 游戏逻辑 --- if game_active: # 计算新蛇头 head = snake[0].copy() if direction == “up”: head[1] -= 1 elif direction == “down”: head[1] += 1 elif direction == “left”: head[0] -= 1 elif direction == “right”: head[0] += 1 # 碰撞检测(撞墙或撞自己) if (head[0] < 0 or head[0] > 4 or head[1] < 0 or head[1] > 4 or head in snake): game_active = False display.show(Image.SAD) # 游戏结束 speech.say(“Game Over”) # 语音播报(可选) sleep(2000) # 重置游戏 snake = [[2, 2]] food = [random.randint(0, 4), random.randint(0, 4)] direction = “right” score = 0 continue # 吃食物检测 snake.insert(0, head) # 将新头插入列表前端 if head == food: score += 1 generate_food() else: snake.pop() # 没吃到食物,移除蛇尾 # 绘制画面 draw() sleep(300) # 控制游戏速度,单位毫秒 else: # 待机状态下的绘制 draw() sleep(100)这段代码结构清晰,包含了语音读取、指令处理、游戏逻辑、碰撞检测和画面绘制。uart.any()和uart.read()是读取语音模块数据的关键。sleep(300)决定了蛇的移动速度,你可以调整这个值来改变游戏难度。
4. 系统集成调试与性能优化
将硬件和软件组合在一起,真正的挑战才刚刚开始。调试是项目成功的关键。
4.1 语音识别模块的调试技巧
确认通信:首先,不加载贪吃蛇程序,只写一个简单的串口回显程序。让Micro:bit将语音模块发来的数据原样通过串口打印到电脑的串口监视器上。当你说话时,观察是否有数据返回。这能最快确定硬件连接和模块基础功能是否正常。
# 简易回显测试代码 from microbit import * uart.init(baudrate=9600, tx=pin1, rx=pin0) while True: if uart.any(): data = uart.read() print(“Received:”, data) # 在Mu Editor的REPL中查看解决误触发:环境噪音可能导致模块误识别。可以:
- 物理层面:将模块远离扬声器、电机等噪音源。有些模块有麦克风灵敏度调节电位器,可以适当调低。
- 软件层面:在代码中增加“指令锁”。例如,识别到一个有效指令后,设置一个100-200毫秒的“冷却时间”,在此期间忽略所有新指令,避免因一个词的尾音或回音导致连续触发。
提升识别率:
- 训练词条:如果模块支持,尽量使用标准的普通话发音录制训练词条。对于“左”、“右”这类易混词,可以改为“向左转”、“向右转”这样的短句,区分度更高。
- 增益调节:在安静环境下测试,找到最佳的拾音距离(通常10-50厘米)。
4.2 游戏逻辑的调试与优化
LED显示错乱:这是最常见的问题。根本原因是绘制逻辑和坐标系统混乱。Micro:bit的
display.set_pixel(x, y, brightness)函数中,x是列(0-4,从左到右),y是行(0-4,从上到下)。务必确保你的蛇身列表里存储的坐标[x, y]与这个系统一致。在每次绘制前display.clear()也很重要。蛇身移动异常:检查蛇身列表的操作顺序。正确的顺序是:先根据方向计算新头坐标 -> 将新头插入列表最前 -> 判断是否吃到食物 -> 如果没吃到,则删除列表最后一项(蛇尾)。顺序错了会导致长度异常或移动卡顿。
游戏卡顿或反应慢:主要原因可能是主循环内的
sleep时间过长,或者某些操作(如随机生成食物时陷入死循环)耗时太久。优化建议:- 将食物生成函数
generate_food()中的while True循环加上一个安全计数器,避免因蛇身几乎铺满屏幕时寻找空位导致的长时间阻塞。 - 使用
running_time()函数来制作更精确的游戏时钟,而不是单纯依赖sleep,这样即使某帧计算耗时稍长,也不会导致游戏速度越来越慢。
- 将食物生成函数
4.3 功能扩展与创意提升
基础版本完成后,你可以尝试以下扩展,让项目更具挑战性和趣味性:
- 增加语音合成反馈:使用另一个语音合成模块(如SYN6288),或者利用Micro:bit V2板载的扬声器(通过
speech.say()函数,但效果较机械)。当游戏开始、结束、吃到食物时,播放对应的语音提示,如“游戏开始”、“真棒”、“哎呀,撞墙了”。 - 多难度级别:通过语音指令“简单”、“困难”来切换游戏速度(即
sleep的时间)。甚至可以引入“加速”道具,吃到后短时间内蛇的移动速度加快。 - 积分系统与显示:将得分
score变量通过LED点阵滚动显示,或者外接一个OLED屏幕来显示更丰富的信息(如长度、速度、历史最高分)。 - 双人对战模式:设计两条蛇,使用不同的语音指令集控制(如“红蛇上”、“蓝蛇左”),在有限的场地内竞争,看谁生存更久。
5. 常见问题排查速查表
下表汇总了开发过程中最可能遇到的“坑”及其解决方案:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 语音模块无任何反应,指示灯不亮 | 电源连接错误或模块损坏 | 1. 用万用表检查VCC和GND之间电压是否为3.3V。 2. 尝试单独给模块供电,检查指示灯状态。 |
| 串口能收到数据,但全是乱码 | 波特率不匹配 | 1. 确认代码中uart.init()的波特率与语音模块设置的波特率完全一致(常用9600)。2. 检查TX/RX线是否接反。 |
| 能识别指令,但游戏无反应 | 指令处理逻辑错误或游戏状态机未切换 | 1. 在收到指令后,添加print()语句,确认进入了正确的判断分支。2. 检查控制游戏主循环的 game_active变量是否被正确设置为True。 |
| 蛇的移动方向不受控制,或反向 | 方向更新逻辑有缺陷 | 检查方向判断代码,确保加入了“禁止直接反向”的逻辑(例如,当前方向是“上”,则不能立刻设置为“下”)。 |
| LED点阵显示异常,有残留亮点 | 未在每次绘制前清屏 | 确保在draw()函数或绘制积木的最开始,执行了display.clear()或“清空屏幕”操作。 |
| 游戏运行几分钟后突然卡死或复位 | 内存泄漏或死循环 | 1. 检查列表操作是否导致内存无限增长(如吃到食物后未正确生成新食物)。 2. 为 generate_food()函数的while循环添加最大尝试次数(如100次),超过则判定为游戏胜利。 |
| 识别率低,经常误触发 | 环境噪音大或词条设置不当 | 1. 在安静环境下测试,调整麦克风距离。 2. 修改识别词条,使用更差异化、更长的词语或短句。 3. 在软件中增加指令去抖和“冷却时间”。 |
这个项目从构思到实现,最深的体会是,把复杂的功能拆解成一个个可测试的小模块,是成功的关键。不要试图一次性写完所有代码。先让语音模块能可靠地打印出ID号,再让贪吃蛇能用按键跑起来,最后把两者结合。每完成一步就充分测试,这样当问题出现时,你就能很快定位到是哪个环节出了差错。硬件项目充满了不确定性,但正是解决这些不确定性的过程,带来了最大的成就感。当你第一次对着麦克风说“右”,屏幕上的像素小蛇乖巧地转向时,那种奇妙的、赋予硬件以生命的愉悦感,是纯软件编程难以比拟的。