【自作ドローンの製作Ⅲ】第11回:傾いた状態での飛行

はじめに
第10回ではドローンをホバリング状態で飛ばすことが出来ました。
第11回では傾いた状態で飛ばすことができたので、プログラムの変更点について書こうと思います。
変更点
- 目標姿勢をランプ上に変化させる機能とIF関数追加
- 加速度センサの初期化失敗対策追加
- プロポのY軸制御の座標系修正追加
目標姿勢をランプ上に変化させる機能とIF関数追加
傾き飛行に関するメインの変更はこの部分になります。主な追加は下記

下記のようにプロポのスイッチで目標姿勢をランプ状に変化させます。姿勢制御については第10回の時点で傾き飛行できる状態になっていたので変更していません。

姿勢を傾ける場合、位置制御をケアする必要があります。現状、位置制御はプロポからの指令で行っていますが基本的に座標系は地球座標系(絶対座標系とか地上座標系とかと同じ意味で言ってます)で考えています。 しかし、ドローンの制御は機体座標系で考えています。姿勢が傾いてくるとこの2つの座標系がずれてくるので地球座標系のプロポ指令を機体座標系に変換してやる必要があります。基本的には姿勢の回転行列を使えば変更できるので傾き飛行に合わせてそこら辺の機能を下記で追加しています。

加速度センサの初期化失敗対策追加
どういう理由か不明ですが、加速度センサの初期化が失敗していたことに気づいたので対策を入れました。とりあえう一回失敗したらもう一回やるように変更しました。今のところ調子よいです。

プロポのY軸制御の座標系修正追加
位置制御の座標変換を実装したところ符号がどうも合わない部分があり、色々調べた結果プロポの操作に対する値の向きがCH4だけ逆だったことが分かったので修正しました。

これによってプログラムの符号も変更する必要があるので変更しました。

傾き飛行
以上の変更を加えて傾き飛行をしている様子です。30度傾けて飛行させることができました。
おわりに
ここまでの変更は下記リポジトリのタグ「第11回」を参照してください。
ついに姿勢を傾けた状態で飛行をさせることができたので【自作ドローンの製作Ⅲ】としてはここで区切りとしたいと思います。ただ、位置制御が安定しないとかまだ90度傾けられてないとか課題はあるのでドローン製作はまだまだ続きそうです。
【自作ドローンの製作Ⅲ】第10回:ドローンの制御

はじめに
第9回では姿勢センサ(BMI088)から姿勢データを取得する部分について書きました。
第10回ではドローンを飛ばすことが出来たので現状のプログラムの構造と飛行の様子について書きます。
クラスの説明
ドローン制御にあたって新たに下記のクラスを追加しました。
- AttitudePlannerクラス
- プロポの信号と現在姿勢から目標姿勢を計算
- AttitudeControllerクラス
- 目標姿勢と現在姿勢の回転行列から誤差回転行列を計算し、PID制御の制御量を計算
- PositionControllerクラス
- 位置の制御量を計算
- Distributorクラス
- 位置と姿勢の操作量をモータ推力とサーボ角度に変換
プログラムの構造
クラスも増えてきたので一旦現在のプログラムの構造を整理しておきます。

飛行の様子
今回のプログラムでホバリング飛行はできるようになりました。
おわりに
ここまでの変更は下記リポジトリのタグ「第10回」を参照してください。
とりあえず自作ドローンの製作Ⅱと同じところまでは来ました。あとは傾いて飛行するのと位置の自動制御をやっていきたいです。ただその前にプログラムの構造の見直しをする予定です。
次の記事
【自作ドローンの製作Ⅲ】第9回:姿勢データの取得

はじめに
第8回ではESCの制御について書きました。
第9回では姿勢センサ(BMI088)から姿勢データを取得する部分について書きます。
使用ライブラリ
BMI088
BMI088をArduinoで使用するためのライブラリです。
Madgwick
加速度と角速度の値から姿勢を計算してくれるライブラリです。
自分は少し改造して使っており、クォータニオンの値を取り出す用のIF関数を独自で追加しています。詳細はMadgwickAHRS.hに変更履歴を残しているのでそれを見てください。
Eigen
行列計算などをしてくれるライブラリです。EigenをTeensyで使用できるように設定周りを公開してくれている方がいたので利用させてもらっています。ただEigenライブラリはデフォルトだとメモリ配置がColMajorなのでMapを使用してメモリ配置がRowMajorのC言語配列をEigen行列に変換すると転置されてしまうことが起きたのでEigenライブラリのメモリ配置をRowMajorに変更する設定を追加しています。詳細はEigen.hに変更履歴を残しているのでそれを見てください。
姿勢データを取得するだけではあまり利用する必要はないですが、のちのち目標姿勢と現在姿勢の偏差を計算する際に行列計算を用いるのでこのライブラリを入れています。
クラスの説明
AttitudeSensorクラス(AttitudeSensor.cpp)
クラス内の関数説明
| 関数 | 概要 |
|---|---|
| AttitudeSensor(SPIClass &bus, uint8_t cs_pin_accel, uint8_t cs_pin_gyro)() | SPIクラスの情報とCSピンの情報を渡す |
| begin(float sample_frequency_Hz)) | サンプリング周波数を渡す(ここで渡した周期でupdate()を呼び出す必要がある) |
| update() | センサデータの更新関数(begin()で渡したサンプリング周波数で呼び出す必要がある) |
| getAccel_mss() | 加速度[m/s2]を取得する |
| getGlobalAccel_mss() | 絶対座標系における加速度[m/s2]を取得する |
| getGyro_rads() | 角速度[rad/s]を取得する |
| getGyro_degs() | 角速度[deg/s]を取得する |
| getRPY_rad() | 姿勢[rad]を取得する |
| getRPY_deg() | 姿勢[deg]を取得する |
| getQuaternion() | クォータニオンを取得する |
| getRotationMatrix() | 回転行列を取得する |
使い方
姿勢データを取得するときの使い方が下記です。
/* ==================================================================== * Include files * ==================================================================== */ // Arduino標準ライブラリ #include <Arduino.h> // 自作ライブラリ #include "config.h" #include "AttitudeSensor.h" /* ==================================================================== * Class declarations * ==================================================================== */ IntervalTimer Timer1; IntervalTimer Timer2; AttitudeSensor attitudeSensor(SPI, CS_PIN_ACCEL, CS_PIN_GYRO); /* ==================================================================== * Prototype declarations * ==================================================================== */ void task_1ms(); void task_20ms(); /* ==================================================================== * Public functions * ==================================================================== */ void setup() { /* PCとのシリアル通信 */ Serial.begin(115200); /* Attitude Sensor */ attitudeSensor.begin(CONFIG_CONTROL_FREQUENCY_Hz); /* タイマー割込み */ Timer1.priority(190); Timer1.begin(task_1ms, 1000); Timer2.priority(200); Timer2.begin(task_20ms, 20000); } /************************************************************************/ void loop() { } /************************************************************************/ void task_1ms() { // 現在姿勢の計算 std::vector<float> cur_rpy(3); // 現在姿勢のオイラー角 attitudeSensor.update(); cur_rpy = attitudeSensor.getRPY_deg(); Serial.print(cur_rpy[0]); Serial.print("\t"); Serial.print(cur_rpy[1]); Serial.print("\t"); Serial.print(cur_rpy[2]); Serial.print("\n"); }
おわりに
ここまでの変更は下記リポジトリのタグ「第9回」を参照してください。
ここまででドローンを飛ばすのに必要な機能は大体実装できたと思うのであとは頑張って飛ぶような制御を実装します。
次の記事
【自作ドローンの製作Ⅲ】第8回:ESCの制御

はじめに
第7回ではサーボモータの制御を実装しました。
第8回ではESCの制御について書きます。
ESCについて
ESCはElectric Speed Controllerの略でドローンで言うとプロペラを回転させるためのモータを制御する基板です。今回自分が使用するESCは「RCX BS-15A Pro 4-In-1 BLHeli_S ESC」と呼ばれる基板です。 本当はリンクを付けたいのですが、以前買った所ではもう見当たりませんでした。 ドローン系のパーツは供給が短く1年も経てば買えなくなることがほとんどです。自分もこのESCが壊れたらおしまいです。
今回使用するESCのファームウェアは「BLHeli_S」と呼ばれるものです。基本的にFPVドローン向けのESCは似たような使用感で使えますが、微妙に異なる部分もあるので自分が使うESCとそのマニュアルは一度見たほうが良いです。 下記は「BLHeli_S」のマニュアルです。
https://bluerobotics.com/wp-content/uploads/2018/10/BLHeli_S-manual-SiLabs-Rev16.x.pdf
ESCを使う上で気にすることはどういうプロトコルでESCと通信するかということです。例えば「BLHeli_S」で言えば下記のプロトコルをサポートしています。
- Oneshot125 (125-250us)
- Oneshot42 (41.7-83.3us)
- Multshot (5-25us)
この辺りはなんじゃこりゃと思う人もいると思いますが下記の記事が詳しく説明してくれています。
上記は基本的にはPWMを使用して通信するプロトコルですがデジタル通信でやり取りする「DSHOT」というプロトコルもあります。これについては下記で実験したので興味がある方は見てください。
クラスの説明
ThrustControllerクラス(ThrustController.cpp)
本来は推力[N]をDuty[0-1]に変換する目的で作成したクラスですが現状は出力制限するだけのクラスになっています。責務分割を考慮してクラスとしてはこのまま実装することにしました。
クラス内の関数説明
| 関数 | 概要 |
|---|---|
| ThrustController() | 何もしない |
| calculateFF(float ref_thrust_N) | 引数を0-1の範囲で制限する計算を実施 |
| getFF() | 制御量[-1~1]を取得 |
ThrustDriverクラス(ThrustDriver.cpp)
ESCにPWM出力を行うクラスです。現状はMultishotプロトコルでESCと通信するよう実装しています。
クラス内の関数説明
| 関数 | 概要 |
|---|---|
| ThrustDriver(int pin_pwm) | PWM出力ピンの情報を渡す |
| begin() | クラスの初期化処理 |
| outputPWM(float duty_norm) | 設定されたDuty[0~1]でPWM出力する。 |
使い方
thrust.cpp
ドローンにはプロペラを回すモータが4つあるので、それらの制御を集約する場所としてthrust.cppを作成しています。ここで4つのモータをまとめて制御するための関数を宣言しています。
/* ==================================================================== * Include files * ==================================================================== */ #include "thrust.h" // 自作クラスの.hファイル #include "ThrustController.h" #include "ThrustDriver.h" // その他必要な.hファイル #include "config.h" /* ==================================================================== * Constant definitions * ==================================================================== */ /* ==================================================================== * Class declarations * ==================================================================== */ // 推力制御に関するクラス static ThrustController thrustController[CONFIG_PROPELLER_NUM]; // ESC出力に関するクラス static ThrustDriver thrustDriver[CONFIG_PROPELLER_NUM] = { ThrustDriver(PIN_ESC1_PWM), ThrustDriver(PIN_ESC2_PWM), ThrustDriver(PIN_ESC3_PWM), ThrustDriver(PIN_ESC4_PWM)}; /* ==================================================================== * Public functions * ==================================================================== */ /** * @brief 初期化処理 */ void thrust_Init() { for (int i = 0; i < CONFIG_PROPELLER_NUM; i++) { thrustDriver[i].begin(); } } /** * @brief 推力制御処理 * @param [in] ref_thrust_N 目標推力[N] * @param [in] command プロポからの指令値 * @return サーボの操作量[0 ~ 1] */ std::vector<float> thrust_Control(std::vector<float> ref_thrust_N, T6L_Command command) { std::vector<float> ref_thrust(CONFIG_PROPELLER_NUM); std::vector<float> duty(CONFIG_PROPELLER_NUM); ref_thrust = ref_thrust_N; // 推力制御 for (int i = 0; i < CONFIG_PROPELLER_NUM; i++) { thrustController[i].calculateFF(ref_thrust[i]); duty[i] = thrustController[i].getFF(); } // スロットルが閾値を超えていない場合は、出力を0にする if (command.throttle < THROTTLE_THRESHOLD) { for (int i = 0; i < CONFIG_PROPELLER_NUM; i++) { duty[i] = 0; } } return duty; } /** * @brief 推力出力処理 * @param [in] duty_norm サーボの操作量[0 ~ 1] */ void thrust_Output(std::vector<float> duty_norm) { for (int i = 0; i < CONFIG_PROPELLER_NUM; i++) { thrustDriver[i].outputPWM(duty_norm[i]); } }
main.cpp
thrust.cppとSBUSで受信したデータを使用して4つのモータを制御するコードが下記です。
/* ==================================================================== * Include files * ==================================================================== */ // Arduino標準ライブラリ #include <Arduino.h> // 自作ライブラリ #include "config.h" #include "SbusReceiver.h" #include "servo.h" #include "thrust.h" /* ==================================================================== * Class declarations * ==================================================================== */ SbusReceiver sbusReceiver(&Serial1); IntervalTimer Timer1; IntervalTimer Timer2; /* ==================================================================== * Prototype declarations * ==================================================================== */ void task_1ms(); void task_20ms(); /* ==================================================================== * Public functions * ==================================================================== */ void setup() { /* PCとのシリアル通信 */ Serial.begin(115200); /* SBUS */ sbusReceiver.begin(); /* Servo */ servo_Init(); /* Thrust */ thrust_Init(); /* タイマー割込み */ Timer1.priority(190); Timer1.begin(task_1ms, 1000); Timer2.priority(200); Timer2.begin(task_20ms, 20000); } /************************************************************************/ void loop() { /* 受信データの更新 */ sbusReceiver.update(); } /************************************************************************/ void task_1ms() { T6L_Command command; std::vector<float> thrust_duty_norm(4); /* 受信データの取得 */ command = sbusReceiver.getCommand(); /* T6Lのthrottleの値を出力値として使用 */ for (int i = 0; i < CONFIG_PROPELLER_NUM; i++) { thrust_duty_norm[i] = command.throttle; } thrust_duty_norm = thrust_Control(thrust_duty_norm, command); /* モータへ出力 */ thrust_Output(thrust_duty_norm); }
おわりに
ここまでの変更は下記リポジトリのタグ「第8回」を参照してください。
次の記事
【自作ドローンの製作Ⅲ】第7回:サーボモータの制御

はじめに
第6回ではSBUSの受信処理を実装しました。
第7回ではサーボモータの制御を実装します。
クラスの説明
サーボモータ制御のために下記3つのクラスを作成しました。
- ServoEncoderクラス(ServoEncoder.cpp)
- ServoControllerクラス(ServoController.cpp)
- ServoDriverクラス(ServoDriver.cpp)
ServoEncoderクラスで現在角度の情報を取得、ServoControllerクラスで目標角度と現在角度から出力Dutyを計算、ServoDriverクラスでモータにPWM出力という流れです。

ServoEncoderクラス(ServoEncoder.cpp)
エンコーダからの情報を角度[rad]or[deg]に変換して外部に提供するクラスです。
クラス内で使用しているライブラリ
Teensyには下記のエンコーダライブラリがあるのでこれを用いてエンコーダパルス情報を取得します。
クラス内の関数説明
| 関数 | 概要 |
|---|---|
| ServoEncoder(int pin_encA, int pin_encB) | エンコーダで使用するピンの情報を渡す |
| readSensor() | エンコーダからパルス数を読み出す(この関数はセンサ値の更新が役目であり外部に情報を提供しないので注意) |
| resetSensor(int reset_value) | パルス数を設定された値でリセットする |
| getAngle_rad() | 現在角度[rad]を取得する |
| getAngle_deg() | 現在角度[deg]を取得する |
ServoControllerクラス(ServoController.cpp)
目標角度[rad]、現在角度[rad]から出力Dutyを計算します。
内部の制御については下記の記事でやったことを流用しています。同じモータを使用しているのでゲインもそのままです。
クラス内の関数説明
| 関数 | 概要 |
|---|---|
| ServoController(float control_cycle_s) | 制御周期[s]を渡す |
| calculateIPD(float ref_angle_rad, float act_angle_rad, float bat_volt_v) | 目標角度[rad]、現在角度[rad]、電源電圧[V]を渡す。IPD制御で制御量を計算します(コンストラクタに渡した制御周期で呼び出す必要があります) |
| resetIntegral() | 積分値をリセットします |
| getIPD_V() | 制御量[V]を取得 |
| getIPD_duty() | 制御量[-1~1]を取得 |
ServoDriverクラス(ServoDriver.cpp)
実際にPWM出力を行うためのクラスです。PWM周波数やPWM分解能などもこのクラスで管理しています。今回はDRV8835のPHASE/ENABLEモードを使用してモータを制御することを前提としたクラスの設計になっています。
クラス内の関数説明
| 関数 | 概要 |
|---|---|
| ServoDriver(int pin_pwm, int pin_dir) | PWM出力ピン、回転方向ピンの情報を渡す |
| begin() | 初期化処理(PWM周波数やPWM分解能の設定はここで行うため、動かし始める前に必ず呼び出すこと) |
| resetSensor(int reset_value) | パルス数を設定された値でリセットする |
| outputPWM(float duty_norm) | 設定されたDuty[-1~1]でPWM出力する。マイナスの値を設定すると反転する。 |
使い方
servo.cpp
ドローンにはサーボモータが4つあるので、それらの制御を集約する場所としてservo.cppを作成しています。ここで4つのサーボをまとめて制御するための関数を宣言しています。
/* ==================================================================== * Include files * ==================================================================== */ #include "servo.h" // 自作クラスの.hファイル #include "ServoEncoder.h" #include "ServoController.h" #include "ServoDriver.h" // その他必要な.hファイル #include "config.h" /* ==================================================================== * constant definitions * ==================================================================== */ static constexpr int SERVO_NUM = CONFIG_SERVO_NUM; /**< サーボモータの数 */ /* ==================================================================== * Class declarations * ==================================================================== */ // エンコーダに関するクラス static ServoEncoder servoEncoder[SERVO_NUM] = {ServoEncoder(CONFIG_PIN_SERVO1_ENC_A, CONFIG_PIN_SERVO1_ENC_B), ServoEncoder(CONFIG_PIN_SERVO2_ENC_A, CONFIG_PIN_SERVO2_ENC_B), ServoEncoder(CONFIG_PIN_SERVO3_ENC_A, CONFIG_PIN_SERVO3_ENC_B), ServoEncoder(CONFIG_PIN_SERVO4_ENC_A, CONFIG_PIN_SERVO4_ENC_B)}; // サーボ制御に関するクラス static ServoController servoController[SERVO_NUM] = {ServoController(CONFIG_CONTROL_CYCLE_sec), ServoController(CONFIG_CONTROL_CYCLE_sec), ServoController(CONFIG_CONTROL_CYCLE_sec), ServoController(CONFIG_CONTROL_CYCLE_sec)}; // モータ出力に関するクラス static ServoDriver servoMotor[SERVO_NUM] = {ServoDriver(CONFIG_PIN_SERVO1_PWM, CONFIG_PIN_SERVO1_DIR), ServoDriver(CONFIG_PIN_SERVO2_PWM, CONFIG_PIN_SERVO2_DIR), ServoDriver(CONFIG_PIN_SERVO3_PWM, CONFIG_PIN_SERVO3_DIR), ServoDriver(CONFIG_PIN_SERVO4_PWM, CONFIG_PIN_SERVO4_DIR)}; /* ==================================================================== * Prototype declarations * ==================================================================== */ /* ==================================================================== * Public functions * ==================================================================== */ /** * @brief 初期化処理 */ void servo_Init() { for (int i = 0; i < SERVO_NUM; i++) { servoMotor[i].begin(); } } /** * @brief 現在サーボ角度取得処理 * @return 現在サーボ角度[rad] */ std::vector<float> servo_ReadSensor() { std::vector<float> act_angle(SERVO_NUM); // 現在角度の取得 for (int i = 0; i < SERVO_NUM; i++) { servoEncoder[i].readSensor(); act_angle[i] = servoEncoder[i].getAngle_rad(); } return act_angle; } /** * @brief サーボ制御処理 * @param [in] ref_angle_rad 目標サーボ角度[rad] * @param [in] act_angle_rad 現在サーボ角度[rad] * @return サーボの操作量[-1 ~ 1] */ std::vector<float> servo_Control(std::vector<float> ref_angle_rad, std::vector<float> act_angle_rad) { std::vector<float> ref_angle(SERVO_NUM); std::vector<float> act_angle(SERVO_NUM); std::vector<float> duty(SERVO_NUM); ref_angle = ref_angle_rad; act_angle = act_angle_rad; // 角度制御 for (int i = 0; i < SERVO_NUM; i++) { servoController[i].calculateIPD(ref_angle[i], act_angle[i], 7.4); duty[i] = servoController[i].getIPD_duty(); } return duty; } /** * @brief サーボ出力処理 * @param [in] duty_norm サーボの操作量[-1 ~ 1] */ void servo_Output(std::vector<float> duty_norm) { for (int i = 0; i < SERVO_NUM; i++) { servoMotor[i].outputPWM(duty_norm[i]); } }
main.cpp
servo.cppとSBUSで受信したデータを使用して4つのサーボモータを制御するコードが下記です。
/* ==================================================================== * Include files * ==================================================================== */ // Arduino標準ライブラリ #include <Arduino.h> // 自作ライブラリ #include "config.h" #include "SbusReceiver.h" #include "servo.h" /* ==================================================================== * Class declarations * ==================================================================== */ SbusReceiver sbusReceiver(&Serial1); IntervalTimer Timer1; IntervalTimer Timer2; /* ==================================================================== * Prototype declarations * ==================================================================== */ void task_1ms(); void task_20ms(); /* ==================================================================== * Public functions * ==================================================================== */ void setup() { /* PCとのシリアル通信 */ Serial.begin(115200); /* SBUS */ sbusReceiver.begin(); /* Servo */ servo_Init(); /* タイマー割込み */ Timer1.priority(190); Timer1.begin(task_1ms, 1000); Timer2.priority(200); Timer2.begin(task_20ms, 20000); } /************************************************************************/ void loop() { /* 受信データの更新 */ sbusReceiver.update(); } /************************************************************************/ void task_1ms() { T6L_Command command; std::vector<float> servo_ref_angle_rad(4); std::vector<float> servo_act_angle_rad(4); std::vector<float> servo_duty_norm(4); /* 受信データの取得 */ command = sbusReceiver.getCommand(); /* T6LのSWがONの場合に更新 */ if (command.sw > SWITCH_THRESHOLD) { /* T6Lのつまみの値を目標値として使用 */ for (int i = 0; i < CONFIG_SERVO_NUM; i++) { servo_ref_angle_rad[i] = command.knob * 360 * DEG_TO_RAD; } } /* サーボの制御 */ servo_act_angle_rad = servo_ReadSensor(); servo_duty_norm = servo_Control(servo_ref_angle_rad, servo_act_angle_rad); /* モータへ出力 */ servo_Output(servo_duty_norm); } /************************************************************************/
おわりに
ここまでの変更は下記リポジトリのタグ「第7回」を参照してください。
次の記事
【自作ドローンの製作Ⅲ】第6回:SBUSの受信処理

はじめに
第5回でソフトの環境構築を行いました。
第6回からは実装に入っていきます。まずはSBUSの受信処理を実装したのでそれに関して書いていきます。
SBUS受信クラス (SbusReceiver.cpp)
SBUSでデータを受信するためのライブラリです。自分はプロポとしてT6L Sportを使用しているのでそれ用にクラスを作成しています。
クラス内で使用しているライブラリ
主な受信処理は下記ライブラリに任せています。自作したクラスでは自分が使いやすいようにちょっとしたデータの変換処理などを行っているだけです。
クラス内の関数説明
| 関数 | 概要 |
|---|---|
| SbusReceiver(HardwareSerial *bus) | メンバ変数の初期化 |
| begin() | SbusRxクラスの初期化 |
| update() | SBUS受信データの更新処理、ループ処理で呼び出す必要あり |
| getCommand() | SBUS受信データの取得処理、値の範囲は0~1 |
使用例
SBUSの受信データを20ms間隔でシリアル通信でPCに表示するコード
/* ==================================================================== * Include files * ==================================================================== */ // Arduino標準ライブラリ #include <Arduino.h> // 自作ライブラリ #include "SbusReceiver.h" /* ==================================================================== * Class declarations * ==================================================================== */ SbusReceiver sbus(&Serial1); IntervalTimer Timer2; /* ==================================================================== * Prototype declarations * ==================================================================== */ void task_20ms(); /* ==================================================================== * Public functions * ==================================================================== */ void setup() { /* PCとのシリアル通信 */ Serial.begin(115200); /* SBUS */ sbus.begin(); /* タイマー割込み */ Timer2.priority(200); Timer2.begin(task_20ms, 20000); } /************************************************************************/ void loop() { /* 受信データの更新 */ sbus.update(); } /************************************************************************/ void task_20ms() { T6L_Command command; /* 受信データの取得 */ command = sbus.getCommand(); /* 受信データの表示 */ Serial.print(command.aileron); Serial.print("\t"); Serial.print(command.elevator); Serial.print("\t"); Serial.print(command.knob); Serial.print("\t"); Serial.print(command.rudder); Serial.print("\t"); Serial.print(command.sw); Serial.print("\t"); Serial.print(command.throttle); Serial.print("\n"); }
おわりに
ここまでの変更は下記リポジトリのタグ「第6回」を参照してください。
次の記事
【自作ドローンの製作Ⅲ】第5回:ソフトウェアの開発環境構築

はじめに
第4回でハードウェアを完成させました。
第5回から徐々にソフトのほうへ移っていきます。今回はソフト開発のための環境構築について書いていきます。
開発環境構築
使用するマイコンはTeensy4.1なのでArduinoで開発できます。開発環境としては下記です。
- エディタ:VS Code
- 開発環境:Platform IO
Platform IOはArduino IDEよりもビルドが早いと感じているのでこちらを使います。ユニットテストや静的解析もやろうと思えばできる環境です。
右クリックでVS Codeを起動できるようにするためのインストール設定
いまさらVS Codeのインストールを書く必要はないと思いますが、インストール時にやっておいたほうが良い設定があったので覚書として書いておきます。
インストール時に下記赤枠にチェックを入れると便利でした。

何ができるようになるかというとファイルやフォルダを右クリックしたときに「Code で開く」というのが表示されるようになります。これで選択したファイルやフォルダを直接VS Codeで開けるようになります。これが地味に便利です。

Platform IOのインストール
Platform IOはVS Codeの拡張機能から入れることができます。

プロジェクトの作成
Platform IOのHOME画面の「New Project」から作成できます。

自分の環境に合わせて設定します。プロジェクト作成の場所を変えたい場合は「Location」のチェックを外して自分で設定できます。

作成すると下記のようなプロジェクトが作成されます。

書き込むときは左のPlatform IOアイコンをクリックしたときに出てくる画面で「Upload」をクリックします。

Clang-formatの適用
Clang-formatは、設定したルールに基づいてコードを自動整形してくれます。自分好みのルールで整形してくれるので便利です。下記のサイトを参考にしました。
Clang-formatの設定ファイルは下記のサイトが自動生成してくれるので便利です。
静的解析の実施
Platform IOは静的解析ができます。CppCheckやClang-Tidyなどが使えるようです。詳しいことは下記参照。
静的解析を実施するのは簡単で「Advanced」の「Check」をクリックすればできます。結果はターミナルに表示されます。

もう1つの方法は「PIO Home」の「Inspect」タブからも解析できます。

こちらから実施したほうが結果は見やすい気がします。

おわりに
ここまでの変更は下記リポジトリのタグ「第5回」を参照してください。
開発環境を整えたので次からはコードを書けるかな?
次の記事