文章目录
前言
提示:本文主要用作在学习江科大自化协STM32入门教程后做的归纳总结笔记,旨在学习记录,如有侵权请联系作者
本案例利用输出占空比可调的PWM波形来驱动舵机,实现了一个通过按键控制舵机按照一定的角度与方向进行转动的功能。另外OLED上也显示了当前的角度,每按一下,角度变量就变一次,舵机也会随即跟随变化到的角度固定下来。
一、应用案例演示
TIM输出比较之PWM驱动舵机
二、电路接线图
舵机型号为SG90,它一共有三根线,其中棕色线接GND,红色线为电源线接5V正极,可以接在STLINK的5V输出引脚,黄色线为PWM信号线,接在PA1引脚,最后再在PB1接一个按键用来控制舵机。
三、应用案例代码
PWM.h文件:
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);
#endif
PWM.c文件:
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; //CCR
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
}
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2, Compare);
}
舵机头文件Servo.h:
#ifndef __SERVO_H
#define __SERVO_H
void Servo_Init(void);
void Servo_SetAngle(float Angle);
#endif
舵机实现文件Servo.c:
#include "stm32f10x.h" // Device header
#include "PWM.h"
void Servo_Init(void)
{
PWM_Init();
}
void Servo_SetAngle(float Angle)
{
PWM_SetCompare2(Angle / 180 * 2000 + 500);
}
主程序main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"
uint8_t KeyNum;
float Angle;
int main(void)
{
OLED_Init();
Servo_Init();
Key_Init();
OLED_ShowString(1, 1, "Angle:");
while (1)
{
KeyNum = Key_GetNum();
if (KeyNum == 1)
{
Angle += 30;
if (Angle > 180)
{
Angle = 0;
}
}
Servo_SetAngle(Angle);
OLED_ShowNum(1, 7, Angle, 3);
}
}
完整工程:TIM输出比较之PWM驱动舵机应用案例
四、应用案例分析
通过下图可以看到,驱动舵机的关键在于输出一个如下要求的PWM信号,也就是说脉冲的周期为20ms,占空比为0.5/20、1/20、1.5/20、2/20、2.5/20这样的PWM信号。
整体思路与LED呼吸灯那一章是一致的,在那一章里已经讲得非常详细了,这里就不再累述了,不懂的可以回过头去看一看。
文章传送门在此:TIM输出比较之PWM驱动LED呼吸灯应用案例
这里需要注意的是,本案例换了一个GPIO口,所以对应的定时器的通道也要更换。如下表所示,可以看到PA1对应的是TIM2的CH2通道。
4.1 初始化PWM模块
4.1.1 RCC开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
4.1.2 配置时基单元
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_InternalClockConfig(TIM2);//选择时基单元的时钟源,选择内部时钟。若不调用这个函数,系统上电后默认也是内部时钟。
计算公式如下:
PWM频率:Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比:Duty = CCR / (ARR + 1)
PWM分辨率:Reso = 1 / (ARR + 1)
换算公式:1 MHz = 1,000 KHz = 1,000,000 Hz
舵机要求的周期是20ms,也就是0.02s。那么频率就是f = 1 / T = 1 / 0.02 = 50Hz。
根据 Freq = CK_PSC / (PSC + 1) / (ARR + 1) ,即 50 = 72000000 / (PSC + 1) / (ARR + 1) 。
经过尝试,设置PSC + 1 = 72,ARR + 1 = 20000 刚好满足等式,也就是说PSC = 72 - 1 ,ARR = 20000 -1。
舵机要求高电平时间是0.5ms-2.5ms,也就是说占空比依次为0.5/20、1/20、1.5/20、2/20、2.5/20。
根据公式可以发现一些对应关系:Duty = CCR / (ARR + 1)
同时20k对应20ms,那CCR设置500,就是0.5ms。CCR设置2500,就是2.5ms。
所以按照这样的对应关系我们就可以推断出CCR的设置依次为500、1000、1500、2000、2500即可满足要求。
4.1.3 配置输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; //CCR
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
4.1.4 配置GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
4.1.5 运行控制
TIM_Cmd(TIM2, ENABLE);
4.2 PWM输出模块
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2, Compare);
}
4.3 舵机模块
设置舵机角度函数:
void Servo_SetAngle(float Angle)
{
PWM_SetCompare2(Angle / 180 * 2000 + 500);
}
4.4 主程序
在主程序while(1)循环里不断检测按键按下,检测到每按下一次,角度+30,加到180后返回0度,如此循环。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"
uint8_t KeyNum;
float Angle;
int main(void)
{
OLED_Init();
Servo_Init();
Key_Init();
OLED_ShowString(1, 1, "Angle:");
while (1)
{
KeyNum = Key_GetNum();
if (KeyNum == 1)
{
Angle += 30;
if (Angle > 180)
{
Angle = 0;
}
}
Servo_SetAngle(Angle);
OLED_ShowNum(1, 7, Angle, 3);
}
}