車用のGPS速度計を作る ~ OLED版

2023年7月1日

gpsoled_1
ハンドルの前にセット

以前にRaspberry Piキャラクタ液晶ディスプレイを使って、車用のGPS速度計を作りました。

この方法では、配線が複雑で操作性が悪いため、実用性に欠けました。

そこで、配線が簡単なI2Cを使うOLED(有機ELディスプレイ)を使ってGPS車速計を作ってみました。

1.ハードウェアの配線

gpsoled_2

Raspberry Pi Zero を使ってGPS車速計を作りますが、接続するパーツは以下になります。

GPS

gpsoled_3

秋月電子通商さんのGPS受信機Raspberry PiGPIOと接続します。下で示すように配線してください。左がGPS受信機で、右がRaspberry Pi です。ちょうど配線が並ぶようになります。

5V / 5V出力
GND / GND
TXD0 / GPIO14
RXD0 / GPIO15
1PPS / GPIO18

※当製品は販売終了になってしまったようです。中古品が手に入るようなら、手に入れてください。また、以下の製品で代用ができると思います。

0.96インチOLED

gpsoled_4

I2C接続のOLEDを使います。Raspberry PiOLEDSDASCL3.3V電源GNDをそれぞれ接続します。

製品によって、VCCGNDの配置が違います。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ダッシュボックスの中に入れて、OLEDGPSアンテナハンドル前に置きました。

動画も作成しました。いい感じで速度が表示されます。少し表示が遅いというのはあるけれど、視認性が良くて便利に使えそうです。

5.遠出する時に使いたい

gpsoled_5

普段、車に乗る時に、今回のセッティングを行うのは面倒です。しかし、遠出をする時は、速度オーバーに気を付けなければなりませんから、今回のGPSでの車速確認は有効でしょう。

また、車によってセッティング方法が異なると思いますので、各自工夫をしてGPS車速計をぜひ使ってみてください。