跳转至

rocketpi_pwm_sg90

效果展示

sg90

功能说明

  • 舵机0-180度来回转动,同时串口打印角度值

硬件连接

image-20251213050319930

image-20251213050455013

sg90驱动原理

SG90 是一种常见的小型舵机(servo motor),广泛应用于机器人、遥控模型等领域。它的工作原理基于 PWM(脉宽调制) 信号控制其旋转角度。下面是 SG90 舵机的驱动原理和控制方法:

SG90 驱动原理

SG90 是一种 伺服电机,它通过接收来自控制系统(如微控制器)发送的 PWM 信号,来控制其旋转角度。舵机的工作原理与其他舵机类似,但它的控制方式是通过调整输入信号的 脉宽 来实现角度控制的。

1. PWM 信号的工作方式

  • PWM(脉宽调制) 信号是一种周期性的方波信号,其周期保持固定,波形的宽度(高电平持续的时间)决定了舵机的角度。
  • 舵机的标准控制信号是一个 50 Hz 的 PWM 信号,即每 20 毫秒(20 ms)为一个周期。每个周期中的高电平持续时间决定舵机转动的角度。

2. 控制信号与角度的关系

SG90 通常使用 1 ms 到 2 ms 的脉宽来控制角度:

  • 1 ms 的脉宽信号通常对应舵机转到
  • 1.5 ms 的脉宽信号通常对应舵机转到 90°
  • 2 ms 的脉宽信号通常对应舵机转到 180°

因此,控制信号的脉宽越长,舵机旋转的角度越大。

举例

  • 每 20 毫秒一个周期,高电平 1 毫秒时,舵机转到 0°。
  • 每 20 毫秒一个周期,高电平 2 毫秒时,舵机转到 180°。

3. PWM 信号的参数

  • 频率:舵机使用 50 Hz 的频率(即每 20 毫秒一个周期)。
  • 脉宽:脉宽通常在 1 ms 到 2 ms 之间,代表舵机的旋转角度。
  • 周期:周期保持为 20 毫秒,控制信号的宽度(高电平的持续时间)决定了舵机的角度。

4. 如何控制 SG90

  • 通过调节 PWM 信号的脉宽,可以控制舵机的旋转角度。

驱动以及测试代码

Core/Src/main.c
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "driver_sg90_test.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
  SG90_Test_Init();
//  SG90_Test_Sweep(500);
    SG90_Test_Scan(2);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 84;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
bsp/sg90/driver_sg90_test.c
#include "driver_sg90_test.h"

#include "tim.h"
#include "usart.h"

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

/**
 * TIM3 is configured for a 20 ms period with 1 µs resolution, so the CCR value
 * directly represents the pulse width in microseconds.
 */

static uint16_t clamp_pulse(uint16_t pulse_us)
{
    if (pulse_us < SG90_MIN_PULSE_US)
    {
        return SG90_MIN_PULSE_US;
    }
    if (pulse_us > SG90_MAX_PULSE_US)
    {
        return SG90_MAX_PULSE_US;
    }
    return pulse_us;
}

void SG90_Test_Init(void)
{
    /* Ensure PWM output is running before updating the duty cycle */
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
    /* Move the horn to the mid position as a safe default */
    (void)SG90_Test_SetAngle(90.0f);
}

static char uart_log_buffer[2][32];
static uint8_t uart_active_buffer;

static void log_angle(float angle_deg)
{
    if (huart2.gState != HAL_UART_STATE_READY)
    {
        return;
    }

    uint8_t next_buffer = uart_active_buffer ^ 1U;
    int len = snprintf(uart_log_buffer[next_buffer], sizeof(uart_log_buffer[next_buffer]),
                       "SG90 angle: %.2f\r\n", angle_deg);
    if (len <= 0)
    {
        return;
    }

    if (HAL_UART_Transmit_DMA(&huart2,
                              (uint8_t *)uart_log_buffer[next_buffer],
                              (uint16_t)len) == HAL_OK)
    {
        uart_active_buffer = next_buffer;
    }
}

HAL_StatusTypeDef SG90_Test_SetPulse(uint16_t pulse_width_us)
{
    uint16_t clamped = clamp_pulse(pulse_width_us);

    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_4, clamped);

    return (clamped == pulse_width_us) ? HAL_OK : HAL_ERROR;
}

HAL_StatusTypeDef SG90_Test_SetAngle(float angle_deg)
{
    if (angle_deg != angle_deg)
    {
        return HAL_ERROR;
    }

    if (angle_deg < 0.0f)
    {
        angle_deg = 0.0f;
    }
    else if (angle_deg > 180.0f)
    {
        angle_deg = 180.0f;
    }

    float pulse = (float)SG90_MIN_PULSE_US +
                  (angle_deg / 180.0f) * (float)(SG90_MAX_PULSE_US - SG90_MIN_PULSE_US);

    return SG90_Test_SetPulse((uint16_t)(pulse + 0.5f));
}

void SG90_Test_Sweep(uint32_t dwell_ms)
{
    static const float positions[] = {0.0f, 45.0f, 90.0f, 135.0f, 180.0f};
    for (size_t i = 0; i < (sizeof(positions) / sizeof(positions[0])); ++i)
    {
        (void)SG90_Test_SetAngle(positions[i]);
        HAL_Delay(dwell_ms);
    }
}

void SG90_Test_Scan(uint32_t step_delay_ms)
{
    const uint32_t steps_per_degree = 4U; /* 0.25 degree increments */
    const uint32_t log_stride = steps_per_degree * 5U; /* log every 5 degrees */
    const uint32_t max_index = 180U * steps_per_degree;
    const float inv_steps = 1.0f / (float)steps_per_degree;
    uint32_t base_delay = step_delay_ms / steps_per_degree;
    uint32_t remainder = step_delay_ms % steps_per_degree;

    while (1)
    {
        /* Forward sweep 0 -> 180 */
        for (uint32_t idx = 0U; idx <= max_index; ++idx)
        {
            float angle = (float)idx * inv_steps;
            (void)SG90_Test_SetAngle(angle);
            if ((idx % log_stride) == 0U || idx == max_index)
            {
                log_angle(angle);
            }
            uint32_t delay_ms = base_delay + ((idx % steps_per_degree) < remainder ? 1U : 0U);
            HAL_Delay(delay_ms);
        }

        /* Reverse sweep 179.75 -> 0.25 to avoid pausing twice at the endpoints */
        for (uint32_t idx = max_index - 1U; idx > 0U; --idx)
        {
            float angle = (float)idx * inv_steps;
            (void)SG90_Test_SetAngle(angle);
            if ((idx % log_stride) == 0U)
            {
                log_angle(angle);
            }
            uint32_t delay_ms = base_delay + ((idx % steps_per_degree) < remainder ? 1U : 0U);
            HAL_Delay(delay_ms);
        }
    }
}
bsp/sg90/driver_sg90_test.h
/**
 * @file driver_sg90_test.h
 * @brief Simple SG90 servo helper functions built on TIM3 CH4 (PC9).
 */

#ifndef DRIVER_SG90_TEST_H
#define DRIVER_SG90_TEST_H

#ifdef __cplusplus
extern "C" {
#endif

#include "stm32f4xx_hal.h"

/**
 * SG90 pulse parameters in microseconds.
 * TIM3 is configured for 1 us ticks (84 MHz / 84 prescaler).
 */
#define SG90_MIN_PULSE_US   500U
#define SG90_MAX_PULSE_US  2500U
#define SG90_FRAME_PERIOD_US 20000U

/**
 * @brief Start TIM3 CH4 PWM output and park the servo at 90 degrees.
 */
void SG90_Test_Init(void);

/**
 * @brief Set the SG90 pulse width directly.
 * @param pulse_width_us Desired high time in microseconds, expected 500-2500.
 *        Values outside the range are clamped and return HAL_ERROR.
 */
HAL_StatusTypeDef SG90_Test_SetPulse(uint16_t pulse_width_us);

/**
 * @brief Position the SG90 by angle.
 * @param angle_deg Target angle in degrees, nominal range 0-180.
 *        Out-of-range values are saturated and report HAL_OK for the clamped setpoint.
 */
HAL_StatusTypeDef SG90_Test_SetAngle(float angle_deg);

/**
 * @brief Sweep through a set of demo angles.
 * @param dwell_ms Delay in milliseconds to hold each position; use >=100 for visible motion.
 */
void SG90_Test_Sweep(uint32_t dwell_ms);

/**
 * @brief Scan 0-180-0 degrees in fine (0.25°) steps while logging each angle over UART (DMA TX).
 *        This routine runs continuously until interrupted.
 *        Angle logs are throttled to once every ~5 degrees to keep motion smooth.
 * @param step_delay_ms Delay budget per degree; smaller values increase the sweep speed.
 *        Use at least 10 ms if the servo struggles to keep up.
 */
void SG90_Test_Scan(uint32_t step_delay_ms);

#ifdef __cplusplus
}
#endif

#endif /* DRIVER_SG90_TEST_H */