STM32使用PWM驱动舵机

你的糖来啦
2024-06-23 / 0 评论 / 27 阅读 / 正在检测是否收录...

舵机的工作原理

以9g舵机为例有三根线:电源正极 电源负极 信号线(输入PWM)

我么可以通过PWM来控制舵机转动,其中改变PWM的占空比可以控制舵机角度,如下图所示:

舵机时序图

使用PWM控制舵机的要求

以9g舵机为例,要求控制PWM为50Hz 周期为20ms

代码部分

PWM.c

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
    //启动TIM GPIO
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    //需要使用GPIO来输出PWM PWM来自片上外设TIM 所以设置为复用推挽输出
    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
    //CK_PSC时钟频率 PSC分频 ARR自动重装
    //PWM频率 = CK_PSC/(PSC+1)/(ARR+1)
    //PWM占空比 = CCR/(ARR+1)
    //PWM分辨率 = 1/(ARR+1)
    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;    //CK_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);
}

//更改CCR占空比用的
void PWM_SetCompare2(uint16_t Compare)
{
    TIM_SetCompare2(TIM2,Compare);
}


控制舵机角度 servo.c

#include "stm32f10x.h"                  // Device header
#include "PWM.h"

void Servo_Init()
{
    PWM_Init();
}

void Servo_SetAngle(float Angle)
{
    PWM_SetCompare2(Angle / 180 *2000 + 500);
}

按钮部分 Key.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

/**
  * 函    数:按键初始化
  * 参    数:无
  * 返 回 值:无
  */
void Key_Init(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);        //开启GPIOB的时钟
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);                        //将PB1和PB11引脚初始化为上拉输入
}

/**
  * 函    数:按键获取键码
  * 参    数:无
  * 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
  * 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
  */
uint8_t Key_GetNum(void)
{
    uint8_t KeyNum = 0;        //定义变量,默认键码值为0
    
    if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)            //读PB1输入寄存器的状态,如果为0,则代表按键1按下
    {
        Delay_ms(20);                                            //延时消抖
        while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);    //等待按键松手
        Delay_ms(20);                                            //延时消抖
        KeyNum = 1;                                                //置键码为1
    }
    
    if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)            //读PB11输入寄存器的状态,如果为0,则代表按键2按下
    {
        Delay_ms(20);                                            //延时消抖
        while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);    //等待按键松手
        Delay_ms(20);                                            //延时消抖
        KeyNum = 2;                                                //置键码为2
    }
    
    return KeyNum;            //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "servo.h"

uint8_t Key_Num;
float Angle;

int main(void)
{
    /*模块初始化*/
    OLED_Init();        //OLED初始化
    Key_Init();
    Servo_Init();
    OLED_ShowString(1,1,"Angle:");
    while (1){
    
        Key_Num = Key_GetNum();
        if(Key_Num == 1)
        {
            Angle += 30;
            if(Angle > 180)
            {
                Angle = 0;
            }
        }
        Servo_SetAngle(Angle);
        OLED_ShowNum(1, 7, Angle, 3);
    }
}
0

评论 (0)

取消