基于STM32F103C8的双轴太阳能自动追光系统Proteus仿真
基于 STM32F103C8 的双轴太阳能自动追光系统 Proteus 仿真
前言
本文设计了一套基于 STM32F103C8 的双轴太阳能自动追光系统。系统使用东、南、西、北四个光敏电阻检测光照方向,通过两个步进电机分别控制水平轴和俯仰轴,使太阳能板朝向光照更强的方向。
系统具有自动追光和手动控制两种工作模式。自动模式根据四路光敏电阻的 ADC 数据计算东西方向、南北方向的光照差值;手动模式通过五个独立按键调整双轴角度。USART3 连接 Proteus 虚拟终端,支持模式切换、角度设置、状态查询和帮助命令。OLED 显示当前工作模式、四路光强、总光强和双轴角度。
开发环境如下:
| 项目 | 说明 |
|---|---|
| 主控芯片 | STM32F103C8 |
| 开发工具 | Keil MDK 5 |
| 编译器 | ARM Compiler 5 |
| 固件库 | STM32F10x 标准外设库 |
| 仿真软件 | Proteus |
| 显示模块 | 0.96 英寸 OLED |
| 执行机构 | 两个四相步进电机 |
| 电机驱动 | 两片 ULN2003A |
| 通信接口 | USART3,9600 bit/s |
完整工程资料免费获取
点赞 + 收藏本文,并在评论区留言“太阳能追光系统”,即可免费获取完整工程网盘链接。
工程资料包含:Keil 源码、Proteus 仿真文件、可直接加载的 HEX 文件和项目演示视频。
闲鱼店铺:大嘴花电子
专注单片机仿真与实物定制,支持 STM32、51 单片机、Arduino、Proteus 仿真等。
有需要仿真文件、代码程序、实物制作的朋友都可以咨询。
价格优惠,沟通好需求后定制方案,谢谢支持!
一、系统运行总图

图 1 太阳能双轴追光系统 Proteus 运行总图
整个系统由 STM32 最小系统、四路光敏检测、双轴步进电机、ULN2003A 驱动、五个控制按键、OLED 和虚拟终端组成。
运行截图中,OLED 已进入 AUTO 自动追光模式,显示总光强 T:4072,四路 ADC 数据分别为 E:1258、S:973、W:1008、N:1074,水平轴与俯仰轴角度显示为 M1:+064.00、M2:-064.00。虚拟终端同步输出 AUTO、MANU、HELP、M1 和 M2 命令说明,说明显示、串口和控制程序已经共同运行。
四个光敏电阻按照方位分布:
- E:东侧光敏电阻;
- S:南侧光敏电阻;
- W:西侧光敏电阻;
- N:北侧光敏电阻。
水平轴根据东、西光照差值转动,俯仰轴根据南、北光照差值转动。两个方向的光强差进入阈值范围后,电机停止,追光机构保持当前位置。
二、系统功能
本系统实现以下功能:
- 采集东、南、西、北四路光敏电阻电压;
- 每路 ADC 连续采样 8 次并计算平均值;
- 计算东西方向光强差和南北方向光强差;
- 自动控制水平轴向东或向西转动;
- 自动控制俯仰轴向南或向北转动;
- 总光强低于夜间阈值时停止自动追光;
- 按键切换自动模式和手动模式;
- 手动调节水平轴和俯仰轴角度;
- OLED 显示模式、光强和双轴角度;
- 虚拟终端发送命令控制模式和目标角度;
- 查询当前四路光强、差值和电机位置。
系统控制流程如下:
系统初始化
↓
读取四路光敏 ADC
↓
计算东西、南北光强差
↓
处理串口命令与模式按键
↓
自动模式:执行双轴追光
手动模式:处理四个角度按键
↓
每 200 ms 刷新 OLED
↓
返回四路光敏采集
三、STM32 最小系统与控制接口

图 2 STM32 最小系统、按键、虚拟终端和 OLED
主控采用 STM32F103C8。复位电路由 10 kΩ 上拉电阻、按键和电容组成,外部晶振连接 PD0、PD1。
3.1 引脚分配
| 功能 | STM32 引脚 |
|---|---|
| 东侧光敏 ADC | PA0 / ADC1_IN0 |
| 南侧光敏 ADC | PA1 / ADC1_IN1 |
| 西侧光敏 ADC | PA2 / ADC1_IN2 |
| 北侧光敏 ADC | PA3 / ADC1_IN3 |
| 模式切换按键 | PA5 |
| 水平轴角度增加 | PA6 |
| 水平轴角度减小 | PA7 |
| 俯仰轴角度增加 | PA11 |
| 俯仰轴角度减小 | PA12 |
| OLED SCL | PB8 |
| OLED SDA | PB9 |
| USART3 TX | PB10 |
| USART3 RX | PB11 |
| 水平轴步进电机 | PB12、PB13、PB14、PB15 |
| 俯仰轴步进电机 | PB0、PB1、PB2、PB3 |
3.2 按键功能
五个按键均配置为上拉输入,按下时引脚变为低电平。
| 按键 | 引脚 | 功能 |
|---|---|---|
| KEY1 | PA5 | 自动/手动模式切换 |
| KEY2 | PA6 | 手动模式下水平角度 +2.5° |
| KEY3 | PA7 | 手动模式下水平角度 -2.5° |
| KEY4 | PA11 | 手动模式下俯仰角度 +2.5° |
| KEY5 | PA12 | 手动模式下俯仰角度 -2.5° |
按键检测包含 20 ms 消抖,并在按键释放后返回一次有效按键事件:
uint8_t Key_Mode_Pressed(void)
{
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) == Bit_RESET)
{
Delay_ms(20);
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) == Bit_RESET)
{
while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) == Bit_RESET);
Delay_ms(20);
return 1;
}
}
return 0;
}
四、四路光敏电阻检测

图 3 东、南、西、北四路光敏电阻
四个光敏电阻分别连接 ADC1 的通道 0~3:
#define CH_EAST ADC_Channel_0
#define CH_SOUTH ADC_Channel_1
#define CH_WEST ADC_Channel_2
#define CH_NORTH ADC_Channel_3
ADC 使用单次软件触发方式,数据右对齐,采样时间为 239.5 个周期:
uint16_t MyADC_GetValue(uint8_t channel)
{
ADC_RegularChannelConfig(
ADC1,
channel,
1,
ADC_SampleTime_239Cycles5
);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1);
}
每一路 ADC 连续读取 8 次并计算平均值:
#define FILTER_COUNT 8
uint16_t Read_ADC_Avg(uint8_t ch)
{
uint32_t sum = 0;
uint8_t i;
for (i = 0; i < FILTER_COUNT; i++)
{
sum += MyADC_GetValue(ch);
}
return (uint16_t)(sum / FILTER_COUNT);
}
四路数据保存在 LightData_t 结构体中:
typedef struct
{
uint16_t east;
uint16_t south;
uint16_t west;
uint16_t north;
int16_t lr_err;
int16_t ud_err;
uint16_t total;
} LightData_t;
方向差值计算公式为:
东西差值 LR = East - West
南北差值 UD = South - North
总光强 Total = East + South + West + North
对应程序如下:
void Read_Light(LightData_t *d)
{
d->east = Read_ADC_Avg(CH_EAST);
d->south = Read_ADC_Avg(CH_SOUTH);
d->west = Read_ADC_Avg(CH_WEST);
d->north = Read_ADC_Avg(CH_NORTH);
d->lr_err = (int16_t)d->east - (int16_t)d->west;
d->ud_err = (int16_t)d->south - (int16_t)d->north;
d->total = d->east + d->south + d->west + d->north;
}
五、双轴自动追光算法
自动追光使用三个参数:
#define TRACK_THRESHOLD 80
#define STEP_INTERVAL_MS 60
#define NIGHT_THRESHOLD 400
| 参数 | 数值 | 作用 |
|---|---|---|
| 光强差阈值 | 80 | 差值绝对值超过 80 时驱动对应轴 |
| 自动控制周期 | 60 ms | 自动模式每 60 ms 执行一次判断 |
| 夜间总光强阈值 | 400 | 四路总光强低于 400 时停止追光 |
完整追光函数如下:
void Track_Sun(const LightData_t *d)
{
if (d->total < NIGHT_THRESHOLD)
{
return;
}
if (d->lr_err > TRACK_THRESHOLD)
{
Step_Once(&m1, 1, +1);
}
else if (d->lr_err < -TRACK_THRESHOLD)
{
Step_Once(&m1, 1, -1);
}
if (d->ud_err > TRACK_THRESHOLD)
{
Step_Once(&m3, 3, +1);
}
else if (d->ud_err < -TRACK_THRESHOLD)
{
Step_Once(&m3, 3, -1);
}
}
水平轴控制规则:
| 东西差值 | 水平轴动作 |
|---|---|
LR > 80 |
水平轴正向步进 |
LR < -80 |
水平轴反向步进 |
-80 ≤ LR ≤ 80 |
水平轴保持 |
俯仰轴控制规则:
| 南北差值 | 俯仰轴动作 |
|---|---|
UD > 80 |
俯仰轴正向步进 |
UD < -80 |
俯仰轴反向步进 |
-80 ≤ UD ≤ 80 |
俯仰轴保持 |
每次自动调整只执行一个步进。系统持续采样和修正,直到东西差值与南北差值均进入阈值范围。
六、双步进电机与 ULN2003A 驱动

图 4 水平轴、俯仰轴与双 ULN2003A 驱动

图 5 两片 ULN2003A 驱动接口
系统使用两个四相步进电机:
- 电机 M1 控制水平轴;
- 电机 M3 控制俯仰轴。
6.1 水平轴输出
水平轴使用 PB12~PB15:
static void Motor1_Output(uint8_t s)
{
switch (s & 0x03)
{
case 0: /* 1100 */ break;
case 1: /* 0110 */ break;
case 2: /* 0011 */ break;
case 3: /* 1001 */ break;
}
}
6.2 俯仰轴输出
俯仰轴使用 PB0~PB3,采用相同的四拍双相励磁顺序:
1100 → 0110 → 0011 → 1001
正转时相序索引加 1,反转时相序索引减 1:
void Step_Once(StepperState *m, uint8_t motor_id, int dir)
{
if (dir > 0)
{
m->seq = (m->seq + 1) & 0x03;
m->cur_steps++;
}
else
{
m->seq = (m->seq + 3) & 0x03;
m->cur_steps--;
}
Motor_Output(motor_id, m->seq);
Delay_ms(STEP_DELAY_MS);
}
电机参数如下:
#define STEPS_PER_REV 720L
#define STEP_DELAY_MS 5
每圈设为 720 步,因此:
每步角度 = 360° ÷ 720 = 0.5°
手动按键每次执行 5 步:
#define MANUAL_STEP_COUNT 5
因此每次按键改变角度:
5 × 0.5° = 2.5°
串口目标角度范围限制为 -180°~180°。
七、自动模式与手动模式
系统上电后默认进入自动模式:
uint8_t g_mode_auto = 1;
7.1 自动模式
自动模式每隔 60 ms 调用一次 Track_Sun():
if (g_mode_auto)
{
if (millis() - last_track >= STEP_INTERVAL_MS)
{
last_track = millis();
Track_Sun(&light);
}
}
7.2 手动模式
KEY1 切换模式。进入手动模式后,KEY2~KEY5 控制双轴:
if (Key_HAdd_Pressed())
{
Step_N(&m1, 1, +1, MANUAL_STEP_COUNT);
}
if (Key_HSub_Pressed())
{
Step_N(&m1, 1, -1, MANUAL_STEP_COUNT);
}
if (Key_VAdd_Pressed())
{
Step_N(&m3, 3, +1, MANUAL_STEP_COUNT);
}
if (Key_VSub_Pressed())
{
Step_N(&m3, 3, -1, MANUAL_STEP_COUNT);
}
按键控制与串口角度控制均使用同一组电机状态变量,因此 OLED 显示的角度与实际累计步数保持一致。
八、OLED 数据显示
OLED 使用 PB8、PB9 模拟 I2C:
| OLED 信号 | STM32 引脚 |
|---|---|
| SCL | PB8 |
| SDA | PB9 |
OLED 每 200 ms 刷新一次:
#define OLED_REFRESH_MS 200
显示内容如下:
| 显示区域 | 内容 |
|---|---|
| 第 1 行 | 工作模式、四路总光强 |
| 第 2 行 | 东侧、南侧 ADC |
| 第 3 行 | 西侧、北侧 ADC |
| 第 4 行 | 水平轴、俯仰轴角度 |
显示格式:
MODE:AUTO T:xxxx
E:xxxx S:xxxx
W:xxxx N:xxxx
M1:xxx.xx M2:xxx.xx
M1、M2 的角度由累计步数除以 2 得到:
float motor1_steps = (float)m1.cur_steps / 2.0f;
float motor3_steps = (float)m3.cur_steps / 2.0f;
九、虚拟终端与串口命令

图 6 按键、虚拟终端与 OLED

图 7 虚拟终端帮助信息与 OLED 启动提示
上电后,OLED 首先显示按键和串口操作提示;虚拟终端输出完整帮助菜单。该截图适合放在串口协议介绍之前,让读者先看到实际人机交互效果,再阅读命令格式。
系统使用 USART3:
| 串口信号 | STM32 引脚 |
|---|---|
| TX | PB10 |
| RX | PB11 |
串口参数:
波特率:9600 bit/s
数据位:8 位
停止位:1 位
校验位:无
硬件流控:无
串口接收使用 RXNE 中断,以回车或换行作为一条命令结束标志。
9.1 命令表
| 命令 | 功能 |
|---|---|
AUTO |
切换到自动追光模式 |
MANU |
切换到手动模式 |
Q |
查询当前系统状态 |
QUERY |
查询当前系统状态 |
HELP |
显示命令帮助 |
M145 |
水平轴移动到 45° |
M1-30 |
水平轴移动到 -30° |
M245 |
俯仰轴移动到 45° |
M2-15 |
俯仰轴移动到 -15° |
执行 M1 或 M2 角度命令后,系统自动切换到手动模式。
9.2 状态查询
发送 Q 或 QUERY 后,虚拟终端返回:
MODE=AUTO E=xxxx S=xxxx W=xxxx N=xxxx
TOT=xxxx LR=xxxx UD=xxxx M1=xxxx M2=xxxx
返回数据包含:
- 当前工作模式;
- 四路 ADC 数据;
- 四路总光强;
- 东西方向差值;
- 南北方向差值;
- 水平轴累计步数;
- 俯仰轴累计步数。
十、主程序设计
系统初始化顺序如下:
SysTick_MyInit();
MyADC_Init();
Motor_GPIO_Init();
Key_GPIO_Init();
OLED_Init();
USART3_MyInit();
Show_Boot_Tips();
主循环完成五项任务:
- 读取四路光敏数据;
- 解析 USART3 命令;
- 处理模式和目标角度;
- 执行自动追光或手动控制;
- 定时刷新 OLED。
核心结构如下:
while (1)
{
Read_Light(&light);
if (g_cmd_ready)
{
USART3_ProcessCommand();
}
/* 处理串口模式、角度和查询标志 */
if (Key_Mode_Pressed())
{
g_mode_auto = !g_mode_auto;
}
if (g_mode_auto)
{
/* 每 60 ms 自动追光 */
}
else
{
/* 处理四个手动角度按键 */
}
if (millis() - last_oled >= OLED_REFRESH_MS)
{
last_oled = millis();
OLED_ShowData(&light);
}
}
SysTick 每 1 ms 进入一次中断并累加 g_ms_ticks。自动追光和 OLED 刷新均使用时间差判断,不使用长时间主循环延时。
十一、工程目录
实际参与编译的应用文件如下:
program
├─ Hardware
│ ├─ adc.c
│ ├─ adc.h
│ ├─ motor.c
│ ├─ motor.h
│ ├─ OLED.c
│ ├─ OLED.h
│ ├─ OLED_Data.c
│ ├─ OLED_Data.h
│ ├─ usart.c
│ └─ usart.h
├─ User
│ ├─ main.c
│ ├─ stm32f10x_conf.h
│ ├─ stm32f10x_it.c
│ └─ stm32f10x_it.h
├─ Library
├─ Start
├─ Objects
│ └─ Project.hex
└─ Project.uvprojx
tracker.c、tracker.h 保存在 Hardware 目录中,但未加入当前 Keil 工程。当前运行的追光算法位于 User/main.c。
十二、Keil 编译与 Proteus 运行
12.1 Keil 编译
- 打开
program/Project.uvprojx; - 选择
Target 1; - 执行 Build;
- 生成
program/Objects/Project.hex。
工程编译结果:
0 Error(s), 0 Warning(s)
工程目录中保存的 Project.build_log.htm 记录了 μVision V5.40、ARM Compiler V5.05 update 1,并显示 0 Error(s), 0 Warning(s)。链接映射文件记录的资源占用为:
Total RO Size = 21676 Bytes(21.17 kB)
Total RW Size = 2776 Bytes( 2.71 kB)
Total ROM Size = 21760 Bytes(21.25 kB)

图 08:Keil Build Output 编译成功
12.2 Proteus 运行
- 打开项目中的 Proteus 仿真文件;
- 双击 STM32F103C8;
- Program File 选择
program/Objects/Project.hex; - 启动仿真;
- 调节四个 LDR 的光照强度;
- 观察水平轴和俯仰轴转动;
- 使用 KEY1 切换自动、手动模式;
- 使用 KEY2~KEY5 调节双轴角度;
- 打开虚拟终端发送控制命令;
- 观察 OLED 显示数据。

图 09:STM32 Program File 配置截图
十三、功能测试
13.1 东西方向追光
增大东侧光敏电阻的光照,使 East - West > 80,水平轴正向步进。
增大西侧光敏电阻的光照,使 East - West < -80,水平轴反向步进。
东西差值进入 -80~80 后,水平轴停止。
图 10:东西方向追光对比图
13.2 南北方向追光
增大南侧光敏电阻的光照,使 South - North > 80,俯仰轴正向步进。
增大北侧光敏电阻的光照,使 South - North < -80,俯仰轴反向步进。
南北差值进入 -80~80 后,俯仰轴停止。
图 11:南北方向追光对比图
13.3 手动角度控制
切换到手动模式后:
- KEY2:水平轴 +2.5°;
- KEY3:水平轴 -2.5°;
- KEY4:俯仰轴 +2.5°;
- KEY5:俯仰轴 -2.5°。

图 12:手动控制角度
13.4 串口角度控制
虚拟终端发送:
M145
水平轴移动到 45°。

图 13:串口角度命令执行
十四、总结
本设计完成了基于 STM32F103C8 的双轴太阳能自动追光系统。
系统使用四路 ADC 获取东、南、西、北光照数据,通过东西差值和南北差值控制两个步进电机,实现水平轴和俯仰轴自动追光。五个按键完成模式切换和手动角度调节,USART3 虚拟终端完成远程命令控制与状态查询,OLED 实时显示系统运行数据。
工程综合使用了 STM32 ADC、GPIO、SysTick、USART 中断、软件 I2C 和步进电机相序控制,形成了光照采集、方向判断、双轴执行、人机交互和数据显示的完整控制系统。
更多推荐

所有评论(0)