2.树莓派4b+ubuntu18.04(ros版本melodic)+arduino mega自制两轮差速小车,实现建图导航功能

这篇文章介绍arduino使用和安装arduino_bridge

将arduino与树莓派连接
查看arduino的端口号,我们这里查看到的时ttyUSB0

ll /dev/ttyUSB*

在这里插入图片描述
将当前用户添加进dialout组

sudo usermod -a -G dialout your_user_name

然后重启树莓派,然后才能生效
然后如果你可以在列出的组中找到dialout,这就说明你已经加入到dialout中了
group
在这里插入图片描述

安装arduino

1.下载arduino ide安装包
官方下载链接:https://www.arduino.cc/en/Main/Software
在这里插入图片描述
2.使用tar命令对压缩包解压

tar -xvf arduino-1.x.y-linux64.tar.xz

3.将解压后的文件移动到/opt下

sudo mv arduino-1.x.y /opt

4.进入安装目录,对install.sh添加可执行权限,并执行安装

cd /opt/arduino-1.x.y
sudo chmod +x install.sh
sudo ./install.sh

5.启动并配置 Arduino IDE
在命令行直接输入:arduino,或者点击左下的显示应用程序搜索 arduino IDE。启动如下:
在这里插入图片描述

// 初始化函数
void setup() {//将LED灯引脚(引脚值为13,被封装为了LED_BUTLIN)设置为输出模式pinMode(LED_BUILTIN, OUTPUT);
}// 循环执行函数
void loop() {digitalWrite(LED_BUILTIN, HIGH);   // 打开LED灯delay(1000);                       // 休眠1000毫秒digitalWrite(LED_BUILTIN, LOW);    // 关闭LED灯delay(1000);                       // 休眠1000毫秒
}

在这里插入图片描述
开发板选择Arduino mega,端口号选择/dev/ttyUSB0
上传该代码,观察arduino板载灯闪烁
到此arduino的安装就结束了

接下来安装ros_arduino_bridge

该功能包包含Arduino库和用来控制Arduino的ROS驱动包,它旨在成为在ROS下运行Arduino控制的机器人的完整解决方案。

其中当前主要关注的是:功能包集中一个兼容不同驱动的机器人的基本控制器(base controller),它可以接收ROS Twist类型的消息,可以发布里程计数据。上位机需要使用ROS(建议 melodic);

1.下载

新建ROS工作空间

mkdir -p catkin_ws/src
cd catkin_ws
catkin_make

进入ROS工作空间的src目录,输入命令:

cd catkin_ws/src
git clone https://github.com/hbrobotics/ros_arduino_bridge.git

2.ros_arduino_bridge 架构
文件结构说明在这里插入代码片 ├── ros_arduino_bridge # metapackage (元功能包)
│ ├── CMakeLists.txt
│ └── package.xml
├── ros_arduino_firmware #固件包,更新到Arduino
│ ├── CMakeLists.txt
│ ├── package.xml
│ └── src
│ └── libraries #库目录
│ ├── MegaRobogaiaPololu #针对Pololu电机控制器,MegaRobogaia编码器的头文件定义
│ │ ├── commands.h #定义命令头文件
│ │ ├── diff_controller.h #差分轮PID控制头文件
│ │ ├── MegaRobogaiaPololu.ino #PID实现文件
│ │ ├── sensors.h #传感器相关实现,超声波测距,Ping函数
│ │ └── servos.h #伺服器头文件
│ └── ROSArduinoBridge #Arduino相关库定义
│ ├── commands.h #定义命令
│ ├── diff_controller.h #差分轮PID控制头文件
│ ├── encoder_driver.h #编码器驱动头文件
│ ├── encoder_driver.ino #编码器驱动实现, 读取编码器数据,重置编码器等
│ ├── motor_driver.h #电机驱动头文件
│ ├── motor_driver.ino #电机驱动实现,初始化控制器,设置速度
│ ├── ROSArduinoBridge.ino #核心功能实现,程序入口
│ ├── sensors.h #传感器头文件及实现
│ ├── servos.h #伺服器头文件,定义插脚,类
│ └── servos.ino #伺服器实现
├── ros_arduino_msgs #消息定义包
│ ├── CMakeLists.txt
│ ├── msg #定义消息
│ │ ├── AnalogFloat.msg #定义模拟IO浮点消息
│ │ ├── Analog.msg #定义模拟IO数字消息
│ │ ├── ArduinoConstants.msg #定义常量消息
│ │ ├── Digital.msg #定义数字IO消息
│ │ └── SensorState.msg #定义传感器状态消息
│ ├── package.xml
│ └── srv #定义服务
│ ├── AnalogRead.srv #模拟IO输入
│ ├── AnalogWrite.srv #模拟IO输出
│ ├── DigitalRead.srv #数字IO输入
│ ├── DigitalSetDirection.srv     #数字IO设置方向
│ ├── DigitalWrite.srv #数字IO输入
│ ├── ServoRead.srv #伺服电机输入
│ └── ServoWrite.srv #伺服电机输出
└── ros_arduino_python #ROS相关的Python包,用于上位机,树莓派等开发板或电脑等。
├── CMakeLists.txt
├── config #配置目录
│ └── arduino_params.yaml #定义相关参数,端口,rate,PID,sensors等默认参数。由arduino.launch调用
├── launch
│ └── arduino.launch #启动文件
├── nodes
│ └── arduino_node.py #python文件,实际处理节点,由arduino.launch调用,即可单独调用。
├── package.xml
├── setup.py
└── src #Python类包目录
└── ros_arduino_python
├── arduino_driver.py #Arduino驱动类
├── arduino_sensors.py #Arduino传感器类
├── base_controller.py #基本控制类,订阅cmd_vel话题,发布odom话题
└── init.py #类包默认空文件

上述目录结构虽然复杂,但是关注的只有两大部分:

ros_arduino_bridge/ros_arduino_firmware/src/libraries/ROSArduinoBridge
ros_arduino_bridge/ros_arduino_python/config/arduino_params.yaml

接下来我们对代码进行测试

1.串口命令

在主程序中,包含了 commands.h,该文件中包含了当前程序预定义的串口命令,可以编译程序并上传至 Arduino 电路板,然后打开串口监视器测试(当前程序并未修改,所以并非所有串口可用):

w 可以用于控制引脚电平
x 可以用于模拟输出
以LED灯控制为例,通过串口监视器录入命令:

w 13 0 == LED灯关闭
w 13 1 == LED灯打开
x 13 50 == LED灯PWM值为50
2.修改主程序入口,主要是添加
#define Tb6612_MOTOR_DRIVER

/**********************************************************************  ROSArduinoBridge控制差速驱动机器人的Arduino程序,允许通过一组简单的串行命令来控制机器人,并接收传感器和里程数据。该程序默认配置假定使用Arduino Mega、Pololu电机控制器盾和Robogaia Mega编码器盾。如果使用不同的电机控制器或编码器方法,可以编辑readEncoder()和setMotorSpeed()包装函数。*********************************************************************/
//是否启用基座控制器
#define USE_BASE      // Enable the base controller code
//#undef USE_BASE     // Disable the base controller code/* Define the motor controller and encoder library you are using */启用基座控制器需要设置的电机驱动以及编码器驱
#ifdef USE_BASE/* The Pololu VNH5019 dual motor driver shield *///#define POLOLU_VNH5019/* The Pololu MC33926 dual motor driver shield *///#define POLOLU_MC33926/* The RoboGaia encoder shield *///#define ROBOGAIA/* Encoders directly attached to Arduino board *///#define ARDUINO_ENC_COUNTER//1.添加自定义编码器驱动#define ARDUINO_MY_COUNTER/* L298 Motor driver*///#define L298_MOTOR_DRIVER#define Tb6612_MOTOR_DRIVER
#endif
//是否启用舵机
//#define USE_SERVOS  // Enable use of PWM servos as defined in servos.h
#undef USE_SERVOS     // Disable use of PWM servos//波特率
/* Serial port baud rate */
#define BAUDRATE     57600
//最大PWM值
/* Maximum PWM signal */
#define MAX_PWM        255#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif/* Include definition of serial commands */
#include "commands.h"/* Sensor functions */
#include "sensors.h"/* Include servo support if required */
#ifdef USE_SERVOS#include <Servo.h>#include "servos.h"
#endif//
#ifdef USE_BASE/* Motor driver function definitions */#include "motor_driver.h"/* Encoder driver function definitions */#include "encoder_driver.h"/* PID parameters and functions */#include "diff_controller.h"/* Run the PID loop at 30 times per second */#define PID_RATE           30     // Hz/* Convert the rate into an interval */const int PID_INTERVAL = 1000 / PID_RATE;/* Track the next time we make a PID calculation */unsigned long nextPID = PID_INTERVAL;/* Stop the robot if it hasn't received a movement commandin this number of milliseconds */#define AUTO_STOP_INTERVAL 2000long lastMotorCommand = AUTO_STOP_INTERVAL;
#endif/* Variable initialization */// A pair of varibles to help parse serial commands (thanks Fergs)
int arg = 0;
int index = 0;// Variable to hold an input character
char chr;// Variable to hold the current single-character command
char cmd;// Character arrays to hold the first and second arguments
char argv1[16];
char argv2[16];// The arguments converted to integers
long arg1;
long arg2;
//重置命令
/* Clear the current command parameters */
void resetCommand() {cmd = NULL;memset(argv1, 0, sizeof(argv1));memset(argv2, 0, sizeof(argv2));arg1 = 0;arg2 = 0;arg = 0;index = 0;
}/* Run a command.  Commands are defined in commands.h */
//执行串口命令
int runCommand() {int i = 0;char *p = argv1;char *str;int pid_args[4];arg1 = atoi(argv1);arg2 = atoi(argv2);switch(cmd) {case GET_BAUDRATE:Serial.println(BAUDRATE);break;case ANALOG_READ:Serial.println(analogRead(arg1));break;case DIGITAL_READ:Serial.println(digitalRead(arg1));break;case ANALOG_WRITE:analogWrite(arg1, arg2);Serial.println("OK"); break;case DIGITAL_WRITE:if (arg2 == 0) digitalWrite(arg1, LOW);else if (arg2 == 1) digitalWrite(arg1, HIGH);Serial.println("OK"); break;case PIN_MODE:if (arg2 == 0) pinMode(arg1, INPUT);else if (arg2 == 1) pinMode(arg1, OUTPUT);Serial.println("OK");break;case PING:Serial.println(Ping(arg1));break;
#ifdef USE_SERVOScase SERVO_WRITE:servos[arg1].setTargetPosition(arg2);Serial.println("OK");break;case SERVO_READ:Serial.println(servos[arg1].getServo().read());break;
#endif#ifdef USE_BASEcase READ_ENCODERS:Serial.print(readEncoder(LEFT));Serial.print(" ");Serial.println(readEncoder(RIGHT));break;case RESET_ENCODERS:resetEncoders();resetPID();Serial.println("OK");break;case MOTOR_SPEEDS:/* Reset the auto stop timer */lastMotorCommand = millis();// 如果参数 arg1 和 arg2 都为 0,则停止电机并重置 PID 控制器if (arg1 == 0 && arg2 == 0) {setMotorSpeeds(0, 0);// 停止电机resetPID();moving = 0; // 将 moving 标志设置为 0,表示没有运动}else moving = 1;//设置PID调试的目标值// 设置左右电机的目标每帧 ticks(脉冲) 数leftPID.TargetTicksPerFrame = arg1;rightPID.TargetTicksPerFrame = arg2;Serial.println("OK"); break;case UPDATE_PID:while ((str = strtok_r(p, ":", &p)) != '\0') {pid_args[i] = atoi(str);i++;}Kp = pid_args[0];Kd = pid_args[1];Ki = pid_args[2];Ko = pid_args[3];Serial.println("OK");break;
#endifdefault:Serial.println("Invalid Command");break;}
}/* Setup function--runs once at startup. */
void setup() {Serial.begin(BAUDRATE);// Initialize the motor controller if used */
#ifdef USE_BASE#ifdef ARDUINO_ENC_COUNTER//set as inputsDDRD &= ~(1<<LEFT_ENC_PIN_A);DDRD &= ~(1<<LEFT_ENC_PIN_B);DDRC &= ~(1<<RIGHT_ENC_PIN_A);DDRC &= ~(1<<RIGHT_ENC_PIN_B);//enable pull up resistorsPORTD |= (1<<LEFT_ENC_PIN_A);PORTD |= (1<<LEFT_ENC_PIN_B);PORTC |= (1<<RIGHT_ENC_PIN_A);PORTC |= (1<<RIGHT_ENC_PIN_B);// tell pin change mask to listen to left encoder pinsPCMSK2 |= (1 << LEFT_ENC_PIN_A)|(1 << LEFT_ENC_PIN_B);// tell pin change mask to listen to right encoder pinsPCMSK1 |= (1 << RIGHT_ENC_PIN_A)|(1 << RIGHT_ENC_PIN_B);// enable PCINT1 and PCINT2 interrupt in the general interrupt maskPCICR |= (1 << PCIE1) | (1 << PCIE2);#elif defined ARDUINO_MY_COUNTERinitEncoders();#endifinitMotorController();resetPID();
#endif/* Attach servos if used */#ifdef USE_SERVOSint i;for (i = 0; i < N_SERVOS; i++) {servos[i].initServo(servoPins[i],stepDelay[i],servoInitPosition[i]);}#endif
}/* Enter the main loop.  Read and parse input from the serial portand run any valid commands. Run a PID calculation at the targetinterval and check for auto-stop conditions.
*/
void loop() {while (Serial.available() > 0) {// Read the next characterchr = Serial.read();// Terminate a command with a CRif (chr == 13) {if (arg == 1) argv1[index] = NULL;else if (arg == 2) argv2[index] = NULL;runCommand();resetCommand();}// Use spaces to delimit parts of the commandelse if (chr == ' ') {// Step through the argumentsif (arg == 0) arg = 1;else if (arg == 1)  {argv1[index] = NULL;arg = 2;index = 0;}continue;}else {if (arg == 0) {// The first arg is the single-letter commandcmd = chr;}else if (arg == 1) {// Subsequent arguments can be more than one characterargv1[index] = chr;index++;}else if (arg == 2) {argv2[index] = chr;index++;}}}// If we are using base control, run a PID calculation at the appropriate intervals#ifdef USE_BASEif (millis() > nextPID) {updatePID();//更新 PID 控制器nextPID += PID_INTERVAL;}// Check to see if we have exceeded the auto-stop intervalif ((millis() - lastMotorCommand) > AUTO_STOP_INTERVAL) {;setMotorSpeeds(0, 0);moving = 0;}
#endif// Sweep servos
#ifdef USE_SERVOSint i;for (i = 0; i < N_SERVOS; i++) {servos[i].doSweep();}
#endif
}

2.encoder_driver文件修改

/* *************************************************************Encoder definitionsAdd an "#ifdef" block to this file to include support fora particular encoder board or library. Then add the appropriate#define near the top of the main ROSArduinoBridge.ino file.************************************************************ */#ifdef USE_BASE#ifdef ROBOGAIA/* The Robogaia Mega Encoder shield */#include "MegaEncoderCounter.h"/* Create the encoder shield object */MegaEncoderCounter encoders = MegaEncoderCounter(4); // Initializes the Mega Encoder Counter in the 4X Count mode/* Wrap the encoder reading function */long readEncoder(int i) {if (i == LEFT) return encoders.YAxisGetCount();else return encoders.XAxisGetCount();}/* Wrap the encoder reset function */void resetEncoder(int i) {if (i == LEFT) return encoders.YAxisReset();else return encoders.XAxisReset();}
#elif defined(ARDUINO_ENC_COUNTER)volatile long left_enc_pos = 0L;volatile long right_enc_pos = 0L;static const int8_t ENC_STATES [] = {0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0};  //encoder lookup table/* Interrupt routine for LEFT encoder, taking care of actual counting */ISR (PCINT2_vect){static uint8_t enc_last=0;enc_last <<=2; //shift previous state two placesenc_last |= (PIND & (3 << 2)) >> 2; //read the current state into lowest 2 bitsleft_enc_pos += ENC_STATES[(enc_last & 0x0f)];}/* Interrupt routine for RIGHT encoder, taking care of actual counting */ISR (PCINT1_vect){static uint8_t enc_last=0;enc_last <<=2; //shift previous state two placesenc_last |= (PINC & (3 << 4)) >> 4; //read the current state into lowest 2 bitsright_enc_pos += ENC_STATES[(enc_last & 0x0f)];}/* Wrap the encoder reading function */long readEncoder(int i) {if (i == LEFT) return left_enc_pos;else return right_enc_pos;}/* Wrap the encoder reset function */void resetEncoder(int i) {if (i == LEFT){left_enc_pos=0L;return;} else { right_enc_pos=0L;return;}}#elif defined ARDUINO_MY_COUNTER
//功能:实现左右电机的脉冲计数
//1.定义计数器volatile long left_count = 0L;volatile long right_count = 0L;
//2.初始化void initEncoders(){pinMode(LEFT_A,INPUT); // 21  --- 2pinMode(LEFT_B,INPUT); // 20  --- 3pinMode(RIGHT_A,INPUT);// 18  --- 5pinMode(RIGHT_B,INPUT);// 19  --- 4attachInterrupt(2,leftEncoderEventA,CHANGE);attachInterrupt(3,leftEncoderEventB,CHANGE);attachInterrupt(5,rightEncoderEventA,CHANGE);attachInterrupt(4,rightEncoderEventB,CHANGE);}
//3.编写中断的回调函数void leftEncoderEventA(){if(digitalRead(LEFT_A) == HIGH){if(digitalRead(LEFT_B) == HIGH){left_count++;} else {left_count--;}} else {if(digitalRead(LEFT_B) == LOW){left_count++;} else {left_count--;}}}void leftEncoderEventB(){if(digitalRead(LEFT_B) == HIGH){if(digitalRead(LEFT_A) == LOW){left_count++;} else {left_count--;}} else {if(digitalRead(LEFT_A) == HIGH){left_count++;} else {left_count--;}}}void rightEncoderEventA(){if(digitalRead(RIGHT_A) == HIGH){if(digitalRead(RIGHT_B) == HIGH){right_count++;} else {right_count--;}} else {if(digitalRead(RIGHT_B) == LOW){right_count++;} else {right_count--;}}  }void rightEncoderEventB(){if(digitalRead(RIGHT_B) == HIGH){if(digitalRead(RIGHT_A) == LOW){right_count++;} else {right_count--;}} else {if(digitalRead(RIGHT_A) == HIGH){right_count++;} else {right_count--;}}  }//4.实现编码器数据读和重置的函数
//i取值是LEFT或者RIGHT,是左右轮的标记long readEncoder(int i){if (i == LEFT) return left_count;else return right_count;}void resetEncoder(int i) {if (i == LEFT){left_count=0L;return;} else { right_count=0L;return;}}
#else#error A encoder driver must be selected!
#endif/* Wrap the encoder reset function */
void resetEncoders() {resetEncoder(LEFT);resetEncoder(RIGHT);
}#endif

3.motor_driver.h文件修改

/***************************************************************Motor driver function definitions - by James Nugen*************************************************************/#ifdef L298_MOTOR_DRIVER#define RIGHT_MOTOR_BACKWARD 5#define LEFT_MOTOR_BACKWARD  6#define RIGHT_MOTOR_FORWARD  9#define LEFT_MOTOR_FORWARD   10#define RIGHT_MOTOR_ENABLE 12#define LEFT_MOTOR_ENABLE 13#elif defined Tb6612_MOTOR_DRIVER//HL正转,LH反转#define AIN1 9#define AIN2 8#define PWMA 3#define STBY 10//第二个电机,待测#define BIN1 7#define BIN2 6#define PWMB 5
#endifvoid initMotorController();//初始化电机控制
void setMotorSpeed(int i, int spd);//设置单个电机转速
void setMotorSpeeds(int leftSpeed, int rightSpeed);//设置多个电机转速

4.motor_driver文件修改

/***************************************************************Motor driver definitionsAdd a "#elif defined" block to this file to include supportfor a particular motor driver.  Then add the appropriate#define near the top of the main ROSArduinoBridge.ino file.*************************************************************/#ifdef USE_BASE#ifdef POLOLU_VNH5019/* Include the Pololu library */#include "DualVNH5019MotorShield.h"/* Create the motor driver object */DualVNH5019MotorShield drive;/* Wrap the motor driver initialization */void initMotorController() {drive.init();}/* Wrap the drive motor set speed function */void setMotorSpeed(int i, int spd) {if (i == LEFT) drive.setM1Speed(spd);else drive.setM2Speed(spd);}// A convenience function for setting both motor speedsvoid setMotorSpeeds(int leftSpeed, int rightSpeed) {setMotorSpeed(LEFT, leftSpeed);setMotorSpeed(RIGHT, rightSpeed);}
#elif defined POLOLU_MC33926/* Include the Pololu library */#include "DualMC33926MotorShield.h"/* Create the motor driver object */DualMC33926MotorShield drive;/* Wrap the motor driver initialization */void initMotorController() {drive.init();}/* Wrap the drive motor set speed function */void setMotorSpeed(int i, int spd) {if (i == LEFT) drive.setM1Speed(spd);else drive.setM2Speed(spd);}// A convenience function for setting both motor speedsvoid setMotorSpeeds(int leftSpeed, int rightSpeed) {setMotorSpeed(LEFT, leftSpeed);setMotorSpeed(RIGHT, rightSpeed);}
#elif defined L298_MOTOR_DRIVERvoid initMotorController() {digitalWrite(RIGHT_MOTOR_ENABLE, HIGH);digitalWrite(LEFT_MOTOR_ENABLE, HIGH);}void setMotorSpeed(int i, int spd) {unsigned char reverse = 0;if (spd < 0){spd = -spd;reverse = 1;}if (spd > 150)spd = ;if (i == LEFT) { if      (reverse == 0) { analogWrite(RIGHT_MOTOR_FORWARD, spd); analogWrite(RIGHT_MOTOR_BACKWARD, 0); }else if (reverse == 1) { analogWrite(RIGHT_MOTOR_BACKWARD, spd); analogWrite(RIGHT_MOTOR_FORWARD, 0); }}else /*if (i == RIGHT) //no need for condition*/ {if      (reverse == 0) { analogWrite(LEFT_MOTOR_FORWARD, spd); analogWrite(LEFT_MOTOR_BACKWARD, 0); }else if (reverse == 1) { analogWrite(LEFT_MOTOR_BACKWARD, spd); analogWrite(LEFT_MOTOR_FORWARD, 0); }}}void setMotorSpeeds(int leftSpeed, int rightSpeed) {setMotorSpeed(LEFT, leftSpeed);setMotorSpeed(RIGHT, rightSpeed);}#elif defined Tb6612_MOTOR_DRIVER//1.初始化void initMotorController(){pinMode(AIN1,OUTPUT);pinMode(AIN2,OUTPUT);pinMode(PWMA,OUTPUT);pinMode(STBY,OUTPUT);pinMode(BIN1,OUTPUT);pinMode(BIN2,OUTPUT);pinMode(PWMB,OUTPUT);}//2.设置单电机转速void setMotorSpeed(int i, int spd){unsigned char reverse = 0;
// 如果速度小于0,则取反并设置反转标志if (spd < 0){spd = -spd;reverse = 1;}// 将速度限制在0-255之间if (spd > 150)spd = 150;//tiaoshiif (i == LEFT) { digitalWrite(STBY, HIGH); // 启用电机驱动器if (reverse == 0) { //左电机digitalWrite(AIN1, LOW);//正转digitalWrite(AIN2, HIGH);} else if (reverse == 1) { digitalWrite(AIN1, HIGH);//反转digitalWrite(AIN2, LOW);}analogWrite(PWMA,spd);// 设置电机速度} else if (i == RIGHT){ // 右电机digitalWrite(STBY, HIGH);if (reverse == 0) { // 正转digitalWrite(BIN1, LOW); // 设置电机反转digitalWrite(BIN2, HIGH);        } else if (reverse ==1) { // 反转digitalWrite(BIN1, HIGH); // 设置电机正转digitalWrite(BIN2, LOW);}analogWrite(PWMB, spd); // 设置电机速度}}//3.设置两个电机转速void setMotorSpeeds(int leftSpeed, int rightSpeed){setMotorSpeed(LEFT, leftSpeed);setMotorSpeed(RIGHT, rightSpeed);}#else#error A motor driver must be selected!
#endif#endif

5.diff_controller文件修改

/* Functions and type-defs for PID control.Taken mostly from Mike Ferguson's ArbotiX code which lives at:http://vanadium-ros-pkg.googlecode.com/svn/trunk/arbotix/
*//* PID setpoint info For a Motor */
typedef struct {double TargetTicksPerFrame;    // target speed in ticks per framelong Encoder;                  // encoder countlong PrevEnc;                  // last encoder count/** Using previous input (PrevInput) instead of PrevError to avoid derivative kick,* see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-derivative-kick/*/int PrevInput;                // last input//int PrevErr;                   // last error/** Using integrated term (ITerm) instead of integrated error (Ierror),* to allow tuning changes,* see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-tuning-changes/*///int Ierror;int ITerm;                    //integrated termlong output;                    // last motor setting
}
SetPointInfo;SetPointInfo leftPID, rightPID;/* PID Parameters */
float Kp = 1.5;
float Kd = 3.0;
float Ki = 0.1;
int Ko = 50;unsigned char moving = 0; // is the base in motion?/*
* Initialize PID variables to zero to prevent startup spikes
* when turning PID on to start moving
* In particular, assign both Encoder and PrevEnc the current encoder value
* See http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-initialization/
* Note that the assumption here is that PID is only turned on
* when going from stop to moving, that's why we can init everything on zero.
*/
void resetPID(){leftPID.TargetTicksPerFrame = 0.0;leftPID.Encoder = readEncoder(LEFT);leftPID.PrevEnc = leftPID.Encoder;leftPID.output = 0;leftPID.PrevInput = 0;leftPID.ITerm = 0;rightPID.TargetTicksPerFrame = 0.0;rightPID.Encoder = readEncoder(RIGHT);rightPID.PrevEnc = rightPID.Encoder;rightPID.output = 0;rightPID.PrevInput = 0;rightPID.ITerm = 0;
}/* PID routine to compute the next motor commands */
void doPID(SetPointInfo * p) {long Perror;long output;int input;//Perror = p->TargetTicksPerFrame - (p->Encoder - p->PrevEnc);input = p->Encoder - p->PrevEnc;Perror = p->TargetTicksPerFrame - input;//Serial.println(input);/** Avoid derivative kick and allow tuning changes,* see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-derivative-kick/* see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-tuning-changes/*///output = (Kp * Perror + Kd * (Perror - p->PrevErr) + Ki * p->Ierror) / Ko;// p->PrevErr = Perror;output = (Kp * Perror - Kd * (input - p->PrevInput) + p->ITerm) / Ko;p->PrevEnc = p->Encoder;output += p->output;// Accumulate Integral error *or* Limit output.// Stop accumulating when output saturatesif (output >= MAX_PWM)output = MAX_PWM;else if (output <= -MAX_PWM)output = -MAX_PWM;else/** allow turning changes, see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-tuning-changes/*/p->ITerm += Ki * Perror;p->output = output;p->PrevInput = input;
}/* Read the encoder values and call the PID routine */
void updatePID() {/* Read the encoders */leftPID.Encoder = readEncoder(LEFT);rightPID.Encoder = readEncoder(RIGHT);/* If we're not moving there is nothing more to do */if (!moving){/** Reset PIDs once, to prevent startup spikes,* see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-initialization/* PrevInput is considered a good proxy to detect* whether reset has already happened*/if (leftPID.PrevInput != 0 || rightPID.PrevInput != 0) resetPID();return;}/* Compute PID update for each motor *///分别调试左右轮doPID(&rightPID);doPID(&leftPID);/* Set the motor speeds accordingly */setMotorSpeeds(leftPID.output, rightPID.output);
}

修改完上述文件后,上传代码进行测试
打开串口监视器,然后输入命令,命令格式为: m num1 num2,num1和num2分别为单位时间内左右电机各自转动的编码器计数,而默认单位时间为 1/30 秒。
观察电机转动情况

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/36587.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PyCharm2024 for mac Python编辑开发

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff08;适合自己的M芯片版或Intel芯片版&#xff09;&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功3、打开访达&#xff0c;点击【文…

机器人控制系列教程之URDF自动生成工具

URDF文件的编写较为复杂&#xff0c;ROS官方提供了URDF的SolidWorks插件&#xff0c;可方便地将 SW 零件和装配体导出为 URDF 文件。导出器将创建一个类似 ROS 的软件包&#xff0c;其中包含网格、纹理和机器人&#xff08;URDF 文件&#xff09;目录。对于单一的 SolidWorks 零…

Python生成图形验证码

文章目录 安装pillow基本用法生成代码 安装pillow pip install pillow 基本用法 特殊字体文字 如下所示&#xff0c;将下载下来的ttf字体文件放到py文件同一文件夹下 分享一个免费下载字体网站&#xff1a;http://www.webpagepublicity.com/free-fonts.html 我选的字体是Baj…

Linux 基于sqlite3数据库的学生管理系统

一、数据库 sqlite官网&#xff1a;www.sqlite.org 1.1 数据库的安装 离线安装&#xff1a; sudo dpkg -i sqlite3_3.22.0-1ubuntu0.4_amd64.deb //数据库软件 sudo dpkg -i libsqlite3-dev_3.22.0-1ubuntu0.4_amd64.deb //数据库的库函数 在线安装&#xff1a; sudo apt-get …

推荐系统三十六式学习笔记:原理篇.模型融合13|经典模型融合办法:线性模型和树模型的组合拳

目录 为什么要融合&#xff1f;“辑度组合”原理逻辑回归梯度提升决策树GBDT二者结合 总结 推荐系统在技术实现上一般划分为三个阶段&#xff1a;挖掘、召回、排序 。 为什么要融合&#xff1f; 挖掘的工作是对用户和物品做非常深入的结构化分析&#xff0c;各个角度各个层面…

MySQL之可扩展性(六)

可扩展性 向外扩展 12.重新均衡分片数据 如有必要&#xff0c;可以通过在分片间移动数据来达到负载均衡。举个例子&#xff0c;许多读者可能听一些大型图片分享网站或流行社区网站的开发者提到过用于分片间移动用户数据的工具。在分片间移动数据的好处很明显。例如&#xff…

鸿蒙开发设备管理:【@ohos.batteryInfo (电量信息)】

电量信息 该模块主要提供电池状态和充放电状态的查询接口。 说明&#xff1a; 本模块首批接口从API version 6开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import batteryInfo from ohos.batteryInfo;属性 描述电池信息。 系统能…

NLP经典论文研读--xlnet论文代码复现记录

xlnet源码解读(简易pytorch实现版本) xlnet这个模型还是相当复杂的&#xff0c;我看了很长一段时间也还是有很多地方没有搞明白&#xff0c;最后又在网上搜了很多大佬写的相关博客&#xff0c;才算是大致弄明白了&#xff0c;想了解xlnet的原理&#xff0c;请参考原论文&#…

(2024,RNN,梯度消失和爆炸,记忆诅咒,重参数化和动态学习率,权重矩阵对角化,复值 RNN)梯度消失和爆炸并不是故事的结局

Recurrent neural networks: vanishing and exploding gradients are not the end of the story 公和众与号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. 梯度消失和梯度爆炸 2. 记…

%运算符

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法介绍 在python中&#xff0c;可以使用%运算符进行灵活多样的格式化处理&#xff0c;通用的语法格式为&#xff1a; &#xff08;格式模板&…

面试突击:ArrayList源码详解

本文已收录于&#xff1a;https://github.com/danmuking/all-in-one&#xff08;持续更新&#xff09; 前言 哈喽&#xff0c;大家好&#xff0c;我是 DanMu。ArrayList 是我们日常开发中不可避免要使用到的一个类&#xff0c;并且在面试过程中也是一个非常高频的知识点&#…

机器人控制系列教程之URDF文件语法介绍

前两期推文&#xff1a;机器人控制系列教程之动力学建模(1)、机器人控制系列教程之动力学建模(2)&#xff0c;我们主要从数学的角度介绍了机器人的动力学建模的方式&#xff0c;随着机器人技术的不断发展&#xff0c;机器人建模成为了机器人系统设计中的一项关键任务。URDF&…

ZSWatch 开源项目介绍

前言 因为时不时逛 GitHub 会发现一些比较不错的开源项目&#xff0c;突发奇想想做一个专题&#xff0c;专门记录开源项目&#xff0c;内容不限于组件、框架以及 DIY 作品&#xff0c;希望能坚持下去&#xff0c;与此同时&#xff0c;也会选取其中的开源项目做专题分析。希望这…

基于Java的汽车租赁系统【附源码】

论文题目 设计&#xff08;论文&#xff09;综述&#xff08;1000字&#xff09; 当今社会&#xff0c;汽车租赁已成为一种受欢迎的出行方式。本文旨在探讨汽车租赁行业的发展趋势、市场规模及其对环境的影响。目前&#xff0c;汽车租赁行业正在经历着快速的发展。随着经济的发…

【独家揭秘】SmartEDA电路仿真软件:电子电路基础学习的神器!

在电子科技日新月异的今天&#xff0c;电路基础学习的重要性愈发凸显。但你是否曾为复杂的电路图、难以理解的电路原理而感到困扰&#xff1f;今天&#xff0c;我要向大家推荐一款学习神器——SmartEDA电路仿真软件&#xff0c;让你轻松踏入电子电路基础学习的殿堂&#xff01;…

22 Shell编程之免交互

目录 22.1 Here Document免交互 22.1.1 Here Document概述 22.1.2 Here Document免交互 22.1.3 Here Document变量设定 22.1.4 Here Document格式控制 22.1.5 Here Document多行注释 22.2 expect免交互 22.2.1 expect概述 22.2.2 expect安装 22.2.3 基本命令介绍 22.2.4expec…

ARM裸机:地址映射

S5PV210的地址映射详解 什么是地址映射&#xff1f; S5PV210属于ARM Cortex-A8架构&#xff0c;32位CPU&#xff0c;CPU设计时就有32根地址线&32根数据线。 32根地址线决定了CPU的地址空间为4G&#xff0c;那么这4G空间如何分配使用&#xff1f;这个问题就是内存映射问题。…

NAND闪存巨头铠侠(Kioxia)计划最迟于10月下旬通过首次公开募股IPO

据路透社于6月26日引用消息来源的报道&#xff0c;在半导体市场条件反弹及财务业绩迅速改善的背景下&#xff0c;NAND闪存巨头铠侠&#xff08;Kioxia&#xff09;正准备尽快提交初步申请&#xff0c;并计划最迟于10月下旬通过首次公开募股&#xff08;IPO&#xff09;在东京证…

9.二维数组的遍历和存储

二维数组的遍历和存储 二维数组的遍历 二维数组a[3][4],可分解为三个一维数组,其数组名分别为: 这三个一维数组都有4个元素,例如:一维数组a[0]的 元素为a[0][0],a[0][1],a[0][2],a[0][3]。所以遍历二维数组无非就是先取出二维数组中得一维数组, 然后再从一维数组中取出每个元…

Eclipse代码编辑器自主配色

1. 打开 Eclipse 的设置 - Java - Editor - Syntax Coloring 2. 自定义各种类型的颜色&#xff0c;例如&#xff1a; 1. Interface 勾选&#xff0c;设置为紫色 2. Class 勾选&#xff0c;设置为淡蓝色 3. Abstract classes 勾选&#xff0c;有自己默认的颜色 …