モデルロケットにラズパイを搭載して打ち上げる
ラズパイ搭載モデルロケット計画
福岡大学の松隈教授が、子供たちを対象に定期的にモデルロケットを作って飛ばすイベントをされています。植松電機のロケット教室を福岡で広める活動をされているとのことです。
モデルロケットには植松電機製のペーパークラフトで作成するロケットキットを使用されています。
そのイベントへ参加したときに「ラズパイを載せてみたい!」と思い、その提案に松隈教授も面白がっていただきこのプロジェクトが発足しました。
第1弾はこの記事を書いている私とり子と息子memetanで松隈教授にサポートいただきながら行っていましたが、今回はさらに小学生のメンバーを増やして大人はサポート役として若者をメインに活動しています。(若者たちかなりツヨイので大人のほうが教えられている…)
参加メンバーについてはこの記事の終わりに「メンバー紹介」として記載しています。
目標
ラズパイを載せてロケットを飛ばし、パラシュートで落ちてくること。
ラズパイでは、ロケット飛行中に以下を行う。
- カメラ映像の記録
- 9軸センサ(加速度・ジャイロ・磁気)の値の記録
前回の結果を踏まえて
前回第1弾で得た反省を踏まえて今回の実装を行う(前回の様子はこちらのブログに記載しています)
- ラズパイの重量でロケットが重くなりパラシュートが開く前に落ちて大破してしまった
→軽量化を行い、また重量を計測し見合ったサイズのロケットとロケットエンジンを使用する。 - 加速度・各速度の値が1秒間隔と長すぎ有用なデータとならなかった
→間隔を短くし取りこぼしがないようにする
前回は大破してしまったので1度しか打ち上げられなかったのですが、確実にパラシュートが開けば中身が壊れることが少ないと思うので、調整しつつ何度もリトライすることができます。
ハードウェア
今回のラズパイ搭載ロケットには以下を使用
- ペーパークラフトロケットB型
- ロケットエンジンC6-3
- Raspberry Pi Zero WH
- Raspberry Pi Zero用スパイカメラ
- BMX055使用9軸センサーモジュール
- リチウムイオン電池・USB基板
- 3Dプリントによる固定具
ロケット選定
前回より機材を積み込みやすくするようにロケットを一回り大きなB型のサイズにしました。(前回はA型でした)
B型のロケットは本来B型エンジン用なのですが、ラズパイなどの重量が増えることを考えてエンジンはC型を使用することにしました。
前回はA型のロケットにB型のエンジンB6-6を使用したのですが、今回はC6-3を使用することにしました。「-」以降の数値が逆噴射までの時間なので今回利用するエンジンは3秒です。より短い時間にすることでパラシュートが開く前に落ちてしまうことを防ぎます。
試しにB型ロケット+C型エンジンでラズパイを載せずにどれくらい飛ぶかのテストも行いました。
速いスピードですごく高い位置まで飛んでいき大学の建物を飛び越えてしまいました。
電池
松隈教授の提案で電池は今回はリチウムイオン電池に昇圧回路を取り付けて使いました。
満充電で加速度センサを動作させるプログラムを動かして試してみて約5時間駆動することを確認しています。
最低でもロケットを飛ばす間にバッテリー切れしなければ良いので大丈夫そうです。
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
固定具はロケットのノーズコーンの位置にちょうどの大きさで入る形になります。ノーズコーンに入れる理由としては、下の部分になるとエンジンに近くなるため燃えてしまう可能性があるためです。
固定具にはラズパイほか部品を載せます。
ソフトウェア
プログラム
memetanがプログラムを作成しました。
前回はNode.jsで作ってましたが今回はPythonをレクチャーするという意図もありPythonでプログラムを作成しました。
以下の流れで処理を行っています。
- (記録モードOFFの場合に)スイッチ入力 ⇒ 記録モードON
- 記録中を示すLEDを点灯
- 9軸センサの記録ファイル作成・オープン
- カメラの動画記録プロセス開始
- 9軸センサの記録開始…ループ処理で行追加して記録
- (記録モードONの場合に)スイッチ入力 ⇒ 記録モードOFF
- 記録中を示すLEDを消灯
- 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月を予定しています。
メンバー紹介
投稿者の人気記事
-
siroitori0413
さんが
2024/10/30
に
編集
をしました。
(メッセージ: 初版)
-
siroitori0413
さんが
2024/10/31
に
編集
をしました。
-
siroitori0413
さんが
2024/10/31
に
編集
をしました。
ログインしてコメントを投稿する