STM32使用HAL库实现PWM驱动舵机教程

什么是舵机?

舵机,也叫伺服电机,在嵌入式开发中,舵机作为一种常见的运动控制组件,具有广泛的应用。

舵机型号介绍:

市面上常见的舵机型号有 SG90、MG90S、MG995、MG996R 等等,主要是扭矩大小、工作电压大小、齿轮材质塑料或金属的不同。

一般分为180度和360度:

  • 180度:可以控制旋转角度、有角度定位。上电后舵机自动复位到0度,通过一定参数的脉冲信号控制它的角度。
  • 360°舵机版本不可控制角度,只能控制顺时针旋转、逆时针旋转、停止和调节转速。
  • 引脚接线参考如下:

    SG90 STM32
    PWM 信号线(橙色线) 任意GPIO
    VCC(红线) 3.3/5V
    GND(棕色线) GND

    SG90原理

            舵机的控制信号是通过脉冲宽度调制(PWM)来实现的。PWM 信号的周期通常为20ms,而脉冲宽度则在 0.5ms 至 2.5ms 之间变化。这个脉冲宽度与舵盘的位置呈线性关系,范围从0度到180度。

            当给舵机提供特定宽度的脉冲信号时,输出轴会保持在相应的角度上,不受外界转矩的影响,直到接收到不同宽度的脉冲信号才会改变输出角度,使舵盘移动到新的位置。舵机内部有一个基准电路,产生周期为 20ms、宽度为 1.5ms 的基准信号。同时,还有一个比较器,用于将外部输入信号与基准信号进行比较,以确定转动方向和幅度,并生成驱动电机转动的信号。

            为了控制舵机,需要使用单片机来生成周期为 20ms 的脉冲信号,并通过控制脉冲的高电平时间在 0.5ms 至 2.5ms 之间来控制舵机的角度。这样,我们可以通过调整 PWM 信号的脉冲宽度来精确控制舵机的位置和运动。

    以 SG90,180度版为例,那么对应的控制关系是这样的:

    脉冲高电平 角度 占空比
    0.5ms 2.5%
    1.0ms 45° 5.0%
    1.5ms 90° 7.5%
    2.0ms 135° 10.0%
    2.5ms 180° 12.5%

    PWM驱动舵机:

     因为:PWM 信号的周期通常为20ms,高电平宽度为0.5ms~2.5ms

    代码:

    TIM_HandleTypeDef tim3_handle = {0};
    // init函数
    void tim3_init(void)
    {
        TIM_OC_InitTypeDef pwm_config = {0};
        
        tim3_handle.Instance = TIM3;
        tim3_handle.Init.Prescaler = 7200 - 1;
        tim3_handle.Init.Period = 200 - 1;
        tim3_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
        HAL_TIM_PWM_Init(&tim3_handle);
        
        pwm_config.OCMode = TIM_OCMODE_PWM1;
        pwm_config.Pulse = 100;
        pwm_config.OCPolarity = TIM_OCPOLARITY_HIGH;
        HAL_TIM_PWM_ConfigChannel(&tim3_handle, &pwm_config, TIM_CHANNEL_1);
        HAL_TIM_PWM_Start(&tim3_handle, TIM_CHANNEL_1);
    }
    //msp函数
    void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
    {
        if(htim->Instance == TIM3)
        {
            GPIO_InitTypeDef gpio_initstruct;
            //打开时钟
            __HAL_RCC_GPIOA_CLK_ENABLE();                           // 使能GPIOB时钟
            __HAL_RCC_TIM3_CLK_ENABLE();
            
            //调用GPIO初始化函数
            gpio_initstruct.Pin = GPIO_PIN_6;                    // 两个LED对应的引脚
            gpio_initstruct.Mode = GPIO_MODE_AF_PP;             // 推挽输出
            gpio_initstruct.Pull = GPIO_PULLUP;                     // 上拉
            gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;           // 高速
    		HAL_GPIO_Init(GPIOA, &gpio_initstruct);
    		
    		gpio_initstruct.Pin = GPIO_PIN_1;
    		gpio_initstruct.Mode = GPIO_MODE_INPUT;
    		gpio_initstruct.Pull = GPIO_PULLUP;           // 上拉
            gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速(可选)
    		HAL_GPIO_Init(GPIOA, &gpio_initstruct);       // 初始化 GPIOA 引脚 1
        }
    }
    //修改CCR值的函数
    void tim3_compare_set(uint16_t val)
    {
        __HAL_TIM_SET_COMPARE(&tim3_handle, TIM_CHANNEL_1, val);
    }
    
    void sg90_init(void)
    {
        tim3_init();
    }
    
    void sg90_angle_set(uint16_t angle)
    {
        uint16_t CCRx = (1.0 / 9.0) * angle + 5.0;
        tim3_compare_set(CCRx);
    }
    
    uint8_t key(void)
    {
        uint8_t KeyNum = 0;
       
    
        if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET)
        {
            // 按键按下,等待去抖动
            delay_ms(20);
            if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET)  // 确认按键仍然按下
            {
                while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET);  // 等待按键释放
                delay_ms(20);  // 再次延时去抖动
                KeyNum = 1;  // 按键按下并释放,返回 1
            }
        }
    	else
    	{
    		// PA1 是低电平
    		delay_ms(20);
    		if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET) 
    		{
    		while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1)==GPIO_PIN_RESET);
    		delay_ms(20);
    		KeyNum=2;
    	}
    }
    	return KeyNum;
    }

    已知 PWM 信号的周期为20ms;高电平 0.5ms 指向 0° 位置,2.5ms 指向 180° 位置。如果我们要指向 angle°:

    2.5-0.5=2ms,对应于180°

    CCRx / (199 + 1) * 20 = 0.5 +(angle / 180)× 2

    于是 CCRx =(1.0 / 9.0) * angle + 5.0

    void SG_Control(uint16_t angle)
    {
       float CCRx;
       CCRx =(1.0 / 9.0) * angle + 5.0;                             //占空比值 = 1/9 * 角度 + 5
       __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, (uint16_t )CCRx);
    }

     

    main.c

    uint8_t KeyNum;			//定义用于接收键码的变量
    float angle;
    int main(void)
    {
        HAL_Init();                         /* 初始化HAL库 */
        stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
        led_init();                         /* 初始化LED灯 */
        sg90_init();
    
        while(1)
        { 
    		KeyNum=key();
    		if(KeyNum==1)
    		{
    			angle+=30;
    			if(angle>180)
    				angle=0;
    		}
           sg90_angle_set(angle);
        }
    }
    
    

     delay.c

    /**
      * @brief  微秒级延时
      * @param  nus 延时时长,范围:0~233015
      * @retval 无
      */
    void delay_us(uint32_t nus)
    {
        uint32_t temp;
        SysTick->LOAD = 72 * nus;                           /* 设置定时器重装值 */
        SysTick->VAL = 0x00;                                /* 清空当前计数值 */
        SysTick->CTRL |= 1 << 2;                            /* 设置分频系数为1分频 */
        SysTick->CTRL |= 1 << 0;                            /* 启动定时器 */
        do
        {
            temp = SysTick->CTRL;
        } while ((temp & 0x01) && !(temp & (1 << 16)));     /* 等待计数到0 */
        SysTick->CTRL &= ~(1 << 0);                         /* 关闭定时器 */
    }
    
    /**
      * @brief  毫秒级延时
      * @param  nms 延时时长,范围:0~4294967295
      * @retval 无
      */
    void delay_ms(uint32_t nms)
    {
        while(nms--)
            delay_us(1000);
    }
     
    /**
      * @brief  秒级延时
      * @param  ns 延时时长,范围:0~4294967295
      * @retval 无
      */
    void delay_s(uint32_t ns)
    {
        while(ns--)
            delay_ms(1000);
    }
    
    /**
      * @brief  重写HAL_Delay函数
      * @param  nms 延时时长,范围:0~4294967295
      * @retval 无
      */
    void HAL_Delay(uint32_t nms)
    {
        delay_ms(nms);
    }
    

    作者:庆庆知识库

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32使用HAL库实现PWM驱动舵机教程

    发表回复