1.概要
ubloxのモジュールをシリアルを使用して位置情報を取得する方法はいろいろな記事に書いてあるがi2cを使用した方法は見ないため記事にした。
GPSモジュールからNMEAをi2cで抜き出しGPSDを使用して解析する
ソースコード全部はここ
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に送るようにします。
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;
}
i2c_lib.h : i2cデバイスに対して書き込み,読み取りの関数
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, ®_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;
}
5.解説 デーモン側のみ
ヘッダとi2cデバイスの定義 GPSのアドレスを定義
#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"
メイン , シグナルハンドラ
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を送る
デーモンの処理を行う本体
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
cgps
参考サイト