STM32F103C8T6控制双舵机系统

系统概述

基于STM32F103C8T6(蓝桥杯CT117E开发板核心芯片)控制两个舵机,实现角度精确控制和PWM占空比调节功能。

硬件配置

引脚分配

舵机 定时器通道 引脚 说明
舵机1 TIM2_CH1 PA0 舵机1 PWM输出
舵机2 TIM2_CH2 PA1 舵机2 PWM输出

舵机规格

  • 工作电压: 4.8V-6V
  • 控制信号: 50Hz PWM (周期20ms)
  • 脉冲宽度: 0.5ms-2.5ms (对应0°-180°)
  • 角度分辨率: 约0.09°/步 (8位分辨率)

软件实现

1. 基础PWM配置

#include "stm32f10x.h"
#include "stdio.h"

// 舵机角度范围定义
#define SERVO_MIN_ANGLE 0
#define SERVO_MAX_ANGLE 180
#define SERVO_MIN_PULSE 500   // 0.5ms = 500us
#define SERVO_MAX_PULSE 2500  // 2.5ms = 2500us

// 全局变量存储当前角度
volatile uint16_t servo1_angle = 90;  // 默认90度
volatile uint16_t servo2_angle = 90;

// PWM初始化函数
void PWM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // 1. 开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    // 2. 配置GPIO
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;  // PA0, PA1
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         // 复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 3. 配置定时器基础
    TIM_TimeBaseStructure.TIM_Period = 19999;               // 自动重装载值 ARR
    TIM_TimeBaseStructure.TIM_Prescaler = 71;               // 预分频器 PSC
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    // 4. 配置PWM输出通道
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;       // PWM模式1
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    
    // 通道1配置 (舵机1)
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
    
    // 通道2配置 (舵机2)
    TIM_OC2Init(TIM2, &TIM_OCInitStructure);
    TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
    
    // 5. 使能定时器
    TIM_ARRPreloadConfig(TIM2, ENABLE);
    TIM_Cmd(TIM2, ENABLE);
    
    // 设置初始角度
    Servo1_SetAngle(servo1_angle);
    Servo2_SetAngle(servo2_angle);
}

2. 角度控制函数

// 角度转脉冲宽度计算
uint16_t AngleToPulse(uint16_t angle)
{
    // 限制角度范围
    if(angle > SERVO_MAX_ANGLE) angle = SERVO_MAX_ANGLE;
    if(angle < SERVO_MIN_ANGLE) angle = SERVO_MIN_ANGLE;
    
    // 线性映射: 角度 -> 脉冲宽度 (us)
    // 角度0°: 500us, 角度180°: 2500us
    uint32_t pulse_width = SERVO_MIN_PULSE + 
                          (angle * (SERVO_MAX_PULSE - SERVO_MIN_PULSE)) / SERVO_MAX_ANGLE;
    
    // 转换为比较寄存器值
    // 定时器计数周期 = 20ms = 20000us
    // 比较值 = 脉冲宽度(us) * (ARR+1) / 20000
    uint16_t compare_value = (pulse_width * 20000) / 20000;
    
    return compare_value;
}

// 设置舵机1角度
void Servo1_SetAngle(uint16_t angle)
{
    servo1_angle = angle;
    uint16_t pulse = AngleToPulse(angle);
    TIM_SetCompare1(TIM2, pulse);
}

// 设置舵机2角度
void Servo2_SetAngle(uint16_t angle)
{
    servo2_angle = angle;
    uint16_t pulse = AngleToPulse(angle);
    TIM_SetCompare2(TIM2, pulse);
}

// 直接设置占空比 (微秒)
void Servo1_SetPulse(uint16_t pulse_us)
{
    if(pulse_us < 500) pulse_us = 500;
    if(pulse_us > 2500) pulse_us = 2500;
    
    uint16_t compare_value = (pulse_us * 20000) / 20000;
    TIM_SetCompare1(TIM2, compare_value);
}

void Servo2_SetPulse(uint16_t pulse_us)
{
    if(pulse_us < 500) pulse_us = 500;
    if(pulse_us > 2500) pulse_us = 2500;
    
    uint16_t compare_value = (pulse_us * 20000) / 20000;
    TIM_SetCompare2(TIM2, compare_value);
}

3. 串口通信控制接口

#include "string.h"

// 串口初始化 (用于调试和控制)
void USART1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    
    // 开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置TX(PA9), RX(PA10)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 串口配置
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    
    USART_Cmd(USART1, ENABLE);
}

// 命令解析函数
void ParseCommand(char* cmd)
{
    uint16_t servo_num, value;
    
    if(sscanf(cmd, "servo%d angle %d", &servo_num, &value) == 2)
    {
        if(servo_num == 1)
            Servo1_SetAngle(value);
        else if(servo_num == 2)
            Servo2_SetAngle(value);
        
        printf("Servo%d set to %d degrees\r\n", servo_num, value);
    }
    else if(sscanf(cmd, "servo%d pulse %d", &servo_num, &value) == 2)
    {
        if(servo_num == 1)
            Servo1_SetPulse(value);
        else if(servo_num == 2)
            Servo2_SetPulse(value);
        
        printf("Servo%d pulse set to %d us\r\n", servo_num, value);
    }
    else
    {
        printf("Unknown command. Usage:\r\n");
        printf("servo1 angle 90\r\n");
        printf("servo1 pulse 1500\r\n");
    }
}

4. 主程序框架

// 简易延时函数
void Delay_ms(uint32_t ms)
{
    for(uint32_t i = 0; i < ms; i++)
        for(uint32_t j = 0; j < 7200; j++);
}

int main(void)
{
    // 系统初始化
    PWM_Init();
    USART1_Init();
    
    printf("Dual Servo Control System Started\r\n");
    printf("STM32F103C8T6 Servo Controller Ready\r\n");
    
    // 演示扫描模式
    uint8_t direction = 0;
    uint16_t current_angle = 0;
    
    while(1)
    {
        // 演示自动扫描模式
        if(direction == 0)
        {
            current_angle++;
            if(current_angle >= 180) direction = 1;
        }
        else
        {
            current_angle--;
            if(current_angle <= 0) direction = 0;
        }
        
        Servo1_SetAngle(current_angle);
        Servo2_SetAngle(180 - current_angle);  // 反向运动
        
        Delay_ms(20);  // 控制扫描速度
        
        // 可以添加串口命令处理
        // 在实际应用中通过串口接收控制命令
    }
}

5. 高级控制功能

// 平滑运动函数
void Servo1_SmoothMove(uint16_t target_angle, uint16_t duration_ms)
{
    int16_t start_angle = servo1_angle;
    int16_t angle_diff = target_angle - start_angle;
    uint16_t steps = duration_ms / 20;  // 每20ms一步
    
    for(uint16_t i = 1; i <= steps; i++)
    {
        uint16_t current_angle = start_angle + (angle_diff * i) / steps;
        Servo1_SetAngle(current_angle);
        Delay_ms(20);
    }
}

// 同步控制两个舵机
void Servos_SyncMove(uint16_t angle1, uint16_t angle2, uint16_t duration_ms)
{
    int16_t start1 = servo1_angle, start2 = servo2_angle;
    int16_t diff1 = angle1 - start1, diff2 = angle2 - start2;
    uint16_t steps = duration_ms / 20;
    
    for(uint16_t i = 1; i <= steps; i++)
    {
        Servo1_SetAngle(start1 + (diff1 * i) / steps);
        Servo2_SetAngle(start2 + (diff2 * i) / steps);
        Delay_ms(20);
    }
}

// 预设位置函数
void SetPresetPosition(uint8_t preset)
{
    switch(preset)
    {
        case 0:  // 归零
            Servos_SyncMove(90, 90, 500);
            break;
        case 1:  // 最大角度
            Servos_SyncMove(0, 180, 500);
            break;
        case 2:  // 交叉
            Servos_SyncMove(180, 0, 500);
            break;
        case 3:  // 中间位置
            Servos_SyncMove(45, 135, 500);
            break;
    }
}

参考代码 stm32f103c8t6控制舵机 www.youwenfan.com/contentcnm/72908.html

使用说明

控制命令示例

servo1 angle 45     // 设置舵机1到45度
servo2 angle 120    // 设置舵机2到120度
servo1 pulse 1500   // 设置舵机1脉冲宽度为1500us
servo2 pulse 2000   // 设置舵机2脉冲宽度为2000us

参数计算

  • 角度转脉冲: 脉冲(us) = 500 + (角度 × 2000 / 180)
  • 脉冲转角度: 角度 = (脉冲 - 500) × 180 / 2000

调试技巧

  1. 使用逻辑分析仪验证PWM波形
  2. 串口打印当前角度和脉冲宽度
  3. 逐步测试从0°到180°的运动范围
  4. 检查电源确保舵机供电充足
posted @ 2025-11-26 15:58  chen_yig  阅读(60)  评论(0)    收藏  举报