Tutorial
6.2. ミッション例(発展)

6.2. ミッション例(発展)

概要

  • より複雑なシーケンスを実装してみよう
  • ミッション例(基本) と同じく高度30mで蓋を開けることに加えて,着陸後に写真を撮る
    • さらに,ミッション中常に高度やGPSの値を記録する
  • 使うデバイス:
  • プログラムについて
    • この例では,これまで学んできた内容に加えて,enumstatic 変数,ファイル分割などを使っており,発展的な内容となっている
    • この他にも,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       // 光センサの出力ピン
 

GitHub (opens in a new tab)

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();
}
 

GitHub (opens in a new tab)

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;
    }
}
 

GitHub (opens in a new tab)

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;
    }
}
 

GitHub (opens in a new tab)