
简介这是一份基于STM32与2轴自由度舵机云台的太阳能追踪算法程序源码包适合嵌入式开发、智能硬件及能源管理方向的初学者和进阶开发者学习。程序通过天文算法计算太阳位置驱动舵机实现水平与垂直方向的双轴追踪整体设计兼顾效率和可扩展性。资料包共10个文件、大小41KB涵盖C语言源码、头文件、Dev-C项目配置、Windows下的Makefile构建脚本、编译生成的目标文件与可执行程序另附讲义文本便于快速理解工程结构。组件划分清晰可直接在Dev-C环境中编译运行也可参考源码思路迁移到其他控制平台。目前已有689人学习下载适合希望动手实践光伏追踪控制、学习STM32外设驱动或舵机控制逻辑的开发者可以直接获得一套完整可运行的最小实现并据此延伸优化追踪精度与响应速度。1. 太阳能追踪算法程序把天文计算先跑在PC上再交给STM32执行如果你手头有一块STM32、两个舵机搭的云台想让它追着太阳跑最稳妥的做法不是直接上硬件调PID而是先把太阳位置的天文算法在一个能单步调试的环境里跑通。这个trace_sun项目就是干这个的它用纯C写了太阳高度角和方位角的计算生成trace_sun.exe在Windows下直接出结果后续把同一套函数挪到STM32上配合2轴自由度舵机云台就能完成追踪。对刚接触太阳能追踪算法的人来说最大的门槛不是舵机控制而是“太阳到底在哪个方位”这个几何问题——这个包的价值正在于此它也解释了为什么文件里既有Dev-C工程配置又有Makefile.win这种PC端构建脚本。2. 太阳轨迹计算赤纬角、时角与高度角/方位角的三个关键公式2.1 太阳位置模型与代码文件的分工太阳能追踪算法的核心不是追踪而是预测。常见的实现思路分三种基于光敏电阻的闭环跟踪、基于日晷视日运动的开环计算、以及两者结合。这个项目走的是第二条路线——根据日期、时间和当地经纬度用天文公式算出太阳当前的高度角仰角和方位角。这样做的好处是响应快阴天也能动缺点是需要准确的时间源。看文件列表能看出分工trace_sun.c放算法主体main.c只做入口和参数初始化trace_sun.h暴露接口。这种拆法在嵌入式里很常见main.c将来可以整个替换成STM32的HAL初始化代码而算法文件基本不动。2.2 trace_sun.c 中核心计算函数的简化实现太阳位置计算的起点是儒略日Julian Day。从公历日期转换到儒略日再算太阳赤纬角与时角。下面这段是算法主干的C实现和trace_sun.c的整体逻辑保持一致#include math.h #include stdio.h // 由公历日期计算儒略日 double julian_day(int year, int month, int day) { int y year, m month; if (m 2) { y--; m 12; } int A y / 100; int B 2 - A A / 4; // 格里高利历修正项 return (int)(365.25 * y) (int)(30.6001 * (m 1)) day 1720994.5 B; } // 计算太阳赤纬角 delta单位弧度 double solar_declination(int year, int month, int day) { double jd julian_day(year, month, day); double n jd - 2451545.0; // 相对于 J2000.0 历元的天数 double L fmod(280.460 0.9856474 * n, 360.0); // 太阳平黄经 double g fmod(357.528 0.9856003 * n, 360.0); // 太阳平近点角 double lambda L 1.915 * sin(g * M_PI / 180.0) 0.020 * sin(2 * g * M_PI / 180.0); // 太阳视黄经 double eps 23.439 - 0.0000004 * n; // 黄赤交角 return asin(sin(eps * M_PI / 180.0) * sin(lambda * M_PI / 180.0)); }这段代码的每一步都有对应的天文意义。julian_day里对1月和2月做年份回退是为了把月份位置调整到天文惯用的基准B是公历1582年改革后引入的世纪修正。solar_declination则是用低精度太阳轨道根数算赤纬角误差大约在0.01度量级对舵机控制绰绰有余。2.3 经纬度与时间参数怎么传入有了赤纬角下一步算时角。时角的定义是当前太阳时与正午12点的角距离每小时对应15度。这里必须区分北京时间与当地时间程序里如果直接传东八区标准时间而不做经度修正会引入最多2小时的方位误差舵机云台会一直偏。// 计算太阳高度角与方位角 void sun_position(double lat, double lon, int year, int month, int day, int hour, int min, int sec, double *altitude, double *azimuth) { double delta solar_declination(year, month, day); double lat_rad lat * M_PI / 180.0; double jd julian_day(year, month, day); double t_utc hour min / 60.0 sec / 3600.0; // 地方太阳时 UTC 经度/15再修正均时差此处用简化值 double hour_angle (t_utc - 12.0 lon / 15.0) * 15.0 * M_PI / 180.0; double sin_alt sin(lat_rad) * sin(delta) cos(lat_rad) * cos(delta) * cos(hour_angle); double alt asin(sin_alt); double cos_az (sin(delta) - sin(lat_rad) * sin(alt)) / (cos(lat_rad) * cos(alt)); cos_az fmax(-1.0, fmin(1.0, cos_az)); // 防止浮点越界 *altitude alt * 180.0 / M_PI; *azimuth 180.0 - acos(cos_az) * 180.0 / M_PI; }注意hour_angle这个变量必须同时综合UTC时间、本地经度和“均时差”。均时差是地球公转椭圆轨道和自转轴倾角造成的表观太阳时与钟表时之差最大到正负16分钟在纯开环追踪里会表现为云台每天固定时刻的微小偏转。fmax/fmin限制了cos_az的取值范围防止在日出日落时刻出现NaN。之所以输出方位角用180.0 - acos(...)这种写法是为了让上午方位角为负、下午为正直接对上经纬云台的转向约定。3. 2轴自由度舵机云台从算法角到PWM脉冲映射3.1 双轴坐标系与追踪死角得到高度角和方位角之后要做的是把它们映射成云台上两个舵机的转角。典型的2轴自由度结构是底部的方位轴负责水平旋转范围0°~180°顶部的俯仰轴负责高低调整范围0°~90°。这里有个容易踩的坑当太阳高度角接近90°即太阳在头顶附近时方位角的计算值会剧烈变化如果直接驱动方位舵机云台会快速旋转。常见的处理策略 1. 把方位轴范围限制在120°范围内例如 -60°~60° 2. 当高度角 85° 时保持当前方位角不动只调俯仰 3. 在代码里对连续两次方位角之差做限幅超过5°/s则按5°/s执行。3.2 舵机角度到PWM占空比的换算STM32输出的PWM频率通常是50Hz对应20ms周期。舵机的0°对应0.5ms高电平90°对应1.5ms180°对应2.5ms。换算关系是线性的但不同舵机的中位偏置略有差异最好在云台组装时用示波器或者听声音校准一次。#define PWM_PERIOD 20000 // 20ms 20000us #define SERVO_MIN 500 // 0度对应的脉冲宽度us #define SERVO_MAX 2500 // 180度对应的脉冲宽度us int angle_to_pulse(float angle_deg) { if (angle_deg 0) angle_deg 0; if (angle_deg 180) angle_deg 180; return SERVO_MIN (int)(angle_deg / 180.0 * (SERVO_MAX - SERVO_MIN)); } // 云台执行把算法算出的高度角、方位角转为两个舵机目标脉宽 void gimbal_set_angles(float altitude, float azimuth) { int pulse_alt angle_to_pulse(altitude); // 俯仰舵机 int pulse_az angle_to_pulse(azimuth 90); // 方位舵机偏移90度作为中位 // 实际工程中这里调用 TIM_SetCompare1(TIMx, pulse_alt); // 调用 TIM_SetCompare2(TIMx, pulse_az); printf(altitude_pulse%dus azimuth_pulse%dus\n, pulse_alt, pulse_az); }这里的azimuth 90是把算法输出的方位角平移到舵机物理行程内。不同传动结构直连、齿轮传动会让负载端的角度和舵机输出轴角度不成1:1这个偏置量需要在现场标定。程序里保留gimbal_set_angles作为独立函数层正是为了方便替换底层的硬件接口。3.3 追踪速度与能耗的取舍太阳能追踪不是越快越好。频繁调整意味着舵机持续耗电和机械磨损在小型独立供电系统里可能得不偿失。追踪策略角度更新频率每日预估转动次数适合场景连续追踪每5秒一次数百次实验教学、追日精度要求高的光伏板定时追踪每15分钟一次约48次小型离网系统、电池供电阈值回滞角度偏差2°才动作约30~60次实际工程推荐兼顾精度与寿命4. Dev-C 环境构建Makefile.win 与开发调试闭环4.1 trace_sun.dev 与 Makefile.win 之间的关系trace_sun.dev是Dev-C的图形化工程文件你在IDE里点“编译运行”时Dev-C会自动调用底层的GCC工具链。而Makefile.win则是用命令行构建时的入口。两者最终调用的目标一致把trace_sun.c和main.c编译成目标文件再链接成trace_sun.exe。Dev-C 5.11之后的版本默认带TDM-GCC 4.9.2对C99的数学库函数支持良好不需要额外配置。排查devc构建异常时最常遇到的是math.h相关的链接错误表现为undefined reference to sin或cos。这是因为GCC在链接时需要显式引入数学库在Makefile中的LIBS字段追加-lm即可。4.2 用命令行复现整个构建过程我习惯在确认IDE能编译后再验证一遍命令行构建确保这个工程以后能嵌入CI流程或者用其他编辑器开发。Windows下的Dev-C自带mingw32-make.exe路径一般在C:\Dev-Cpp\bin下。cd trace_sun C:\Dev-Cpp\bin\mingw32-make.exe -f Makefile.win clean C:\Dev-Cpp\bin\mingw32-make.exe -f Makefile.win allclean目标负责删除中间产物trace_sun.o、main.o等all目标执行编译和链接。第一次执行时如果报mingw32-make: command not found要么是路径没加入PATH环境变量要么是误用了GNU Make。Dev-C打包的mingw32-make和MSYS的make在规则解析上基本兼容但为了避免奇怪的换行符问题保持使用配套版本更稳。-f参数显式指定Makefile.win避免Make自动查找Makefile时把Linux风格的构建脚本误用。4.3 构建产物与硬件移植节点编译出的trace_sun.exe是一个纯控制台程序运行后会按时间顺序打印一组太阳高度角、方位角以及对应的舵机脉宽。这正好构成一个闭环验证你可以在任意整点运行它人工比对当天太阳实际位置确认算法正确后再移植到硬件上。从PC迁移到STM32时建议保留原算法的函数签名void sun_position(double lat, double lon, int year, int month, int day, int hour, int min, int sec, double *altitude, double *azimuth);STM32工程里只需写一个RTC时间获取函数来替换main.c里的实参传入再实现gimbal_set_angles的底层PWM操作。算法部分因为不依赖任何标准库以外的头文件直接拷贝.c/.h即可注意不要动M_PI的定义——STM32的math.h在C99模式下默认包含它。5. 太阳能追踪算法的精度校正真太阳时与子夜跨日边界最后要处理的是精度问题。第2章的代码里我特意用简化值略过了均时差在工程上如果想再提升一档就需要引入真太阳时修正。均时差EoT的近似计算公式为// 均时差单位分钟用于把钟表时换算成真太阳时 double equation_of_time(int year, int month, int day) { double jd julian_day(year, month, day); double n jd - 2451545.0; double g fmod(357.528 0.9856003 * n, 360.0) * M_PI / 180.0; double L fmod(280.460 0.9856474 * n, 360.0) * M_PI / 180.0; double eot -0.171 * sin(g) - 0.1299 * sin(2 * L); return eot; // 单位分钟 }把这个函数的结果加到第2章的hour_angle之前也就是将t_utc修正为t_utc eot / 60.0即可消除表观太阳时与钟表时的偏差。在春分和秋分前后均时差为零在2月中旬和11月初偏差最大超过14分钟对聚焦型太阳能设备影响显著对普通平板光伏板影响很小。另一个容易忽略的边界是子夜跨日如果程序在23:59分启动且把日期、时间分开传入那么计算儒略日时不会自动进位到第二天。实际调试时跨越午夜追踪云台可能会突然反向旋转180度。稳妥的办法是使用UNIX时间戳作为唯一时间源在计算前先换算成年月日时分秒避免维护多个时间副本。升级到STM32后建议直接使用HAL_RTC_GetTime获取结构化时间再用HAL_RTC_GetDate读出日期两者组合成统一的时标再喂给sun_position函数。若对时间精度有更高要求给RTC加一个带温补的晶振或者在main循环里用GPS/NTP模块做周期校时即可。本文还有配套的精品资源点击获取