打印
[其他ST产品]

用stm32控制舵机——以stm32F103C8T6为例

[复制链接]
3182|58
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。使用stm32控制机器时,经常要用到舵机,如使某个部位转到特定的角度,或者在行进过程中的方向控制,这篇文章将以stm32F103C8T6为例,从分析舵机的原理出发,到介绍使用stm的TIM功能输出PWM波,掌握理论后进行实战,先控制一个舵机上手,然后控制多个舵机。

一、舵机的原理



如图所示,一个舵机由变速齿轮箱,电位器,电路板与直流电机组成。电机的高速、短周期运动由齿轮箱转换为慢速、长周期的运动,最终到最外端的齿轮,齿轮的转动带动电位器转动,电位器的电位与信号线进行比较,从而实现转动到特定角度的功能。

使用特权

评论回复
沙发
yellow555|  楼主 | 2023-7-26 17:09 | 只看该作者
伺服电机由信号线输入的PWM信号控制。信号的频率应为50Hz,周期为20ms,PWM的占空比决定了舵机旋转到的角度。

使用特权

评论回复
板凳
yellow555|  楼主 | 2023-7-26 17:09 | 只看该作者
二、如何控制一个舵机
舵机的控制由一个脉冲宽度调制信号(PWM波)来实现,该信号在这个实验里使用stm32来发出。

通常来说,1ms的脉宽对应0度位置,1.5ms对应90度,2ms对应180度。这一数据会由于舵机型号不同而略有差异。

使用特权

评论回复
地板
yellow555|  楼主 | 2023-7-26 17:17 | 只看该作者
三、使用stm32TIM功能输出PWM波来控制舵机
根据STM32F10xxx参考手册,
通用TIM 定时器与高级TIM功能包括:
● 16位向上、向下、向上/向下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意
数值
● 4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 使用外部信号控制定时器和定时器互连的同步电路
● 如下事件发生时产生中断/DMA:
─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
─ 输入捕获
─ 输出比较
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
在这里使用4个独立通道:PWM生成,要注意的是,基本定时器并不具备该功能。
不同型号的芯片包含的TIM模块可能不同,使用时建议先查询数据手册。

使用特权

评论回复
5
yellow555|  楼主 | 2023-7-26 17:18 | 只看该作者
配置时基单元
要使用该功能,首先要配置时基单元,时基单元可以理解为定时器的心脏,它主要包含:
● 计数器寄存器(TIMx_CNT)
● 预分频器寄存器 (TIMx_PSC)
● 自动装载寄存器 (TIMx_ARR)
之间的关系是:

驱动定时器的时钟:CK_CNT=CK_INT/(PSC+1),CNT开始计数,由ARR决定产生更新事件。

使用特权

评论回复
6
yellow555|  楼主 | 2023-7-26 17:19 | 只看该作者
预分频器
预分频器可以将计数器的时钟频率按1到65536之间的任意值进行分频,选中的时钟由分频器分频后进行计数,在这里使用内部时钟INT,经过分频后CK_CNT=CK_INT/(PSC+1)

使用特权

评论回复
7
yellow555|  楼主 | 2023-7-26 17:21 | 只看该作者
那么如何得知内部时钟的频率呢?参考时钟树,可以发现:
在STM32F10xxx中,TIM1和TIM8挂载在APB2总线上,其他都挂载到APB1总线上

使用特权

评论回复
8
yellow555|  楼主 | 2023-7-26 17:21 | 只看该作者
定时器时钟频率分配由硬件按以下2种情况自动设置: 1. 如果相应的APB预分频系数是1,定时器的时钟频率与所在APB总线频率保持一致2. 否则,定时器的时钟频率被设为与其相连的APB总线频率的2倍。
在标准库中,APB1与APB2预分频系数是2,所以定时器时钟TIMxCLK=36MHz*2=72MHz

使用特权

评论回复
9
yellow555|  楼主 | 2023-7-26 17:22 | 只看该作者
计数器模式

向上计数模式
在向上计数模式中,计数器CNT从0计数到自动加载值(ARR中的内容),然后重新从0开始计数并且产生一个计数器溢出事件。
这里还有一个自动加载影子寄存器,简单来说就是当写入新数据至影子计数器时,若ARPE=0,则ARR寄存器值立即装入,若ARPE=1,要等到下一个更新事件才会写入。

使用特权

评论回复
10
yellow555|  楼主 | 2023-7-26 17:22 | 只看该作者
向下计数模式
在向下计数模式中,计数器从自动加载值开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出的事件。

使用特权

评论回复
11
yellow555|  楼主 | 2023-7-26 17:22 | 只看该作者
中央对齐模式
在这里使用向上或者向下计数模式

**
在PWM模式(模式1或模式2)下,TIMx_CNT和TIMx_CCRx始终在进行比较。
PWM模式1与PWM模式2的描述如下:(见STM32F10xxx参考手册)
PWM模式1——在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。
PWM模式2- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为 有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平。

使用特权

评论回复
12
yellow555|  楼主 | 2023-7-26 17:22 | 只看该作者
综上所述,产生PWM波的步骤如下:
1.配置预分频寄存器PSC的值为(7200-1),内部时钟为72MHz,则计数一次的时间为(7200/72000000)s=0.1ms;
2.配置自动装载寄存器ARR的值为(200-1),那么产生一次计数中断的周期为20ms,与驱动舵机需要的周期相符
3.使用输出比较功能的PWM模式,更改CCR寄存器的值即可改变占空比,如前面所说的,脉宽为1ms对应0°,2ms对应180°

使用特权

评论回复
13
yellow555|  楼主 | 2023-7-26 17:25 | 只看该作者
四、实战——先从控制一个舵机入手
在这里,将以十分常见的SG90舵机为例(下图),使用stm32C8T6驱动
长这个样子

首先,我们新建bsp_servo.h与bsp_servo.c文件

使用特权

评论回复
14
yellow555|  楼主 | 2023-7-26 17:26 | 只看该作者
/*先使用TIM1的通道1*/
#define            GENERAL_TIM                   TIM3
#define            GENERAL_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            GENERAL_TIM_CLK               RCC_APB1Periph_TIM3
//配置预分频寄存器PSC的值为(7200-1),内部时钟为72MHz,则计数一次的时间为(7200/72000000)s=0.1ms;
#define            GENERAL_TIM_Prescaler         (72000-1)
//配置自动装载寄存器ARR的值为(200-1),那么产生一次计数中断的周期为20ms,与驱动舵机需要的周期相符
#define            GENERAL_TIM_Period            (20-1)

// TIM3 输出比较通道1
#define            GENERAL_TIM_CH1_GPIO_CLK      RCC_APB2Periph_GPIOA
#define            GENERAL_TIM_CH1_PORT          GPIOA
#define            GENERAL_TIM_CH1_PIN           GPIO_Pin_6

使用特权

评论回复
15
yellow555|  楼主 | 2023-7-26 17:26 | 只看该作者
之后来到bsp_servo.c文件,配置GPIO端口为复用,输出端口为PA6,具体使用哪个端口可以参考对应型号的data sheet。

static void GENERAL_TIM_GPIO_Config(void) 
{
  GPIO_InitTypeDef GPIO_InitStructure;

  // 输出比较通道1 GPIO 初始化
        RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH1_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);
}

使用TIM功能的PWM模式
static void GENERAL_TIM_Mode_Config(void)
{
  // 开启定时器时钟,即内部时钟CK_INT=72M
        GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);

/*--------------------时基结构体初始化-------------------------*/       
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
        // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
        TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_Period;       
        // 驱动CNT计数器的时钟 = Fck_int/(psc+1)
        TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_Prescaler;       
        // 时钟分频因子 ,配置死区时间时需要用到
        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;               
        // 计数器计数模式,设置为向上计数
        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;               
        // 重复计数器的值,没用到不用管
        TIM_TimeBaseStructure.TIM_RepetitionCounter=0;       
        // 初始化定时器
        TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
       
       
        /*--------------------输出比较结构体初始化-------------------*/       
       
        TIM_OCInitTypeDef  TIM_OCInitStructure;
        // 配置为PWM模式1
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
        // 输出使能
        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
        // 输出通道电平极性配置       
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
       
        // 输出比较通道 1
        TIM_OCInitStructure.TIM_Pulse = 0;
        TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
        TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
       
                // 使能计数器
        TIM_Cmd(GENERAL_TIM, ENABLE);
}


定义一个初始化函数,同时初始化时基与输出比较
void GENERAL_TIM_Init(void)
{
        GENERAL_TIM_GPIO_Config();
        GENERAL_TIM_Mode_Config();               
}

使用特权

评论回复
16
yellow555|  楼主 | 2023-7-26 17:26 | 只看该作者
这样初始化配置就完成了,接下来来到main.c使用TIM_SetComparex(TIMx, Compare);来配置占空比。

使用特权

评论回复
17
yellow555|  楼主 | 2023-7-26 17:27 | 只看该作者
首先定义一个延时函数,使舵机有时间转动特定的角度

void Delay(__IO uint32_t nCount)         //简单的延时函数
{
        for(; nCount != 0; nCount--);
}

使用特权

评论回复
18
yellow555|  楼主 | 2023-7-26 17:27 | 只看该作者
为了方便使用定义一个宏

#define SOFT_DELAY Delay(0x0FFFFF);

使用特权

评论回复
19
yellow555|  楼主 | 2023-7-26 17:28 | 只看该作者
前面将TIM3配置为PWM模式1,即为向上计数,CNT<CCR时通道为有效电平,这一段时间即为脉冲宽度,ARR=(200-1),要使舵机在0°到180°旋转,CRR应在10-20

使用特权

评论回复
20
yellow555|  楼主 | 2023-7-26 17:28 | 只看该作者
int main(void)
{
        GENERAL_TIM_Init();
       
        while(1)
        {
                for(int i = 5;i < 26;i++)//结果测试,设置CCR的值为5-6对应舵机旋转到0°-180°
                {
                        TIM_SetCompare1(GENERAL_TIM , i);
                        SOFT_DELAY;
                }
        }
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

37

主题

464

帖子

3

粉丝