简介:面向嵌入式与机电一体化方向本科毕业设计的手指运动识别手套完整工程包,基于STM32F103C8T6实现手指弯曲与伸直动作的实时检测、数据处理及蓝牙或Wi-Fi无线传输。内容按需求分析、技术选型、系统设计分模块展开,涵盖电容/光电传感器接入、ucos操作系统移植、无线通信与接收端反馈等关键环节,适合需要搭建同类智能手套项目的学生对照学习。压缩包共116个文件,大小15.11MB,以55个.c源文件和51个.h头文件为主体,包含STM32标准外设库、ucos内核源码、1个Keil工程文件(.uvproj),另附Python辅助脚本、汇编启动文件、Markdown说明文档以及jpg/gif演示素材,目录结构清晰,便于按模块检索。目前已有167人学习浏览,资源具备较高参考价值。整包提供了从工程框架到底层驱动的完整示例,可借鉴传感器数据采集、任务调度、无线透传等实际写法;配合说明文档和演示图片,能直观了解手指动作识别流程,有效缩短毕业设计的前期调研与开发周期。
1. 手指运动识别手套,把 STM32 从课堂搬到手腕上
本科毕业设计选这个题目,最大的收益不是“做出一个手套”,而是把嵌入式开发里最常用的几块能力一次串完:传感器模拟量采集、多通道 ADC 与 DMA、数据滤波、阈值分类、串口与上位机交互。一个手指运动识别手套看起来只是把弯曲传感器贴在手套上,再用 STM32 读电压、判姿势,但真正动手后你会发现,难点全在细节——传感器的一致性、佩戴位置的偏差、基线漂移、阈值怎么定、手势之间互相干扰怎么处理。这篇文章不写“项目介绍”,直接给出从器件选型到代码落地的完整方案,你照着搭就能跑通第一版。适合正在做本科毕设的嵌入式方向学生,也适合想拿一个具体项目练手 STM32 的开发者。
2. 从传感器到 GPIO:看懂手指弯曲信号是怎么变成数字的
2.1 弯曲传感器是核心,但先要分清楚阻抗型和电压型
手指运动识别手套的传感部分通常使用柔性弯曲传感器(Flex Sensor)。常见做法是选阻值型弯曲传感器,它本质上是一个可变电阻:平直状态时阻值在 10kΩ 到 25kΩ 之间(具体看型号),弯曲角度越大,阻值越大。这种传感器的典型线性度不算好,误差在 ±5% 到 ±10% 之间,但对手势识别这种粒度的应用来说足够。你需要避开的坑是:不同批次、甚至同一批次不同位置的传感器,平直状态阻值差异可能很大,所以后面第三章的标定环节不能省。
也有人会考虑用 IMU(六轴/九轴传感器)来做手指姿态识别。IMU 方案的优点是精度高、能识别手指在三维空间的角度,但问题也明显:你需要给每个手指节都配一个 IMU,成本高,I2C 总线上挂多个从设备还要处理地址冲突。对于本科毕设,弯曲传感器 + STM32 是性价比最高、工作量最可控的组合——你只需要一个 ADC 通道接一个传感器,五根手指就是五个 ADC 通道。
2.2 硬件连接方式:分压电路才能让 STM32 读到有效电压
STM32 的 ADC 只能读电压,不能直接读电阻。所以弯曲传感器要先构成一个分压电路。典型接法如下:
3.3V —— 弯曲传感器 —— A(ADC引脚) —— 固定电阻 —— GNDA 点的电压计算公式是:
V_adc = 3.3V × R_fixed / (R_flex + R_fixed)这里R_flex是弯曲传感器的当前阻值,R_fixed是你选用的固定电阻。R_fixed的取值非常关键,它决定了 ADC 读数的动态范围。很多人直接把R_fixed设为 10kΩ,但这样在传感器平直时(R_flex≈10kΩ)读到的电压是 1.65V,弯曲到最大时(R_flex≈30kΩ)电压变成 0.825V——满量程 3.3V 只用了四分之一,ADC 分辨率浪费严重。
我一般会这样算:先查传感器手册拿到平直和最大弯曲的阻值,然后让这两个状态对应的电压差尽量大。如果平直 10kΩ、最大弯曲 40kΩ,R_fixed用 15kΩ 时,电压从 1.98V 变化到 0.9V,差了 1.08V,比 10kΩ 方案的 0.825V 好得多。这样后续判手势时,每个手指的电压区间拉得更开,阈值才好设。
注意:弯曲传感器是两端的,没有正负极之分。但焊接时焊点处很容易折断,建议用热缩管包住焊点再固定在手套上。
2.3 STM32 的选型:F103 就够用,别为性能溢付
手指运动识别手套对主控的要求并不高:五个 ADC 通道、一个串口、若干 GPIO,跑在 72MHz 的 STM32F103C8T6 完全够用。这块芯片的 ADC 是 12 位分辨率,满量程 4095,按 3.3V 参考电压算,理论分辨率约 0.8mV。你要是想省事,可以直接用 STM32F103C8T6 最小系统板,成本低,资料多,Keil 环境下调试也顺手。
但有一个细节要注意:STM32F103 的 ADC 输入阻抗有限制,官方建议外部信号源阻抗不要超过 4kΩ 左右(具体数值在参考手册里有说明)。弯曲传感器阻值在 10kΩ 到 40kΩ 之间变化,如果你的分压网络等效阻抗太高,ADC 采样时内部采样电容来不及充满,读数会偏低。解决办法是开 ADC 的采样时间到最大档(239.5 个周期),或者干脆在 ADC 引脚和分压点之间加一个电压跟随器。做毕设时先把采样时间拉到最大,能解决大部分读数不稳的问题。
2.4 器件清单与成本估算,照着买就行
| 器件 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|
| 主控 | STM32F103C8T6 最小系统板 | 1 | 带 USB 转串口更省事 |
| 弯曲传感器 | Flex Sensor 2.2 英寸或 4.5 英寸 | 5 | 尺寸决定弯曲灵敏度 |
| 固定电阻 | 15kΩ,1% 精度 | 5 | 精度影响一致性 |
| 手套 | 普通棉质或弹性手套 | 1 | 不要太厚 |
| 杜邦线/软导线 | 30AWG 硅胶线 | 若干 | 柔韧性好 |
| 面包板/洞洞板 | 任意 | 1 | 原型阶段用面包板 |
| 3.3V 稳压 | AMS1117-3.3(如果板卡不带) | 1 | 注意压降和纹波 |
3. 用 STM32 的 ADC 多通道采集,把五根手指的数据拉齐
3.1 ADC 多通道的两种扫描方式,别被“多通道”三个字带偏
STM32F103 的 ADC 支持最多 18 个通道(在 F103 上实际可用的外部通道是 16 个),但 ADC 本身只有一个转换器,所谓“多通道”是靠扫描 + 连续转换完成的。这里有两种常见的配置方式:
- 非扫描模式 + 手动切换通道:每次转换前改 ADC 的采样通道寄存器,转换完再切换。代码简单,但在五通道场景下多了一堆寄存器操作,转换效率低。
- 扫描模式 + DMA:配置 ADC 扫描一组通道,每次转换完成后自动切换到下一个通道,全部通道转换完会产生一次 DMA 请求,把数据搬进内存数组。这是推荐做法,CPU 完全不参与数据搬运。
我的建议是直接用扫描模式 + DMA。代码量多几行,但稳定性强很多,而且 DMA 把数据送到内存后,主循环只需要直接读数组,不用关心 ADC 什么时候转换完。
3.2 STM32CubeMX 配置要点,参数这样设
如果你用 STM32CubeMX 生成工程,需要注意以下几个配置项:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| ADC Mode | Independent | 单 ADC 即可 |
| Scan Conversion Mode | Enabled | 多通道必须开 |
| Continuous Conversion | Enabled | 连续转换,DMA 自动搬数 |
| DMA Continuous Requests | Enabled | 保持 DMA 一直搬 |
| Number Of Conversion | 5 | 五根手指 |
| Conversion Trigger | Software | 用 HAL_ADC_Start_DMA 触发 |
| ADC Conversion Sample Time | 239.5 Cycles | 最大采样时间,扛高阻抗 |
五个通道的 Rank 顺序要和你的硬件接线对应。比如你把手套的食指、中指、无名指、大拇指、小拇指分别接在 PA0 到 PA4,就在 CubeMX 里把 Conversion 通道依次配成 ADC_CHANNEL_0 到 ADC_CHANNEL_4。
提示:ADC 引脚不要和串口引脚冲突。F103 最小系统板上 PA9/PA10 通常是串口 1,PA0-PA4 没有冲突,可以放心用。
3.3 ADC 采样的完整代码:DMA 搬运 + 软件数字滤波
这是核心代码,直接在 main.c 里补齐:
/* 定义 ADC 缓冲区,五通道,存多组数据做滑动滤波 */ #define ADC_CHANNEL_NUM 5 #define ADC_FILTER_DEPTH 8 uint16_t adc_raw[ADC_CHANNEL_NUM] = {0}; uint16_t adc_filter_buf[ADC_CHANNEL_NUM][ADC_FILTER_DEPTH] = {0}; uint8_t adc_filter_index = 0; uint16_t adc_filtered[ADC_CHANNEL_NUM] = {0}; /* 定时器中断里调用,或直接放主循环,每 10ms 读一次 */ void ADC_Update(void) { uint8_t ch = 0; uint8_t i = 0; uint32_t sum = 0; /* 启动一次 DMA 搬运,五通道数据会自动填充 adc_raw */ HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_raw, ADC_CHANNEL_NUM); /* 等 DMA 搬运完成 */ while (HAL_ADC_GetState(&hadc1) == HAL_ADC_STATE_BUSY_INTERNAL); /* 写入滑动滤波缓冲区 */ for (ch = 0; ch < ADC_CHANNEL_NUM; ch++) { adc_filter_buf[ch][adc_filter_index] = adc_raw[ch]; } adc_filter_index++; if (adc_filter_index >= ADC_FILTER_DEPTH) { adc_filter_index = 0; } /* 计算滑动平均值,作为滤波后的结果 */ for (ch = 0; ch < ADC_CHANNEL_NUM; ch++) { sum = 0; for (i = 0; i < ADC_FILTER_DEPTH; i++) { sum += adc_filter_buf[ch][i]; } adc_filtered[ch] = (uint16_t)(sum / ADC_FILTER_DEPTH); } }这段代码的逻辑是:每个更新周期触发一次 DMA 搬运,五通道数据进入adc_raw,然后存入一个环形缓冲区做滑动滤波,最后输出adc_filtered供上层手势判定使用。滑动滤波的作用是消除手指轻微抖动和传感器噪声带来的毛刺,深度 8 大约对应 80ms 的延迟,对手势识别来说完全可接受。
需要注意两个参数:
ADC_FILTER_DEPTH越大越平滑,但延迟越高。做静态手势识别可以取 16,做动态手势(比如快速握拳张开)建议取 4 到 8,不然会明显感觉“跟不上手”。HAL_ADC_Start_DMA不能连续调用,要等上一次转换完成。上面代码里加了一个状态检测while (HAL_ADC_GetState(...) == BUSY_INTERNAL),但更好的做法是只在主循环里跑一次启动,DMA 连续模式会一直更新数据,不需要重复触发。
3.4 把 ADC 原始值映射成手指弯曲角度
我们真正关心的是“手指弯曲了多少度”,而 ADC 值只是电压的数字化表示。惯用做法是采集两个基准状态:手指完全伸直时的 ADC 值作为最小值flat_value,手指最大弯曲时的 ADC 值作为最大值bend_value,然后再做线性映射。
/* 标定结构体,每个手指存一组基准值 */ typedef struct { uint16_t flat_value; /* 平直时 ADC 值 */ uint16_t bend_value; /* 最大弯曲时 ADC 值 */ } FingerCalib_t; FingerCalib_t finger_calib[ADC_CHANNEL_NUM]; uint8_t angle_min = 0; uint8_t angle_max = 100; /* 把 ADC 值映射到 0-100 的弯曲度 */ uint8_t ADC_To_Angle(uint8_t ch, uint16_t adc_val) { uint16_t flat = finger_calib[ch].flat_value; uint16_t bend = finger_calib[ch].bend_value; int32_t angle = 0; if (bend == flat) return 0; /* 注意分压电路里 ADC 值随弯曲增大而减小,方向要判断对 */ if (flat > bend) { angle = (int32_t)(flat - adc_val) * (angle_max - angle_min) / (flat - bend) + angle_min; } else { angle = (int32_t)(adc_val - flat) * (angle_max - angle_min) / (bend - flat) + angle_min; } if (angle < 0) angle = 0; if (angle > 100) angle = 100; return (uint8_t)angle; }这里的angle表示手指弯曲程度,0 代表完全伸直,100 代表完全弯曲。为什么用 0-100 而不是 0-90 度?因为弯曲传感器的输出和真实角度并非严格线性,用 0-100 的“弯曲度”概念来处理,实现的是线性近似,在后续阈值分类时更加直观。
4. 手势识别算法:从阈值判定到多手指联动判断
4.1 多级阈值分类:单手指状态先定下来
采集到每根手指的弯曲度之后,第一步是把单根手指的状态归为“伸直”“半曲”“全曲”三个离散状态。这里不能只设一个阈值,常见的做法是用两个阈值配合迟滞区间,避免手指在临界位置时状态来回跳变。
typedef enum { FINGER_STRAIGHT = 0, /* 伸直 */ FINGER_HALF, /* 半曲 */ FINGER_FULL /* 全曲 */ } FingerState_t; #define THRESHOLD_HALF_UP 45 /* 进入半曲的阈值 */ #define THRESHOLD_HALF_DOWN 30 /* 退回伸直的阈值 */ #define THRESHOLD_FULL_UP 75 /* 进入全曲的阈值 */ #define THRESHOLD_FULL_DOWN 60 /* 退回半曲的阈值 */ FingerState_t finger_state[ADC_CHANNEL_NUM]; void Finger_UpdateState(uint8_t ch, uint8_t angle) { switch (finger_state[ch]) { case FINGER_STRAIGHT: if (angle > THRESHOLD_HALF_UP) finger_state[ch] = FINGER_HALF; break; case FINGER_HALF: if (angle > THRESHOLD_FULL_UP) finger_state[ch] = FINGER_FULL; else if (angle < THRESHOLD_HALF_DOWN) finger_state[ch] = FINGER_STRAIGHT; break; case FINGER_FULL: if (angle < THRESHOLD_FULL_DOWN) finger_state[ch] = FINGER_HALF; break; } }迟滞区间的核心思想是:进入某个状态和离开某个状态使用不同的阈值。以半曲为例,弯曲到 45 才进入半曲,但伸直要降到 30 才退出半曲。这中间 15 个单位的区间就是迟滞带,能有效抑制临界点附近的抖动。如果你发现手势识别时状态跳变频繁,优先调大迟滞区间,而不是去调滤波参数。
4.2 结合五个手指的状态来识别手势
有了单指状态后,手势判定就可以写成一张状态组合表。例如常见的几个手势:
typedef enum { GESTURE_NONE = 0, GESTURE_FIST, /* 握拳 */ GESTURE_OPEN_PALM, /* 张手 */ GESTURE_POINT, /* 食指指向前方 */ GESTURE_OK, /* 拇指食指比 OK */ GESTURE_SCISSORS /* 剪刀手 */ } GestureId_t; #define THUMB 0 #define INDEX 1 #define MIDDLE 2 #define RING 3 #define LITTLE 4 GestureId_t Gesture_Detect(void) { uint8_t full_count = 0; uint8_t straight_count = 0; for (uint8_t ch = 0; ch < ADC_CHANNEL_NUM; ch++) { if (finger_state[ch] == FINGER_FULL) full_count++; if (finger_state[ch] == FINGER_STRAIGHT) straight_count++; } /* 握拳:五根手指全曲 */ if (full_count == 5) return GESTURE_FIST; /* 张手:五根手指全伸直 */ if (straight_count == 5) return GESTURE_OPEN_PALM; /* 食指指:食指伸直,其余全曲 */ if (finger_state[INDEX] == FINGER_STRAIGHT && full_count == 4) return GESTURE_POINT; /* 剪刀手:食指和中指伸直,其余全曲 */ if (finger_state[INDEX] == FINGER_STRAIGHT && finger_state[MIDDLE] == FINGER_STRAIGHT && full_count == 3) return GESTURE_SCISSORS; /* OK 手势:拇指和食指指尖相对,判定略复杂,留到 4.3 讲 */ return GESTURE_NONE; }这个判定函数的逻辑是:先做整手统计,再用单指条件过滤。full_count == 4这个条件在食指伸直的情况下等价于排除了“中指单独伸直”的可能。因为四根手指都弯曲时,唯一伸直的那根是谁已经固定了,不需要再单独判断其他手指的状态。
4.3 OK 手势的特殊处理:拇指的弯曲度判断要反向
OK 手势是典型的“看起来简单、做起来麻烦”的判定。问题出在拇指的弯曲方向和传感器安装方式上:食指、中指、无名指、小拇指的弯曲方向一致,但拇指是内收弯曲,当你把传感器贴在拇指上时,它可能不是弯曲而是被拉伸。这就导致同一个FINGER_FULL状态在拇指上的物理含义和在其他手指上不同。
解决办法是单独标定拇指的映射方向。在ADC_To_Angle函数里,判断flat > bend的那个分支就是处理电压随弯曲增大而上升的情况。拇指可能表现为弯曲时 ADC 值上升而不是下降。你在标定前先用手掰一下拇指传感器确认这个方向,反过来就把flat和bend对调再初始化一次。
OK 手势的判定逻辑可以这样写:拇指为全曲或半曲状态,食指为半曲状态(指尖和拇指接触时食指不会完全弯到底),中指无名指小拇指全曲或半曲均可,但要排除握拳,因为握拳时食指是全曲状态。
/* OK 手势:拇指全曲/半曲,食指半曲(不是伸直也不是全曲) */ if ((finger_state[THUMB] == FINGER_FULL || finger_state[THUMB] == FINGER_HALF) && finger_state[INDEX] == FINGER_HALF && finger_state[MIDDLE] != FINGER_STRAIGHT && finger_state[RING] != FINGER_STRAIGHT && finger_state[LITTLE] != FINGER_STRAIGHT) { return GESTURE_OK; }这个判定里,食指必须是半曲状态。如果你测试发现 OK 手势总是被识别成握拳,大概率是食指弯曲角度过大进入了FINGER_FULL,可以适当调高THRESHOLD_FULL_UP,或者调整标定时的“最大弯曲”基准值。
4.4 识别稳定性问题:停留时长的确认机制
手指从做出一个手势到稳定停在那个姿态,中间会有过渡过程。如果不做处理,手势识别结果会在一瞬间闪过多个状态,比如从握拳到张手的过程中先变成“剪刀手”再变成“张手”。常见做法是持续确认机制:同一个手势连续出现 N 次才认为有效,通常 N 取 3 到 5 次,配合 10ms 的采样周期就是 30 到 50ms 的延迟,体感上察觉不到。
GestureId_t gesture_current = GESTURE_NONE; GestureId_t gesture_candidate = GESTURE_NONE; uint8_t gesture_vote_count = 0; #define GESTURE_VOTE_THRESHOLD 4 void Gesture_Update(void) { gesture_candidate = Gesture_Detect(); if (gesture_candidate == gesture_current) { gesture_vote_count = 0; return; } if (gesture_candidate == GESTURE_NONE) { gesture_vote_count = 0; return; } gesture_vote_count++; if (gesture_vote_count >= GESTURE_VOTE_THRESHOLD) { gesture_current = gesture_candidate; gesture_vote_count = 0; /* 此时可以向上位机发送一次新手势事件 */ USART_SendGesture(gesture_current); } }这段投票逻辑的作用是“确认手势变化再上报”。注意它把GESTURE_NONE单独处理了:如果你在做一个手势的途中手指稍微松开,导致一瞬间没有命中任何手势规则,不会重置投票计数,只有手势真正变化时才重新计票。
5. 穿戴标定与调试:数据校准、佩戴一致性跟手性排查
5.1 第一步:采集两个基准状态,做快速标定
任何新人戴上手套后,第一件事就是重新标定。因为每个人的手指长度、粗细以及手套松紧程度不同,传感器弯曲的幅度也会不同。
标定过程建议做成一个串口交互命令:通过串口发送指令,提示用户分别做“完全伸直”和“完全弯曲”两个动作,各保持 2 秒,单片机在这段时间内持续采样并取平均值。
void Calibration_Start(uint8_t ch, uint8_t mode) { uint32_t sum = 0; uint32_t sample_count = 0; /* mode: 0=标定平直值,1=标定弯曲值 */ while (sample_count < 200) { if (/* 串口收到 "C0" 或 "C1" 指令 */. ) { sum += adc_filtered[ch]; sample_count++; } HAL_Delay(10); } if (mode == 0) finger_calib[ch].flat_value = (uint16_t)(sum / sample_count); else finger_calib[ch].bend_value = (uint16_t)(sum / sample_count); }200 次采样大约耗时 2 秒,取平均值能消除手部微小抖动。
5.2 用串口波形观察 ADC 原始值,排干扰的第一手段
调试过程中最烦人的问题就是“明明手上没动,数据在跳”。排查手段是先把原始 ADC 值通过串口发到上位机看波形。很多嵌入式开发者会直接用串口助手打印数值,但数据刷新太快时根本看不清。
我常用的做法是在串口助手里开启“时间戳显示”,然后程序里以固定的 50ms 周期发送数据:
uint16_t timestamp = 0; void UART_SendTelemetry(void) { char buf[64]; timestamp++; sprintf(buf, "%d,%d,%d,%d,%d,%d\r\n", timestamp, adc_filtered[0], adc_filtered[1], adc_filtered[2], adc_filtered[3], adc_filtered[4]); HAL_UART_Transmit(&huart1, (uint8_t *)buf, strlen(buf), 100); }把数据粘贴到 Excel 或直接用串口助手的曲线绘制功能,就能看到每个通道的噪声幅度。如果噪声表现为 3-5 个 ADC 码值的快速抖动,属于正常范围,滑动滤波已经能压住。如果跳动超过 20 个码值,优先检查接线是否牢固、传感器分压点是否受到手指运动时线缆弯折的影响。如果所有通道同时跳动,要考虑电源问题,比如用 USB 供电时电流不足或者开关电源纹波过大。
5.3 从 2.2 英寸到 4.5 英寸传感器:尺寸差异对灵敏度的影响
这是实测中非常明显的一个点:2.2 英寸的弯曲传感器灵敏度更高,但有效弯曲区域短,贴在小手指上正好;4.5 英寸的传感器更适合食指和中指,因为这两个手指较长,传感器需要覆盖两个指节才能采集到完整弯曲。
如果你用 4.5 英寸传感器贴在小手指上,可能出现的情况是:小手指弯曲 90 度时传感器才弯曲 30 度,ADC 变化范围被压缩到很小。解决方案是给每个手指单独选型,或者统一用 2.2 英寸、然后佩戴时把传感器固定在指背正中央,确保最大弯折点落在传感器敏感区。
5.4 手势识别响应“慢半拍”的排查顺序
第 4 章实现了投票确认机制,如果出现响应慢,排查顺序应该是:先确认是滑动滤波延迟、投票延迟还是串口上报延迟。
滑动滤波器的时间常数可以通过ADC_FILTER_DEPTH调整,深度 8 的延迟大约 40ms。投票机制的延迟是GESTURE_VOTE_THRESHOLD × 周期,4 次投票即 40ms。两者叠加在 100ms 以内,体感上属于正常范围。如果明显觉得慢,优先把投票阈值从 4 降到 2——这个改动对稳定性的影响通常不大,但对响应速度的提升很明显。
另一个隐蔽的坑是while (HAL_ADC_GetState(&hadc1) == HAL_ADC_STATE_BUSY_INTERNAL);这个循环。DMA 搬运在连续模式下不会特别慢,但如果你的主循环里有其他耗时操作(比如HAL_UART_Transmit的阻塞发送),就会拖慢整个周期。建议改用 DMA 发送串口数据,或者把串口发送改成中断方式。
5.5 进阶方向:手势模板化与最近邻分类
阈值分类能做到 6-8 个手势的可靠识别,但如果你想识别更复杂的手势(比如数字 1 到 9、握拳并旋转手腕),阈值规则会变得非常繁琐。更工程化的方案是直接采集手势模板:让用户每个手势做 50 次,把五根手指的弯曲度均值作为模板,识别时计算当前弯曲度向量和所有模板的欧氏距离:
distance = sqrt( sum( (angle_i - template_i)^2 ) )选距离最小的模板作为识别结果。这种思路用 STM32F103 完全跑得动——五维向量的欧氏距离计算量极小,在 72MHz 下几百个时钟周期就能完成。模板数量可以从 5 个拓展到 15 个,不需要重新设计电路或换主控。
具体做法是:在标定完finger_calib后,再增加一个手势模板学习流程。串口发送“LEARN 3”,提示用户摆出手势 3 的姿势保持 5 秒,这期间每 100ms 采集一次弯曲度向量,累计 50 组后求平均存入 Flash。识别时每 100ms 算一次距离,取距离最小的手势号作为结果,同样加上迟滞判定防止频繁跳变。
Flash 存储几个模板只需要很小空间:每个模板五字节,50 个模板也才 250 字节,F103 的 64KB Flash 完全无压力。存储地址建议放在最后一页,避免和代码区冲突,F103C8T6 的最后一页从 0x0800FC00 开始。写入前先擦除整页,更新模板时注意不要频繁写 Flash,磨损寿命按 1 万次算,日常调试足够。
本文还有配套的精品资源,点击获取