简介:这是一套面向电子竞赛、嵌入式课程设计及新能源应用开发的STM32太阳能追日跟踪系统项目资料。方案以STM32单片机为核心,融合光敏传感器采集与日晷算法估算太阳位置,再驱动步进电机精确调节电池板角度,切实提升太阳能利用效率。压缩包共365个文件、84.26MB,包含C/H源码、Keil工程文件、电路原理图、设计文档、实物照片及演示视频等,源码支持二次开发,原理图辅助硬件复现,文档与视频则降低学习门槛,从资源组成到功能实现均有清晰呈现。已有6060人浏览学习。资料还涵盖硬件选型指南、调试技巧和自适应跟踪策略,教学价值与工程实用性兼备,适合希望从零掌握STM32编程、传感器数据融合及电机控制集成的学习者作为课程设计或竞赛参考。
1. 阳光追踪系统到底在追什么:一个让光伏板“长眼睛”的嵌入式方案
一块固定安装的太阳能板,上午和下午的发电量可能差出三成以上。原因不复杂——太阳在动,板子不动,光线斜射到电池板表面,有效接收面积就变小了。想要把这块损失捡回来,最直接的做法是让板子跟着太阳转,也就是“追日光”。这个基于STM32的太阳能追光系统,做的就是这件事:通过光敏传感器感知光照方向,用单片机判断太阳在哪边,再驱动电机把电池板转到正对太阳的角度。整机成本可以压得很低,核心器件就是一块STM32、几个光敏电阻和一个步进电机。
这个项目非常适合两类人:一是正在做嵌入式课设或竞赛作品的学生,二是想给自家离网光伏设备加一套低成本追光机构的开发者。它不涉及复杂的视觉识别,也不需要高精度云台,本质是一个“传感器采集→逻辑判断→电机执行”的经典单片机闭环,所有环节都能在开发板上复现。下文从硬件选型、核心代码、工程结构一路拆到避坑记录,最后给出二次开发的几个切入点。
2. 硬件方案与系统架构:从传感器到电机的完整链路
2.1 主控选型与系统框图
主控采用STM32F103系列,这是最常见也最稳妥的选择。它自带多路ADC、多路定时器PWM输出、充足的GPIO,价格便宜,开发资料铺天盖地,遇到问题几乎都能搜到解决方案。用F103C8T6这种小封装型号就够用,不需要上F407那样的大芯片——本项目的计算量很小,资源冗余太多反而浪费。
系统整体分四层:传感层负责采集光照,控制层负责逻辑判断,执行层负责角度调整,人机层负责状态显示和参数设置。传感层用两个光敏电阻分别朝向东西两侧,中间用一块挡光板隔开;控制层读取两路ADC值并做差值比较;执行层根据差值方向驱动步进电机正转或反转;人机层用一块1602或OLED屏显示当前角度和光照强度。
层与层之间的耦合要做到尽量松:传感器模块只负责输出模拟电压,不参与任何判断逻辑;电机驱动模块只接收“正转N步”“反转N步”这样的指令,不关心指令是怎么算出来的。这样后续想替换传感器型号、换更大扭矩的电机,都只需要改对应模块,不用动主流程。
2.2 光敏传感器与信号调理
光敏电阻是最经济的感光方案,但它有两个天生的毛病:响应慢、一致性差。响应慢在追光场景里不是大问题,因为太阳角速度很慢,传感器有几百毫秒的延迟完全无感;一致性差才是真正的坑——同型号的两个光敏电阻,放在同一光照下读数可能差出20%。这个问题要靠标定解决,具体做法在后面的工程结构章节里详细展开。
信号调理部分比较常规:光敏电阻和一个固定电阻串联分压,分压点接入STM32的ADC引脚。光照越强,光敏电阻阻值越小,分压点电压越高,ADC读数越大。固定电阻的取值需要按环境光照范围调整,室内弱光环境用10K左右,室外强光环境用1K到4.7K更合适。取值过大会导致弱光下分压变化不敏感,过小则强光下容易提前进入饱和区。
两个光敏电阻的安装位置决定了追踪精度。常见做法是在一块长方形小板上,左右各放一个光敏电阻,中间竖一块不透光的隔板。当阳光正对时,两个电阻被隔板均匀遮挡,读数接近;当阳光偏斜时,一侧被照亮、另一侧被遮挡,读数差立刻拉开。这个机械结构是整个系统里最不能省的部分,比代码里的比较算法更影响追踪效果。
2.3 执行机构与供电设计
执行机构有两种常见选择:步进电机和舵机。步进电机的优势是角度可精确控制、能自锁、力矩大,适合承载较大面积的电池板;舵机的优势是控制简单,一个PWM信号就能指定角度,但转角范围通常只有180度,且力矩偏小。如果不追求全程追踪,只在早上、正午、下午三个时段做粗对准,舵机方案也够用;想要连续追踪就老老实实上步进电机。
步进电机的驱动芯片选择ULN2003或A4988。ULN2003是达林顿管阵列,适合驱动28BYJ-48这种小五线四相步进电机,接线简单、成本极低,缺点是效率低、发热大;A4988支持细分驱动,能有效降低电机振动和噪声,但需要额外的使能和细分引脚配置。本项目作为入门级方案,用ULN2003加28BYJ-48就能跑通全流程。
供电方案需要区分模拟部分和数字部分。STM32和传感器用3.3V供电,电机驱动用5V或12V独立供电。千万不要让电机和单片机共用一组电源,电机启动瞬间的电流冲击会把单片机拉复位,这是新手最容易踩的坑。如果只能用单电源,至少要在电机驱动电源引脚旁边加大容量电解电容,并在单片机电源入口串一个低压差稳压器做隔离。
2.4 硬件清单汇总
| 器件 | 型号/规格 | 数量 | 作用 |
|---|---|---|---|
| 主控 | STM32F103C8T6 | 1 | 核心控制与逻辑判断 |
| 光敏电阻 | 5537或5528 | 2 | 感知光照方向 |
| 固定电阻 | 4.7K~10K | 2 | 与光敏电阻组成分压网络 |
| 步进电机 | 28BYJ-48 | 1 | 驱动电池板旋转 |
| 驱动板 | ULN2003 | 1 | 电机驱动与隔离 |
| 显示屏 | 0.96寸OLED(I2C) | 1 | 状态显示 |
| 稳压模块 | AMS1117-3.3 | 1 | 给MCU供电 |
| 电池板 | 5V/2W小面积板 | 1 | 被追踪对象 |
如果手头有现成的开发板,比如某STM32最小系统板,大部分器件都能直接插杜邦线连接。实际项目里不需要一开始就做成PCB,先用面包板验证算法和方向逻辑,跑通了再画板也不迟。
3. 核心模块拆解:光检测、比较算法与电机控制
3.1 ADC驱动与光照强度采集
STM32的ADC采集是整套系统的数据源头。使用HAL库的标准流程是:启用ADC外设时钟、配置GPIO为模拟输入、设置ADC通道和采样时间、启动转换并读取结果。下面这段代码演示了双通道采集的完整写法,两个光敏电阻分别接在PA0和PA1上。
/* main.c ADC初始化与双通道采集 */ ADC_HandleTypeDef hadc1; void MX_ADC1_Init(void) { hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; // 扫描模式,多通道依次转换 hadc1.Init.ContinuousConvMode = DISABLE; // 单次转换,读一次转一次 hadc1.Init.NbrOfConversion = 2; // 转换2个通道 hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发 // 配置两个通道:PA0对应Channel0,PA1对应Channel1 ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // 采样时间取大值,降低噪声 HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = 2; HAL_ADC_ConfigChannel(&hadc1, &sConfig); } uint16_t read_light_channel(uint32_t channel) { ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = channel; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 100); // 等待转换完成,超时100ms uint16_t value = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); return value; } /* 读左右两路光照强度 */ uint16_t light_left = read_light_channel(ADC_CHANNEL_0); uint16_t light_right = read_light_channel(ADC_CHANNEL_1);逻辑说明:ScanConvMode开启后,ADC会按Rank顺序依次转换配置好的所有通道,转换结果存放在数据寄存器里,每次HAL_ADC_GetValue取到的是最近一次转换的通道值。采样时间设置成239.5个周期,虽然转换速度变慢,但能显著降低内阻较大的光敏电阻带来的信号抖动。
参数说明:ADC_CHANNEL_0和ADC_CHANNEL_1分别对应PA0和PA1引脚,如果你把传感器接到了别的引脚,需要同步修改这两个宏以及GPIO初始化里的引脚号。HAL_ADC_PollForConversion的第二个参数是超时时间,在轮询模式下如果超过这个毫秒数还没转换完,函数会返回超时错误码,可以用来判断硬件接线是否正常。
3.2 双路光强比较与追光策略
追光逻辑的核心是一个差值比较:左右两路光强读数做差,差值超过一定阈值就认为太阳偏了,需要转动电池板;差值在阈值以内就认为当前角度已经对准,保持不动。这个阈值很重要,设得太小会导致电机频繁启停,设得太大又会在太阳缓慢移动时出现较长的“无响应空窗”。
/* 追光主逻辑 */ #define LIGHT_THRESHOLD 30 // 光强差阈值,ADC读数差大于此值才转动 #define MOTOR_STEP_DELAY 2 // 步进电机相邻两步之间的延时,单位ms void solar_tracking_tick(void) { uint16_t light_left = read_light_channel(ADC_CHANNEL_0); uint16_t light_right = read_light_channel(ADC_CHANNEL_1); int16_t diff = (int16_t)light_left - (int16_t)light_right; if (diff > LIGHT_THRESHOLD) { // 左边更亮,说明太阳在左侧,电机反转(向左转) motor_step_backward(MOTOR_STEP_DELAY); } else if (diff < -LIGHT_THRESHOLD) { // 右边更亮,说明太阳在右侧,电机正转(向右转) motor_step_forward(MOTOR_STEP_DELAY); } else { // 差值在阈值内,认为已对准,电机保持不动 motor_stop(); } }逻辑说明:diff用有符号int16_t保存,避免无符号数减法产生负数溢出。判断顺序是先处理“左侧更亮”,再处理“右侧更亮”,最后是“对准不动”。每次调用tracking_tick只转动一步,不追求一步到位,这样即使追过头了,下一次循环的反向判断也能纠回来。
参数说明:LIGHT_THRESHOLD的单位是ADC读数的原始值,不是电压也不是光照强度。STM32的ADC是12位分辨率,满量程4095,在室外强光下左右两路的读数差常常能到几百;在室内弱光下可能只有几十。如果你在室内测试,建议把这个值调到10到20之间,避免因为阈值过高导致电机完全不动作。
这种“一次一步”的策略比大步长对转更稳。由于电机转动后光敏电阻的位置跟着变,大步长很容易让系统在目标角度附近来回振荡,赶上风大的时候甚至会一直抖动。每次只转一个步距角,让系统有时间重新采样再判断,相当于一个简化版的闭环控制。
3.3 步进电机控制与PWM输出
28BYJ-48是四相五线步进电机,工作在8拍模式时步距角为5.625度,意味着转一圈需要64个半步。通过ULN2003驱动板,STM32的四个GPIO分别控制电机的四个相线圈。下面这段代码实现了半步模式下正转、反转和停止三个基础动作。
/* 电机驱动引脚定义 */ #define MOTOR_IN1 GPIO_PIN_4 // PA4 #define MOTOR_IN2 GPIO_PIN_5 // PA5 #define MOTOR_IN3 GPIO_PIN_6 // PA6 #define MOTOR_IN4 GPIO_PIN_7 // PA7 /* 8拍:A->AB->B->BC->C->CD->D->DA */ const uint8_t motor_phase_table[8] = { 0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09 }; void motor_write_phase(uint8_t phase_index) { uint8_t phase = motor_phase_table[phase_index & 0x07]; HAL_GPIO_WritePin(GPIOA, MOTOR_IN1, (phase & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, MOTOR_IN2, (phase & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, MOTOR_IN3, (phase & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, MOTOR_IN4, (phase & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); } void motor_step_forward(uint32_t delay_ms) { static uint8_t phase_index = 0; phase_index = (phase_index + 1) % 8; // 正转:相序递增 motor_write_phase(phase_index); HAL_Delay(delay_ms); } void motor_step_backward(uint32_t delay_ms) { static uint8_t phase_index = 0; phase_index = (phase_index + 7) % 8; // 反转:相序递减 motor_write_phase(phase_index); HAL_Delay(delay_ms); } void motor_stop(void) { HAL_GPIO_WritePin(GPIOA, MOTOR_IN1 | MOTOR_IN2 | MOTOR_IN3 | MOTOR_IN4, GPIO_PIN_RESET); }逻辑说明:motor_phase_table里保存的是8个拍子的线圈通断组合,每个值的高4位代表D相,低4位代表A相。正转时相序下标递增,反转时递减,就实现了电机的双向旋转。HAL_Delay(delay_ms)控制步与步之间的间隔,间隔越短转速越快,但超过电机响应上限就会丢步。
参数说明:MOTOR_STEP_DELAY设为2毫秒时,28BYJ-48在空载下能稳定转动;如果电池板面积较大、负载较重,需要把这个值调大到5甚至10毫秒,否则电机会因为力矩不足而丢步。丢步表现为电机嗡嗡响但不转,或者在某一拍突然卡住,这时候优先加大延时而不是换驱动芯片。
停止动作要把四个引脚全部拉低,让所有线圈断电。注意这不是自锁状态,外界稍微给点力就能转动电机;如果需要掉电自锁,可以改成让当前拍子的线圈保持通电,代价是功耗显著增加,对追光这种不需要频繁动作的场景来说得不偿失。
3.4 系统主流程梳理
主循环的结构很简单:读光强、做比较、转电机,然后刷新显示屏,最后延时防止循环过快导致电机反应跟不上。核心逻辑在一个无限循环里重复执行,不需要上操作系统,也不需要中断服务程序介入。
/* 主循环骨架 */ int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_OLED_Init(); motor_phase_reset(); // 上电时让电机先回到一个已知相序 OLED_ShowString(0, 0, "Sun Tracker Ready"); while (1) { solar_tracking_tick(); // 追光一步 update_display(); // 刷新OLED显示 HAL_Delay(20); // 控制循环频率在50Hz左右 } }逻辑说明:上电时把相序变量重置到第0拍,保证电机每次启动都从同一个状态开始,避免因上电瞬间相序错乱导致电机抖动。主循环里tracking_tick每执行一次只转一个步距角,配合20毫秒的延时,电机每秒最多转50步,也就是约28度,足够跟上太阳的移动速度。
这个主循环还有一个隐含的调度作用:所有模块都在同一个循环里顺序执行,任何模块的阻塞都会拖慢整个系统。比如OLED刷新如果用了较长时间的延时,追光响应就会变慢。实际调试时如果发现电机动作“迟钝”,先检查是不是显示刷新占据了太多时间。
4. 工程代码结构:从Keil工程到传感器标定
4.1 工程目录与模块划分
完整的工程采用标准HAL库分层结构,代码拆成驱动层和应用层两个层次。驱动层负责直接操作硬件寄存器或调用HAL库API,应用层负责业务逻辑。下面是一个推荐的目录组织方式。
SunTracker/ ├── Core/ │ ├── Inc/ // 头文件:main.h、gpio.h、adc.h等 │ └── Src/ // 启动文件与中断处理 ├── Drivers/ // STM32 HAL库源码,不建议修改 │ ├── CMSIS/ │ └── STM32F1xx_HAL_Driver/ ├── Hardware/ // 硬件驱动层 │ ├── motor.c // 步进电机驱动 │ ├── light_sensor.c // 光敏传感器采集 │ └── oled.c // 显示屏驱动 ├── App/ // 应用层 │ ├── tracker.c // 追光逻辑 │ └── display.c // 界面刷新 └── Project/ // Keil工程文件模块划分的原则是“硬件变化不波及应用层”。如果要把光敏电阻换成光敏二极管,只需要改light_sensor.c内部实现,对上层暴露的采集接口保持不变;如果要把步进电机换成舵机,则把motor.c的接口抽象成motor_set_angle(),上层不用知道底层的PWM占空比是怎么设置的。
驱动层的每个模块向外暴露尽量少的接口函数。比如motor.c只暴露motor_init()、motor_step_forward()、motor_step_backward()和motor_stop()四个函数,light_sensor.c只暴露light_sensor_init()和light_sensor_read_both()一个采集函数。这样做的直接好处是:应用层的代码可读性极强,追踪逻辑一眼就能看明白。
4.2 光敏传感器标定方法
标定是让双路光强比较可靠工作的关键步骤。两个光敏电阻的参数天生有离散性,如果不做补偿,即使阳光正对,两路ADC读数也可能明显不等,导致系统认为太阳“偏了”并开始误转。标定的目标是计算出一个补偿系数,让两侧读数在相同光照下保持一致。
/* 标定流程:在均匀光照下采集左右两路读数,计算补偿系数 */ #define CAL_SAMPLES 20 // 每个通道采样次数,取平均值降低随机噪声 typedef struct { uint16_t left_raw; // 左侧光敏ADC原始值 uint16_t right_raw; // 右侧光敏ADC原始值 float right_comp; // 右侧补偿系数 } light_cal_t; void light_sensor_calibrate(light_cal_t *cal) { uint32_t sum_left = 0, sum_right = 0; for (int i = 0; i < CAL_SAMPLES; i++) { sum_left += read_light_channel(ADC_CHANNEL_0); sum_right += read_light_channel(ADC_CHANNEL_1); HAL_Delay(5); } cal->left_raw = sum_left / CAL_SAMPLES; cal->right_raw = sum_right / CAL_SAMPLES; // 以左侧为基准,计算右侧补偿系数 cal->right_comp = (float)cal->left_raw / (float)cal->right_raw; } /* 使用补偿后的值做比较 */ uint16_t light_sensor_read_compensated(light_cal_t *cal, uint8_t is_left) { uint16_t raw = is_left ? read_light_channel(ADC_CHANNEL_0) : read_light_channel(ADC_CHANNEL_1); return is_left ? raw : (uint16_t)(raw * cal->right_comp); }逻辑说明:标定过程要求在均匀光照下进行,最好把设备放在室内散射光环境中,避免阳光直射造成两侧读数差异被误判为“方向偏差”。采样20次取平均,再去计算补偿系数,能有效滤除光敏电阻自身噪声带来的随机误差。
参数说明:CAL_SAMPLES决定了标定的稳定性和耗时。采样次数越多,平均值越稳定,但标定时间也随之拉长。20次配合5毫秒间隔,总耗时约100毫秒,对一次性上电标定来说完全无感。补偿系数的单位是“无量纲比例”,含义是右侧需要乘多大系数才能与左侧对齐。
这个标定只需要在设备首次上电或传感器位置变动后执行一次,不需要每次开机都做。把标定结果存到Flash里,二次上电直接读取,能省掉每次开机的等待时间。
4.3 配置项与参数调整
代码里需要预留几个可配置的参数,方便在不同使用场景下调整。这些参数建议全部集中到一个config.h头文件中,而不是散落在各个模块里。修改时只改一处,不用担心遗漏。
/* config.h 追光系统可配置参数 */ #define CFG_LIGHT_THRESHOLD 30 // 光强差阈值,越小越灵敏,越大越稳定 #define CFG_MOTOR_STEP_DELAY 5 // 步进延时ms,负载大时调大 #define CFG_TRACK_INTERVAL_MS 20 // 主循环周期ms #define CFG_CAL_SAMPLES 20 // 标定采样次数 #define CFG_SWITCH_HYSTERESIS 5 // 换向滞回量,防止在阈值边缘来回切换滞回量的作用容易被忽视。当差值恰好落在阈值附近时,光照的微小波动会让系统在“正转”和“反转”之间反复切换,电机表现为来回抖动。引入滞回量后,正转的判断阈值是+30,反转的判断阈值变成-25,中间有一段互不重叠的区域,只有差值越过对应边界才会切换方向。
我在实际项目里遇到过一个很微妙的问题:在某个角度下,左侧光敏电阻正好被电池板的支撑杆挡住一小块阴影,导致左右差值长期落在阈值边缘。系统每十几秒就来回抖一下,虽然幅度不大,但电机持续发热。后来加上滞回量并调大阈值之后,这个问题才彻底消失。如果你的追光系统也出现类似的周期性抖动,优先检查是不是传感器被局部遮挡,而不是盲目调参数。
5. 避坑与排查:追光系统最常见的五个坑
5.1 现象:追光方向反了
表现:阳光在左边,电池板却往右边转。原因十有八九是左右两个光敏电阻的接线顺序反了,或者分压电阻的位置接错,导致ADC通道0和通道1的实际物理位置与代码里的假设对不上。解决方法是把两个传感器对调接线,或者在代码里把ADC_CHANNEL_0和ADC_CHANNEL_1互换,两者任选其一,然后重新标定。注意这一步必须放在所有方向调试之前做,否则后面所有的角度记录都会建立在错误的方向基础上。
5.2 现象:电机原地抖动,转不到目标角度
表现:电机在某个位置来回小幅转动,电池板始终稳定不下来。常见原因有两个:一是LIGHT_THRESHOLD设得过大,导致系统在差值超过阈值时转过了一个步距角,紧接着下一轮判断差值又反向越过阈值,于是又转回来,形成了持续的“追过→回转”循环;二是机械传动部分存在间隙,电机反转时齿轮先吃掉间隙再带动电池板,产生一两个步距角的空程。解决方法是调大阈值并引入滞回量,同时在机械上尽量压紧传动齿轮或改用同步带。
5.3 现象:ADC读数跳动严重,追光逻辑时而正常时而乱转
表现:在固定光照下,ADC读数在几十甚至上百的范围内跳动,追光判断结果不稳定。原因通常是光敏电阻的分压电路缺少滤波电容,或者用杜邦线连接导致信号线上耦合了电机驱动产生的电磁干扰。解决方法是:在ADC采样引脚对地并一个0.1微法的陶瓷电容,采样代码里连续读三次取中值,并把电机驱动线和传感器信号线分开走线。还有一个细节容易被忽略——电机转动的瞬间,电池电压会跌落,如果ADC的参考电压也跟着抖,读数就会跟着跳,这时候优先检查供电稳定性。
5.4 现象:手动复位或断电重启后,电池板角度丢失
表现:每次重新上电,电池板都回到上次关机时的位置,如果这时太阳已经移动了一段距离,追光系统要从上次的角度慢慢追过去,需要几分钟才能重新对准。原因是没有做角度记忆功能,也没有上电回零动作。解决方法有两种:简单做法是加一个限位开关,上电时让电机反转直到触发限位,以此作为零度基准;进阶做法是用STM32的内部Flash存储当前角度,每次追光完成后把角度值写进去,下次上电直接读出来续转。前者适合入门阶段,后者适合做产品原型。
5.5 现象:OLED显示屏花屏,但追光功能正常
表现:屏幕偶尔出现乱码或显示异常,但电机的追光行为不受影响,很容易被误判成“屏幕坏了”。原因多半是OLED模块的I2C总线在电机启停瞬间受到干扰,或者SCL/SDA引脚上没接上拉电阻。解决方法是检查I2C引脚的外部上拉是否到位,并确认OLED的供电和MCU共地。如果问题仍然存在,把OLED改用SPI接口,抗干扰能力比I2C强不少。
6. 二次开发思路:把“能跑”变成“好用”
6.1 从单一追光到自动/手动双模式
现在的代码是开机就追光,没有人工介入的通道。实际使用中,很多时候需要手动控制电池板角度——比如调试时定位、测试时固定朝向来对比发电量。改造思路是增加一个按键输入,短按切换自动/手动模式,长按进入手动微调。手动模式下按一次按键让电机转一个固定步数,并把手动调整后的角度值作为“初始角”写入Flash,这样切换回自动模式后,追光逻辑会从当前手动位置继续工作,而不是突然跳到默认位置。
6.2 增加回程保护与光线遮挡判断
如果追光机构没有机械限位,电机转到极限位置后继续转,轻则丢步,重则把线拉断或损坏传动机构。在代码里加一个软件限位:设定最大角度值和最小角度值,每次追光动作前先判断下一步会不会超过边界,超过就停止并只在反向时允许动作。同时可以增加一个阴雨天保护——当两路ADC读数都很低时,说明光线强度不足以支撑有效的追光,此时让电机回到一个预设的“收藏”位置,避免长时间在低光照下做无意义的转动。
6.3 从角度控制到功率优化
现在系统只追光,不关注电池板实际发了多少电。一个很实用的升级是引入发电反馈:在电池板输出端接一个电流采样电阻或INA226电流传感器,实时读取发电功率。然后调整追光策略——不再单纯追求“左右光强相等”,而是让系统在上一个角度和当前角度分别测一次功率,保留功率更大的角度。这个思路本质上是一种爬山算法,比纯光敏传感器方案更抗干扰,因为光照传感器的读数会受云层、遮挡物影响,而真实发电功率才是最可信的指标。
6.4 数据上报与远程监控
给系统加一块ESP8266或HC-05蓝牙模块,把当前角度、两路光照强度、电机状态传给手机或上位机。改造的接入点不在通信本身,而在数据格式设计:建议传固定长度的结构体,包含帧头、角度值、光线值、状态标志位和校验字节,接收端按同样的协议解析。如果只想快速验证,直接用串口输出一行文本也行,但项目和产品之间就差这种“可解析协议”的距离。
写在这里我想到一个教训:自己第一次调通追光逻辑时,只验证了“阳光偏移后电机跟着转”,就以为大功告成了,结果在室外实测时发现早上的追踪起始角不对——因为系统上电后从零度角开始追,而太阳实际已经在东边某个角度了。后来加上电回零和初始角度估算,才算真正能用。从那以后,我每次做类似带执行机构的嵌入式项目,都会强制走一遍“上电状态恢复”的检查流程:假设系统掉电一小时再开机,它还知道自己在哪儿吗?希望这个思路对你改造自己的追光系统有帮助。
本文还有配套的精品资源,点击获取