4. 舵机控制

有一种电机可以在程序的控制下,在一定范围内连续改变输出轴角度并且可以保持住。 这种电机最早被用在航模和船模等遥控模型中,控制各种舵面的转动,这就是舵机。 现在舵机除了运用在遥控模型中,也大量的运用在各种机器人、机械臂的关节以及智能小车的转向机构中。 下图就是一种标准舵机的外形。

舵机外形图

4.1. 舵机分类

  1. 按照舵机的控制电路可以分为:模拟舵机和数字舵机。模拟舵机和数字舵机的机械结构可以说是完全相同的, 模拟舵机的控制电路为纯模拟电路,需要一直发送目标信号,才能转到指定的位置,响应速度较慢,无反应区较大; 数字舵机内部控制电路则加上了微控制器,只需要发送一次目标信号,即可到达指定位置,速度比模拟舵机更快,无反应区也更小。

  2. 按照使用对象的不同,可以分为:航模舵机、车模舵机、船模舵机和机器人舵机。航模舵机一般要求速度快、精度高, 而车模和船模用的舵机一般要求具有大扭矩和防水性好。

  3. 按照内部机械材质,又可分成:塑料齿舵机和金属齿舵机。塑料齿舵机内部的传动齿轮是塑料的,重量轻价格便宜, 但是扭矩一般较小无法做大;金属齿舵机的扭矩更大,舵机更结实耐用,但是相比塑料齿更重也更贵。

  4. 按照外部接口和舵机的控制方式,又可分为:PWM舵机和串行总线舵机。

4.2. 舵机结构

舵机主要由以下几个部分组成:外壳、舵盘、直流电机、减速齿轮组、角度传感器、控制驱动电路和接口线缆等。常见的舵机内部结构如下图所示。

舵机结构解析图

其中角度传感器负责舵机的位置反馈,直接装在舵机的主输出轴上,将轴旋转后产生的角度变化变成电压信号发回控制电路,当前轴角度; 控制驱动电路用来接收外部接口传来的信号和角度传感器反馈的电压值,以及驱动直流电机旋转; 减速齿轮组则是降低直流电机的转速并且放大扭矩,这一部分就跟前面介绍的直流减速电机差不多了。

市面上常见的廉价舵机通常采用小型的直流有刷电机和塑料材质减速齿轮组,传感器一般使用电位器返回模拟电压。而一些稍贵的则会使用金属齿轮组。 一些高端的舵机内部甚至会采用无刷电机和磁电编码器。

4.3. 舵机工作原理

模拟舵机和数字舵机内部电路不同,所以原理上稍有差别,这里以模拟舵机进行讲解。模拟舵机内部的控制驱动电路板从外界接收控制信号, 经过处理后变为一个直流偏置电压,在控制板内部有一个基准电压,这个基准电压由电位器产生并反馈到控制板。将外部获得的直流偏置电压与电位器的电压进行比较获得电压差, 并输出到电机驱动芯片驱动电机,电压差的正负决定电机的正反转,大小决定旋转的角度,电压差为0时,电机停止转动。大致原理框图如下图所示。

舵机内部控制流程

从图中可以看到,舵机内部是闭环控制的,所以这一类电机实际上是一种位置(角度)伺服的简化版伺服电机,将工业伺服电机的三闭环控制简化成了只有一个位置闭环。 舵机这个名字是国内起的一种俗称,本质上属于伺服电机,它的英文就直接叫Servo,或者RC Servo。

4.4. 舵机控制原理

舵机的输入有三根线,一般的中间的红色线为电源正极,咖啡色线的为电源负极,黄色色线为控制线号线。 如下图所示。

舵机控制线

舵机的控制通常采用PWM信号,例如需要一个周期为20ms的脉冲宽度调制(PWM), 脉冲宽度部分一般为0.5ms-2.5ms范围内的角度控制脉冲部分,总间隔为2ms。 当脉冲宽度为1.5ms时,舵机旋转至中间角度,大于1.5ms时 舵机旋转角度增大,小于1.5ms时舵机旋转角度减小。舵机分90°、180°、270°和360°舵机, 以180°的舵机为例来看看脉冲宽度与角度的关系,见下图所示。

脉冲宽度与角度的关系

上图中脉冲宽度与舵机旋转角度为线性关系,其他舵机控制脉冲也类似,0.5ms对应0度,2.5ms对应最大 旋转角度,脉冲宽度与旋转角度也是线性关系。

4.5. 舵机几个参数介绍

舵机速度的单位是sec/60°,就是舵机转过60°需要的时间,如果控制脉冲变化宽度大,变化速度快, 舵机就有可能在一次脉冲的变化过程中还没有转到目标角度时,而脉冲就再次发生了变化, 舵机的转动速度一般有0.16sec/60°、0.12sec/60°等,0.16sec/60°就是舵机转动60°需要0.16秒的时间。 舵机的速度还有工作电压有关,在允许的电压范围内,电压越大速度越快,反之亦然。

舵机扭矩的单位是KG*CM,这是一个扭矩的单位,可以理解为在舵盘上距离舵机轴中心水平距离1CM处, 舵机能够带动的物体重量,如下图所示。

舵机扭力单位示意图

通常说的55g舵机、9g舵机等,这里的55g和9g指的是舵机本身的重量。

4.6. 舵机基本控制实验

4.6.1. 硬件设计

本实验使用MG996R舵机来演示,这款舵机的规格如下。

  • 尺寸:40.8*20*38mm

  • 重量:55g

  • 速度:4.8V@ 0.20sec/60°——6.0V@ 0.19sec/60°

  • 扭力:4.8V@ 13kg-cm——6.0V@ 15kg-cm

  • 电压:4.8V-7.2V

  • 空载工作电流:120mA

  • 堵转工作电流:1450mA

  • 响应脉宽时间≤5usec

  • 角度偏差:回中误差0度,左右各45°误差≤3°。

  • 齿轮:5级金属齿轮组

  • 连接线长度:300mm

  • 接口规格:JR/FP通用。

电机开发板上预留了两个舵机接口,本实验同时开启两个接口的控制, 舵机可以由GPT0的GTIOC0A和GTIOC0B控制,也就是PB04和PB05引脚,接口原理图如下图所示。

舵机接口原理图

4.6.2. 软件设计

4.6.2.1. 新建工程

由于本实验需要用到定时器PWM输出, 因此我们在前面定时器章节的“实验2:PWM互补输出”例程的基础上修改程序。

对于 e2 studio 开发环境:

拷贝一份我们之前的 e2s 工程 “006_GPT_PWM_Output”, 然后将工程文件夹重命名为 “100_Motor_SteeringGear_Control”,最后再将它导入到我们的 e2 studio 工作空间中。

对于 Keil 开发环境:

拷贝一份我们之前的 Keil 工程 “006_GPT_PWM_Output”, 然后将工程文件夹重命名为 “100_Motor_SteeringGear_Control”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。

工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “motor_controls” 文件夹, 再进入 “motor_controls” 文件夹里面新建源文件和头文件:“steering_gear.c” 和 “steering_gear.h”。 并删除之前的 “gpt” 文件夹。 工程文件结构如下。

文件结构
100_Motor_SteeringGear_Control
├─ ......
└─ src
   ├─ beep
   │  ├─ bsp_beep.c
   │  └─ bsp_beep.h
   ├─ debug_uart
   │  ├─ bsp_debug_uart.c
   │  └─ bsp_debug_uart.h
   ├─ key
   │  ├─ bsp_key_irq.c
   │  └─ bsp_key_irq.h
   ├─ led
   │  ├─ bsp_led.c
   │  └─ bsp_led.h
   ├─ motor_controls
   │  ├─ steering_gear.c   //新建文件
   │  └─ steering_gear.h   //新建文件
   └─ hal_entry.c

4.6.2.2. FSP配置

下面以野火启明6T2开发板为例来讲解相关的 FSP 配置。

在 PWM互补输出 实验中已经配置了PWM模块,只需要在此的基础上进行修改即可。

首先在“Pins”配置页修改 GPT 配置引脚, 我们将 GPT0 的 GTIOC0A 和 GTIOC0B 信号输出分别连接到 PB04PB05 引脚,如下图所示。

图

然后在“Stacks”配置页中更改 GPT 模块,并对其作如下图所示的配置。

图

上图中框起来的部分是需要我们去修改的区域,其他的配置属性按照默认即可。 图中需要更改的配置如下:

  • Pin Output Support:这一项配置允许输出 PWM 信号到引脚,我们改为使能引脚输出。

  • NameChannel:这两项分别设置 GPT 模块名字为 “gear_pwm” 和选择第 0 个 GPT 定时器(第0个通道)。

  • Mode:配置 GPT 的工作模式为 PWM 输出模式。

  • PeriodPeriod Unit:我们将PWM频率设为 50 Hz, 因此“Period”设置为 50,单位“Period Unit”设置为 Hertz,即赫兹(Hz)。

  • GTIOCA Output Enabled:使能 GTIOCA 输出。

  • GTIOCB Output Enabled:使能 GTIOCB 输出。

  • GTIOCB Stop Level:设置定时器停止时 GTIOCB 输出的电平为低电平。

  • GTIOC4A:选择连接到 PB04 引脚,这个软件会自动设置的,我们只要确认了就好。

  • GTIOC4B:选择连接到 PB05 引脚,这个软件会自动设置的,我们只要确认了就好。

GPT的“Output”部分的属性描述如下表所示。

GPT属性描述:“Output”部分

属性

描述

Custom Waveform > GTIOA/GTIOB > Initial Output Level

设置GPT初始化时 GTIOCxA/GTIOCxB 的电平(低电平或高电平)。

Custom Waveform > GTIOA/GTIOB > Cycle End Output Level

完成一个计数周期时 GTIOCxA/GTIOCxB 的输出电平(保持不变、输出高、输出低或反转电平)。

Custom Waveform > GTIOA/GTIOB > Compare Match Output Level

发生比较匹配时 GTIOCxA/GTIOCxB 的输出电平(保持不变、输出高、输出低或反转电平)。

Custom Waveform > GTIOA/GTIOB > Retain Output Level at Count Stop

在计数停止时保持 GTIOCxA/GTIOCxB 电平。

Custom Waveform > Custom Waveform Enable

使能自定义波形的配置。

Duty Cycle Percent (only applicable in PWM mode)

设定 PWM 占空比(仅用于 PWM 模式)

GTIOCA Output Enabled

使能 GTIOCA 输出到相应引脚

GTIOCA Stop Level

当 GPT 定时器停止时 GTIOCA 的输出电平

GTIOCB Output Enabled

使能 GTIOCB 输出到相应引脚

GTIOCB Stop Level

当 GPT 定时器停止时 GTIOCB 的输出电平

4.6.2.3. GPT初始化函数

代码清单4-1:GPT初始化函数
/* 舵机定时器初始化函数 */
void Motor_Init(void)
{
    fsp_err_t err = FSP_SUCCESS;
    /* 初始化 GPT 模块 */
    err = R_GPT_Open(&gear_pwm_ctrl, &gear_pwm_cfg);
    assert(FSP_SUCCESS == err);

    /* 启动 GPT 定时器 */
    err = R_GPT_Start(&gear_pwm_ctrl);
    assert(FSP_SUCCESS == err);
}

4.6.2.4. 设置齿轮角度函数

代码清单4-2:设设置齿轮角度函数
/**
* @brief 设置齿轮角度
* @param[in] angle:角度值,-90 ~ 90度
*/
void Motor_SetAngle(uint32_t angle)
{
    timer_info_t info;
    uint32_t current_period_counts;

    /* 获得GPT的信息 */
    R_GPT_InfoGet(&gear_pwm_ctrl, &info);

    /* 获得计时器一个周期需要的计数次数 */
    current_period_counts = info.period_counts;

    /*将脉宽角度转换为定时器计数器数值*/
    angle = (uint32_t)((0.5 + angle / 180.0 * (2.5 - 0.5)) / 20.0 * current_period_counts);

    /*将期望角度的数值输入以PWM方式实现*/
    R_GPT_DutyCycleSet(&gear_pwm_ctrl, current_period_counts - angle, GPT_IO_PIN_GTIOCA_AND_GTIOCB);
}

4.6.2.5. 按键中断函数

代码清单4-3:按键中断函数
/*按键1中断回调函数*/
void key1_irq_callback(external_irq_callback_args_t *p_args)
{
    FSP_PARAMETER_NOT_USED(p_args);
    //判断角度区间
    Angle_Num = ((180 - Angle_Num) >= 30) ? (Angle_Num + 30) : Angle_Num;
    //逆时针转30度
    Motor_SetAngle(Angle_Num);
    //等待完成
    R_BSP_SoftwareDelay(200, BSP_DELAY_UNITS_MILLISECONDS);
}


/*按键2中断回调函数*/
void key2_irq_callback(external_irq_callback_args_t *p_args)
{
    FSP_PARAMETER_NOT_USED(p_args);

    //判断角度区间
    Angle_Num = ((Angle_Num - 0) >= 30) ? (Angle_Num - 30) : Angle_Num;
    //顺时针转30度
    Motor_SetAngle(Angle_Num);
    //等待完成
    R_BSP_SoftwareDelay(200, BSP_DELAY_UNITS_MILLISECONDS);

}

4.6.2.6. hal_entry入口函数

代码清单4-4:hal_entry入口函数
/* 用户头文件包含 */
#include "hal_data.h"
#include "led/bsp_led.h"
#include "beep/bsp_beep.h"
#include "key/bsp_key_irq.h"
#include "debug_uart/bsp_debug_uart.h"

#include "gpt/bsp_gpt_timing.h"
#include "gpt/bsp_gpt_pwm_output.h"

void hal_entry(void)
{
    /* TODO: add your own code here */
    LED_Init();         // LED 初始化
    Debug_UART9_Init(); // SCI9 UART 调试串口初始化
    GPT_PWM_Init();     // GPT 初始化

    printf("这是一个 GPT 的PWM输出功能实验\r\n");
    printf("使用示波器测量 PE10 和 PE13 输出的PWM波形\r\n");

    // LED1 闪烁指示程序正在运行...
    while(1)
    {
        LED1_ON;
        R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
        LED1_OFF;
        R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
    }


#if BSP_TZ_SECURE_BUILD
    /* Enter non-secure code */
    R_BSP_NonSecureEnter();
#endif
}

4.6.3. 下载验证

编译并下载程序后,复位开发板使程序重新运行,如果有条件的话,这里我们先不连接舵机,先通过示波器连接到开发板的PWM输出引脚上,通过示波器来观察PWM 的变化情况,使用示波器的CH1连接到 PB04 或 B05 ,注意示波器要与开发板共地。

通过示波器可以观察到CH1通道的PWM波形,当按下KEY1或者KEY2时,可以改变CH1通道的占空比, 如下图所示。

示波器观察PWM输出情况

经过验证可以知道我们的PWM脉冲宽度是在0.5~2.5ms之间变化。这正是我们想要的结果,这说明我们的代码是 正确的,这时我们就可以接上舵机来测试了。

通过按键KEY1和KEY2来调整舵机角度。