LoginSignup
1
0

PicoのGPSロガーにOLEDディスプレイを追加

Last updated at Posted at 2024-01-21

はじめに

Picoで自作しているGPSロガーですが、動作状況がわからないので、心拍数やGPS情報を低消費電力のOLEDディスプレイで表示しました。

前提

の続きでございます。

配線とケース収納

よくワンコイン前後で売られている「SSD1306」のOLEDディスプレイを接続します。私の場合、GPSでUART0(GP0,GP1)を使っていたので、I2C0が使えずI2C1(GP18,GP19)に接続しています。

864372e706e67.png

GDuGRkDa4AAFYdT.jpg

パッケージ追加とプログラム(main.py)

PicoをPCに接続して、ThonnyからOLEDディスプレイ用のパッケージをインストールします。
メニューバーから[ツール]-[パッケージを管理...]をクリックします。

pkg_menu.png

「ssd1306」で検索して、一番上の「micropython-ssd1306」です。
pkg.png

CSVファイルにログを書き込むタイミングでOLEDの表示も更新するようにしました。表示する項目は以下です。

  • 心拍数
  • 時間(GPS)
  • 経度緯度(GPS)
  • 高度(GPS)

表示の更新は、衛星を掴んでから5秒間隔です。

main.py
from pico import l76x
from pico.micropyGPS.micropyGPS import MicropyGPS
from machine import Pin, PWM
import time
import ssd1306

# OLEDの初期設定
sda = machine.Pin(18)
scl = machine.Pin(19)
i2c = machine.I2C(1,sda=sda, scl=scl, freq=400000)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

# GPSの初期設定
gnss_l76b=l76x.L76X(uartx=0,_baudrate = 9600)
gnss_l76b.l76x_exit_backup_mode()
gnss_l76b.l76x_send_command(gnss_l76b.SET_SYNC_PPS_NMEA_ON)
parser = MicropyGPS(location_formatting='dd')
sec = 0
tpfile = open("gpslog.csv","a")

# 心拍センサー(デジタル出力)とブザーの設定
hrm_pin = Pin(26, Pin.IN, Pin.PULL_DOWN)
buzzer = PWM(Pin(14))
heart_rates = []
last_heartbeat_time = 0

def add_hours_to_time(hh, mm, ss, add_hours):
    hh += add_hours
    if hh >= 24:
        hh -= 24
    return hh, mm, ss

def beep(slp):
    buzzer.freq(3000)  # ブザーの周波数を設定
    buzzer.duty_u16(32768)  # ブザーをオン(半分のデューティサイクル)
    time.sleep(slp)  # ブザーを鳴らす時間
    buzzer.duty_u16(0)  # ブザーをオフ

def filter_heart_rates(heart_rate, window_size=3):
    if len(heart_rates) < window_size:
        return True
    avg = sum(heart_rates[-window_size:]) / window_size
    return abs(heart_rate - avg) < 20  # 許容する最大の逸脱値

def on_heartbeat(pin):
    global last_heartbeat_time, heart_rates
    current_time = time.ticks_ms()
    if last_heartbeat_time > 0:
        interval = time.ticks_diff(current_time, last_heartbeat_time)
        heart_rate = 60000 // interval
        if 50 <= heart_rate <= 200: # 心拍数50~200が有効データ範囲
            if filter_heart_rates(heart_rate):
                heart_rates.append(heart_rate)
                print("Heart Rate:", heart_rate)
                # 心拍数に基づいてビープ音の長さを変更
                if heart_rate > 160:
                    beep(0.3)
                elif heart_rate > 120:
                    beep(0.1)
        else:
            print("Heart Rate [spike!]:", heart_rate)
    last_heartbeat_time = current_time

# 心拍センサーの割り込み設定
hrm_pin.irq(trigger=Pin.IRQ_RISING, handler=on_heartbeat)
prev_hr = 0

# メインループ
while True:
    if gnss_l76b.uart_any():
        try:
            sentence = parser.update(chr(gnss_l76b.uart_receive_byte()[0]))
        except:
            pass
        if sentence and parser.satellites_in_use > 0 and sec != parser.timestamp[2] and (parser.timestamp[2] % 5) == 0:
            datetime = f"20{parser.date[2]:02}-{parser.date[1]:02}-{parser.date[0]:02}T{parser.timestamp[0]:02}:{parser.timestamp[1]:02}:{parser.timestamp[2]:02}Z"
            lat = f"{parser.latitude[0]:.9f}"
            lon = f"{parser.longitude[0]:.9f}"
            alt = int(parser.altitude)
            avg_heart_rate = sum(heart_rates) // len(heart_rates) if heart_rates else 0
            if avg_heart_rate == 0:
                avg_heart_rate = prev_hr # 心拍数を計測していない場合は前回値を出力
            else:
                prev_hr = avg_heart_rate
            data = f"{datetime},{lat},{lon},{alt},{parser.geoid_height},{parser.fix_stat},{parser.hdop},{parser.satellites_in_use},{avg_heart_rate}\n"
            print(data)
            sec = parser.timestamp[2]
            tpfile.write(data)
            tpfile.flush()
            heart_rates.clear()  # 心拍数リストをクリア
            hour, minute, second = add_hours_to_time(parser.timestamp[0], parser.timestamp[1], parser.timestamp[2], 9)
            oled.fill(0)
            oled.rect(0, 0, 127, 63, 1)
            oled.text(f"H:{avg_heart_rate:3}  {hour:02}:{minute:02}:{second:02}",2,5)
            oled.text(f"Geopoint:",2,20)
            oled.text(f" {parser.latitude[0]:.3f},{parser.longitude[0]:.3f}",0,30)
            oled.text(f"Altitude:",2,43)
            oled.text(f" {alt:4}m",0,53)
            oled.show()

使ってみた

いつもトレーニングで登っている吾妻山公園へ。
二宮、小田原、真鶴まで途切れて続く街灯りが好きです。

PXL_20240121_094444062.NIGHT.jpg

OLEDは、ばっちり綺麗に表示しています。
何気に時間と高度が分かるのは嬉しいかも。

PXL_20240121_094312724.jpg

おわりに

まだ本体の制作費に10K円もかかってないと思います(バッテリーは別です)。ディスプレイをもっと大きくしてサイクルコンピュータ?みたいなのも作れそうな気がしますね。

普通なら「ガーミン買って終わり」で既製品もカッコいいのですが、データを自由に流用できる自作デバイスも良いですね!安く作れると満足感もあります。

Pico Wがどこまでできるか調べてませんが、スマホアプリにデータ転送ができるように作れるならPico Wで作ってみたいですね。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0