STM32F103-定时器编码器模式和高级定时器输出互补的PWM信号
刚使用STM32时使用输入捕获中断的方式计算编码器的速度从而得到电机的转速,经过一段时间的学习发现使用输入捕获中断的方式得到电机的转速,在电机转速不高的情况下可以很好的得到电机的速度,但是当电机转速很高时,STM32会很频繁的进入中断,单片机花费在中断处理的时间会很长,影响系统正常的运行。
经过了解,使用STM32定时器的编码器模式,可以不使用输入捕获中断就可以得到电机转速。
在这里很感谢网上大神的无私分享,让我收获很多也学习了很多。
编码器如何产生A,B两相信号和定时器编码器基础知识就不在这过多的说明,大家网上可以看到很多资料。在这里就再着重说一下编码器模式的难点。这张图个人感觉是整个编码器模式最难理解的部分。个人的理解是:假如有A,B两相编码器信号,A相此时比B相提前90度。在下图第三种情况下:若相对信号电平信号为B相信号,此时B相电平为高电平,此时A相信号下降沿,那么计数器向上计数一次;此时B相电平为低电平,此时A相信号上升沿,那么计数器向上计数一次。若相对信号电平信号为A相信号,此时A相电平为高电平,此时B相信号上升沿,那么计数器向上计数一次;此时A相电平为低电平,此时B相信号下降沿,那么计数器向上计数一次。所以计数器一共会计数4次,提高了计数精度。在后面的程序中除以4就是因为编码器模式采用了这种4倍频。(个人水平有限,如有问题还请包涵~谢谢)
下面是定时器2编码器模式和定时器8输出互补的PWM信号和定时器3中断的具体配置:
(1),将定时器2两个引脚开启时钟和配置成浮空输入
(2),将定时器2配置成编码器模式,A相和B相信号发生变化时都对计数器进行计数。
(3),设定定时器2计数器初始值
(4),配置定时器8,输出两路互补的PWM波控制电机
(5),配置定时器3,每0.05S中断一次读取定时器2计数器的值,计算编码器的速度。并且每次中断更新定时器2的初值
//*****************主函数*****************//
#include "sys.h"
#include "delay.h"
#include "timer.h"
//#include "usart.h"
#include "SEGGER_RTT.h"
int16_t Signal_Fre = 0;
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
//uart_init(115200); //串口初始化为115200
TIM8_PWM_Init(899,3); //PWM频率=72000000/900/4=20Khz
TIM2_Encoder_Init(65535,0); //定时器2编码器模式
TIM3_Int_Init(4999,719);//0.05s定时器溢出中断
TIM8_PWMStopGPIO_Init();
delay_ms(10);
SEGGER_RTT_printf(0,"Test \r\n");
while(1)
{
TIM_SetCompare1(TIM8,450);
TIM_SetCompare2(TIM8,0);
delay_ms(10);
}
}
//**********定时器配置****************//
#include "timer.h"
//#include "usart.h"
#include "SEGGER_RTT.h"
extern int16_t Signal_Fre;
///
//TIM2--编码器模式
//TIM3--0.05秒定时-计算编码器速度
//TIM8--PWM互补输出
///
/***************TIM2初始化********************/
//通用定时器2--编码器模式,PA0,PA1;
//arr:自动重装值
//psc:时钟预分频数
void TIM2_Encoder_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能定时器2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period=arr;
TIM_TimeBaseStructure.TIM_Prescaler=psc;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_BothEdge,TIM_ICPolarity_BothEdge); //编码器模式
TIM_ICStructInit(&TIM_ICInitStructure);
//TIM_ICInitStructure.TIM_Channel=TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter=6;
//TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
//TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
//TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_ICInit(TIM2,&TIM_ICInitStructure);
TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清除更新标志位
//TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除中断标志位
TIM_SetCounter(TIM2,32768);
TIM_Cmd(TIM2,ENABLE);
}
/****************************TIM3初始化*********************************************/
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period=arr;
TIM_TimeBaseStructure.TIM_Prescaler=psc;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM3,ENABLE);
}
/*********************************TIM3中断处理***************************************/
void TIM3_IRQHandler()
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
Signal_Fre = TIM_GetCounter(TIM2);
Signal_Fre = (int)Signal_Fre * 20 / 4; //定时器0.05s中断一次,并且编码器模式是4倍频,计算出编码器的速度
TIM_SetCounter(TIM2,32768);
SEGGER_RTT_printf(0,"Signal_Fre=%d \r\n",Signal_Fre);
SEGGER_RTT_printf(0,"TIM3IRQ \r\n");
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx更新中断标志
}
}
/********************TIM8初始化***********************/
//TIM8 PWM部分初始化
//CH1:PC6
//CH1N:PA7
//CH2:PC7
//CH2N:PB0
//CH3:PC8
//CH3N:PB1
//BKIN:PA7
//arr:自动重装值
//psc:时钟预分频数
void TIM8_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE); //使能定时器8时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC , ENABLE); //使能GPIO外设功能模块时钟
//设置该引脚为复用输出功能,输出TIM8 CH1的PWM脉冲波形 GPIOC.6
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //TIM_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIO
//设置该引脚为复用输出功能,输出TIM8 CH1N的PWM脉冲波形 GPIOA.7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //TIM_CH1N
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
//设置该引脚为复用输出功能,输出TIM8 CH2的PWM脉冲波形 GPIOC.7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIO
//设置该引脚为复用输出功能,输出TIM8 CH1N的PWM脉冲波形 GPIOB.0
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //TIM_CH2N
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
//设置该引脚为复用输出功能,输出TIM8 CH3的PWM脉冲波形 GPIOC.8
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIO
//设置该引脚为复用输出功能,输出TIM8 CH3N的PWM脉冲波形 GPIOB.1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //TIM_CH3N
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
//刹车引脚PA6
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //BKIN
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
//初始化TIM1
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM1 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; //互补比较输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //占空比 CCR的值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
//TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; //互补输出极性:TIM输出比较极性高
//TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low ; //互补输出极性:TIM输出比较极性低
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
TIM_OC1Init(TIM8, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM8
TIM_OC1PreloadConfig(TIM8, TIM_OCPreload_Enable); //使能TIM1在CCR上的预装载寄存器
TIM_OCInitStructure.TIM_Pulse = 0; //占空比 CCR的值
TIM_OC2Init(TIM8, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM8
TIM_OC2PreloadConfig(TIM8, TIM_OCPreload_Enable); //使能TIM1在CCR上的预装载寄存器
//TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable; //刹车引脚恢复后引脚输出状态
TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable; //刹车引脚不能恢复后引脚输出状态
TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable; //刹车使能
TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High; //刹车引脚高电平刹车
TIM_BDTRInitStructure.TIM_DeadTime = 36; //死区时间36:500ns
TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF;
TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
//TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Disable;
TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
//TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Disable;
TIM_BDTRConfig(TIM8,&TIM_BDTRInitStructure);
TIM_Cmd(TIM8, ENABLE); //使能TIM8
TIM_CtrlPWMOutputs(TIM8,ENABLE); //主输出使能,当使用通用定时器时,这个不需要
}
void TIM8_PWMStopGPIO_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_Cmd(TIM8, DISABLE); //失能TIM8
//设置该引脚为复用输出功能,输出TIM8 CH1的PWM脉冲波形 GPIOC.6
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //TIM_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIO
GPIO_ResetBits(GPIOC,GPIO_Pin_6); //PC.6 输出低
//设置该引脚为复用输出功能,输出TIM8 CH1N的PWM脉冲波形 GPIOA.7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //TIM_CH1N
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
GPIO_ResetBits(GPIOA,GPIO_Pin_7); //PA.7 输出低
//设置该引脚为复用输出功能,输出TIM8 CH2的PWM脉冲波形 GPIOC.7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIO
GPIO_ResetBits(GPIOC,GPIO_Pin_7); //PC.7 输出低
//设置该引脚为复用输出功能,输出TIM8 CH1N的PWM脉冲波形 GPIOB.0
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //TIM_CH2N
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
GPIO_ResetBits(GPIOB,GPIO_Pin_0); //PB.0 输出低
}
void TIM8_PWMStartGPIO_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_Cmd(TIM8, ENABLE); //使能TIM8
//设置该引脚为复用输出功能,输出TIM8 CH1的PWM脉冲波形 GPIOC.6
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //TIM_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIO
//设置该引脚为复用输出功能,输出TIM8 CH1N的PWM脉冲波形 GPIOA.7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //TIM_CH1N
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
//设置该引脚为复用输出功能,输出TIM8 CH2的PWM脉冲波形 GPIOC.7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIO
//设置该引脚为复用输出功能,输出TIM8 CH1N的PWM脉冲波形 GPIOB.0
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //TIM_CH2N
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
}
使用STM32高级定时器输出PWM信号,使用单极性模式控制电机。STM32高级定时器在空闲状态希望两路互补的PWM信号都为低电平,但是在实际应用中发现一路互补的PWM信号,在空闲时一个为低电平但是另外一个恒为高电平,这在实际应用中肯定会导致问题,所以我想的方法是:在不需要输出PWM信号的时候,使用TIM8_PWMStopGPIO_Init()这个函数,在这个函数里将高级定时器失能,并且将两路四个PWM输出引脚推挽输出强制拉低,确定四个引脚都为低电平,不会引起电机运行。在需要输出PWM信号的时候再使能高级定时器,输出PWM信号。
我使用的是RTT打印,这样使用比较方便,打印速度比较快,而且还不占用USART端口。
目前该程序可以准确读出编码器的转速,但是也有几点疑惑和问题:
(1),对于高级定时器,在不输出PWM信号时,失能定时器8并且将定时器8四个引脚推挽输出强制拉低;在输出PWM信号时,再使能定时器8。这种方法是否可行或者有更好的方法呢?
(2),在定时器3中可以加入PID算法,这样可以使用PID调节电机转速。目前这个功能已经实现。但是我想使用CAN和LIN通信组成一个系统,目前CAN通信已经可以实现,但是目前LIN通信我调试了已经有一段时间了,一直没有成功。想请问一个各位大神能否发我一些资料或者代码也可以留一个联系方式,想请教一下各位。
(最后再重申一下哈,本人水平有限,如有错误还请多多包涵,谢谢~)