目录
1.1 本系统双轴跟踪系统机械结构设计
为了能够在俯仰和水平两个方位上跟踪太阳,获得更多的能量,本系统采用高度角-方位角式双轴跟踪结构,即在单轴式跟踪的结构上,再增加一个传动轴,实现同时满足二维空间上对太阳的全方位跟踪。这样就可以使太阳能电池板全天候保持与光线垂直,从而获最大辐射量,以提高发电效率。高度角-方位角式双轴跟踪结构如图1所示。

1.2 舵机的选型
本系统选用TS90A型号的模拟舵机,与一般的模拟舵机SG90相比,其采用了金属的马达齿、金属的电位器齿轮轴、以及有档点可限位的齿轮,使舵机转动更加顺滑,物理稳定性更强。如图2所示为TS90A舵机。

1.3 舵机的控制原理
舵机是一种根据输入PWM信号占空比来控制输出角度的装置。其控制原理如下:控制电路板接受来自信号线的控制信号,控制电机转动,电机带动一系列齿轮组,减速后传动至输出舵盘。舵机的输出轴和位置反馈电位计是相连的,舵盘转动的同时,带动位置反馈电位计,电位计将输出一个电压信号到控制电路板,进行反馈,然后控制电路板根据所在位置决定电机的转动方向和速度,从而达到目标停止。
输入PWM信号要求:周期为20ms(即50Hz),高电平宽度为0.5ms~2.5ms。
舵机工作控制信号时序图:
波形按照这个规定,准确地输出就能控制舵机了。
舵机接线:
棕色——GND
红色——5V
橙色——PWM
1.4 原理介绍&代码浅析
1.4.1 STM32定时器的输出比较简介
OC(Output Compare)输出比较
输出比较可以通过比较CNT(计数器)与CCR(捕获/比较器)的寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。
每个高级定时器和通用定时器都拥有4个输出比较通道。
高级定时器的前3个通道额外拥有死去生成和互补输出的功能。
输出比较模式及输出比较相关的内容请移步到
江协科技的STM32教程中 P15 [6-3]TIM输出比较https://www.bilibili.com/video/BV1th411z7sn?p=15&vd_source=b4d125df2ebf1ab26fbed06ba725ac39
1.4.2 PWM简介
PWM(Pulse Width Modulation)脉冲宽度调制
在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域。
PWM参数:
频率=1/Ts 占空比=TON/Ts 分辨率=占空比变化步距
PWM频率: Freq = CK_PSC/(PSC+1)/(ARR+1) 即 计数器的更新频率公式
PWM占空比: Duty = CCR/(ARR+1) CCR是自己设定的值
PWM分辨率: Reso = 1/(ARR+1)
1.4.3 高级定时器的输出比较通道
( 本部分为补充学习内容,本项目的制作并未使用到,可忽略此部分内容。)
下图为高级定时器的输出比较部分的原理框图:

该图比通用定时器主要多出蓝色框框的地方。高级定时器的输出比较通道常外接如图4所示的推挽电路。

推挽电路:通常是VCC与GND之间连接两个功率开关器件,常为MOS管,两个管之间为输出电路。
推挽电路的工作流程:在推挽电路中,若上管导通,下管断开,输出为高电平;若下管导通,上管断开,输出为低电平;若上下管都导通,那就是电源短路,这样是不允许的;若上下管都断开,那输出就是高阻态。
两个推挽电路可构成H桥电路,可以控制直流电机正反转; 三个推挽电路可用于驱动三相无刷电机。
为了避免换相导通时短暂出现上下管同时导通的问题,就有了 高级定时器中的死区生成电路(见图3),其会在上管关闭的时候,延迟一小段时间,再导通下管。
1.4.4 代码浅析
PSC、ARR、CCR参数的确定
因 PWM频率: Freq = CK_PSC/(PSC+1)/(ARR+1)
即 Freq = 72M/(PSC+1)/(ARR+1) = 1/20ms = 50Hz
为了方便计算设置 (PSC+1) = 72,则 (ARR+1) = 20k
因 PWM占空比: Duty = CCR/(ARR+1)
0.5ms = Duty * 20ms,则 0.5ms时Duty = 0.025
即 Duty = CCR/(ARR+1) = 0.025,已知(ARR+1) = 20k
则 0.5ms时 CCR = 500
同理可得 2.5ms时,CCR = 2500
参数确定后,代入如下代码的相应位置:
servo.c
#include "servo.h"
void Servo_Init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能定时器2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1 | GPIO_Pin_0; //PA1、0 作为复用推挽输出引脚,这样引脚的控制权会交给片上外设,定时器才可通过该引脚输出PWM波形
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_InternalClockConfig(TIM2); //初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period= 20000 - 1; //周期 ARR
TIM_TimeBaseStructure.TIM_Prescaler= 72 - 1; //预分频器 PSC
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //对时基单元的参数进行初始化
TIM_OCInitTypeDef TIM_OCInitStructure;
// TIM_OCStructInit(&TIM_OCInitStructure); //若不想把所有成员都列一遍去赋值,则可以调用此函数先赋一个初始值,然后再更改想改的值。
//这样做的好处是可避免:比如高级定时器想当通用定时器使用,则高级定时器才用到的成员变量就不需要赋值,万一哪一次将其恢复高级定时器用,则会出现有些成员变量少赋值,导致程序执行出现问题
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1; //输出比较模式:PWM模式1
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High; //输出比较极性:高极性,即有效电平是高电平
TIM_OCInitStructure.TIM_Pulse=0; //用于设置CCR寄存器的初始值
TIM_OC1Init(TIM2,&TIM_OCInitStructure); //输出比较通道1初始化
TIM_OC2Init(TIM2,&TIM_OCInitStructure); //输出比较通道2初始化,因为是同一个定时器的不同通道,会共用一个计数器,因此它们的频率需要相同,相位也是同步的,但占空比由各自的CCR决定
TIM_OC1PreloadConfig(TIM2,TIM_OCPreload_Enable);
TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable); //TIMx_CCRx寄存器能够在任何时候通过软件进行更新以控制波形,这个通过软件写入控制波形的值是立即生效还是在定时器发生下一次更新事件时被更新的,
//是由TIM_OCxPreloadConfig(TIMx, TIM_OCPreload_Enable);函数决定的。
//Enable:下一次更新事件时被更新;
//Disable:立即生效
TIM_Cmd(TIM2,ENABLE); //启动定时器
}
//0 500
//180 2500
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); //改变CCR的值,500-2500对应0°到180°
}
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2, Compare);
}
void Servo1_SetAngle(float Angle)
{
PWM_SetCompare1(Angle / 180 * 2000 + 500);
}
void Servo2_SetAngle(float Angle)
{
PWM_SetCompare2(Angle / 180 * 2000 + 500);
}
servo.h
#ifndef __SERVO_H
#define __SERVO_H
#include "sys.h"
void Servo_Init(void);
void Servo1_SetAngle(float Angle);
void Servo2_SetAngle(float Angle);
#endif
每一句代码后面都有注释解释清楚这一步是干嘛的。