1. CW32饭盒派开发板与光照检测入门
第一次拿到CW32饭盒派开发板时,这块蓝色的小板子确实让我想起了学生时代的便当盒。作为武汉芯源半导体推出的大学计划开发板,它搭载了CW32F030系列MCU,板载资源丰富得让人惊喜——从基本的GPIO、ADC到UART、SPI等外设一应俱全,最吸引我的是它预留的传感器接口,这让我立刻想到了做个光照检测实验。
光照强度检测在智能家居、农业大棚等场景中非常实用。比如根据室内光线自动调节灯光亮度,或是监测植物生长环境的光照条件。要实现这个功能,核心就是光敏电阻模块和ADC(模数转换器)的配合使用。光敏电阻的阻值会随光照变化,通过测量其分压值就能间接得到光照强度。
在正式开始前,我们需要准备以下材料:
- CW32饭盒派开发板
- 光敏电阻模块(建议选用GL5516型号)
- 杜邦线若干
- Keil MDK开发环境
- CW32配套固件库
特别提醒:光敏电阻模块有数字输出(DO)和模拟输出(AO)两种接口,我们要使用AO接口连接开发板的ADC引脚,这样才能获取连续的光照强度值,而不是简单的阈值判断。
2. 硬件连接与ADC配置
2.1 电路连接详解
将光敏电阻模块与开发板连接时,需要注意电压匹配问题。大多数光敏电阻模块的工作电压是3.3V-5V,而CW32的IO口电压是3.3V。为了获得最佳精度,建议采用3.3V供电:
光敏电阻模块 -> CW32开发板 VCC -> 3.3V GND -> GND AO -> PA4 (ADC通道4) DO -> 不连接这里选择PA4引脚是因为查看原理图发现它对应ADC1的通道4,且没有与其他功能复用。实际接线时,可以用万用表确认AO引脚输出的电压范围:在完全黑暗环境下约0.1V,强光照射时接近3.3V。
2.2 ADC参数配置要点
CW32的ADC是12位精度,最大采样率1Msps。对于光照检测这种变化较慢的信号,我们不需要太高采样率,但要注意基准电压的选择:
void ADC_Config(void) { ADC_InitTypeDef ADC_InitStruct = {0}; // 时钟配置(PCLK作为ADC时钟源) RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_ADC1, ENABLE); // ADC基本参数 ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b; ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // 单次转换 ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO; ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStruct.ADC_ScanDirection = ADC_ScanDirection_Upward; ADC_Init(ADC1, &ADC_InitStruct); // 通道配置(单通道) ADC_ChannelConfig(ADC1, ADC_Channel_4, ADC_SampleTime_55_5Cycles); // 校准(必须执行) ADC_GetCalibrationFactor(ADC1); ADC_Cmd(ADC1, ENABLE); while(ADC_GetFlagStatus(ADC1, ADC_FLAG_RDY) == RESET); }关键细节:ADC校准是保证精度的关键步骤,每次上电后都需要执行。实测发现跳过校准会导致读数偏差达5%以上。另外,采样时间设置为55.5个周期(约4us)能在速度和抗噪间取得平衡。
3. 光照强度算法实现
3.1 原始数据处理
ADC读取的是电压值,需要转换为光照强度百分比。这里有个常见误区——直接线性映射。实际上光敏电阻的响应曲线是非线性的,更准确的算法应该考虑对数关系:
#define LIGHT_MIN 800 // 完全黑暗时的ADC值 #define LIGHT_MAX 3500 // 强光照射时的ADC值 uint16_t GetLightIntensity(void) { uint32_t sum = 0; // 多次采样取平均 for(uint8_t i=0; i<10; i++){ ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); sum += ADC_GetConversionValue(ADC1); Delay_ms(10); } uint16_t adc_value = sum / 10; // 边界限制 if(adc_value < LIGHT_MIN) adc_value = LIGHT_MIN; if(adc_value > LIGHT_MAX) adc_value = LIGHT_MAX; // 非线性转换(对数关系) float ratio = (float)(adc_value - LIGHT_MIN) / (LIGHT_MAX - LIGHT_MIN); uint16_t intensity = 100 * (1 - exp(-2 * ratio)); // 指数补偿 return intensity; }这个算法中,LIGHT_MIN和LIGHT_MAX需要通过实验校准:用完全遮光和手机闪光灯直射分别测得两个极值。实测发现加入指数补偿后,在中等光照范围(20-80%)的读数更符合人眼感知。
3.2 动态阈值校准技巧
固定阈值在环境光线变化大的场景下效果不好。我改进为自动校准算法,每5分钟更新一次极值:
typedef struct { uint16_t min_val; uint16_t max_val; uint32_t last_calib_time; } LightCalib_t; void AutoCalibration(LightCalib_t *calib, uint16_t cur_val) { uint32_t now = GetSystemTick(); // 每5分钟重新校准 if(now - calib->last_calib_time > 300000) { calib->min_val = 4095; calib->max_val = 0; calib->last_calib_time = now; } // 动态更新极值 if(cur_val < calib->min_val) calib->min_val = cur_val; if(cur_val > calib->max_val) calib->max_val = cur_val; }实测技巧:在校准期间让用户手动遮挡和强光照射传感器各一次,可以快速获得准确的极值。这种方法在智能家居产品中很实用,能适应不同安装环境。
4. 系统优化与抗干扰设计
4.1 软件滤波方案对比
光照检测容易受到瞬时干扰(如阴影闪过),需要合适的滤波算法。我对比了三种常见方案:
| 滤波方式 | 实现复杂度 | 响应速度 | 抗干扰性 | 适用场景 |
|---|---|---|---|---|
| 算术平均 | 低 | 快 | 一般 | 光线稳定环境 |
| 滑动平均 | 中 | 中等 | 较好 | 多数常规场景 |
| 卡尔曼滤波 | 高 | 慢 | 优秀 | 高精度要求场合 |
最终选择滑动加权平均作为折中方案:
#define FILTER_DEPTH 5 uint16_t LightFilter(uint16_t new_val) { static uint16_t buf[FILTER_DEPTH] = {0}; static uint8_t index = 0; uint32_t sum = 0; buf[index++] = new_val; if(index >= FILTER_DEPTH) index = 0; // 加权系数:最新数据权重最高 for(uint8_t i=0; i<FILTER_DEPTH; i++){ sum += buf[i] * (i+1); } return sum / (FILTER_DEPTH*(FILTER_DEPTH+1)/2); }4.2 硬件抗干扰措施
在面包板测试时发现ADC读数偶尔跳变,通过以下改进显著提升稳定性:
- 在光敏电阻的AO引脚与GND之间添加0.1uF去耦电容
- 使用屏蔽线连接传感器,避免与电机等干扰源并行走线
- 在ADC输入引脚串联100Ω电阻并并联22pF电容形成低通滤波
改造后噪声幅度从±50LSB降低到±5LSB以内。对于要求更高的场合,可以考虑使用集成光照传感器(如BH1750),但光敏电阻方案成本优势明显(仅需0.5元)。
5. 应用案例:智能灯光控制
将光照检测与PWM调光结合,实现了一个完整的智能灯控demo。核心逻辑如下:
void LightControlTask(void) { static uint8_t last_intensity = 0; uint8_t current = GetLightIntensity(); // 变化超过5%才调整,避免频繁动作 if(abs(current - last_intensity) > 5) { uint16_t pwm_duty = 0; if(current < 30) { // 太暗→全亮 pwm_duty = 1000; } else if(current > 80) { // 太亮→关闭 pwm_duty = 0; } else { // 线性调节 pwm_duty = (80 - current) * 10; } TIM_SetCompare1(TIM1, pwm_duty); last_intensity = current; } }这个案例中,我特意加入了5%的滞回区间,避免在临界点附近频繁开关。实际部署时还可以增加以下优化:
- 结合时间判断,白天即使光线不足也不开灯
- 添加人体感应,无人时自动关闭
- 通过WiFi模块上报数据到云平台
调试中发现一个有趣现象:LED灯本身的光会影响光敏电阻读数。解决方法是在灯罩内壁贴黑色消光胶带,并将传感器安装在背光位置。