news 2026/10/3 16:33:49

树莓派+Pico失语患者沟通板:按键、菜单、语音播报全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
树莓派+Pico失语患者沟通板:按键、菜单、语音播报全解析

去年秋天,朋友的父亲脑梗出院后,人醒过来了,话却说不出来。医生说这叫运动性失语,听力和理解力大多还在,只是嘴和脑子的连线断了。那段时间,家里全靠按铃呼叫护士,但护士不可能时刻盯着,家人又听不懂那些含糊的“啊啊”到底是想喝水、想翻身,还是肚子疼。我帮他们做了一个“Stroke Communication Aid”的树莓派语音沟通板——准确说,是一台负责发声的树莓派加上一个负责按键输入的Pico面板,患者只要按下大按钮,就能把“我想喝水”“请帮我翻身”“我腿疼”这些话通过扬声器清楚地说出去。这个项目非常适合家有失语长辈的护理者、想用低成本硬件解决实际问题的创客朋友参考,核心就三件事:按键、菜单、播报。

1. 为什么做“树莓派语音沟通板”:一次真实的护理困境

1.1 失语不等于失能,需求一直都在

脑卒中后失语的患者,最痛苦的不是肢体不便,而是“心里有,嘴上说不出”。口渴、疼痛、想上厕所、被子太薄、药该吃了——这些需求每时每刻都在产生,但家人必须靠猜。我朋友最初买过商业的病床呼叫器,只有一两个按键,支持录制固定语音,功能倒是没问题,价格却让普通家庭犹豫:稍微做得像样一点的医用餐桌沟通器要上千元,而且多数按键少、无法扩展,后期想换语音还得联系厂家。

这个需求本质上是:把高频的、可枚举的日常短语固化成按钮,让患者用最少的手指动作完成表达。注意,这里不是做复杂的语义理解,而是做一个“词汇表面板”。临床上很多失语患者对简单文字、数字、颜色仍然有辨识能力,所以我们做的是“结构化选择”,不是AI对话。这个定位决定了整个项目的复杂度上限——它不需要自然语言处理,不需要摄像头,不需要云端,一台便宜的树莓派加一个单片机面板就足够了。

1.2 为什么是树莓派而不是买成品

我当时的思考逻辑很简单:成品沟通器贵、封闭、不好改,而树莓派生态成熟,GPIO、音频、WiFi、Python一把抓。选树莓派而不是普通单片机的主要原因有三点:

  • 音频播放能力强。树莓派接上I2S功放模块,能播放清晰的WAV/MP3,音量可调,树莓派Zero 2 W的价格也才一百出头。
  • 文件管理方便。语音素材存在SD卡里,家人随时可以通过WiFi或U盘增删,不用重新烧录芯片。
  • 系统容错好。哪怕程序崩了,SSH进去重启服务就行;单片机固件一旦跑飞,患者家属可不会重新烧录。

我还对比过“树莓派Zero 2 W + Pico单片机”和“单块树莓派直连按钮”两种路线,后面详细说。如果你家里已经有树莓派4B或树莓派5,也可以直接按本文思路复刻,只是体积大一点、功耗高一点而已。

2. 双板分工:树莓派管声音,RP2040管交互

2.1 一开始我也打算一块树莓派干完所有事……

最开始我图省事,想用树莓派Zero 2 W的GPIO直接接按钮和OLED屏,Python里同时轮询按键、刷新屏幕、调aplay播放语音。原型跑通后发现问题很明显:树莓派是跑Linux的重负载设备,启动慢(十几秒到几十秒),系统更新、WiFi闪断、Python进程卡死都会让按钮失灵;而且GPIO轮询在Linux下延迟不稳定,按键按下去偶尔要等一两百毫秒才响应,老人会以为没按上,再按一下,结果播了两遍。

更关键的是,树莓派Zero 2 W没有板载3.5mm耳机孔,音频得走USB声卡或I2S功放,这倒是其次;真正的问题是它不适合做“实时的人机交互面板”。所以后来我把输入面板拆出去,用了一块树莓派Pico(RP2040芯片)专门负责按钮扫描和OLED显示,树莓派只负责一件事:收到命令后播放对应的语音文件。Pico的GPIO是裸金属级别的,按键响应可以做到毫秒级,而且它不吃Linux那一整套系统,断电重启也是秒开。两块板子各干各的活,稳定得多。

2.2 硬件选型与成本清单

以下是实际下单的硬件清单,价格按2024年国内常见渠道参考,会有浮动:

硬件型号/规格参考价作用
树莓派Raspberry Pi Zero 2 W约100元主控,播放语音、WiFi管理
单片机Raspberry Pi Pico(RP2040)约15元按键扫描、OLED显示、串口通信
OLED屏0.96寸 I2C SSD1306 128x64约15元显示当前菜单页和选中项
I2S功放MAX98357A模块约12元树莓派音频输出到扬声器
扬声器3W 8Ω小喇叭,直径4cm约8元发声
大按钮彩色带帽按钮 12mm或更大约10元患者按压,防误触
锂电池支持2A输出的移动电源/充电宝约40元整体供电
外壳亚克力板或3D打印盒约20元固定面板,绝缘
杂项杜邦线、热熔胶、纸质标签约10元连接与标识

总成本控制在220元左右,比商业成品低一个数量级,而且所有零件坏了都能单独换。

2.3 连接关系与通信协议

整个系统的连接非常清晰:

  • 树莓派Zero 2 W的USB口给树莓派供电,电池用移动电源;
  • 树莓派的5V和GND引脚给Pico供电(也可以Pico用独立USB口,但没必要);
  • Pico接OLED屏和几个大按钮;
  • Pico的GPIO UART接到树莓派的GPIO串口(UART),用于发送“播放编号”;
  • 树莓派的I2S引脚接MAX98357A功放,功放接扬声器。

通信协议我设计得很简单,每行一条命令,格式就是:

PLAY|编号

比如Pico检测到用户按下“喝水”按钮,就通过串口发一行PLAY|12,树莓派收到后查表,播放12_want_water.wav这个文件。为什么用串口文本而不是直接在GPIO上拉电平?因为按钮不止一个,未来要扩展短语库,文本协议比一堆引脚编码好维护得多,而且串口线就两根,不容易接错。

3. 端到端跑通:从按键到扬声器出声

3.1 树莓派端系统与依赖

我用的是Raspberry Pi OS Lite(不带桌面版),理由很简单:这个项目不需要图形界面,桌面环境会吃掉内存和CPU,还有一堆后台更新,没必要。刷写系统时在rpi-imager里直接开启SSH并预配好WiFi,这样第一次通电就能远程登录。

系统装好后,需要安装两个关键依赖:

sudo apt update sudo apt install -y python3-serial alsa-utils

python3-serial用来读取Pico发来的串口数据;alsa-utils提供aplay命令,负责播放WAV文件。我没用pygame或mpv,因为aplay是最轻量的播放器,一条命令就能播完退出,而且退出后立刻能响应下一条指令,不会出现Python非阻塞播放导致队列积压的问题。

音频输出配置我放在了/boot/firmware/config.txt(老系统是/boot/config.txt),加上一行:

dtoverlay=googlevoicehat-codec

这是让树莓派把I2S引脚映射给MAX98357A功放。不加这一行,功放不会出声。改完重启,用aplay -l能看到声卡设备。然后调节音量:

sudo amixer set 'DAC' 80%

这里有个坑:MAX98357A默认的DAC可能被静音,导致怎么播放都没有声音。我第一次调通时卡在“功放通电了但完全不响”,后来发现是音量被设为0%。你如果复刻,务必在配置后跑一句speaker-test -t wav -c 1验证。

3.2 RP2040面板:按键扫描与OLED显示

Pico端我用MicroPython写固件,简单、易改。按键布局上,我做的是“一键直达”方案,比多级菜单更适合患者:一块面板上排列5个大按钮,其中4个是常用短语(喝水、翻身、热了、盖被子),一个红色的是紧急呼叫“请马上到我身边来”。另外加一个翻页按钮,按一下切换第二组4个短语。

OLED屏显示当前是第几组、每个按钮对应的编号。这里我必须诚实说明:0.96寸OLED默认MicroPython的ssd1306库不支持中文显示,显示中文需要额外烧录字形数据,成本很高。我的第一版方案是“OLED显示页码和编号 + 按钮旁边贴打印好的中文标签”,实际使用证明够用了。如果你想把中文直接显示在OLED上,那属于V2的改进项,后面我会提到。

Pico端的核心代码骨架如下:

from machine import Pin, UART, I2C import ssd1306, time # OLED 0.96寸,I2C0:SDA=GP8,SCL=GP9 i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000) oled = ssd1306.SSD1306_I2C(128, 64, i2c) # UART0:TX=GP0,RX=GP1,接树莓派GPIO14/GPIO15 uart = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1)) # 按键:使用内部下拉,按下去为高电平 btn_page = Pin(16, Pin.IN, Pin.PULL_DOWN) # 翻页 btn_call = Pin(17, Pin.IN, Pin.PULL_DOWN) # 紧急呼叫 btn_say = Pin(18, Pin.IN, Pin.PULL_DOWN) # 当前选中项确认播放 # 页面内容示例:每页4个短语ID pages = [ [1, 2, 3, 4], # 第一页:1喝水 2翻身 3热了 4盖被子 [5, 6, 7, 8] # 第二页:5想上厕所 6肚子疼 7累了 8坐起来 ] current_page = 0 #############################看后续补充#############################

上面有一段被我故意截断了,因为实际按键扫描、OLED刷新、防抖逻辑加在一起会占不少篇幅。这里说几个关键点:按键扫描必须带防抖,否则Pico这种高速轮询会导致一次按压被识别成多次,嘭嘭嘭连着播好几遍;我的做法是判断电平变化后延迟20毫秒再读一次,确认稳定才生效。紧急呼叫按钮则单独处理,它不参与翻页,无论在哪个页面按下,都立即发送PLAY|99。

Pico与树莓派之间的串口通信,波特率选9600就够,因为命令只有一行“PLAY|编号”,9600波特每秒能传约960字节,一帧命令才十几字节,完全没有压力。波特率太高反而容易受杜邦线干扰。

3.3 串口命令转播放:树莓派服务脚本

树莓派端的服务脚本我用Python写,结构大概是:

import serial, subprocess, time, os SOUND_DIR = "/home/pi/stroke_voice/audio" SOUND_MAP = { 1: "01_want_water.wav", 2: "02_turn_over.wav", 3: "03_too_hot.wav", 4: "04_cover_blanket.wav", 99: "99_help.wav", } ser = serial.Serial("/dev/serial0", 9600, timeout=0.5) ser.reset_input_buffer() def play(index): filename = SOUND_MAP.get(int(index)) if not filename: return path = os.path.join(SOUND_DIR, filename) if os.path.exists(path): subprocess.run(["aplay", "-q", path]) while True: line = ser.readline() if line: try: cmd = line.decode().strip() if cmd.startswith("PLAY|"): play(cmd.split("|")[1]) except Exception as e: print("error:", e)

几个细节值得解释。subprocess.run是阻塞式的,播完这一条才继续读下一行,好处是避免两句语音叠在一起,坏处是如果老人连续按键,后面的指令会排队。所以我在Pico端做了限制:每次播放开始后1.2秒内忽略普通按键,防止误触和连按。紧急呼叫按钮不受这个限制,这很重要——紧急需求应该永远可以打断普通播报。

树莓派的串口默认可能被系统登录控制台占用,需要编辑/boot/cmdline.txt,把其中console=serial0,115200这段删掉,否则Pico发来的数据会被内核当成登录输入吃掉。删掉后重启,/dev/serial0就干净了。

3.4 开机自启与掉电恢复

这设备是给患者用的,不可能指望他们每次开机去终端敲命令。我把它做成systemd服务,开机自动运行,程序崩溃了还会自动重启:

[Unit] Description=Stroke Voice Aid Server After=multi-user.target [Service] User=pi ExecStart=/usr/bin/python3 /home/pi/stroke_voice/server.py Restart=always RestartSec=5 [Install] WantedBy=multi-user.target

把文件放到/etc/systemd/system/stroke-vaid.service,然后:

sudo systemctl enable --now stroke-vaid

还有一个权限坑:如果脚本读不到/dev/serial0,提示Permission denied,多半是用户不在dialout组。执行sudo usermod -aG dialout pi后重新登录即可。

4. 语音素材:用TTS还是录家人的声音

4.1 两种路线对比

语音文件是这个项目的灵魂。同样是“我想喝水”,用电子合成音说出来,和用家人自己的声音说出来,对患者是完全不同的感受。我把两条路线都试过:

方案优点缺点适合场景
TTS合成(espeak-ng/piper等)快速批量生成,改文案方便机械感强,语句平淡,老人反感快速原型验证、大量短句
录制家人声音亲切、有情感、辨识度高录制耗时,改一次文案要重录长期给同一患者使用

我个人强烈推荐第二条路线。脑卒中失语患者往往对熟悉的声音更敏感,家人录的“爸,我在呢,别着急”和机器人说的“别着急”完全是两回事。朋友后来录完素材给他父亲试听时,老人眼睛明显亮了——这个细节不是技术参数能替代的。

4.2 实用录音规范

如果走真人录音路线,请遵守一套简单的录音规范,后期会省很多事:

  • 用手机录音即可,但要在安静房间、同一位置、同一音量下录完所有句子;
  • 句子尽量短,一句话控制在6~10个字,比如“我想喝水”而不是“爸你现在想不想要喝一点水”;
  • 语速比平时慢20%,每个字咬清楚,方便机器播放时听清;
  • 每句话之间停顿2秒,方便后期切分;
  • 用Audacity做降噪和归一化,把每段音频振幅统一到-3dB左右,避免“翻身”声音小得像蚊子叫,“肚子疼”又轰得人一激灵。

导出格式我建议用WAV,采样率22050Hz、16bit、单声道。这个规格比CD音质低,但人声清晰度足够,文件大小只有44.1kHz的一半,一分钟约2.6MB,100条短语也就260MB,SD卡完全装得下。有人会说MP3更省空间,但aplay对WAV支持最直接,不用额外解码,启动播放几乎没有延迟,这在交互体验上很重要。

4.3 生成文件的命名与映射

文件命名我采用“两位序号_语义.wav”的格式,并把编号固化在Pico端和树莓派端两处:

01_want_water.wav 02_turn_over.wav 03_too_hot.wav ...

为什么不用中文文件名?因为树莓派系统的locale有时候对UTF-8文件名不敏感,SSH传输也容易乱码,全用拼音或英文最稳。Pico端按钮对应的编号、OLED上显示的编号、树莓派端SOUND_MAP里的编号,三者必须一致。建议做一张Excel对照表:编号、按钮位置、OLED显示、音频文件名、具体语音内容,五列。没有这张表,后期改起来一定会漏。

5. 实测踩坑:三个差点劝退我的问题

5.1 供电:树莓派Zero 2 W的“隐形杀手”

第一版调试时,我在实验室用电脑USB口给树莓派供电,Pico也从树莓派取电,功放一播放声频就会随机重启。查了半天,发现是电流不够。树莓派Zero 2 W官方推荐供电能力是2.5A,MAX98357A播放瞬间的电流会突然拉高,USB口根本喂不饱。后来换成额定2A以上的移动电源,并且用粗短线连接,再也没出现过播放时重启。这里有个隐藏经验:不要用“标称2A但实际线材很细”的劣质充电线,线压降会让树莓派检测到欠压(红灯常亮、性能下降)。排查这类问题,可以在树莓派上跑vcgencmd get_throttled,如果返回0x1,就意味着曾发生过欠压。

5.2 OLED不亮或花屏

Pico连接0.96寸OLED,最常见的两个现象:完全不亮、显示雪花。完全不亮先查I2C地址,SSD1306常见地址是0x3C或0x3D,MicroPython里可以用一行代码确认:

print(i2c.scan())

如果扫描得到地址,但屏幕还是白的,多半是VCC供电不稳或者SDA/SCL线太长。杜邦线超过20cm就容易出这个问题,解决办法是缩短排线、把电源线和信号线分开走,不要在OLED下方捋成一团。花屏则大概率是屏幕初始化前I2C总线被干扰,加一句time.sleep(0.1)等电源稳定后再初始化,一般能解决。

5.3 串口数据“发过去没反应”的排查链路

这是最磨人的坑。现象是Pico端明明发PLAY|12,树莓派服务端却毫无反应。我按这个顺序排查:

首先,确认/dev/serial0是否存在且未被控制台占用。执行:

ls -l /dev/serial0

如果串口被登录控制台占用,Pico发来的数据会显示成一堆乱码。这时去/boot/cmdline.txt删掉console=serial0,115200,重启。

其次,确认Pico的TX/RX和树莓派交叉连接。UART是交叉的:Pico的TX(GP0)接树莓派的RX(GPIO15),Pico的RX(GP1)接树莓派的TX(GPIO14),必须共地。接错线收不到,还少共地也收不到。

最后做最小回归测试:在Pico端临时写个死循环uart.write("PLAY|12\n"),树莓派上cat /dev/serial0,能看到PLAY|12说明链路通了,再回头查自己的Python服务是不是没启动或读错了波特率。这套“最小链路验证法”在排查串口问题时百试百灵,不要一上来就改代码。

5.4 老人第一次用,按键设计其实比代码更重要

朋友把设备带回家,第一周他父亲基本不用。不是设备坏了,而是老人觉得“按错会麻烦别人”。这个反馈让我意识到,这套设备真正的难点不是技术,是交互设计。后来我们做了三处改进,效果立竿见影:

  • 紧急按钮单独用红色大按钮,和其他按钮明显区分,且按下有咔哒声反馈;
  • 每个按钮旁边贴带大字的打印标签,字高不低于8mm,黑体加粗;
  • 刚开始只启用“喝水”“翻身”“热了”“紧急”四个按钮,等老人用顺手了,再把翻页功能和第二组短语加进去。

不要一上来就给人二十个短语,患者认知负荷会瞬间爆炸。从四个词开始,让设备变成习惯,再逐步扩展,这才是这类护理硬件真正该有的思路。

6. 如果再做一个V2,我会优先改这几件事

6.1 中文字库与提示图标

第一版的OLED只能显示页码和数字,按钮标签靠贴纸。如果做V2,我会在0.96寸OLED上显示中文。实现方式是在Pico端烧录一个简单的汉字点阵字库,比如16x16像素的常用汉字,再配合自绘的简单图标(水滴代表喝水、箭头代表翻身),屏幕虽小但信息密度会高很多。这需要额外写一版字库生成工具,但效果值得。

6.2 低电量和故障自检语音

树莓派可以通过vcgencmd get_throttled检测是否欠压,如果检测到欠压或者电量低,就播一句“电量不足,请帮我充一下电”。对长期卧床的患者来说,设备没电而自己说不出来,是一种被剥夺感。这个功能虽然只有几行代码,但实用性极高,属于“少了自己才知道多重要”的那种细节。

6.3 网页后台管理短语库

目前改短语需要拔SD卡或者SSH进去改文件,对家人还是太硬核。V2我打算在树莓派上跑一个小Web服务,家人手机连同一局域网就能打开页面,增删短语、上传录音、调整编号,实时生效。通信链路不变,只是把“更新音频映射”这件事从命令行变到浏览器。

6.4 独立后备播放通道

说到底,树莓派挂了,整个板就哑了。V2的终级形态是让Pico直接驱动一个DFPlayer Mini MP3模块,把最常用的几条紧急语音预存在独立TF卡里,树莓派只是扩充通道。树莓派正常时用树莓派的高音质播报;树莓派异常时,Pico仍然能独立播放“紧急、喝水、翻身”这三条保命级别的指令。对医疗护理场景来说,冗余比功能多更值钱。

这个项目做完,我最大的体会是:硬件选型其实没有那么多玄学,树莓派负责它擅长的生态和音频,Pico负责它擅长的实时交互,各司其职;而真正决定设备能不能被用起来的,永远是那些容易被忽略的细节——声音是不是家人的、按钮是不是够大、开机是不是稳、没电了会不会说话。如果你也要复刻这个东西,我的建议是:先剪二十句核心短语,先跑通按键到喇叭这条链路,再谈OLED菜单和花哨功能。先把“我渴了”这句播出来——比什么架构设计都重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 16:31:28

这个bug,半年了才被我解决

我是写前端的,日常跟浏览器、组件、接口打交道。今天不聊技术,想聊个我亲身趟过的"隐性 bug"——用咱们这行能听懂的方式说。同行的你大概也有过这种时候:线上监控一堆,自己的身体却从没配过一条告警。一、cron 飘了我这…

作者头像 李华
网站建设 2026/10/3 16:28:39

接口基础知识

一、接口的概念接口使用interface关键字定义,它是一种行为规范 / 能力标准,主要用来规定 “某类事物必须具备哪些行为”。public interface 接口名 { }接口可以理解为:制定规则;定义能力;不关心具体实现,只…

作者头像 李华
网站建设 2026/10/3 16:25:54

Android内存泄漏就这样产生了:从Base URL改到TaoToken排查一次OOM

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华