ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《DMG474电机控制专题教程V1.1 》第14章 舵机控制

《DMG474电机控制专题教程V1.1 》第14章 舵机控制

原创 嵌入式/内核开发 作者:正点原子 时间:2026-04-17 10:05:50 0 删除 编辑

第14章 舵机控制


       本章我们的学习目的主要是了解舵机的驱动原理以及结构组成,并使用STM32将舵机驱动起来。 本章分为如下几个小节:

       14.1 舵机的简介以及分类

       14.2 舵机的结构

       14.3 舵机的工作原理以及控制原理

       14.4 舵机的参数介绍

       14.5 舵机的控制实现


        14.1 舵机的简介以及分类

       舵机(Servo)实际上可以看作一个伺服电机,其拥有驱动电路、控制电路,只不过一般舵机工作在一定的角度范围内,最常见的就是180度的舵机,它适用于那些需要角度不断变化并可以保持的控制系统中。目前在高 档遥控玩具,如飞机、潜艇模型、遥控机器人中已经得到了普遍应用。下图为常见舵机的实物图:


图14.1.1 SG90舵机


       舵机按照控制电路分类,可以分为数字舵机和模拟舵机,它们的机械结构是完全一模一样的,其最大的区别体现在控制电路上,数字舵机的伺服控制器采用了数字电路(拥有MCU和晶振),而模拟舵机的控制器采用的是模拟电路。它们最主要的区别在于:模拟舵机需要给它不停的发送PWM信号,才能让它保持在规定的位置或者让它按照某个速度转动,数字舵机则只需要发送一次PWM信号就能保持在规定的某个位置。

       按照旋转角度可分为180°与360°的舵机,180度舵机只能在0度到180度(±90度)之间运动;360°舵机转动的方式和普通的电机类似,可以连续的转动,不过我们只可以控制它转动的方向和速度,不能调节转动角度。本章我们主要讲解180°的模拟舵机。


        14.2 舵机的结构

       舵机的结构主要由以下几个部分组成:直流电机、控制电路、角度传感器、输出轴以及减速齿轮组。

       角度传感器:用来实现位置反馈的,它会将其旋转后产生的电阻变化信号发送回控制电路,从而监控当前轴旋转的角度;

       控制电路:用来驱动电机和接收PWM控制信号以及电位器反馈信号;

       减速齿轮组:用来增大直流电机的扭矩。齿轮的材质一般有塑料齿轮、混合材料齿轮和金属齿轮。下面我们来看下舵机结构实物图,如下图所示:


图14.2.1 舵机的结构图


        14.3 舵机的工作原理以及控制原理

       1.舵机的工作原理:

       ① 首先由控制电路板接收到信号线的控制信号(一般为PWM信号),进而控制直流电机转动;

       ② 电机的输出轴和减速齿轮组的轴是相连的,所以只要电机一转动就会带动减速齿轮组转动;

       ③ 减速齿轮组又和角度传感器连接在一起,所以同时也会带动角度传感器转动;

       ④ 接着角度传感器会将当前的电压信号反馈到控制电路板,控制电路板会根据反馈的比例电压调节舵机的转动位置,当达到目标位置后停止。可以简单理解舵机内部其实就是一个闭环控制系统。工作流程如下图所示:


图14.3.1 舵机的工作原理图


       2.舵机的控制原理:

       舵机的输入线共有三条:如下图14.3.2所示,一般棕色表示GND,红色表示VCC,橙色表示信号脚,工作电压一般在4.8 ~ 6V,不同的工作电压对应不同的转矩标准,即输出力矩不同,6V的工作电压对应的力矩要大一些;根据不同的扭矩负载,工作电流可达2A以上。


图14.3.2 舵机接口定义


       其中信号线就是控制电路用于接受外部控制信号的,该信号一般周期为20ms(频率:50HZ)的脉宽调制(PWM)信号。并且该脉冲的高电平持续时间为0.5 ~ 2.5ms,对应舵盘旋转角度的0 ~ 180°,所对应的控制关系如下图所示:


图14.3.3 信号脉宽与舵盘位置关系图


       从图14.3.3可以看出脉冲宽度与舵机旋转角度呈线性变化关系。也就是说我们只需要不断的提供一个脉冲周期为20ms的PWM信号给舵机,并且控制该脉冲信号的高电平持续时间占比,就可以控制输出轴保持在对应的角度上,注意角度范围不可超过180°。


        14.4 舵机的参数介绍

       舵机的几个重要指标:

       ① 响应速度:即在接收到目标位置之后多久才可以转到目标角度,一般舵机为60度每0.17秒,另外根据负载扭矩相应速度也不等。

       ② 工作死区,死区数值越小,控制精度越高;数值越大,精度越小。

       ③ 材质:舵机材质一般有塑料齿轮和金属齿轮,金属材质相对的质量更好,价格也更高些。

       ④ 舵机的扭矩:根据应用场合可从飞行器模型舵机的1.2KG/cm到大扭矩舵机的500~2000KG/cm不等,不过一般扭矩越大舵机消耗电流也越大。

       由此可见这类舵机具有以下这些特点:输出力矩大,稳定性好;体积小,易安装;控制简单等。所以现在不仅仅应用于航模运动中,在机器人的关节,飞机的舵面,以及机械手等应用也越来越广泛。


        14.5 舵机的控制实现

       本小节我们将使用STM32控制舵机转动到指定的角度位置。关于定时器如何输出PWM波的知识,请大家回顾高级定时器PWM输出实验的介绍。


       14.5.1 硬件设计


       1. 例程功能

       G474电机开发板全部有2个舵机接口,分别由TIM8_CH1、TIM8_CH2控制,本实验需要将舵机接入对应的舵机接口,通过按键KEY0设置控制哪个接口的舵机,KEY1增加舵机角度,最大180°,KEY2减小舵机,角度最小0°,LED0作为程序运行状态指示灯,闪烁周期200ms。


       2. 硬件资源

       1) LED灯:LED0 – PE0

       2) 独立按键

              KEY0 – PE12

              KEY1 – PE13

               KEY2 – PE14

       3) 定时器8通道1,将TIM8_CH1输出到PC6。

              定时器8通道2,将TIM8_CH2输出到PC7。

       4) 舵机


       3. 原理图

       开发板设计了2组舵机接口,支持板载5V供电(因为舵机所需电流较大,所以建议使用DC口供电),原理图如下图:


图14.5.1.1 开发板舵机接口原理图


       图14.5.1.1中的P8的SRV+,是为舵机供电用的,它的来源可以选择板载VCC5(5V)供电,也可选择外部电源接入供电(针对驱动电压比较高的舵机,比如一些12V驱动的舵机)。在本实验我们选择板载电源供电,所以首先我们需要使用跳线帽将SRV+与VCC5接一起。开发板全部有2个舵机接口,其中SERVOx(x = 1或2)接舵机的信号线(通常信号线为橙色线),SRV+接舵机的电源正极(通常是红色线),GND接电源负极(通常为棕色线)。

       主要特别注意的点是舵机控制信号SERVO1~2并不是直接控制舵机,而是经过电平转换来控制舵机,这样既隔离了IO,又可以控制各种不同电平的舵机。电平转换电路实际就是一个反向器,因为是反向器,所以控制电平需要反过来,比如要给舵机一个周期20ms的脉冲信号且高电平的持续时间为2ms,那么控制信号实际需要输出2ms低电平+18ms高电平。

       定时器属于STM32G474的内部资源,只需要软件设置好即可正常工作。外部需要将舵机接入对应开发板的舵机接口,同时还使用按键KEY0、KEY1、KEY2进行控制。我们这里接了三个舵机(只有一个舵机的话可接到其中一个接口)实物连接图如下:


图14.5.1.2 实物连接图


       14.5.2 程序设计 


       14.5.2.1 定时器的HAL库驱动

       本实验用到的HAL库驱动请回顾高级定时器PWM输出实验的介绍。下面介绍一下舵机控制的配置步骤。


       舵机控制的配置步骤 


       1)开启TIMx和通道输出的GPIO时钟,配置该IO口的复用功能输出。

       首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器8通道1、通道2,对应IO分别是PC6、PC7,它们的时钟开启方法如下:

__HAL_RCC_TIM8_CLK_ENABLE();            /* 使能定时器8 */
__HAL_RCC_GPIOC_CLK_ENABLE();           /* 开启GPIOC时钟 */

       IO口复用功能是通过函数HAL_GPIO_Init来配置的。

       2)初始化TIMx,设置TIMx的ARR和PSC等参数。

       使用定时器的PWM模式功能时,我们调用的是HAL_TIM_PWM_Init函数来初始化定时器ARR和PSC等参数。

       注意:该函数会调用HAL_TIM_PWM_MspInit函数来完成对定时器底层以及其输出通道IO的初始化,包括:定时器及GPIO时钟使能、GPIO模式设置、中断设置等。

       3)设置定时器为PWM模式,输出比较极性,比较值等参数。

       在HAL库中,通过HAL_TIM_PWM_ConfigChannel函数来设置定时器为PWM1模式或者PWM2模式,根据需求设置输出比较的极性,设置比较值(控制占空比)等。

       本实验我们设置TIM8的通道1、2为PWM2模式,比较值我们设置为:0.5/20*(arr+1),即默认设置PWM波的占空比为2.5%,也就是0.5ms的脉宽,对应的舵机舵盘位置为0°。


       14.5.2.2 程序流程图


图14.5.2.2 舵机控制流程图


       14.5.2.3 程序解析

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。舵机的控制源码主要有:高级定时器驱动源码和舵机角度设置源码四个文件:atim.c和atim.h以及steering_engine.c和steering_engine.h。源码中有明确的注释。

       首先看atim.h头文件的几个宏定义:

/********************** 高级定时器 PWM输出实验相关宏定义 ************************/
/* TIMX PWM输出定义 */
#define ATIM_TIMX_PWM_CH1_GPIO_PORT            GPIOC
#define ATIM_TIMX_PWM_CH1_GPIO_PIN             GPIO_PIN_6
#define ATIM_TIMX_PWM_CH1_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */
 
#define ATIM_TIMX_PWM_CH2_GPIO_PORT            GPIOC
#define ATIM_TIMX_PWM_CH2_GPIO_PIN             GPIO_PIN_7
#define ATIM_TIMX_PWM_CH2_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)  /* PC口时钟使能 */
 
/* TIMX 引脚复用设置
 * 因为PC6/PC7默认并不是TIM8的功能脚,必须开启复用,PC6/PC7才能用作TIM8的PWM输出引脚
 */
#define ATIM_TIMX_PWM_CHY_GPIO_AF            GPIO_AF4_TIM8
 
#define ATIM_TIMX_PWM                          TIM8
#define ATIM_TIMX_PWM_IRQn                     TIM8_UP_IRQn
#define ATIM_TIMX_PWM_IRQHandler               TIM8_UP_IRQHandler
#define ATIM_TIMX_PWM_CH1                      TIM_CHANNEL_1  /* 通道1 */
#define ATIM_TIMX_PWM_CH2                      TIM_CHANNEL_2  /* 通道2 */
#define ATIM_TIMX_PWM_CHY_CLK_ENABLE()         do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0)    /* TIM8 时钟使能 */

       可以把上面的宏定义分成两部分,第一部分是定时器8输入通道1、2对应的IO口的宏定义,第二部分则是定时器8输入通道1、2的相应宏定义。下面我们来看下atim.c程序。

/**
 * @brief       高级定时器TIMX PWM 初始化函数
 * @note
 *              高级定时器的时钟来自APB2,而PCLK2 = 170Mhz,我们设置PPRE2不分频,因此
 *              高级定时器时钟 = 170Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值
 * @param       psc: 时钟预分频数
 * @retval      无
 */
void atim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
    ATIM_TIMX_PWM_CHY_CLK_ENABLE();                       /* TIMX 时钟使能 */
    ATIM_TIMX_PWM_CH1_GPIO_CLK_ENABLE();                /* IO时钟使能 */
    ATIM_TIMX_PWM_CH2_GPIO_CLK_ENABLE();                 /* IO时钟使能 */
    
    g_atimx_pwm_chy_handle.Instance = ATIM_TIMX_PWM;     /* 定时器x */
    g_atimx_pwm_chy_handle.Init.Prescaler = psc;         /* 定时器分频 */
    g_atimx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    g_atimx_pwm_chy_handle.Init.Period = arr;            /* 自动重装载值 */
    g_atimx_pwm_chy_handle.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;   
g_atimx_pwm_chy_handle.Init.AutoReloadPreload = 
TIM_AUTORELOAD_PRELOAD_DISABLE;                /*使能TIMx_ARR进行缓冲*/
    g_atimx_pwm_chy_handle.Init.RepetitionCounter = 0;  /* 开始时不计数*/
    HAL_TIM_PWM_Init(&g_atimx_pwm_chy_handle);          /* 初始化PWM */
    
    g_atimx_oc_pwm_chy_handle.OCMode = TIM_OCMODE_PWM2; /* 模式选择PWM2 */
g_atimx_oc_pwm_chy_handle.Pulse = 0.5/20*(arr+1);   
/* 输出比较极性为高 */
    g_atimx_oc_pwm_chy_handle.OCPolarity = TIM_OCPOLARITY_HIGH;         
    g_atimx_oc_pwm_chy_handle.OCNPolarity = TIM_OCNPOLARITY_HIGH;
    g_atimx_oc_pwm_chy_handle.OCFastMode = TIM_OCFAST_DISABLE;
    g_atimx_oc_pwm_chy_handle.OCIdleState = TIM_OCIDLESTATE_RESET;
    g_atimx_oc_pwm_chy_handle.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&g_atimx_pwm_chy_handle, 
&g_atimx_oc_pwm_chy_handle, ATIM_TIMX_PWM_CH1);   /* 配置TIMx通道y */
HAL_TIM_PWM_ConfigChannel(&g_atimx_pwm_chy_handle, 
&g_atimx_oc_pwm_chy_handle, ATIM_TIMX_PWM_CH2);   /* 配置TIMx通道y */   
 
/* 开启对应PWM通道 */
    HAL_TIM_PWM_Start(&g_atimx_pwm_chy_handle, ATIM_TIMX_PWM_CH1);     
    HAL_TIM_PWM_Start(&g_atimx_pwm_chy_handle, ATIM_TIMX_PWM_CH2);    
}

       HAL_TIM_PWM_Init初始化TIM8并设置TIM8的ARR和PSC等参数,然后通过调用函数HAL_TIM_PWM_ConfigChannel设置TIM8_CH1\2的PWM模式以及比较值等参数,最后通过调用函数HAL_TIM_PWM_Start来使能TIM8以及使能PWM通道TIM8_CH1\2输出。通道的GPIO口初始化和定时器使能等程序放到回调函数HAL_TIM_PWM_MspInit中这里就不贴代码了,感兴趣打开源码看看。接着我们来看下steering_engine.c,首先看角度转比较值函数。

/**
 * @brief       舵机角度转定时器比较值
 * @param       angle:角度
 * @retval      定时器装比较值
 */
uint16_t angle_to_tim_val(float angle)
{
    uint16_t ret;
    if((angle < 0)||(angle > 180))
    {
        ret = 0;
    }
    else
{
    /* 0° -- 500,45° -- 1000,90° -- 1500,135° -- 2000,180° -- 2500 */
        ret = 1500 + (int)((float)((angle - 90) * 100 / 9));     
        if((ret < 500)||(ret > 2500))
        {
            ret = 0;
        }
    }
    return ret;
}

       这个函数主要功能就是把角度值转换成定时器的比较值,我们前面介绍讲过舵机的旋转角度和脉宽成线性关系,比较值决定脉宽时间,所以我们这里把要转动的角度转成比较值控制脉宽时间,从而达到控制舵机旋转角度。接着看下一个舵机角度的设置函数。

/**
 * @brief       舵机角度设定
 * @param       id:舵机编号对应舵机的接口:1~3
 * @param       angle:角度
 * @retval      0:成功
 */
uint8_t servo_angle_set(uint8_t id,float angle)
{
    uint16_t val;
    switch(id)
    {
        case 1:
            val = angle_to_tim_val(angle);    /* 得到角度转换的比较值 */
            if(val != 0)
            {
         /* 设置比较值 */
__HAL_TIM_SetCompare(&g_atimx_pwm_chy_handle,TIM_CHANNEL_1,val);    
            }
            break;
        case 2:
            val = angle_to_tim_val(angle);
            if(val != 0)
            {
             __HAL_TIM_SetCompare(&g_atimx_pwm_chy_handle,TIM_CHANNEL_2,val);
            }
            break;
        default:
            break;
    }
    return 0;
}

       由于我们的开发板有三个舵机接口,所以我们写的这个函数有一个id判断,id:1、2,分别控制舵机接口1、2,想要控制哪个接口的舵机就选择哪个id即可,然后第二个参数设置我们要舵机旋转的角度值,里边会调用到角度转比较值函数,然后我们将返回的数值,设置到定时器的比较值,即可完成脉宽时间的设置,控制舵机旋转对应角度。

       在main函数里面编写如下代码:

int main(void)
{
    uint8_t key, t, id = 1;
    char buf[32];
    float angle[2] = {0};                       /* 初始化角度0° */
    
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8);       /* 设置时钟,170Mhz */
    delay_init(170);                            /* 延时初始化 */
    usart_init(115200);                         /* 初始化串口 */
    led_init();                                 /* 初始化LED */
    key_init();                                 /* 初始化按键 */
lcd_init();                                 /* 初始化LCD */
/* 1Mhz的计数频率,计数20000次为20ms */
    atim_timx_pwm_chy_init(20000 - 1, 170 - 1); 
    /* 显示提示信息 */
    lcd_show_string(10,10,200,16,16,"Servo Test",RED);        
    lcd_show_string(10,30,200,16,16,"KEY0:ID + +",RED);
    lcd_show_string(10,50,200,16,16,"KEY1:Angle +   ",RED);
    lcd_show_string(10,70,200,16,16,"KEY2:Angle -  ",RED);
    lcd_show_string(10,90,200,16,16,"Servo ID: 1",BLUE);
    printf("KEY0 选择控制的舵机接口\r\n");
    printf("KEY1 舵机旋转角度+45°\r\n");
    printf("KEY2 舵机旋转角度-45°\r\n");
    
    while (1)
    {      
        if (t % 10 == 1)
        {
            sprintf(buf,"Servo 1: %.1f",angle[0]);
            lcd_show_string(10,110,200,16,16,buf,BLUE);
            sprintf(buf,"Servo 2: %.1f",angle[1]);
            lcd_show_string(10,130,200,16,16,buf,BLUE);
        }
        
        key = key_scan(0);     
        if (key == KEY0_PRES)
        {
            id++;
            if (id == 3)
            {
                id = 1;
            }
/* 按下KEY0:选择控制哪个舵机,并显示当前ID */
            sprintf(buf,"Servo ID: %1d",id);        
            lcd_show_string(10,90,200,16,16,buf,BLUE);
            printf("所选舵机接口为%d\r\n",id);
        }
        else if (key == KEY1_PRES)
        {
            angle[id - 1] += 45;
            if (angle[id - 1] > 180)
            {
                angle[id - 1] = 180;
            }
            servo_angle_set(id,angle[id - 1]);  
/* 控制该ID的舵机,并设置角度值 */    
            printf("Servo 1: %.1f\r\n",angle[0]);
            printf("Servo 2: %.1f\r\n",angle[1]);
            printf("\r\n");
        }
        else if (key == KEY2_PRES)
        {
            angle[id - 1] -= 45;
            if(angle[id - 1] < 0)
            {
                angle[id - 1] = 0;
            }
/* 控制该ID的舵机,并设置角度值 */
            servo_angle_set(id,angle[id - 1]);      
            printf("Servo 1: %.1f\r\n",angle[0]);
            printf("Servo 2: %.1f\r\n",angle[1]);
            printf("\r\n");
        }   
        
        t++;
        if (t % 20 == 0)
        {
            LED0_TOGGLE();                 /* LED0(红灯) 翻转 */        
        }
        delay_ms(10);
    }
}

       先看atim_timx_pwm_chy_init(20000 - 1, 170 - 1)这个语句,这两个形参分别设置自动重载寄存器的值为20000,以及定时器预分频系数为170。先看预分频系数,我们设置为170分频,然后定时器8的时钟是1倍的APB2,即170MHz,可以得到计数器的计数频率是1MHZ,也就是计数一次1us,我们计数20000次就是20ms,符合我们控制舵机的脉冲周期要求,通过按键设置比较值控制脉宽时间;然后LCD显示一些提示信息包括当前id以及当前控制舵机的角度值;注意需提前将舵机接入对应的舵机接口。按键功能如下:按键KEY0用于设置控制某个接口的舵机;KEY1增加舵机旋转角度,最大到180°;KEY2减小舵机旋转角度,最小到0°。LED0周期闪烁提示程序正在运行。


       14.5.3 下载验证

       下载代码后,可以看到LED0每隔200ms左右闪烁一次,LCD显示当前所控制的舵机接口号以及舵机旋转角度,当我们按下KEY0选择控制哪个舵机接口,按下KEY1将舵机旋转角度每次加45°,最大到180°;按下KEY2将舵机旋转角度每次减45°,最小到0°。同时我们也可以不接舵机,使用示波器查看输出波形的脉宽时间。

       下面我们使用正点原子DS100手持数字示波器,把舵机接口一的控制信号引脚连接至示波器通道,看波形截获,具体如下:


图14.5.3.1 舵机接口一旋转90°的波形图


       可以看到当控制舵机旋转90°时,脉冲频率50HZ(20ms),脉宽1.5ms和我们前面介绍的完全一致,大家可以自己尝试测量看看。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3116249/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    763814