1. 项目背景与核心价值
去年在整理工作室时翻出一块吃灰多年的STM32F103开发板,看着旁边落灰的数位板,突然萌生了一个疯狂的想法——能不能用单片机实现电子画板的核心功能?经过三个月的周末攻坚,这个硬核手绘方案终于落地。它不仅能实现1024级压感识别,还支持自定义快捷键和笔刷预设,整套硬件成本不到200元。
这种方案特别适合两类开发者:想要深入理解触摸屏和ADC采集原理的嵌入式学习者,以及需要低成本数位板方案的电子创客。相比商业产品动辄上千元的售价,这个项目用最基础的STM32F103C8T6(俗称"蓝莓派")就实现了80%的核心功能,剩下的20%差异主要在于专业软件的适配优化。
2. 硬件架构设计解析
2.1 核心部件选型
主控选用STM32F103C8T6主要基于三点考量:
- 充足的ADC通道(10个12位ADC)满足多维度数据采集
- 内置USB全速接口可实现HID设备模拟
- 72MHz主频足够处理触摸数据滤波算法
触摸检测采用自制矩阵电路:
- X/Y轴各用10条0.5mm间距的铜箔胶带
- 通过74HC4051多路复用器切换检测线路
- 压感检测使用FSR402压力传感器(量程0-10N)
实测发现铜箔间距小于0.3mm时会出现误触,建议保持在0.5-1mm区间
2.2 信号处理链路优化
原始信号采集面临三个主要噪声源:
- 电源纹波(表现为基线漂移)
- 触摸抖动(高频毛刺)
- 环境电磁干扰(50Hz工频)
解决方案:
c复制// 递推平均滤波算法
#define FILTER_LEN 5
uint16_t Filter_Avg(uint16_t new_val) {
static uint16_t buf[FILTER_LEN] = {0};
static uint8_t idx = 0;
uint32_t sum = 0;
buf[idx++] = new_val;
if(idx >= FILTER_LEN) idx = 0;
for(uint8_t i=0; i<FILTER_LEN; i++) {
sum += buf[i];
}
return (uint16_t)(sum / FILTER_LEN);
}
配合硬件端的RC低通滤波(截止频率1kHz),实测可将信噪比提升至45dB以上。
3. 固件开发关键实现
3.1 USB HID设备配置
使用CubeMX配置USB协议栈时需要注意:
- 报告描述符要符合USAGE PAGE (Digitizer)规范
- 输入报告需包含X/Y坐标、压力值、悬停状态
- 采样率建议设置在100-200Hz之间
典型报告描述符片段:
c复制0x05, 0x0D, // USAGE_PAGE (Digitizers)
0x09, 0x01, // USAGE (Digitizer)
0xA1, 0x01, // COLLECTION (Application)
0x09, 0x22, // USAGE (Finger)
0xA1, 0x00, // COLLECTION (Physical)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xFF, 0x7F, // LOGICAL_MAXIMUM (32767)
0x75, 0x10, // REPORT_SIZE (16)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
3.2 压感曲线校准
通过实验发现,FSR传感器的电阻-压力关系呈非线性:
code复制压力(N) | 电阻(kΩ)
0.5 | 12.5
1.0 | 8.2
2.0 | 4.1
5.0 | 1.3
采用分段线性插值算法:
c复制uint16_t ConvertPressure(uint16_t adc_val) {
const uint16_t seg_points[] = {0, 500, 1000, 2000, 5000};
const uint16_t adc_vals[] = {4095, 2500, 1800, 900, 300};
for(uint8_t i=1; i<5; i++) {
if(adc_val >= adc_vals[i]) {
return seg_points[i-1] +
(seg_points[i] - seg_points[i-1]) *
(adc_val - adc_vals[i-1]) /
(adc_vals[i] - adc_vals[i-1]);
}
}
return 10000; // 超量程
}
4. 软件适配与优化技巧
4.1 Windows Ink兼容性调试
要使设备被识别为笔输入设备,需要:
- 在设备描述符中设置正确的bInterfaceProtocol
- 添加Win8兼容性标识符
- 实现悬停距离报告(0-127范围)
注册表关键项:
code复制[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_0483&PID_5750]
"Capabilities"=dword:000000E0
"DeviceCharacteristics"=dword:00000100
4.2 笔迹预测算法
为降低延迟,实现了一个简单的卡尔曼预测器:
c复制typedef struct {
float x;
float y;
float vx;
float vy;
float P[4][4];
} KalmanState;
void Kalman_Predict(KalmanState *s, float dt) {
// 状态转移矩阵
s->x += s->vx * dt;
s->y += s->vy * dt;
// 协方差预测
s->P[0][0] += dt*(dt*s->P[2][2] + s->P[0][2] + s->P[2][0]);
s->P[0][1] += dt*(dt*s->P[2][3] + s->P[0][3] + s->P[2][1]);
s->P[1][0] += dt*(dt*s->P[3][2] + s->P[1][2] + s->P[3][0]);
s->P[1][1] += dt*(dt*s->P[3][3] + s->P[1][3] + s->P[3][1]);
}
实测可将显示延迟从35ms降低到18ms左右。
5. 制作工艺与结构设计
5.1 触摸层压合工艺
采用三层结构设计:
- 基层:2mm亚克力板(激光切割开槽)
- 中间层:铜箔矩阵(0.5mm间距)
- 表层:0.3mm磨砂PET膜
粘合注意事项:
- 使用3M 467MP双面胶(厚度0.13mm)
- 贴合时需用滚轮排除气泡
- 边缘留出5mm非工作区
5.2 电磁屏蔽方案
测试发现USB3.0接口会引入明显干扰,解决方法:
- 在PCB上铺铜作为屏蔽层
- USB数据线加装磁环
- 电源输入端增加π型滤波电路
实测干扰强度对比:
| 方案 | 噪声幅度(mV) |
|---|---|
| 无屏蔽 | 120-150 |
| 单层屏蔽 | 50-70 |
| 复合屏蔽 | <20 |
6. 性能优化实战记录
6.1 ADC采样时序优化
原始配置下ADC采样周期为28.5μs,通过以下调整提升至15μs:
- 将ADC时钟从PCLK2/8改为PCLK2/6
- 关闭非必要通道的扫描模式
- 使用DMA双缓冲模式
关键配置代码:
c复制hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV6;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DMAContinuousRequests = ENABLE;
6.2 笔迹平滑算法对比
测试三种平滑算法效果:
| 算法类型 | 延迟(ms) | 内存占用(B) | 平滑效果 |
|---|---|---|---|
| 移动平均 | 3.2 | 32 | ★★☆ |
| 卡尔曼滤波 | 5.8 | 128 | ★★★ |
| 贝塞尔拟合 | 8.5 | 64 | ★★☆ |
最终选择改进的加权移动平均:
c复制void SmoothTrack(Point *points, uint8_t count) {
static Point prev[3] = {0};
const float weights[] = {0.6f, 0.3f, 0.1f};
for(uint8_t i=0; i<count; i++) {
points[i].x = weights[0]*points[i].x
+ weights[1]*prev[0].x
+ weights[2]*prev[1].x;
// 更新历史数据
prev[1] = prev[0];
prev[0] = points[i];
}
}
7. 量产可行性分析
7.1 BOM成本核算
主要元器件成本清单(小批量100套):
| 部件 | 单价(元) | 数量 | 小计 |
|---|---|---|---|
| STM32F103C8T6 | 8.5 | 1 | 8.5 |
| FSR402 | 3.2 | 1 | 3.2 |
| 74HC4051 | 0.8 | 2 | 1.6 |
| 亚克力外壳 | 12.0 | 1 | 12.0 |
| PET膜 | 2.5 | 1 | 2.5 |
| 其他电子件 | - | - | 5.2 |
| 合计 | 33.0 |
7.2 生产测试方案
建议的测试流程:
- 电源测试:3.3V/5V电压偏差<±3%
- ADC基准测试:2.5V基准误差<±10mV
- 触点测试:全矩阵扫描无断路
- 压力线性度测试:R²>0.98
- USB枚举测试:设备描述符校验
开发了一个基于Python的自动化测试工具:
python复制import pyvisa
from matplotlib import pyplot as plt
rm = pyvisa.ResourceManager()
scope = rm.open_resource("USB0::0x0699::0x0368::C012345::INSTR")
def test_adc_noise():
samples = scope.query_ascii_values(":MEASure:VPP? CH1")
return max(samples) < 0.05 # 噪声小于50mV
8. 扩展功能开发
8.1 手势识别实现
通过分析触点移动轨迹,实现了三种手势:
- 双击:300ms内同一区域两次触点
- 长按:持续500ms以上的稳定触点
- 滑动:连续移动距离超过15mm
状态机实现片段:
c复制typedef enum {
STATE_IDLE,
STATE_TOUCH_DOWN,
STATE_HOLD,
STATE_MOVING
} TouchState;
void HandleGesture(TouchEvent *evt) {
static TouchState state = STATE_IDLE;
static uint32_t down_time = 0;
static Point down_pos = {0};
switch(state) {
case STATE_IDLE:
if(evt->is_touch) {
down_time = HAL_GetTick();
down_pos = evt->pos;
state = STATE_TOUCH_DOWN;
}
break;
// 其他状态处理...
}
}
8.2 无线化改造
添加HC-05蓝牙模块实现无线传输:
- 修改HID报告描述符为蓝牙HIDP规范
- 增加简单的AES-128加密传输
- 优化电源管理(平均电流<15mA)
关键修改点:
c复制// 修改USB HID为蓝牙HIDP
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x02, // USAGE (Mouse)
0xA1, 0x01, // COLLECTION (Application)
// ...其余描述符保持类似结构
实测在200mAh电池下可连续工作约13小时。