編集履歴一覧に戻る
nakajiのアイコン画像

nakaji が 2021年02月13日22時45分21秒 に編集

初版

タイトルの変更

+

モデルロケット用高度計の製作

タグの変更

+

秋葉原2021

+

ロケット

メイン画像の変更

メイン画像が設定されました

本文の変更

+

## 目次 - [はじめに](#はじめに) - [部品表](#部品表) - [製作の注意点](#製作の注意点) - [回路の大きさと重さ](#回路の大きさと重さ) - [電源の安全性](#電源の安全性) - [ロケットの速度](#ロケットの速度) - [部品選定](#部品選定) - [マイコン](#マイコン) - [センサー](#センサー) - [データ保存用メモリ](#データ保存用メモリ) - [構成](#構成) - [回路](#回路) - [プログラム](#プログラム) - [実際の測定結果](#実際の測定結果) ## はじめに 最近、理科教育や航空宇宙技術の教育を目的として、モデルロケットを用いた教育が行われています 私の通う学校でもモデルロケットを使った教育がありました その際にモデルロケット用の高度計の製作を依頼されたので、紹介したいと思います モデルロケットを使ったCanSatというコンテストもあるようなので、出たい人は参考にもなるかもしれないです ## 部品表 はじめに、今回使った主な部品をまとめておきます | 種類 | 型番 | | ---------- | --- | | マイコン | [ATMEGA328P-PU](https://akizukidenshi.com/catalog/g/gI-03142/) | | 気圧センサ | [BME280](https://akizukidenshi.com/catalog/g/gK-09421/) | | EEPROM | [24FC1025](https://akizukidenshi.com/catalog/g/gI-03570/) | | 3.3Vレギュレータ | [S-812C33AY-B-G](https://akizukidenshi.com/catalog/g/gI-03289/) | ## 製作の注意点 始めに、製作で気を付けた3つのポイントを解説します ### 回路の大きさと重さ モデルロケット製作は以下のサイトを参考にしました [植松電機ペーパークラフトロケット](https://uematsudenki.com/p-craft/rocket/) こちらにならって搭載物の入れ物はチップスターの箱とします チップスターの箱は 内径65mm × 高さ135mmm の円柱です 今回はパラシュートやカメラなども積み込む予定だったので、 回路の大きさは内径65mm × 高さ35mmm 以内で設計しました ### 電源の安全性 ロケットは燃料を燃やして飛ばすので、万が一回路が燃えてデータがとれなかったら最悪です 今回の回路で一番燃えやすいのは電源です なので、リチウムイオン電池やコイン型のリチウム電池(CR2032など)は燃えやすいので使わず アルカリ電池、しかも乾電池は大きいのでボタン電池(LR44)4つを電源としました ちなみにボタン電池4つで電源電圧は1.5V × 4 = 6.0V となりますが ボタン電池は内部抵抗が高く、無負荷時より動作時の電圧が低下しやすいです 試しにどれくらい電圧が下がるのかを測定してみました 条件 - ゴールデンパワー製LR44 (秋月電子で10個100円で売られています) - 無負荷時の電圧が1.587V - 15Ω を負荷としたので約80mA流れた この場合、1.587V → 1.2Vとなりました 実際は80mAも流れませんが 瞬間的に電源電圧が下がった場合にリセットされては困るので 1.2V × 4 = 4.8V以下で動くように設計しました ### ロケットの速度 高度測定のサンプリングレートと分解能はロケットの速度より十分高いものが必要です [先ほどのサイト](https://uematsudenki.com/p-craft/rocket/)によると - ロケットエンジンの燃焼時間は3秒 - 最大高度は30m(今回は搭載物が多いため) となるため**平均高度上昇量が10m/s**としました 3秒間で十分なデータをとるためにサンプリングレートは100Hz以上としました よって必要な分解能は10m/s÷100Hz=**10cm**です ## 部品選定 ### マイコン 先ほどの注意点を加味すると必要な要件は以下の2つです - 電源やセンサーも含めた基板の大きさが45mm × 45mm × 35mm 以内 - 電源電圧が4.8V以下 よって、市販のArduinoやM5Stackは5V電源なので使うことはできません そこで、今回はArduino Unoのマイコンである**AVR ATMEGA328P-PU**だけを抜き出して 3.3V動作のArduinoとして使うことにしました ### センサー 高度を測る方法にはいくつかあります。 - GPSを使って高度を測定 - 気圧の低下から高度を推定 GPSは安いものだとサンプリングレートが1Hzで話になりません あまり高価な物も買えないので、今回は気圧センサーを使うことにしました 計算の簡略化のため気圧と高度の関係を 10cm上昇 = 1Pa低下 と仮定します よってセンサーの要件は以下になります - サンプリングレート100Hz - 分解能5cm(気圧だと0.5Pa) - 電源電圧3.3V 以上のことから、最低サンプリングレートが**157Hz**、分解能が**0.18Pa**である**BME280**を使いました ![]() ### データ保存用メモリ サンプリングレート100Hzとなると気圧データをマイコン内に全て保存することはできません 一般にこういう場合には以下の2つで対処します - データを無線で送信し受信側でデータを保存する - マイコン以外にデータ保存用のメモリを用意する 経験上、無線にはトラブルがつきものなので今回はデータ保存用のメモリを用意しました 今回使用したメモリはEEPROMです 似たようなものだとSDカードやUSBメモリがよく使われますが、 SDやUSBは電源が5V、数百ミリアンペアの電流が必要なので今回は使えませんでした EEPROMは低電圧、小電流で動かせますが容量が少ないです そうなるとメモリの容量が心配なので大丈夫か計算しました - 保存するデータはタイムスタンプ(8bit,単位ms)と気圧(16bit,単位Pa*10^-2) - データ取得時間30秒 - データ取得と書き込みのサンプリングレート150Hz - 100Hz以上あればよいのですが、センサーが157Hzと余裕があるので150Hzで計算します - 必要なメモリ容量は (8 bit+16 bit) * 150Hz * 30s = 108,000 bit = **108k bit** 今回は秋月電子通商で一番容量が多い **1M bit**のEEPROM **24FC1025**を買ったので大丈夫そうです ![]() ## 構成 ### 回路 作成した実際の回路がこちらです ![キャプションを入力できます](https://camo.elchika.com/aec2bc682f4a91b5feae6a89cdf7818faef92105/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64656262303562652d346235322d346166322d383336632d6264346639303930666564632f30326162373836392d323664382d343332642d393638662d386264663364363762333437/) ![キャプションを入力できます](https://camo.elchika.com/c1d6c40a412b9e513a395bc8d12f3b9c0fb9b7b0/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64656262303562652d346235322d346166322d383336632d6264346639303930666564632f63356538623534392d333338332d346439332d613135362d636238323232666631333234/) ロケットの振動があるのでブレッドボードボードではなくユニバーサル基板で作成しました 大きさ制限をクリアするためにかなり密度の高い実装になりました・・・ ### プログラム 実際の測定で用いたプログラムがこちらです ```arduino:Lチカの例 #include <EEPROM.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> #include <skMC24xxx.h> // #define AUTO_START // Availables #define MODE_PIN 7 //high -> export , low -> measule #define DEBUG_LED_PIN 2 #define AUTO_START_PRESS 10//Pa #define DATA_BYTE_ONCE 3 #define ERR_COUNT_MAX 6000 #define MEASUREMENT_TIME_MS 120000 // BME280 #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C // EPROM skMC24xxx MEM(0, 0, 0); // A0/A1端子は全てGNDに配線 bool mode = 0; unsigned char data_set[DATA_BYTE_ONCE];//unsignedじゃないと読みだし、書き込み時におかしくなる void setup() { Serial.begin(115200); Serial.println("Program start"); //BME280 Wire.begin(); if (!bme.begin(0x76)) //SDO pullup -> 0x77, pulldown -> 0x76 { Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!"); Serial.print("SensorID was: 0x"); Serial.println(bme.sensorID(), 16); Serial.print(" ID of 0xFF probably means a bad address, a BMP 180 or BMP 085\n"); Serial.print(" ID of 0x56-0x58 represents a BMP 280,\n"); Serial.print(" ID of 0x60 represents a BME 280.\n"); Serial.print(" ID of 0x61 represents a BME 680.\n"); while (1) delay(10); } bme.setSampling(Adafruit_BME280::MODE_NORMAL, Adafruit_BME280::SAMPLING_NONE, // temperature SAMPLING_NONE = don't sanpling Adafruit_BME280::SAMPLING_X2, // pressure Adafruit_BME280::SAMPLING_NONE, // humidity Adafruit_BME280::FILTER_X2 , Adafruit_BME280::STANDBY_MS_0_5); /*Measurement time = (STANDBY_MS + sampling time for each sensor)*/ // PIN MODE pinMode(MODE_PIN, INPUT); pinMode(DEBUG_LED_PIN, OUTPUT); digitalWrite(DEBUG_LED_PIN, HIGH); } void loop() { // モード選択(読み出し or 書き込み) // 読み出しモード // 書き込みモード // 気圧読み出し // 測定スタートか判定 // 測定スタート // 時間と気圧取得 // 時間(ミリ秒)→スタートを0とする // 気圧() long buf = 0, last_buf = 0, i = 0, j = 0; float shot_judge = 0, press = 0, max_press = 0, ave_press = 0, last_press = 0, press_buf = 0, last_press_array[5]; long st = 0, time = 0, last_millis = 0, time_dif=0; unsigned int address = 0, saved_last_address = 0; unsigned int err = 0, err_count = 0, err_buf[ERR_COUNT_MAX]; unsigned int ui_buf = 0; bool toggle = 0; //モード読み出し mode = digitalRead(MODE_PIN); if (mode) data_export(); //データエクスポート else { Serial.println("Measurement mode"); delay(3000);//測定安定化 } #ifdef AUTO_START //打ち上げ待機 st = millis(); // max_press = bme.readPressure(); for (i = 0; i < 5; i++) last_press_array[i] = bme.readPressure(); i = 0; while (shot_judge < AUTO_START_PRESS) { buf = (st - millis()) % 200; if (buf > last_buf) { toggle ^= 1; digitalWrite(DEBUG_LED_PIN, toggle); } last_buf = buf; press = bme.readPressure(); ave_press = 0; for (j = 0; j < 5; j++) ave_press += last_press_array[j]; ave_press /= 5; shot_judge = ave_press - press; // shot_judge = max_press - press; // if (press > max_press) // max_press = press; last_press_array[i] = press; i++; if (i > 5) i = 0; Serial.print("press: "); Serial.print(press); // Serial.print(" max: "); // Serial.print(max_press); Serial.print(" ave: "); Serial.print(ave_press); Serial.print(" judge: "); Serial.println(shot_judge); delay(5); } #endif digitalWrite(DEBUG_LED_PIN, LOW); Serial.println("Measurement start."); //データ取得 // EEPROMにデータ書き込み st = millis(); last_press = press; last_millis = st; address = 0; while ((time - st) < MEASUREMENT_TIME_MS) { press = bme.readPressure(); press_buf = last_press - press; last_press = press; // time_dif = millis() - st; time = millis(); time_dif = (unsigned int)(time - last_millis); last_millis = time; data_set[0] = (unsigned char)time_dif; // data_set[0] = ((unsigned int)time_dif >> 8); //上位ビット // data_set[1] = ((unsigned int)time_dif & 0xff); //下位ビット data_set[1] = ((int)(press_buf * 100) >> 8); //上位ビット data_set[2] = ((int)(press_buf * 100) & 0xff); //下位ビット err = MEM.Write2(address, data_set, DATA_BYTE_ONCE); if (err != 0) { Serial.println(String(address) + " err " + String(err)); err_buf[err_count] = address; err_count++; } address += DATA_BYTE_ONCE; delay(8);//Write cycle time 5ms Serial.print( "time: "); Serial.print(time_dif, DEC); Serial.print( " press: " ); Serial.print(press_buf, DEC); // Serial.print( " data_0: "); // Serial.print(data_set[0], DEC); // Serial.print( " data_1: "); // Serial.print(data_set[1], HEX); // Serial.print( " data_2: "); // Serial.print(data_set[2], HEX); Serial.println(); // digitalWrite(DEBUG_LED_PIN, LOW); // delay(200); // digitalWrite(DEBUG_LED_PIN, HIGH); // delay(200); } address -= DATA_BYTE_ONCE; Serial.println("Measurement finished."); Serial.println(); //最終データの保存先のEEPROMアドレスを保存 EEPROM.put(0, (unsigned int)address); EEPROM.get(0, ui_buf); Serial.println("Last address : " + String(address)); Serial.println("Saved last address : " + String(ui_buf)); //エラー件数の保存 EEPROM.put(2, (unsigned int)err_count); EEPROM.get(2, ui_buf); Serial.println("Write err count : " + String(err_count)); Serial.println("Saved write err count : " + String(ui_buf)); //エラーアドレスの保存 // if (err_count > 0) { // for (i = 0; i < err_count; i++) { // EEPROM.put(4 + i * 2, err_buf[i]); // EEPROM.get(4 + i * 2, ui_buf); // Serial.println("err address is " + String(err_buf[i])); // Serial.println("saved err address is " + String(ui_buf)); // } // } Serial.println(); while (1) { digitalWrite(DEBUG_LED_PIN, LOW); delay(1000); digitalWrite(DEBUG_LED_PIN, HIGH); delay(1000); mode = digitalRead(MODE_PIN); if (mode) data_export(); } } void data_export() { long buf = 0, i = 0, j = 0; float press = 0; long time = 0; unsigned int address = 0, saved_last_address = 0; unsigned int err_count = 0, err = 0; Serial.println("Data export mode"); delay(3000); EEPROM.get(0, saved_last_address);//最終データ保存先EEPROMアドレス読み出し Serial.println("last address is " + String(saved_last_address)); Serial.println(); //データエクスポート Serial.println("time(ms),press(Pa)"); while (address < saved_last_address) { address = i * DATA_BYTE_ONCE; err = MEM.Read2(address, data_set, DATA_BYTE_ONCE); // time = (unsigned int)((data_set[0] << 8) + data_set[1]); time = data_set[0]; press = (int)((data_set[1] << 8) + data_set[2]) / 100.0; if (err != 0) Serial.print("err"); else Serial.print(time); Serial.print(","); if (err != 0) Serial.println("err"); else Serial.println(press); delay(5); i++; } i--; //エラーデータエクスポート EEPROM.get(2, err_count); Serial.println(); Serial.println("Export data count: " + String(i)); Serial.println("Write err count: " + String(err_count)); Serial.println("Data export process finished."); while (1) { digitalWrite(DEBUG_LED_PIN, LOW); delay(1000); digitalWrite(DEBUG_LED_PIN, HIGH); delay(1000); } } ``` 気圧低下が大きいと自動で測定開始としていましたが、 発射場所が平らな場所でなくロケットの持ち運びで誤作動してしまいました よって、自動測定開始はdefineで切り替えれるようにし。 今回は電源をいれてから30秒間測定しました ## 実際の測定結果 実際にモデルロケットを飛ばしたときのデータが次のグラフです ![キャプションを入力できます](https://camo.elchika.com/d86993c49ee8fc25cf57751315950d1fa88b2f00/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f64656262303562652d346235322d346166322d383336632d6264346639303930666564632f30336466356165662d363935302d343938342d626563352d363639653033623238313763/) ぶっつけ本番でしたが、うまくデータがとれて良かったです! 今回は搭載物が重すぎたため - 燃焼途中なのに高度が低下する - 最大高度20mくらい といった散々な結果でした・・・ 次は荷物を減らしてもっと高く打ち上げてみたいです