FTM模块既可以用于PWM模式来产生PWM波控制电机和舵机,也可以用于正交解码模式用于读取编码器的脉冲数,从而实现测速。对于有两个电机的智能车,两个电机需要4路PWM波即对应着4个FTM通道(根据底层库文档:FTM0有8个通道,而FTM1、FTM2都只有2个通道,因此最好用FTM0来控制两个电机),两个编码器也需要4路通道来解码(用掉了FTM1和FTM2),剩下的舵机则只能用PIT定时器来模拟出一个PWM波了
观察上图,一旦你选定FTM模块号(FTM0、1、2)和对应的通道号,那么引脚也就确定下来了。或者,即使你仍旧还是配置了对应的GPIO口,那么这些GPIO口必须与用到的这些FTM通道对应的引脚一致
一、电机配置
只需配置对应的额FTM模块即可,在画原理图时电机需要接和FTM通道所对应的的那几个引脚。接下来是配置FTM来驱动电机
1.声明FTM的配置结构体
static FTM_InitTypeDef FTM_Init;
2.选择FTM模块号(0、1、2共三个,区别在于通道数不同,对应的引脚有差别)
FTM_Init.FTM_Ftmx = FTM0;//选择FTM0号
3.选择FTM的工作模式——选择PWM模式。产生PWM波可以驱动电机,正交解码主要用于编码器计脉冲数来测速
FTM_Init.FTM_Mode = FTM_MODE_PWM;//PWM模式
4.在PWM模式下需要确定输出频率。即一个周期最多有多少份,占空比不同,则控制电机的转速不同
FTM_Init.FTM_PwmFreq = 15000;//总共15000份
5.死区设置。防止同一个电机下的两路同时导通造成破坏,因此,4路共需要2个死区。
FTM_Init.FTM_PwmDeadtimeVal = 2;//死区个数为2
FTM_Init.FTM_PwmDeadtimeCfg = DEADTIME_CH45 | DEADTIME_CH67;//在通道4、5之间,6、7之间各插入一个死区
6.初始化或者使能
LPLD_FTM_Init(FTM_Init);//配置初始化
//使能,并且可以选定通道和引脚的对应关系
LPLD_FTM_PWM_Enable(FTM0, FTM_Ch4, 0, PTD4, ALIGN_LEFT);//FTM0的通道ch4和引脚PTD4对应
LPLD_FTM_PWM_Enable(FTM0, FTM_Ch5, 0, PTD5, ALIGN_LEFT);
LPLD_FTM_PWM_Enable(FTM0, FTM_Ch6, 0, PTD6, ALIGN_LEFT);
LPLD_FTM_PWM_Enable(FTM0, FTM_Ch7, 0, PTD7, ALIGN_LEFT);
使能过程确定了很多配置:模块号、通道号、初始占空比、通道号对应的引脚号、脉冲对齐方式
int16 PWM[4];
void MotorOut(int16* PWM)
{
//修改某FTM模块下的某个通道的占空比
LPLD_FTM_PWM_ChangeDuty(FTM0, FTM_Ch4, PWM[0]);
LPLD_FTM_PWM_ChangeDuty(FTM0, FTM_Ch5, PWM[1]);
LPLD_FTM_PWM_ChangeDuty(FTM0, FTM_Ch6, PWM[2]);
LPLD_FTM_PWM_ChangeDuty(FTM0, FTM_Ch7, PWM[3]);
}
8.进一步的,在中断中调用电机占空比的修改函数,这样就涉及到了定时器PIT的配置以及对应的中断优先级的配置,以下简单说明:
static PIT_InitTypeDef PIT_InitStructure;//声明PIT配置结构体
PIT_InitStructure.PIT_Pitx = PIT0;//选择PIT模块号
PIT_InitStructure.PIT_PeriodMs = 5;//定时周期5ms
PIT_InitStructure.PIT_Isr = pit0_isr;//中断函数
LPLD_PIT_Init(PIT_InitStructure);//初始化
LPLD_PIT_EnableIrq(PIT_InitStructure);//使能中断
PIT模块号有0、1、2、3,共4个定时器
定时周期T:每隔T时间就会触发一次定时器中断,调用对应的中断函数
中断函数:周期调用此函数,故可以把电机输出函数放在这里从而实现周期控制电机输出而不是一直控制,造成资源浪费
8.2 PIT中断优先级的配置
static NVIC_InitTypeDef NVIC_InitStructure;//声明NVIC配置结构体
NVIC_InitStructure.NVIC_IRQChannel = PIT0_IRQn;//要配置的那个中断号即配置PIT0中断
NVIC_InitStructure.NVIC_IRQChannelGroupPriority = NVIC_PriorityGroup_2;//中断分组
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//配置抢占式优先级为最高级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//配置响应式优先级为次高级
LPLD_NVIC_Init(NVIC_InitStructure);//初始化NVIC
8.3 编写中断函数
void pit0_isr()
{
//每5ms修改一次电机转速
MotorOut(&PWM);
}
8.4 另外,上面这些函数显然不能都放在一个.c文件中,由此又涉及到了多个.c文件之间的协调,主要就在对应的.h文件的编写
8.5 PWM[ ]数组来自于根据速度偏差和PID算法计算出来的,在初期可先用常数值控制让小车匀速运动,然后再添加控制程序
二、编码器配置
编码器用的是FTM的正交解码模式,这里配置了一个编码器
1.配置GPIO口,这些GPIO口和FTM正交解码通道对应
static GPIO_InitTypeDef encoder_init_struct;//声明GPIO结构体
encoder_init_struct.GPIO_PTx = PTB;//选择GPIO组
encoder_init_struct.GPIO_Pins =GPIO_Pin18|GPIO_Pin19;//选择引脚,一个编码器有两个引脚
encoder_init_struct.GPIO_Dir = DIR_INPUT;//方向选择
encoder_init_struct.GPIO_PinControl = INPUT_PULL_UP; //内部上拉
LPLD_GPIO_Init(encoder_init_struct);//初始化
2.配置FTM为正交解码模式
static FTM_InitTypeDef Init_FTM_Struct;//声明结构体
Init_FTM_Struct.FTM_Ftmx = FTM2;//选择模块号
Init_FTM_Struct.FTM_Mode = FTM_MODE_QD;//正交解码模式
Init_FTM_Struct.FTM_QdMode = QD_MODE_PHAB;//AB相解码模式,和编码器类型有关
LPLD_FTM_Init(Init_FTM_StructR);
LPLD_FTM_QD_Enable(FTM2, PTB18, PTB19);//使能,将模块号和引脚对应
void GetSpeed()
{
static int16 l_count[5];
int16 L_count,L_count0,i;
L_count0 = LPLD_FTM_GetCounter(FTM2);//读取FTM2的计数器值
LPLD_FTM_ClearCounter(FTM2);//读取完就清除
l_count[0] = L_count0;
for(i=4;i>0;i--)//滤波
{
l_count[i] = l_count[i-1];
}
L_count = 0.4*l_count[0]+0.2*l_count[1]+0.2*l_count[2]+0.1*l_count[3]+0.1*l_count[4];//滤波后的值
//通过计数器值来计算车速和距离,和读取脉冲数的周期间隔有关,另外还需要测一下一米对应的脉冲数,方便推测
}
4 . 定时器周期调用GetSpeed()函数,先配置PIT(同前面),然后放在PIT中断函数中以实现周期调用
5 . 由编码器脉冲数推测速度和行驶距离。设调用周期是T,行驶1m所产生的脉冲数是C,在T时间内调用GetSpeed()函数得到的脉冲数是L_count,则1s内产生的脉冲数应该是 L_count / T,对应的距离应该是(L_count / T)/ C,也就是1s内的行驶距离即速度
6 .根据测的当前速度和设定目标速度求速度偏差,然后通过PID算法算出应该输出的PWM占空比值,输出到电机
三、舵机控制
1.需配置舵机信号线 —— GPIO口
static GPIO_InitTypeDef Servo_GPIO;
Servo_GPIO.GPIO_PTx = PTD;//引脚组
Servo_GPIO.GPIO_Pins = GPIO_Pin12;//引脚口
Servo_GPIO.GPIO_Dir = DIR_OUTPUT;//方向,CPU输出给舵机
Servo_GPIO.GPIO_Output = OUTPUT_L;//初始电平
LPLD_GPIO_Init(Servo_GPIO);
2 . PIT模拟产生PWM波。先是定时调用舵机输出即信号线拉高,然后用这个PIT定时再把舵机输出拉低,需要两个PIT
2.1 配置PIT0来定时调用舵机输出
void pit0_isr()
{
//定时调用舵机输出
ServOut(pwm);
}
2.2 配置PIT1来定时拉低舵机PWM从而实现模拟PWM波
static PIT_InitTypeDef Servo_pit;
Servo_pit.PIT_Isr = Servo_pit_isr;//中断函数
Servo_pit.PIT_PeriodUs = 200000;//200ms,随便写的,不影响
Servo_pit.PIT_Pitx = PIT1;//模块号
LPLD_PIT_Init(Servo_pit);
LPLD_PIT_EnableIrq(Servo_pit);
PIT->CHANNEL[1].TCTRL &= ~PIT_TCTRL_TEN_MASK;//停止计时。然后过了20ms再通过PIT1中断函数开始计时,实现了每20ms反转一次电平
// 上面是配置舵机的程序片段
//PIT1的中断函数
void Servo_pit_isr()
{
PTD12_O = 0;//拉低
PIT->CHANNEL[1].TCTRL &= ~PIT_TCTRL_TEN_MASK;//修改寄存器的值以停止计时
}
//舵机输出,每20ms调用一次,由PIT0的中断函数调用
void ServOut(pwm)
{
PIT->CHANNEL[1].LDVAL = pwm*(g_bus_clock / 1000000) - 1;//把PID计算出的打角转成占空比加载到PIT的LDVAL寄存器中
PTD12_O = 1;//信号线电平拉高
PIT->CHANNEL[1].TCTRL |= PIT_TCTRL_TEN_MASK;//开始计时即CVAL寄存器开始自减,减到0则触发PIT1的中断,
//对应的中断函数Servo_pit_isr中执行的是把电平拉低并停止计时
}
//上面是两个PIT配合形成的PWM波
LDVAL寄存器:存放计数值,自减,当减到0时,PIT就会触发中断。但真正自减的并不是这个寄存器而是另外一个即CVAL寄存器,当CVAL减到0,PIT重新把LDVAL的值加载到CVAL寄存器
CVAL寄存器:真正在自减的寄存器,减到0则触发中断
TCTRL寄存器:2位可用,一位决定定时器是否开始工作即CVAL寄存器是否开始自减,一位决定是否产生定时中断
过程图示为:
观察发现:
Servout()函数调用周期是20ms(由PIT0决定),而且主要负责拉高电平和给PIT1的LVDAL赋值即决定PIT1的中断周期,这时PIT1的中断周期不再是配置时的那个200ms了。当LVDAL加载的时间用尽而触发了PIT1的中断,调用对应的Servo_pit_isr()中断函数再把舵机信号线电平拉低并且停止计时,等待下一次给LVDAL装载值,而这个下一次给LVDAL装载值就是下一次调用Servout()函数(此时距上次过去了20ms)
至于为什么是20ms,这和舵机基准信号的周期是20ms有关,可参考这篇介绍:https://blog.csdn.net/weixin_38907560/article/details/81546006
通过两个PIT的配合,我们模拟出了一个PWM波,其中PIT0负责拉高和决定高电平持续时间,PIT1负责拉低电平
总结一下:
1.电机:FTM的PWM工作模式,一个电机有两路分别控制正反转,不能同时导通,需要配置死区,使能的时候把通道号与引脚号对应起来,通过底层库函数修改占空比值来控制电机转速,最好在中断中调用
2.编码器:FTM的正交解码模式,一个编码器有两路(AB相或者方向计数),可通过库函数直接读取对应FTM的值即编码器的值,同样最好是在中断中调用,并介绍了如何由编码器计数值来推测小车的速度和行驶的距离
3.舵机:舵机的基准信号的周期是20ms,因此调用舵机输出函数的周期也是20ms(是为了配合模拟PWM波),一个函数负责拉高信号线电平和决定高电平持续时间,一个负责拉低电平
以上是我关于电机、编码器、舵机方面配置的总结