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低电平时,正传,反之则倒转
显然,我们通过控制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控制电机驱动完成
评论一个我看看?
试试二级回复
山楂树
酸枣树