赞
踩
前言:最近一段时间在准备智能车考核,其中要求使用TC264单片机实现PID控制的小车摄像头循迹。其中关于PID的部分在之前我已经上传过了,这篇文章主要讲怎么实现循迹与舵机的位置式PID调参和电机的增量式调参的一些心得。
一、摄像头循迹的实现
首先我们要明白英飞凌公司旗下的小钻风摄像头为灰度摄像头,它收集到的图像信号为黑白灰像素点构成的图像,那我们想要小车能识别到赛道边缘从而实现循迹,就需要对采集到的图像进行二值化处理。
那么什么叫二值化处理呢,下面我结合代码进行说明。
- int16 i,j;
- uint8 mt9v03x_image_2[120][188];
- uint8 th=100;//阈值大小
-
-
- mt9v03x_init();//摄像头初始化
- tft180_set_dir(TFT180_CROSSWISE_180); // 需要横屏不然显示不下
- tft180_init();//屏幕初始化
- if(mt9v03x_finish_flag)//这个if函数内部进行对采集到的每一帧画面进行处理
- {
- erzhihua();//二值化函数
- tft180_displayimage03x((const uint8 *)mt9v03x_image_2, 160, 128);//将处理的图像显示出来
- mt9v03x_finish_flag = 0;
- }
-
-
-
- void erzhihua(void)
- {
-
- //说明:摄像头采集的图像会以数组的形式存起来,其中数组的里面的数据为0~255的uint8型的数,其中0为黑色,255为白色
- int i,j;
-
- for(i=0;i<MT9V03X_H;i++)
- {
- for(j=0;j<MT9V03X_W;j++)
- {
- mt9v03x_image_2[i][j] = mt9v03x_image[i][j];
- }
- }//为防止二值化对原始数据的破坏导致不能正常输出,我们复制了一个一样的数组,对其进行操作
-
-
- for(i=0;i<MT9V03X_H;i++)//行遍历
- {
- for(j=0;j<MT9V03X_W;j++)//列遍历
- {
- if(mt9v03x_image[i][j]<th)
- {
- mt9v03x_image[i][j]=0;
- mt9v03x_image_2[i][j]=0; //小于阈值变黑
- }
- else
- {
- mt9v03x_image[i][j]=255;
- mt9v03x_image_2[i][j]=255; //大于阈值变白
- }
- }
- }
- }
简单来说二值化就是将黑白灰图像转化为只有黑白的图像,通过阈值的合理设定达到对赛道边缘的黑化(便于检测)的效果。
但是二值化仅仅使得我们知道了赛道边缘在哪里,而我们想要小车保持在赛道中间,就需要控制转向的舵机的配合。具体怎么配合呢?
首先,我们要将得到二值化图像进行处理,将赛道的中线表示出来。此时这个所谓的赛道中线其实本质上就是一串数组对应的宽和高。通过将得到的赛道中线的水平位置与屏幕显示的中间位置(就是数组的宽的一半)进行比较,算出小车与赛道中心的误差值,将这个误差值输入到舵机的PID中进行计算,最后输出PWM给舵机进行转向。
具体代码如下
- int16 i,j;
- uint8 mt9v03x_image_2[120][188];
- uint8 th=100;//阈值大小
- uint8 mid[120];
- int side_left[120],side_right[120];
-
- void erzhihua(void)
- {
-
-
- int i,j;
-
- for(i=0;i<MT9V03X_H;i++)
- {
- for(j=0;j<MT9V03X_W;j++)
- {
- mt9v03x_image_2[i][j] = mt9v03x_image[i][j];
- }
- }
-
-
- for(i=0;i<MT9V03X_H;i++)
- {
- for(j=0;j<MT9V03X_W;j++)
- {
- if(mt9v03x_image[i][j]<th)
- {
- mt9v03x_image[i][j]=0;
- mt9v03x_image_2[i][j]=0; //小于阈值变黑
- }
- else
- {
- mt9v03x_image[i][j]=255;
- mt9v03x_image_2[i][j]=255; //大于阈值变白
- }
- }
- }
- }
-
-
-
- void xunji_Mid(void)
- {
- erzhihua();
- int i,j;
-
- for(i=0;i<MT9V03X_H;i++)
- {
- for(j=80;j<MT9V03X_W;j++)
- {
- if( mt9v03x_image_2[i][j]==0)
- { side_right[i]=j;break;}//从屏幕中间开始向两边寻找黑点,找到就记下黑点的横坐标
- }
- for(j=80;j>=0;j--)
- {
- if( mt9v03x_image_2[i][j]==0)
- { side_left[i]=j;break;}
- }
- mid[i]=(side_right[i]+side_left[i])/2;//赛道中线就是左右端点之和除以二
-
- }
- for(int i=120;i>70;i--)
- {
- feedbackpositionleijia+=mid[i];
- }
- feedbackposition= feedbackpositionleijia/50;//为提高数据可信度,减小误差,将50个中点求和算平均值
- feedbackpositionleijia=0;
- }
-
- void show_line(void)//将中线与边界线在显示的图像中描黑
- {
-
- int i;
- for(i = MT9V03X_H-1; i >= 0; i--)
- {
- //画赛道中线
- if(mid[i] < MT9V03X_W && mid[i] >= 0)
- {
- mt9v03x_image_2[i][side_left[i]] = 0;
- mt9v03x_image_2[i][side_right[i]] = 0;
- mt9v03x_image_2[i][mid[i]] = 0;
- }
- }
-
- }
-
-
- if(mt9v03x_finish_flag)
- {
- erzhihua();
- xunji_Mid();
- show_line();
- tft180_displayimage03x((const uint8 *)mt9v03x_image_2, 160, 128);
- mt9v03x_finish_flag = 0;
- }//最后写在主函数循环里面的显示函数
-
-
-
-
-
-
- PID_Calc_wz(&mypid_Angle,targetposition,feedbackposition);//位置式pid控制舵机
- int angleout=dutyzhong+mypid_Angle.output;
- pwm_set_duty(SERVO_MOTOR_PWM,angleout);
-
-
- int dutyzhong=720;//每个舵机的中点对应的PWM占空比不同,一般在750上下
- float feedbackposition;
- float feedbackpositionleijia;
- float targetposition=80;//这里获取小车目标位置
-
-
-
-
- void PID_Init(PID *pid,float p,float i,float d,int16 maxI,int16 maxOut)
- {
- pid->kp=p;//调参只要改动这三个值就行p,i,d
- pid->ki=i;
- pid->kd=d;
- pid->maxIntegral=maxI;//最大积分
- pid->maxOutput=maxOut;//最大输出值
- }
-
- 进行一次位置式pid计算
- 参数为(pid结构体,目标值,反馈值),计算结果放在pid结构体的output成员中
- void PID_Calc_wz(PID *pid,float reference,float feedback)
- {
- //更新数据
- pid->lastError=pid->error;//将旧error存起来
- pid->error=reference-feedback;//计算新error
- //计算微分
- int dout=(pid->error-pid->lastError)*pid->kd;//误差的差值乘以Kd
- //计算比例
- int pout=pid->error*pid->kp;//误差乘以Kp
- //计算积分
- pid->integral+=pid->error*pid->ki;//误差的累加乘以Ki
- //积分限幅
- if(pid->integral > pid->maxIntegral) pid->integral=pid->maxIntegral;//积分过大则限幅赋值为max
- else if(pid->integral < -pid->maxIntegral) pid->integral=-pid->maxIntegral;//积分过小则赋值为min
- //计算输出
- pid->output=pout+dout+pid->integral;//输出值为P+I+D
- //输出限幅
- if(pid->output > pid->maxOutput) pid->output=pid->maxOutput;
- else if(pid->output < -pid->maxOutput) pid->output=-pid->maxOutput;
-
- if(pid->output < pid->minOutput) pid->output=pid->minOutput;
-
-
- }
-
二、PID调参技巧
位置式PID:先调Kp,使得波形在目标值上下震荡,且幅度会逐渐减小。接下来就加入Kd来对震荡进行抑制,当Kd过大时就没有抑制作用,反而会使得波形不停震荡。最后加入Ki来使得最后趋于稳定的值更趋近于目标值。当你初步调节完成PID参数时,先记录下此时的参数,然后回过去继续微调Kp、Kd、Ki。直到最后波形达到一定的响应与稳定时才算调好了参数。而且每个电机的PID参数不尽相同,需要单独调节。
增量式PID:因为由于公式为增量,Ki对应的量才是error,所以先调Ki。接着再调Kp,最后调Kp。具体调到什么情况合适参考位置式PID。
可以用vofa这个软件,用电脑这个上位机来实时显示波形,便于我们的调参。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。