这个项目让我想起了去年帮一位程序员朋友解决的眼部疲劳问题。他每天盯着屏幕超过12小时,经常抱怨眼睛干涩、胀痛。市面上那些动辄上千元的智能护眼仪,要么功能华而不实,要么操作复杂得让人头疼。于是我们决定自己动手,用最朴素的单片机方案打造一款真正实用的眼部按摩仪。
这个控制系统是整个设备的大脑,负责协调振动马达、加热模块、定时功能等核心部件。不同于商业产品的花哨设计,我们坚持"够用就好"的原则,选用性价比较高的STM32F103C8T6作为主控芯片。这款ARM Cortex-M3内核的单片机,72MHz主频完全能满足控制需求,而且开发环境成熟,各种外设驱动一应俱全。
选择STM32F103C8T6主要基于三点考虑:
实际采购时有个小技巧:一定要选择正规渠道的"蓝板"核心板,那些廉价的"绿板"经常出现USB识别不稳的问题。我们最初为了省20块钱买了三块绿板,结果有两块在烧录程序时频繁断开连接,反而浪费更多时间。
眼部按摩需要温和的振动,我们选用了直径10mm的扁平振动马达,工作电压3V,电流约80mA。驱动电路设计时特别注意了三点:
调试时发现一个有趣现象:当PWM频率超过200Hz时,振动反而变得不明显。这是因为人眼对高频振动不敏感,最终我们将频率设定在80-120Hz范围内,效果最佳。
加热模块使用5V/1A的陶瓷发热片,配合DS18B20数字温度传感器构成闭环控制。这里有几个关键参数:
实际测试时,从室温加热到40℃约需90秒。有个重要发现:加热片一定要均匀分布在眼罩两侧,如果集中在一处会导致局部过热。我们最终采用环形布局,六个小型发热片呈放射状排列。
程序采用裸机开发,没有上RTOS,通过状态机实现多任务调度。主循环结构如下:
c复制while(1) {
static uint32_t tick = 0;
// 10ms定时任务
if(GetTick() - tick >= 10) {
tick = GetTick();
Key_Scan(); // 按键扫描
Mode_Update(); // 模式切换
Temp_Control(); // 温度控制
Motor_Drive(); // 马达驱动
LED_Display(); // 状态显示
}
// 其他事件处理
if(usart_rx_flag) {
Usart_Process();
usart_rx_flag = 0;
}
}
这种架构既保证了实时性,又避免了RTOS的学习成本。实测各任务执行时间都在2ms以内,完全满足需求。
振动强度分为5档,通过改变PWM占空比实现。但单纯调整占空比会导致振动生硬,我们加入了缓启动算法:
c复制void Motor_SoftStart(uint8_t target_duty) {
static uint8_t current_duty = 0;
const uint8_t step = 2; // 每次变化步长
while(current_duty != target_duty) {
if(current_duty < target_duty) {
current_duty = (current_duty + step) > target_duty ?
target_duty : (current_duty + step);
} else {
current_duty = (current_duty - step) < target_duty ?
target_duty : (current_duty - step);
}
Set_PWM_Duty(current_duty);
Delay_ms(30); // 变化间隔
}
}
这个简单的算法让振动强度变化非常自然,用户体验提升明显。测试者反馈说"就像有手指在轻轻按压眼周"。
温度控制采用位置式PID算法,参数整定过程很有意思:
最终算法实现:
c复制typedef struct {
float SetTemp; // 目标温度
float ActualTemp; // 实际温度
float Err; // 当前误差
float Err_Last; // 上次误差
float Kp, Ki, Kd; // PID参数
float Integral; // 积分项
} PID;
float PID_Calc(PID *pid) {
pid->Err = pid->SetTemp - pid->ActualTemp;
pid->Integral += pid->Err;
float result = pid->Kp * pid->Err +
pid->Ki * pid->Integral +
pid->Kd * (pid->Err - pid->Err_Last);
pid->Err_Last = pid->Err;
return result;
}
实际运行中,温度波动控制在±0.5℃以内,响应速度和安全性能达到医疗级标准。
设计了三键控制:
LED指示灯方案:
有个细节值得分享:按键消抖不是用传统的延时法,而是采用状态机实现:
c复制typedef enum {
KEY_STATE_RELEASE, // 未按下
KEY_STATE_WAIT, // 消抖等待
KEY_STATE_PRESS, // 确认按下
KEY_STATE_HOLD // 长按状态
} KeyState;
KeyState key_state = KEY_STATE_RELEASE;
uint32_t key_tick = 0;
void Key_Scan(void) {
switch(key_state) {
case KEY_STATE_RELEASE:
if(KEY_PRESSED) {
key_state = KEY_STATE_WAIT;
key_tick = GetTick();
}
break;
case KEY_STATE_WAIT:
if(GetTick() - key_tick > 20) { // 20ms消抖
if(KEY_PRESSED) {
key_state = KEY_STATE_PRESS;
Key_Action(); // 执行按键动作
} else {
key_state = KEY_STATE_RELEASE;
}
}
break;
// 其他状态处理...
}
}
这种方法既可靠又不阻塞系统,实测按键响应非常跟手。
预设了三种专业按摩模式:
按压模式(适合快速缓解疲劳)
揉捏模式(促进血液循环)
热敷+振动组合模式
模式切换时,系统会自动保存用户上次使用的参数,这个贴心的设计获得测试者一致好评。
虽然这不是便携设备,但我们仍然优化了功耗:
实测整机工作电流:
硬件看门狗(STM32内置IWDG)
温度安全监控
c复制if(CurrentTemp > 42.0f) {
Heat_Off();
Motor_Stop();
LED_Alert();
while(1); // 进入死循环等待复位
}
振动马达过流保护
锂电池保护(采用18650电池供电时)
眼罩部分我们尝试了三种材料:
安装振动马达时有个重要技巧:要用双面胶+热熔胶双重固定,纯胶粘容易因振动脱落。我们最初没注意这点,测试时有个马达直接震飞了。
有个值得记录的bug:最初温度采样偶尔会出现跳变,后来发现是DS18B20的读取时序不够严格。修改后的读取函数必须包含精确的延时:
c复制void DS18B20_ReadBit(uint8_t *bit) {
GPIO_Reset(); // 拉低总线
Delay_us(2); // 精确延时2μs
GPIO_Set(); // 释放总线
Delay_us(12); // 等待12μs
*bit = GPIO_Read();
Delay_us(50); // 完成整个时隙
}
通过20人次的实测反馈,我们做了这些改进:
最终成品成本控制在80元以内,但体验不输千元级产品。最让我自豪的是,那位程序员朋友使用两周后说:"现在加班到凌晨,眼睛也不会那么酸胀了。"这或许就是创客最大的成就感。