简介:基于STM32F4xx的光敏电阻自动灯光调节是一份适合嵌入式初学者的完整工程资源。项目利用光敏电阻感知环境光线,通过STM32内置ADC采集信号,并以PWM调节LED亮度,覆盖了GPIO、ADC、定时器PWM等核心外设的配置方法,也涉及HAL库编程与硬件接口设计。资源共61个文件,压缩包1.71MB,以Keil工程文件为主,包含C源码、头文件、启动文件、编译生成的hex/axf及调试记录等,可直接打开学习或烧录验证。目前已有3015人学习下载,适合正在学习STM32、ARM Cortex-M4或想动手实践传感器控制项目的开发者。通过该工程可以理解光敏电阻分压采样、ADC转换、PWM占空比控制等完整链路,还能参考其代码结构、外设初始化流程与工程组织方式,提升嵌入式开发和调试能力,为后续更复杂的物联网或智能控制项目打下基础。
1. 光敏电阻自动调光到底在调什么:先看懂这条信号链
晚上走出客厅,阳台的灯自己亮了;书桌灯随着窗外天色变暗自动把亮度抬起来——这类场景背后都是同一套东西:光敏电阻把光照强度变成电阻值,STM32F4xx 的 ADC 把分压电压读成数字量,再通过定时器 PWM 控制灯的亮度。听起来很简单,但真正让项目卡住的从来不是“读 ADC”这三行代码,而是阈值怎么定、灯光为什么在临界点反复闪烁、白天为什么偶尔误亮。
这套做法面向正在用 STM32F4xx 做小产品的开发者,从硬件分压、CubeMX 工程配置、迟滞判断,一路讲到 5 个高频翻车现场,内容都按可复现的方式拆开,抄完心里也有底。下面先从最容易被忽视的电路选型讲起。
2. 为什么是 STM32F4xx + 光敏电阻:从分压电路到 ADC 采样的选型逻辑
2.1 光敏电阻分压电路:一个固定电阻决定量程
先明确物理基础。光敏电阻是半导体器件,光照越强,内部载流子越多,电阻越小;暗电阻能到兆欧级,亮电阻可以低到几千欧甚至几百欧。这个变化范围跨度太大,加上本身是纯电阻器件,没法直接进 ADC,必须靠一个固定电阻做分压,把电阻变化换算成电压变化。
常见接法是这样:光敏电阻一端接 VCC(3.3V),另一端和固定电阻串联到 GND,两个电阻的中间抽头接 ADC 引脚。此时分压点电压Vout = VCC × R_fixed / (R_g + R_fixed)。
光照越强 R_g 越小,Vout 越高,ADC 读数越大。这个方向关系一定要先确认,因为后面所有阈值判断都依赖它。如果接反了(固定电阻在上、光敏电阻在下),ADC 值和光照会变成反比,逻辑也要跟着反写。> 提示:分压接法决定阈值方向,写代码之前先用手电筒照一下光敏电阻,确认 ADC 读数确实随光照上升,再往下走。
固定电阻的取值是这里第一个需要仔细考虑的点——选太小,暗光下电压变化不敏感;选太大,强光下电压早早进入饱和区,白天和傍晚的读数区分不出来。工程上习惯取亮阻和暗阻的几何中点附近:R_fixed = sqrt(R_dark × R_light)。例如常见的 GL5528 在典型环境下暗阻约 1MΩ、亮阻约 10kΩ,几何中点接近 100kΩ,但实际项目里很多人直接上 10kΩ,配合室内几十勒克斯到几千勒克斯的实测范围,也能把 ADC 值拉开,只是线性区间会窄一些。
分压点纹波是另一个容易被忽略但实际影响很大的环节:ADC 引脚到光敏电阻之间的走线如果超过几厘米,在电机、继电器、开关电源附近很容易捡到耦合噪声。最实用的做法是在 ADC 引脚对地加一个 0.1uF 陶瓷电容,做硬件低通,能解决很大一部分“读数跳变”问题,后面排查章节还会再提。
2.2 STM32F4xx 的 ADC 和 PWM 资源:为什么选 F4 处理这个场景
这个项目并不需要多少算力,但 F4 在这个场景里确实比 F1 和新出的低端系列都要顺手。首先是 ADC:F4 内置 12 位逐次逼近型 ADC,多个转换器可并行使用,工作时钟最高能到 36MHz 左右,即使按最保守的 84 周期采样时间,单次转换也就一两微秒量级,对光敏这种变化缓慢的信号绰绰有余。
其次是定时器资源。自动调光在多数人手上不只是开灯关灯,而是用 PWM 把 LED 调到某个亮度。F4 的通用定时器(TIM2~TIM5)本身 16 位或 32 位,能直接输出多路 PWM,占空比调到 1% 级别;配合 168MHz 主频,PWM 频率取 1kHz 到 20kHz 都很轻松。做调光灯时 PWM 频率别低于 1kHz,否则 LED 会明显闪,人眼虽然不一定察觉,但手机摄像头一拍就见条纹。
另外 ADC 和 PWM 都是外设,不占用 CPU。后续如果要扩展多路光敏(比如同时采集两个方向的采光)或者把灯光联动到按键、OLED 显示上,F4 的引脚和外设余量也够。如果用 F1 或者低主频的 F0,也不是不能做,但 PWM 频率和 ADC 采样时间之间要互相妥协,代码里绕路的成本比硬件差价高得多。
2.3 完整信号链:光照 → 电阻 → 电压 → 数字量 → PWM 占空比
把整条链路画出来长这样:环境光照射到光敏电阻 → 电阻值变化 → 分压点电压变化 → STM32F4xx ADC 采样得到 0~4095 的数字量 → 软件滤波后做阈值或亮度映射 → 修改定时器比较寄存器 → PWM 输出 → 灯具实际亮度变化。
每一级都有延迟和误差:光照变化本身要时间(室内通常几百毫秒到几秒);分压电路的 RC 时间常数受电容影响;ADC 采样量化误差在半 LSB 量级;软件滤波引入几十到几百毫秒延迟;最终 LED 的光响应几乎无延迟,但人的视觉对缓慢变化很不敏感,所以整条链路做到“从光照变化到灯变亮在 0.5~1 秒内”就已经很自然。
这里有一个值得建立的观念:不要试图在这条链路上做“实时”。光敏电阻响应本来就慢,环境光也几乎没有瞬变,过度追求采样频率只会放大噪声。F4 的 ADC 每秒采几万次很容易,但对于这个场景,稳定的慢反馈比快的乱反馈好得多。这也是为什么后面推荐低通滤波和迟滞判断,而不是直接用单次采样做控制。
3. 搭硬件与生成工程:CubeMX 配置与最小可运行代码
3.1 硬件连接:光敏电阻、固定电阻、LED 该接哪些引脚
拿最小系统来说,我一般这样接:光敏电阻一端接 3.3V,另一端接到分压节点;10kΩ 固定电阻从分压节点接到 GND;分压节点同时接 0.1uF 电容到 GND,并引线到 PA1(ADC1 通道 1)。LED 如果是普通 5mm 小灯珠,串一个 330Ω 限流电阻后接 PB0(TIM3 通道 3 的 PWM 输出);如果是一整条灯带,PB0 只输出 PWM 控制信号,灯带的电源单独接,不能从开发板上取电。
| 器件 | 引脚 | 说明 |
|---|---|---|
| 光敏电阻 | 3.3V → 分压点 | 光照强电阻减小 |
| 固定电阻 10kΩ | 分压点 → GND | 和光敏电阻串联分压 |
| 滤波电容 0.1uF | 分压点 → GND | 滤除高频耦合噪声 |
| ADC 引脚 | PA1 | ADC1_IN1,采样分压电压 |
| LED 限流电阻 330Ω | PB0 → LED | 保护 LED,控制亮度 |
| LED 负极 | GND | 普通 LED 直接灌电流 |
PA1 只是常用选择,也可以用 PA0、PA2 等,关键是 CubeMX 里对应的 ADC 通道要一致。PB0 对应 TIM3_CH3,是 F4 上比较顺手的 PWM 引脚组合。
电源方面容易被忽略:如果整个板子用的是 USB 供电,LED 和传感器共用一路 3.3V,那么 LED 拉电流时会把 3.3V 打掉二三十毫伏,ADC 参考电压跟着抖,读数就会周期性漂移。这个问题的正解不是换 LDO,而是别让大功率负载和传感器共用同一路电源,或者至少把 LED 的地线单独走,最后在电源入口单点合并。
接线和电源处理好之后,下一步就是把工程搭起来。手焊时按图接,别凭记忆,曾经有人把 PA1 接成了 PB1,ADC 读出来永远是 0,查了半天才发现是通道配错了。
3.2 CubeMX 工程配置:ADC + 定时器 PWM 的关键参数
在 CubeMX 里要配置三块:时钟树、ADC1、TIM3。
时钟树:F4 系列默认用 HSE 外部晶振(常见 8MHz),主频按型号能力拉到 168MHz 或更高(不同型号有差异)。ADC1 的时钟源选 APB2 分频后的时钟,注意要让 ADCCLK 落在器件手册要求的范围里,一般不要超过 36MHz,如果发现系统挂掉或者 ADC 读数异常,先查这个。
ADC1 配置参数用下面这组,直接照填即可:
| 参数 | 取值 | 说明 |
|---|---|---|
| Resolution | 12 bit | 读数 0~4095 |
| ScanConvMode | Disabled | 单通道用不到扫描 |
| ContinuousConvMode | Disabled | 用单次转换,软件控制 |
| EOC Selection | EOC flag | 转换结束靠标志位判断 |
| NumberOfConversion | 1 | 只采一路 |
| Sampling Time | 84 Cycles | 兼顾速度和稳定 |
| External Trigger | None | 软件触发,逻辑最简单 |
TIM3 配置:选 PWM 模式 1。预分频 PSC 取 83,计数周期 ARR 取 999,这样在 84MHz 的定时器时钟下 PWM 频率就是 84MHz ÷ 84 ÷ 1000 = 1kHz。占空比的最小步进 1/1000,相当于 0.1%,对调光来说已经足够细腻。> 注意:PSC 和 ARR 填完,实际频率在 CubeMX 的 Clock Configuration 页面能直接看到,别自己心算,那个页面显示的就是芯片复位后的真实频率。
PWM 通道初始比较值可以给 0,让灯先灭着。TIM3 在 APB1 总线上,如果 APB1 分频不是 1,定时器时钟会自动倍频,这些 CubeMX 都会算好,你只需要确认最终 PWM 频率在 1kHz 到 20kHz 之间。
3.3 初始化与 HAL 库代码:读 ADC、调亮度、让灯先动起来
CubeMX 生成工程后,主循环的任务就是循环读 ADC → 滤波 → 决定 PWM 占空比。最小可运行的代码块如下,拆成两个函数,一个是 ADC 读取,一个是 PWM 设置:
/* 读取光敏电阻当前 ADC 值,做 8 次均值滤波 */ uint16_t read_light_value(void) { uint16_t sum = 0; for (uint8_t i = 0; i < 8; i++) { HAL_ADC_Start(&hadc1); if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) { sum += HAL_ADC_GetValue(&hadc1); } HAL_ADC_Stop(&hadc1); } return sum / 8; } /* 设置 PWM 占空比,duty 范围 0~999 */ void set_light_brightness(uint16_t duty) { if (duty > 999) duty = 999; __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, duty); }两个函数都用了 HAL 标准接口,没有任何平台私有操作。HAL_ADC_PollForConversion的第二个参数是超时时间,单位毫秒,写 10 是安全值;如果 ADC 配置错了,函数会返回超时,不会卡死。HAL_ADC_Stop每次转换完都调用,保证下一次HAL_ADC_Start从空闲状态开始,避免连续转换模式下状态残留。
8 次均值滤波是这个阶段最简单的软件低通:光敏电阻本身响应慢,8 次采样在 1kHz 触发频率下耗时也就几毫秒,几乎不会拖慢主循环。如果读数还是抖,把次数从 8 提到 16 或 32,但别超过 64,后面会说为什么。
主循环里最简逻辑可以写成:
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_TIM3_Init(); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3); while (1) { uint16_t lv = read_light_value(); if (lv < 1200) { /* 环境光很暗 */ set_light_brightness(300); } else if (lv < 2500) { /* 黄昏或阴天 */ set_light_brightness(120); } else { /* 白天 */ set_light_brightness(0); } HAL_Delay(50); /* 控制判断频率 */ } }这个逻辑能跑,但只适合验证硬件。1200 和 2500 这两个阈值是随手写的,每套电路、每种光敏电阻的读数都不同,直接抄数值大概率不准。更关键的是,这个写法没有迟滞,光照在阈值附近波动时,占空比会在两个档位之间来回跳,灯会像“呼吸”一样起伏。真正能用的判断逻辑放在第 4 章。
HAL_Delay(50)表示 50ms 才做一次完整判断。这个时间要和你后面的滤波窗口匹配:如果滤波做了 32 次采样、每次转换要 1ms,再加上 50ms 延时,响应延迟会在 100ms 级别,对光控场景完全可接受。
4. 把 ADC 值变成灯光动作:阈值判断、迟滞与滤波
4.1 ADC 值到光照的映射:先把数字量翻译成人话
12 位 ADC 的读数范围是 0~4095,对应 0~3.3V 的输入电压。理论上可以用公式 V = value × 3.3 / 4095 换算成电压,再通过分压公式反推光敏电阻阻值,从而算出一个“光照强度”。但实际项目里几乎没人这么做——光敏电阻本身的阻值离散性太大,同型号不同个体的亮阻和暗阻可能差 20% 以上,反推出来的勒克斯数是数学上自洽的,物理上却没什么参考价值。
更务实的做法是直接拿 ADC 原始值做判断。比如实测室内开灯时读数是 1800,关灯拉窗帘时是 400,那就直接拿 1800 和 400 当参考,不做任何单位换算。这种做法把光敏电阻当成一个“可变分压器”,核心参考系来自实测标定,而不是器件手册。后面的调试流程也围绕实测展开。
建议把每组实测数值记录下来。做这类项目我习惯在工程里留一个calib.h:
#define LIGHT_ADC_MIN 400 /* 全黑或拉窗帘实测 */ #define LIGHT_ADC_MAX 1800 /* 开灯实测 */ #define PWM_MAX 999这两个最小值最大值今后既用于判断阈值,也用于把 ADC 值映射成线性亮度(例如用线性插值把 400~1800 映射到 0~999),映射公式就是最普通的比例缩放:
uint16_t map_light_to_duty(uint16_t adc) { if (adc >= LIGHT_ADC_MAX) return 0; if (adc <= LIGHT_ADC_MIN) return PWM_MAX; uint32_t range = LIGHT_ADC_MAX - LIGHT_ADC_MIN; uint32_t diff = LIGHT_ADC_MAX - adc; return (uint16_t)(diff * PWM_MAX / range); }注意diff * PWM_MAX可能超过 16 位,所以中间量要用 uint32_t 或者先约分。这类映射实现里最容易忽视的细节就是这个溢出问题,算错之后灯光会在某个区间一下子跳到最亮。
4.2 迟滞比较器:让灯光在阈值附近不抖动的核心
直接用单阈值判断的问题很明显:环境光在黄昏时是一条缓慢下坡的曲线,但室内经常有人走动、云层偶尔遮住太阳,这些扰动叠加在缓慢趋势上,会让 ADC 值在一个数值附近反复穿越阈值。每一次穿越都会让灯变亮又变暗,表现成了闪烁。
解决办法是迟滞比较,思路是“从亮到暗”和“从暗到亮”用不同的阈值,中间留一个死区。比如:
#define LIGHT_ON_THRESHOLD 1600 /* 光线降到这个值时才开灯 */ #define LIGHT_OFF_THRESHOLD 1900 /* 光线升到这个值以上才关灯 */ uint8_t light_on = 0; /* 当前灯的状态 */ uint16_t lv = read_light_value(); /* 已经滤波后的值 */ if (light_on == 0 && lv < LIGHT_ON_THRESHOLD) { light_on = 1; } else if (light_on == 1 && lv > LIGHT_OFF_THRESHOLD) { light_on = 0; }死区大小是 1900 - 1600 = 300 个 ADC 刻度,在这个范围内,灯保持上一个状态不动摇。死区设多大没有标准答案:室外光控可以只留 50~100 刻度,因为光照变化快、扰动少;室内灯光和窗外光线叠加的场所,建议至少留 200 刻度,否则窗帘晃动带起的阴影都会触发翻转。死区设太大也有代价——灯光切换会明显变迟钝,让人觉得“反应慢半拍”。
迟滞不仅解决抖动,还天然做了一次“状态记忆”。灯一旦点亮,要等环境光恢复到明显更亮的水平才关,这符合人对自动灯光“不轻易变化”的心理预期。
4.3 软件滤波:均值采样与中值滤波哪个更配光敏电阻
迟滞解决的是“穿阈值来回跳”,软件滤波解决的是“ADC 读数本身带毛刺”。光敏电阻的信号噪声来源主要是两类:一是工频和开关电源耦合,表现为周期性的小偏移;二是瞬态脉冲,比如附近继电器吸合、无线模块发射时捡到的尖峰。
均值滤波对第一类噪声有效,对第二类无效,因为一个尖峰会被平均进去,但会整体拉偏结果;中值滤波恰好相反,它对尖峰免疫,但对连续正态分布的小噪声效果普通。所以最稳的组合是“先均值,再中值”,或者反过来,看排序的代价。
给一个适合 F4 工程的中值滤波实现,窗口选 5,既能挡尖峰,排序代价又小:
#define FILTER_N 5 uint16_t median_filter(uint16_t *buf, uint8_t len) { uint8_t i, j; uint16_t tmp[FILTER_N]; for (i = 0; i < len; i++) tmp[i] = buf[i]; /* 简单选择排序,N=5 时足够快 */ for (i = 0; i < len - 1; i++) { for (j = i + 1; j < len; j++) { if (tmp[j] < tmp[i]) { uint16_t t = tmp[i]; tmp[i] = tmp[j]; tmp[j] = t; } } } return tmp[len / 2]; }用法是主循环里维护一个滚动数组,每次调用先把最新的 ADC 读进数组,再取中位值作为本次有效读数。数组长度和调用频率要匹配:如果每 10ms 采一次、窗口 5,那么中位值反映的是最近 50ms 的照度趋势;如果每 100ms 采一次、窗口 5,则反映 500ms。窗口越大越平滑,响应越慢。
我一般不直接让主循环一直跑read_light_value(),而是用一个软定时器,每 20ms 触发一次采样入队,每 100ms 做一次完整的“滤波 + 阈值判断 + 占空比更新”。这样做的原因是让采样节奏和判断节奏解耦,后面接低功耗也好实现,外设可以在大部分时间睡着。
5. 光敏电阻自动调光的排查避坑:5 个最常见翻车现场
5.1 现象:晚上灯一直在闪烁、亮度来回跳
这是迟滞缺失的典型症状。现象:天黑到某个临界点时,灯不是稳定点亮,而是在亮和灭之间来回摆动,或者两个亮度档位之间以几秒的周期反复切换。原因很明确:阈值只有一个点,环境光微小波动穿过该点就翻转逻辑,而光敏电阻的数据又天然带噪声。
解决思路分三步:先确认滤波是否已经把读数毛刺压住,用串口打原始值看波动幅度;再给开关逻辑加迟滞,死区至少覆盖实测噪声幅度的 5 倍以上;最后把阈值判断放到一个和 ADC 采样独立的、更低频的循环里,避免“噪声 + 频繁判断”互相放大。实际调试时,先定阈值,再故意用手在光敏电阻上方缓慢晃动,观察灯是否稳定,合格的系统在这种扰动下不应有输出变化。
5.2 现象:白天也亮灯、傍晚才亮灯的临界点完全调不准
这个翻车现场通常不是逻辑问题,而是量程设计问题。现象:白天大晴天灯也亮着;或者把阈值改到几乎接近 4095 才能压住,但一到阴天又过亮。如果 ADC 值在白天光线强时已经进入饱和区(比如接近 4095)或晚上全黑时无法拉开,阈值无论怎么设都不好使。
原因大概率出在固定电阻取值。分压的输出范围完全由 R_fixed 和光敏电阻阻值域的相对位置决定。解决:把电路板放在目标安装位置,用串口分别记录“最亮时刻”“最暗时刻”“临界时刻”三个读数,如果亮暗读数差小于几百个 LSB,就重选固定电阻,原则是让临界时刻的读数落在量程的 1/4~3/4 之间,左右留出裕量给天气变化。这个重选过程不用重新编译逻辑,只需改硬件电阻再重复一遍标定。
5.3 现象:ADC 读数跳变,灯光忽明忽暗,串口打印值乱跳
如果滤波和迟滞都做了,读数还在跳,那就要查硬件了。最频繁的原因有三个:ADC 参考电压不稳(VREF 或板载 3.3V 被其他负载拉出纹波);分压点到 ADC 引脚的飞线在天线或 DC-DC 附近捡噪声;ADC 输入阻抗和分压输出阻抗不匹配,导致引脚内部采样电容还没充满就被搬走了一次电荷。
对应解决办法:电源入口加 100uF 电解 + 0.1uF 陶瓷组合,ADC 引脚对地加 0.1uF;分压点到引脚的走线尽量短,避开大电流回路;把采样时间从 3 Cycles 提到 84 Cycles。第三点最容易忽略——F4 的 ADC 在低阻信号源上可以用短采样时间,但光敏电阻分压后的源阻抗通常在几 kΩ 到几十 kΩ 之间,采样时间必须放宽,否则每次采样结果几乎取决于上次电容残留状态,读数就变成连续随机值。
5.4 现象:灯光响应迟钝,进门半天才亮起来
这个场景和前面反过来,滤波阈值都正常,但体验很差。现象:人走到房间中央,灯要过 1~2 秒才有反应。原因通常是三个叠加:ADC 均值窗口过大、阈值判断循环间隔太长、光照变化本身通过 RC 低通电路被拉慢。
解决:先看滤波窗口和判断周期。如果均值做了 32 次、每次隔 100ms,光判断本身就已经 3 秒延迟;把判断周期缩短到 100ms 是优先动作。其次,光敏电阻并联的那个 0.1uF 电容如果增大到 10uF,RC 时间常数会显著变大,光照突变时电压要几百毫秒才能爬到终值。最后,检查供电——LED 的亮度变化如果是占空比直接跳变,人眼会感知“突然亮”,但如果你在代码里加了几百毫秒的渐变,那这种“迟钝”其实是渐变的代价,要区分“亮度变化慢”和“响应延迟大”这两个概念。
5.5 现象:同一套代码换了一批光敏电阻就不准
换了元器件批次灯就不对了。现象:阈值原来定得好好的,同一型号另一批光敏电阻装上后,傍晚亮灯时间明显偏移,甚至白天误亮。原因:光敏电阻不只是离散性大,它的亮阻暗阻随批次差异能超过 ±30%,而且是同一个方向上的系统性偏移。
解决:设计初期就把“标定参数”从代码里独立出来,放到一个可改写的校准区(EEPROM、Flash 扇区或至少单独的头文件),量产出厂时逐台标定。开发阶段最省事的做法是保留串口标定函数(第 6 章会写),在换批次后重新记两个临界点即可。如果不想逐台标定,就选用封装内自带校准的模拟环境光传感器,但那已经是另一种方案了,成本高于光敏电阻,取舍标准是量产的校准人工成本。
6. 让调光更像产品:串口校准、渐变亮度与低功耗
6.1 串口标定:把阈值调准的土办法
开发阶段我会直接在工程里留一组调试代码,通过串口输出原始 ADC 值、滤波后数值、当前阈值和灯状态:
printf("adc=%4u f=%4u on=%d pwm=%u\r\n", raw, filtered, light_on, pwm_duty);把这行放到 200ms 定时器里,在目标安装位置观察早晚读数,把实际值填回前面的calib.h。这套流程很土,但比任何传感器换算都可靠。
6.2 渐变亮度:用 PWM 斜坡消除突变
突然亮灭很刺眼,尤其夜里。给占空比变化加一个斜坡渐变,100ms 步进 20 级左右即可:
void fade_to(uint16_t target_duty, uint32_t step_ms) { uint16_t cur = (uint16_t)__HAL_TIM_GET_COMPARE(&htim3, TIM_CHANNEL_3); while (cur < target_duty) { cur++; __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, cur); HAL_Delay(step_ms); } while (cur > target_duty) { cur--; __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, cur); HAL_Delay(step_ms); } }步长取 1,起始占空比就是当前值,函数天然支持从任何亮度平滑过渡。
6.3 低功耗思路:别让 CPU 一直在采样
如果目标是电池供电,主循环里每 50ms 一次的采样会一直醒着耗电。更合理的做法是让 F4 在 Stop 模式下睡眠,用定时器定时唤醒,唤醒后读一次 ADC、更新 PWM,再睡回去。RTC 定时唤醒是现成方案,把 ADC 采样和 PWM 更新压缩到一次唤醒内完成,整机的平均功耗会降一个数量级,这是产品化时最值得做的一步。
这类小项目做多了之后,我的习惯是永远先把串口调试口留出来,标定、排查、验收全靠它。光敏电阻自动调光看起来简单,但真正的价值不在那三行 ADC 代码,而在你愿意为它做的校准和防抖设计。希望帮到你。
本文还有配套的精品资源,点击获取