お散歩距離計をつくる。

ラズパイピコ

 ラズパイピコを使った電子工作の応用として、お散歩距離計を作りました。GPSモジュールで現在地を定期的に取得し、移動距離を積算してLEDで表示します。電源には単3電池を3本使います。筐体は3dプリンターで作成しました。

広告

外観と機能

 作成したお散歩距離計の外観です。オモテ面の左側に4桁の7セグメントLEDとRaspberry Pi Pico、右側にGPSモジュールとタクトボタン、スライドスイッチを配置しました。
 スライドスイッチで電源を入れるとGPSモジュールが1秒ごとに現在地の緯度と経度を取得し、前回の測定地点との距離を算出します。移動距離の積算値を7セグメントLEDに表示しています。
 タクトボタンを3秒以上押し続けると、積算距離がリセットされます。(スライドスイッチをoff-onしても同じです)

 配線が下手っぴなのはご愛嬌。

 ウラ面には電池ボックスを作ってあります。GPSモジュールの駆動電圧が3.8~12Vなので、単3乾電池を3本使うようにしました。電池のせいで重く、大きくなってしまったのはちょっと残念。
 リチウムポリマー電池を使えばもっと小さくできたかと思いますが、発火リスクが怖いので今回は見送りました。

使った部品、回路

 使用部品は下記のとおりです。下記の他に導線、筐体のフタを閉じるためのネジ、市販の電池ケースをバラして取り出した単3乾電池用の電極なども使っています。各部品の使い方は過去にここ(4桁7セグメントLED)ここ(GPSモジュール)で解説しています。

電子部品個数備考
1個Raspberry Pi Pico
1個GPS受信キットAE-GYSFDMAXB
太陽誘電のGPSモジュールGYSFDMAXBを使用したセットです。
秋月電子通商で2200円でした。
1個4桁7セグメントLED
アノードコモン、カソードコモンのどちらでも良いですが、今回はアノードコモンを使いました。
1個タクトボタンスイッチ
1個スライドスイッチ
8個カーボン抵抗
4桁7セグメントLEDに使いました。

 各部品は以下のように配線しました。

プログラム

 ちょっと長いですが、以下の通りです。7セグメントLED、GPSモジュールについては過去に書いた別の記事の通りです。距離の算出にはヒュベニの式を使っています。また、LEDの表示にはマルチスレッドを使っています。

from machine import Pin, UART
from mpy_decimal import *
import math
import time
import _thread

#UARTの準備
uart = UART(0, 9600, tx=Pin(16), rx=Pin(17))

#ボタンの準備
pulldownpin = Pin(19,Pin.OUT)
btn = Pin(18,Pin.IN,Pin.PULL_DOWN)

#カソードのGPIOを設定
led_Cathodes = [
    Pin(1, Pin.OUT), #A
    Pin(14, Pin.OUT), #B
    Pin(10, Pin.OUT), #C
    Pin(6, Pin.OUT), #D
    Pin(4, Pin.OUT), #E
    Pin(2, Pin.OUT), #F
    Pin(12, Pin.OUT), #G
    Pin(8, Pin.OUT)] #DP

#アノードのGPIOを設定
led_Anodes = [
    Pin(0, Pin.OUT), #DIG1
    Pin(13, Pin.OUT), #DIG2
    Pin(15, Pin.OUT), #DIG3
    Pin(11, Pin.OUT)] #DIG4

#7セグメントLEDで表示する数字
seg7num = [
    [0,0,0,0,0,0,1,1], #0
    [1,0,0,1,1,1,1,1], #1
    [0,0,1,0,0,1,0,1], #2
    [0,0,0,0,1,1,0,1], #3
    [1,0,0,1,1,0,0,1], #4
    [0,1,0,0,1,0,0,1], #5
    [0,1,0,0,0,0,0,1], #6
    [0,0,0,1,1,0,1,1], #7
    [0,0,0,0,0,0,0,1], #8
    [0,0,0,1,1,0,0,1]] #9

#指定の桁の7セグメントLEDに任意の数字を表示する
def on_seg7num(num, dig, dot=False):
    ns = seg7num[num]
    for i,n in enumerate(ns):
        led_Cathodes[i].value(n)
    v = 0 if dot else 1
    led_Cathodes[7].value(v)
    if dig==0:
        led_Anodes[0].value(1)
        led_Anodes[1].value(0)
        led_Anodes[2].value(0)
        led_Anodes[3].value(0)
    elif dig==1:
        led_Anodes[0].value(0)
        led_Anodes[1].value(1)
        led_Anodes[2].value(0)
        led_Anodes[3].value(0)   
    elif dig==2:
        led_Anodes[0].value(0)
        led_Anodes[1].value(0)
        led_Anodes[2].value(1)
        led_Anodes[3].value(0) 
    elif dig==3:
        led_Anodes[0].value(0)
        led_Anodes[1].value(0)
        led_Anodes[2].value(0)
        led_Anodes[3].value(1) 
    else:
        led_Anodes[0].value(0)
        led_Anodes[1].value(0)
        led_Anodes[2].value(0)
        led_Anodes[3].value(0)

#4桁7セグメントLEDを消灯する
def clear_seg7num(dig):
    led_Cathodes[dig].value(1)
    led_Anodes[dig].value(0)

#4桁7セグメントLEDを制御して数字を表示させる
def show_seg7num():
    global distance,showflg
    while True:
        if showflg:
            #7セグメントLEDを表示する
            num=int(distance)
            #千の位、百の位、十の位、一の位の数字を取り出す
            nm = [(num//1000)%10, (num//100)%10, (num//10)%10, num%10]
            #表示する桁を切り替える
            for i in range(4):
                if distance>999:
                    on_seg7num(nm[i],i,dot=False)
                    time.sleep(0.004)
                elif distance>99 and i>0:
                    on_seg7num(nm[i],i,dot=False)
                    time.sleep(0.004)
                elif distance>9 and i>1:
                    on_seg7num(nm[i],i,dot=False)
                    time.sleep(0.004)
                elif i==3:
                    on_seg7num(nm[i],i,dot=False)
                    time.sleep(0.004)
                clear_seg7num(i)
    #スレッドの終了
    _thread.exit()

#ヒュベニの式で2点間の距離を求める
def hubenyDist(lat1,lon1,lat2,lon2):      
    #赤道半径(WGS84)
    Rx=DecimalNumber("6378137")
    #極半径(WGS84)
    Ry=DecimalNumber("6356752.314")
    #緯度の差(ラジアン)
    Dy = lat1-lat2
    if Dy < 0:
        Dy*=-1
    Dy=Dy/180*DecimalNumber.pi()
    #経度の差(ラジアン)
    Dx = lon1-lon2
    if Dx < 0:
        Dx*=-1
    Dx=Dx/180*DecimalNumber.pi()
    #平均緯度(ラジアン)
    P=(lat1+lat2)/2/180*DecimalNumber.pi()
    #離心率
    E=(Rx*Rx-Ry*Ry)/Rx/Rx
    E=E.square_root()
    W=1-E*E*P.sin()*P.sin()
    W=W.square_root()
    #子午線曲率半径
    M=Rx*(1-E*E)/W/W/W
    #卯酉線曲率半径
    N=Rx/W
    #2点間の距離
    D=Dy*Dy*M*M+Dx*Dx*N*N*P.cos()*P.cos()
    return D.square_root()

#GPSで現在の緯度経度を取得する
def getLocation():
    res=uart.read()
    lat,lon=0,0
    #print(res.decode('utf-8'))
    for term in res.decode('utf-8').split('\n'):
        if '$GPGLL' in term:
            #緯度を十進数に変換
            lat=DecimalNumber(term.split(',')[1])
            latdeg=lat/100
            latdeg=latdeg.to_int_truncate()
            latmin=lat-latdeg*100
            latmin=latmin/60
            lat=latdeg+latmin
            #南緯ならマイナスにする
            if term.split(',')[2]=='S':
                lat*=-1
            #経度を十進数に変換
            lon=DecimalNumber(term.split(',')[3])
            londeg=lon/100
            londeg=londeg.to_int_truncate()
            lonmin=lon-londeg*100
            lonmin=lonmin/60
            lon=londeg+lonmin
            #西経ならマイナスにする
            if term.split(',')[4]=='W':
                lon*=-1
    return lat, lon

#プログラムの初期化
def initialize():
    global distance,lat,lon,deflat,deflon,showflg
    print("initializing...")
    showflg=False
    while int(distance)!=0:
        try:
            lat,lon=getLocation()
            distance=float(str((hubenyDist(deflat,deflon,lat,lon))))
            if lat!=0 and lon!=0:
                deflat,deflon=lat,lon
            print(distance,lat,lon)
        except Exception as e:
            print("initial error")
        time.sleep(1)
    showflg=True

#各種変数の初期値など
lat,lon=DecimalNumber("35.360587"), DecimalNumber("138.727473")
deflat,deflon=DecimalNumber("35.674784"), DecimalNumber("138.238854")
distance=-1
showflg=False
pulldownpin.high()

#LED表示開始(別スレッド)
_thread.start_new_thread(show_seg7num,())

#初期化
initialize()

#距離測定を開始
sig_start = time.ticks_ms()
integdist=0
btnflg=False
while True:
    sig_end = time.ticks_ms()
    duration = (sig_end - sig_start) / 1000
    #ボタンを押しているか確認
    if btn.value()==1:
        if btnflg==False:
            btnflg=True
            btnsig_start = time.ticks_ms()
        else:
            btnsig_end = time.ticks_ms()
            if (btnsig_end-btnsig_start)/1000>3:
                initialize()
    else:
        btnflg=False
    #GPS信号を確認
    if duration>1:
        try:
            lat,lon=getLocation()
            shortdist=float(str((hubenyDist(deflat,deflon,lat,lon))))
            #距離が突然100mを超えたらエラーとみなし、スキップする
            if shortdist<100:
                distance=shortdist+integdist
                print(distance)
                #一定距離を超えたら、測長の起点を更新する
                if shortdist>5:
                    integdist=distance
                    deflat,deflon=lat,lon
        except Exception as e:
            print("measure error")
        sig_start = time.ticks_ms()

pulldownpin.low()

コメント

タイトルとURLをコピーしました