車用のGPS速度計を作る ~ OLED版
以前にRaspberry Piとキャラクタ液晶ディスプレイを使って、車用のGPS速度計を作りました。
この方法では、配線が複雑で操作性が悪いため、実用性に欠けました。
そこで、配線が簡単なI2Cを使うOLED(有機ELディスプレイ)を使ってGPS車速計を作ってみました。
1.ハードウェアの配線
Raspberry Pi Zero を使ってGPS車速計を作りますが、接続するパーツは以下になります。
GPS
秋月電子通商さんのGPS受信機をRaspberry PiのGPIOと接続します。下で示すように配線してください。左がGPS受信機で、右がRaspberry Pi です。ちょうど配線が並ぶようになります。
5V / 5V出力
GND / GND
TXD0 / GPIO14
RXD0 / GPIO15
1PPS / GPIO18
※当製品は販売終了になってしまったようです。中古品が手に入るようなら、手に入れてください。また、以下の製品で代用ができると思います。
0.96インチOLED
I2C接続のOLEDを使います。Raspberry PiとOLEDのSDAとSCL、3.3V電源、GNDをそれぞれ接続します。
製品によって、VCCとGNDの配置が違います。M5 ATOM Liteであれば、下の製品がピン配列に合って良いでしょう。
2.ソフトウェア
Raspberry Pi のインタフェイスを有効にする
今回は、I2Cとシリアルの2つのインタフェイスを使用します。これらを有効にしてください。
SSD1306用ライブラリ
0.96インチOLEDを制御するSSD1306用ライブラリを利用します。
このライブラリは、そのままではエラーになります。delay関数をsleep関数に置き換えるのと、swap_values関数をssd1306_swap関数に置き換えてください。それ以外にも問題がありますが、今回は使用しません。
プログラムソース
プログラムは、従来のGPSコードを流用しています。使わない機能もありますので、適度に修正してください。
#include <stdio.h>
#include <stdlib.h>
#include <wiringPi.h>
#include <wiringSerial.h>
#include <string.h>
#include <unistd.h>
#include "./oled1306lib/ssd1306_i2c.h"
#define INTERRUPT_PIN 18
#define SERIAL_PORT "/dev/serial0"
#define ONE_LINE 1024
int g_fd ; // serial id
char g_oneline[ONE_LINE] ;
char g_ido[128] ;
char g_keido[128] ;
float g_speed ;
char g_speedchr[16] ;
int g_satelites = 0 ;
char g_satelitesChar[3] ;
int g_disp_mode = 0 ; // 0 : speed 1 : lat/lot 2 : time 99 : test
void MySplit() {
int counter = 0;
int onelineLength = strlen(g_oneline) ;
char buff[256] ;
int point = 0 ;
float value = 0 ;
int _do ;
int _hun ;
int _byou ;
float amari ;
memset(buff, 0x00, 256) ;
for (int i=0;i<onelineLength;i++) {
if (g_oneline[i] == ',') {
switch(point) {
case 2 :
// ido
value = atof(buff) ;
_do = (int)(value / 100) ;
_hun = (int)(value - (_do * 100)) ;
amari = value - _do * 100 - _hun ;
_byou = (int)(amari * 60) ;
amari = amari * 60 - (float)_byou ;
sprintf(g_ido, "%03d'%02d'%02d'%1d", _do, _hun, _byou, (int)(amari * (float)10)) ;
break ;
case 4 :
// keido
value = atof(buff) ;
_do = (int)(value / 100) ;
_hun = (int)(value - (_do * 100)) ;
amari = value - _do * 100 - _hun ;
_byou = (int)(amari * 60) ;
amari = amari * 60 - (float)_byou ;
sprintf(g_keido, "%03d'%02d'%02d'%1d", _do, _hun, _byou, (int)(amari * (float)10)) ;
break ;
case 7 :
// satelites
g_satelites = atoi(buff) ;
sprintf(g_satelitesChar, "%02d", g_satelites) ;
break ;
default :
break ;
}
point ++ ;
memset(buff, 0x00, 256) ;
counter = 0 ;
} else {
buff[counter] = g_oneline[i] ;
counter ++ ;
}
}
}
void MySplit2() {
int counter = 0;
int onelineLength = strlen(g_oneline) ;
char buff[256] ;
int point = 0 ;
float value = 0 ;
memset(buff, 0x00, 256) ;
for (int i=0;i<onelineLength;i++) {
if (g_oneline[i] == ',') {
switch(point) {
case 7 :
// speed
value = atof(buff) ;
g_speed = value ;
sprintf(g_speedchr, "%03d", (int)(value + 0.5)) ;
break ;
default :
break ;
}
point ++ ;
memset(buff, 0x00, 256) ;
counter = 0 ;
} else {
buff[counter] = g_oneline[i] ;
counter ++ ;
}
}
}
void nmea0183analysis() {
if (strncmp(g_oneline, "$GPGGA", 6) == 0) {
MySplit() ;
} else if (strncmp(g_oneline, "$GPVTG", 6) == 0) {
MySplit2() ;
}
}
void get_gps_data() {
char buff[1024] ;
int read_length ;
int onelinepointer ;
onelinepointer = strlen(g_oneline) ;
read_length = serialDataAvail(g_fd) ;
/* 受信データをバッファに入れる */
for (int i=0;i<read_length;i++) {
if (i+onelinepointer >= ONE_LINE) {
break ;
}
g_oneline[onelinepointer] = serialGetchar(g_fd) ;
if (g_oneline[onelinepointer] == 0x0a) {
nmea0183analysis() ;
memset(g_oneline, 0x00, ONE_LINE) ;
onelinepointer = 0 ;
} else {
onelinepointer++ ;
}
}
}
int init() {
if (wiringPiSetupGpio() == -1) {
return -1 ;
}
g_fd = serialOpen(SERIAL_PORT, 9600);
if (g_fd < 0) {
printf("open error\n") ;
return -1 ;
}
pinMode(INTERRUPT_PIN, INPUT) ;
pullUpDnControl(INTERRUPT_PIN, PUD_UP) ;
wiringPiISR(INTERRUPT_PIN, INT_EDGE_FALLING, get_gps_data) ;
waitForInterrupt(INTERRUPT_PIN, 2000) ;
memset(g_oneline, 0x00, ONE_LINE);
// OLED
ssd1306_begin(SSD1306_SWITCHCAPVCC, SSD1306_I2C_ADDRESS);
ssd1306_clearDisplay();
strcpy(g_speedchr, "000") ;
strcpy(g_satelitesChar, "00") ;
return 1 ;
}
int main(void) {
if (init() == -1) {
return 1 ;
}
while(1) {
switch(g_disp_mode) {
case 0 :
ssd1306_clearDisplay();
ssd1306_setTextSize(5) ;
ssd1306_drawString(g_speedchr);
ssd1306_drawChar(96, 48, 'k', WHITE, 2);
ssd1306_drawChar(112, 48, 'm', WHITE, 2);
ssd1306_drawChar(0, 48, g_satelitesChar[0], WHITE, 2);
ssd1306_drawChar(12, 48, g_satelitesChar[1], WHITE, 2);
ssd1306_display();
break ;
case 1:
break ;
case 99 :
default :
break ;
}
sleep(1) ;
}
serialClose(g_fd) ;
return 1 ;
}
表示内容には、速度だけでなく、左下画面に衛星受信数を付けました。プログラムソースが用意できたら、
gcc gps_oled.c ./oled1306lib/ssd1306_i2c.c -lwiringPi -o gps_oled
ビルドして実行形式を作成します(環境により、パスを修正してください)。
3.起動時設定
Raspberry Piを起動した時点でアプリケーションが起動するようにします。「/etc/rc.local」ファイルの「exit 0」の記述の前に、起動コマンドを記述します。
4.車でのセッティング
車によってセッティングの方法も変わると思います。私の車では、モバイルバッテリーとRaspberry Pi Zeroをダッシュボックスの中に入れて、OLEDとGPSアンテナをハンドル前に置きました。
動画も作成しました。いい感じで速度が表示されます。少し表示が遅いというのはあるけれど、視認性が良くて便利に使えそうです。
5.遠出する時に使いたい
普段、車に乗る時に、今回のセッティングを行うのは面倒です。しかし、遠出をする時は、速度オーバーに気を付けなければなりませんから、今回のGPSでの車速確認は有効でしょう。
また、車によってセッティング方法が異なると思いますので、各自工夫をしてGPS車速計をぜひ使ってみてください。