SG90舵机驱动

SG90 伺服电机驱动

舵机是通俗的叫法,它的本质是一个伺服电机,也可以叫做位置(角度)伺服驱动器。一般被应用在那些需要控制角度变化的系统中,可以方便的实现转动任意的角度,实现控制角度的变化。

sg90舵机的图示

工作原理

sg90 舵机模块内有控制电路,控制信号通过信号线输入到内部的控制电路中,调制芯片将输入的信号进行调制,获得直流偏置电压。然后再由内部的基准电路产生周期为 20ms,宽度为 1.5ms 的基准信号,将直流偏置电压和电位器电压进行比较,从而获得输出的电压差。由电压差控制舵机的转动,这个电压差的正负控制舵机正反转。

舵机的控制信号是脉宽调制(PWM)信号,其中脉冲宽度从最小到最大,相对应舵盘的位置为 0—180 度,呈线性变化。给它提供一定的脉宽,它的输出轴就会保持在一个相对应的角度上,无论外界转矩怎样改变,直到给它提供一个另外宽度的脉冲信号,它才会改变输出角度到新的对应的位置上。

控制电路板接受来自信号线相应的 PWM 控制信号,进而控制电机转动,电机带动一系列齿轮组,减速后传动至输出舵盘。舵机的输出轴和位置反馈电位计是相连的,舵盘转动的同时,带动位置反馈电位计,电位计将输出一个电压信号到控制电路板,进行反馈,然后控制电路板根据所在位置决定电机的转动方向和速度,从而达到目标停止。

angle

注意:每个舵机的这个脉冲宽度要根据实际的调节测试得出。

接线方式

接线

这里的 SG90 舵机使用的是 180 度的数字舵机,只需要给信号线发送一个固定脉宽的信号,即可旋转到指定的位置。

驱动程序

driver_sg90.h

#ifndef __DRIVER_SG90_H__
#define __DRIVER_SG90_H__

#include "main.h"
#include "tim.h"

#ifdef __cplusplus
extern "C" {
#endif

uint8_t sg90_init(TIM_HandleTypeDef *tim_handle);
uint8_t sg90_set_angle(uint8_t angle);

#ifdef __cplusplus
}
#endif

#endif

driver_sg90.c

#include "driver_sg90.h"

/**
 * @brief Timer handle definition
 */
static TIM_HandleTypeDef sg90_tim_handle;

#define SG90_GPIO_Port          GPIOB
#define SG90_GPIO_Pin           GPIO_PIN_15

#define SG90_IN1                TIM_CHANNEL_2

#define SG90_MIN_PULSE_WIDTH    200             /* 测试得到 0° 对应的脉冲宽度单位为 200 us */
#define SG90_MAX_PULSE_WIDTH    1200            /* 测试得到 180° 对应的脉冲宽度单位为 1200 us */
#define SG90_MIN_ANGLE          0               /* 最小角度 */
#define SG90_MAX_ANGLE          180             /* 最大角度 */

#define TIMER_FREQUENCY         1000000         /* 定时器计数器频率为 1 MHz */

/**
 * @brief 舵机初始化
 * @param tim_handle 定时器 
 * @return 
 */
uint8_t sg90_init(TIM_HandleTypeDef *tim_handle)
{
    sg90_tim_handle = *tim_handle;
    TIM_OC_InitTypeDef sConfig = {0};

    /* Configure timer for 1 MHz frequency */
    sg90_tim_handle.Init.Prescaler = (SystemCoreClock / TIMER_FREQUENCY) - 1;
    sg90_tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    sg90_tim_handle.Init.Period = 20000 - 1;                                    /* Provides 20ms PWM cycle */
    sg90_tim_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    sg90_tim_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_PWM_Init(&sg90_tim_handle) != HAL_OK)
    {
        return 1;                                                               /* Initialization failed */
    }

    /* Configure PWM channel */
    sConfig.OCMode = TIM_OCMODE_PWM1;
    sConfig.Pulse = SG90_MIN_PULSE_WIDTH;                                       /* Default Starting at 0° */ 
    sConfig.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfig.OCFastMode = TIM_OCFAST_DISABLE;
    if (HAL_TIM_PWM_ConfigChannel(&sg90_tim_handle, &sConfig, SG90_IN1) != HAL_OK)
    {
        return 2;                                                               /* Channel configuration failed */
    }

    HAL_TIM_PWM_Start(&sg90_tim_handle, SG90_IN1);
    
    /* Initialization success */
    return 0;
}

/**
 * @brief 设置舵机旋转角度
 * @param angle 目标角度
 * @return 
 */
uint8_t sg90_set_angle(uint8_t angle)
{
    if (angle < SG90_MIN_ANGLE)
    {
        angle = SG90_MIN_ANGLE;
    }
    else if(angle > SG90_MAX_ANGLE)
    {
        angle = SG90_MAX_ANGLE;
    }
    
    /* Calculate the corresponding pulse width */
    /* 
    (SG90_MAX_PULSE_WIDTH - SG90_MIN_PULSE_WIDTH) / (SG90_MAX_ANGLE - SG90_MIN_ANGLE) 
    equals to the pulse width corresponding to per degree 
    */
    uint32_t pulse_width = SG90_MIN_PULSE_WIDTH +
                            ((SG90_MAX_PULSE_WIDTH - SG90_MIN_PULSE_WIDTH) / (SG90_MAX_ANGLE - SG90_MIN_ANGLE)) * angle;

    /* Set the pulse width */
    __HAL_TIM_SET_COMPARE(&sg90_tim_handle, SG90_IN1, pulse_width);

    return 0;
}

driver_sg90_test.h

#ifndef __DRIVER_SG90_TEST_H__
#define __DRIVER_SG90_TEST_H__

#include "main.h"
#include "driver_sg90.h"

#ifdef __cplusplus
extern "C" {
#endif

void sg90_test(void);

#ifdef __cplusplus
}
#endif

#endif

driver_sg90_test.c

#include "driver_sg90_test.h"
#include "tim.h"

void sg90_test(void)
{
    sg90_init(&htim12);

    while (1)
    {
        /************* 功能测试 *****************/
        for (uint8_t i = 0; i < 3; i++)
        {
            sg90_set_angle(0);
            HAL_Delay(200);
            sg90_set_angle(180);
            HAL_Delay(200);

            // 进行测试调节
            for (uint8_t angle = 0; angle <= 180; angle += 30)
            {
                sg90_set_angle(angle);
                HAL_Delay(300);
            }
        }

        /************* 进阶测试 ******************/
        // 正弦插值
        for (uint16_t i = 0; i < 360; i += 2)
        {
            // i为弧度值 振幅为 120 度,中心在 0 度
            float rad = (float)i * 3.14159f / 180.0f;
            uint8_t angle = 0 + (int)(120 * sinf(rad));
            sg90_set_angle(angle);
            HAL_Delay(200);
        }
        HAL_Delay(200);

        // 随机平滑变化
        uint8_t current_angle = 90;
        uint8_t target_angle;
        for (uint8_t i = 0; i < 3; i++)
        {
            target_angle = 0 + (rand() % 180); // 随机角度范围在0-180之间

            // 平滑过渡到目标角度
            if (target_angle > current_angle)
            {
                for (uint8_t angle = current_angle; angle <= target_angle; angle++)
                {
                    sg90_set_angle(angle);
                    HAL_Delay(100);
                }
            }
            else
            {
                for (uint8_t angle = current_angle; angle >= target_angle; angle--)
                {
                    sg90_set_angle(angle);
                    HAL_Delay(100);
                }
            }

            current_angle = target_angle;
            HAL_Delay(300);
        }
    }
}

HAL 配置

TIM

这里使用定时器 TIM12 通道2 配置为 PWM 输出,参数设置如图所示。

自动生成的 HAL 程序如下,这个不重要,因为驱动程序初始化时会修改,这里只要确保打开定时器的 PWM 输出即可。


tim.c

/* TIM12 init function */
void MX_TIM12_Init(void)
{

  /* USER CODE BEGIN TIM12_Init 0 */

  /* USER CODE END TIM12_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  /* USER CODE BEGIN TIM12_Init 1 */

  /* USER CODE END TIM12_Init 1 */
  htim12.Instance = TIM12;
  htim12.Init.Prescaler = 84-1;
  htim12.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim12.Init.Period = 20000;
  htim12.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim12.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim12) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim12, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim12) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim12, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM12_Init 2 */

  /* USER CODE END TIM12_Init 2 */
  HAL_TIM_MspPostInit(&htim12);

}