/*このスケッチを丸ごとコピーするだけでは再現できません。
特に倒立は、フレームの形状や使用部品及び部品配置によってバランスが異なり、
安定させるには機体に合わせた設定値の変更が必要です。
------------------------------------------------------------------------------------*/
#include "Servo.h" //サーボ用ライブラリー
#include "IRremote.h" //赤外線リモコン用ライブラリー
//ArduinoとMPU6050とのI2C通信用に以下3つのライブラリー追加する。
#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"
Servo leftservo; //左ドライブ用サーボの宣言
Servo rightservo; //右ドライブ用サーボの宣言
Servo elv_servo; //エレベーターアーム用サーボの宣言
Servo spa_servo; //サポートアーム用サーボの宣言
//MPU6050の設定値
MPU6050 accelgyro;
int16_t ax, ay, az;
int16_t gx, gy, gz;
#define Gry_offset 0
#define Gyr_Gain 0.00763358
#define pi 3.14159
/* 重要“Angle_offset”は倒立時の前後バランスの設定(この設定が狂ってると上手く倒立出来ません)
センサーの取り付けは、振動などで動かないように固定します。*/
#define Angle_offset -2.5
//IRリモコン関連の設定
#define MAX_TIME 150 // max ms between codes
long lastPressTime = 0;
int state = HIGH;
int receiver = 4; //D2:IR受信モジュールの入力端子を指定
IRrecv irrecv(receiver);
decode_results results;
//PID制御関連
float kp, ki, kd;
float r_angle, f_angle, omega;
float Turn_Speed = 0, Turn_Speed_K = 0;
float Run_Speed = 0, Run_Speed_K = 0;
float LOutput, ROutput;
unsigned long preTime = 0;
float SampleTime = 0.08;//0.08
unsigned long lastTime;
float Input, Output;
float errSum, dErr, error, lastErr;
int timeChange;
float kidt;
//超音波センサーの入出力端子設定
int trigger_front = 12;
int echo_front = 13;
int trigger_left = 10;
int echo_left = 11;
int trigger_right = 6;
int echo_right = 7;
//mode表示LEDの設定
int modeLED1 = 14;
int modeLED2 = 15;
int modeLED3 = 16;
int powerLED = 17;
char contcommand;
int modecontrol = 0; //初期モード設定
int power = 0; //初期モード設定
int pos1 = 175; //ELVサーボの初期値設定
int pos2 = 160; //SPAサーボの初期値設定
void setup() { //初期設定
Wire.begin();
accelgyro.initialize();
Serial.begin(115200);
irrecv.enableIRIn();
elv_servo.attach(3);
elv_servo.write(pos1);
spa_servo.attach(5);
spa_servo.write(pos2);
leftservo.attach(8);
leftservo.write(90);
rightservo.attach(9);
rightservo.write(90);
pinMode(trigger_front, OUTPUT);
pinMode(echo_front, INPUT);
pinMode(trigger_left, OUTPUT);
pinMode(echo_left, INPUT);
pinMode(trigger_right, OUTPUT);
pinMode(echo_right, INPUT);
pinMode(modeLED1, OUTPUT);
pinMode(modeLED2, OUTPUT);
pinMode(modeLED3, OUTPUT);
pinMode(powerLED, OUTPUT);
digitalWrite(modeLED1, LOW);
digitalWrite(modeLED2, HIGH);
digitalWrite(modeLED3, HIGH);
digitalWrite(powerLED, HIGH);
}
void loop() {
if (irrecv.decode(&results)) { // 赤外線リモコンのボタンが放された時の処理
INVremote();
irrecv.resume(); // Receive the next value
if (state == HIGH) {
state = LOW; // Button pressed, so set state to LOW
}
lastPressTime = millis();
irrecv.resume(); // Receive the next value
}
if (state == LOW && millis() - lastPressTime > MAX_TIME) {
state = HIGH;
Run_Speed_K = 0;//リモコンボタンが放された時0に戻す
Turn_Speed = 0;//リモコンボタンが放された時0に戻す
/*IRリモコンの送信機について
使用するIRリモコンはボタンが押されてる間、同じコードが連続出力するタイプで有ること。
その理由は、倒立運転時に前進/後進ボタンが押されると出力されたコード数を加減算して
前後進の速度制御を行うからです。
使用するIRリモコンにより、出力コードが異なるので先にコード確認用ソフトで使用ボタンの
コードを調べて書き換えます。以下のコードはシャープのテレビ用リモコンの例です。*/
if (results.value == 0xF1480148) { //"テレビ用赤外線リモコンの青ボタン" mode0(停止)
Stop();
modecontrol = 0;
power = 0;
digitalWrite (powerLED, HIGH);
digitalWrite (modeLED1, LOW);
digitalWrite (modeLED2, HIGH);
digitalWrite (modeLED3, HIGH);
}
if (results.value == 0xF1488140) { //"テレビ用赤外線リモコンの赤ボタンに設定" mode1(自律走行開始)
modecontrol = 0;
power = 1;
digitalWrite (powerLED, LOW);
digitalWrite (modeLED1, LOW);
digitalWrite (modeLED2, HIGH);
digitalWrite (modeLED3, HIGH);
}
if (results.value == 0xF148414C) { //"テレビ用赤外線リモコンの緑ボタンに設定" mode2(手動運転開始)
modecontrol = 1;
//digitalWrite (powerLED, HIGH);
digitalWrite (modeLED1, HIGH);
digitalWrite (modeLED2, LOW);
digitalWrite (modeLED3, HIGH);
Stop();
}
if (results.value == 0xF148C144) { //"テレビ用赤外線リモコンの黄ボタンに設定" mode3(倒立自動運転開始)
INV_start(); //起立アームの起動
delay(2000); //安定時間の設定
INV_end1(); //起立補助アームの格納
digitalWrite (modeLED1, HIGH);
digitalWrite (modeLED2, HIGH);
digitalWrite (modeLED3, LOW);
}
}
if ( power == 1) { // 超音波のセンサー受信距離による自律運転
// mode1 各超音波センサーの制御
long duration_front, duration_left, duration_right, right, left, front;
digitalWrite(trigger_front, LOW);
delayMicroseconds(2);
digitalWrite(trigger_front, HIGH);
delayMicroseconds(5);
digitalWrite(trigger_front, LOW);
duration_front = pulseIn(echo_front, HIGH);
front = duration_front / 29 / 2;
Serial.print("front"); Serial.println(front);
digitalWrite(trigger_left, LOW);
delayMicroseconds(2);
digitalWrite(trigger_left, HIGH);
delayMicroseconds(5);
digitalWrite(trigger_left, LOW);
duration_left = pulseIn(echo_left, HIGH);
left = duration_left / 29 / 2;
Serial.print("left"); Serial.println(left);
digitalWrite(trigger_right, LOW);
delayMicroseconds(2);
digitalWrite(trigger_right, HIGH);
delayMicroseconds(5);
digitalWrite(trigger_right, LOW);
duration_right = pulseIn(echo_right, HIGH);
right = duration_right / 29 / 2;
Serial.print("right"); Serial.println(right);
//以下の障害物回避制御は簡単な方法ですが、工夫によってはもっと複雑に制御が可能です。
if (front > 15 && right > 10 && left > 10 ) { //前方の及び左右の設定距離内に障害物が無い場合。
forward(); //後進
}
if (front < 15 && right < left) { //前方の及び左右の設定距離内に障害物が無い場合。
back();
turnleft(); //左旋回
}
if (front < 15 && right > left) { //今度は、右側が障害物に近い場合の処理。
back();
turnright(); //右旋回
}
if (right < 10) { //左ショート旋回
s_left();
}
if (left < 10) { //右ショート旋回
s_right();
}
}
if (modecontrol == 1) { // mode2 手動運転時の赤外線リモコン操作
switch (results.value) {
case 0xF148EA81: //テレビ用赤外線リモコンの十字キーの“↑キー”に設定
forward();
break;
case 0xF148EB80: //テレビ用赤外線リモコンの十字キーの“←キー”に設定
s_left();
break;
case 0xF1484A8B: //テレビ用赤外線リモコンの十字キー中心の“決定ボタン”に設定
Stop();
break;
case 0xF1481B8F: //テレビ用赤外線リモコンの十字キーの“→キー”に設定
s_right();
break;
case 0xF1480481: //テレビ用赤外線リモコンの十字キーの“↓キー”に設定↓
back();
break;
case 0xF148AF80: //テレビ用赤外線リモコンの“終了ボタン”に設定
modecontrol = 0;
Stop();
digitalWrite (modeLED1, HIGH);
digitalWrite (modeLED2, LOW);
digitalWrite (modeLED3, HIGH);
break;
default:
;
}
delay(500);
}
if (modecontrol == 2 ) { // mode3 倒立制御
accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
r_angle = (atan2(ay, az) * 180 / pi + Angle_offset);
omega = Gyr_Gain * (gx + Gry_offset);
if (abs(r_angle) < 45) { //前後の倒立角度が45度以内の場合の処理。
myPID();
ServoControl();
}
else { //前後の倒立角度が45度以上に傾いた場合はサーボ出力を中立(90)にする。
leftservo.write(90);
rightservo.write(90);
}
if (pos1 <= 50) { //倒立成立時の処理:ELVサーボの設定値が50以下の場合の処理。
delay (50);
spa_servo.write(pos2 = 160); //設定値は機体に応じて設定が必要
elv_servo.write(pos1 = 175); //設定値は機体に応じて設定が必要
delay (130);
kidt = 0.12;
}
}
}
void INV_start() { //起立アーム操作
spa_servo.write(pos2 = 145); //設定値は機体に応じてカット&トライの設定が必要
for (pos1 = 175; pos1 > 43.5; pos1 -= 1) { //設定値は機体に応じてカット&トライの設定が必要
elv_servo.write(pos1);
delay (10);
}
}
void INV_end1() { //アーム格納操作1
spa_servo.write(pos2 = 146);//144.9 //設定値は機体に応じてカット&トライの設定が必要
elv_servo.write(pos1 = 42.5);//44.2 //設定値は機体に応じてカット&トライの設定が必要
kidt = 0.02; //倒立起動時のみki値を下げます。
modecontrol = 2;
}
void INV_end2() { //アーム格納操作2
spa_servo.write(pos2 = 143.0); //設定値は機体に応じてカット&トライの設定変更が必要
elv_servo.write(pos1 = 46.5); //設定値は機体に応じてカット&トライの設定変更が必要
for (pos1 = 45.0; pos1 < 175; pos1 += 1) { //設定値は機体に応じてカット&トライの設定が必要
elv_servo.write(pos1);
delay (15);
}
delay (100);
spa_servo.write(pos2 = 160);
leftservo.write(90);
rightservo.write(90);
digitalWrite (modeLED2, LOW);
digitalWrite (modeLED3, HIGH);
modecontrol = 0;
}
void INVremote() { //mode3 倒立運転時のリモコン操作
switch (results.value) {
case 0XF148EA81: // forward:前進
Serial.print("FORWARD"); Serial.println(Run_Speed);
Run_Speed_K = -10;
Run_Speed = Run_Speed + Run_Speed_K;//十字キーの“↑キー”を押してる間、-10を加算し続けます。
break;
case 0XF1480481: // back:後進
Serial.print("BACK"); Serial.println(Run_Speed);
Run_Speed_K = 10;
Run_Speed = Run_Speed + Run_Speed_K;//十字キーの“↓キー”を押してる間、10を加算し続けます。
break;
case 0XF148EB80: // left:左旋回
Serial.print("LEFT"); Serial.println(Turn_Speed);
Turn_Speed = -20;
break;
case 0XF1481B8F: // right:右旋回
Serial.print("RIGHT"); Serial.println(Turn_Speed);
Turn_Speed = 20;
break;
default:
Run_Speed_K = 0;
Turn_Speed = 0;
}
}
void myPID() { //倒立PID制御
kp = 14; //要調整 設定値は機体に応じてカット&トライの設定変更が必要
ki = kidt;
kd = 200; //要調整 設定値は機体に応じてカット&トライの設定変更が必要
unsigned long now = millis();
float dt = (now - preTime) / 1000.0;
preTime = now;
float K = 0.8;//0.8
float A = K / (K + dt);
f_angle = A * (f_angle + omega * dt) + (1 - A) * r_angle;
timeChange = (now - lastTime);
if (timeChange >= SampleTime) {
Input = f_angle;
error = Input;
errSum += error * timeChange;
dErr = (error - lastErr) / timeChange;
Output = kp * error + ki * errSum + kd * dErr;
LOutput = Output + Run_Speed + Turn_Speed;
ROutput = Output + Run_Speed - Turn_Speed;
lastErr = error;
lastTime = now;
}
}
void ServoControl() { // mode3 倒立運転時のサーボモーター制御
LOutput = constrain(LOutput, -110, 100);
ROutput = constrain(ROutput, -110, 100);
if (LOutput > 0) { // 左サーボモーターの制御
leftservo.write(LOutput = map(LOutput, 0, -100, 90, 110));//(LOutput, 0, 255, 90, 180)
}
else if (LOutput < 0) {
leftservo.write(LOutput = map(LOutput, 0, 100, 90, 70));//(LOutput, 0, -255, 90, 0)
}
else {
leftservo.write(90);
}
if (ROutput > 0) { // 右サーボモーターの制御
rightservo.write(ROutput = map(ROutput, 0, -100, 90, 65));//(ROutput, 0, 255, 90, 0)
}
else if (ROutput < 0) {
rightservo.write(ROutput = map(ROutput, 0, 100, 90, 115));//(ROutput, 0, -255, 90, 180)
}
else {
rightservo.write(90);
}
}
//超音波運転及び手動運転時のサーボモータ制御
void forward() { //前進
leftservo.write(79.5);//80
rightservo.write(100);//100
}
void Stop() { //停止
leftservo.write(90);
rightservo.write(90);
}
void turnright() { //右旋回(右超信地旋回)
leftservo.write(80);//0
rightservo.write(80);//0
delay(300);
}
void turnleft() { //左旋回(左超信地旋回)
leftservo.write(100);//180
rightservo.write(100);//180
delay(300);
}
void s_right() { //右ショート旋回
leftservo.write(75 );//75
rightservo.write(100);//100
delay(100);
}
void s_left() { //左ショート旋回
leftservo.write(80);//80
rightservo.write(105);//105
delay(100);
}
void back() { //後進
leftservo.write(97);//100
rightservo.write(80);//80
delay(600);
}
|