TIM | 电机驱动
TIM | 电机驱动

TIM | 电机驱动

TIM | 电机驱动

TIM是定时器,在STM32上共有TIM1~TIM8共8个定时器,如图所示,有2个高级定时器、4个通用定时器、2个基本定时器。

高级定时器可以输出4路PWM信号(CH1 CH2 CH3 CH4)和对应的4路互补PWM信号(CH1N CH2N CH3N CH4N),外加死区和刹车(BKIN)功能。通用定时器可以输出4路PWM信号,(ETR是啥我没搞懂)。通用定时器为TIM6和TIM7,仅有向上计数模式的定时和中断功能。

基本定时器-以蜂鸣器为例

为了理解定时器的工作方法,先介绍通用定时器的使用方法。这里以使用TIM6控制 BEEP 蜂鸣器的声调为例。

首先要在BEEP.h文件里定义蜂鸣器的GPIO端口,我使用的开发板将蜂鸣器接到了C1口,所以当C1口高电平时蜂鸣器响,低电平时不响。

然后要在BEEP.c文件里编写蜂鸣器占用的C1口的初始化函数。至此,GPIO部分就编写完成,与点亮LED的过程几乎一致。

以下就是通用定时器TIM6的配置部分

/*BEEP使用的基本定时器TIM6*/
#define            BEEP_TIM                   TIM6
#define            BEEP_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            BEEP_TIM_CLK               RCC_APB1Periph_TIM6
#define            BEEP_TIM_Period            1000-1            //这是1ms,改频率的话就改这个1ms
#define            BEEP_TIM_Prescaler         71
#define            BEEP_TIM_IRQ               TIM6_IRQn
#define            BEEP_TIM_IRQHandler        TIM6_IRQHandler//中断服务函数

这里面的 BEEP_TIM_Period 是计数的周期,在这种分频计数下,从0计数到999即为1ms,可以利用这个时长结合主函数内变量的变化来形成长时间的定时。这个period最大值为65535
BEEP.c

// TIM6中断优先级配置
static void BEEP_TIM_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure; 
    // 设置中断组为0
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
        // 设置中断来源
    NVIC_InitStructure.NVIC_IRQChannel = BEEP_TIM_IRQ ;
        // 设置主优先级为 0
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;    
      // 设置抢占优先级为3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

static void BEEP_TIM_Mode_Config(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

        // 开启定时器时钟,即内部时钟CK_INT=72M
    BEEP_TIM_APBxClock_FUN(BEEP_TIM_CLK, ENABLE);

        // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断//这里才是改动period的地方
    TIM_TimeBaseStructure.TIM_Period = BEEP_TIM_Period;

      // 时钟预分频数为
    TIM_TimeBaseStructure.TIM_Prescaler= BEEP_TIM_Prescaler;

        // 时钟分频因子 ,基本定时器没有,不用管
    //TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;

        // 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置
    //TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; 

        // 重复计数器的值,基本定时器没有,不用管
        //TIM_TimeBaseStructure.TIM_RepetitionCounter=0;

      // 初始化定时器
    TIM_TimeBaseInit(BEEP_TIM, &TIM_TimeBaseStructure);

        // 清除计数器中断标志位
    TIM_ClearFlag(BEEP_TIM, TIM_FLAG_Update);

        // 开启计数器中断
    TIM_ITConfig(BEEP_TIM,TIM_IT_Update,ENABLE);

        // 使能计数器
    TIM_Cmd(BEEP_TIM, ENABLE);
}

void BEEP_TIM_Init(void)
{
    BEEP_TIM_NVIC_Config();
    BEEP_TIM_Mode_Config();
}

至此就完成了定时器6的初始化,接下来我们需要编写定时器6计数溢出产生中断时程序执行的命令,即中断服务函数

void  TIM6_IRQHandler (void)//BEEP_TIM 中断服务函数
{
    if ( TIM_GetITStatus( BEEP_TIM, TIM_IT_Update) != RESET ) 
    {
        BEEP_time++;
        TIM_ClearITPendingBit(BEEP_TIM , TIM_FLAG_Update);//清空重新计数   
    }
}

函数名为库函数自带的,用哪个定时器就写TIM几,下面的if语句就是判断TIM6是否溢出了

中断服务函数的具体用法可以看 stm32f10x_it.c ,在库文件里

我们在main.c中定义一个变量 u8 BEEP_time 并在定义中断服务函数的.c文件中定义 extern u8 BEEP_time 这样每一次中断都让BEEP_time自加,在程序中更加容易地控制时间,在main.c中根据实际需要控制蜂鸣器的时间

通用定时器-以电机驱动为例

电机驱动的工作原理

如图,我们将电机的两极接入OUTA 和 OUTB 当OUTA高电平OUTB低电平时,正传,反之则倒转
L298N接口
显然,我们通过控制INA和INB的输入电平和PWM,就能控制电机的旋转方向和速度

注意:电机驱动的GND必须与开发板的GND相连,原因是要让电机驱动与开发板有共同的低电平位置

上面介绍了基础定时器,现在来介绍能输出4路PWM的通用定时器

在实际的项目中,我使用TIM3的4个通道输出PWM来控制电机驱动

首先仍然是.h文件的宏定义

/************通用定时器TIM参数定义,只限TIM2、3、4、5************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 我们这里默认使用TIM3

#define            GENERAL_TIM                   TIM3
#define            GENERAL_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            GENERAL_TIM_CLK               RCC_APB1Periph_TIM3
#define            GENERAL_TIM_Period            1000//这个狗屁Peiod设置太少了还不行呢
#define            GENERAL_TIM_Prescaler         71
// 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

// 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

// TIM3 输出比较通道3
#define            GENERAL_TIM_CH3_GPIO_CLK      RCC_APB2Periph_GPIOB
#define            GENERAL_TIM_CH3_PORT          GPIOB
#define            GENERAL_TIM_CH3_PIN           GPIO_Pin_0

// TIM3 输出比较通道4
#define            GENERAL_TIM_CH4_GPIO_CLK      RCC_APB2Periph_GPIOB
#define            GENERAL_TIM_CH4_PORT          GPIOB
#define            GENERAL_TIM_CH4_PIN           GPIO_Pin_1

这里根据文章开头的TIM通道分配图片选择了4个通道的GPIO输出端口,这个端口必须根据那张图片进行选择,不能随意选。

然后我们在.c文件中写4个GPIO输出端口的初始化函数,这是输出模式为GPIO_Mode_AF_PP;推挽复用输出

TIM3的初始化函数

static void GENERAL_TIM_Mode_Config(void)
{
  // 开启定时器时钟,即内部时钟CK_INT=72M
    GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);

/*--------------------时基结构体初始化-------------------------*/
    // 配置周期,这里配置为100K

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

    /*--------------------输出比较结构体初始化-------------------*/
    // 占空比配置,暂时的配置,后面用TIMx_compare函数控制占空比
    uint16_t CCR1_Val = 5;
    uint16_t CCR2_Val = 5;
    uint16_t CCR3_Val = 5;
    uint16_t CCR4_Val = 5;

    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 = CCR1_Val;
    TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);

    // 输出比较通道 2
    TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
    TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
    TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);

    // 输出比较通道 3
    TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
    TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);
    TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);

    // 输出比较通道 4
    TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
    TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);
    TIM_OC4PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);

    // 使能计数器
    TIM_Cmd(GENERAL_TIM, ENABLE);
}

至此,完成TIM3的初始化配置

要想控制电机的转速,其实就是控制PWM信号的占空比,对于通用定时器来说,使用TIM_SetComparex(TIMx,x)函数控制占空比非常方便。

函数名上的x指 CH1 CH2 CH3 CH4中的哪个通道,第一个形参写指定的定时器,第二个形参写占空比(肯定不能大于Period);

比如,现在的Period是1000,我想让TIM3的CH2通道,也就是A7口输出80%的PWM,那么就可以写TIM_SetCompare2(TIM3,800);

最后,根据实际的电机驱动接线情况,将前进、后退、左转、右转封装成4个函数

输出的PWM占空比并不相同,这是由于项目的需要、电机的转速误差决定的

void Go(void)//左右轮同时前进
{
    TIM_SetCompare1(TIM3,400);
    TIM_SetCompare2(TIM3,0);
    TIM_SetCompare3(TIM3,400);
    TIM_SetCompare4(TIM3,0);
}

void TurnRight(void)//左前进右后退
{
    TIM_SetCompare1(TIM3,900);
    TIM_SetCompare2(TIM3,0);
    TIM_SetCompare3(TIM3,0);
    TIM_SetCompare4(TIM3,800);
}

void TurnLeft(void)//左后退右前进
{

    TIM_SetCompare1(TIM3,0);
    TIM_SetCompare2(TIM3,700);
    TIM_SetCompare3(TIM3,700);
    TIM_SetCompare4(TIM3,0);

}

void Back(void)//左后退右后退
{
    TIM_SetCompare1(TIM3,0);
    TIM_SetCompare2(TIM3,700);
    TIM_SetCompare3(TIM3,0);
    TIM_SetCompare4(TIM3,600);
}

void Stop(void)
{
    TIM_SetCompare1(TIM3,0);
    TIM_SetCompare2(TIM3,0);
    TIM_SetCompare3(TIM3,0);
    TIM_SetCompare4(TIM3,0);
}

至此,PWM控制电机驱动完成

4条评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注