ラズパイピコを使った電子工作の応用として、お散歩距離計を作りました。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()
コメント