要素を取り出して表示する方法について質問です

[戻る]
mio 2008/10/03(金) 16:23:24
はじめましてmioと申します。
私はLINUXでC言語を使用して,GPS端末から受信したデータをシリアル通信でPCに表示す
るプログラムを作成しています。
GPS端末から受信するデータは"NMEA"というフォーマットのデータで,緯度経度や時刻に
関する情報を文字と数値で表しています。

最初の段階として,受信したデータをそのまま表示する機能は実現出来たのですが,
次の段階として,表示したい3つの緯度・経度・時刻のみを取り出して表示する機能を付
加しようと思い,NMEAデータから緯度経度時刻のすべてを含む行を判別して必要な部分を
取り出すために,strtok関数とstrcmp関数を使用して、緯度経度時刻の表示も可能になり
ました。
しかしここで,上記2つの機能をあわせて,指定した回数判別して表示する機能を実現した
いのですが, セグメンテーション違反です と表示されてしまうのを解決することが出
来ず困っています。
どの点に問題があるのかご指摘をお願いいたします。
以下は上記にあげた二つのプログラムになります。

@指定した回数NMEAデータを受信して表示するプログラム
#include<unistd.h>
#include<fcntl.h>
#include<strings.h>
#include<termios.h>
#include<stdio.h>

#define BAUDRATE B4800
#define MODEMDEVICE "/dev/ttyS0"

int main()
{
int fd, c, res;
int i=0;
int j=0;
struct termios oldtio, newtio;
unsigned char buf[512];

if((fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ))==-1){
perror(MODEMDEVICE);
exit(-1);
}

tcgetattr(fd, &oldtio); /*シリアルポートの設定を待避*/
bzero(&newtio, sizeof(newtio)); /*新しいポートの設定の構造体をクリア*/
newtio.c_cflag = (BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD);/*ボー:4800*/
newtio.c_iflag = (IGNPAR | ICRNL);
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);

while (1) {
res = read(fd,buf,512);
buf[--res]=0; j++;
printf("%s\n",buf);
j++;
if(j==12)break; /*NMEAセンテンス12行表示*/
}
tcsetattr(fd, TCSANOW, &oldtio); 
close(fd); 
return(0);
}

実行結果
$GPRMC,081312,A,3428.9433,N,13549.4905,E,0.0,0.0,060808,6.7,W,A*06

$GPRMB,A,,,,,,,,,,,,A,A*

$GPGSA,A,3,03,06,07,,16,,21,,25,27,,,2.3,2.0,1.0*34
・・と12行このようなNMEAセンテンスを表示します。

mio 2008/10/03(金) 17:04:47
ANMEAセンテンスから時刻、緯度、経度のみを出力
二つ目の機能として、必要な部分のみを取り出す機能を、NMEAのうちの時刻緯度経度の
すべてを含んだセンテンスであるGPRMCを模した配列に対して試します。

#include <stdio.h>
#include <string.h>
#include <math.h> 

char buf[512]
="$GPRMC,143514,A,3428.9468,N,13549.4507,E,0.0,196.2,180708,6.7,W,A*05"; /*緯
度経度時刻がすべて含まれているGPRMCからデータを取得したいのでGPRMCのモデルを1行
用意*/

void pnmea(void) {
int utc; 
double lat; /*緯*/
double lon; /*経*/
char ew; /*東西*/
char ns; /*南北*/
char stat; 
char *p; 
/*一行のNMEAセンテンスから時刻緯度経度のみを取り出すために、strtokで”,”ごとの
トークンを得る*/
if ((p=strtok(buf,","))==NULL) return;
if (strcmp(p,"$GPRMC")!=0) return; /*センテンスの先頭は$GPRMCか?*/
if ((p=strtok(NULL,","))==NULL) return;
sscanf(p,"%d",&utc);
if ((p=strtok(NULL,","))==NULL) return; 
stat=p[0];
if ((p=strtok(NULL,","))==NULL) return; /緯度*/
sscanf(p,"%lf",&lat);
if ((p=strtok(NULL,","))==NULL) return;
ns=p[0];
if ((p=strtok(NULL,","))==NULL) return; /*経度*/
sscanf(p,"%lf",&lon);
if ((p=strtok(NULL,","))==NULL) return;
ew=p[0];

if (stat!='A') return;
printf("%02d時%02d分%02d秒(UTC) ",utc/10000, (utc%10000)/100, utc%100);
if (ns=='N') printf("北緯"); else if (ns=='S') printf("南緯");
printf("%.0lf度%.4lf分",floor(lat/100.0), fmod(lat,100.0));
if (ew=='E') printf("東経"); else if (ns=='W') printf("西経");
printf("%.0lf度%.4lf分",floor(lon/100.0), fmod(lon,100.0));
printf("\n");
}
int main(void) {
pnmea();
return 0;
}
A実行結果
14時35分14秒(UTC) 北緯34度28.9468分東経135度49.4507分

επιστημη [HomePage] 2008/10/03(金) 17:06:13
res = read(fd,buf,512);
buf[--res]=0; j++;

ここんとこで res > 0 であることは保証されていますか?
そうでないとbuf[-1]をアクセスしちゃいますが。

mio 2008/10/03(金) 17:06:20
@とAを組み合わせたプログラムBとして、
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<termios.h>
#include<stdio.h>
#include<math.h>

#define BAUDRATE B4800
#define MODEMDEVICE "/dev/ttyS0"

int main()
{
int fd, c, res;
int utc;
char stat;
char ns;
char ew;
char *p;
double lat;
double lon;
struct termios oldtio, newtio;
char buf[512];
FILE *fp_gps;

while(1){
if((fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ))==-1){
perror(MODEMDEVICE);
exit(-1);
}

tcgetattr(fd, &oldtio);
bzero(&newtio, sizeof(newtio));
newtio.c_cflag = (BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD);
newtio.c_iflag = (IGNPAR | ICRNL);
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);

res = read(fd,buf,512);
buf[--res]=0; /* 文字列終端をセット */


if ((p=strtok(buf,","))==NULL);
if (strcmp(p,"$GPRMC")!=0) return; /*センテンスの先頭は$GPRMCか?*/
if ((p=strtok(NULL,","))==NULL); /*utc:世界標準時*/
sscanf(p,"%d",&utc);
if ((p=strtok(NULL,","))==NULL); /*stat:ステータス*/
stat=p[0];
if ((p=strtok(NULL,","))==NULL); /*lat:緯度*/
sscanf(p,"%lf",&lat);
if ((p=strtok(NULL,","))==NULL); /*ns:南北*/
ns=p[0];
if ((p=strtok(NULL,","))==NULL); /*lon:経度*/
sscanf(p,"%lf",&lon);
if ((p=strtok(NULL,","))==NULL); /*ew:東西*/
ew=p[0];

if (ew==p[0]){
fp_gps = fopen("gdata/gdata.dat","w");
if (fp_gps == NULL){
printf("open fp_gps err\n");
}
}
/*時刻緯度経度に対応するトークン表示*/
printf("%02d時%02d分%02d秒(UTC) ",utc/10000+9, (utc%10000)/100, utc%
100);/*utc+9時間は日本標準時*/
fprintf(fp_gps,"%02d時%02d分%02d秒(UTC) ",utc/10000+9, (utc%10000)/100, utc%
100);

if (ns=='N') printf("北緯"); else if (ns=='S') printf("南緯");
printf("%.0lf度%.4lf分",floor(lat/100.0), fmod(lat,100.0));
fprintf(fp_gps,"%.0lf度%.4lf分",floor(lat/100.0), fmod(lat,100.0));

if (ew=='E') printf("東経"); else if (ns=='W') printf("西経");
printf("%.0lf度%.4lf分",floor(lon/100.0), fmod(lon,100.0));
fprintf(fp_gps,"%.0lf度%.4lf分",floor(lon/100.0), fmod(lon,100.0));
printf("\n");

}
tcsetattr(fd, TCSANOW, &oldtio);
fclose(fp_gps);
close(fd);
}

プログラムAのreturn文はすべて消去して、上記のように組み合わせました。
ここで実行を行うと一行目にはその時刻に対応して
16時34分14秒(UTC) 北緯34度28.9468分東経135度49.4507分とAの機能通り表示してくれ
るのですが、その次の行ではセグメンテーション違反です と表示され、そこで結果は
終了してしまいます。
配列の容量に問題があるのでしょうか?
乱文で申し訳ありませんが、ご指摘よろしくお願いいたします。

PATIO 2008/10/03(金) 18:35:51
えーと、どうやって動作確認をしているのでしょう?
gdbを使っているのですか?
それともprintfでコンソール上でしょうか?

上記の説明で「その次の行ではセグメンテーション違反です」とあるのは
次の行が実行されたときの話ですか?
それともコンソール上の次の行の話でしょうか?

もしコンソール上の次の行の話をしているのであれば、
それだけではまだまだ追求が足りないと思います。
処理単位毎にでもprintfを入れて見て何処まで動いてそれが出ているのかを
ちゃんと追いかけないと場所の特定が出来ないと思います。

ソースを見ても見当がつかないのであれば、
必要であれば、printfをバンバン入れてまずは場所の特定が
出来るように調べて見た方が良いと思いますよ。

PATIO 2008/10/03(金) 18:49:35
ソースを貼る時はインデントが保持されるようにして
貼らないと多分わかりにくいです。
スペースかなんかを入れてインデントがわかるようにしてください。
特に長いソースはとても見づらくなります。

ぱっと見変だと思った事。

  if((fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ))==-1){
    perror(MODEMDEVICE);
    exit(-1);
  }

この部分がWhileループの中にあるのに

  fclose(fp_gps);

これは外にある。

これだと毎回毎回、シリアルポートを開きに行きませんか?
しかも、前回のオープンの後始末(fclose)をしていないのに。

しかも毎回、tcgetattrしているから最初に取得したパラメータは喪失している。

readでしくじった時の処理が全く入っていない。

くらいかな。

古いスレッドには追加発言できません。