STM32
直播中

卞轮辉

7年用户 1038经验值
私信 关注
[问答]

如何使用stm32F103C8T6的TIM功能去控制多个SG90舵机呢

怎样使用STM32F103C8T6的tiM功能去控制一个SG90舵机呢?
如何使用stm32F103C8T6的TIM功能去控制多个SG90舵机呢?

回帖(1)

李宏

2021-11-25 14:45:51
舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。使用stm32控制机器时,经常要用到舵机,如使某个部位转到特定的角度,或者在行进过程中的方向控制,这篇文章将以stm32F103C8T6为例,从分析舵机的原理出发,到介绍使用stm的TIM功能输出PWM波,掌握理论后进行实战,先控制一个舵机上手,然后控制多个舵机。
  一、舵机的原理

一个舵机由变速齿轮箱,电位器,电路板与直流电机组成。电机的高速、短周期运动由齿轮箱转换为慢速、长周期的运动,最终到最外端的齿轮,齿轮的转动带动电位器转动,电位器的电位与信号线进行比较,从而实现转动到特定角度的功能。
伺服电机由信号线输入的PWM信号控制。信号的频率应为50Hz,周期为20ms,PWM的占空比决定了舵机旋转到的角度。
  二、如何控制一个舵机

  舵机的控制由一个脉冲宽度调制信号(PWM波)来实现,该信号在这个实验里使用stm32来发出。

  

  

通常来说,1ms的脉宽对应0度位置,1.5ms对应90度,2ms对应180度。这一数据会由于舵机型号不同而略有差异。
  三、使用stm32TIM功能输出PWM波来控制舵机

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

  

  

驱动定时器的时钟:CK_CNT=CK_INT/(PSC+1),CNT开始计数,由ARR决定产生更新事件。
  预分频器
预分频器可以将计数器的时钟频率按1到65536之间的任意值进行分频,选中的时钟由分频器分频后进行计数,在这里使用内部时钟INT,经过分频后CK_CNT=CK_INT/(PSC+1)

  

  

那么如何得知内部时钟的频率呢?参考时钟树,可以发现:
在STM32F10xxx中,TIM1和TIM8挂载在APB2总线上,其他都挂载到APB1总线上

  

  


  

  

定时器时钟频率分配由硬件按以下2种情况自动设置: 1. 如果相应的APB预分频系数是1,定时器的时钟频率与所在APB总线频率保持一致2. 否则,定时器的时钟频率被设为与其相连的APB总线频率的2倍。
在标准库中,APB1与APB2预分频系数是2,所以定时器时钟TIMxCLK=36MHz*2=72MHz
  计数器模式
  


  • 向上计数模式

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


  • 向下计数模式

  在向下计数模式中,计数器从自动加载值开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出的事件。
  


  • 中央对齐模式

  在这里使用向上或者向下计数模式
  **
在PWM模式(模式1或模式2)下,TIMx_CNT和TIMx_CCRx始终在进行比较。
PWM模式1与PWM模式2的描述如下:(见STM32F10xxx参考手册)
PWM模式1——在向上计数时,一旦TIMx_CNTTIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。
PWM模式2- 在向上计数时,一旦TIMx_CNTTIMx_CCR1时通道1为有效电平,否则为无效电平。
  综上所述,产生PWM波的步骤如下:
1.配置预分频寄存器PSC的值为(7200-1),内部时钟为72MHz,则计数一次的时间为(7200/72000000)s=0.1ms;
2.配置自动装载寄存器ARR的值为(200-1),那么产生一次计数中断的周期为20ms,与驱动舵机需要的周期相符
3.使用输出比较功能的PWM模式,更改CCR寄存器的值即可改变占空比,如前面所说的,脉宽为1ms对应0°,2ms对应180°
  四、实战——先从控制一个舵机入手

  在这里,将以十分常见的SG90舵机为例(下图),使用stm32C8T6驱动
长这个样子

首先,我们新建bsp_servo.h与bsp_servo.c文件
  本实验使用TIM3的输出通道1,在bsp_servo.h文件中定义宏
  
/*先使用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


之后来到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();               
}


这样初始化配置就完成了,接下来来到main.c使用TIM_SetComparex(TIMx, Compare);来配置占空比。


首先定义一个延时函数,使舵机有时间转动特定的角度


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


为了方便使用定义一个宏


#define SOFT_DELAY Delay(0x0FFFFF);


前面将TIM3配置为PWM模式1,即为向上计数,CNT

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;
                }
        }
}


   此时将舵机上电,信号线连接到PA6,可以看到舵机旋转

五、控制多个舵机

  控制一个舵机成功后,进一步控制多个舵机,先以TIM3增加输出通道为例,这里增加Channel2,输出端口配置为PA7

// TIM3 输出比较通道2
#define            GENERAL_TIM_CH2_GPIO_CLK      RCC_APB2Periph_GPIOA
#define            GENERAL_TIM_CH2_PORT          GPIOA
#define            GENERAL_TIM_CH2_PIN           GPIO_Pin_7


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);
       
         // 输出比较通道1 GPIO 初始化
        RCC_APB2PeriphClockCmd(GENERAL_TIM_CH2_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH2_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH2_PORT, &GPIO_InitStructure);
}


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);
       
        // 输出比较通道 2
        TIM_OCInitStructure.TIM_Pulse = 0;
        TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
        TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
       
                // 使能计数器
        TIM_Cmd(GENERAL_TIM, ENABLE);
}


然后来到main.c,这里是使用TIM_SetCompare2(GENERAL_TIM ,Compare);对于函数
TIM_SetComparex(TIMy,Compare);设置的是TIMy模块CHx的脉宽。


int main(void)
{
        GENERAL_TIM_Init();
       
        while(1)
        {
                for(int i = 5;i<26;i++)
                {
            TIM_SetCompare1(GENERAL_TIM ,i);
                        SOFT_DELAY;
                }
                for(int i = 5;i<26;i++)
                {
            TIM_SetCompare2(GENERAL_TIM ,i);
                        SOFT_DELAY;
                }
        }
}


  可以看到两个舵机按照程序转动起来。

之后要增加控制舵机的数据,可以先增加TIMx的通道数,一个通用或者高级定时器有四个输出通道,还可以增加新的TIM,达到控制多个舵机的目的。为了避免篇幅过于冗长,这里不挂出具体代码。
最后再来一个挂载四个的。

  
举报

更多回帖

发帖
×
20
完善资料,
赚取积分