rocketpi_pwm_sg90
效果展示

功能说明
硬件连接


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 的脉宽信号通常对应舵机转到 0°。
- 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 */
|