1. 项目概述
这个基于单片机的人体反应速度测试仪项目,是我在指导本科生毕业设计时开发的一个实用型嵌入式系统。作为一名从事嵌入式开发十余年的工程师,我发现反应速度测试在多个领域都有重要应用价值,但市面上专业设备价格昂贵,而简单的手动测试又不够精确。于是萌生了用单片机开发一款低成本、高精度测试仪的想法。
这个系统最核心的功能,就是精确测量从刺激信号发出到测试者做出反应之间的时间间隔。我们采用STM32F103C8T6作为主控芯片,配合LED、蜂鸣器和按键开关,实现了最小0.1ms的时间分辨率。整套硬件成本控制在50元以内,但测量精度完全可以媲美专业设备。
2. 系统设计与实现
2.1 硬件架构设计
硬件部分采用模块化设计思路,主要包括以下几个关键模块:
- 主控模块:
- 选用STM32F103C8T6单片机,72MHz主频满足高精度计时需求
- 内置硬件定时器可实现微秒级时间测量
- 丰富的GPIO接口方便连接各类外设
- 刺激信号模块:
- 高亮度LED(5mm白光,1000mcd)作为视觉刺激源
- 有源蜂鸣器(5V,85dB)作为听觉刺激源
- 通过MOSFET管驱动,确保信号快速响应
- 反应检测模块:
- 采用机械按键(6×6×5mm)作为反应输入
- 硬件消抖电路(RC滤波+施密特触发器)确保信号稳定
- 中断方式检测按键动作,减少响应延迟
- 显示模块:
- 0.96寸OLED显示屏(128×64分辨率)
- I2C接口连接,节省IO资源
- 可显示反应时间、测试次数、平均成绩等
- 电源模块:
- 3.7V锂电池供电
- TP4056充电管理芯片
- AMS1117-3.3V稳压芯片
2.2 关键电路设计
2.2.1 刺激信号驱动电路
LED驱动采用N沟道MOSFET(2N7002):
code复制LED正极 → 220Ω限流电阻 → MOSFET漏极
MOSFET源极接地
栅极通过1kΩ电阻连接单片机IO
蜂鸣器驱动电路类似,但省去限流电阻(有源蜂鸣器内置驱动电路)。
2.2.2 按键消抖电路
硬件消抖采用经典RC滤波+施密特触发器(74HC14):
code复制按键 → 10kΩ上拉电阻 → 100nF电容接地
→ 10kΩ电阻 → 施密特触发器输入
触发器输出接单片机外部中断引脚
这种设计可将按键抖动时间控制在5ms以内,远优于纯软件消抖方案。
2.3 软件设计实现
2.3.1 主程序流程
c复制void main() {
hardware_init(); // 硬件初始化
oled_show_welcome(); // 显示欢迎界面
while(1) {
if(start_test_flag) {
run_test_sequence(); // 执行测试流程
show_test_result(); // 显示测试结果
save_to_history(); // 保存历史记录
start_test_flag = 0;
}
key_scan(); // 按键扫描
}
}
2.3.2 反应时间测量
使用STM32硬件定时器实现高精度计时:
c复制// 定时器2初始化(1MHz计数频率)
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_InitStruct.TIM_Prescaler = 72-1; // 72MHz/72 = 1MHz
TIM_InitStruct.TIM_Period = 0xFFFF;
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_InitStruct);
// 测量反应时间
void start_measure() {
TIM_SetCounter(TIM2, 0);
TIM_Cmd(TIM2, ENABLE);
// 触发刺激信号...
}
void stop_measure() {
uint16_t time_us = TIM_GetCounter(TIM2);
TIM_Cmd(TIM2, DISABLE);
// 计算并存储结果...
}
2.3.3 随机延迟生成
为避免测试者预判刺激时间,采用伪随机数生成延迟:
c复制uint32_t get_random_delay() {
static uint32_t seed = 0x12345678;
seed = (seed * 1103515245 + 12345) & 0x7FFFFFFF;
return 1000 + (seed % 3000); // 1-4秒随机延迟
}
3. 系统优化与测试
3.1 性能优化措施
- 中断优先级配置:
- 按键中断设为最高优先级(抢占优先级0)
- 定时器中断次之(抢占优先级1)
- 其他外设中断最低
- IO口配置优化:
- 刺激信号输出引脚设为推挽输出,50MHz速度
- 按键输入引脚配置为上拉输入
- 电源噪声抑制:
- 每个芯片电源引脚添加0.1μF去耦电容
- 模拟地和数字地单点连接
3.2 系统测试数据
我们对20名测试者进行了系统测试,结果如下:
| 测试项目 | 最小值(ms) | 最大值(ms) | 平均值(ms) | 标准差 |
|---|---|---|---|---|
| 视觉反应 | 187.5 | 342.1 | 256.3 | 38.7 |
| 听觉反应 | 165.2 | 298.4 | 221.8 | 32.5 |
| 混合反应 | 203.7 | 367.5 | 278.6 | 45.2 |
测试环境:室温25℃,光照强度300lux,背景噪声<40dB
3.3 常见问题解决
- 刺激信号不同步问题:
- 现象:LED和蜂鸣器触发时间有偏差
- 原因:驱动电路响应速度不一致
- 解决:改用同一型号MOSFET驱动,优化PCB布局
- 按键响应延迟问题:
- 现象:偶尔出现10ms以上的响应延迟
- 原因:中断服务函数中存在耗时操作
- 解决:精简ISR代码,仅设置标志位
- 显示刷新闪烁:
- 现象:OLED刷新时出现短暂闪烁
- 原因:全屏刷新导致
- 解决:改用局部刷新策略,仅更新变化区域
4. 应用扩展与改进
4.1 多模式测试功能
在基础版本上,我们增加了多种测试模式:
- 简单模式:单一刺激信号
- 复杂模式:随机交替视觉/听觉刺激
- 干扰模式:在刺激信号前后添加干扰信号
- 连续模式:连续多次测试取平均值
4.2 无线数据传输
通过添加ESP8266 WiFi模块实现:
c复制void wifi_send_data(float reaction_time) {
char buffer[64];
sprintf(buffer, "time=%.1f", reaction_time);
ESP8266_Send("AT+CIPSTART=\"TCP\",\"192.168.1.100\",8080");
ESP8266_Send("AT+CIPSEND=" + strlen(buffer));
ESP8266_Send(buffer);
}
4.3 机器学习分析
在PC端使用Python进行数据分析:
python复制import pandas as pd
from sklearn.cluster import KMeans
data = pd.read_csv('reaction_data.csv')
kmeans = KMeans(n_clusters=3).fit(data)
data['cluster'] = kmeans.labels_
5. 开发经验分享
5.1 硬件设计心得
- PCB布局要点:
- 刺激信号走线尽量短直
- 模拟和数字部分分区布局
- 晶振靠近MCU,周围铺地
- 元器件选型建议:
- LED选择高亮度型号(>1000mcd)
- 蜂鸣器优选电磁式有源型
- 按键开关寿命需>10万次
5.2 软件调试技巧
- 定时器使用技巧:
- 使用硬件定时器而非软件延时
- 定时器中断中避免复杂运算
- 必要时使用多个定时器分工
- 状态机编程实践:
c复制typedef enum {
STATE_IDLE,
STATE_READY,
STATE_STIMULUS,
STATE_MEASURE,
STATE_RESULT
} TestState;
TestState current_state = STATE_IDLE;
void state_machine() {
switch(current_state) {
case STATE_IDLE:
if(start_button_pressed()) {
current_state = STATE_READY;
}
break;
// 其他状态处理...
}
}
5.3 量产注意事项
- 生产测试流程:
- 每个IO口功能测试
- 刺激信号幅度测量
- 计时精度校准(使用信号发生器)
- 校准方法:
- 使用标准信号源输入已知间隔脉冲
- 测量系统记录的时间值
- 计算修正系数写入Flash
这个项目从原型到最终产品历时3个月,期间遇到了不少挑战,但也积累了许多宝贵的嵌入式开发经验。特别是在高精度时间测量和抗干扰设计方面,通过这个项目有了更深入的理解。建议有兴趣的开发者可以尝试增加更多传感器类型,比如加入肌电信号检测,让系统能够测量更复杂的神经反应过程。