您现在的位置: 晨光科技 >> 文章 >> 技术 >> 单片机 >> 正文  
  Arduino编码器小车左右转速同步的PID调节实验         
Arduino编码器小车左右转速同步的PID调节实验
[ 作者:佚名    转贴自:http://www.eefocus.com/zhang700309/blog/13-06/294572_38be0.html    点击数:59    更新时间:2018/5/10    文章录入:LA ]
[注:本站登载的某些文章并不代表本站支持或反对其观点或肯定其真实性]
 

    制作模型车的DIY爱好者会有这样的经验,你给小车左右两侧轮子的驱动电机同样的功率值,但两个轮子的转速可能是不一样,而且不同功率值下,两个轮子的转速差也不一样。当然如果两个轮子在行进过程中,分别遇到的阻力不一样时,两个轮子的转速也会发生偏差。这样的左右两轮转速不一致,就会反映在小车不能走直线上,这是让DIY爱好者头痛的事!

    如果驱动轮子的电机安装了编码器,情况会如何?编码器这样的传感器是可以测量转速和转角的。如果把左右电机中编码器反馈的转速送入Arduino单片控制器中,计算机就会知道小车不能走直线的真正原因,并实时计算出两轮的转速差,然后可以设立一个PID算法,让转速慢轮子的电机功率值加大,从而消除车轮的转速差。这就是最简单的PID控制理论应用到实践中的一次尝试吧。

    我们在大学学到了很多、甚至更高深的理论,但有机会实践这些理论的机会少之又少,其实制作DIY模型,就可以方便地去实践这些理论的。为这次的理论实践,我采用的模型是DFRobot公司出品的mini编码器小车,如下图。

图1 DFRobot编码器小车全景图

    如何才能直观地看到小车左右车轮电机转速和功率值的变化以及PID比例调节的效果,小车上的Arduino单片机控制器是做不到这点的,我采用的是上位机LabVIEW制作的前面板作为人机交互的界面,来实现对它们的监控。Arduino控制器作为下位机,接受上位机的小车左电机功率值,并把左右电机的转速和PID调节后右电机的功率值反馈到上位机去显示出来。上下位机的串口通信采用了XBee 1mW Zigbee100米传输无线数传模块套装,目的是使USB有线通信变为无线通信模式。如图2所示。

 

图2编码器小车与PC机的XBee无线通信设备

    LabVIEW人机交互界面如下图所示,这个图是实验当时用截图软件截屏而得,可以看到,当左电机的PWM功率值为68时,PID调节后的右电机PWM功率值需为60,才能使左右电机转速一致。(注意:左右电机转速相差5,并不说明是转速不同,而是编码器的分辨率不够,因为这个小车编码器码盘的齿数只有12个。)

 

图3编码器小车左右转速同步的PID调节实验人机交互面板

    PID算法放在下位机Arduino程序中,这个指令是:

val_right=(float)val_right+(rpm1-rpm2)*0.4;

    式子中rpm1是左电机的转速;rpm2是右电机的转速;val_right是右电机的PWM功率值。左右电机的转速差乘以比例因子0.4叠加到上一次PID计算得到的val_right,就得到了当前右电机PWM功率值val_right。如果左电机转速rpm1比右电机转速rpm2要低,则从算式中可以看到,会自动减小右电机功率值val_right,通过循环执行Arduino程序中PID算式,以达到两电机转速一致的目的。

    一个有趣的实验,可以边实验边观察。如果您拿起这个编码器小车,用手摩擦小车的左轮,左轮的转速变慢,在PID比例算式的作用下,右轮电机的PWM功率值会变小,以使右轮速度也跟着变慢。这时,再把手移到小车右轮,右轮遇到阻力时,为了保证依然与左轮转速同步,在LabVIEW前面板反馈的右电机PWM功率值,您会观察到它的值再不断增加。所以一个PID比例算法的功效,通过编码器小车这个物理设备和LabVIEW这个人机交互界面,就真真切切地感受到了。

    LabVIEW虚拟仪器程序由前面板和框图程序组成,前面板是人机交互的界面,界面上有用户输入和显示输出两类控件;框图程序则是用户编制的程序源代码,以定义和控制在前面板上的控件输入和输出功能。这个LabVIEW上位机程序用到了事件结构的编程技术,以响应前面板上的四个按钮,这个四个按钮分别为“前进”、“后退”、“停止”和“退出”。图4为框图程序的“前进”事件分支的程序截图。(双击图片,可以放大!

 

图4框图程序的“前进”事件分支

    从图4看出,如果按下前面板的“前进”按钮,在后台程序中就会引发“前进”:值改变事件分支,这个分支程序的任务是先把三个字节数据转换为一个字符串,再输入到VISA写入VI中,这个VI就会通过RS322通信协议把三个字节下达给小车上的Arduino控制器。三个字节中第一个字节如果是0x11(十六进制),则表示小车启动,若是0x22,则小车停止;第二个字节决定小车前进或者后退,0xAA表示前进,0xBB表示后退;第三个字节是小车左电机的PWM功率值,功率值的大小由图3中旋钮控件来调节。图4是框图程序的“前进”事件分支,这三个字节的值,大家都看到了,再想想如果是按下“后退”或“停止”按钮,它们相应的事件分支中三个字节分别应为多少?

    如果没有按钮按下事件,框图程序就会执行“超时”分支,如图5所示,在这个分支里,VISA读取VI循环读取Arduino下位机上传的5个字节字符串,利用“字符串至字节数组转换”VI和“索引数组”,把字符串变为5个字节的数据,从数组一个个取出来,数组的第0和1元素字节分别是左电机转速值的高、低字节,第2和3元素字节分别是右电机转速值的高、低字节。把高字节和低字节带入算式:高字节×256+低字节,就得到了电机的转速。数组的第5元素字节就是Arduino上传的PID校正后的右电机PWM功率值。

 

图5 框图程序的“超时”事件分支

    要退出一个包含事件结构的While循环,可以使用“退出”按钮,将其连线到While循环的条件端子,要特别注意的是,必须将“退出”按钮布尔控件放在事件结构中,并为“退出”按钮配置处理“值改变”事件的事件分支。如图6所示。如果“退出”按钮在事件结构外面,事件结构将一直等待,即使单击了“退出”按钮也无济于事。

 

图6框图程序的“退出”事件分支

LabVIEW程序请下载:http://yunpan.cn/QeKeTEJiiTwEm (访问密码:64a5)

    与上位机LabVIEW程序相配合的是在编码器小车上Arduino控制器的C程序。这个C程序的任务是:接受上位计算机的小车前进、后退、停止以及小车左电机功率设置字节指令,并通过L298P直流电机驱动板,驱动直流伺服电机按照上位机指令运行。采集左右两车轮的电机中编码器脉冲信号,并处理成电机转速信息,计算出两电机的转速差,根据转速差,进行PID比例调节以增大或减小右车轮电机功率值,从而达到左右车轮转速一致。然后把两车轮转速值和右轮电机功率值上传给上位机显示。

Arduino程序: 

//定义变量程序段
//把小车左轮电机编码器码盘的OUTA信号连接到Arduino控制器的数字端口2,
//数字端口2是Arduino的外部中断0的端口。
#define PinA_left 2 //外部中断0
#define PinB_left 8 //小车左车轮电机编码器码盘的OUTB信号连接到数字端口8 
//把小车右车轮电机编码器码盘的OUTA信号连接到Arduino控制器的数字端口3,
//数字端口3是Arduino的外部中断1的端口。
#define PinA_right 3 //外部中断1
#define PinB_right 9 //小车右车轮电机编码器码盘的OUTB信号连接到数字端口9
int E_left =5; //L298P直流电机驱动板的左轮电机使能端口连接到数字接口5
int M_left =4; //L298P直流电机驱动板的左轮电机转向端口连接到数字接口4
int E_right =6; //连接小车右轮电机的使能端口到数字接口6
int M_right =7; //连接小车右轮电机的转向端口到数字接口7
int val_right; //小车右轮电机的PWM功率值
int val_start;//上位机控制字节,用于控制电机是否启动;
int val_FB;   //上位机控制字节,用于控制电机是正转还是反转;
int val_left;//上位机控制字节,用于提供给左轮电机PWM功率值。
int count1 = 0;  //左轮编码器码盘脉冲计数值
int count2= 0; //右轮编码器码盘脉冲计数值
int rpm1 = 0;  //左轮电机每分钟(min)转速(r/min)
int rpm2 = 0;  //右轮电机每分钟(min)转速(r/min)
int rpm1_HIGH = 0;//左轮电机转速分解成高、低两个字节数据,以方便上传给PC机
int rpm1_LOW = 0;
int rpm2_HIGH = 0;//右轮电机转速分解成高、低两个字节数据
int rpm2_LOW = 0;
int flag;//设置小车行车状态,是前进、后退还是停止
unsigned long time = 0, old_time = 0; // 时间标记
unsigned long time1 = 0, time2 = 0; // 时间标记
//初始化程序段
void setup()
{
  Serial.begin(9600);    // 启动串口通信,波特率为9600b/s
  pinMode(M_left, OUTPUT);   //L298P直流电机驱动板的控制端口设置为输出模式
  pinMode(E_left, OUTPUT); 
  pinMode(M_right, OUTPUT); 
  pinMode(E_right, OUTPUT);
  pinMode(PinA_left,INPUT); //伺服电机编码器的OUTA和OUTB信号端设置为输入模式
  pinMode(PinB_left,INPUT);
  pinMode(PinA_right,INPUT); 
  pinMode(PinB_right,INPUT);
  //定义外部中断0和1的中断子程序Code(),中断触发为下跳沿触发
  //当编码器码盘的OUTA脉冲信号发生下跳沿中断时,
  //将自动调用执行中断子程序Code()。
  attachInterrupt(0, Code1, FALLING);//小车左车轮电机的编码器脉冲中断函数
  attachInterrupt(1, Code2, FALLING);//小车右车轮电机的编码器脉冲中断函数
}
//子程序程序段
void advance()//小车前进
{
     digitalWrite(M_left,HIGH);
     analogWrite(E_left,val_left);
     digitalWrite(M_right,LOW);
     analogWrite(E_right,val_right);
}
void back()//小车后退
{
     digitalWrite(M_left,LOW);
     analogWrite(E_left,val_left);
     digitalWrite(M_right,HIGH);
     analogWrite(E_right,val_right);
}
void Stop()//小车停止
{
     digitalWrite(E_right, LOW);
     digitalWrite(E_left, LOW); 
} 
  
//主程序段
void loop()
{
  if (Serial.available()>0) //如果Arduino控制器读缓冲区中存在上位机下达的字节
  {
      val_start= Serial.read(); //从读缓冲区中读取上位机的三个控制字节
      delay(5); 
      val_FB = Serial.read(); 
      delay(5);  
      val_left= Serial.read(); 
      delay(5);      
     if(val_start==0x11)     //如果读出的第一个字节为小车启动标志字节0x11
     {
       if(val_FB ==0xAA)   //如果读出的第二个字节为小车前进标志字节0xAA
       { 
         //读出的第三个字节为小车左车轮电机的PWM功率值,把它赋值给右车轮电机功率变量
         val_right=val_left; 
         advance(); //小车前进
         flag='a';   //设置小车前进标志字符     
         count1 = 0; //恢复到编码器测速的初始状态
         count2 = 0;
         old_time=  millis();    
       }
       else if(val_FB ==0xBB) //如果读出的第二个字节为小车后退标志字节0xBB    
       { 
         val_right=val_left;
         back();  //小车后退
         flag='b'; //设置小车后退标志字符       
         count1 = 0; //恢复到编码器测速的初始状态
         count2 = 0;
         old_time=  millis();        
       }
     }
      else if(val_start==0x22) //如果读出的第一个字节为小车停止标志字节0x22
      { 
         Stop(); //小车停止
         flag='s'; //设置小车停止标志字符         
      }
  }
  time = millis();//以毫秒为单位,计算当前时间 
  //计算出每一秒钟编码器码盘计得的脉冲数,
  if(abs(time - old_time) >= 1000) // 如果计时时间已达1秒
  {
    detachInterrupt(0); // 关闭外部中断0
    detachInterrupt(1); // 关闭外部中断1    
     //把每一秒钟编码器码盘计得的脉冲数,换算为当前转速值
     //转速单位是每分钟多少转,即r/min。这个编码器码盘为12个齿。
    rpm1 =(float)count1*60/12;//小车左车轮电机转速
    rpm2 =(float)count2*60/12; //小车右车轮电机转速
    rpm1_HIGH=rpm1/256;//把转速值分解为高字节和低字节
    rpm1_LOW=rpm1%256; 
    rpm2_HIGH=rpm2/256;
    rpm2_LOW=rpm2%256; 
   //根据左右车轮转速差rpm1-rpm2,乘以比例因子0.4,获得比例调节后的右车轮电机PWM功率值
    val_right=(float)val_right+(rpm1-rpm2)*0.4; 
    Serial.print(rpm1_HIGH,BYTE);//向上位计算机上传左车轮电机当前转速的高、低字节
    Serial.print(rpm1_LOW,BYTE);
    Serial.print(rpm2_HIGH,BYTE);//向上位计算机上传右车轮电机当前转速的高、低字节
    Serial.print(rpm2_LOW,BYTE);
    Serial.print(val_right,BYTE);// 向上位计算机上传PID调节后的右轮电机PWM功率值   
    if(flag=='a') //根据刚刚调节后的小车电机PWM功率值,及时修正小车前进或者后退状态
    advance();
    if(flag=='b') 
    back();   
   //恢复到编码器测速的初始状态
    count1 = 0;   //把脉冲计数值清零,以便计算下一秒的脉冲计数
    count2 = 0; 
    old_time=  millis();     // 记录每秒测速时的时间节点   
    attachInterrupt(0, Code1,FALLING); // 重新开放外部中断0
    attachInterrupt(1, Code2,FALLING); // 重新开放外部中断1
  }
}
// 左侧车轮电机的编码器码盘计数中断子程序
void Code1()
{  
  //为了不计入噪音干扰脉冲,
   //当2次中断之间的时间大于5ms时,计一次有效计数
  if((millis()-time1)>5) 
  //当编码器码盘的OUTA脉冲信号下跳沿每中断一次,
  count1 += 1; // 编码器码盘计数加一  
  time1==millis();
}
// 右侧车轮电机的编码器码盘计数中断子程序
void Code2()
{  
  if((millis()-time2)>5) 
  //当编码器码盘的OUTA脉冲信号下跳沿每中断一次,
  count2 += 1; // 编码器码盘计数加一
  time2==millis();  
}

    最后看看两张图,一个是小车的安装图,另一张是小车用的锂电池。至于小车用到的DFRobot Mini Encoder Kit编码器套件的详细介绍,请看文章: 

http://www.eefocus.com/zhang700309/blog/13-05/294304_fddf6.html

图7 编码器小车安装

图8  8.4V、3500mAh锂聚合物电池

  • 上一篇文章: 蓝牙技术架起LabVIEW和Arduino之间无线沟通的桥梁

  • 下一篇文章: 基于LabVIEW和Arduino上下位机通信的编码器转速表的设计
  •    
    [注:标题搜索比内容搜索快]
    发表评论】【告诉好友】【打印此文】【关闭窗口
     最新5篇热点文章
  • 轨道钢承重计算公式及应用[109]

  • 【选型】如何为变频器选取阻值…[86]

  • AIS2023参展厂商名录[345]

  • AGV综合选型[170]

  • APIE 2023第4届亚太国际智能装…[138]

  •  
     最新5篇推荐文章
  • 外媒:正在唤醒中国的习近平[305]

  • 中国反伪科学运动背后的CIA黑手…[494]

  • [转载]袁隆平真言:中国最大的…[668]

  • 台专家:当年我们造IDF时 大陆…[572]

  • 旅日华人:中国严重误判日本民…[577]

  •  
     相 关 文 章
  • Arduino和STM32性能对比究竟谁…[26]

  • Labview及Proteus软件环境下单…[38]

  • 基于labview串口通讯的虚拟数字…[41]

  • AVR编程语言Arduino也可以用Pr…[47]

  • Arduino编程 PWM方法调节LED光…[37]


  •   网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
        没有任何评论
    设为首页 | 加入收藏 | 联系站长 | 友情链接 | 版权申明 | 管理登录 | 
    版权所有 Copyright© 2003 晨光科技        站长:璀璨星辰        页面执行时间:271.48毫秒
    Powered by:MyPower Ver3.5