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
调试技巧
- 使用逻辑分析仪验证PWM波形
- 串口打印当前角度和脉冲宽度
- 逐步测试从0°到180°的运动范围
- 检查电源确保舵机供电充足
浙公网安备 33010602011771号