提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
该文章主要是记录本人在完成2020年电赛C题的过程中所遇到的问题以及完成情况。好了,废话不多说,直接进入主题。
一、小车的总体构成
首先,小车运用红外传感器进行循迹,通过在探测到黑线后,通过检测引脚上产生的电平信号转变成具有特定脉宽的PWM,从而控制舵机转动相应的角度。通过L298N驱动电机,同时霍尔编码器记录轮子转动时产生的脉冲数,通过PID控制算法进行闭环计算,使得小车达到以一个稳定的速度运动的状态。
二、原理介绍及程序实现
1.红外传感器
由于该模块在本项目中并未采用PID循迹,应用起来相对来说比较简单。所以在此就不再做相关的描述了。
2.舵机的转向实现
这是舵机转动相应的角度所需的脉宽。 注意:这是没有保护程序的舵机,有保护程序的舵机一次能转动大约相较于前一位置的脉宽正负0.2ms,超过这个幅度,舵机可能不会进行相应的转动
上代码:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能GPIOB
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//使能TIM4
//初始化GPIO口
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct); //初始化GPIOB
//定时器配置
Time_InitStruct.TIM_Prescaler=7199; //设置分频因子
Time_InitStruct.TIM_Period=199; //设置计数周期
Time_InitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //不分频
Time_InitStruct.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM4,&Time_InitStruct); //初始化定时器4
//PWM波输出配置
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1; //选择PWM1模式
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High; //输出极性高
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;//输出使能比较
TIM_OC4PreloadConfig(TIM4,TIM_OCPreload_Enable); //TIM4定时器自动重装载使能
TIM_OC4Init(TIM4,&TIM_OCInitStruct); //TIM4输出通道4配置
TIM_Cmd(TIM4,ENABLE); //启动定时器
在这里,结构体的定义就没有给出了。
通过对定时器分频因子和计数周期的设置,我们可以得到定时器产生的PWM周期,即(7199+1)*(199+1)/72000000=0.02s,即产生脉冲的周期为20ms,于是我们就可以通过调节高电平的脉宽来驱动舵机转动。
TIM_SetCompare4(TIM4,5);
TIM_SetCompare4(TIM4,10);
TIM_SetCompare4(TIM4,15);
TIM_SetCompare4(TIM4,20);
TIM_SetCompare4(TIM4,25);
通过对TIM4的通道4设置比较值即可达到目的。
3.编码器
stm32的定时器自带编码器模式。接下来就讲一下编码器计数的原理,通过对单一引脚的高低电平的检测,通过对上升沿或者下降沿(当然也可以同时在上升沿和下降沿进行捕获,后文会提出)进行计数。看图:
上面两种实质上是一样的,只是采样的边沿不一样。同时精度很低。一个周期只能进行一次计数。
下面这一种结合上升沿和下降沿采样进行计数。因为同时在上升沿和下降沿计数,所以其精度会比单一边沿计数提高一倍。看图:
从图中就可清晰看出,双边沿采样的的精度比前两种提高了。
接下来讲述一种精度更高的边沿计数方法,从编码器点击分别引入A,B相至单片机定时器引脚处,其中A、B相之间会有90度的相位差,也可以参考下面附上的草图。这时候我们可以结合另一相的高低电平状态和前两种计数方法来进行计数。以此来达到精度较高的计数,我们也称之为“四倍频计数”。接下来,具体讲述其计数方法。
上图为计数的点位。
下图为具体的计数点位:
即当A相检测到为上升沿时,此时如果B相的电平状态为高电平时,编码器进行向下计数(即获取的编码器数值减一)
当A相检测到为下降沿时,如果此时B相的电平状态为低电平,编码器进行向下计数。
当B相检测到为上升沿时,如果此时A相的电平状态为低电平,编码器也进行向下计数。
当B相检测到为下降沿时,如果此时A相的电平状态为高电平,编码器一样进行向下计数。
接下来是编码器进行向上计数的四种情形:
当A相检测到为上升沿时,如果此时B相的电平状态为低电平,编码器进行向上计数。
当A相检测到为下降沿时,如果此时B相的电平状态为高电平,编码器进行向上计数。
当B相检测到为上升沿时,如果此时A相的电平状态为高电平,编码器进行向上计数。
当B相检测到位下降沿时,如果此时A相的电平状态为低电平,编码器进行向上计数。
通过外部中断对此产生中断事件,判断另一相的状态,即可完成相应的计数。当然,stm32的定时器右相应的编码器模式:
使用这个即可完成编码器模式:TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity)
第一个参数为完成编码器模式的定时器,第二个参数为编码器计数模式,可有以下参数可供选择
TIM_EncoderMode_TI1 在定时器的连接Tl1的引脚进行计数(即我们前文提及的A相)
TIM_EncoderMode_TI2 在定时器的连接Tl2的引脚进行计数(即我们前文提及的B相)
TIM_EncoderMode_TI12 在定时器的连接编码器的引脚进行计数(即四倍频技术)
剩下的两个参数都配置为TIM_ICPolarity_Falling即可。
4.定时器的相关配置
在这里,就假定你们具有stm32定时器相关的配置的知识了,我就具体只给出相关配置的代码了。
上代码:
TIM_TimeBaseInitTypeDef TIM_InitStruct; //定时器结构体
NVIC_InitTypeDef NVIC_InitStruct; //中断结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE); //时钟使能
TIM_InitStruct.TIM_Prescaler=719; //分频因子
TIM_InitStruct.TIM_Period=9999; //计数周期
TIM_InitStruct.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_InitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //时钟不分频
TIM_TimeBaseInit(TIM5,&TIM_InitStruct); //定时器5初始化
TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE); //定时器溢出中断配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断组
NVIC_InitStruct.NVIC_IRQChannel=TIM5_IRQn; //中断源
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
TIM_Cmd(TIM5,ENABLE);
其次控制定时器PWM输出的配置已经在舵机那一部分讲解完毕了。
定时器的部分就到此结束了。
5电机控速
通过在定时器的溢出中断中定时获取编码器的计数再通过如下PID控制进而可以完成对电机的速度控制
void TIM5_IRQHandler(void) //定时器5的中断处理函数
{
if(TIM_GetITStatus(TIM5,TIM_IT_Update) != RESET) //判断定时器的溢出中断标志位
{
TIM_ClearITPendingBit(TIM5,TIM_IT_Update); //清除溢出中断标志位
Speed_T2=(TIM2->CNT-1000); //获取编码器计数
Speed_T3=(TIM3->CNT-1000);
Speed=(Speed_T2+Speed_T3)/2;
TIM2->CNT=1000; //恢复至初始值
TIM3->CNT=1000;
}
Speed_Contorl(Speed,Target); //PID输出
}
PID输出,具体可看PID算法原理,该播主讲述很详细,在此我就不在过多介绍。
float MUL;
motor_Pid.motor_error=target-speed;
motor_Pid.motor_sum_error+=motor_Pid.motor_error;
if(motor_Pid.motor_sum_error>=30000)
motor_Pid.motor_sum_error=30000;
MUL=motor_Pid.p*motor_Pid.motor_error+motor_Pid.i*motor_Pid.motor_sum_error+motor_Pid.d*(motor_Pid.motor_error-motor_Pid.motor_last_error);
motor_Pid.motor_last_error=motor_Pid.motor_error;
MUL=0.05*MUL;
return MUL;
总结
分想完毕,以此记录。