Arduino智能小车——小车测速

准备材料

1.     测速模块

2.     2-Arduino小车测速模板安装与测试

3.     固定铜柱

a)      建议铜柱长度30CM,大小为M3

b)      由于铜柱导电,有些电路板如果固定孔设计不当的话很容易导致电路板烧坏,尤其是在以后的项目中可能会用到更高的电压,因此在这里我建议大家可以准备一些尼龙柱来固定电路板。尼龙柱的尺寸跟铜柱尺寸相同M3,30CM。大家也可以顺便买一点尼龙螺丝。

c)       2-Arduino小车测速模板安装与测试

4.     准备杜邦线

5.     驱动模块的安装需要由尼龙柱支撑

6.     2-Arduino小车测速模板安装与测试

7.     驱动模块安装时需要注意,不能影响轮子的正常工作,不能触碰到轮轴上的码盘。

  编码器上有三个引脚分别是“VCC”,“GND”,“OUT”。左右两边两个测速模块的“VCC”引脚接电源或开发板的“5V”或“3.3V”引脚,“GND”接电源或开发板的“GND”引脚,左边测速模块“OUT”接开发板的“3”引脚,右边测速模块“OUT”接开发板的“2”引脚。引脚接错的话可以再随后调试过程中换过来,也可以在代码里更改。

8.     2-Arduino小车测速模板安装与测试2-Arduino小车测速模板安装与测试

9.     测速模块的工作原理比较简单,如下图所示,在于电机同轴的码盘上有很多开孔(光栅),编码器相当于光敏元件。码盘随着小车轮子的运动转动时,码盘(光栅)会不断遮挡光敏元件发出的光波,这时候编码器就会根据光栅的遮挡不断的产生方波信号,方波信号会从“OUT”引脚输出,我们只需不断检测“OUT”引脚的输出,根据方波信号的周期简介计算出小车运行的速度。小车上使用的码盘(光栅)精度不高,在某些高精度的编码器上光栅会更加密集,测量效果会更好。

10.  2-Arduino小车测速模板安装与测试

11.  我们使用的Arduino UNO只有“2”,“3”引脚可以触发外部中断,因此在接线的时候我们便将左右两边的输出“OUT”引脚分别接在“2”“3”引脚上。

12.  在程序初始化阶段中调用函数attachInterrupt(interrupt,function, mode)可以对中断引脚初始化,其中

13.  interrupt:要初始化的外部中断编号,由上表可知我们Arduino UNO只能使用外部中断0和外部中断1;

14.  function:中断服务函数的名字,即当外部中断被触发时,将会自动调用这个函数;

15.  mode:中断触发的方式,可选方式如下

2-Arduino小车测速模板安装与测试

16.  脉宽调制(PWM)基本原理:控制方式就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也就是在输出波形的半个周期中产生多个脉冲,使各脉冲的等值电压为正弦波形,所获得的输出平滑且低次谐波少。按一定的规则对各脉冲的宽度进行调制,即可改变逆变电路输出电压的大小,也可改变输出频率。

17.  2-Arduino小车测速模板安装与测试

18.   通俗一点讲那,就是当如果我们想输出5V电压时,只需一直输出高电平即可;当我们想输出3.75V电压时,那我们就需要在一个周期内(一个高电平和一个低电平为一个周期)3.75÷5=75%时间输出高电平,25%时间输出低电平;同理,如果想输出2.5V电压时,我们需要在一个周期内50%时间输出高电平,50%时间输出低电平。

19.  2-Arduino小车测速模板安装与测试

20.  Arduino UNO开发板上只有带有“~”表示的引脚才具有PWM功能,因此我们在控制驱动时可以使用这几个引脚。

21.  在前面的教程中已经讲过如果想控制驱动的输出时,需要对驱动的“ENA”“ENB”进行控制,因此我们需要将图中被选中部分的两个跳线帽拔掉。并将“ENA”连接Arduino UNO开发板的“5”引脚,“ENB”连接“6”引脚。

22.  由于之前设计不太合理,占用了太多的PWM引脚,因此在代码里对控制小车电机的引脚做了点小改动

23.  16到19号引脚

24.  2-Arduino小车测速模板安装与测试2-Arduino小车测速模板安装与测试

25.  大家记得要把对应的接线也改过来

a)      int leftMotor1 = 5;

b)      int leftMotor2 = 6;

c)       int rightMotor1 = 7;

d)      int rightMotor2 = 8;

e)      现在改为

f)        int leftMotor1 = 16;   

g)      int leftMotor2 = 17;

h)      int rightMotor1 = 18;

i)        int rightMotor2 = 19;

26.  代码如下

a)      #include <ros.h>

b)      #include <Servo.h>

c)       #include<geometry_msgs/Twist.h>

d)       

e)      int leftCounter = 0,rightCounter = 0;

f)        unsigned long time = 0,old_time = 0; //时间标记

g)      unsigned long time1 = 0;

h)      float lv, rv;  //左、右轮速度

i)         

j)        //定义五中运动状态

k)       #define STOP      0

l)        #define FORWARD   1

m)    #define BACKWARD  2

n)      #define TURNLEFT  3

o)      #define TURNRIGHT 4

p)      #define CHANGESPEED 5 //换挡的

q)      //定义需要用到的引脚

r)       int leftMotor1 = 16;

s)       int leftMotor2 = 17;

t)       int rightMotor1 = 18;

u)      int rightMotor2 = 19;

v)       bool speedLevel = 1;

w)      

x)       //调速

y)       int leftPWM =5;

z)       int rightPWM = 6;

aa)    

bb)   //x轴方向的速度

cc)    double lin_vel = 0.0;

dd)   //y轴方向的速度

ee)   double ang_vel = 0.0;

ff)      //定义接受的键

gg)   int cmd_ctrl = 0;

hh)    

ii)       //注册ROS节点

jj)       ros::NodeHandle nh;

kk)     

ll)       //回调函数

mm)voidmessageCb(const geometry_msgs::Twist& vel)

nn)   {

oo)         lin_vel = vel.linear.x;

pp)         ang_vel = vel.angular.z;

qq)         cmd_ctrl = 1 * lin_vel + 3 * ang_vel;

rr)      }

ss)     //设置订阅的消息类型和发布的主题

tt)      ros::Subscriber<geometry_msgs::Twist>sub("/turtle1/cmd_vel", messageCb);

uu)    

vv)    void setup() {

ww)    // put your setup code here,to run once:

xx)          nh.initNode();

yy)          nh.subscribe(sub);

zz)            

aaa)        attachInterrupt(0,RightCount_CallBack, FALLING);

bbb)           attachInterrupt(1, LeftCount_CallBack,FALLING);

ccc)        

ddd)           pinMode(leftMotor1, OUTPUT);

eee)       pinMode(leftMotor2,OUTPUT);

fff)          pinMode(rightMotor1, OUTPUT);

ggg)           pinMode(rightMotor2, OUTPUT);

hhh)       pinMode(leftPWM,OUTPUT);

iii)           pinMode(rightPWM, OUTPUT);

jjj)     }

kkk)   

lll)     void loop() {

mmm)    // put your main code here,to run repeatedly:

nnn)     SpeedDetection();

ooo)         switch(cmd_ctrl)

ppp)       {

qqq)           case 2:

rrr)           motorRun(FORWARD); 

sss)          delay(1000);

ttt)             motorRun(STOP); 

uuu)         break;

vvv)         case -2:

www)            motorRun(BACKWARD);   

xxx)           delay(1000);

yyy)           motorRun(STOP); 

zzz)           break;

aaaa)           case 6:

bbbb)           motorRun(TURNLEFT);    

cccc)              delay(1000);

dddd)           break;

eeee)          case -6:

ffff)           motorRun(TURNRIGHT);   

gggg)           delay(1000);

hhhh)           motorRun(STOP); 

iiii)             break;

jjjj)          default:

kkkk)            motorRun(STOP);      

llll)            break;

mmmm)           }

nnnn)      if (speedLevel) //根据不通的档位输出不同速度

oooo)      {

pppp)          analogWrite(leftPWM, 120);

qqqq)          analogWrite(rightPWM, 120);

rrrr)      }

ssss)     else

tttt)      {

uuuu)          analogWrite(leftPWM, 250);

vvvv)            analogWrite(rightPWM, 250); 

wwww)               }

xxxx)         motorRun(STOP); 

yyyy)        nh.spinOnce();

zzzz)     }

aaaaa) /*

bbbbb)           * *速度计算

ccccc)   */

ddddd)          bool SpeedDetection()

eeeee) {

fffff)       time = millis(); //以毫秒为单位,计算当前时间

ggggg)                if (abs(time - old_time) >= 1000) // 如果计时时间已达1秒

hhhhh)                 {

iiiii)               detachInterrupt(0);// 关闭外部中断0

jjjjj)               detachInterrupt(1);// 关闭外部中断1

kkkkk)              //把每一秒钟编码器码盘计得的脉冲数,换算为当前转速值

lllll)               //转速单位是每分钟多少转,即r/min。这个编码器码盘为20个空洞。

mmmmm)              lv = (float)leftCounter * 60 / 20;//小车车轮电机转速

nnnnn)                       rv = (float)rightCounter * 60 / 20;

ooooo)                      //恢复到编码器测速的初始状态

ppppp)                      leftCounter = 0;  //把脉冲计数值清零,以便计算下一秒的脉冲计数

qqqqq)                      rightCounter = 0;

rrrrr)            old_time = millis();    // 记录每秒测速时的时间节点  

sssss)               attachInterrupt(0,RightCount_CallBack, FALLING); // 重新开放外部中断0

ttttt)             attachInterrupt(1,LeftCount_CallBack, FALLING);

uuuuu)                       return 1;       

vvvvv)        }

wwwww)             else

xxxxx)                return 0;

yyyyy)  }

zzzzz)   

aaaaaa)          

bbbbbb)       /*

cccccc) * *右轮编码器中断服务函数

dddddd)        */

eeeeee)         void RightCount_CallBack()

ffffff)     {

gggggg)             rightCounter++; 

hhhhhh)        }

iiiiii) /*

jjjjjj)  * *左轮编码器中断服务函数

kkkkkk)           */

llllll) void LeftCount_CallBack()

mmmmmm)         {

nnnnnn)              leftCounter++; 

oooooo)       }

pppppp)        

qqqqqq)        

rrrrrr)   //运动控制函数

ssssss) void motorRun(int cmd)

tttttt)    {

uuuuuu)              switch(cmd) {

vvvvvv)                 case FORWARD:

wwwwww)        digitalWrite(leftMotor1, LOW);

xxxxxx)       digitalWrite(leftMotor2,HIGH);

yyyyyy)                 digitalWrite(rightMotor1, HIGH);

zzzzzz)                 digitalWrite(rightMotor2, LOW);

aaaaaaa)               break;

bbbbbbb)         case BACKWARD:

ccccccc)              digitalWrite(leftMotor1, HIGH);

ddddddd)         digitalWrite(leftMotor2, LOW);

eeeeeee)            digitalWrite(rightMotor1, LOW);

fffffff)         digitalWrite(rightMotor2, HIGH);

ggggggg)         break;

hhhhhhh)         case TURNLEFT:

iiiiiii)       digitalWrite(leftMotor1,LOW);

jjjjjjj)       digitalWrite(leftMotor2,  HIGH);

kkkkkkk)              digitalWrite(rightMotor1, LOW);

lllllll)       digitalWrite(rightMotor2,HIGH);

mmmmmmm)          break;

nnnnnnn)         case TURNRIGHT:

ooooooo)          digitalWrite(leftMotor1, HIGH);

ppppppp)         digitalWrite(leftMotor2, LOW);

qqqqqqq)         digitalWrite(rightMotor1, HIGH);

rrrrrrr)       digitalWrite(rightMotor2, LOW);

sssssss)                break;

ttttttt)      case CHANGESPEED:

uuuuuuu)           if (speedLevel)   //接收到换挡命令的时候切换档位

vvvvvvv)                      speedLevel = 0;

wwwwwww)                 else

xxxxxxx)                        speedLevel = 1;

yyyyyyy)                break;

zzzzzzz)             default:

aaaaaaaa)          digitalWrite(leftMotor1, LOW);

bbbbbbbb)       digitalWrite(leftMotor2,LOW);

cccccccc)            digitalWrite(rightMotor1, LOW);

dddddddd)       digitalWrite(rightMotor2, LOW);

eeeeeeee)     }

ffffffff)  }

27.  Arduion的PWM引脚需要和正常引脚一样,在voidsetup()函数中初始化为输出模式

28.   

a)      pinMode(leftPWM, OUTPUT);

b)      pinMode(rightPWM, OUTPUT);

29.  在小车的控制状态函数void motorRun(intcmd)中添加多一个选择项,用来切换速度。

30.   

a)      case CHANGESPEED:

b)            Serial.println("CHANGE SPEED");//输出状态

c)             if(speedLevel)  //接收到换挡命令的时候切换档位

d)              speedLevel=0;

e)            else

f)                speedLevel=1;

g)            break;

31.  在主函数void loop()中添加PWM输出的函数,analogWrite(pin, value)函数中“pin”代表使用的引脚,“value”代表输出PWM值的大小,范围是0~255。

32.   

a)      if(speedLevel)  //根据不通的档位输出不同速度

b)          {

c)             analogWrite(leftPWM, 120);

d)            analogWrite(rightPWM, 120);

e)          }

f)            else

g)          {

h)            analogWrite(leftPWM, 250);

i)              analogWrite(rightPWM, 250);

j)            }

 


相关文章:

  • 2021-12-12
  • 2022-12-23
  • 2022-12-23
  • 2022-01-14
猜你喜欢
  • 2021-10-28
  • 2021-04-27
  • 2021-04-13
  • 2021-09-18
  • 2021-12-07
  • 2022-12-23
  • 2021-08-06
相关资源
相似解决方案