1. 项目概述
作为一名嵌入式系统开发者,我最近完成了一个基于STM32F103C8T6微控制器的智能护眼台灯系统重构项目。这个项目源于对市面上现有智能台灯产品的不满——它们要么功能单一,要么用户体验糟糕。通过这次重构,我成功将工业级的控制算法和智能化的传感器融合技术应用到了这个看似简单的家用设备上,使其真正具备了"智能"和"护眼"的双重特性。
这个台灯的核心创新点在于:
- 采用了PID闭环控制算法实现无级、恒定的自动调光
- 结合PIR红外和超声波传感器实现了真正的人体存在检测(而不仅仅是运动检测)
- 引入了Gamma校正技术使亮度变化更符合人眼感知特性
- 设计了分级坐姿提醒系统,避免传统方案的粗暴报警方式
整个系统在STM32F103C8T6这颗72MHz主频的Cortex-M3内核MCU上实现,充分利用了其丰富的外设资源和足够的计算能力。下面我将详细分享这个项目的设计思路、实现细节和实际使用中的经验教训。
2. 硬件架构设计
2.1 微控制器选型与资源分配
选择STM32F103C8T6作为主控芯片主要基于以下几个考虑:
- 性价比高:作为经典的Cortex-M3 MCU,价格适中但性能足够
- 外设丰富:具备多个定时器、ADC、USART等必要外设
- 开发生态完善:有成熟的开发工具链和大量参考资源
在实际资源分配上,我做了如下规划:
| 功能模块 | 使用外设 | 引脚分配 | 配置说明 |
|---|---|---|---|
| PWM调光 | TIM3_CH1 | PA6 | 16位分辨率,20kHz频率 |
| OLED显示 | I2C1 | PB6/PB7 | 400kHz速率,启用DMA传输 |
| 超声波测距 | TIM2输入捕获 | PA0/PA1 | 微秒级精度测量 |
| 光敏电阻 | ADC1_IN4 | PA4 | DMA循环采样,软件滤波 |
| 蓝牙模块 | USART1 | PA9/PA10 | 115200波特率,中断接收 |
| PIR传感器 | EXTI | PB1 | 双边沿触发中断 |
| 按键 | GPIO输入 | PB12-PB15 | 定时扫描,软件消抖 |
| 蜂鸣器 | TIM4_CH3 | PB8 | PWM调制实现音量控制 |
2.2 传感器选型与信号处理
2.2.1 光敏电阻电路设计
光敏电阻的阻值随光照强度变化呈非线性关系,典型特性为:
R = R0 * (L0/L)^γ
其中:
- R为当前电阻值
- R0为参考光照L0下的电阻值
- γ为材料常数,通常在0.7-1.0之间
为了获得线性化的光照度测量,我设计了如下电路和处理流程:
- 采用分压电路将光敏电阻值转换为电压信号
- 通过100nF电容滤波消除高频噪声
- ADC采样后,在软件中进行对数变换:
Lux = 10^(a * ADC + b)
其中a、b为校准系数
2.2.2 超声波传感器优化
HC-SR04超声波模块存在两个主要问题:
- 2cm左右的盲区
- 硬表面反射导致的多径干扰
我的解决方案是:
- 软件上采用中值滤波:连续采样5次,取中间值
- 增加异常值剔除:超过400cm或小于2cm的数据直接丢弃
- 动态调整采样频率:当检测到人存在时提高采样率
3. 核心算法实现
3.1 PID恒照度控制
PID控制器的离散化实现公式:
u(k) = Kpe(k) + Ki∑e(j) + Kd*(e(k)-e(k-1))
在我的实现中,参数设置为:
- Kp=0.5:提供快速响应
- Ki=0.05:消除稳态误差
- Kd=0.1:抑制超调
实际代码实现:
c复制void Lamp_Run_PID(void) {
if (g_LampState.currentMode != MODE_AUTO_PID) return;
float currentLux = (float)g_LampState.sensorLux;
float error = lightPID.setPoint - currentLux;
// 积分项限幅防饱和
lightPID.integral += error;
if (lightPID.integral > 1000) lightPID.integral = 1000;
if (lightPID.integral < -1000) lightPID.integral = -1000;
float derivative = error - lightPID.prevError;
float output = (lightPID.Kp * error) +
(lightPID.Ki * lightPID.integral) +
(lightPID.Kd * derivative);
lightPID.prevError = error;
int newPWM = g_LampState.currentPWM + (int)output;
newPWM = (newPWM > 65535) ? 65535 : ((newPWM < 0) ? 0 : newPWM);
g_LampState.currentPWM = (uint16_t)newPWM;
LED_PWM_Set(g_LampState.currentPWM);
}
3.2 Gamma校正
人眼对亮度的感知是非线性的,遵循近似γ=2.2的幂律关系。为了实现线性的亮度感知,需要在PWM输出前进行Gamma校正:
校正公式:
PWMout = PWMmax * (PWMin/PWMmax)^γ
实际实现时,我预先计算了一个256点的查找表(LUT)来避免实时计算:
c复制const uint16_t GAMMA_LUT[101] = {
0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100,
121, 144, 169, 196, 225, 256, 289, 324, 361, 400,
// ...中间省略...
38416, 40401, 42436, 44521, 46656, 48841, 51076, 53361, 55696, 58081,
60516, 63001, 65535
};
3.3 传感器融合算法
人体存在检测采用了PIR和超声波融合的策略:
c复制void Lamp_Run_HealthMonitor(void) {
// 获取传感器数据
g_LampState.distanceCm = HCSR04_GetDistance();
bool pirTriggered = PIR_ReadState();
// 融合判断逻辑
if (pirTriggered || (g_LampState.distanceCm < DIST_PERSON_EXIST_CM
&& g_LampState.distanceCm > 2)) {
g_LampState.isPersonPresent = true;
noPersonTimer = NO_PERSON_TIMEOUT;
} else {
if (noPersonTimer > 0) noPersonTimer--;
else g_LampState.isPersonPresent = false;
}
// 无人时自动关灯
if (!g_LampState.isPersonPresent) {
g_LampState.targetBrightness = 0;
if(g_LampState.currentMode == MODE_AUTO_PID) LED_PWM_Set(0);
return;
}
// 坐姿检测逻辑
if (g_LampState.distanceCm < DIST_BAD_POSTURE_CM
&& g_LampState.distanceCm > 2) {
if (badPostureCounter < POSTURE_TIME_THRES + 50) {
badPostureCounter++;
}
} else {
if (badPostureCounter > 0) badPostureCounter -= 2;
}
// 分级报警处理
if (badPostureCounter > POSTURE_TIME_THRES) {
g_LampState.posture = POSTURE_BAD_ALARM;
// 触发蜂鸣器
} else if (badPostureCounter > POSTURE_TIME_THRES / 2) {
g_LampState.posture = POSTURE_BAD_WARNING;
// 灯光呼吸提醒
} else {
g_LampState.posture = POSTURE_GOOD;
}
}
4. 软件架构设计
4.1 主程序流程
系统采用时间片轮询架构,主要任务分配如下:
| 任务 | 执行周期 | 优先级 | 说明 |
|---|---|---|---|
| 按键扫描 | 10ms | 高 | 检测用户输入 |
| PWM调光更新 | 10ms | 高 | 平滑亮度过渡 |
| 传感器数据采集 | 100ms | 中 | 读取光照、距离等 |
| 核心算法运行 | 100ms | 中 | PID计算、融合判断 |
| OLED刷新 | 100ms | 低 | 界面更新 |
主循环代码框架:
c复制while (1) {
// 10ms任务
if (tick_10ms++ >= 10) {
tick_10ms = 0;
KEY_Scan();
Lamp_Update_PWM_Soft();
}
// 100ms任务
if (tick_100ms++ >= 100) {
tick_100ms = 0;
g_LampState.sensorLux = Get_Adc_Average();
Lamp_Run_HealthMonitor();
Lamp_Run_PID();
OLED_Refresh();
}
delay_ms(1);
}
4.2 状态机设计
系统主要维护两个状态机:
-
工作模式状态机:
- 手动模式
- 自动PID模式
- 蓝牙远程模式
- 番茄工作模式
-
坐姿监测状态机:
- 良好姿势
- 预警状态(视觉提醒)
- 报警状态(声音提醒)
状态转换条件都基于传感器数据和定时器超时触发,确保系统响应及时且稳定。
5. 实际使用效果与优化
5.1 性能测试数据
经过实际测试,系统各项指标如下:
| 指标 | 测试结果 |
|---|---|
| 调光响应时间 | 从0到100%亮度约1.5秒 |
| 恒照度控制精度 | ±5%设定值(500Lux时) |
| 人体检测准确率 | 静态检测成功率>95% |
| 坐姿误报率 | <5次/小时 |
| 整机功耗 | 工作状态3.5W,待机0.2W |
5.2 遇到的问题与解决方案
-
PWM频闪问题
- 现象:在低亮度时,手机摄像头能观察到明显频闪
- 原因:PWM频率太低(初始设为1kHz)
- 解决:提高到20kHz,超出人眼和摄像头可感知范围
-
超声波误触发
- 现象:无人时会偶尔误判为有人
- 原因:超声波遇到硬表面反射产生假回波
- 解决:增加距离有效性检查和中值滤波
-
PID振荡问题
- 现象:光照度在小范围内不断波动
- 原因:微分项过强导致超调
- 解决:调整Kd参数,增加输出变化率限制
5.3 使用建议
-
安装位置:
- 台灯应放置在桌面正前方,超声波传感器对准使用者头部位置
- 避免强光直射光敏电阻
-
参数调整:
- 对于不同桌面反射率,可能需要调整超声波存在判断阈值
- 根据环境光照情况,设置合适的PID目标照度值
-
维护提示:
- 定期清洁传感器窗口,避免灰尘影响检测精度
- 每半年校准一次光敏电阻(用专业照度计比对)
6. 扩展功能设想
基于现有硬件平台,还可以进一步扩展以下功能:
-
学习模式记忆
- 记录用户每天的使用习惯(开启时间、常用亮度等)
- 自动预测并提前调整到偏好设置
-
环境光自适应
- 根据昼夜时间自动调整色温
- 与室内其他智能灯光联动
-
健康数据分析
- 统计坐姿不良时间和频率
- 生成健康报告并通过蓝牙发送到手机
-
语音控制集成
- 增加麦克风模块实现语音指令
- 支持"调亮一点"、"阅读模式"等自然语言控制
这个项目让我深刻体会到,即使是像台灯这样看似简单的日常用品,通过合理的硬件设计和智能化的软件算法,也能大幅提升使用体验。特别是在护眼和健康关怀方面,还有很多创新空间值得探索。
