STM32F103C8T6实战:用电位器精准控制SG90舵机角度(附完整代码)

在嵌入式开发领域,精确控制舵机角度是一个经典且实用的项目。本文将带你从零开始,使用STM32F103C8T6微控制器和电位器实现对SG90舵机的精准角度控制。这个项目不仅适合初学者理解PWM和ADC的基本原理,也能帮助有一定经验的开发者优化控制精度。

1. 硬件准备与连接

要实现电位器控制舵机,我们需要以下硬件组件:

  • STM32F103C8T6开发板:作为控制核心
  • SG90舵机:9g微型舵机,工作电压4.8-6V
  • 10KΩ电位器:用于角度调节
  • 面包板和连接线:用于电路搭建
  • 外部电源(可选):当舵机功率需求较大时使用

硬件连接示意图

组件 STM32引脚 备注
电位器中间引脚 PA0 ADC输入
电位器两端 3.3V和GND 供电
舵机信号线 PB0 TIM3_CH3 PWM输出
舵机电源 5V 建议外部供电

注意:SG90舵机工作电流可能达到500mA,直接使用STM32的5V输出可能导致电压不稳,建议使用外部电源供电。

2. PWM信号配置与舵机控制原理

2.1 舵机控制基础

SG90舵机通过PWM信号控制角度,其关键参数如下:

  • 信号周期:20ms(50Hz)
  • 脉冲宽度范围:0.5ms-2.5ms
  • 对应角度范围:0°-180°

PWM占空比与角度关系

脉冲宽度(ms) 对应角度
0.5
1.0 45°
1.5 90°
2.0 135°
2.5 180°

2.2 STM32定时器配置

我们使用TIM3的通道3(PB0)生成PWM信号。以下是关键配置参数计算:

  1. 系统时钟:72MHz
  2. 目标PWM频率:50Hz(周期20ms)
  3. ARR(自动重装载值):18000-1
  4. PSC(预分频器):80-1

计算验证:

PWM频率 = 系统时钟 / ( (ARR+1) × (PSC+1) )
        = 72,000,000 / (18,000 × 80) 
        = 50Hz

PWM初始化代码

void PWM_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // 开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    // 配置PB0为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 定时器基础配置
    TIM_TimeBaseStructure.TIM_Period = 18000 - 1;  // ARR
    TIM_TimeBaseStructure.TIM_Prescaler = 80 - 1;  // PSC
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // PWM模式配置
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_Pulse = 0;  // 初始占空比
    TIM_OC3Init(TIM3, &TIM_OCInitStructure);
    
    // 使能定时器
    TIM_Cmd(TIM3, ENABLE);
}

3. ADC配置与电位器读数

3.1 ADC初始化

我们使用PA0(ADC1通道0)读取电位器电压值:

void ADC1_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    
    // 开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
    
    // 配置PA0为模拟输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // ADC配置
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);  // 12MHz
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    
    // 配置规则通道
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    
    // 校准ADC
    ADC_Cmd(ADC1, ENABLE);
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
}

3.2 读取ADC值

uint16_t Get_ADC_Value(uint8_t ch) {
    ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_55Cycles5);
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
    return ADC_GetConversionValue(ADC1);
}

4. 系统集成与角度映射

4.1 角度控制函数

将ADC值(0-4095)映射到舵机角度(0-180°):

void Set_Servo_Angle(uint16_t angle) {
    // 限制角度范围
    if(angle > 180) angle = 180;
    
    // 计算对应的CCR值
    // 0.5ms = 450, 2.5ms = 2250 (基于ARR=18000-1)
    uint16_t pulse = 450 + angle * 10;
    TIM_SetCompare3(TIM3, pulse);
}

4.2 主程序逻辑

int main(void) {
    uint16_t adc_value, angle;
    
    // 初始化各模块
    PWM_Init();
    ADC1_Init();
    
    while(1) {
        // 读取电位器值
        adc_value = Get_ADC_Value(ADC_Channel_0);
        
        // 映射到角度 (4095/180 ≈ 22.75)
        angle = adc_value / 22.75;
        
        // 设置舵机角度
        Set_Servo_Angle(angle);
        
        // 适当延时
        Delay_ms(20);
    }
}

5. 优化与调试技巧

5.1 提高控制精度

  1. ADC采样平均:多次采样取平均减少噪声影响
uint16_t Get_ADC_Average(uint8_t ch, uint8_t times) {
    uint32_t sum = 0;
    for(uint8_t i=0; i<times; i++) {
        sum += Get_ADC_Value(ch);
        Delay_ms(5);
    }
    return sum/times;
}
  1. 非线性补偿:针对电位器非线性特性进行补偿
// 使用查表法补偿非线性
const uint16_t angle_compensation[181] = { /* 补偿值表 */ };
angle = angle_compensation[adc_value / 22.75];

5.2 常见问题排查

  • 舵机不转动

    • 检查电源是否足够
    • 确认PWM信号线连接正确
    • 测量PWM信号是否符合规范
  • 角度控制不准确

    • 检查ARR和PSC配置
    • 验证ADC读数是否稳定
    • 调整角度映射公式
  • 系统不稳定

    • 增加电源滤波电容
    • 缩短信号线长度
    • 检查接地是否良好

在实际项目中,我发现使用钽电容对舵机电源进行滤波能显著提高系统稳定性。另外,给STM32和舵机使用独立的电源,通过共地连接,可以避免很多电源干扰问题。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐