自作デジタルスタビライザの制作
1. 概要
ここでは3つのサーボとArduino Nanoと加速度&ジャイロセンサーを使ってデジタリスタビライザーを製作してみたので紹介します。
とりあえず出来上がりはこんな感じで、こんな風に握り手を動かしても先端に取り付けられたスマホが水平を保ってくれるように動きます。
またジョイスティックで方向を変えられるようにしました。
主な構成要素
・Micro2BB サーボ 3個
・Arduino Nano コントロールボード
・MPU6050 3軸加速度センサー&3軸ジャイロ または MPU9250 3軸加速度センサー&3軸ジャイロ&3軸磁気センサー
・ジョイススティック
・基板(ユニバーサル基板)
・6Vニッケル水素電池
・3Dプリントしたサーボブラケットとケース
2. システム設計
システムの構成要素は次のようなものになっています。
ユニバーサル基板の回路図はこちら
3. 加速度&ジャイロセンサー
今回使用するのはMPU6050というチップの載せた3軸加速度+3軸ジャイロセンサーです。
Type:GY521
Weight:18.1 g
Size:3 x 2.1 x 0.5 cm
Input Voltage:3-5V
Interface: I2C (2 wire, IIC)
Gyro rage:±250 500 1000 2000 °/s
Accelerometer rage:±2±4±8±16g
データシートはこちら
MPU-6050 DataSheetMPU-6050 Register Map
4. 加速度&ジャイロセンサー&地磁気センサー
さらにMPU9250 3軸加速度+3軸ジャイロセンサー+3軸地磁気センサーでも動かしてみます。
モジュールモデル:GY-9250
チップ:MPU-9250
コア電圧:DC2.4V~3.6V
・I/O電圧:1.71V~コア電圧まで
・インターフェース:I2CまたはSPI
・サイズ:約13x11mm
・重さ:0.45g (端子含まず)
加速度
・測定レンジ ±2 / ±4 / ±8 / ±16g
・分解能:16ビット
・出力レート:0.24~4000Hz
ジャイロ
・測定レンジ ±250 / ±500 / ±1000 / ±2000dps(°/sec)
・分解能:16ビット
・出力レート:4~8000Hz
コンパス(AK8963)
・測定レンジ:±4800μT
・分解能:14ビット/16ビット
取り付けもMPU6050と互換性があります
今回は比較的実装が簡単な相補フィルターを使用します。
基本的な考え
角度 = 0.98 x (前回の角度 + ジャイロの値 x サンプリング周期) x 0.02 x 加速度センサーで出した角度
5. 構造設計
構造設計はFusion360で行い、3Dプリンターで出力しました。
6. Arduinoのスケッチ
Arduinoのスケッチを示します。(良いスケッチとは思えませんが)
MPU6050の場合
#include#include const int MPU_addr=0x68; // I2C address of the MPU-6050 int16_t AcXr,AcYr,AcZr,Tmpr,GyXr,GyYr,GyZr; float AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ; float AcXAng,AcZAng; float angX = 0.0; float angY = 0.0; float angZ = 0.0; int angXi = 0; int angYi = 0; int angZi = 0; int angXiLast = 0; int angYiLast = 0; int angZiLast = 0; int aX,aY,aZ; int angXout, angYout,angZout; int ZeroX = 93; int ZeroY = 82; int ZeroZ = 93; float dt=30; int djoy=3; int joy_1 = 1; int joy_2 = 2; int val1,val2; int angjoy1 =0; int angjoy2 =0; int dstep = 50; Servo myservo2; Servo myservo3; Servo myservo4; int Width_Min = 700; //最小パルス幅(ms) int Width_Max = 2300; //最大パルス幅(ms) void setup() { Wire.begin(); Wire.beginTransmission(MPU_addr); Wire.write(0x6B); // PWR_MGMT_1 register Wire.write(0); // set to zero (wakes up the MPU-6050) Wire.endTransmission(true); //LPF設定 Wire.beginTransmission(MPU_addr); Wire.write(0x1A); Wire.write(0x01); Wire.endTransmission(); myservo2.attach(2,Width_Min,Width_Max); myservo3.attach(3,Width_Min,Width_Max); myservo4.attach(4,Width_Min,Width_Max); Serial.begin(9600); } void loop() { Wire.beginTransmission(MPU_addr); Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H) Wire.endTransmission(false); Wire.requestFrom(MPU_addr,14,true); // request a total of 14 registers AcXr=Wire.read()<<8|Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L) AcYr=Wire.read()<<8|Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L) AcZr=Wire.read()<<8|Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L) Tmpr=Wire.read()<<8|Wire.read(); // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L) GyXr=Wire.read()<<8|Wire.read(); // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L) GyYr=Wire.read()<<8|Wire.read(); // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L) GyZr=Wire.read()<<8|Wire.read(); // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L) // joystic data val1 = analogRead(joy_1); val2 = analogRead(joy_2); if (val1 < 300){ angjoy1 = angjoy1 - djoy; } if (val1 > 700){ angjoy1 = angjoy1 + djoy; } if (val2 < 300){ angjoy2 = angjoy2 - djoy; } if (val2 > 700){ angjoy2 = angjoy2 + djoy; } // accel data // full scale range ±2g LSB sensitivity 16384 LSB/g) -> ±2 x 16384 = ±32768 LSB AcX = AcXr/16384.0; AcY = AcYr/16384.0; AcZ = AcZr/16384.0; // temperture Tmp = Tmpr/320.00+36.53; // gyro data // full scale range ±250 deg/s LSB sensitivity 131 LSB/deg/s -> ±250 x 131 = ±32750 LSB GyX = GyXr/131.0; GyY = GyYr/131.0; GyZ = GyZr/131.0; AcXAng = atan(AcZ/AcY)/3.142*180; AcZAng = atan(AcX/AcY)/3.142*180; //相補フィルター angX = 0.95*(angX - GyX*dt*0.001*1.5) + 0.05*AcXAng; angZ = 0.95*(angZ + GyZ*dt*0.001*1.2) + 0.05*AcZAng; //鉛直軸 angY = angY - GyY*dt*0.001; angXiLast = angXi; angYiLast = angYi; angZiLast = angZi; angXi = (int) angX + angjoy1; angYi = (int) angY - angjoy2; angZi = (int) angZ; aX = angXi - angXiLast; aY = angYi - angYiLast; aZ = angZi - angZiLast; for (int k=1; k <= dstep ; k++){ angXout = aX*k/dstep + angXiLast; angYout = aY*k/dstep + angYiLast; angZout = aZ*k/dstep + angZiLast; if (angXout < -40) angXout = -40; if (angXout > 20) angXout = 20; if (angYout < -80) angYout = -80; if (angXout > 80) angYout = 80; if (angZout < -70) angZout = -70; if (angZout > 70) angZout = 70; myservo2.write(ZeroY - angYout); myservo3.write(ZeroX - angXout); myservo4.write(ZeroZ - angZout); delayMicroseconds(dt/dstep*1000); } }
MPU9250の場合
#include#include const int MPU_addr=0x68; // I2C address of the MPU-6050 const int AK8963_addr=0x0c; // I2C address of the AK8963 int16_t AcXr,AcYr,AcZr,Tmpr,GyXr,GyYr,GyZr,MgXr,MgYr,MgZr; int16_t Mg[7]; float AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ,MgX,MgY,MgZ,MgXmin,MgYmin,MgZmin,MgXmax,MgYmax,MgZmax,MgXoff,MgYoff,MgZoff; float AcXAng,AcZAng,AcXAngPi,AcZAngPi,MgYAng; float Upper,Lower; float angX = 0.0; float angY = 0.0; float angZ = 0.0; int angXi = 0; int angYi = 0; int angZi = 0; int angXiLast = 0; int angYiLast = 0; int angZiLast = 0; int aX,aY,aZ; int angXout, angYout,angZout; int ZeroX = 93; int ZeroY = 82; int ZeroZ = 93; float dt=30; int djoy=3; int joy_1 = 1; int joy_2 = 2; int val1,val2; int angjoy1 =0; int angjoy2 =0; int dstep = 50; Servo myservo2; Servo myservo3; Servo myservo4; int Width_Min = 700; //最小パルス幅(ms) int Width_Max = 2300; //最大パルス幅(ms) void setup() { Wire.begin(); Wire.beginTransmission(MPU_addr); Wire.write(0x6B); // PWR_MGMT_1 register Wire.write(0); // set to zero (wakes up the MPU-6050) Wire.endTransmission(true); //LPF設定 Wire.beginTransmission(MPU_addr); Wire.write(0x1A); Wire.write(0x01); Wire.endTransmission(); //AK8963設定 Wire.beginTransmission(MPU_addr); Wire.write(0x37); //磁気センサのバイパスモード設定用のアドレス Wire.write(0x02); //bypass mode(磁気センサが使用出来るようになる) Wire.endTransmission(); Wire.beginTransmission(AK8963_addr); Wire.write(0x0a); //磁気センサ設定用のアドレス Wire.write(0x16); //磁気センサの出力周期(100Hz) 0x12:磁気センサの出力周期(8Hz) Wire.endTransmission(); myservo2.attach(2,Width_Min,Width_Max); myservo3.attach(3,Width_Min,Width_Max); myservo4.attach(4,Width_Min,Width_Max); Serial.begin(9600); } void loop() { Wire.beginTransmission(MPU_addr); Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H) Wire.endTransmission(false); Wire.requestFrom(MPU_addr,14,true); // request a total of 14 registers AcXr=Wire.read()<<8|Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L) AcYr=Wire.read()<<8|Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L) AcZr=Wire.read()<<8|Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L) Tmpr=Wire.read()<<8|Wire.read(); // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L) GyXr=Wire.read()<<8|Wire.read(); // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L) GyYr=Wire.read()<<8|Wire.read(); // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L) GyZr=Wire.read()<<8|Wire.read(); // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L) Wire.beginTransmission(AK8963_addr); Wire.write(0x02); // starting with register 0x02 Wire.requestFrom(AK8963_addr,1,true); Wire.endTransmission(false); //ST1Bit=Wire.read(); if((Wire.read() & 0x01)){ //読み出し準備ができたか確認 Wire.beginTransmission(AK8963_addr); Wire.write(0x03); // starting with register 0x02 Wire.endTransmission(false); Wire.requestFrom(AK8963_addr,7,true); // request a total of 7 registers MgYr=Wire.read()|(Wire.read()<<8); MgXr=Wire.read()|(Wire.read()<<8); MgZr=-(Wire.read()|(Wire.read()<<8)); } // joystic data val1 = analogRead(joy_1); val2 = analogRead(joy_2); if (val1 < 300){ angjoy1 = angjoy1 - djoy; } if (val1 > 700){ angjoy1 = angjoy1 + djoy; } if (val2 < 300){ angjoy2 = angjoy2 - djoy; } if (val2 > 700){ angjoy2 = angjoy2 + djoy; } // accel data // full scale range ±2g LSB sensitivity 16384 LSB/g) -> ±2 x 16384 = ±32768 LSB AcX = AcXr/16384.0; AcY = AcYr/16384.0; AcZ = AcZr/16384.0; // temperture Tmp = Tmpr/320.00+36.53; // gyro data // full scale range ±250 deg/s LSB sensitivity 131 LSB/deg/s -> ±250 x 131 = ±32750 LSB GyX = GyXr/131.0; GyY = GyYr/131.0; GyZ = GyZr/131.0; AcZAngPi = atan(AcX/AcY); AcZAng = AcZAngPi/3.142*180; AcXAngPi = atan(-AcZ/(AcX*sin(AcZAngPi)+AcY*cos(AcZAngPi))); AcXAng = AcXAngPi/3.142*180; // Magnetic data MgX = MgXr / 32768.0f * 4800.0f;//[uT]に変換 MgY = MgYr / 32768.0f * 4800.0f;//[uT]に変換 MgZ = MgZr / 32768.0f * 4800.0f;//[uT]に変換 //Calculation of offset if (MgX <= MgXmin) MgXmin = MgX; if (MgX >= MgXmax) MgXmax = MgX; if (MgY <= MgYmin) MgYmin = MgY; if (MgY >= MgYmax) MgYmax = MgY; if (MgZ <= MgZmin) MgZmin = MgZ; if (MgZ >= MgZmax) MgZmax = MgZ; MgXoff = (MgXmax - MgXmin)/2 + MgXmin; MgYoff = (MgYmax - MgYmin)/2 + MgYmin; MgZoff = (MgZmax - MgZmin)/2 + MgZmin; Upper = (MgY - MgYoff)*sin(AcZAngPi) - (MgX - MgXoff)*cos(AcZAngPi); Lower = (MgZ - MgZoff)*cos(AcXAngPi) + (MgX - MgXoff)*sin(AcXAngPi)*sin(AcZAngPi) + (MgY - MgYoff)*sin(AcXAngPi)*cos(AcZAngPi); if(Lower > 0.0){ if(Upper > 0.0){ MgYAng = atan(Upper/Lower)/3.142*180; }else if(Upper < 0.0){ MgYAng = atan(Upper/Lower)/3.142*180; } }else if(Lower < 0.0){ if(Upper > 0.0){ MgYAng = atan(Upper/Lower)/3.142*180 + 180; }else if(Upper < 0.0){ MgYAng = atan(Upper/Lower)/3.142*180 - 180; } } //相補フィルター angX = 0.9*(angX - GyX*dt*0.001*1.5) - 0.1*AcXAng; angZ = 0.9*(angZ + GyZ*dt*0.001*1.2) + 0.1*AcZAng; angY = 0.9*(angY - GyY*dt*0.001) - 0.1*MgYAng; angXiLast = angXi; angYiLast = angYi; angZiLast = angZi; angXi = (int) angX + angjoy1; angYi = (int) angY - angjoy2; angZi = (int) angZ; aX = angXi - angXiLast; aY = angYi - angYiLast; aZ = angZi - angZiLast; for (int k=1; k <= dstep ; k++){ angXout = aX*k/dstep + angXiLast; angYout = aY*k/dstep + angYiLast; angZout = aZ*k/dstep + angZiLast; if (angXout < -40) angXout = -40; if (angXout > 20) angXout = 20; if (angYout < -80) angYout = -80; if (angYout > 80) angYout = 80; if (angZout < -70) angZout = -70; if (angZout > 70) angZout = 70; myservo2.write(ZeroY - angYout); myservo3.write(ZeroX - angXout); myservo4.write(ZeroZ - angZout); delayMicroseconds(dt/dstep*1000); } }
更新履歴
- 2017.11.14MPU9250を追加しました。NEW
- 2017.9.2デジタルスタビライザーのページを作りました。