
简介针对STM32与Arduino联合实现的太阳能追光系统这份资料提供了完整的工程源码与硬件配置适合嵌入式学习者、电子设计竞赛参赛者以及新能源应用开发者。项目以自供能为特色涵盖光照检测、角度追踪算法、步进电机驱动和电源管理可帮助读者掌握双控制器协同开发方法。压缩包共1011个文件约23.96MB以C代码文件561个和头文件245个为主还有编译链接配置、汇编启动文件、CubeMX配置以及少量文档清晰呈现工程结构。已有1813人学习资料中既包含STM32主控逻辑与Arduino辅助控制实现也涉及DSP数学库等移植内容便于对照原理图、代码和调试文件复现整套追光系统。1. 项目概述与核心思路1.1 这个追光系统到底在解决什么问题太阳能追光系统的本质是想办法让光伏板始终正对太阳。固定安装的太阳能板在阳光斜射时接收到的等效光照强度会按入射角余弦衰减早上和傍晚差距尤其明显。实测下来一套简单的单轴追光系统能把日发电量提升20%到40%双轴系统在高纬度或者季节变化明显的地区收益更大。这个项目的应用场景很典型课程设计、毕业设计、智能农业设备里的独立供电单元升级或者纯粹想玩玩单片机的人给自己阳台上的小光伏板做个自动支架。不管哪种情况核心需求都是同一个——用光敏传感器感知光源方向靠电机驱动机械结构去追踪太阳。我选择用STM32和Arduino双平台来聊这个项目是因为两条路线代表了两种完全不同的学习路径。Arduino屏蔽了大量底层寄存器操作你可以用十几行代码就看到系统转起来适合快速验证方案和入门理解STM32则要求你理解ADC配置、定时器PWM生成、GPIO复用这些底层机制踩坑更多但做完之后你对单片机的理解会上一个台阶。1.2 Arduino和STM32怎么选选型这事别听别人吹得看你的目标和手里有什么工具。对比维度Arduino以UNO/Nano为例STM32以F103C8T6为例核心芯片ATmega328P8位AVRCortex-M332位ARMADC精度10位0~102312位0~4095工作电压/逻辑电平5V3.3VPWM输出6路8位精度多路16位定时器PWM上手难度低库函数封装完善高需要配置底层寄存器或通过CubeMX生成初始化代码价格十几到几十元几元到十几元适合场景快速原型、学习、小负载多路传感器采集、精确控制、边缘计算迭代如果你之前没接触过嵌入式别犹豫先用Arduino把整个追光流程跑通。Arduino自带Servo库一行命令就能控制舵机转动光敏电阻分压电路连接也很直观。等你理解了“传感器读数→比较差值→调整执行器”这条闭环控制链路再切到STM32补齐底层知识会顺畅很多。反过来如果你是在做毕业设计或者需要同时控制多个传感器比如风速、温度、光照采集加双轴电机STM32的硬件资源会更宽裕。F103C8T6的千兆赫级主频、多个ADC通道、多路定时器可以让你把追光逻辑和远程监控逻辑通过FreeRTOS分任务管理不必像Arduino那样在一个loop里挤时间片。2. 硬件设计与选型2.1 传感器方案对比从光敏电阻到四象限传感器市面上做光照方向检测的方案有好几种针对追光这个词可选路径包括光敏电阻LDR固定电阻分压。优点是便宜、电路简单、调试直观缺点是温漂明显、响应慢、同一批次个体差异大。光电池小面积太阳能板直接做传感器。输出信号就是电压稍微放大后接入ADC即可因为太阳能板本身就是工作对象“以板测光”的思路很自然。光电二极管/光电三极管。响应速度快线性度比LDR好但需要接运放做电流转电压电路复杂度高一些。四象限光敏传感器QPD。用四块独立光敏面加上隔光罩通过四个象限的差值判断光源方位和俯仰角精度高但价格也高。对绝大多数学习项目和小型应用来说光敏电阻阴影遮挡法是最划算的选择。阴影遮挡法的原理特别容易理解拿一块长条形的不透光隔板隔板左右两侧各放一个光敏传感器。当光源正对隔板顶面时两个传感器接收到的光线强度一致当光源偏向某一侧时隔板会挡住那一侧的部分光线对应传感器的读数下降通过比较左右两侧读数的差值就能推算出光源偏转方向和偏转程度。我实际做下来隔板高度建议在25mm到35mm之间。隔板太低角度分辨率不够太高光线稍微偏一点传感器就完全进入阴影区导致控制系统在零点附近震荡。尺寸这个东西没有绝对标准装好后对着太阳实测一下用透明胶带暂时固定隔板把角度偏差从30度扫到30度记录两侧ADC数值曲线曲线越陡说明灵敏度越高但也要确保在扫描终点没有提前进入饱和区。2.2 双轴还是单轴先想清楚再动手单轴追光系统只控制一个方位角水平旋转安装简单一个舵机就能带起来。如果你的光伏板主要在低纬度地区或者只是做个演示单轴完全够用。太阳能板朝正南方向后单轴追踪太阳的东升西落一天下来发电量提升已经很可观。双轴系统在单轴基础上增加了俯仰控制仰角追踪可以跟随太阳高度角的变化。因为不同纬度和季节的太阳高度角差异很大双轴收益在春秋两季尤其明显。但代价是结构复杂程度翻倍需要两个舵机或步进电机、一套十字万向节支架、可能还要考虑重心平衡。我做第一版追光系统时贪求全直接上了双轴结果头两周的时间都花在机械结构和调试上。后来拆掉一个舵机先用单轴跑通闭环逻辑又把双轴加回来反而很快完成了。这也是我这次特别想分享的经验——先用单轴验证算法再扩展机械结构。双轴的传感器布局可以沿用单轴思路做一个十字形隔板架四个方位各放一个光敏电阻水平差值控制方位舵机垂直差值控制俯仰舵机。2.3 舵机怎么选不是越贵就越好市面上9g舵机如SG90、金属齿大扭矩舵机如MG996R是追光项目最常用的执行器。选型时关注三个参数第一是扭矩。太阳能板越大风阻和惯性力矩越大小舵机撑不住。单片小光伏板用SG90就够了桌面型太阳能板支架也勉强能用如果你做的是一块40cm乘40cm以上的板子或者放在室外风大的地方至少选MG996R并且做好支架重心平衡不给舵机额外增加静态负载。第二是工作电压。SG90标称4.8V到6VMG996R也是如此。Arduino的5V引脚可以直接带一路SG90但MG996R瞬间电流可能到1A以上直接从单片机供电口取电会把主控拉复位。正确做法是舵机独立供电电源负极和单片机共地即可。第三是PWM频率和脉宽范围。标准舵机的控制信号是50Hz20ms周期、0.5ms到2.5ms的脉宽对应0到180度。Arduino的Servo库会自动处理这些参数。STM32上如果你自己用定时器生成PWM就要把计数器周期设置为20ms再根据你想达到的角度范围调整脉宽上下限这里很容易犯的错误是默认用了1ms到2ms的标准导致舵机只能在90度到180度之间转动。3. 电路接线与参数计算3.1 光敏电阻分压电路计算光敏电阻和固定电阻串联中间抽头接到单片机的ADC引脚这个电路是追光系统的感知入口。选固定电阻阻值时考虑的是让ADC的输入电压范围在整个光照变化区间内都处于合适区间。以常用的LDR亮阻约800Ω到1.2kΩ暗阻约50kΩ到300kΩ为例电源接5V固定电阻R固定取10kΩ。可以用欧姆定律算一下ADC输入电压摆幅光照充足时LDR阻值约1kΩVout 5 × 10000 / (1000 10000) ≈ 4.55V光照微弱时LDR阻值约100kΩVout 5 × 10000 / (100000 10000) ≈ 0.45V这样ADC输入范围几乎覆盖了整个0到5V区间10位AD转换分辨率足够。如果你在测试中发现读数长期偏高或偏低把固定电阻往LDR阻值相近的方向调整。多说一个细节STM32的ADC输入电压范围是0到VREFF103系列VREF一般接3.3V不能直接把5V传感器的输出怼进去要用电阻分压或者确认光敏分压电路输出不超过3.3V。如果沿用刚才的分压参数在STM32系统中把光敏电阻串联电路的供电接到3.3V即可。3.2 完整接线总览这里给出一套Arduino UNO SG90舵机 两个光敏电阻的最小系统接线表照抄即可跑通第一版组件引脚连接目标光敏电阻左LDR一端A0光敏电阻左LDR另一端5V10kΩ固定电阻左一端A010kΩ固定电阻左另一端GND光敏电阻右LDR一端A1光敏电阻右LDR另一端5V10kΩ固定电阻右一端A110kΩ固定电阻右另一端GND舵机信号线9号引脚舵机电源线外部5V电源正极舵机地线外部电源GND外部电源GND—Arduino GND共地注意这里的共地非常关键。舵机电源和单片机电源如果各自独立不共地PWM信号参考电压和舵机电源参考电压不一致会导致舵机位置漂移甚至不动作。在面包板上接线时用一根黑色杜邦线把两个电源的负极连起来能避免很多诡异问题。3.3 电源选型和功耗预算整套系统的功耗大头在舵机不在单片机。SG90正常转动电流在100mA到250mA之间堵转时会飙升到500mA以上。Arduino UNO如果通过USB供电调节器输出电流能力有限不足以稳定驱动舵机。我的做法是舵机电源用一节18650锂电池加降压模块调到5V或者直接用一个手机充电宝转USB 5V供电充电宝的电流输出能力通常在1A以上带单个SG90绰绰有余。STM32的3.3V电源用AMS1117稳压芯片从5V降压或者直接用带3.3V输出的FTDI模块供电。整机功耗算下来大概在0.5W到1.5W之间白天依靠一个小型太阳能板搭配锂电池就能自维持。如果你考虑做成独立离网系统可以按这个功耗来配电池容量和太阳能板功率。光是这个系统用一块3W的太阳能板和2000mAh锂电池连续阴天也能撑两到三天待机加半天的追光运行。4. 软件实现与代码逻辑4.1 Arduino版最快跑通闭环控制Arduino版本的代码核心逻辑极其简单循环比较两个光敏电阻的ADC读数差值超过预设阈值时让舵机朝光线强的方向步进差值在阈值内时保持当前角度防止舵机在零点附近来回抖动。#include Servo.h #define LDR_LEFT A0 #define LDR_RIGHT A1 #define SERVO_PIN 9 #define DEADZONE 25 #define STEP_SIZE 2 Servo myservo; int angle 90; int readAverage(int pin, int times) { long sum 0; for (int i 0; i times; i) { sum analogRead(pin); delay(5); } return sum / times; } void setup() { myservo.attach(SERVO_PIN); myservo.write(angle); Serial.begin(9600); delay(500); } void loop() { int left readAverage(LDR_LEFT, 5); int right readAverage(LDR_RIGHT, 5); int diff left - right; if (diff DEADZONE) { angle min(angle STEP_SIZE, 180); } else if (diff -DEADZONE) { angle max(angle - STEP_SIZE, 0); } myservo.write(angle); Serial.print(L:); Serial.print(left); Serial.print( R:); Serial.print(right); Serial.print( ANG:); Serial.println(angle); delay(50); }这里有几个我调试了很久才想明白的细节。采样为什么要做多次平均因为环境光本身就有轻微波动加上ADC引脚的噪声单次采样可能偏离真实值几个到十几个LSB。我做了5次采样平均每两次采样之间留5ms延时效果稳定很多。你还可以试试去掉最大值和最小值后对中间值取平均也就是中值平均滤波应对偶发干扰效果更好。DEADZONE值为什么设成25这个值要结合你的分压电路参数来定。如果固定电阻太大或太小同样的光线变化反馈到ADC上的差值大小差异会非常大。我刚开始把死区设太小时舵机在正午的强光下不停小幅抖动角度变化肉眼可见设太大又会出现明显追踪滞后。25这个数值在我的电路配置下表现比较均衡你可以用串口监视器观察两个通道的稳定读数然后根据波动幅度调整这个参数。STEP_SIZE设成2度每次循环配合50ms延时舵机从90度转到接近极限位置大概需要几秒钟。这个响应速度对追光场景完全够用同时平稳的运动方式也保护了舵机齿轮避免频繁大角度动作造成磨损。4.2 STM32版本从CubeMX到Keil的完整流程STM32版本的开发路线和Arduino完全不一样。我推荐用STM32CubeMX生成初始化代码IDE选Keil MDK或者VSCode加EIDE插件都行。CubeMX里需要配置的核心外设RCCHSE外部高速晶振配置为Crystal/Ceramic ResonatorSYSDebug选项选Serial Wire否则烧录一次后第二次连接可能报找不到目标芯片ADC1开启两个通道。比如IN0和IN1采样时间拉长到239.5周期能显著降低输入阻抗带来的采样误差TIM1设置PWM输出通道1输出比较频率50Hz占空比初值对应90度角系统时钟从默认的8MHz倍频到64MHz或72MHz看你的芯片频率上限在main.c的主循环里写控制逻辑uint16_t ldr_left, ldr_right; int diff; int angle 900; // 对应90度这里假设脉宽步进值以1/20000周期计 while (1) { HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); ldr_left HAL_ADC_GetValue(hadc1); HAL_ADC_Start(hadc2); HAL_ADC_PollForConversion(hadc2, 10); ldr_right HAL_ADC_GetValue(hadc2); diff (int)ldr_left - (int)ldr_right; if (diff 80) { if (angle 1800) angle 20; } else if (diff -80) { if (angle 0) angle - 20; } __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, angle); HAL_Delay(50); }关于角度和占空比的关系需要解释一下。20ms周期的脉冲0到180度对应0.5ms到2.5ms脉宽也就是占空比0.025到0.125。当你使用16位定时器计数器计数范围从0到9999对应20ms周期时映射关系就是250比1度。如果你使用ARR19999那么就是500比1度。上面代码里的angle900其实是我用一个简化映射模拟出来的示例实际你需要按定时器配置修改这个比例。这块儿非常容易翻车。我在做STM32版本时前面几次配置PWM都出现舵机没有反应后来用逻辑分析仪看信号才发现脉宽根本没有对应到合适的电平持续时间。最靠谱的排查方法就是写一个测试程序固定把angle设成100然后用示波器或者逻辑分析仪看舵机信号脚的输出确认高电平时间在0.5ms到2.5ms的合理区间再接舵机测试。4.3 控制逻辑进阶不只有“差值比较”前面写的代码全是位置式差值比较。对于追光系统还可以做增量式控制或者PID控制。增量式控制很直观每次只根据上一轮差值和这一轮差值的变化量来调整避免连续向同一个方向大步进。这个和算法上的区别效果上类似给电机运动加了一个缓冲能明显减少超调。如果做过PID你会发现追光系统用P环节就够了。I环节用在光照稳定但传感器存在系统偏差的场景里D环节可以抑制天气突变引起的抖动。但是追光系统本身响应很慢环境光非线性和传感器自身差异带来的误差被死区机制抵消了一部分没必要把控制问题复杂化。我见过一个项目里写了完整的三环PID最后效果和简单差值比较差不多还白白增加了调试成本。5. 常见问题与排查技巧实录5.1 舵机抖动、不动、乱动这个是我被问得最多的问题也是所有舵机控制项目的通用难点。先说舵机完全不动。先测信号线有没有电平输出再看电源线电压是否正常。曾经有一次我把舵机信号线插反折腾了半小时还有一次是PWM通道的时钟没有使能输出引脚一直是低电平自然没反应。建议按信号线、电源线、地线、PWM配置四步挨个排查不要跳着查。再说舵机乱动或者抖动。多半是电源不够导致的。特别是STM32开发板板载稳压器输出能力有限舵机启动瞬间把电压拉低单片机复位表现出来就是舵机一顿一顿地抽搐。解决办法只有独立供电加共地。另外舵机信号线走线不要和电机电源线绑在一起如果万不得已要靠近也可以在信号线对地加一个1kΩ下拉电阻降低干扰影响。5.2 环境光干扰导致追踪错误室外的自然光环境和室内差异很大。在室内测试时头顶的LED灯光会在不同区域产生明显的明暗差导致追光系统被照明灯“带跑”而不是去追太阳。在室外云层飘过、地面反光、阴影边界都会造成某一个传感器的读数瞬间异常。处理环境光干扰我的经验是三层滤波软件上做多次采样取中值或平均去掉瞬时毛刺。控制上加大死区让系统对小扰动不敏感。硬件上用遮光罩只保证前方和侧前方的一定张角内自然光进入传感器抑制后方和下方杂散光的影响。如果你发现系统偶尔会“发疯”地转到一个错误方向大概率是某一侧传感器被一阵强反光闪了一下。应对思路是限制单次连续步进次数记录舵机连续朝同一方向动作的累计步数如果超过一个合理值比如从90度往同一方向移动超过60度就要判断一下对应方向的传感器读数是否真的持续偏高而不是偶尔一次。5.3 ADC读数不稳定Arduino的10位ADC在引脚悬空时会出现明显的随机波动接上传感器后有改善但也会有小幅跳变。要把“读数稳”落在实处两个办法一是把采样周期拉长用多个采样点进行一次计算这个方法我在上面的代码里已经体现了。二是在ADC引脚和GND之间并联一个0.1μF滤波电容可以把高频噪声滤掉不少。实测下来加了电容后同样条件下读数波动从上下20缩到上下3左右。STM32上还要注意ADC采样时间的配置。采样时间短源阻抗高时采样电容来不及充满导致读数偏低且不稳定。CubeMX里把采样时间设置成239.5周期是消除这个问题的常规操作。5.4 常见问题速查表现象可能原因解决方法舵机完全不动信号线没接触好或PWM未配置用示波器/逻辑分析仪测信号输出舵机抖动/复位供电不足独立电源供电保证足够电流务必共地角度一直偏向某一侧两个传感器静态读数不一致用软件校准初始化时记录差值的基准量正午阳光强烈时读数饱和分压电阻选配不合理增大固定电阻阻值重新计算分压追光速度过慢STEP_SIZE太小或循环延时太长适当增大步进角和减小延时云层遮挡后乱转环境光变化过快增大死区限制单次连续步进次数夜晚还在持续转动未设置无光照待机状态加一个全局光强阈值判断低于阈值不执行追光关于传感器的静态校准多说一句。就算你用同一个批次的光敏电阻装到左右两个位置后由于焊接、导线长度、安装角度的微小差异两个通道在相同光照下的ADC读数也可能差十几个数。正确做法是上电后用遮光罩盖住整体读取一段基础偏差值存在全局变量里后续每次比较都用当前读数减去这个基础偏差。5.5 夜间待机和快速回归最后再加一个小技巧。追光系统本身是日间运行设备但晚上一直在loop里空转不仅浪费电还会让舵机在低光环境下反复调整到极限角度。我习惯在程序里加一个环境光总强度判断。把左右两个传感器的ADC读数相加当总和低于某个设定阈值时说明环境已经变暗此时可以把舵机缓缓转回初始位置比如90度然后进入低功耗等待状态。第二天早上光照恢复后再重启追踪。这个逻辑用Arduino实现非常直接只需要在loop开头加一个条件判断即可。我第一版完全不考虑晚间状态舵机在晚上疯狂抖动第二天早上一看舵机齿轮已经磨损得有明显虚位了。加了这个简易待机逻辑之后问题彻底消失而且整机待机电流能降到只有1mA上下对电池系统的压力小很多。这个项目最让人上瘾的地方在于它把传感器信号处理、执行器驱动、机械结构设计、逻辑闭环完整串了一遍做完一遍以后你再去看那些更复杂的自动化设备思路基本都是一样的感知、决策、执行。下一步如果想升级可以把光敏传感器方案替换为太阳位置算法加对时模块做成能自动校准的系统或者接入ESP8266上报发电数据这些都是在目前这套硬件基础上的自然扩展。本文还有配套的精品资源点击获取