6.2. ミッション例(発展)
概要
- より複雑なシーケンスを実装してみよう
- ミッション例(基本) と同じく高度30mで蓋を開けることに加えて,着陸後に写真を撮る
- さらに,ミッション中常に高度やGPSの値を記録する
- 使うデバイス:
- プログラムについて
- この例では,これまで学んできた内容に加えて,
enum
やstatic
変数,ファイル分割などを使っており,発展的な内容となっている - この他にも,Arduino(C++)には様々な機能があるため,興味があれば調べてみるとよい
- この例では,これまで学んできた内容に加えて,
- フローチャートは以下の通り
ソースコード
port_config.hpp
#pragma once
#define PIN_SERVO 4 // サーボモーターの入力ピン
#define PIN_CAMERA_SS 7 // カメラの SS ピン
#define PIN_GPS_TX 8 // GPS の TX ピン
#define PIN_SD_SS 10 // SDカードの SS ピン
#define PIN_CDS A1 // 光センサの出力ピン
device.hpp
#pragma once
#include <CanSatSchool.h>
#include "port_config.hpp"
// 気圧・温湿度計を宣言
BaroThermoHygrometer bth;
// 光センサを宣言
CdS cds{PIN_CDS};
// GPS を宣言
GPSReceiver gps{PIN_GPS_TX};
// サーボモーターを宣言
ServoMotor servo{PIN_SERVO};
// カメラを宣言
Camera camera{PIN_CAMERA_SS};
void initDevice()
{
// Wire (Arduino-I2C) を初期化
Wire.begin();
// SPI を初期化
SPI.begin();
// 気圧・温湿度計を初期化
bth.init();
// 光センサを初期化
cds.init();
// GPS を初期化
gps.init();
// サーボモーターを初期化
servo.init();
// カメラを初期化
camera.init();
}
sequence.hpp
#pragma once
#include <CanSatSchool.h>
#include "device.hpp"
enum MissionPhase {
LOADING, // 準備
LAUNCHING, // 打ち上げ
DESCENDING, // 降下
LANDED, // 着地
FINISHED, // 終了
};
MissionPhase phase = LOADING; // ミッションのフェーズ
float altitude_at_ground; // 地表の高度 [m]
float height; // 高度 [m]
int light; // 光センサの値
// 気圧の差から相対高度を計算する関数
float calculateHeightFromPressure(float pressure)
{
float pressure_at_sea_level = 1013.250; // 海抜 0m での大気圧 [hPa]
float altitude = (pressure_at_sea_level - pressure) * 10; // ざっくり 10m 上昇すると 1hPa 下がるとする
return altitude - altitude_at_ground;
}
void calibratePressureSensor()
{
// 安定するまで待つ
logger.info(F("Waiting for stabilization..."));
unsigned long wait_start = millis();
while (millis() - wait_start < 10000)
{
// 気圧を取得する
float pressure = bth.read().pressure;
logger.debug(F("Pressure:"), pressure, F("[hPa]"));
delay(1000);
}
logger.info(F("Calibrating pressure sensor..."));
// 地表の気圧を取得する
float pressure_at_ground = bth.read().pressure;
logger.info(F("Pressure at ground:"), pressure_at_ground, F("[hPa]"));
// 地表の海抜高度を計算する
float pressure_at_sea_level = 1013.250; // 海抜 0m での大気圧 [hPa]
altitude_at_ground = (pressure_at_sea_level - pressure_at_ground) * 10; // ざっくり 10m 上昇すると 1hPa 下がるとする
logger.info(F("Altitude at ground:"), altitude_at_ground, F("[m]"));
logger.info(F("Calibration completed"));
}
// 高頻度で実行する共通のタスク
void commonTask()
{
// 高度を取得する
height = calculateHeightFromPressure(bth.read().pressure);
// 光センサの値を取得する
light = cds.read();
}
// 低頻度で実行する定期タスク
void commonTaskLowFreq()
{
// 現在のフェーズを記録する
switch (phase) {
case LOADING:
logger.debug(F("Phase: LOADING"));
break;
case LAUNCHING:
logger.debug(F("Phase: LAUNCHING"));
break;
case DESCENDING:
logger.debug(F("Phase: DESCENDING"));
break;
case LANDED:
logger.debug(F("Phase: LANDED"));
break;
case FINISHED:
logger.debug(F("Phase: FINISHED"));
break;
}
// 高度を記録する
logger.info(F("Height:"), height, F("[m]"));
// 光センサの値を記録する
logger.debug(F("Light:"), light);
// 位置を取得して記録する
logger.info(gps.read());
}
// LOADING フェーズのタスク
void loadingTask()
{
int light_threshold = 30; // 搭載されたと判断する明るさの閾値
static bool loaded = false; // 搭載されたかどうか
// 明るさが閾値を下回ったら搭載されたと判断
if (!loaded && light < light_threshold) {
logger.info(F("Loaded"));
loaded = true;
}
// 搭載後に高度が 10m 以上になったら打ち上げられたと判断
if (loaded && height >= 10) {
logger.info(F("Launched!"));
phase = LAUNCHING;
}
}
// LAUNCHING フェーズのタスク
void launchingTask()
{
int light_threshold = 60; // 放出されたと判断する明るさの閾値
// 明るさが閾値を超えたら放出されたと判断
if (light > light_threshold) {
logger.info(F("Released!"));
phase = DESCENDING;
}
}
// DESCENDING フェーズのタスク
void descendingTask()
{
float lid_open_height = 30; // 蓋を開ける高度 [m]
static bool lid_opened = false; // 蓋が開いたかどうか
// 目標高度に達したら蓋を開ける
if (!lid_opened && height <= lid_open_height) {
logger.info(F("Opening the lid"));
servo.rotateTo(90);
lid_opened = true;
}
// 10回以上連続で高度が 1.5m 未満になったら着陸したと判断
static int count = 0;
if (height <= 1.5) {
count++;
} else {
count = 0;
}
if (count >= 10) {
logger.info(F("Landed!"));
phase = LANDED;
}
}
// LANDED フェーズのタスク
void landedTask()
{
// 10秒ごとに3回カメラで写真を撮る
static unsigned long last_time_ms = 0;
static int count = 0;
unsigned long current_time_ms = millis();
unsigned long interval_ms = 10000;
if (current_time_ms - last_time_ms > interval_ms) {
camera.takePictureAndSaveAs(String(current_time_ms) + ".jpg");
last_time_ms = current_time_ms;
count++;
}
if (count >= 3) {
logger.info(F("Finished!"));
phase = FINISHED;
}
}
tutorial_mission_advanced.ino
// ミッション例:高度30mで蓋を開け,着陸後に写真を撮る
// 使うデバイス:
// - 光センサ:放出されたかどうかを判断する(ピン A1)
// - 気圧・温湿度計:気圧を読んで高度を計算する(ピン A4, A5)
// - GPS:飛行地点や着陸地点を取得する(ピン 8)
// - サーボモーター:蓋を開ける(ピン 4)
// - カメラ:着陸地点で写真を撮る(ピン 7, 11, 12, 13, A4, A5)
#include <CanSatSchool.h>
#include "device.hpp"
#include "sequence.hpp"
void setup()
{
// ロガーを初期化
String file_name = "log.txt"; // 記録するファイル名
logger.enableSDCard(file_name, PIN_SD_SS);
// logger.enableComputer(); // PCとのシリアル通信を有効にする
// logger.setDebug(); // デバッグモードを有効にする
initDevice();
calibratePressureSensor();
}
void loop()
{
static unsigned long last_time_ms = 0;
unsigned long current_time_ms = millis();
// commonTask は常に実行する
commonTask();
// 2000ms ごとに commonTaskLowFreq を実行する
unsigned long interval_ms = 2000;
if (current_time_ms - last_time_ms > interval_ms) {
commonTaskLowFreq();
last_time_ms = current_time_ms;
}
// ミッションのフェーズによってタスクを実行する
switch (phase) {
case LOADING:
loadingTask();
break;
case LAUNCHING:
launchingTask();
break;
case DESCENDING:
descendingTask();
break;
case LANDED:
landedTask();
break;
case FINISHED:
// 何もしない
break;
}
}