PWM驱动舵机&PWM驱动直流电机
PWM驱动舵机
- 接线图如下:

- 复制后修改PWM驱动LED呼吸灯的PWM.c文件
PWM.c代码:
#include "stm32f10x.h" // Device header
//初始化
void PWM_Init(void)
{
//开启TIM2的时钟,TIM1(高级时钟)对频率要求高,连接在APB2;TIM2、TIM3、TIM4(通用时钟)连接在APB1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*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);
//启用STM32自带的内部时钟,系统默认配置,可以不写
TIM_InternalClockConfig(TIM2);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//选择分频数,TIM_CKD_DIV1表示分频数为1,即不分频
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//选择计数器的计数模式,TIM_CounterMode_Up表示向上计数,即从0开始计数,到达预定值清零再次从零开始计数
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
/*设置自动重装器(ARR),因为预分频器设置为将7200个脉冲分频为1个脉冲,那么设置自动重装器为1000-1,
即1000HZ,7200*1000正好等于内部定时器频率72MHZ,此时一个脉冲信号72MHZ全部输入完毕,
正好过去1秒,自动重装器计时器就会清零*/
TIM_TimeBaseInitStructure.TIM_Period = 20000-1;
//设置预分频器(PSC),选择预分频数,7200-1表示对72MHZ进行7200次分频,即将7200个脉冲分频为1个脉冲
TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;
//重复计数器,高级计数器(TIM1)才有,本计数器TIM2用不到
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitTypeStructure;
/*
TIM_OCStructInit();将输出比较结构体函数赋一个默认值,因为在使用输出比较函数配置一个通用定时器(如TIM2),
TIM_OC1Init结构体函数中有很多参数用不到(这些用不到的参数在高级定时器配置中要用到),不赋一个默认值,会出
现一些莫名其妙的问题
*/
TIM_OCStructInit(&TIM_OCInitTypeStructure);
//输出比较模式
TIM_OCInitTypeStructure.TIM_OCMode = TIM_OCMode_PWM1;
//极性,TIM_OCPolarity_High表示有限电平为高电平
TIM_OCInitTypeStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitTypeStructure.TIM_OutputState = TIM_OutputState_Enable;
/*
设置CCR的值,要结合自动重装器(ARR)和预分频器(PSC)的值来使用,当ARR=100-1,PSC=720-1,TIM_Pulse=50,
可以输出一个频率是1000HZ,占空比为50%,分辨率为1%的PWM波形
*/
TIM_OCInitTypeStructure.TIM_Pulse = 0;
//初始化输出比较函数,由于使用的是PA1,它对应的输出比较口是CH2 (引脚定义图查得)
TIM_OC2Init(TIM2,&TIM_OCInitTypeStructure);
//启动定时器
TIM_Cmd(TIM2, ENABLE);
}
//独立设置CCR的值
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2,Compare);
}
与上一个工程PWM驱动LED呼吸灯的代码不同的是:
①根据接线图,舵机的PWM橙色线要接在GPIOA的PA1口,此口对应定时器通道是CH2(根据引脚定义图得到),所以要将初始化GPIO相应引脚口的代码修改为GPIO_Pin_1,将初试化输出比较函数修改为 CH2,即TIM_OC2Init(TIM2,&TIM_OCInitTypeStructure);
②将对应的设置CCR的函数修改名称为PWM_Compare2,将该函数里面调用的函数Tim_Compare1();修改为Tim_Compare2(); (Tim_Compare2();是官方函数,其中2对应的是CH2通道).
- 新建一个Servo.c文件和Servo.h文件
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);
}
值得注意的是:
①在Servo_Init();初始化函数中调用PWM.c文件中的PWM_Init();初始化函数来进行舵机函数的初始化
②在设置舵机角度的函数Servo_SetAngle(float Angle);调用了PWM.c的PWM_SetCompare2();来设置角度.
③为什么PWM_SetCompare2();的参数要写成Angle/180*2000 + 500?
答:驱动舵机的关键是将PWM波形变化为舵机所需要的波形,如图:
第一张图片显示舵机要求的周期是20ms,那么频率就是50hz,高电平宽度为0.5ms~2.5ms,也就是占空比的时间,最小占空比:0.5ms / 20ms = 2.5%,最大占空比:2.5ms / 20ms = 12.5%,根据公式:最小CCR:CCR_min = 2.5% × 20,000 = 500,最大CCR:CCR_max = 12.5% × 20,000 = 2,500。经过一系列变换可以变成我们熟知的0~180范围. ④因为PSC和ARR的值已经写死了,所以在使用PWM_SetCompare2();函数传参时也就在传输CCR的值,而CCR的值与占空比直接相关,所以舵机的角度是通过改变PWM波形的占空比来实现的. ![]()
main.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "Key.h"
#include "Servo.h"
uint8_t KeyNum;
float Angle = 0;
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
PWM_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);
}
}
PWM驱动直流电机
- 接线图如图:

- 复制后修改PWM驱动LED呼吸灯的PWM.c文件
PWM.c代码:
#include "stm32f10x.h" // Device header
//初始化
void PWM_Init(void)
{
//开启TIM2的时钟,TIM1(高级时钟)对频率要求高,连接在APB2;TIM2、TIM3、TIM4(通用时钟)连接在APB1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
//使用复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//启用STM32自带的内部时钟,系统默认配置,可以不写
TIM_InternalClockConfig(TIM2);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//选择分频数,TIM_CKD_DIV1表示分频数为1,即不分频
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//选择计数器的计数模式,TIM_CounterMode_Up表示向上计数,即从0开始计数,到达预定值清零再次从零开始计数
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
/*设置自动重装器(ARR),因为预分频器设置为将7200个脉冲分频为1个脉冲,那么设置自动重装器为1000-1,
即1000HZ,7200*1000正好等于内部定时器频率72MHZ,此时一个脉冲信号72MHZ全部输入完毕,
正好过去1秒,自动重装器计时器就会清零*/
TIM_TimeBaseInitStructure.TIM_Period = 100-1;
//设置预分频器(PSC),选择预分频数,7200-1表示对72MHZ进行7200次分频,即将7200个脉冲分频为1个脉冲
TIM_TimeBaseInitStructure.TIM_Prescaler = 36-1;
//重复计数器,高级计数器(TIM1)才有,本计数器TIM2用不到
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitTypeStructure;
/*
TIM_OCStructInit();将输出比较结构体函数赋一个默认值,因为在使用输出比较函数配置一个通用定时器(如TIM2),
TIM_OC1Init结构体函数中有很多参数用不到(这些用不到的参数在高级定时器配置中要用到),不赋一个默认值,会出
现一些莫名其妙的问题
*/
TIM_OCStructInit(&TIM_OCInitTypeStructure);
//输出比较模式
TIM_OCInitTypeStructure.TIM_OCMode = TIM_OCMode_PWM1;
//极性,TIM_OCPolarity_High表示有限电平为高电平
TIM_OCInitTypeStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitTypeStructure.TIM_OutputState = TIM_OutputState_Enable;
/*
设置CCR的值,要结合自动重装器(ARR)和预分频器(PSC)的值来使用,当ARR=100-1,PSC=720-1,TIM_Pulse=50,
可以输出一个频率是1000HZ,占空比为50%,分辨率为1%的PWM波形
*/
TIM_OCInitTypeStructure.TIM_Pulse = 0;
//初始化输出比较函数,由于使用的是PA0口,它对应的输出比较口是CH1(根据引脚定义图查得)
TIM_OC3Init(TIM2,&TIM_OCInitTypeStructure);
//启动定时器
TIM_Cmd(TIM2, ENABLE);
}
//独立设置CCR的值
void PWM_Compare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2,Compare);
}
值得注意的是:
①接线图红色芯片是TB6612电机驱动模块,VM要接在STLink的5v引脚,AO1和AO2在连接电机时不分正反,AIN1和AIN2是控制引脚.
②因为电机驱动模块的PWMA是接在PA2引脚上的,所以在初始化GPIO参数的引脚一栏中应该填上GPIO_Pin_2参数.又因为PA2对应的定时器通道为CH3,所以初始化输出比较函数应该为TIM_OC3Init(TIM2,&TIM_OCInitTypeStructure);
所以在设置CCR值的函数void PWM_Compare3(uint16_t Compare);应该调用官方函数TIM_SetCompare3(TIM2,Compare);
- 建立Motor.c和Motor.h文件:
Motor.c代码:
#include "stm32f10x.h" // Device header
#include "PWM.h"
void Motor_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
PWM_Init();
}
//设置电机转速
void Motor_SetSpeed(uint8_t Speed)
{
//正转
if(Speed >= 0)
{
GPIO_SetBits(GPIOA,GPIO_Pin_4);
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
PWM_Compare3(Speed);
}
else
{
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
GPIO_SetBits(GPIOA,GPIO_Pin_5);
//SetCompare3函数必须传入正数
PWM_Compare3(-Speed);
}
}
值得注意的是:
①控制电机正反转的是电机驱动模块的AIN1和AIN2,根据接线图连接到了STM32的PA4和PA5口,所以在初始化Motor函数中初始化GPIO的这两个引脚.
②为什么在设置电机转速的函数里Speed要以100为分界线?
答:占空比 = CCR / (ARR + 1),在PWM.c文件里ARR值为100-1,所以:当你设置 CCR = 50时,占空比 = 50 / 100 = 50%。当你设置 CCR = 100时,占空比 = 100 / 100 = 100%(全速运转).
main.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "Motor.h"
#include "Key.h"
int8_t Speed;
uint8_t KeyNum;
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Motor_Init();
OLED_ShowString(1,1,"Speed:");
while (1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
Speed += 20;
if(Speed >100)
{
Speed = -100;
}
}
Motor_SetSpeed(Speed);
OLED_ShowSignedNum(1,7,Speed,3);
}
}

979

被折叠的 条评论
为什么被折叠?



