プロが教えるわが家の防犯対策術!

C言語とシリアル通信の送受信データの概念

今シリアル通信で基盤上のデータを読み書きするツールを作っています。
基盤はまだ手元にないので、先にプログラムと、基盤の動作を模したプログラムを作るつもりです。
基盤の説明にはデータはバイナリで送るようにと書かれていたので、
文字列で1バイトずつ割り当てようと考えました。
しかし、どうやって1234などの数値を1バイトずつ割り振るのか、
バイナリって何だっけと調べているうちにわけが分からなくなってきました。

たとえば1234という10進数の数値を送りたいとき、
現状ではchar型の文字列"1234"を渡しています。
もしかしてこれは間違っていて、本当は

char s[] = { 1, 2, 3, 4 };
16進数の場合はabcdなら
char s[] = { 0xa, 0xb, 0xc, 0xd };
として渡すのが正しいのでしょうか。
これでprintfを使うと文字化けして何だかバイナリっぽいぞと感じましたが。

また、バイナリ以外ならどんな送り方があるのでしょう?

A 回答 (9件)

まあ、それは、データ形式の名前を気にするより、単に BCD でと言うことですね。



こんな感じで変換できますね。
(データを変換するだけなので、最初に送る 0xa1 はつけていません)

あと、BCD だと、(単純なバイナリ送信と違って) 'A' ~ 'F' に相当するコードは出てきません。
なので、0xa1 のように、「BCD としては存在しない」データがコマンドなどに割り当たっているわけです。

#include <stdio.h>

unsigned char toBCD2(int v)
{
return ( (v / 10) * 0x10 + v % 10);
}

void longToBCD(long src, char bcd[], int keta)
{
int i;
for(i = 0; i < keta; i++) // 桁は、BCD の配列の個数(安直ですが)
{
int work = src % 100;
bcd[i] = toBCD2(work);
src /= 100;
}
}

int main()
{
int i;

char result[5];
longToBCD(0x00201e90, result, 5);
for (i = 0; i < 5; i++)
{
printf("%02X ", result[i]);
}

return 0;
}

この回答への補足

char buf[] = { 0x76, 0x49, 0x10, 0x02, 0x00 };
こうですね…0x00が抜けていました。

補足日時:2010/11/08 15:22
    • good
    • 0
この回答へのお礼

回答ありがとうございます。

printfの%sで見てみると
[0] = 118(76)
[1] = 73(49)
[2] = 16(10)
[3] = 2(02)
こうなってるんですね。ASCII表示したものではなく、カッコ内の数値(16進数表記)を見たいがために%02Xを使う。
そしてこれを手作業でやるのが、他の方々が回答に書かれているように、
char buf[] = { 0x76, 0x49, 0x10, 0x02 };
ということですね。

ASCIIではその規格上、たとえば数字の1は49という値で表すので、
char buf [] = { 49, 50, 51 };
とすれば
printf("123");
と同じになり、
char buf [] = { 118, 73, 16, 2 };
なら、バイナリとして扱うべき0x76,0x49,0x10,0x02のバイト列をASCIIで扱ってしまい、
謎めいた文字列が出る結果になる。

そしてBCD。これは10進数の1桁を2進数で表す。
A~Fが使えないのは、それらが10進数表記だと2桁になり何かが…何かがおかしくなるため。
1010 0001 1010
↓   ↓ ↓
10   1 10
BCDって何?と聞かれると上記のような図しか頭に浮かんでこないのですが、
回答に挙げていただいたコードで、その処理が成されているのですね。

みなさまのおかげでASCIIとバイナリの違いと扱い方、送受信の形式の種類、また通信規則の概念を大分理解できたと思います。ただバイナリの正体が頭の中で解明していないので、後でもうすこしまとめてみます。

お礼日時:2010/11/08 15:17

>00201E90h


>↓
>2104976d
>↓
>A176491002


[スタート|プログラム|アクセサリ]
から電卓を起動

電卓のメニューから 「関数電卓」を選択(XPの場合)

16進を選択し 00201E90 と入力
10進を選択すると、結果は、2104976


2104976 の値に対し、BCD なので
BYTE 配列の中身は、
[0x02][0x10][0x49][0x76]
上位下位を逆転するので
[0x76][0x49][0x10][0x02]
先頭に A1(16進)がつくので
[0xA1][0x76][0x49][0x10][0x02]


LONG値で5バイト必要なのは

LONG値は32Bitと仮定
LONG値の範囲(16進) 0x00000000 ~ 0xFFFFFFFF
10進に変換すると 0 ~ 4294967295

4294967295 を BCDで表記
[0x42][0x94][0x96][0x72][0x95] で5バイト

それを上位下位逆転して送信するのなら
[0x95][0x72][0x96][0x94][0x42]
    • good
    • 0
この回答へのお礼

回答ありがとうございます、遅くなりました。
あれから基盤と接続し、ちゃんとデータのやり取りができて感動しました。
あれからshortやunsigned longなどの型も登場しまして、
同じ-1という入力値でも型によって中身の値は全く異なるということや、それが16進数表現だとどうなるかといったあたりで妙に理解に苦しみました…。

しかし、それも今では安定して動作しています(おそらく)。
この場を借りて皆様にお礼を申し上げます。ありがとうございました。

お礼日時:2011/05/13 20:18

どんな通信でも必ずプロトコルを決めなければなりません。


例えば端末のリターンキー(\n)で一つのセッションが終了というのもプロトコルであるし、ADCが16bit転送で終了というのも簡単ですがプロトコルです。
既存のLSIを使用するのであれば、初めから決まっていますね。

自身でプロトコルを決めるのであれば、それをどう設計するかは技術者の腕です。誤り訂正符号はどうするのか?エンディアンは?データは可変長なのかパケットなのか?などなど。
プロトコルが決まれば後はそれに合わせてプログラミングするだけです。

そういうことでプロトコルの定義か先決です。
    • good
    • 0
この回答へのお礼

すみません。私がこの仕様書を読むのが下手なのか、資料が抜けているのか、
情報が断片的に出てきます。

「通信設定」という見出しの内容には

ボーレート:9600bps
データ長:8ビット
ストップビット:2ビット
パリティビット:なし
モード:バイナリ

とあります。

そしてデータ読み込みの際、読み込むアドレスを変換する例には、

00201E90h

2104976d

A176491002

と書いてあります。他の方へのお礼に書いたように、これをデータ部の5バイト分にも適用させるとのことです。そのあたりがまだモヤモヤしている最中です。

お礼日時:2010/11/05 18:43

余談ですが, char が符号付きか符号なしかは処理系に依存します>#2.

    • good
    • 0

まず、コンピュータの中ではすべてのものは数値であることに気をつけてください。


たとえば、"1234" という文字列は(多くの場合) 0x31 0x32 0x33 0x34 という数値の並びであって、これを、一定の規則で文字と見なしているのに過ぎません。
では、0x31 0x32 0x33 0x34 を送信したのに、"1234" という文字列だと認識できるのはなぜかと言えば、「通信の際に、ASCIIコードという文字コードを使う」という取り決めをしているからです。
「文字コードを使う」という取り決めをしていなければ、0x31 0x32 0x33 0x34 という一連の数値が送られてきたと考えることも可能です。
これは、もしかしたら、0x31323334 = 825373492(10) という数字が送られてきたのかもしれません。
(数値場合、並び順が決められないので、別の解釈になるかもしれません)

さて、「たとえば1234という10進数の数値を送りたいとき、現状ではchar型の文字列"1234"を渡しています」とお書きですが、実際には(人がキーボードをたたくのでない限り)「数値を文字に変換する」というステップが入るはずです。
これを文字変換というステップを踏まずに直接通信に使用するのが「バイナリで送る」という意味です。
たとえば、整数値であれば処理系により、たとえば、4バイトだったり、2バイトだったりのサイズになりますが、このままの形で1バイトずつ送ればいいわけです。

処理系依存なところがありますが、int が4バイト幅なら、

union
{
int word;
char byte[4];
}
data;

に対して、

data.word = 1234;
とした場合、
data.byte[0], data.byte[1], data.byte[2], data.byte[3] の順番で送る(もしくは、その逆で送る)ことで、1234 をバイナリで送ることができます。

バイナリの場合、このようにデータが何バイトなのか? どういう順番で送るのか? その他諸々を決めておく必要があります。また、文字列で送る場合(これを、アスキーモードという場合が多いです)文字コードとして使われてない(データの塊なら、CR とか LF とか。データ自体なら、, とか スペースとか)データの区切りに使えるデータがあって、区切りがわかりやすいですが、バイナリの場合、何でもありです。このため、データの区切りをどうするのかも決める必要があります。

こういったことは、「バイナリで送信」と書かれているからには必ず定義されているので、具体的には、そちらを参照してください。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
なるほどだから基盤の仕様がないと、具体的な部分を答えようがないのですね。

電文フォーマットは
先頭に16進数表記のコマンドコード:読み込みはA1、書き込みはB1
次に4バイト分のアドレス用の領域があり、
ここはBCDにして上下の並びを逆にするとあります。
書き込みの場合はその後、5バイト分のデータ領域があります。
これも同じように10進数に直して並び替え、Long型では最大で5バイト分必要であるために5バイトだそうです。

読み込みの場合は5バイト、書き込みの場合は10バイト必要ということです。

では書き込みの場合、

unsigned char buf[10];

buf[0] = 0xB1;

となり、次に16進数表記のアドレスをBCDに・・・
何か

[52(0x34)],[18(0x12)]
これだと2バイトですね。コマンドコード表にBCDデータとして使用と書かれているので、実際のバイナリ部分はWBCDかも知れませんが記述が見当たりません・・・。

それ以前にデータ部分に関しては5バイト必要なので、
これはWBCDにも当てはらまないのですか・・・?

お礼日時:2010/11/05 18:28

1234 を送る場合に考えられるパターン、


ASCII 形式、
[49(0x31)],[50(0x32)],[51(0x33)],[52(0x34)] = ASCIIコードを送る場合
[51(0x33)],[52(0x34)],[49(0x31)],[50(0x32)] = 上位下位が逆の場合

以下はバイナリ形式
BCD形式、[18(0x12)],[52(0x34)] = 16進で A~F を使わずに表現
BCD形式、[52(0x34)],[18(0x12)] = 上位下位が逆の場合
BIN形式、[0x04][0xD2] = 16 進で 1234 は 0x4D2 なので
BIN形式、[0xD2][0x04] = 上位下位が逆の場合

WBCD形式、[18(0x12)],[52(0x34)],[0],[0] = 先のBCDの4バイト単位バージョン
WBCD形式、[0],[0],[18(0x12)],[52(0x34)] = さらに上位下位が逆
WBCD形式、[52(0x34)],[18(0x12)],[0],[0]
WBCD形式、[0],[0],[52(0x34)],[18(0x12)]
WBIN形式、[0x04][0xD2],[0],[0] = 先の BIN形式の4バイト単位バージョン
WBIN形式、[0],[0],[0x04][0xD2] = さらに上位下位が逆
WBIN形式、[0xD2][0x04],[0],[0]
WBIN形式、[0],[0],[0xD2][0x04]

基本的にASCIIだと4バイト必ず必要だがバイナリにすると2バイトで
済む為、基本的に、[1(0x01)],[2(0x01)],[3(0x01)],[4(0x01)] は無いと思うが
[1(0x01)],[2(0x01)],[3(0x01)],[4(0x01)] のパターンと
[3(0x01)],[4(0x01)],[1(0x01)],[2(0x01)] 上位下位を逆にしたこのパターンもありうる
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
今ひとつ気づいたことは、私は手元の仕様書から、送受信のプロトコル部分が把握できていないということです。

・BCDデータとして使用
・下位と上位を逆にする

という仕様を発見しました。
BCD形式、[52(0x34)],[18(0x12)] = 上位下位が逆の場合
上記にあてはまりますね!

お礼日時:2010/11/05 17:54

No.1さんの意見はごもっともと思います。


ただ、それで終わりは寂しいので……(^^;
なので、自分なりに回答させて頂きます。

まず、シリアル通信というよりも、バイナリとテキストの
違いをしっかり把握した方がいいと思います。
かなり乱暴な説明かもしれませんが、わかりやすさを
最優先でお話させて頂きます。妙な説明もあるかもですが、
そこはどうか寛大に見てやって下さい。


例えばメモ帳を開いて、200 とキーボードから入力、保存。
このファイルには "200" という文字列が保存されますよね。
ですが、これはテキストとして考えるから "200"という文字列と
解釈できるわけで、バイナリとして見たら別の話になります。
バイナリデータとしてバイト単位で解釈すれば、

'2' は 50(0x32)
'0' は 48(0x30)

……となります。※詳細はASCII文字コード表をご覧下さい
この事は、バイナリエディタでファイルを見れば確認できます。

メモ帳で 200 とテキストが表示されるのは、対象データを
「テキストとして扱うから」なのです。改行コード等も、
テキストならではの概念であり、バイナリとして見れば
ただの数値、単なるbyteの羅列でしかありませんよね。

で、メモ帳は、それらのbyteデータを適切なキャラクタに変換、
表示しているわけです。そのように、文字コードに沿って
キャラクタ変換する事を「エンコード」と言います。
(EUCとかシフトJISとか、耳にした事があると思います)


さて、上記を踏まえた上で「シリアル通信時にバイナリデータで
送る」話です。バイナリで送るので、1234という値を送る場合、
"1234"という文字列を送ってはいけません。

1234 = 0x04D2

なので、

char buf[2]={0x04, 0xd2};

を送る事になります。この例は2byteを1つの数値と考えた
場合ですが、場合によって4byteかもしれませんし様々です。
全ては電文仕様に依存しますので、プロトコル仕様書を
見る他ありません。
※上記は単純な例です。実際には、上位・下位byteの
指定があるかと思いますが、それは仕様書を参照して下さい。

実際に作り込む場合には、プロトコル仕様は必須です。
普通はカッチリと電文フォーマットが規定されています。
でないと通信が成り立たないので(^^;
プロトコル仕様に沿ってバイト配列を整形(つまり電文を
作って)それを送受信する事になります。

なお、「もしバイナリ以外なら?」という話については、
自分はよくテキストによる通信を作ったりしますね。
これも結局はプロトコル仕様書に「テキストでよろしく」
と書かれていたら従っているだけです。いずれにせよ、
ハード依存となる部分ですので……。
    • good
    • 0

具体的な送受信の仕方はOS,シリアルポートの仕様,相手側基盤とのプロトコルに依存しますので書くことができませんが・・・



C言語の「文字列」とは、NULL(=0x00)が終端です。
文字列中にNULLが現れた時点で、文字列の終わりとみなされます。

通信相手の基盤がバイナリでの通信を求めているのならNULLも送受信するべきデータの1つになる可能性があります。
つまり、C言語の文字列操作関数(printf,sprintf,strcpy…)は一切使えません。
「文字列で…」なにかする考え方は捨てましょう。トラブルの元です。

10進数の1234を送る場合、1234=0x04D2ですので、0x04と0xD2を1バイトずつ送ることになります。
値をあらかじめ配列に入れておきたいなら以下です。

unsigned char s[2] = { 0x04, 0xD2 };

※char型のデフォルトはsigned(符号付き)になりますが、signedだと何らかの演算をした場合に符号ビットが反転して何かとトラブルの元になります。データ通信に使う変数はunsignedで宣言することをオススメします。

もしデバッグ用に画面表示させたいなら printf( "%02X%02X\n", s[0], s[1] ); などとして16進数文字列で表示させましょう。
    • good
    • 0

「データをどのような形式で渡せばいいのか」はその「基盤」に依存します. つまり, この質問に対して答えることは不可能.

    • good
    • 0
この回答へのお礼

回答ありがとうございます。
答えるのが不可能な質問でありながら、皆様からご回答をいただいて感謝の気持ちでいっぱいです。
今からじっくり読んで理解を深めると共に、質問内容で何が悪かったのかを反省します。

お礼日時:2010/11/05 17:36

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!