AndroidアプリなどJava言語と、組込み機器C言語で通信させるとき、せちがらい制約があります。その制約と対応方法の覚書です。
Javaには構造体が無い
古くはVB6(VBA) 、C#、Switf には構造体はありますが Java だけなんで?て感じです。Java側でByte配列へ編集もしくは分解するしかないようです。以下事例です。
組込み側:
/// BTスレーブ要求フレーム ///
typedef struct _ST_SLAVE_REQ {
uint8_t head[ 4 ]; // HERAER兼パケット識別
uint16_t len; // パケット長
uint16_t req; // 要求コード
uint8_t foot[ 4 ]; // FOOTER
uint16_t csum; // CHECKUSUM
} ST_SLAVE_REQ;
// Memo: 実際にはCPUアーキテクチャによってパウンダリ調整なし、2byte調整、4byte調整があるのでよく確認します。
Java側 :
/// BTスレーブ要求フレーム ///
short frmLen = 14;
byte[] buff = new byte[frmLen];
copyBytes(buff, 0, appCom.ID_SLAVE, 4 ); // 4: HERAER兼パケット識別
copyBin16(buff, 4, frmLen ); // 2: パケット長
copyBin16(buff, 6, appCom.REQ_START_MONITER ); // 2: 要求コード
copyBytes(buff, 8, appCom.ID_TAIL, 4 ); // 4: HERAER兼パケット識別
short csum = checksum(buff, frmLen - 2); // Checksum
copyBin16(buff, 12, csum ); // 2: Checksum
Bundle arg = new Bundle();
arg.putByteArray( "BINARY", buff ); // BIN DATA
WriteValueBLE( mServiceCANonNUS, mMessengerCANonNUS, arg );
byteコピー などはメソッドを追加します。web検索すると Unsafe.copyMemory なるものがでてきますが作った方が早い!
///////////////////////////////////////////////////
// Byteコピー
///////////////////////////////////////////////////
public boolean copyBytes(
byte[] to,
int offset,
byte[] from,
int len
) {
if ( from.length < len ) { // ちゃんと実体sizeをちぇっくしましょう。
return false;
}
if ( to.length - offset < len ) {
return false;
}
for( int i = 0; i < len; i ++ ) {
to[ i + offset ] = from[ i ];
}
return true;
}
unit16変換関数を作ります。バイトオーダーは組込み機器側のCPUアーキテクチャに合せます。
///////////////////////////////////////////////////
// int16コピー
///////////////////////////////////////////////////
public boolean copyBin16(
byte[] to,
int offset,
short from
) {
if ( to.length - offset < 2) {
return false;
}
// ARMはリトルエンディアン
to[ 0 + offset ] = (byte)( from % 256 );
to[ 1 + offset ] = (byte)( from / 256 );
return true;
}
Javaにはunsignedが無い
似たような言語では、C#はあり、Switf は無いようです。 (アプリだけ作ってる方々は、組込み機器ではsignedを基本使わないこと自体あまり知られていないのかもしれませんが…)
事例1:
以下の場合、Java側で正しい値になりません。以下例では、frm.len は 4002( 0x9C42) となり、Java側で 0x9C がUnder Flow し -100 となります。そのまま extractBin16 で加算されると66 (0x42) + -100 * 256 = -25,534 と全く異なる値になってしまいます。
組込み側:
/// BTスレーブ応答フレーム ///
typedef struct _ST_SLAVE_RESP {
uint16_t len; // パケット長 6
uint8_t data[40000]; // CAN DATA部
} ST_SLAVE_RESP;
:
前略
:
ST_SLAVE_RESP frm;
frm.len = sizeof(ST_SLAVE_RESP) - sizeof( uint16_t ); // パケット長 ,checksum分引く = 40,002(0x9C42)
:
中略
:
err_code = ble_nus_data_send( /*in*/ &m_nus,
/*in*/ &frm,
/*in-out*/ &sendLen,
/*in*/ m_conn_handle );
Java側:
/// BLEデータの取出し
byte alldata[] = characteristic.getValue();
// パケット長 6
short len = extractBin16(alldata, 4);
:
中略
:
///////////////////////////////////////////////////
// int16取り出し
///////////////////////////////////////////////////
public short extractBin16(
byte[] from,
int offset
) {
short val = 0;
short tmp;
// QualcommはARM系なのでリトルエンディアン、Nordic もARM系なのでリトルエンディアン
val += from[ 0 + offset ]; // 0x42 → 10進 66
val += ( from[ 1 + offset ] * 256 ); // 0x9C → 10進 -100
return val;
}
修正例は以下のとおりです。これは VB6(VBA) も同様で昔はよく引っ掛かっていたものです。
///////////////////////////////////////////////////
// int16取り出し
///////////////////////////////////////////////////
public short extractBin16(
byte[] from,
int offset
) {
short val = 0;
short tmp;
// ARMはリトルエンディアン
tmp = from[ 0 + offset ];
if ( tmp < 0 ) { // Javaはunsignedがないため、1の補数で補完する
tmp = (short)(255 + tmp + 1);
}
val += tmp;
tmp = from[ 1 + offset ];
if ( tmp < 0 ) { // Javaはunsignedがないため、1の補数で補完する
tmp = (short)(255 + tmp + 1);
}
val += ( tmp * 256 );
return val;
}
事例2:
チエックサム計算も同様にくるってしまいます。尚、shortがオーバーフローしたとき、プログラム異常になってしまうのかわかりませんが対策しておきます。
組込み側:
uint16_t CalcCheckSum(
uint8_t *buf, // i : 対象Buffer
int16_t len // i : 対象Buffer長。
) {
uint16_t i;
uint16_t sum;
sum = 0;
for( i = 0; i < len; i ++ ) {
sum += buf[ i ];
}
return sum;
}
Java側:
///////////////////////////////////////////////////
// checksum
///////////////////////////////////////////////////
public short checksum(
byte[] to,
int len
) {
long sum = 0;
short tmp;
for( int i = 0; i < len; i ++ ) {
tmp = (short)to[ i ] ;
if ( tmp < 0 ) { // Javaはunsignedがないため補完する
tmp = (short)(255 + tmp + 1);
}
sum += tmp ;
}
return((short)sum);
}
Stringとuint8[]
組込みC側と送受信する文字列は、Stringでは渡せないです。copyValueOf(char[] data)、valueOf(char[] data) があるようですがコードもかさむんで、Byte[] でよいでしょう。
final byte[] ID_MASTER = {0x42,0x54,0x30,0x32}; // "BT02" MASTERから
final byte[] ID_TAIL = {0x42,0x45,0x4E,0x44}; // "BEND" 終了