注:项目使用Rust语言开发,开发工具为 VSCode 的插件 “Raspberry Pi Pico”
项目介绍
本项目旨在开发一款基于树莓派 RP2350B 核心板的“体感鼠标与演示控制器”。本项目的定位为一款嵌入式开发示教器,而非市售消费级电子产品。其核心目的在于通过实际案例展示现代微控制器在传感器数据采集、硬件通信接口(I2C、PIO、ADC 等)、以及高速 USB 复合设备开发中的综合应用能力,为系统学习和验证嵌入式 Rust 与异步编程架构(如 Embassy 框架)提供一个直观的实践平台。
在功能表现上,该示教器巧妙融合了重力体感传感器与多按键输入,实现了以下交互功能:
- 体感鼠标: 利用内置三轴加速度传感器(KXTJ3-1057)捕捉用户的倾侧手势,并经过低通滤波与映射算法,实时转化为高精度的 USB HID 鼠标光标位移。
- 演示控制器: 针对 PPT 或网页演示等常见应用场景,实现了左右键点击功能,并通过独立的物理按键一键触发诸如“翻页操作”以及“一键屏幕回中”的快捷热键。
- 状态与交互反馈: 基于 WS2812B RGB 灯珠和 74HC595 驱动的数码管模块,分别实现了四档光标移动灵敏度的直观显示反馈,以及 USB 运行连接状态与闲置低功耗模式(5秒挂起)的可视化。
综合而言,本项目不仅是一个集趣味性与实用功能于一体的多模态输入设备,更是一个囊括了异步非阻塞外设控制、状态机管理、实时算法处理和 USB 协议栈等多个嵌入式技术难点的经典示例项目。
使用到的硬件介绍
本项目在硬件层面以树莓派新一代 RP2350B 生态微控制器为核心,结合了多种常见的模拟与数字外设,作为示教用途的硬件平台:
- 主控核心板(基于 RP2350B):
本项目的大脑。其搭载了全新的 RP2350B 芯片。该芯片拥有更高的时钟频率与多核架构支持,同时延续了树莓派系列特有的可编程 I/O(PIO)能力。其强大的多接口引脚配置和原生全速 USB 控制器,使其能够极度胜任实现高速轮询率的低延迟 USB 复合 HID 设备。 - 三轴加速度计(KXTJ3-1057):
使用 I2C 接口与主控通信的姿态传感器模块。主要负责实时采集设备的重力加速度或运动加速度。本项目通过不断读取其 X 和 Y 轴的倾角数据并结合滤波算法,作为“体感鼠标”在屏幕空间二维相对位移的数据来源。 - 串行转并行驱动数码管模块(基于 74HC595):
一种极具代表性的示教教学模块。通过 74HC595 移位寄存器芯片仅需 3 根 GPIO 引脚(时钟、锁存、数据)即可控制需要消耗大量引脚的八段数码管。在该项目中,数码管主要用于直观指示当前的鼠标控制灵敏度的数字档位(档位 "1" 至 "4")。 - WS2812B 级联 RGB 智能 LED:
采用单根数据线驱动、支持级联且内部集成 PWM 控制芯片的全彩 LED 模块。通过 RP2350B 的 PIO 硬件生成精确的纳秒级时序波形,在完全不占用 CPU 的情况下点亮两颗 LED 灯,一颗用于特定颜色的档位指示,另一颗用于利用彩虹和闪烁特效指示 USB 连接状态和工作模式。 - 复合按键输入与 ADC 测量架构:
利用单路的模数转换器 (ADC) 引脚和分压电阻网络阵列,实现类似按键矩阵的输入设计。以此读取多个微动开关/按钮的状态,映射各类逻辑动作如:鼠标左键、右键、键盘 PgDn 翻页以及回中键等,并结合软件消除抖动干扰。
方案框图与项目设计思路介绍
本项目的设计哲学遵循“高内聚低耦合”的模块化开发原则。为了确保各种外部传感器、LED 动画和 USB 通信能够高度实时、无阻塞地协同运行,本示教器在方案上采用了基于 Embassy 框架的异步无操作系统(RTOS-less Async)方案。
方案框图
项目的总体架构大体分为三层:输入侧、输出侧、核心逻辑与协议。下面是本项目的总设计架构图:

项目设计思路
本项目的核心设计思路基于**“异步事件驱动”和“多模态输入/输出融合”**的理念,力求在不引入复杂实时操作系统(RTOS)的前提下,实现极高的数据并发吞吐效率:
- 采用 Embassy 异步架构彻底解耦任务:
有别于传统单片机在while(1)中使用阻塞延时(如等待 I2C 响应或串行位移)的弊端,项目设计全面采用了 Embassy 异步并发框架。输入端(IMU与ADC轮询)、处理端(核心状态机)与输出端(WS2812渲染、UI显示、USB上报)均被封装为独立的协程 Task。各任务之间通过线程安全的异步通道(Channel/Mutex)解耦式交换数据。 - 巧妙规避 GPIO 资源枯竭( ADC 分压复用技术):
将所有物理微动按键串联在同一组电阻分压网络中,仅占用 RP2350B 的唯一外部 ADC 通道,通过识别不同的电压阈值并结合20ms 滑动窗口软件消抖计算,复原并分离出各个独立按键。这一设计极大地节省了宝贵的 I/O 系统引脚资源,体现了嵌入式系统的紧凑化设计思想。 - 基于 PIO 的时序解放(驱动 WS2812):
WS2812 是一款对时序要求极高(纳秒级)的智能灯珠。如果用纯 CPU “翻转引脚”的方式进行控制极易受外部中断影响。本项目充分利用树莓派 RP2350B 的可编程I/O(PIO)状态机,由硬件 PIO 独立维持方波序列输出,CPU 只需在状态变更时下发 RGB 色彩值即可。这使得灯珠彩虹渐变这样的吃性能动作能够在极低 CPU 负载下运行。 - 低通滤波与映射算法结合(体感转换):
对于加速度输入,采集到的重力倾角数据本身包含人体生理上的高频微小手抖和漂移噪点。项目中引入了低通滤波系数算法(Alpha 平滑因子),并在内部设定了角度阈值的“死区”。使得玩家手腕轻微倾斜时防抖静止,超过死区且平滑后的分量才转化为平滑的鼠标 (dx, dy) 向量输出;并支持用组合按键实时调节4挡位移比例乘数(即调节鼠标灵敏度)。 - 智能功耗分级与状态感知:
结合外设的状态响应(捕捉 USB Host 连接事件)以及“无源定时器”,在主控中设计了一套简单的休眠状态机机制:当接上电脑但 5 秒无任何按键及位移数据产生时,主控判定进入低功耗状态并下发指令,控制所有数码管全清灭并休眠 LED,唤醒后通过平滑动画即刻恢复工作状态。
调试软件及使用的编程语言说明、软件流程图及关键代码介绍
调试软件与编程语言说明
- 编程语言: 本项目采用 Rust 语言进行开发。由于 C/C++ 等传统语言在大型嵌入式工程中极易引发内存越界和悬空指针等问题,本项目选用了 Rust。它能够利用编译器借停检查器(Borrow Checker)在编译阶段杜绝数据竞争,并利用无开销抽象保证了在 MCU 上拥有与 C 语言一致的运行效率。
- 开发与编译环境: 使用
VS Code作为主要代码编辑环境,结合cargo进行包管理与执行编译任务。 - 异步框架库: 核心选用了基于 Rust 的
Embassy(面向嵌入式系统的异步执行器),这是本项目实现无阻塞外设驱动与多任务调度的基石。 - 烧录与调试工具: 利用树莓派官方提供的
picotool配合elf目标文件直接实现快速挂载烧录;通过 USB CDC ACM(在电脑设备管理器中显现为“虚拟串口” / 比如 COM3)实现了无需额外 TTL 模块直接接收微控制器发出来的 ADC 原生电压值和按键位掩码,为闭环调试提供了极大极大的便利。
软件流程图


关键代码介绍
滑动窗口软件消抖(集成于按键处理逻辑中)
传统的物理按键在闭合瞬间会有多次物理弹跳(Noise),这里利用主循环 500 微秒的周期,借助时钟记录比对实现非阻塞消抖。
// Software debouncing: 如果连续读到的新电平和上次记录的不一样,则复位计时器。
if raw_current_bits != last_raw_bits {
last_debounce_time = embassy_time::Instant::now();
last_raw_bits = raw_current_bits;
}
// 仅当该状态在时间轴上持续稳定超过 20ms 没有任何抖动波动时,才认可它是最终状态
if last_debounce_time.elapsed().as_millis() >= 20 {
current_bits = raw_current_bits;
}
// 通过位运算 XOR 和 AND,提取出“刚按下瞬间(下降沿)”的事件
let falling_edges = last_bits & (!current_bits);
let changed_bits = last_bits ^ current_bits;
last_bits = current_bits;
一键光标回中策略
因为传统鼠标接口无法直接控制屏幕绝对坐标,这里巧妙利用高频次发送满量程 (-127, -127) 冲撞屏幕左上角归零,随后再根据主流屏幕的物理分辨率通过小步伐多次累加的方式移动回正中央。
// Recenter 触发自按键位 5
if (falling_edges & 0x20) != 0 {
recenter_step = 80;
}
if recenter_step > 0 {
// 维持 10ms (100Hz) 的 USB 发送频率
if last_imu_send_time.elapsed().as_millis() >= 10 {
last_imu_send_time = embassy_time::Instant::now();
// 前 40 步:向左上角发力 (撞到 (0,0) 位置零)
// 后 40 步:用 (32, 18) 抵消 OS 的鼠标加速度机制缓步横向和纵向走位
let (dx, dy) = if recenter_step > 18 {
(-127i8, -127i8)
} else {
(32i8, 18i8) // 32 * 40步 = +1280 像素 (适配 2560x1440 屏幕的正中)
};
let report = [ m_buttons, dx as u8, dy as u8, 0, 0 ];
let _ = mouse_hid.write(&report).await;
recenter_step -= 1;
}
}
基于异步架构下的 WS2812 状态渲染任务
这是典型的协程任务,通过非阻塞读取 state_channel 根据不同枚举状态(Disconnected, Active, Sleep)用不同算法渲染彩色。
#[embassy_executor::task]
pub async fn ws2812_task(
mut ws: PioWs2812<'static, PIO0, 0, 2, Grb>,
state_channel: &'static Channel<ThreadModeRawMutex, DeviceState, 1>,
) {
let mut current_state = DeviceState::Disconnected(2);
let mut tick: u8 = 0;
loop {
// 非阻塞探查是否有状态变更
if let Ok(new_state) = state_channel.try_receive() {
current_state = new_state;
}
match current_state {
DeviceState::Sleep => {
ws.write(&[RGB8::new(0, 0, 0), RGB8::new(0, 0, 0)]).await;
}
DeviceState::Active(level) => {
// 灯 1 显示指定档位死区色,灯 2 显示彩虹流水呼吸灯 (利用 tick 以及 hsv 算法转变)
let color_sens = match level { ... };
let color_status = hsv2rgb(Hsv { hue: tick.wrapping_mul(2), sat: 255, val: 10 });
ws.write(&[color_sens, color_status]).await;
}
// ... 针对 Disconnected 等状态的闪动处理忽略 ...
}
tick = tick.wrapping_add(1);
Timer::after_millis(50).await; // 挂起,出让 CPU 资源
}
}
功能展示图和说明
因控制功能不便使用静态图片展示,见视频。
当未连接电脑时,左侧ws2812红灯闪烁:
未操作5秒后,进入低功耗模式:
不同的灵敏度下,右侧ws2812颜色不同,数码管显示对应挡位:



项目中遇到的难题及解决方法
在基于树莓派 RP2350B 和 Embassy 框架进行本项目开发的过程中,遇到了一些涉及底层硬件时序、协议兼容以及人机交互逻辑体验的典型难题,主要包括以下几点:
难题一:硬件串行移位寄存器(74HC595)高频刷新导致段码错乱(吞信号)
问题描述:
在最初配置数码管驱动时,发现在刷新特定数字(如2、4、7、9等)时,某些段位的灯管会出现明显的缺划或乱码现象(原本只要某个数字中有一个位为 0,这一个位就不亮)。特别是系统以 200Hz+ 的高速频率刷新显示任务时问题尤为严重。
解决方法:
通过查阅 74HC595 数据手册并结合逻辑分析,排除了纯代码位运算的逻辑谬误后,定位到由于 RP2350 的处理主频极快,在模拟 CLK(时钟引脚)与 RCK(锁存引脚)的上、下升沿电平翻转时,维持的高电平/低电平时间(脉宽)不足,即电平突变超越了外部 74HC595 的固有阻容寄生网络响应电容(RC常量),导致芯片漏过或误读了上升沿。
在 segments.rs 驱动核心在相应的信号 set_high() 后立刻强制执行基于 embassy_time::block_for(Duration::from_micros(1)) 插入了 1 微秒 的严格时序硬等待(Setup/Hold Delay),从而使其与外部物理电路的响应速度完全匹配。修复后数码管显色清晰完整。
难题二:光标 "一键回中" 时操作系统级别带来的加速度漂移(跑飞)
问题描述:
标准 USB 协议采用的是相对坐标系计算位移(dx, dy)。为了实现“按键后光标回到屏幕中心”,原定计划是以最大的相对位移 -127 迅速向左上角发弹定位原点 (0,0) 后,直接发送目标中心大坐标数值如 +1280 的位移。但由于一次位移不能超过 8-bit 有符号整数范围(127),需要分步进行。发现在 Windows 中启用“提高指针精准度”(鼠标平滑加速度)机制后,大步长的相对输入(例如 dx=96)会被操作系统直接当做“极速一甩”,光标常常飞到屏幕右下外侧。
解决方法:
不再使用少数的大幅度位移。改为在进入 recenter 宏状态时,采用“高频次化整为零微步推进”的方法:即在重置到左上角后,在接下来持续释放 40 个极小的数据包 (32, 18)。由于单次步长足够小,操作系统的底层加速算法认定此时用户处于“精确走位慢速滑动”区,没有介入放大倍数,最终可靠准确地完成了在 2560x1440 等高分辨率屏幕上的光标物理回归。
难题三:同时挂载多个设备类(复合设备)与端点资源的争用
问题描述:
本项目不仅需要实现体感鼠标(Mouse),还需要支持模拟按键映射(如翻页 PgDn 和 PgUp 发送的 Keyboard 设备),同时为了不损失开发调试的友好性,还要同时映射出一个虚拟串口(CDC ACM)。初期强行通过创建多端点进行混用,经常遇到 USB 主机端识别设备异常,描述符分配失效或死锁问题。
解决方法:
在使用 embassy-usb 定义通信框架时,系统级剥离与解耦。首先在固件入口精确定义好包含了由 MouseReport、KeyboardReport 以及独立的数据类 CdcAcmClass 共属的完整的 Composite 综合描述块资源分配;其次,建立专用的基于 embassy_sync::channel::Channel 的互斥信息通道,实现“主逻辑任务只做收集、运算,最后统一根据动作投喂给这三个互不依赖的独立发射模块接口”(即主循环只关心把字串丢给 log_channel、把坐标丢给 mouse_hid 或者按键扔给 kbd_hid)。这一设计有效避开了时序上与终端争用堵塞的风险,成功打通三合一通道。
难题四:机械触动按键存在密集多重触发反馈(按键抖动)
问题描述:
使用的物理机械开关在压下或抬起的瞬间,受微动弹簧片变形接触不良的影响,会在极短几毫秒内产生数百次的虚假开关拉扯现象(如一次点按触发多次左键连击或档位跳变)。特别因为按键采取的是经过 ADC 模拟分压推导而来,其电压在跳动期受电容影响极其不稳定。
解决方法:
在主循环数据收取侧摒弃了单纯的 delay() 傻缓。因为项目是多任务并发结构,强行死等延时会拉跨 IMU 与串口的发包实时性。取而代之我们编写了一套软件消抖滑动窗口状态机(20ms Debouncing State Window),依靠系统时间戳 Instant 。仅在两次采集之间超过了 20ms 没有出现电压突变(即电平绝对安稳静止)才承认当前的按键状态,并基于前后状态的位与异或判定(XOR 和 AND 逻辑)只在“刚下降沿(初次按下)”的那一刻提交动作,从而实现极干脆零误触的极致体验。
心得体会
完成“基于树莓派RP2350B核心板制作体感鼠标与演示控制器”这一示教器项目,我对嵌入式系统架构、硬件信号交互以及 Rust 语言在硬件控制领域的应用有了更为深刻的理解与体会,总结如下:
1. "异步不等于不用管时间,时序永远是硬件的底线"
在利用高级框架(如 Embassy)编写协程任务时,因为代码结构极为清爽优雅,很容易让人产生“代码能够无缝操控硬件”的错觉。但在调试 74HC595 数码管缺段字乱码的惨痛教训中重重地给我上了一课。即便 MCU 和程序的算力再快,信号在走线上蔓延、在容性负载中攀升的纳秒级微观常数是不会因为软件优雅而消失的。懂得在高速逻辑中适时、精确地“踩下一脚刹车(1us block delay)”等待外部老旧芯片反应,是硬件开发有别于纯软件开发的独特浪漫。
2. Rust 语言为底层开发重塑了“安全感”
过去使用 C 语言处理复杂的共享状态(例如主循环向 USB 发送任务分发数据、多个传感器中断回调写入同一个缓冲结构)时,指针满天飞和锁处理不当导致的 “Hard Fault” 非常折磨人。而引入 Rust 后,利用它的所有权模型加静态借用检查,辅以 Embassy 的 Channel 和 Mutex 作为跨任务通信唯一的桥梁。尽管前期在满足编译器各种严格校验方面付出了额外的学习成本,但换来的是**“只要能编译成功,跑起来就绝不会跑飞崩溃”**的底气。这种安全感让项目架构的后续扩展(如临时挂入一根虚拟串口链路)变得毫无后顾之忧。
3. 用户体验的构建是一场与操作系统底层交互的博弈
原本认为把传感器测出的加速度数据一乘比例就算做出了体感鼠标,按下一个键发出位移就算做出了“屏幕回中”,但在实操接入 Windows 系统时才发现事实远非如此简单。人类手部的原生理微颤如果不经过底层“死区与低通滤波(Alpha-filter)”会变成屏幕上的持续抖动;大跨度的坐标强切如果不作平滑切割会引发 OS 系统层级“鼠标曲线加速机制”的介入而导致光标脱靶。这让我明白,一个好的外设开发不仅是对自身内部寄存器的掌控,由于我们是通过 HID 协议沟通,它更是对目标上位机(Host OS)逻辑盲区的一场行为适配艺术。
4. 硬件复用与整体设计美学
在单片机的引脚资源规划上,最初本以为按键多必须占用大量 GPIO,但通过将 ADC 通道与电阻网络结合,成功把多开关转译成具有阈值宽度的电压段位。同时外加严谨的 20ms 时间窗口非阻塞消抖算法配合。这样的处理不再靠单纯罗列元件,而是用软件算力弥补并优化硬件电路的瓶颈,这让我领略了化繁为简的系统设计之美。
总而言之,该示教项目的完整穿跑,打破了我对单片机开发停留在“跑流水灯即可”的初级阶段认知。它切实证明了,在这个“万物皆智能的时代”,一套稳健的异步逻辑底座搭配严谨的人机交互调优,哪怕是仅挂几块再寻常不过的模块,也能缔造出极具高级灵动体验的交互型且流畅实用的智能人机亲和交互的硬件系统。