1. 项目概述:当风扇“听懂”人话
最近在捣鼓行空板K10,发现这玩意儿真是个宝藏开发板,尤其适合做点好玩又实用的智能家居小项目。手头正好有个闲置的USB小风扇,天气渐热,每次想开个风扇还得起身去按开关,或者满桌子找遥控器,实在麻烦。于是,一个念头冒了出来:能不能让风扇“听懂”人话?我说“开风扇”,它就转;我说“调大风”,它就加速;我说“摇头”,它就开始左右摆动。听起来像是科幻电影里的场景,但用行空板K10加上一些简单的传感器和语音模块,完全可以在家里低成本实现。
这个“智能语音风扇”项目的核心,就是利用行空板K10作为大脑,结合离线语音识别模块,赋予传统风扇智能交互能力。它不依赖于复杂的云服务或网络,所有指令都在本地处理,响应速度快,且不用担心隐私问题。对于刚接触物联网和智能硬件开发的朋友来说,这是一个绝佳的练手项目,涵盖了硬件连接、传感器控制、语音交互逻辑编程等多个基础且实用的知识点。整个过程下来,你不仅能收获一个酷炫的智能小家电,更能深刻理解从想法到实物的完整实现路径。
2. 核心硬件选型与功能设计思路
2.1 为什么选择行空板K10作为主控?
市面上开发板那么多,树莓派、Arduino、Micro:bit各有千秋,我最终选择行空板K10,主要是基于以下几个非常实际的考量:
首先,集成度高,开箱即用。行空板K10自带了一块2.8英寸的触摸屏,这对于我们这个项目来说简直是“神来之笔”。我们不仅可以通过语音控制风扇,还可以在屏幕上实时显示风扇的状态(如当前档位、模式、环境温度等),甚至可以通过触摸屏进行手动控制,实现“语音+触控”双交互。这比用Arduino再加一堆外设和显示屏要方便整洁得多。
其次,强大的计算与连接能力。K10基于高性能处理器,运行完整的Linux系统,这意味着我们可以使用Python这样简单易用又功能强大的语言进行编程。Python丰富的库生态,让我们处理语音识别结果、控制GPIO引脚、编写图形界面都变得轻而易举。同时,板载Wi-Fi和蓝牙模块,为未来扩展(比如接入家庭局域网实现手机App控制)预留了可能性,虽然本项目是离线语音,但硬件基础已经打好。
再者,丰富的接口与供电便利。K10提供了多个GPIO、I2C、UART接口,方便我们连接各种传感器和执行器。更重要的是,它可以通过Type-C接口供电,并且能通过其GPIO口输出稳定的5V电压,这正好可以用来驱动我们后续要用的电机驱动模块和语音模块,简化了整个系统的电源设计。
注意:行空板K10的GPIO口输出电压是3.3V逻辑电平,在连接一些需要5V逻辑或更高驱动电压的外设时(如某些电机驱动模块),务必注意电平匹配和驱动能力,可能需要额外的电平转换电路或驱动芯片。
2.2 离线语音识别模块:LD3320 vs. SYN7318
实现语音控制的核心是语音识别模块。在线语音识别(如百度、科大讯飞云API)需要网络,有延迟和隐私顾虑。因此,离线语音识别模块是我们的不二之选。它内置识别算法和词条库,上电即可工作,说啥听啥完全在本地决定。
我对比了两款常见的离线语音模块:
- LD3320:这是一款非常经典且性价比极高的芯片。它采用非特定人语音识别技术,理论上不需要训练,直接说普通话关键词就能识别。其优点是价格便宜,电路简单,通过UART或SPI与主控通信。缺点是识别率在复杂环境下(有背景噪音)可能有所下降,且可定制的词条数量有限(通常几十条)。
- SYN7318:这是一款性能更强大的语音合成与识别模块。它不仅能够识别语音,还能用非常自然的语音进行回复(TTS)。识别率相对LD3320更高,抗噪能力更好,且支持更多数量的指令词定制。通信方式也是UART。缺点是价格比LD3320贵一些。
对于风扇控制这个场景,指令相对固定且简单(“打开风扇”、“关闭风扇”、“加大风量”、“减小风量”、“开始摇头”、“停止摇头”等),大概10-20个词条就够了。考虑到成本和复杂度,我选择了LD3320模块。它的性能完全足够,而且其简单的特性更适合初学者理解语音交互的基本流程:声音 -> 模块识别 -> 输出文本结果 -> 主控解析文本 -> 执行对应动作。
2.3 执行机构:风扇与摇头电机的驱动方案
被控制的对象是风扇,主要需要两个执行机构:
- 风扇电机:用于产生风力。我们需要控制其开关和转速(档位)。普通的USB风扇电机通常是直流电机,工作电压5V。直接通过行空板的GPIO口驱动是不行的,电流不够。因此需要一个电机驱动模块。这里我选用最常用的L9110S或L298N这类双路电机驱动模块。它们可以通过主控板的PWM(脉冲宽度调制)信号来精确控制电机的转速,实现无级调速或多档位调节。
- 摇头舵机:用于实现风扇头部的左右摆动。这是一个标准的舵机(Servo)。舵机通过接收特定周期的PWM信号来控制旋转角度。行空板K10的Python库(如
pinpong库)可以很方便地生成控制舵机的PWM信号。我们只需要一个GPIO口连接舵机的信号线即可。
整体驱动逻辑:行空板K10解析到“加大风量”指令后,会在程序中提高输出给L9110S模块的PWM信号占空比,从而让电机获得更高的平均电压,转速加快。解析到“开始摇头”指令后,则控制舵机信号引脚,让舵机在0-180度之间周期性往复运动。
3. 系统搭建与硬件连接详解
3.1 物料清单与电路连接图
在开始焊接和插线前,请准备好以下材料:
- 主控:行空板K10 1块
- 语音模块:LD3320离线语音识别模块 1个
- 电机驱动:L9110S电机驱动模块 1个
- 执行器:5V直流风扇电机 1个,9g微型舵机 1个
- 电源:5V/2A以上的USB电源适配器 1个,用于给行空板供电(行空板再为其他模块供电)
- 连接件:杜邦线(公对公、公对母)若干,可能需要面包板辅助连接
- 结构件:固定风扇电机和舵机的支架或外壳(可用3D打印或手工制作)
电路连接是关键一步,务必仔细:
行空板K10与LD3320语音模块连接:
- LD3320的VCC-> 行空板的5V引脚。
- LD3320的GND-> 行空板的GND引脚。
- LD3320的TX(发送端) -> 行空板的RX(接收端,例如
P14,需在软件中配置为UART接收)。 - LD3320的RX(接收端) -> 行空板的TX(发送端,例如
P15)。
注意:UART通信是交叉连接,模块的TX接主控的RX,模块的RX接主控的TX。行空板的引脚编号请参考官方文档,确认其UART功能对应的物理引脚。
行空板K10与L9110S电机驱动模块连接(以驱动一路电机为例):
- L9110S的VCC-> 行空板的5V引脚。
- L9110S的GND-> 行空板的GND引脚。
- L9110S的A-1A(输入A) -> 行空板的一个GPIO/PWM引脚 (例如
P8)。 - L9110S的A-1B(输入B) -> 行空板的另一个GPIO引脚 (例如
P9),或者直接接GND(取决于电机转向控制逻辑,若只需单向调速,可将1B接GND)。
L9110S与风扇电机连接:
- 风扇电机的正极 -> L9110S的A-OA输出端。
- 风扇电机的负极 -> L9110S的A-OB输出端。
行空板K10与舵机连接:
- 舵机的红色线(VCC)-> 行空板的5V引脚。
- 舵机的棕色线(GND)-> 行空板的GND引脚。
- 舵机的橙色线(信号)-> 行空板的一个GPIO/PWM引脚 (例如
P13)。
电源规划:所有模块的5V和GND最好从行空板上的同一组引脚引出,并确保你的USB电源适配器能提供足够的电流(总电流 > 行空板自身功耗 + 电机启动电流 + 舵机工作电流 + 模块静态电流)。如果电机启动时导致行空板重启,说明电源功率不足,需要考虑为电机驱动模块单独供电(但需共地)。
3.2 LD3320模块的指令词烧录与配置
LD3320模块买回来通常需要自己烧录识别词条。这个过程一般通过厂家提供的上位机软件(PC软件)和USB转UART工具完成。
- 连接配置:将LD3320模块通过USB转TTL工具连接到电脑。打开上位机软件,选择正确的串口号。
- 词条设计:设计你的语音指令集。要简短、清晰、不易混淆。例如:
- 主指令:
kai feng shan(打开风扇),guan feng shan(关闭风扇) - 档位指令:
yi dang feng(一档风),er dang feng(二档风),san dang feng(三档风) - 摇头指令:
kai shi yao tou(开始摇头),ting zhi yao tou(停止摇头) - 通用指令:
zui da feng(最大风),zui xiao feng(最小风)
- 主指令:
- 烧录与测试:在上位机软件中输入拼音词条(注意不是汉字),每个词条对应一个返回码(通常是一个字节的16进制数,如
0x01)。将这些词条和返回码烧录到LD3320模块中。烧录成功后,你可以通过上位机软件实时测试:对着模块的麦克风说话,软件会显示识别到的词条和对应的返回码。务必在此环节确保每个指令都能被稳定识别,这是后续编程的基础。
4. 软件编程:行空板上的Python逻辑实现
硬件连接妥当后,大脑(行空板)需要编程。我们使用行空板自带的Jupyter Notebook或Thonny IDE进行Python开发。
4.1 环境准备与库安装
行空板K10默认安装了Python3和许多常用库。我们需要确保几个关键库可用:
pinpong:这是一个优秀的国产开源库,用于控制行空板(及Arduino等)的GPIO、PWM、UART等,对初学者非常友好。pynput或threading:用于实现多线程,让语音监听和风扇控制互不阻塞。unihiker:行空板官方GUI库,用于在板载屏幕上创建交互界面。
通常pinpong和unihiker在行空板镜像中已预装。可以通过在Jupyter中运行!pip list | grep pinpong来检查。如果没有,使用!pip install pinpong安装。
4.2 核心代码结构解析
整个程序可以分为几个模块:串口通信模块(读取语音指令)、指令解析模块(将返回码转化为具体动作)、电机控制模块(PWM控制风扇和舵机)、GUI显示模块(在屏幕上显示状态)。
下面是一个高度简化的核心逻辑框架:
# -*- coding: utf-8 -*- import serial import time from pinpong.board import Board, Pin, PWM from unihiker import GUI import threading # 初始化行空板 Board().begin() # 初始化GUI gui = GUI() # 初始化串口(连接LD3320) # 端口号根据实际连接调整,可能是 '/dev/ttyS1' 或 'COMx'(在Thonny中查看) ser = serial.Serial('/dev/ttyS1', 9600, timeout=0.5) # 初始化GPIO和PWM fan_speed_pin = Pin(Pin.P8, Pin.OUT) # 连接L9110S A-1A fan_speed_pwm = PWM(fan_speed_pin) servo_pin = Pin(Pin.P13, Pin.OUT) # 连接舵机信号线 servo_pwm = PWM(servo_pin) # 全局状态变量 current_speed = 0 # 0-100代表速度百分比 is_fan_on = False is_shaking = False shake_angle = 90 shake_direction = 1 def parse_voice_command(data_hex): """解析LD3320传来的16进制数据""" command_map = { 0x01: "fan_on", 0x02: "fan_off", 0x03: "speed_up", 0x04: "speed_down", 0x05: "shake_on", 0x06: "shake_off", } return command_map.get(data_hex, "unknown") def control_fan_speed(speed_percent): """控制风扇转速,speed_percent: 0-100""" global current_speed if 0 <= speed_percent <= 100: # 将百分比转换为PWM占空比(0-4095对应行空板PWM分辨率) duty = int(speed_percent / 100.0 * 4095) fan_speed_pwm.duty(duty) current_speed = speed_percent update_gui_display() print(f"风扇速度设置为: {speed_percent}%") def control_servo_shake(enable): """控制舵机摇头""" global is_shaking is_shaking = enable if enable: print("开始摇头") # 这里可以启动一个线程来控制舵机往复运动 else: # 停止摇头,舵机回中或停止 servo_pwm.duty(angle_to_duty(90)) # 假设90度是中间位置 print("停止摇头") def angle_to_duty(angle): """将角度(0-180)转换为舵机PWM占空比(通常对应0.5ms-2.5ms脉宽)""" # 具体计算公式需根据舵机规格调整,这是一个示例 min_duty = 102 # 对应0.5ms @ 20ms周期 (50Hz) max_duty = 512 # 对应2.5ms @ 20ms周期 return int(min_duty + (angle / 180.0) * (max_duty - min_duty)) def update_gui_display(): """更新行空板屏幕上的状态显示""" gui.clear() gui.draw_text(x=120, y=50, text=f"风扇状态: {'开启' if is_fan_on else '关闭'}", font_size=20) gui.draw_text(x=120, y=100, text=f"当前档位: {current_speed}%", font_size=20) gui.draw_text(x=120, y=150, text=f"摇头: {'开启' if is_shaking else '关闭'}", font_size=20) # 可以添加一些触摸按钮,实现手动控制 btn_on = gui.add_button(x=50, y=200, text="打开", onclick=lambda: execute_command("fan_on")) btn_off = gui.add_button(x=150, y=200, text="关闭", onclick=lambda: execute_command("fan_off")) def execute_command(cmd): """执行具体的命令""" global current_speed, is_fan_on if cmd == "fan_on": is_fan_on = True control_fan_speed(50) # 默认打开到50%速度 elif cmd == "fan_off": is_fan_on = False control_fan_speed(0) elif cmd == "speed_up" and is_fan_on: new_speed = min(current_speed + 25, 100) control_fan_speed(new_speed) elif cmd == "speed_down" and is_fan_on: new_speed = max(current_speed - 25, 0) control_fan_speed(new_speed) elif cmd == "shake_on": control_servo_shake(True) elif cmd == "shake_off": control_servo_shake(False) update_gui_display() def voice_listener_thread(): """语音监听线程,持续读取串口数据""" print("语音监听线程启动...") while True: if ser.in_waiting > 0: data = ser.read(ser.in_waiting) for byte in data: cmd = parse_voice_command(byte) if cmd != "unknown": print(f"收到语音指令: {cmd} (原始数据: {hex(byte)})") execute_command(cmd) time.sleep(0.1) # 短暂休眠,避免CPU占用过高 # 主程序 if __name__ == "__main__": update_gui_display() # 初始化显示 # 启动语音监听线程 listener_thread = threading.Thread(target=voice_listener_thread, daemon=True) listener_thread.start() print("智能语音风扇系统已启动,等待指令...") # 保持主线程运行(例如,等待GUI事件) gui.wait()4.3 代码关键点与调试心得
串口数据读取:
LD3320模块会持续输出数据,即使没有识别到指令也可能有杂波。因此,在voice_listener_thread函数中,我们采用循环读取并逐个字节解析的方式。parse_voice_command函数里的command_map字典,其键值(0x01,0x02等)必须与你在LD3320上位机软件中为每个词条设置的返回码完全一致。PWM控制精度:行空板的PWM分辨率可能是10位(0-1023)或更高(如12位0-4095),代码中的
duty值范围需要根据实际API调整。pinpong库的PWM.duty()方法可能接受0-1023或0-4095的值,务必查阅其文档。舵机的控制信号是标准的50Hz PWM波,脉宽在0.5ms到2.5ms之间对应0-180度。angle_to_duty函数中的min_duty和max_duty需要根据你使用的PWM频率和分辨率进行校准。一个实用的校准方法是:先将舵机角度设为90度(中间),观察实际位置,然后微调angle_to_duty(90)返回的duty值,直到舵机准确停在中间。多线程应用:语音监听是一个“阻塞”或“持续等待”的任务,如果放在主线程,会导致GUI界面卡死。因此我们使用
threading模块将其放入一个独立的守护线程中运行。这样,语音监听和GUI界面响应就能互不干扰。状态管理:使用全局变量(如
current_speed,is_fan_on)来记录系统状态至关重要。这确保了无论是语音指令还是触摸屏按钮触发,都能基于正确的当前状态来执行动作(例如,只有在风扇开启时,“加大风量”指令才有效)。
5. 系统集成、测试与优化
5.1 组装与初步测试
将所有的电路从面包板转移到洞洞板进行焊接,或者使用定制的小型PCB,可以使系统更稳固。将行空板、LD3320模块、L9110S模块、电源接口等整合到一个合适的外壳中。风扇电机和舵机需要机械固定,确保舵机能带动风扇头平稳摆动。
上电前,务必再次检查所有电源连接(VCC和GND),防止短路。首次上电建议按模块逐个进行:
- 只给行空板上电,观察屏幕是否正常启动,程序是否运行。
- 连接LD3320模块,通过行空板上的串口调试工具(或编写简单的串口打印程序)查看是否能在说话时收到正确的返回码。
- 连接L9110S和电机,但先不接电机负载,用万用表测量输出端电压是否随PWM变化。
- 最后连接舵机,观察其是否响应角度控制信号。
5.2 常见问题与排查技巧
在实际调试中,你几乎一定会遇到下面这些问题:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 语音模块无反应,收不到数据 | 1. 电源未接通或电压不足。 2. 串口线接反(TX/RX交叉)。 3. 行空板串口引脚或端口号错误。 4. 波特率不匹配。 | 1. 用万用表测量模块VCC-GND间电压是否为5V。 2. 确认TX-RX交叉连接。 3. 核对行空板引脚定义,在代码中尝试不同的串口设备文件(如 /dev/ttyS0,/dev/ttyS1)。4. 确认LD3320的波特率(通常是9600),与代码中 serial.Serial的波特率参数一致。 |
| 能收到数据,但指令解析错误 | 1. LD3320返回码与代码中command_map字典键值不匹配。2. 串口读取到多余字节或干扰数据。 | 1. 将读取到的原始16进制数据打印出来,与上位机软件中设置的返回码对比,修正字典。 2. 在解析前增加数据校验,例如只处理特定长度的有效数据包,或增加简单的通信协议(如固定帧头帧尾)。 |
| 风扇电机不转或抖动 | 1. 电机驱动模块供电不足。 2. PWM频率不适合直流电机。 3. 电机驱动模块输入逻辑错误。 | 1. 单独为L9110S模块提供更强的5V电源(需与行空板共地)。 2. 尝试调整PWM频率。对于直流电机调速,通常几百Hz到几KHz都可以,用 PWM.freq()设置,默认可能就行。3. 确认L9110S的输入逻辑:对于单向调速,A-1A接PWM,A-1B接GND;如需正反转,则需两个GPIO口按特定逻辑控制。 |
| 舵机乱转或不听使唤 | 1. 供电电流不足。 2. PWM频率不是50Hz。 3. 占空比计算错误。 | 1. 舵机工作瞬间电流较大,确保5V电源能提供足够电流(单个9g舵机约需0.5A),必要时单独供电。 2. 设置舵机PWM频率为50Hz: servo_pwm.freq(50)。3. 使用 angle_to_duty函数仔细校准0度和180度对应的duty值。 |
| 行空板在电机启动时重启 | 系统总功耗超过USB电源适配器或行空板供电能力。 | 为电机驱动模块(L9110S)和舵机使用独立的5V电源适配器供电,务必将其GND与行空板的GND连接在一起(共地),这是关键。 |
| 语音识别率低 | 1. 环境噪音大。 2. 麦克风位置不佳。 3. 指令词设计不合理。 | 1. 尽量在相对安静环境下使用,或为LD3320模块加装简单的麦克风放大和滤波电路(参考其手册)。 2. 将麦克风外引,放置在离使用者较近、避开风扇自身噪音的位置。 3. 优化指令词,选择发音差异大的词,避免过长或过于相似的短语。 |
5.3 功能优化与扩展思路
基础功能实现后,你可以考虑以下优化,让项目更“智能”:
- 加入环境温湿度传感器:添加一个DHT11或DHT22温湿度传感器,通过I2C或单总线与行空板连接。你可以编程实现“环境温度高于28度时自动打开风扇”,或者根据湿度调节风量。
- 实现自然语言交互:目前的指令是固定的关键词。你可以尝试集成一个更强大的离线语音识别与合成模块(如SYN7318),或者利用行空板的算力运行轻量级AI模型(如通过
SnowBoy唤醒词检测+PocketSphinx语音识别),实现“我有点热” -> 自动提高档位这样的简单语义理解。 - 增加网络功能与远程控制:利用行空板的Wi-Fi,接入家庭局域网。你可以编写一个简单的HTTP服务器,在行空板上运行。这样,在手机浏览器输入行空板的IP地址,就能看到一个控制页面,实现远程开关和调速。更进一步,可以连接至物联网平台(但需注意相关合规要求)。
- 改进用户界面:利用
unihiker库制作更炫酷的UI。例如,用滑块控件调节风速,用动画显示风扇转动,记录并显示运行时长和耗电估算(需电流传感器)等。 - 增加物理安全保护:在风扇罩内加入红外或超声波测距传感器,当检测到物体过于靠近扇叶时,自动停止电机,防止意外。
这个项目从构思到实现,最深的体会是:硬件项目的成功,一半在于清晰的逻辑设计,另一半在于耐心细致的调试。特别是电源和信号干扰问题,常常是导致系统不稳定的元凶。每次遇到问题,按照“电源 -> 信号 -> 代码”的顺序进行排查,总能找到突破口。当你对着自己组装的这个“傻乎乎”但又能听懂话的风扇说出指令,并看到它应声而动时,那种亲手创造智能的成就感,是单纯购买成品无法比拟的。