目录
Arduino基础入门
最开始那几天,由于没有找到好用的教程,再加上刚进入这个全新的领域,所以进度十分缓慢,不会连接电路,上传错误等问题频繁出现。但通过同伴的教程推荐,慢慢找到了学习的方法和路径。在通过阅读官方函数参考文档形成初步印象后,我借助Tony的arduino基础篇和中文社区中的小实验,慢慢熟悉了基本函数的用法和电路连接的相关知识,Tony基础篇1到16中的小实验(其他基础实验在做小车需要用到时也作为初步认识做了相关实验)基本都有动手操作。先阅读他人的代码,观察实验现象,再在代码中加入自己的理解进行实验操作。
Tonyの博客_TonyIOT_CSDN博客-Arduino,树莓派,Processing领域博主
Arduino教程汇总贴(2020.2.2更新)-Arduino中文社区 - Powered by Discuz!
这个是我初期学习的一部分内容:
这些有趣的小实验极大程度上激发了我学习arduino的热情。接下来就是学习制作基于arduino板驱动的小车了。
基于arduino板控制小车
让小车动起来
由于Arduino开发板的通用IO驱动能力有限,有些外设不能直接使用IO进行驱动,需要借助一些驱动电路间接控制大功率器件。
因而我们学习使用了电机驱动板L298N,使其接入Arduino板上的信号,将L298N连接上电机两侧的正负接脚,每块L298N连接两个电机,再通过电池给L298N供电,拿其中一块L298N反向给arduino板供电,最终实现了电机的基本转动。
//前轮 int output3=3; int output4=4; int output5=5; int output6=6; //后轮 int output9=9; int output10=10; int output11=11; int output12=12; void reverse() { for(int i=3;i<=6;i++) { if(digitalRead(1)) { digitalWrite(i,HIGH); }else { digitalWrite(i,LOW); } } for(int i=9;i<=12;i++) { if(digitalRead(i)) { digitalWrite(i,HIGH); }else { digitalWrite(i,LOW); } } } void setup() { for(int i=3;i<=5;i++) { pinMOde(i,OUTPUT); } for(int i=9;i<=12;i++) { pinMOde(i,OUTPUT); } } void loop() { //前进 int cnt=0; if(cnt!=0) { reverse(); delay(2000); } digitalWrite(output3,HIGH); //给高电平 digitalWrite(output4,LOW); //给低电平 digitalWrite(output5,HIGH); //给高电平 digitalWrite(output6,LOW); //给低电平 digitalWrite(output9,HIGH); //给高电平 digitalWrite(output10,LOW); //给低电平 digitalWrite(output11,HIGH); //给高电平 digitalWrite(output12,LOW); //给低电平 delay(2000); //延时2秒 digitalWrite(output3,HIGH); //给高电平 digitalWrite(output4,LOW); //给低电平 digitalWrite(output5,HIGH); //给高电平 digitalWrite(output6,LOW); //给低电平 digitalWrite(output9,HIGH); //给高电平 digitalWrite(output10,LOW); //给低电平 digitalWrite(output11,HIGH); //给高电平 digitalWrite(output12,LOW); //给低电平 delay(2000); cnt++; }
学习PWM频率和占空比 初步调速
这部分我们先是直接采用了analogWrite函数进行了调节
void L1_forward(int sp)//左前轮前进 { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,HIGH); analogWrite(L1_ENA,sp); }
利用麦克纳姆轮原理控制全向移动
了解向各个方向移动与各轮转向的关系后,就可通过调节与各轮转向相关的高低电平引脚模式来实现全向移动了。
/************************************************************/ /********硬件:Arduino UNO + L298N电机驱动两个********/ /************************************************************/ int L1_IN1 = 7;int L1_IN2 = 5;int L1_ENA = 6;//左前轮 int R1_IN1 = 4;int R1_IN2 = 2;int R1_ENA = 3;//右前轮 int L2_IN1 = 12;int L2_IN2 = 13;int L2_ENA = 11;//左后轮 int R2_IN1 = 8;int R2_IN2 = 9;int R2_ENA = 10;//右后轮 void setup() { // put your setup code here, to run once: pinMode(L1_IN1, OUTPUT);pinMode(L1_IN2, OUTPUT);pinMode(L1_ENA, OUTPUT); pinMode(R1_IN1, OUTPUT);pinMode(R1_IN2, OUTPUT);pinMode(R1_ENA, OUTPUT); pinMode(L2_IN1, OUTPUT);pinMode(L2_IN2, OUTPUT);pinMode(L2_ENA, OUTPUT); pinMode(R2_IN1, OUTPUT);pinMode(R2_IN2, OUTPUT);pinMode(R2_ENA, OUTPUT); } void L1_forward(int sp)//左前轮前进 { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,HIGH); analogWrite(L1_ENA,sp); } void R1_forward(int sp)//右前轮前进 { digitalWrite(R1_IN1,HIGH); digitalWrite(R1_IN2,LOW); analogWrite(R1_ENA,sp); } void L2_forward(int sp)//左后轮前进 { digitalWrite(L2_IN1,HIGH); digitalWrite(L2_IN2,LOW); analogWrite(L2_ENA,sp); } void R2_forward(int sp)//右后轮前进 { digitalWrite(R2_IN1,HIGH); digitalWrite(R2_IN2,LOW); analogWrite(R2_ENA,sp); } void allstop() { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,LOW); digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,LOW); digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,LOW); digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,LOW); } void L1_backward(int sp)//左前轮后退 { digitalWrite(L1_IN1,HIGH); digitalWrite(L1_IN2,LOW); analogWrite(L1_ENA,sp); } void R1_backward(int sp)//右前轮后退 { digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,HIGH); analogWrite(R1_ENA,sp); } void L2_backward(int sp)//左后轮后退 { digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,HIGH); analogWrite(L2_ENA,sp); } void R2_backward(int sp)//右后轮后退 { digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,HIGH); analogWrite(R2_ENA,sp); } void loop() { // put your main code here, to run repeatedly: /*前进*/ L1_forward(100); R1_forward(100); L2_forward(100); R2_forward(100); delay(1500); allstop(); delay(1500); /*后退*/ L1_backward(100); R1_backward(100); L2_backward(100); R2_backward(100); delay(1500); allstop(); delay(1500); /*顺时针原地旋转*/ L1_forward(200); R1_backward(200); L2_forward(200); R2_backward(200); delay(1500); allstop(); delay(1500); /*逆时针原地旋转*/ L1_backward(200); R1_forward(200); L2_backward(200); R2_forward(200); delay(1500); allstop(); delay(1500); /*左边平移*/ L1_backward(150); R1_forward(150); L2_forward(150); R2_backward(150); delay(1500); allstop(); delay(1500); /*右边平移*/ L1_forward(150); R1_backward(150); L2_backward(150); R2_forward(150); delay(1500); allstop(); delay(1500); /*斜向左上方*/ R1_forward(150); L2_forward(150); delay(1500); allstop(); delay(1500); /*斜向右上方*/ L1_forward(150); R2_forward(150); delay(1500); allstop(); delay(1500); /*斜向左下方*/ L1_backward(150); R2_backward(150); delay(1500); allstop(); delay(1500); /*斜向右下方*/ R1_backward(150); L2_backward(150); delay(1500); allstop(); delay(1500); }
加入遥控小车功能
方案1:红外遥控
通过红外发射二极管发射出去,而红外接收端则要将信号进行解调处理,还原成二进制脉冲码进行处理。当按下遥控器按键时,遥控器发出红外载波信号,红外接收器接收到信号,程序对载波信号进行解码,通过数据码的不同来判断按下的是哪个键。我们依次测试了每个按键代表的红外编码,后期可以完善控制小车运动方向。
/* * IRrecvDemo * 红外控制,接收红外命令控制板载LED灯亮灭 */ #include <IRremote.h> int RECV_PIN = 5; int L1_IN1 = 7;int L1_IN2 = A1;int L1_ENA = 6;//左前轮 int R1_IN1 = 4;int R1_IN2 = 2;int R1_ENA = 3;//右前轮 int L2_IN1 = 12;int L2_IN2 = 13;int L2_ENA = 11;//左后轮 int R2_IN1 = 8;int R2_IN2 = 9;int R2_ENA = 10;//右后轮 IRrecv irrecv(RECV_PIN); decode_results results; void setup() { Serial.begin(9600); irrecv.enableIRIn(); // Start the receiver pinMode(L1_IN1, OUTPUT);pinMode(L1_IN2, OUTPUT);pinMode(L1_ENA, OUTPUT); pinMode(R1_IN1, OUTPUT);pinMode(R1_IN2, OUTPUT);pinMode(R1_ENA, OUTPUT); pinMode(L2_IN1, OUTPUT);pinMode(L2_IN2, OUTPUT);pinMode(L2_ENA, OUTPUT); pinMode(R2_IN1, OUTPUT);pinMode(R2_IN2, OUTPUT);pinMode(R2_ENA, OUTPUT); } void L1_forward(int sp)//左前轮前进 { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,HIGH); for(int i=0;i<=sp;i++) { analogWrite(L1_ENA,sp); } } void R1_forward(int sp)//右前轮前进 { digitalWrite(R1_IN1,HIGH); digitalWrite(R1_IN2,LOW); for(int i=50;i<=sp;i++) { analogWrite(R1_ENA,sp); } } void L2_forward(int sp)//左后轮前进 { digitalWrite(L2_IN1,HIGH); digitalWrite(L2_IN2,LOW); for(int i=50;i<=sp;i++) { analogWrite(L2_ENA,sp); } } void R2_forward(int sp)//右后轮前进 { digitalWrite(R2_IN1,HIGH); digitalWrite(R2_IN2,LOW); //analogWrite(R2_ENA,sp); for(int i=50;i<=sp;i++) { analogWrite(R2_ENA,sp); } } void allstop() { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,LOW); digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,LOW); digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,LOW); digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,LOW); } void L1_backward(int sp)//左前轮后退 { digitalWrite(L1_IN1,HIGH); digitalWrite(L1_IN2,LOW); analogWrite(L1_ENA,sp); } void R1_backward(int sp)//右前轮后退 { digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,HIGH); analogWrite(R1_ENA,sp); } void L2_backward(int sp)//左后轮后退 { digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,HIGH); analogWrite(L2_ENA,sp); } void R2_backward(int sp)//右后轮后退 { digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,HIGH); analogWrite(R2_ENA,sp); } void loop() { if (irrecv.decode(&results)) { Serial.println(results.value, HEX); if (results.value == 0xFFE01F) { L1_forward(100); R1_forward(100); L2_forward(100); R2_forward(100); delay(1500); allstop(); delay(1500); } else if (results.value == 0xFFE21D) { /*后退*/ L1_backward(100); R1_backward(100); L2_backward(100); R2_backward(100); delay(1500); allstop(); delay(1500); } irrecv.resume(); // Receive the next value } delay(100); }
对于以上代码,我们多次改变了引脚号,尝试在UNO板(仅有5个pwm口,数字输出接脚也有限)上完成红外识别和多引脚输出控制步进电机。由于,后期我们还想加上霍尔元件编码器,所以我们改用了Arduino Mega2560作为主控板。
但是此方案接收短,信号稳定性差,易受外界环境干扰影响红外接收端的识别,并且我们发现,对于下面控制程序中的走8字部分,由于耗时较长,从else语句中出来需要一定时间,当按键发出停止指令的时候无法很快执行,目前正在探索解决的方案(硬件中断貌似行不通,所以我们在考虑陈同学推荐的软件中断方法)。
#include <IRremote.h> /*定义红外接收管接线引脚*/ int RECV_PIN = 11;//定义红外接收引脚号 IRrecv irrecv(RECV_PIN); decode_results results;//解码结果放在decode results结构的result中 /*定义电机驱动接线引脚*/ int L1_IN1 = 44; int L1_IN2 = 42; int L1_ENA = 7;//左前轮 int R1_IN1 = 40; int R1_IN2 = 38; int R1_ENA = 6;//右前轮 int L2_IN1 = 52; int L2_IN2 = 50; int L2_ENA = 5;//左后轮 int R2_IN1 = 48; int R2_IN2 = 46; int R2_ENA = 4;//右后轮 void setup() { // put your setup code here, to run once: pinMode(L1_IN1, OUTPUT);pinMode(L1_IN2, OUTPUT);pinMode(L1_ENA, OUTPUT); pinMode(R1_IN1, OUTPUT);pinMode(R1_IN2, OUTPUT);pinMode(R1_ENA, OUTPUT); pinMode(L2_IN1, OUTPUT);pinMode(L2_IN2, OUTPUT);pinMode(L2_ENA, OUTPUT); pinMode(R2_IN1, OUTPUT);pinMode(R2_IN2, OUTPUT);pinMode(R2_ENA, OUTPUT); Serial.begin(9600); // In case the interrupt driver crashes on setup, give a clue // to the user what's going on. Serial.println("Enabling IRin"); irrecv.enableIRIn(); // 启动接收器 Serial.println("Enabled IRin"); } void loop() { if (irrecv.decode(&results)) { Serial.print("HEX格式 》》 "); Serial.println(results.value,HEX);//以16进制换行输出接收代码 Serial.print("默认格式 》》 "); Serial.println(results.value); if (results.value == 0xFF30CF) //1键为前进键 { L1_forward(100); R1_forward(100); L2_forward(100); R2_forward(100); } else if (results.value == 0xFF18E7) //2键为后退键 { L1_backward(100); R1_backward(100); L2_backward(100); R2_backward(100); } else if (results.value == 0xFF7A85) //3键为左边平移键 { L1_forward(150);R1_backward(150); L2_forward(150);R2_backward(150); } else if (results.value == 0xFF10EF) //4键为右边平移键 { L1_backward(150);R1_forward(150); L2_backward(150);R2_forward(150); } else if (results.value == 0xFF38C7) //5键为顺时针原地旋转键 { L1_backward(150);R1_forward(150); L2_forward(150); R2_backward(150); } else if (results.value == 0xFF5AA5) //6键为逆时针原地旋转键 { L1_forward(150); R1_backward(150); L2_backward(150);R2_forward(150); } else if (results.value == 0xFF4AB5) //8键为原地画数字8键 { //前 L1_forward(100); R1_forward(100); L2_forward(100); R2_forward(100); delay(500); allstop(); delay(500); //右 L1_backward(150);R1_forward(150); L2_backward(150);R2_forward(150); delay(800); allstop(); delay(500); //前 L1_forward(100); R1_forward(100); L2_forward(100); R2_forward(100); delay(500); allstop(); delay(500); //左 L1_forward(150); R1_backward(150); L2_forward(150); R2_backward(150); delay(800); allstop(); delay(500); //后 L1_backward(100);R1_backward(100); L2_backward(100);R2_backward(100); delay(500); allstop(); delay(500); //右 L1_backward(150);R1_forward(150); L2_backward(150);R2_forward(150); delay(800); allstop(); delay(500); //后 L1_backward(100);R1_backward(100); L2_backward(100);R2_backward(100); delay(500); allstop(); delay(500); //左 L1_forward(150); R1_backward(150); L2_forward(150); R2_backward(150); delay(800); allstop(); delay(500); } else if (results.value == 0xFF6897) //0键为停止键 { allstop();Serial.println("allstop"); } irrecv.resume(); // Receive the next value Serial.println("-------"); } delay(100); } void L1_forward(int sp)//左前轮前进 { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,HIGH); analogWrite(L1_ENA,sp); } void R1_forward(int sp)//右前轮前进 { digitalWrite(R1_IN1,HIGH); digitalWrite(R1_IN2,LOW); analogWrite(R1_ENA,sp); } void L2_forward(int sp)//左后轮前进 { digitalWrite(L2_IN1,HIGH); digitalWrite(L2_IN2,LOW); analogWrite(L2_ENA,sp); } void R2_forward(int sp)//右后轮前进 { digitalWrite(R2_IN1,HIGH); digitalWrite(R2_IN2,LOW); analogWrite(R2_ENA,sp); } void allstop() { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,LOW); digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,LOW); digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,LOW); digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,LOW); } void L1_backward(int sp)//左前轮后退 { digitalWrite(L1_IN1,HIGH); digitalWrite(L1_IN2,LOW); analogWrite(L1_ENA,sp); } void R1_backward(int sp)//右前轮后退 { digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,HIGH); analogWrite(R1_ENA,sp); } void L2_backward(int sp)//左后轮后退 { digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,HIGH); analogWrite(L2_ENA,sp); } void R2_backward(int sp)//右后轮后退 { digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,HIGH); analogWrite(R2_ENA,sp); }
方案2:利用串口通信,一个板用来接收数据,一个板用来控制输出。
/* * arduino uno端程序 * 串口使用情况 serial -----computer serial1----- nano softwearserial */ #include<SoftwareSerial.h> SoftwareSerial softSerial(6,5); void setup() { //初始化serial,该串口用于与计算机连接通信: Serial.begin(9600); pinMode(13,OUTPUT); //初始化serial1,该串口用于与设备B连接通信; softSerial.begin(9600); softSerial.listen(); } //两个字符串分别用于存储A,B两端传来的数据 String device_A_String=""; String device_B_String=""; void loop() { // 读取从计算机传入的数据,并通过softSerial发送个设备B: if(Serial.available()>0) { if(Serial.peek()!='\n') { device_A_String+=(char)Serial.read(); } else { Serial.read(); Serial.print("you said:"); Serial.println(device_A_String); softSerial.println(device_A_String); device_A_String=""; } } //读取从设备B传入的数据,并在串口监视器中显示 if(softSerial.available()>0) { if(softSerial.peek()!='\n') { device_B_String+=(char)softSerial.read(); } else { softSerial.read(); Serial.print("device B said:"); Serial.println(device_B_String); if(device_B_String=='A') { digitalWrite(13,HIGH); delay(5000); digitalWrite(13,LOW); } device_B_String=""; } } }
(我们先尝试了一下,然后失败了。)后面我们准备再试试WIFI模块实现手机发送信号
稳定小车速度
1.简单闭环控制
我们先借助电位器输入目标转速,仅使用编码器A相位,初步稳定了电机的速度。
【Arduino 101】霍尔编码器(增量,正交)与起停式闭环控制_机器人工坊-CSDN博客_arduino闭环控制
2.加入PID算法
在之前实现的小车运动中,小车很难走出一条比直的直线,总是容易偏离原来的路线,这是因为原有的代码无法控制四个轮子速度相同。因此我们借助他人的博客学习了PID算法。
用Arduino实现霍尔编码减速电机PI调速(增量式)_cbirdfly's Blog-CSDN博客
由于我们使用的Arduino Mega2560主控板只有6个中断注册pin口,而每个轮子有A B相位,共需要8个pin口,所以我们暂时只采用A编码器,采用增量式PID算法,我们改编了上述博主的代码,增加了关于前两个轮子的控制程序。
//后左电机端口定义 #define MotorLpin1 35 //控制位3 #define MotorLpin2 37 //控制位4 #define MotorLpwm 3 //使能调速 ENB #define MotorLcountA 18 //编码器A //后右电机端口定义 #define MotorRpin1 31 //控制位1 #define MotorRpin2 33 //控制位2 #define MotorRpwm 2 //使能调速 ENA #define MotorRcountA 19 //编码器A //前左电机端口定义 #define MotorfLpin1 41 //控制位3 #define MotorfLpin2 43 //控制位4 #define MotorfLpwm 5 //使能调速 ENB #define MotorfLcountA 20 //编码器Af //前右电机端口定义 #define MotorfRpin1 45 //控制位3 #define MotorfRpin2 47 //控制位4 #define MotorfRpwm 6 //使能调速 ENB #define MotorfRcountA 21 //编码器Af volatile float motorL=0;//中断变量,左轮子脉冲计数 volatile float motorfL=0;//中断变量,前左轮子脉冲计数 volatile float motorR=0;//中断变量,右轮子脉冲计数 volatile float motorfR=0;//中断变量,前右轮子脉冲计数 float V_L=0; //左轮速度 单位cm/s float V_fL=0; float V_R=0; //右边轮速 单位cm/s float V_fR=0; int v1=0; //单位cm/s int v2=0; //单位cm/s int vf1=0; int vf2=0; float Target_V_L=50,Target_V_R=50;//单位cm/s float Target_V_fL=50,Target_V_fR=50; int Pwm_L=0,Pwm_R=0; //左右轮PWM int Pwm_fL=0,Pwm_fR=0; //PID变量 float kp=10,ki=0,kd=4; //PID参数 /************************************** * Arduino初始化函数 * *************************************/ void setup() { Motor_Init();//电机端口初始化 Serial.begin(9600);//开启串口 } /********************************************************* * 函数功能:增量式PI控制器(左轮) * 入口参数:当前速度(编码器测量值),目标速度 * 返回 值:电机PWM * 参考资料: * 增量式离散PID公式: * Pwm-=Kp*[e(k)-e(k-1)]+Ki*e(k)+Kd*[e(k)-2e(k-1)+e(k-2)] * e(k):本次偏差 * e(k-1):上一次偏差 * e(k-2):上上次偏差 * Pwm:代表增量输出 * 在速度闭环控制系统里面我们只使用PI控制,因此对PID公式可简化为: * Pwm-=Kp*[e(k)-e(k-1)]+Ki*e(k) * e(k):本次偏差 * e(k-1):上一次偏差 * Pwm:代表增量输出 * * 注意增量式PID先调I,再调P,最后再调D *********************************************************/ int Incremental_Pi_L(int current_speed,int target_speed){ static float pwm,bias,last_bias,prev_bias; //静态变量存在程序全周期:pwm:增量输出,bias:本次偏差,last_bias:上次偏差,prev_bais_:上上次偏差 bias=current_speed-target_speed; //计算本次偏差e(k) pwm-=(kp*(bias-last_bias)+ki*bias+kd*(bias-2*last_bias+prev_bias)); //增量式PID控制器 prev_bias=last_bias; //保存上上次偏差 last_bias=bias; //保存上一次偏差 //PWM 限幅度 Arduino的PWM 最高为255 限制在250 if(pwm<-250){ pwm=250; } if(pwm>250){ pwm=250; } //Serial.println(pwm); return pwm; //增量输出 } //右轮速度增量式PID控制器 int Incremental_Pi_R(float current_speed,float target_speed){ static float pwm,bias,last_bias,prev_bias; //静态变量存在程序全周期:pwm:增量输出,bias:本次偏差,last_bias:上次偏差,prev_bais_:上上次偏差 bias=current_speed-target_speed; //计算本次偏差e(k) pwm-=(kp*(bias-last_bias)+ki*bias+kd*(bias-2*last_bias+prev_bias)); //增量式PID控制器 prev_bias=last_bias; //保存上上次偏差 last_bias=bias; //保存上一次偏差 //PWM 限幅度 Arduino的PWM 最高为255限制在250 if(pwm<-250){ pwm=250; } if(pwm>250){ pwm=250; } //Serial.println(pwm); return pwm; //增量输出 } int Incremental_Pi_fL(int current_speed,int target_speed){ static float pwm,bias,last_bias,prev_bias; //静态变量存在程序全周期:pwm:增量输出,bias:本次偏差,last_bias:上次偏差,prev_bais_:上上次偏差 bias=current_speed-target_speed; //计算本次偏差e(k) pwm-=(kp*(bias-last_bias)+ki*bias+kd*(bias-2*last_bias+prev_bias)); //增量式PID控制器 prev_bias=last_bias; //保存上上次偏差 last_bias=bias; //保存上一次偏差 //PWM 限幅度 Arduino的PWM 最高为255 限制在250 if(pwm<-250){ pwm=250; } if(pwm>250){ pwm=250; } //Serial.println(pwm); return pwm; //增量输出 } int Incremental_Pi_fR(int current_speed,int target_speed){ static float pwm,bias,last_bias,prev_bias; //静态变量存在程序全周期:pwm:增量输出,bias:本次偏差,last_bias:上次偏差,prev_bais_:上上次偏差 bias=current_speed-target_speed; //计算本次偏差e(k) pwm-=(kp*(bias-last_bias)+ki*bias+kd*(bias-2*last_bias+prev_bias)); //增量式PID控制器 prev_bias=last_bias; //保存上上次偏差 last_bias=bias; //保存上一次偏差 //PWM 限幅度 Arduino的PWM 最高为255 限制在250 if(pwm<-250){ pwm=250; } if(pwm>250){ pwm=250; } //Serial.println(pwm); return pwm; //增量输出 } /**************************************************************************(测试完成) 函数功能:设置双轮工作模式和运动速度 入口参数:工作模式,左右轮pwm 返回 值:无 **************************************************************************/ void Set_Pwm(int mode,int speed_L,int speed_R){ if(mode==1){ //前进模式 //左电机 digitalWrite(MotorLpin1,LOW); digitalWrite(MotorLpin2,HIGH); analogWrite(MotorLpwm,speed_L); //右电机 digitalWrite(MotorRpin1,HIGH); digitalWrite(MotorRpin2,LOW); analogWrite(MotorRpwm,speed_R); }else if(mode==2){ //后退模式 //左电机 digitalWrite(MotorLpin1,HIGH); digitalWrite(MotorLpin2,LOW); analogWrite(MotorLpwm,speed_L); //右电机 digitalWrite(MotorRpin1,LOW); digitalWrite(MotorRpin2,HIGH); analogWrite(MotorRpwm,speed_R); }else if(mode==3){ //左转模式 //左电机 digitalWrite(MotorLpin1,HIGH); digitalWrite(MotorLpin2,LOW); analogWrite(MotorLpwm,speed_L); //右电机 digitalWrite(MotorRpin1,HIGH); digitalWrite(MotorRpin2,LOW); analogWrite(MotorRpwm,speed_R); }else if(mode==4){ //右转模式 //左电机 digitalWrite(MotorLpin1,LOW); digitalWrite(MotorLpin2,HIGH); analogWrite(MotorLpwm,speed_L); //右电机 digitalWrite(MotorRpin1,LOW); digitalWrite(MotorRpin2,HIGH); analogWrite(MotorRpwm,speed_R); } } void Setf_Pwm(int mode,int speed_fL,int speed_fR){ if(mode==1){ //前进模式 //左电机 digitalWrite(MotorfLpin1,LOW); digitalWrite(MotorfLpin2,HIGH); analogWrite(MotorfLpwm,speed_fL); //右电机 digitalWrite(MotorfRpin1,HIGH); digitalWrite(MotorfRpin2,LOW); analogWrite(MotorfRpwm,speed_fR); }else if(mode==2){ //后退模式 //左电机 digitalWrite(MotorfLpin1,HIGH); digitalWrite(MotorfLpin2,LOW); analogWrite(MotorfLpwm,speed_fL); //右电机 digitalWrite(MotorfRpin1,LOW); digitalWrite(MotorfRpin2,HIGH); analogWrite(MotorfRpwm,speed_fR); }else if(mode==3){ //左转模式 //左电机 digitalWrite(MotorfLpin1,HIGH); digitalWrite(MotorfLpin2,LOW); analogWrite(MotorfLpwm,speed_fL); //右电机 digitalWrite(MotorfRpin1,HIGH); digitalWrite(MotorfRpin2,LOW); analogWrite(MotorfRpwm,speed_fR); }else if(mode==4){ //右转模式 //左电机 digitalWrite(MotorfLpin1,LOW); digitalWrite(MotorfLpin2,HIGH); analogWrite(MotorfLpwm,speed_fL); //右电机 digitalWrite(MotorfRpin1,LOW); digitalWrite(MotorfRpin2,HIGH); analogWrite(MotorfRpwm,speed_fR); } } /**************************************************************************(测试完成) 函数功能:电机端口初始化,控制芯片引脚拉低 入口参数:无 返回 值:无 **************************************************************************/ void Motor_Init(){ //左电机 pinMode(MotorLpin1,OUTPUT); //驱动芯片控制引脚 pinMode(MotorLpin2,OUTPUT); //驱动芯片控制引脚 pinMode(MotorLpwm,OUTPUT); //驱动芯片控制引脚,PWM调速 pinMode(MotorLcountA,INPUT); //左轮编码器A引脚 //右电机 pinMode(MotorRpin1,OUTPUT); //驱动芯片控制引脚 pinMode(MotorRpin2,OUTPUT); //驱动芯片控制引脚 pinMode(MotorRpwm,OUTPUT); //驱动芯片控制引脚,PWM调速 pinMode(MotorRcountA,INPUT); //右轮编码器A引脚 //驱动芯片控制引脚全部拉低 digitalWrite(MotorLpin1,LOW); //左电机 digitalWrite(MotorLpin2,LOW); digitalWrite(MotorLpwm,LOW); digitalWrite(MotorRpin1,LOW); //右电机 digitalWrite(MotorRpin2,LOW); digitalWrite(MotorRpwm,LOW); //左电机 pinMode(MotorfLpin1,OUTPUT); //驱动芯片控制引脚 pinMode(MotorfLpin2,OUTPUT); //驱动芯片控制引脚 pinMode(MotorfLpwm,OUTPUT); //驱动芯片控制引脚,PWM调速 pinMode(MotorfLcountA,INPUT); //左轮编码器A引脚 //右电机 pinMode(MotorfRpin1,OUTPUT); //驱动芯片控制引脚 pinMode(MotorfRpin2,OUTPUT); //驱动芯片控制引脚 pinMode(MotorfRpwm,OUTPUT); //驱动芯片控制引脚,PWM调速 pinMode(MotorfRcountA,INPUT); //右轮编码器A引脚 //驱动芯片控制引脚全部拉低 digitalWrite(MotorfLpin1,LOW); //左电机 digitalWrite(MotorfLpin2,LOW); digitalWrite(MotorfLpwm,LOW); digitalWrite(MotorfRpin1,LOW); //右电机 digitalWrite(MotorfRpin2,LOW); digitalWrite(MotorfRpwm,LOW); } /*********************************** * 电机实际速度计算: * 公式: * 已知参数: * 车轮直径65mm, * 左边轮子一圈:390脉冲(RISING), * 右边轮子一圈:390脉冲(RISING), * 单位时间读两个轮子脉冲读取两个轮子脉冲 ***********************************/ void Read_Moto_V(){ unsigned long nowtime=0; motorL=0; motorR=0; nowtime=millis()+50;//读50毫秒 attachInterrupt(digitalPinToInterrupt(MotorLcountA),Read_Moto_L,RISING);//左轮脉冲开中断计数 attachInterrupt(digitalPinToInterrupt(MotorfLcountA),Read_Moto_fL,RISING);//左轮脉冲开中断计数 attachInterrupt(digitalPinToInterrupt(MotorRcountA),Read_Moto_R,RISING);//右轮脉冲开中断计数 attachInterrupt(digitalPinToInterrupt(MotorfRcountA),Read_Moto_fR,RISING);//前右轮脉冲开中断计数 while(millis()<nowtime); //达到50毫秒关闭中断 detachInterrupt(digitalPinToInterrupt(MotorLcountA));//左轮脉冲关中断计数 detachInterrupt(digitalPinToInterrupt(MotorfLcountA));//左轮脉冲关中断计数 detachInterrupt(digitalPinToInterrupt(MotorRcountA));//右轮脉冲关中断计数 detachInterrupt(digitalPinToInterrupt(MotorfRcountA));//右轮脉冲关中断计数 V_L=((motorL/390)*6.5*PI)/0.05; //单位cm/s V_fL=((motorfL/390)*6.5*PI)/0.05; //单位cm/s V_R=((motorR/390)*6.5*PI)/0.05; //单位cm/s V_fR=((motorfR/390)*6.5*PI)/0.05; //单位cm/s v1=V_L; vf1=V_fL; v2=V_R; vf2=V_fR; } /*************************** * 中断函数:读左轮脉冲 * **************************/ void Read_Moto_L(){ motorL++; } void Read_Moto_fL(){ motorfL++; } /************************** * 中断函数:读右轮脉冲 * *************************/ void Read_Moto_R(){ motorR++; } void Read_Moto_fR(){ motorfR++; } /*************************************** * Arduino主循环 * ***************************************/ void loop() { Read_Moto_V();//读取脉冲计算速度 Pwm_L=Incremental_Pi_L(V_L,Target_V_L);//左轮PI运算 Pwm_fL=Incremental_Pi_fL(V_fL,Target_V_fL);//前左轮PI运算 Pwm_R=Incremental_Pi_R(V_R,Target_V_R);//右轮PI运算 Pwm_fR=Incremental_Pi_fR(V_fR,Target_V_fR);//右轮PI运算 Serial.print(V_L);//直接用串口绘图画出速度曲线 Serial.print(","); Serial.print(V_R);//直接用串口绘图画出速度曲线 Serial.print(","); Serial.print(V_fR); Serial.print(","); Serial.println(V_fL); Set_Pwm(1,Pwm_L,Pwm_R); //设置左右轮速度 Setf_Pwm(1,Pwm_fL,Pwm_fR); //设置左右轮速度 }
由于我们还不太熟悉PID参数的调节,并且有一个编码器出现了问题,目前我们并没有完全实现利用PID算法控制小车的运动。而且可能因为机械误差的问题,还不能实现轮子的速度都保持一致。我们准备借助串口绘图器,控制变量法得出较为合适的PID参数。预计这两天内会解决问题。
版权声明:本文为CSDN博主「studyingbear」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/studingbear/article/details/121744644
目录
Arduino基础入门
最开始那几天,由于没有找到好用的教程,再加上刚进入这个全新的领域,所以进度十分缓慢,不会连接电路,上传错误等问题频繁出现。但通过同伴的教程推荐,慢慢找到了学习的方法和路径。在通过阅读官方函数参考文档形成初步印象后,我借助Tony的arduino基础篇和中文社区中的小实验,慢慢熟悉了基本函数的用法和电路连接的相关知识,Tony基础篇1到16中的小实验(其他基础实验在做小车需要用到时也作为初步认识做了相关实验)基本都有动手操作。先阅读他人的代码,观察实验现象,再在代码中加入自己的理解进行实验操作。
Tonyの博客_TonyIOT_CSDN博客-Arduino,树莓派,Processing领域博主
Arduino教程汇总贴(2020.2.2更新)-Arduino中文社区 - Powered by Discuz!
这个是我初期学习的一部分内容:
这些有趣的小实验极大程度上激发了我学习arduino的热情。接下来就是学习制作基于arduino板驱动的小车了。
基于arduino板控制小车
让小车动起来
由于Arduino开发板的通用IO驱动能力有限,有些外设不能直接使用IO进行驱动,需要借助一些驱动电路间接控制大功率器件。
因而我们学习使用了电机驱动板L298N,使其接入Arduino板上的信号,将L298N连接上电机两侧的正负接脚,每块L298N连接两个电机,再通过电池给L298N供电,拿其中一块L298N反向给arduino板供电,最终实现了电机的基本转动。
//前轮 int output3=3; int output4=4; int output5=5; int output6=6; //后轮 int output9=9; int output10=10; int output11=11; int output12=12; void reverse() { for(int i=3;i<=6;i++) { if(digitalRead(1)) { digitalWrite(i,HIGH); }else { digitalWrite(i,LOW); } } for(int i=9;i<=12;i++) { if(digitalRead(i)) { digitalWrite(i,HIGH); }else { digitalWrite(i,LOW); } } } void setup() { for(int i=3;i<=5;i++) { pinMOde(i,OUTPUT); } for(int i=9;i<=12;i++) { pinMOde(i,OUTPUT); } } void loop() { //前进 int cnt=0; if(cnt!=0) { reverse(); delay(2000); } digitalWrite(output3,HIGH); //给高电平 digitalWrite(output4,LOW); //给低电平 digitalWrite(output5,HIGH); //给高电平 digitalWrite(output6,LOW); //给低电平 digitalWrite(output9,HIGH); //给高电平 digitalWrite(output10,LOW); //给低电平 digitalWrite(output11,HIGH); //给高电平 digitalWrite(output12,LOW); //给低电平 delay(2000); //延时2秒 digitalWrite(output3,HIGH); //给高电平 digitalWrite(output4,LOW); //给低电平 digitalWrite(output5,HIGH); //给高电平 digitalWrite(output6,LOW); //给低电平 digitalWrite(output9,HIGH); //给高电平 digitalWrite(output10,LOW); //给低电平 digitalWrite(output11,HIGH); //给高电平 digitalWrite(output12,LOW); //给低电平 delay(2000); cnt++; }
学习PWM频率和占空比 初步调速
这部分我们先是直接采用了analogWrite函数进行了调节
void L1_forward(int sp)//左前轮前进 { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,HIGH); analogWrite(L1_ENA,sp); }
利用麦克纳姆轮原理控制全向移动
了解向各个方向移动与各轮转向的关系后,就可通过调节与各轮转向相关的高低电平引脚模式来实现全向移动了。
/************************************************************/ /********硬件:Arduino UNO + L298N电机驱动两个********/ /************************************************************/ int L1_IN1 = 7;int L1_IN2 = 5;int L1_ENA = 6;//左前轮 int R1_IN1 = 4;int R1_IN2 = 2;int R1_ENA = 3;//右前轮 int L2_IN1 = 12;int L2_IN2 = 13;int L2_ENA = 11;//左后轮 int R2_IN1 = 8;int R2_IN2 = 9;int R2_ENA = 10;//右后轮 void setup() { // put your setup code here, to run once: pinMode(L1_IN1, OUTPUT);pinMode(L1_IN2, OUTPUT);pinMode(L1_ENA, OUTPUT); pinMode(R1_IN1, OUTPUT);pinMode(R1_IN2, OUTPUT);pinMode(R1_ENA, OUTPUT); pinMode(L2_IN1, OUTPUT);pinMode(L2_IN2, OUTPUT);pinMode(L2_ENA, OUTPUT); pinMode(R2_IN1, OUTPUT);pinMode(R2_IN2, OUTPUT);pinMode(R2_ENA, OUTPUT); } void L1_forward(int sp)//左前轮前进 { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,HIGH); analogWrite(L1_ENA,sp); } void R1_forward(int sp)//右前轮前进 { digitalWrite(R1_IN1,HIGH); digitalWrite(R1_IN2,LOW); analogWrite(R1_ENA,sp); } void L2_forward(int sp)//左后轮前进 { digitalWrite(L2_IN1,HIGH); digitalWrite(L2_IN2,LOW); analogWrite(L2_ENA,sp); } void R2_forward(int sp)//右后轮前进 { digitalWrite(R2_IN1,HIGH); digitalWrite(R2_IN2,LOW); analogWrite(R2_ENA,sp); } void allstop() { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,LOW); digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,LOW); digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,LOW); digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,LOW); } void L1_backward(int sp)//左前轮后退 { digitalWrite(L1_IN1,HIGH); digitalWrite(L1_IN2,LOW); analogWrite(L1_ENA,sp); } void R1_backward(int sp)//右前轮后退 { digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,HIGH); analogWrite(R1_ENA,sp); } void L2_backward(int sp)//左后轮后退 { digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,HIGH); analogWrite(L2_ENA,sp); } void R2_backward(int sp)//右后轮后退 { digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,HIGH); analogWrite(R2_ENA,sp); } void loop() { // put your main code here, to run repeatedly: /*前进*/ L1_forward(100); R1_forward(100); L2_forward(100); R2_forward(100); delay(1500); allstop(); delay(1500); /*后退*/ L1_backward(100); R1_backward(100); L2_backward(100); R2_backward(100); delay(1500); allstop(); delay(1500); /*顺时针原地旋转*/ L1_forward(200); R1_backward(200); L2_forward(200); R2_backward(200); delay(1500); allstop(); delay(1500); /*逆时针原地旋转*/ L1_backward(200); R1_forward(200); L2_backward(200); R2_forward(200); delay(1500); allstop(); delay(1500); /*左边平移*/ L1_backward(150); R1_forward(150); L2_forward(150); R2_backward(150); delay(1500); allstop(); delay(1500); /*右边平移*/ L1_forward(150); R1_backward(150); L2_backward(150); R2_forward(150); delay(1500); allstop(); delay(1500); /*斜向左上方*/ R1_forward(150); L2_forward(150); delay(1500); allstop(); delay(1500); /*斜向右上方*/ L1_forward(150); R2_forward(150); delay(1500); allstop(); delay(1500); /*斜向左下方*/ L1_backward(150); R2_backward(150); delay(1500); allstop(); delay(1500); /*斜向右下方*/ R1_backward(150); L2_backward(150); delay(1500); allstop(); delay(1500); }
加入遥控小车功能
方案1:红外遥控
通过红外发射二极管发射出去,而红外接收端则要将信号进行解调处理,还原成二进制脉冲码进行处理。当按下遥控器按键时,遥控器发出红外载波信号,红外接收器接收到信号,程序对载波信号进行解码,通过数据码的不同来判断按下的是哪个键。我们依次测试了每个按键代表的红外编码,后期可以完善控制小车运动方向。
/* * IRrecvDemo * 红外控制,接收红外命令控制板载LED灯亮灭 */ #include <IRremote.h> int RECV_PIN = 5; int L1_IN1 = 7;int L1_IN2 = A1;int L1_ENA = 6;//左前轮 int R1_IN1 = 4;int R1_IN2 = 2;int R1_ENA = 3;//右前轮 int L2_IN1 = 12;int L2_IN2 = 13;int L2_ENA = 11;//左后轮 int R2_IN1 = 8;int R2_IN2 = 9;int R2_ENA = 10;//右后轮 IRrecv irrecv(RECV_PIN); decode_results results; void setup() { Serial.begin(9600); irrecv.enableIRIn(); // Start the receiver pinMode(L1_IN1, OUTPUT);pinMode(L1_IN2, OUTPUT);pinMode(L1_ENA, OUTPUT); pinMode(R1_IN1, OUTPUT);pinMode(R1_IN2, OUTPUT);pinMode(R1_ENA, OUTPUT); pinMode(L2_IN1, OUTPUT);pinMode(L2_IN2, OUTPUT);pinMode(L2_ENA, OUTPUT); pinMode(R2_IN1, OUTPUT);pinMode(R2_IN2, OUTPUT);pinMode(R2_ENA, OUTPUT); } void L1_forward(int sp)//左前轮前进 { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,HIGH); for(int i=0;i<=sp;i++) { analogWrite(L1_ENA,sp); } } void R1_forward(int sp)//右前轮前进 { digitalWrite(R1_IN1,HIGH); digitalWrite(R1_IN2,LOW); for(int i=50;i<=sp;i++) { analogWrite(R1_ENA,sp); } } void L2_forward(int sp)//左后轮前进 { digitalWrite(L2_IN1,HIGH); digitalWrite(L2_IN2,LOW); for(int i=50;i<=sp;i++) { analogWrite(L2_ENA,sp); } } void R2_forward(int sp)//右后轮前进 { digitalWrite(R2_IN1,HIGH); digitalWrite(R2_IN2,LOW); //analogWrite(R2_ENA,sp); for(int i=50;i<=sp;i++) { analogWrite(R2_ENA,sp); } } void allstop() { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,LOW); digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,LOW); digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,LOW); digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,LOW); } void L1_backward(int sp)//左前轮后退 { digitalWrite(L1_IN1,HIGH); digitalWrite(L1_IN2,LOW); analogWrite(L1_ENA,sp); } void R1_backward(int sp)//右前轮后退 { digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,HIGH); analogWrite(R1_ENA,sp); } void L2_backward(int sp)//左后轮后退 { digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,HIGH); analogWrite(L2_ENA,sp); } void R2_backward(int sp)//右后轮后退 { digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,HIGH); analogWrite(R2_ENA,sp); } void loop() { if (irrecv.decode(&results)) { Serial.println(results.value, HEX); if (results.value == 0xFFE01F) { L1_forward(100); R1_forward(100); L2_forward(100); R2_forward(100); delay(1500); allstop(); delay(1500); } else if (results.value == 0xFFE21D) { /*后退*/ L1_backward(100); R1_backward(100); L2_backward(100); R2_backward(100); delay(1500); allstop(); delay(1500); } irrecv.resume(); // Receive the next value } delay(100); }
对于以上代码,我们多次改变了引脚号,尝试在UNO板(仅有5个pwm口,数字输出接脚也有限)上完成红外识别和多引脚输出控制步进电机。由于,后期我们还想加上霍尔元件编码器,所以我们改用了Arduino Mega2560作为主控板。
但是此方案接收短,信号稳定性差,易受外界环境干扰影响红外接收端的识别,并且我们发现,对于下面控制程序中的走8字部分,由于耗时较长,从else语句中出来需要一定时间,当按键发出停止指令的时候无法很快执行,目前正在探索解决的方案(硬件中断貌似行不通,所以我们在考虑陈同学推荐的软件中断方法)。
#include <IRremote.h> /*定义红外接收管接线引脚*/ int RECV_PIN = 11;//定义红外接收引脚号 IRrecv irrecv(RECV_PIN); decode_results results;//解码结果放在decode results结构的result中 /*定义电机驱动接线引脚*/ int L1_IN1 = 44; int L1_IN2 = 42; int L1_ENA = 7;//左前轮 int R1_IN1 = 40; int R1_IN2 = 38; int R1_ENA = 6;//右前轮 int L2_IN1 = 52; int L2_IN2 = 50; int L2_ENA = 5;//左后轮 int R2_IN1 = 48; int R2_IN2 = 46; int R2_ENA = 4;//右后轮 void setup() { // put your setup code here, to run once: pinMode(L1_IN1, OUTPUT);pinMode(L1_IN2, OUTPUT);pinMode(L1_ENA, OUTPUT); pinMode(R1_IN1, OUTPUT);pinMode(R1_IN2, OUTPUT);pinMode(R1_ENA, OUTPUT); pinMode(L2_IN1, OUTPUT);pinMode(L2_IN2, OUTPUT);pinMode(L2_ENA, OUTPUT); pinMode(R2_IN1, OUTPUT);pinMode(R2_IN2, OUTPUT);pinMode(R2_ENA, OUTPUT); Serial.begin(9600); // In case the interrupt driver crashes on setup, give a clue // to the user what's going on. Serial.println("Enabling IRin"); irrecv.enableIRIn(); // 启动接收器 Serial.println("Enabled IRin"); } void loop() { if (irrecv.decode(&results)) { Serial.print("HEX格式 》》 "); Serial.println(results.value,HEX);//以16进制换行输出接收代码 Serial.print("默认格式 》》 "); Serial.println(results.value); if (results.value == 0xFF30CF) //1键为前进键 { L1_forward(100); R1_forward(100); L2_forward(100); R2_forward(100); } else if (results.value == 0xFF18E7) //2键为后退键 { L1_backward(100); R1_backward(100); L2_backward(100); R2_backward(100); } else if (results.value == 0xFF7A85) //3键为左边平移键 { L1_forward(150);R1_backward(150); L2_forward(150);R2_backward(150); } else if (results.value == 0xFF10EF) //4键为右边平移键 { L1_backward(150);R1_forward(150); L2_backward(150);R2_forward(150); } else if (results.value == 0xFF38C7) //5键为顺时针原地旋转键 { L1_backward(150);R1_forward(150); L2_forward(150); R2_backward(150); } else if (results.value == 0xFF5AA5) //6键为逆时针原地旋转键 { L1_forward(150); R1_backward(150); L2_backward(150);R2_forward(150); } else if (results.value == 0xFF4AB5) //8键为原地画数字8键 { //前 L1_forward(100); R1_forward(100); L2_forward(100); R2_forward(100); delay(500); allstop(); delay(500); //右 L1_backward(150);R1_forward(150); L2_backward(150);R2_forward(150); delay(800); allstop(); delay(500); //前 L1_forward(100); R1_forward(100); L2_forward(100); R2_forward(100); delay(500); allstop(); delay(500); //左 L1_forward(150); R1_backward(150); L2_forward(150); R2_backward(150); delay(800); allstop(); delay(500); //后 L1_backward(100);R1_backward(100); L2_backward(100);R2_backward(100); delay(500); allstop(); delay(500); //右 L1_backward(150);R1_forward(150); L2_backward(150);R2_forward(150); delay(800); allstop(); delay(500); //后 L1_backward(100);R1_backward(100); L2_backward(100);R2_backward(100); delay(500); allstop(); delay(500); //左 L1_forward(150); R1_backward(150); L2_forward(150); R2_backward(150); delay(800); allstop(); delay(500); } else if (results.value == 0xFF6897) //0键为停止键 { allstop();Serial.println("allstop"); } irrecv.resume(); // Receive the next value Serial.println("-------"); } delay(100); } void L1_forward(int sp)//左前轮前进 { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,HIGH); analogWrite(L1_ENA,sp); } void R1_forward(int sp)//右前轮前进 { digitalWrite(R1_IN1,HIGH); digitalWrite(R1_IN2,LOW); analogWrite(R1_ENA,sp); } void L2_forward(int sp)//左后轮前进 { digitalWrite(L2_IN1,HIGH); digitalWrite(L2_IN2,LOW); analogWrite(L2_ENA,sp); } void R2_forward(int sp)//右后轮前进 { digitalWrite(R2_IN1,HIGH); digitalWrite(R2_IN2,LOW); analogWrite(R2_ENA,sp); } void allstop() { digitalWrite(L1_IN1,LOW); digitalWrite(L1_IN2,LOW); digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,LOW); digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,LOW); digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,LOW); } void L1_backward(int sp)//左前轮后退 { digitalWrite(L1_IN1,HIGH); digitalWrite(L1_IN2,LOW); analogWrite(L1_ENA,sp); } void R1_backward(int sp)//右前轮后退 { digitalWrite(R1_IN1,LOW); digitalWrite(R1_IN2,HIGH); analogWrite(R1_ENA,sp); } void L2_backward(int sp)//左后轮后退 { digitalWrite(L2_IN1,LOW); digitalWrite(L2_IN2,HIGH); analogWrite(L2_ENA,sp); } void R2_backward(int sp)//右后轮后退 { digitalWrite(R2_IN1,LOW); digitalWrite(R2_IN2,HIGH); analogWrite(R2_ENA,sp); }
方案2:利用串口通信,一个板用来接收数据,一个板用来控制输出。
/* * arduino uno端程序 * 串口使用情况 serial -----computer serial1----- nano softwearserial */ #include<SoftwareSerial.h> SoftwareSerial softSerial(6,5); void setup() { //初始化serial,该串口用于与计算机连接通信: Serial.begin(9600); pinMode(13,OUTPUT); //初始化serial1,该串口用于与设备B连接通信; softSerial.begin(9600); softSerial.listen(); } //两个字符串分别用于存储A,B两端传来的数据 String device_A_String=""; String device_B_String=""; void loop() { // 读取从计算机传入的数据,并通过softSerial发送个设备B: if(Serial.available()>0) { if(Serial.peek()!='\n') { device_A_String+=(char)Serial.read(); } else { Serial.read(); Serial.print("you said:"); Serial.println(device_A_String); softSerial.println(device_A_String); device_A_String=""; } } //读取从设备B传入的数据,并在串口监视器中显示 if(softSerial.available()>0) { if(softSerial.peek()!='\n') { device_B_String+=(char)softSerial.read(); } else { softSerial.read(); Serial.print("device B said:"); Serial.println(device_B_String); if(device_B_String=='A') { digitalWrite(13,HIGH); delay(5000); digitalWrite(13,LOW); } device_B_String=""; } } }
(我们先尝试了一下,然后失败了。)后面我们准备再试试WIFI模块实现手机发送信号
稳定小车速度
1.简单闭环控制
我们先借助电位器输入目标转速,仅使用编码器A相位,初步稳定了电机的速度。
【Arduino 101】霍尔编码器(增量,正交)与起停式闭环控制_机器人工坊-CSDN博客_arduino闭环控制
2.加入PID算法
在之前实现的小车运动中,小车很难走出一条比直的直线,总是容易偏离原来的路线,这是因为原有的代码无法控制四个轮子速度相同。因此我们借助他人的博客学习了PID算法。
用Arduino实现霍尔编码减速电机PI调速(增量式)_cbirdfly's Blog-CSDN博客
由于我们使用的Arduino Mega2560主控板只有6个中断注册pin口,而每个轮子有A B相位,共需要8个pin口,所以我们暂时只采用A编码器,采用增量式PID算法,我们改编了上述博主的代码,增加了关于前两个轮子的控制程序。
//后左电机端口定义 #define MotorLpin1 35 //控制位3 #define MotorLpin2 37 //控制位4 #define MotorLpwm 3 //使能调速 ENB #define MotorLcountA 18 //编码器A //后右电机端口定义 #define MotorRpin1 31 //控制位1 #define MotorRpin2 33 //控制位2 #define MotorRpwm 2 //使能调速 ENA #define MotorRcountA 19 //编码器A //前左电机端口定义 #define MotorfLpin1 41 //控制位3 #define MotorfLpin2 43 //控制位4 #define MotorfLpwm 5 //使能调速 ENB #define MotorfLcountA 20 //编码器Af //前右电机端口定义 #define MotorfRpin1 45 //控制位3 #define MotorfRpin2 47 //控制位4 #define MotorfRpwm 6 //使能调速 ENB #define MotorfRcountA 21 //编码器Af volatile float motorL=0;//中断变量,左轮子脉冲计数 volatile float motorfL=0;//中断变量,前左轮子脉冲计数 volatile float motorR=0;//中断变量,右轮子脉冲计数 volatile float motorfR=0;//中断变量,前右轮子脉冲计数 float V_L=0; //左轮速度 单位cm/s float V_fL=0; float V_R=0; //右边轮速 单位cm/s float V_fR=0; int v1=0; //单位cm/s int v2=0; //单位cm/s int vf1=0; int vf2=0; float Target_V_L=50,Target_V_R=50;//单位cm/s float Target_V_fL=50,Target_V_fR=50; int Pwm_L=0,Pwm_R=0; //左右轮PWM int Pwm_fL=0,Pwm_fR=0; //PID变量 float kp=10,ki=0,kd=4; //PID参数 /************************************** * Arduino初始化函数 * *************************************/ void setup() { Motor_Init();//电机端口初始化 Serial.begin(9600);//开启串口 } /********************************************************* * 函数功能:增量式PI控制器(左轮) * 入口参数:当前速度(编码器测量值),目标速度 * 返回 值:电机PWM * 参考资料: * 增量式离散PID公式: * Pwm-=Kp*[e(k)-e(k-1)]+Ki*e(k)+Kd*[e(k)-2e(k-1)+e(k-2)] * e(k):本次偏差 * e(k-1):上一次偏差 * e(k-2):上上次偏差 * Pwm:代表增量输出 * 在速度闭环控制系统里面我们只使用PI控制,因此对PID公式可简化为: * Pwm-=Kp*[e(k)-e(k-1)]+Ki*e(k) * e(k):本次偏差 * e(k-1):上一次偏差 * Pwm:代表增量输出 * * 注意增量式PID先调I,再调P,最后再调D *********************************************************/ int Incremental_Pi_L(int current_speed,int target_speed){ static float pwm,bias,last_bias,prev_bias; //静态变量存在程序全周期:pwm:增量输出,bias:本次偏差,last_bias:上次偏差,prev_bais_:上上次偏差 bias=current_speed-target_speed; //计算本次偏差e(k) pwm-=(kp*(bias-last_bias)+ki*bias+kd*(bias-2*last_bias+prev_bias)); //增量式PID控制器 prev_bias=last_bias; //保存上上次偏差 last_bias=bias; //保存上一次偏差 //PWM 限幅度 Arduino的PWM 最高为255 限制在250 if(pwm<-250){ pwm=250; } if(pwm>250){ pwm=250; } //Serial.println(pwm); return pwm; //增量输出 } //右轮速度增量式PID控制器 int Incremental_Pi_R(float current_speed,float target_speed){ static float pwm,bias,last_bias,prev_bias; //静态变量存在程序全周期:pwm:增量输出,bias:本次偏差,last_bias:上次偏差,prev_bais_:上上次偏差 bias=current_speed-target_speed; //计算本次偏差e(k) pwm-=(kp*(bias-last_bias)+ki*bias+kd*(bias-2*last_bias+prev_bias)); //增量式PID控制器 prev_bias=last_bias; //保存上上次偏差 last_bias=bias; //保存上一次偏差 //PWM 限幅度 Arduino的PWM 最高为255限制在250 if(pwm<-250){ pwm=250; } if(pwm>250){ pwm=250; } //Serial.println(pwm); return pwm; //增量输出 } int Incremental_Pi_fL(int current_speed,int target_speed){ static float pwm,bias,last_bias,prev_bias; //静态变量存在程序全周期:pwm:增量输出,bias:本次偏差,last_bias:上次偏差,prev_bais_:上上次偏差 bias=current_speed-target_speed; //计算本次偏差e(k) pwm-=(kp*(bias-last_bias)+ki*bias+kd*(bias-2*last_bias+prev_bias)); //增量式PID控制器 prev_bias=last_bias; //保存上上次偏差 last_bias=bias; //保存上一次偏差 //PWM 限幅度 Arduino的PWM 最高为255 限制在250 if(pwm<-250){ pwm=250; } if(pwm>250){ pwm=250; } //Serial.println(pwm); return pwm; //增量输出 } int Incremental_Pi_fR(int current_speed,int target_speed){ static float pwm,bias,last_bias,prev_bias; //静态变量存在程序全周期:pwm:增量输出,bias:本次偏差,last_bias:上次偏差,prev_bais_:上上次偏差 bias=current_speed-target_speed; //计算本次偏差e(k) pwm-=(kp*(bias-last_bias)+ki*bias+kd*(bias-2*last_bias+prev_bias)); //增量式PID控制器 prev_bias=last_bias; //保存上上次偏差 last_bias=bias; //保存上一次偏差 //PWM 限幅度 Arduino的PWM 最高为255 限制在250 if(pwm<-250){ pwm=250; } if(pwm>250){ pwm=250; } //Serial.println(pwm); return pwm; //增量输出 } /**************************************************************************(测试完成) 函数功能:设置双轮工作模式和运动速度 入口参数:工作模式,左右轮pwm 返回 值:无 **************************************************************************/ void Set_Pwm(int mode,int speed_L,int speed_R){ if(mode==1){ //前进模式 //左电机 digitalWrite(MotorLpin1,LOW); digitalWrite(MotorLpin2,HIGH); analogWrite(MotorLpwm,speed_L); //右电机 digitalWrite(MotorRpin1,HIGH); digitalWrite(MotorRpin2,LOW); analogWrite(MotorRpwm,speed_R); }else if(mode==2){ //后退模式 //左电机 digitalWrite(MotorLpin1,HIGH); digitalWrite(MotorLpin2,LOW); analogWrite(MotorLpwm,speed_L); //右电机 digitalWrite(MotorRpin1,LOW); digitalWrite(MotorRpin2,HIGH); analogWrite(MotorRpwm,speed_R); }else if(mode==3){ //左转模式 //左电机 digitalWrite(MotorLpin1,HIGH); digitalWrite(MotorLpin2,LOW); analogWrite(MotorLpwm,speed_L); //右电机 digitalWrite(MotorRpin1,HIGH); digitalWrite(MotorRpin2,LOW); analogWrite(MotorRpwm,speed_R); }else if(mode==4){ //右转模式 //左电机 digitalWrite(MotorLpin1,LOW); digitalWrite(MotorLpin2,HIGH); analogWrite(MotorLpwm,speed_L); //右电机 digitalWrite(MotorRpin1,LOW); digitalWrite(MotorRpin2,HIGH); analogWrite(MotorRpwm,speed_R); } } void Setf_Pwm(int mode,int speed_fL,int speed_fR){ if(mode==1){ //前进模式 //左电机 digitalWrite(MotorfLpin1,LOW); digitalWrite(MotorfLpin2,HIGH); analogWrite(MotorfLpwm,speed_fL); //右电机 digitalWrite(MotorfRpin1,HIGH); digitalWrite(MotorfRpin2,LOW); analogWrite(MotorfRpwm,speed_fR); }else if(mode==2){ //后退模式 //左电机 digitalWrite(MotorfLpin1,HIGH); digitalWrite(MotorfLpin2,LOW); analogWrite(MotorfLpwm,speed_fL); //右电机 digitalWrite(MotorfRpin1,LOW); digitalWrite(MotorfRpin2,HIGH); analogWrite(MotorfRpwm,speed_fR); }else if(mode==3){ //左转模式 //左电机 digitalWrite(MotorfLpin1,HIGH); digitalWrite(MotorfLpin2,LOW); analogWrite(MotorfLpwm,speed_fL); //右电机 digitalWrite(MotorfRpin1,HIGH); digitalWrite(MotorfRpin2,LOW); analogWrite(MotorfRpwm,speed_fR); }else if(mode==4){ //右转模式 //左电机 digitalWrite(MotorfLpin1,LOW); digitalWrite(MotorfLpin2,HIGH); analogWrite(MotorfLpwm,speed_fL); //右电机 digitalWrite(MotorfRpin1,LOW); digitalWrite(MotorfRpin2,HIGH); analogWrite(MotorfRpwm,speed_fR); } } /**************************************************************************(测试完成) 函数功能:电机端口初始化,控制芯片引脚拉低 入口参数:无 返回 值:无 **************************************************************************/ void Motor_Init(){ //左电机 pinMode(MotorLpin1,OUTPUT); //驱动芯片控制引脚 pinMode(MotorLpin2,OUTPUT); //驱动芯片控制引脚 pinMode(MotorLpwm,OUTPUT); //驱动芯片控制引脚,PWM调速 pinMode(MotorLcountA,INPUT); //左轮编码器A引脚 //右电机 pinMode(MotorRpin1,OUTPUT); //驱动芯片控制引脚 pinMode(MotorRpin2,OUTPUT); //驱动芯片控制引脚 pinMode(MotorRpwm,OUTPUT); //驱动芯片控制引脚,PWM调速 pinMode(MotorRcountA,INPUT); //右轮编码器A引脚 //驱动芯片控制引脚全部拉低 digitalWrite(MotorLpin1,LOW); //左电机 digitalWrite(MotorLpin2,LOW); digitalWrite(MotorLpwm,LOW); digitalWrite(MotorRpin1,LOW); //右电机 digitalWrite(MotorRpin2,LOW); digitalWrite(MotorRpwm,LOW); //左电机 pinMode(MotorfLpin1,OUTPUT); //驱动芯片控制引脚 pinMode(MotorfLpin2,OUTPUT); //驱动芯片控制引脚 pinMode(MotorfLpwm,OUTPUT); //驱动芯片控制引脚,PWM调速 pinMode(MotorfLcountA,INPUT); //左轮编码器A引脚 //右电机 pinMode(MotorfRpin1,OUTPUT); //驱动芯片控制引脚 pinMode(MotorfRpin2,OUTPUT); //驱动芯片控制引脚 pinMode(MotorfRpwm,OUTPUT); //驱动芯片控制引脚,PWM调速 pinMode(MotorfRcountA,INPUT); //右轮编码器A引脚 //驱动芯片控制引脚全部拉低 digitalWrite(MotorfLpin1,LOW); //左电机 digitalWrite(MotorfLpin2,LOW); digitalWrite(MotorfLpwm,LOW); digitalWrite(MotorfRpin1,LOW); //右电机 digitalWrite(MotorfRpin2,LOW); digitalWrite(MotorfRpwm,LOW); } /*********************************** * 电机实际速度计算: * 公式: * 已知参数: * 车轮直径65mm, * 左边轮子一圈:390脉冲(RISING), * 右边轮子一圈:390脉冲(RISING), * 单位时间读两个轮子脉冲读取两个轮子脉冲 ***********************************/ void Read_Moto_V(){ unsigned long nowtime=0; motorL=0; motorR=0; nowtime=millis()+50;//读50毫秒 attachInterrupt(digitalPinToInterrupt(MotorLcountA),Read_Moto_L,RISING);//左轮脉冲开中断计数 attachInterrupt(digitalPinToInterrupt(MotorfLcountA),Read_Moto_fL,RISING);//左轮脉冲开中断计数 attachInterrupt(digitalPinToInterrupt(MotorRcountA),Read_Moto_R,RISING);//右轮脉冲开中断计数 attachInterrupt(digitalPinToInterrupt(MotorfRcountA),Read_Moto_fR,RISING);//前右轮脉冲开中断计数 while(millis()<nowtime); //达到50毫秒关闭中断 detachInterrupt(digitalPinToInterrupt(MotorLcountA));//左轮脉冲关中断计数 detachInterrupt(digitalPinToInterrupt(MotorfLcountA));//左轮脉冲关中断计数 detachInterrupt(digitalPinToInterrupt(MotorRcountA));//右轮脉冲关中断计数 detachInterrupt(digitalPinToInterrupt(MotorfRcountA));//右轮脉冲关中断计数 V_L=((motorL/390)*6.5*PI)/0.05; //单位cm/s V_fL=((motorfL/390)*6.5*PI)/0.05; //单位cm/s V_R=((motorR/390)*6.5*PI)/0.05; //单位cm/s V_fR=((motorfR/390)*6.5*PI)/0.05; //单位cm/s v1=V_L; vf1=V_fL; v2=V_R; vf2=V_fR; } /*************************** * 中断函数:读左轮脉冲 * **************************/ void Read_Moto_L(){ motorL++; } void Read_Moto_fL(){ motorfL++; } /************************** * 中断函数:读右轮脉冲 * *************************/ void Read_Moto_R(){ motorR++; } void Read_Moto_fR(){ motorfR++; } /*************************************** * Arduino主循环 * ***************************************/ void loop() { Read_Moto_V();//读取脉冲计算速度 Pwm_L=Incremental_Pi_L(V_L,Target_V_L);//左轮PI运算 Pwm_fL=Incremental_Pi_fL(V_fL,Target_V_fL);//前左轮PI运算 Pwm_R=Incremental_Pi_R(V_R,Target_V_R);//右轮PI运算 Pwm_fR=Incremental_Pi_fR(V_fR,Target_V_fR);//右轮PI运算 Serial.print(V_L);//直接用串口绘图画出速度曲线 Serial.print(","); Serial.print(V_R);//直接用串口绘图画出速度曲线 Serial.print(","); Serial.print(V_fR); Serial.print(","); Serial.println(V_fL); Set_Pwm(1,Pwm_L,Pwm_R); //设置左右轮速度 Setf_Pwm(1,Pwm_fL,Pwm_fR); //设置左右轮速度 }
由于我们还不太熟悉PID参数的调节,并且有一个编码器出现了问题,目前我们并没有完全实现利用PID算法控制小车的运动。而且可能因为机械误差的问题,还不能实现轮子的速度都保持一致。我们准备借助串口绘图器,控制变量法得出较为合适的PID参数。预计这两天内会解决问题。
版权声明:本文为CSDN博主「studyingbear」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/studingbear/article/details/121744644
暂无评论