ubloxのGPSモジュールをi2cで使用する



# 1.概要

ubloxのモジュールをシリアルを使用して位置情報を取得する方法はいろいろな記事に書いてあるがi2cを使用した方法は見ないため記事にした。  
GPSモジュールからNMEAをi2cで抜き出しGPSDを使用して解析する

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482400/019fa947-508c-bc4b-ddcb-bec9365ae946.png" width=90%>

ソースコード全部はここ  
https://github.com/cherryblossoms-puppy/ublox-i2c-udp-server

GPSD本家  
https://gpsd.gitlab.io/gpsd/

# 2.環境
* raspberry pi 4
* Linux raspberrypi 6.1.14-v8+ #1633 SMP PREEMPT Thu Mar  2 11:09:44 GMT 2023 aarch64 GNU/Linux
* gpsd: 3.25.1~dev (revision release-3.25-15-g0b97c2092)
* GPSモジュール　ublox LEA-6S


# 3.初期設定
raspi-config でi2cが有効になっているか確認  
配線は省略します.(i2c接続するだけなので)  

gpsdとi2c-toolのインストール  

```
sudo apt-get install gpsd gpsd-clients  
sudo apt-get install i2c-tools  
```

正常に接続できていれば0x42でデバイスを見つけることができます
```
pi@raspberrypi:~/Desktop/gps $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- 42 -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --        
```

# 4.コード

i2cからデータを読み取りUDPを使用してGPSDに送るようにします。

<details><summary>ublox-i2c-udp-server.c : デーモン本体　</summary><div>

```C:ublox-i2c-udp-server.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <errno.h>
#include <linux/i2c.h>
#include <signal.h>
#include <sys/time.h>

#include "i2c_lib.h"

#define I2C_DEV_1 "/dev/i2c-1" // GPS
#define ADDR_GPS 0x42

int readGPS(int sock,struct sockaddr_in addr)
{
	unsigned char data[2] = {0, 0};
	unsigned char *pdata = data;
	if (i2c_read(I2C_DEV_1, ADDR_GPS, 0xFD, pdata, 2) < 0)
	{
		fprintf(stderr, "%s i2c read err\n", __func__);
		return (-1);
	}

	char msb = data[0];
	char lsb = data[1];

	short bytesAvailable = (short)msb << 8 | lsb;

	if (bytesAvailable <= 0)
		return 0;

	unsigned char *rcvbuffer = (unsigned char *)malloc(bytesAvailable);

	if (!rcvbuffer)
		return -1;

	for (int i = 0; i < bytesAvailable; i++)
	{
		if (i2c_read(I2C_DEV_1, ADDR_GPS, 0xFF, rcvbuffer + i, 1) < 0)
		{
			fprintf(stderr, "%s i2c read err\n", __func__);
			free(rcvbuffer);
			return (-1);
		}
	}
	
	// udp 送信処理
	if (sendto(sock, rcvbuffer, bytesAvailable, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0)
	{
		fprintf(stderr, "%s :sendto error %dbyte\n", __func__, bytesAvailable);
		free(rcvbuffer);
		return -1;
	}

	free(rcvbuffer);
	return 1;
}

int endflag = 0;
void SignalHandler(int signo)
{
	endflag = 1;
}
int main(int argc, char *argv[])
{
	signal(SIGUSR1, SignalHandler);
	signal(SIGINT, SignalHandler);
	signal(SIGTERM, SignalHandler);

	int sock;
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	if(argc == 3){
		addr.sin_addr.s_addr = inet_addr(argv[1]);
		addr.sin_port = htons(atoi(argv[2]));
	}else{
		addr.sin_addr.s_addr = inet_addr("127.0.0.1");
		addr.sin_port = htons(12345);
	}
	sock = socket(AF_INET, SOCK_DGRAM, 0);

	while (!endflag)
	{
		readGPS(sock,addr);
		struct timeval tv_sleep;

		tv_sleep.tv_sec = 0;
		tv_sleep.tv_usec = 10000;
		select(0, NULL, NULL, NULL, &tv_sleep);
	}

	close(sock);
	return 0;
}
```
</div></details>

<details><summary>i2c_lib.h : i2cデバイスに対して書き込み,読み取りの関数　</summary><div>

```C:i2c_lib.h

unsigned char i2c_read(char *dev_name, unsigned char dev_addr, unsigned char reg_addr, unsigned char *data, unsigned short length)
{
    /* I2Cデバイスをオープンする. */
    int fd = open(dev_name, O_RDWR);
    if (fd == -1)
    {
        fprintf(stderr, "i2c_read: failed to open: %s\n", strerror(errno));
        return -1;
    }

    /* I2C-Readメッセージを作成する. */
    struct i2c_msg messages[] = {
        {dev_addr, 0, 1, &reg_addr},        /* レジスタアドレスをセット. */
        {dev_addr, I2C_M_RD, length, data}, /* dataにlengthバイト読み込む. */
    };
    struct i2c_rdwr_ioctl_data ioctl_data = {messages, 2};

    /* I2C-Readを行う. */
    if (ioctl(fd, I2C_RDWR, &ioctl_data) != 2)
    {
        fprintf(stderr, "i2c_read: failed to ioctl: %s\n", strerror(errno));
        close(fd);
        return -1;
    }

    close(fd);
    return 0;
}

unsigned char i2c_write(char *dev_name, unsigned char dev_addr, unsigned char reg_addr, const unsigned char *data, unsigned short length)
{
    /* I2Cデバイスをオープンする. */
    int fd = open(dev_name, O_RDWR);
    if (fd == -1)
    {
        fprintf(stderr, "i2c_write: failed to open: %s\n", strerror(errno));
        return -1;
    }

    /* I2C-Write用のバッファを準備する. */
    unsigned char *buffer = (unsigned char *)malloc(length + 1);
    if (buffer == NULL)
    {
        fprintf(stderr, "i2c_write: failed to memory allocate\n");
        close(fd);
        return -1;
    }
    buffer[0] = reg_addr;             /* 1バイト目にレジスタアドレスをセット. */
    memcpy(&buffer[1], data, length); /* 2バイト目以降にデータをセット. */

    /* I2C-Writeメッセージを作成する. */
    struct i2c_msg message = {dev_addr, 0, length + 1, buffer};
    struct i2c_rdwr_ioctl_data ioctl_data = {&message, 1};

    /* I2C-Writeを行う. */
    if (ioctl(fd, I2C_RDWR, &ioctl_data) != 1)
    {
        fprintf(stderr, "i2c_write: failed to ioctl: %s\n", strerror(errno));
        free(buffer);
        close(fd);
        return -1;
    }

    free(buffer);
    close(fd);
    return 0;
}
```
</div></details>

# 5.解説　デーモン側のみ

### ヘッダとi2cデバイスの定義　GPSのアドレスを定義

```C:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <errno.h>
#include <linux/i2c.h>
#include <signal.h>
#include <sys/time.h>

#include "i2c_lib.h"

#define I2C_DEV_1 "/dev/i2c-1" // GPS
#define ADDR_GPS 0x42
```
ubloxのGPSモジュールのi2cアドレスは0x42  
raspberry pi のGPIOのi2cは"/dev/i2c-1"  

### メイン , シグナルハンドラ
```C:
int endflag = 0;
void SignalHandler(int signo)
{
	endflag = 1;
}
int main(int argc, char *argv[])
{
	signal(SIGUSR1, SignalHandler);
	signal(SIGINT, SignalHandler);
	signal(SIGTERM, SignalHandler);

	int sock;
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	if(argc == 3){
		addr.sin_addr.s_addr = inet_addr(argv[1]);
		addr.sin_port = htons(atoi(argv[2]));
	}else{
		addr.sin_addr.s_addr = inet_addr("127.0.0.1");
		addr.sin_port = htons(12345);
	}
	sock = socket(AF_INET, SOCK_DGRAM, 0);

	while (!endflag)
	{
		readGPS(sock,addr);
		struct timeval tv_sleep;

		tv_sleep.tv_sec = 0;
		tv_sleep.tv_usec = 10000;
		select(0, NULL, NULL, NULL, &tv_sleep);
	}

	close(sock);
	return 0;
}
```
killall や割り込み時にデーモンを終了させる処理、起動時オプションを処理するコード  
一定周期でreadGPS()を実行する  
killall でデーモンを終了  
  
デフォルトでは127.0.0.1:12345に対してUDPを送る  

### デーモンの処理を行う本体
```C:
int readGPS(int sock,struct sockaddr_in addr)
{
	unsigned char data[2] = {0, 0};
	unsigned char *pdata = data;
	if (i2c_read(I2C_DEV_1, ADDR_GPS, 0xFD, pdata, 2) < 0)
	{
		fprintf(stderr, "%s i2c read err\n", __func__);
		return (-1);
	}

	char msb = data[0];
	char lsb = data[1];

	short bytesAvailable = (short)msb << 8 | lsb;

	if (bytesAvailable <= 0)
		return 0;

	unsigned char *rcvbuffer = (unsigned char *)malloc(bytesAvailable);

	if (!rcvbuffer)
		return -1;

    //i2c読み取り
	for (int i = 0; i < bytesAvailable; i++)
	{
		if (i2c_read(I2C_DEV_1, ADDR_GPS, 0xFF, rcvbuffer + i, 1) < 0)
		{
			fprintf(stderr, "%s i2c read err\n", __func__);
			free(rcvbuffer);
			return (-1);
		}
	}
	
	// udp 送信処理
	if (sendto(sock, rcvbuffer, bytesAvailable, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0)
	{
		fprintf(stderr, "%s :sendto error %dbyte\n", __func__, bytesAvailable);
		free(rcvbuffer);
		return -1;
	}

	free(rcvbuffer);
	return 1;
}
```
レジスタ0xFD,0xFDの値はモジュールの読み取り可能なバイト数を返す  
読み取り可能なバイト数(bytesAvailable)をもとにレジスタ0xFFに読み取りをおこなう  

i2cから受信したデータをsendtoを使用してUDPで送信を行う

# 6.ビルド 起動方法

### ビルド
```
gcc -o ublox_i2c_udp_server ublox-i2c-udp-server.c
```

### 起動　デーモン

デーモンをUDPで127.0.0.1:12345に送るように起動
```
./ublox_i2c_udp_server　127.0.0.1　12345 &
```

### 起動　GPSD　
起動オプションにUDPで127.0.0.1:12345をリッスンさせます。
```
gpsd udp://127.0.0.1:12345 &
```

# 7.結果

gpsmon cgps などで位置情報が取得できていることを確認できます。

### gpsmon 
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482400/a2ded615-eb00-2c2b-4737-63ae355e7a11.png">

### cgps
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482400/21c93bd6-e9b6-44a7-e725-4634dbd43669.png">


# 参考サイト

https://qiita.com/nhiro/items/feb91561a6752144af93