首页 / 资讯中心 / 文章详情

基于STM32F103的太阳能逐日系统设计与实现

基于STM32F103的太阳能逐日系统设计与实现 ★ FEATURED ARTICLE
简介一套采用Cortex-M3内核的STM32F103主控芯片的太阳能逐日系统嵌入式软件工程面向嵌入式开发者、电子爱好者和新能源控制学习者覆盖太阳能板电压电流采集、最大功率点跟踪、储能电池充放电管理及系统保护等核心控制逻辑。rar压缩包内共181个文件整体约3.91MB文件以C源文件与头文件为主另有Keil工程配置文件、预编译的库文件以及axf和hex等可执行产物可支持直接打开查看、重新编译和烧录到开发板验证。目前已有51人学习或下载数据规模不算大但因资源包内同时包含源码层次结构、驱动实现和工程配置仍适合作为入门或课程设计的参考资料。工程采用STM32标准外设库组织驱动层包含模数转换、定时器、I2C、CAN、USART等外设接口完整展示从硬件抽象到应用层控制逻辑的编写方式拿到压缩包后可基于Keil直接打开工程结合源码理解逐日系统如何实现数据采集、最大功率点跟踪、电池保护等功能减少从零搭建的重复工作。1. 太阳能逐日系统为什么绕不开STM32F103从双轴跟踪到Cortex-M3选型做逐日系统的第一反应是把太阳能板固定住但光伏发电的收益大头在春秋两季的早晚时段——太阳斜射角度变化快固定倾角板在早晨和下午的发电量掉得厉害。双轴逐日系统就是让板子始终正对太阳实测能比固定支架多收 20% 到 35% 的发电量。这个项目用 STM32F103Cortex-M3 内核做主控是因为它在 8 位单片机和带浮点单元的 M4 之间卡得刚好算视日轨道、读光敏采样、输出两路 PWM 控制电机资源富余但不浪费开发资料也多到让人安心。这套东西适合刚把 32 位机玩熟的工程师也适合给光伏电站做低成本跟踪控制器——不依赖云端本地闭环就能干活。2. 先立住控制逻辑从光敏传感器到逐日算法的完整链路2.1 逐日系统两种常见方案光电跟踪与视日轨迹跟踪逐日跟踪的核心是怎么知道太阳在哪。业内最常见的是两条路光电闭环和视日轨迹开环。光电跟踪的做法是在一块隔光板上装三到四只光敏电阻或光电二极管板子朝向太阳时各路光照均衡一旦偏了某一路的电压明显高于另一边控制器拿这个偏差去驱动电机追平。优点是反馈直接阴天多云时也能大致朝最亮方向转缺点是会被反射光干扰比如旁边有水面或白色建筑时容易追着反光跑。视日轨迹跟踪则是用经纬度和时间算出太阳高度角与方位角按公式推电机要走的步数属于开环。优点是全天稳定不受环境光干扰缺点是安装时要标定水平角和正北方向而且云层遮光时它仍然按当前的轨迹走有时候会对着乌云发呆。我一般推荐混合策略晴天用视日轨迹做粗定位每 15 分钟校准一次光电传感器做细纠偏只在偏差超过回差阈值时才启动电机。这样既避免光电方案在正午附近反复震荡又补上了轨迹算法因安装倾斜产生的累计误差。2.2 三路光敏传感器怎么接、怎么读ADC采样与比较逻辑逐日控制器最少需要三路光敏信号东、西、南或上、下、左、右看你的支架自由度。如果只做水平方位角跟踪两路东西向就够了做双轴的话还需要仰角通道。我用的是贴片光敏电阻配合 10kΩ 分压电阻一端接 3.3V另一端串联电阻到 GND中间抽头进 STM32F103 的 ADC 引脚。选择 ADC 引脚时要注意STM32F103 的内部 ADC 是 12 位的转换结果存在 16 位寄存器里左对齐还是右对齐要心里有数。我用的是右对齐读取时直接ADC1-DR 0x0FFF就行。光敏电阻的阻值随光照变化不是线性的所以代码里不需要把 ADC 值换算成物理照度直接比较三路的原始数值即可。比较逻辑不能做成“哪边亮就往哪转”的死逻辑否则正午时东西两路的差值很小电机会频繁启停。我要在代码里加一个回差阈值比如亮度差大于 200 个 ADC 码才动作小于 50 就停机等待。这个阈值既避免电机抖动也省电。2.3 电机与限位步进电机或直流减速电机怎么选逐日系统的执行机构有两种主流选择。步进电机配合细分驱动器适合精密跟踪能精确控制每次转动的步数但价格高、功耗大户外长期工作还要考虑散热。直流减速电机便宜皮实用 H 桥配合 PWM 控制转速靠限位开关做终点保护是我在小型太阳能逐日板上用得最多的方案。选电机时主要看扭矩和自锁能力。太阳能板在风大时会对传动机构形成很大的反向力矩直流减速电机如果蜗轮蜗杆结构自带自锁最理想如果是普通齿轮箱控制器断电后板子可能被风吹转。我用的是 12V 直流减速电机带 1:60 减速箱配合两路继电器或 H 桥芯片驱动电流 1A 级别STM32F103 的 GPIO 直接推不动必须加三极管或者 MOS 管放大。电机供电和逻辑供电要分开我吃过一次亏直接共用一个 5V 电源电机一启动ADC 采集值就跳得一塌糊涂。限位开关是必须的不仅防止机械撞限位还能在断电重启时让控制器“归零”。软件里要记录当前角度但掉电后丢失所以每次上电先让电机慢速转到限位开关触发位置以此为原点。这个归零动作看起来简单布线和防水却要专门处理不然户外几个月后微动开关就锈死了。3. 用STM32F103跑通逐日控制最小系统、CubeMX工程与定时器PWM3.1 最小系统三板斧时钟、复位、供电5V转3.3V别踩坑STM32F103 最小系统看着简单但少了任何一个元件都会让板子“薛定谔地工作”。电源部分我直接用 AMS1117-3.3 把 5V 转 3.3V注意输入输出电容——输入 10uF 钽电容输出 22uF 陶瓷电容离芯片引脚越近越好。有人问为什么不用 1117 的典型 1uF 输出电容实测在电机启停的浪涌下容易复位大一点稳妥。复位电路用 10kΩ 上拉电阻加 0.1uF 电容到地即可NRST 引脚内部已有上拉但外部再补一个更抗干扰。BOOT0 必须接 10kΩ 下拉到 GNDBOOT1 可以悬空——很多第一次画板的人把 BOOT0 悬空烧录时正常复位后却偶尔进不了主程序。时钟电路是经常翻车的地方8MHz 晶振配两个 20pF 负载电容走线要尽量短且对称。用内部 HSI 时钟虽然能省两颗电容但串口波特率会偏PWM 频率也不准所以我坚持外部晶振。用 Keil MDK 做 ST-Link 调试时如果程序跑飞或进不了调试先量晶振引脚波形八成是电容虚焊或晶振体质量问题。5V 转 3.3V 这个环节还要注意 ADC 的参考电压。STM32F103 的 VDDA 和 VREF- 引脚如果不接内部会默认供电但电机开启时电压跌落会导致采样值漂移。我建议把 VDDA 单独用一个 LC 滤波接 3.3VVREF 接一个 100nF 电容到地这样 ADC 数值稳定得多。3.2 CubeMX定时器3生成PWM频率与占空比怎么设用 STM32CubeMX 配置定时器 3 生成两路 PWM 是最直接的路子。打开 CubeMX选芯片型号 STM32F103C8T6在 Pinout 视图里把定时器3 的通道1PA6和通道2PA7设为 PWM Generation CH1/CH2。注意定时器3 挂载在 APB1 总线上时钟树默认是 72MHz 主频三分频后得到 36MHz。常见做法是让 PWM 频率落在 500Hz 到 2kHz 之间太高了电机驱动芯片开关损耗大太低了大功率电机线圈会啸叫。计算方式很简单定时器时钟 36MHz分频系数 PSC 设为 72-171那么计数器时钟就是 500kHz自动重装载值 ARR 设为 500-1499这样 PWM 频率就是 500000/5001kHz。占空比通过 CCR 寄存器控制CCR250 时占空比 50%。在 CubeMX 里配置的参数就是这三个Prescaler71Counter Period499Pulse即初始 CCR设成 0。生成代码后在主循环里用__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, pwm_value)就能动态改占空比。注意定时器3 的通道引脚 PA6 和 PA7 默认复用为 SPI 功能CubeMX 里选择 PWM 后它会自动配置 GPIO 为复用推挽输出但在老版本固件库里需要手动调GPIO_PinAFConfig。如果你是从寄存器裸奔过来的留意别把 PA6 当普通 GPIO 操作了。3.3 串口打印调试裸机状态机与日志逐日系统调试时看不到电机转速也看不到 ADC 数值串口打印是唯一的“眼睛”。STM32F103 的 USART1 挂在 APB2 总线上72MHz配置波特率 115200、8 位数据、无校验、1 停止位。CubeMX 生成代码后重定向fputc到 USART1然后就能用 printf 打印日志。我通常会定义一个简单的状态机日志函数把当前工作状态、ADC 三路原始值、PWM 占空比、电机方向都打出来格式用逗号分隔方便直接用串口助手画波形或导入 Excel 分析。比如printf(state%d, adc_e%d, adc_w%d, adc_s%d, pwm%d, dir%d\r\n, state, adc_east, adc_west, adc_south, pwm_value, motor_dir);打印的时间点放在主循环末尾加一个软件延时——用DWT-CYCCNT或者定时器计时控制日志间隔 200ms 一条。别用HAL_Delay来测量精确时刻它本身会阻塞。串口打印的坑主要是重定向问题。GCC 环境下要自己实现_writeKeil 下要勾选 MicroLIB否则 printf 编译过了但串口没输出。我一般建议直接写一个UART_SendString绕开半主机模式这样不管哪个工具链都稳定。4. 代码骨架ADC采集、PID/比较算法与电机驱动4.1 ADC多通道采样与滑动滤波ADC 是逐日系统的感官入口但光敏电阻信号本身就带噪电机 PWM 一开电源上的纹波直接串进采样端。所以我不用单次采样而是用循环采样加滑动滤波。在 CubeMX 里把 ADC1 的 IN0、IN1、IN2 三个通道配置为 Scan Mode、Continuous Conversion Mode采样时间设成 55.5 周期最慢档这样抗噪性能最好。生成代码后启动 DMA 循环采集三个通道的数据到数组里#define ADC_BUF_SIZE 16 uint16_t adc_raw[3][ADC_BUF_SIZE]; // 三路各存16个历史值 HAL_ADC_Start_DMA(hadc1, (uint32_t *)dma_buf, 48); // 3通道x16次注意 DMA 缓冲长度必须等于通道数乘采样次数我在第一次跑的时候只给了 3结果 DMA 直接数组越界程序跑飞。DMA 传输完成后在回调里做一下数据处理取最新 16 次的中位值再平均不是简单的算术平均——中位值能干掉电机尖峰引起的离群点。代码里我维护一个环形缓冲只保留最近 16 个点。中位值平均滤波的好处是既能滤掉脉冲干扰又不会像纯滑动平均那样让阶跃响应变慢。太阳被云挡住时ADC 值变化是缓慢的电机刹车时瞬间跳变是剧烈的。这两类信号在中位值滤波器面前分得很清楚。4.2 偏差方向判断与回差控制逐日算法不复杂本质是比大小。水平轴东和西两路 ADC 值相减差值为正说明东边亮板子要往东转差值为负往西转。垂直轴南和北或上和下相减决定仰角电机方向。但直接比大小会引发严重的抖动问题。太阳在正午前后 1 个小时内东西方向照度差不大ADC 值在阈值附近反复横跳电机跟着频繁换向齿轮箱很快就磨损了。我的解决方法是引入回差控制滞回比较#define HYSTERESIS 200 // 回差阈值单位ADC码 #define DEAD_ZONE 50 // 死区避免微小波动触发 int diff_east_west adc_east - adc_west; if (diff_east_west HYSTERESIS last_dir ! EAST) { motor_drive(EAST, PWM_LOW); last_dir EAST; } else if (diff_east_west -HYSTERESIS last_dir ! WEST) { motor_drive(WEST, PWM_LOW); last_dir WEST; } else if (abs(diff_east_west) DEAD_ZONE motor_running) { motor_stop(); last_dir IDLE; }逻辑说明last_dir记录上一次动作方向防止电机已经在朝东转时diff 轻微回落就立刻停转这样能保证每次动作有最小持续时间。PWM_LOW是低速档比如占空比 30%电机慢转时惯性小到了目标位置更容易停准。为什么不用 PID因为被控对象是电机转速和角度系统是积分型对象纯 P 控制就会有振荡PID 参数调起来很玄学而在户外的光照条件下对象特性还会变。回差加死区虽然粗糙但户外现场可靠性远高于花哨的算法。如果真要做到精细可以用位置式 PD输出叠加到 PWM 占空比上但至少我做过三个项目简单回差控制就够了。4.3 电机驱动PWM输出与方向引脚直流减速电机的驱动我用的是双 H 桥芯片比如 L298N 或 TB6612STM32F103 的 PA6/PA7 输出 PWM 接到使能引脚方向用两个 GPIO 控制。TB6612 比 L298N 的压降小适合 12V 电机不需要额外散热片我推荐用它。驱动代码最好封装成一个函数内部做互斥逻辑防止两个方向同时使能void motor_control(uint8_t ch, uint8_t dir, uint8_t duty) { if (dir EAST) { HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, GPIO_PIN_RESET); } else if (dir WEST) { HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, GPIO_PIN_RESET); } __HAL_TIM_SET_COMPARE(htim3, ch, duty); }命令除了方向还有 PWM 占空比这个 duty 要加斜坡。我见过直接给 100% 占空比的板子电机轴瞬间大扭矩太阳能板的安装支架都会被扭变形。正确做法是 500ms 内从 0 慢慢加到目标占空比每 10ms 加 2 个点。电机停止时最好加 100ms 的反向刹车脉冲。直流电机惯性大直接停 PWM 后板子会因惯性冲过头撞击限位或产生跟踪过冲。刹车脉冲就是把方向反过来占空比 30%持续 100ms然后再发停止。5. 避坑/常见问题STM32F103逐日系统实战排查记录5.1 现象ADC值漂移太阳不动电机乱转户外实测时遇到过最诡异的问题板子朝东固定不动但日志里 ADC 值一直在缓慢爬升电机转一会在西边停过一会又往东追。用万用表量光敏电阻两端电压看起来正常但单片机的采样值却逐步走高。原因有两个一是板子发热后AMS1117 的输出电压会从 3.30V 漂到 3.28V而光敏电阻的分压参考是同一个 3.3V理论上不会变但 ADC 的参考电压 VREF 直接连的 VDDA纹波一大采样值就跟着晃。二是更隐蔽的电机驱动继电器的线圈电源和逻辑电源共地继电器吸合瞬间地线上出现几十毫伏的压降ADC 的 VSSA 与主地不同。解决把继电器供电换成单独 5V 隔离电源或者用固态继电器替代机械继电器VREF 加一级 RC 滤波R10ΩC10uF截止频率约 1.6kHz能把高频纹波滤掉大半。之后 ADC 漂移量从 ±80 码降到了 ±10 码。5.2 现象定时器3 PWM没输出万用表测PA6始终低电平CubeMX 生成的工程编译下载后逻辑应该没问题但 PA6 量不出波形。查了很久发现是 CubeMX 里虽然选了定时器3 的 PWM但定时器3 的时钟源没有使能——在 Clock Configuration 页面里APB1 定时器时钟被设置为默认的 36MHz但固件库初始化时并没有强制开定时器3 的时钟。更深层的原因是 CubeMX 版本差异新版本自动调用__HAL_RCC_TIM3_CLK_ENABLE()老版本则需要手动在MX_TIM3_Init()前面加上这一句。排查方法是读 RCC 寄存器或直接看生成的代码里有没有。我遇到过芯片本身 PA6 复用被另一个外设抢占了比如 SPI1 默认也映射在这两个引脚上CubeMX 里要确认没有其他外设同时占用。解决检查tim.c里的MX_TIM3_Init()是否被调用检查main.c里是否调用了HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1)。PWM 配置了但没启动输出就是低电平这个最容易漏。5.3 现象一上电电机就转到底连板子都没启动有一次给客户装了一套每次上电电机就像疯了一样往一个方向转直到限位开关咔嗒一声才停。当时怀疑是代码逻辑问题但单独给 STM32F103 供电时控制信号正常电机就是不动合上电机电源后立刻乱转。定位到是 GPIO 初始化之前的电平问题STM32F103 在复位期间所有 GPIO 是浮空输入状态而驱动板 IN1/IN2 引脚内部没有下拉电阻。此时IN1和IN2悬空H 桥芯片内部逻辑电平不确定有一半的概率会把电机驱动起来。STM32F103 程序加载完成还要几十毫秒这期间电机已经转了。解决硬件上在 IN1/IN2 对地各加一个 10kΩ 下拉电阻软件上在系统初始化第一件事也就是 GPIO 还没配置前先禁止所有外设时钟把用到的引脚尽早初始化为推挽输出并输出低电平。另外驱动板的 PWM 使能引脚也要加下拉防止复位期间 PWM 输出高电平。5.4 现象串口打印乱码全是“烫烫烫”一类串口助手收到的数据是乱码但示波器看波形和数据正常。这类问题多半是波特率不匹配而波特率不匹配的根源在时钟配置。STM32F103 的 USART1 挂在 APB2 上如果固件里把系统时钟从 72MHz 调成了 8MHz比如误用 HSICubeMX 生成的波特率参数是按 72MHz 算的实际 8MHz 下的 115200 就偏了 8 倍左右成了 14400串口当然乱。另外 STM32F103 的 PLL 倍频系数在system_stm32f10x.c里定义如果是从旧工程拷贝过来可能还是 9 倍频的旧代码与 CubeMX 生成的配置不一致。解决直接用 ST-Link 的 SWD 接口读出当前运行频率或者在代码里打印SystemCoreClock的数值看是不是 72000000。如果不对检查外部晶振是否是 8MHz以及RCC_CFGR的 PLLSRC 和 PLLMUL 设置。还有一个冷门坑串口工具选择的“解码方式”如果是十六进制显示文本就全变乱码。我调试逐日系统时习惯先发一个固定的hello字符串用示波器量波特率确认无误再谈别的。5.5 现象Keil MDK 工程打不开STM32F103 芯片选不到在 Keil MDK 里新建工程芯片列表找不到 STM32F103C8T6这是 pack 包没装好。STM32F103 属于 STM32F1 系列需要安装 Keil.STM32F1xx_DFP 这个 pack。老版本 Keil MDK4 不需要 pack但 MDK5 以后必须在线安装或手动导入。我推荐手动下载 pack 包在 Keil 的 Pack Installer 里选择 ST 目录找到 F1 系列最新版安装后重启 Keil。如果在线装不上去 ST 官网或 Keil 官网下离线包双击即可。装完后还要注意 MDK 版本太老的 MDK比如 5.20 之前对较新 pack 兼容性差建议至少 5.30 以上。调试时 ST-Link 识别不到芯片除了接线问题还要检查 Keil 的 Flash Download 选项里有没有选中正确的编程算法STM32F10x Medium-density Flash如果没有下载时报错 No Algorithm found。这是很多人第一次用 ST-Link 时被卡住的地方。6. 最后一块板子从样机到长期运行的几个实用技巧样机在台架上转了两周没问题真装到屋顶才发现问题多出在没人想的地方。第一件事是给 ADC 采样加看门狗。独立看门狗不太适合逐日系统——电机正常转动时单圈耗时可能超过喂狗周期设计不当会让系统误复位。我改用窗口看门狗或简单的软件看门狗在主循环里维护一个时间戳如果超过 5 秒没有成功执行完一次“采样-决策-驱动”循环就强制重启。这能有效防止程序卡死在某个电机驱动的死循环里。第二件事是断电记忆。STM32F103 没有 EEPROM但内部 Flash 可以用来存角度数据。我会在每次电机停止后把当前角度写进 Flash 的最后一个扇区上电时读回来配合限位开关归零。频繁擦写会损耗 Flash所以只在角度变化大于 1 度时才写不要每循环都写。这个习惯帮我躲过了无数次户外调试时断电丢角度的返工。第三件事是看 PWM 占空比对电机温度的影响。低速大扭矩时电机发热很厉害我调试时发现占空比 30% 持续 10 分钟电机外壳就能到 70 度。后来把低速档改成 20% 加间歇驱动——转 900ms 停 300ms发热降了一半跟踪精度没受影响。户外设备上可靠比速度更值钱。这套系统的验证方法其实很土拿一个手电筒模拟太阳从东往西慢慢移动看板子是否平滑跟随、有没有来回震荡。手电筒光强变化梯度大能暴露出 ADC 阈值设置不合理的毛病。晚上再用天空背景光测试确认不会出现“乱追亮处”的情况——周围环境光不均匀时传感器阵列偶尔会把路灯也当成太阳。我自己的教训是永远不要省限位开关电路里的那两颗电阻永远不要直接用板载 LED 来判断 PWM 输出——LED 在高频 PWM 下几乎看不出明暗必须用示波器或逻辑分析仪确认波形。逐日系统本质上是一套慢速位置的闭环调试它最需要的耐心是等等太阳也等电机停下来。希望这些踩坑和参数能帮你少走几趟弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站