siroitori0413のアイコン画像
siroitori0413 2024年10月30日作成 (2024年10月31日更新) © GPL-3.0+
製作品 製作品 閲覧数 377
siroitori0413 2024年10月30日作成 (2024年10月31日更新) © GPL-3.0+ 製作品 製作品 閲覧数 377

モデルロケットにラズパイを搭載して打ち上げる

モデルロケットにラズパイを搭載して打ち上げる

ラズパイ搭載モデルロケット計画

福岡大学の松隈教授が、子供たちを対象に定期的にモデルロケットを作って飛ばすイベントをされています。植松電機のロケット教室を福岡で広める活動をされているとのことです。
モデルロケットには植松電機製のペーパークラフトで作成するロケットキットを使用されています。

そのイベントへ参加したときに「ラズパイを載せてみたい!」と思い、その提案に松隈教授も面白がっていただきこのプロジェクトが発足しました。

モデルロケット発射の様子

第1弾はこの記事を書いている私とり子と息子memetanで松隈教授にサポートいただきながら行っていましたが、今回はさらに小学生のメンバーを増やして大人はサポート役として若者をメインに活動しています。(若者たちかなりツヨイので大人のほうが教えられている…)

参加メンバーについてはこの記事の終わりに「メンバー紹介」として記載しています。

目標

ラズパイを載せてロケットを飛ばし、パラシュートで落ちてくること。
ラズパイでは、ロケット飛行中に以下を行う。

  • カメラ映像の記録
  • 9軸センサ(加速度・ジャイロ・磁気)の値の記録

前回の結果を踏まえて

前回第1弾で得た反省を踏まえて今回の実装を行う(前回の様子はこちらのブログに記載しています)

  • ラズパイの重量でロケットが重くなりパラシュートが開く前に落ちて大破してしまった
     →軽量化を行い、また重量を計測し見合ったサイズのロケットとロケットエンジンを使用する。
  • 加速度・各速度の値が1秒間隔と長すぎ有用なデータとならなかった
     →間隔を短くし取りこぼしがないようにする

前回は大破してしまったので1度しか打ち上げられなかったのですが、確実にパラシュートが開けば中身が壊れることが少ないと思うので、調整しつつ何度もリトライすることができます。

ハードウェア

今回のラズパイ搭載ロケットには以下を使用

ロケット選定

前回より機材を積み込みやすくするようにロケットを一回り大きなB型のサイズにしました。(前回はA型でした)

B型ロケット(組み立て前)
B型ロケット
B型ロケット重量

B型のロケットは本来B型エンジン用なのですが、ラズパイなどの重量が増えることを考えてエンジンはC型を使用することにしました。
前回はA型のロケットにB型のエンジンB6-6を使用したのですが、今回はC6-3を使用することにしました。「-」以降の数値が逆噴射までの時間なので今回利用するエンジンは3秒です。より短い時間にすることでパラシュートが開く前に落ちてしまうことを防ぎます。
エンジンC6-3重量

試しにB型ロケット+C型エンジンでラズパイを載せずにどれくらい飛ぶかのテストも行いました。
速いスピードですごく高い位置まで飛んでいき大学の建物を飛び越えてしまいました。

ここに動画が表示されます

電池

松隈教授の提案で電池は今回はリチウムイオン電池に昇圧回路を取り付けて使いました。
リチウムイオン電池と昇圧回路
満充電で加速度センサを動作させるプログラムを動かして試してみて約5時間駆動することを確認しています。
最低でもロケットを飛ばす間にバッテリー切れしなければ良いので大丈夫そうです。

9軸センサ

最終的に速度と位置の情報を計算して取得するための9軸センサの配線です。
テストプログラムを実行し正しく値が取得できることを確認しました。
9軸センサ

取得値からの位置計測の計算方法については松隈教授に教えていただきました。
テスト動作の結果の位置表示は以下のようになり正しく動作することが確認できました。
9軸センサの値からの位置計測

スイッチとLED

リモート接続だとロケットが発射したときに通信が切れてしまう恐れがあるので、飛ばす間はWiFi接続は(Bluetooeh接続も)行いません。
このためロケット発射時(記録開始時・終了時)には物理スイッチでON/OFFさせる必要があります。
またラズパイは固定具で固定されてしまうため物理ボタンが手の届く位置にくるようにと考えジャンパワイヤの先にスイッチを接続することにしました。また、飛んでいる間の振動でスイッチが意図せず押されたらいけないので、スイッチはONしたあとジャンパワイヤごと抜いてしまい、落ちてきたあとにジャンパワイヤをまた差し込んでOFFにする方式としました。

またこのときに本当にON/OFFになったか確認する手段として、LEDを配線しました。
当初ラズパイ本体についているLEDを使用するよう設計していましたが、ロケットへのラズパイの配置の状態ではLEDランプの状態が位置的に見えなくなることが判明したので、別で取り付けるようにしました。

キャプションを入力できます

キャプションを入力できます

3Dプリント固定具

3D造形が得意な小学生のfumiくんがAutodesk Fusionで設計・自宅3Dプリンタで出力してくれました。
3Dプリンター:Sermoon V1 Pro(Creality Japan)
フィラメント:PLA

ロケット内部パーツ固定部品3Dモデル

ロケットへの装着

固定具はロケットのノーズコーンの位置にちょうどの大きさで入る形になります。ノーズコーンに入れる理由としては、下の部分になるとエンジンに近くなるため燃えてしまう可能性があるためです。
固定具にはラズパイほか部品を載せます。

笑顔で固定具にやすりがけをする松隈教授

ソフトウェア

プログラム

memetanがプログラムを作成しました。
前回はNode.jsで作ってましたが今回はPythonをレクチャーするという意図もありPythonでプログラムを作成しました。

以下の流れで処理を行っています。

  1. (記録モードOFFの場合に)スイッチ入力 ⇒ 記録モードON
  2. 記録中を示すLEDを点灯
  3. 9軸センサの記録ファイル作成・オープン
  4. カメラの動画記録プロセス開始
  5. 9軸センサの記録開始…ループ処理で行追加して記録
  6. (記録モードONの場合に)スイッチ入力 ⇒ 記録モードOFF
  7. 記録中を示すLEDを消灯
  8. 9軸センサの記録終了、ファイルクローズ
  9. カメラの動画記録プロセス終了

raspi-rocket.py

#!/usr/bin/python3 # -*- coding: utf-8 -*- import smbus import time import RPi.GPIO as GPIO import itertools import subprocess import sys import os import signal GPIO_BTN = 15 GPIO_LED = 18 I2C_BUS = 1 I2C_ACC = 0x19 I2C_GYRO = 0x69 I2C_MAG = 0x13 GPIO.setmode(GPIO.BCM) GPIO.setup(GPIO_BTN, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(GPIO_LED, GPIO.OUT) def led(state): GPIO.output(GPIO_LED, state) def setup_bus(bus): # range (resolution): +-2g bus.write_byte_data(I2C_ACC, 0x0f, 0x03) # bw (bandwidth): 7.81 hz bus.write_byte_data(I2C_ACC, 0x10, 0x08) # disable suspend, sleep_dur: 0.5ms bus.write_byte_data(I2C_ACC, 0x11, 0x00) # range (full scale): 125 degree bus.write_byte_data(I2C_GYRO, 0x0f, 0x04) # bw (odr): 100 hz bus.write_byte_data(I2C_GYRO, 0x10, 0x07) # disable suspend, sleep_dur: 2ms bus.write_byte_data(I2C_GYRO, 0x11, 0x00) data = bus.read_byte_data(I2C_MAG, 0x4b) if (data == 0): # soft reset bus.write_byte_data(I2C_MAG, 0x4b, 0x83) time.sleep(0.5) # normal opmode, data rate (odr): 10 hz bus.write_byte_data(I2C_MAG, 0x4c, 0x00) # enable x, y and z-axis bus.write_byte_data(I2C_MAG, 0x4e, 0x84) # no. of repetitions x and y axis: 9 bus.write_byte_data(I2C_MAG, 0x51, 0x04) # no. of repetitions z-axis: 15 bus.write_byte_data(I2C_MAG, 0x52, 0x16) def read_data(bus): # x_accel lsb, xaccl msb, y_accel lsb, yaccl msb, z_accel lsb, zaccl msb data = bus.read_i2c_block_data(I2C_ACC, 0x02, 6) x_accel = ((data[1] * 256) + (data[0] & 0xf0)) / 16 if x_accel > 2047: x_accel -= 4096 y_accel = ((data[3] * 256) + (data[2] & 0xf0)) / 16 if y_accel > 2047: y_accel -= 4096 z_accel = ((data[5] * 256) + (data[4] & 0xf0)) / 16 if z_accel > 2047: z_accel -= 4096 # x_gyro lsb, xgyro msb, y_gyro lsb, ygyro msb, z_gyro lsb, zgyro msb data = bus.read_i2c_block_data(I2C_MAG, 0x02, 6) x_gyro = data[1] * 256 + data[0] if x_gyro > 32767: x_gyro -= 65536 y_gyro = data[3] * 256 + data[2] if y_gyro > 32767: y_gyro -= 65536 z_gyro = data[5] * 256 + data[4] if z_gyro > 32767: z_gyro -= 65536 # x-axis lsb, x-axis msb, y-axis lsb, y-axis msb, z-axis lsb, z-axis msb data = bus.read_i2c_block_data(I2C_GYRO, 0x42, 6) x_mag = ((data[1] * 256) + (data[0] & 0xf8)) / 8 if x_mag > 4095: x_mag -= 8192 y_mag = ((data[3] * 256) + (data[2] & 0xf8)) / 8 if y_mag > 4095: y_mag -= 8192 z_mag = ((data[5] * 256) + (data[4] & 0xfe)) / 2 if z_mag > 16383: z_mag -= 32768 return ( (x_accel, y_accel, z_accel), (x_gyro, y_gyro, z_gyro), (x_mag, y_mag, z_mag) ) def record_bus_frame(bus, file): data = read_data(bus) file.write(str(time.time_ns())) file.write(",") file.write( ",".join( map( lambda x: str(x), list(itertools.chain.from_iterable(data)) ) ) ) file.write("\n") file.flush() def start_camera_record(ts): vid = subprocess.Popen([ "libcamera-vid", "-t", "0", "--width", "640", "--height", "480", "--codec", "h264", "--framerate", "30", "-o", "-" ], stdout=subprocess.PIPE, stderr=sys.stderr) subprocess.Popen([ "ffmpeg", "-i", "-", "-c", "copy", "recording-" + str(ts) + ".mp4" ], stdin=vid.stdout, stdout=sys.stdout, stderr=sys.stderr) return vid def before_record(): ts = time.time_ns() text_file = open("recording-" + str(ts) + ".txt", "w") text_file.write( "timestamp_ns," "acc_x,acc_y,acc_z," "gyro_x,gyro_y,gyro_z," "mag_x,mag_y,mag_z\n" ) camera = start_camera_record(ts) return (text_file, camera) def record_tick(bus, data): text_file, *_ = data record_bus_frame(bus, text_file) def after_record(data): text_file, camera = data text_file.close() os.kill(camera.pid, signal.SIGTERM) if __name__ == '__main__': bus = smbus.SMBus(I2C_BUS) setup_bus(bus) record_state = False record_data = None while True: if record_state: record_tick(bus, record_data) toggle = not GPIO.input(15) if not toggle: if not record_state: time.sleep(0.5) continue record_state = not record_state led(record_state) if record_state: record_data = before_record() else: after_record(record_data) while not GPIO.input(15): time.sleep(0.5)

出力される9軸センサのログとなるテキストは、以下のような形式(カンマ区切りCSV)となります。下記はプログラムを実際に動かして記録したものであり、およそ6ミリ秒ごとに記録できています。

timestamp_ns,acc_x,acc_y,acc_z,gyro_x,gyro_y,gyro_z,mag_x,mag_y,mag_z 1729777337952932558,-1016.0,94.0,-44.0,-21,-109,45,-79.0,-64.0,-11.0 1729777337958797524,-1017.0,95.0,-44.0,-21,-109,45,-79.0,-64.0,-11.0 1729777337963993494,-1017.0,95.0,-44.0,-39,-19,67,-79.0,-64.0,-11.0 1729777337969275463,-1017.0,95.0,-44.0,-39,-19,67,-79.0,-64.0,-11.0

動画はMP4コンテナ(H264コーデック)で出力されます。

ロケット打ち上げ

いざロケット打ち上げ!!
のはずでしたが、、あいにくの天候で打ち上げることができずラズパイIoTコンテストに間に合うタイミングとなりませんでした。。
打ち上げが行えたら追記します。次回打ち上げは12月を予定しています。

メンバー紹介

メンバー 属性 役割
memetan 17歳男子 ラズパイ全般・プログラム担当
fumi 小学5年男子 パーツ固定部品の3D設計担当
ゆう 小学5年男子 ロケット担当
松隈先生 福岡大学教授 現場監督
とり子 memetanの母 ドキュメント整理
fumiくんのお父さん 雑用
ゆうママ ゆうくんのお母さん 雑用

活動の様子

3
1
siroitori0413のアイコン画像
https://siroitori.hatenablog.com/
ログインしてコメントを投稿する