まずはC#でデータをためていく方法について考えましょう。
素朴にstringに加えていきます。
class Program {
public static void Main() {
string str = "";
str += "abc";
str += "def";
System.Console.WriteLine(str);
}
}
C/C++から考えると恐ろしいですが、実際には+=は末尾に付け加えるメソッドになっています。
ところが文字列でないデータ列の場合、これでは読み出せません。なぜならば、
str[n]としたところでn文字目が読み出されるからです。やはり8bitごと区切られたデータが欲しい。
それではListを使いましょうか?
using System.Collections.Generic;
class Program {
public static void Main() {
List<byte> data = new List<byte>();
data.Add(0x10);
data.Add(0x20);
data.Add(0x30);
data.Add(0x40);
foreach(byte value in data) {
System.Console.WriteLine(value);
}
}
}
うむ……これでは配列が欲しいとき困る。
そんなときにMemoryStreamです。可変長で、好きなときにWriteで書き込みできます。(内部の実装はお察し)
データをためて行くにはWriteしていけばよいです。
using System.IO;
class Program {
public static void Main() {
MemoryStream ms = new MemoryStream();
ms.Write(new byte[] { 0x10 }, 0, 1);
ms.Write(new byte[] { 0x20 }, 0, 1);
ms.Write(new byte[] { 0x30, 0x40 }, 0, 2);
byte[] data = ms.ToArray();
foreach(byte value in data) {
System.Console.WriteLine(value);
}
}
}
さて本題。今、0byte目, 1byte目, 2byte目に意味のある数字が並んでおり、それぞれa, b, cと名付けましょう。
そして、0, 2byte目は8bitの非負整数、1byte目は16bitの非負整数であるとします。与えられたストリーム(byte列としましょう)からこれらに振り分けるにはどうしたらいいでしょうか?
力尽くで、if文など駆使して書けるでしょうが、d, e, f, ...と続く場合は見づらいコードになります。
ここはC/C++のような構造体を使ってシンプルに書きたいものです。が、C#にはわかりやすい書き方はありません。
正確に言うと余り推奨していないやり方なのでしょう。
以下の例は構造体にオフセットを用いて各データを並べています。
これはごく普通のやり方なのですが、System.Runtime.InteropServices.StructLayout()で属性を設定します。
LayoutKind.Explicitを指定することでFieldOffsetにより構造体の位置を指定します。
先頭が0byte目で、例えば[FieldOffset(0)]の次に来るpublic なんちゃらは、0byte目のメモリ空間を使うことになります。[FieldOffset(1)]ならば次に来るpublicなんちゃらは1byte目のメモリ空間を使うことになります。
おなじ[FieldOffset(x)]を設定したら、それらはメモリの開始位置を共有します。
byteなら1byte, shortなら2byte, intなら4byteのように領域が決まっていますが、
例えばbyte, short, intの3つのフィールドを[FieldOffset(0)]で宣言したとき、
Bxxx
SSxx
IIII
のように、共有します。即ち、byteのデータはshortの最下位バイトと共有し、intの最下位バイトとも共有するわけです。先頭なのに何で最下位か? これはリトルエンディアンと呼ばれる方式で、
0x11223344という32bitの整数は「0x44」「0x33」「0x22」「0x11」の順番でメモリに格納されます。
従って共有されるのは最下位バイトなのです。
さて、実際に宣言してみると問題が発生します。
というのも、C#のbyte配列というものはオブジェクトであって、メモリをリニアーに配置した値型ではないのです。簡単に言うとポインタであってメモリ領域ではないのです。C/C++では構造体は配列と同じくデータ領域でしかなかったため柔軟に対応できました。
この問題を解決するためにC#では固定長配列が宣言できます。ただしunsafeで。ぶっちゃけunsafeなんか使うくらいならC/C++使った方がいいと思うんですけど(外部ライブラリ作ってでもいい)、やってみました。
fixed 基本型名 変数名[データ数]
で固定長が宣言できます。
using System.Runtime.InteropServices;
class Program {
[StructLayout(LayoutKind.Explicit)]
unsafe struct S {
[FieldOffset(0)]
public fixed byte data[4];
[FieldOffset(0)]
public byte a;
[FieldOffset(1)]
public ushort b;
[FieldOffset(3)]
public byte c;
}
public static void Main() {
S s = new S();
unsafe {
byte[] data = new byte[] { 1, 2, 3, 4 };
int i = 0;
foreach(byte d in data) {
s.data[i] = data[i];
i++;
}
}
System.Console.WriteLine("{0:X}, {1:X}, {2:X}", s.a, s.b, s.c);
}
}
もっといい方法はないかね。
2013/11/26
2013/11/25
C#Tips6 反復子を使おう
Tips1でforeachで反復できる実装の仕方を紹介しました。
要は、foreach(★ in ●)に対して、●を返すためにSystem.Collections.IEnumerableインターフェースを実装して、GetEnumerator()によって取得できるようにしました。また、★にデータを入れるためにSystem.Collections.IEnumeratorインターフェースを実装しました。
ところでC#にも最近流行りの反復子と呼ばれる機構が備わっています。
次の例では
10
20
30
と表示されます。
反復子を実装するメソッドには戻り値としてSystem.Collections.IEnumerableインターフェース型を返します。
反復子はyield returnで、yield単体では意味を持ちません。従ってyieldという変数名は使用可能です。(しない方がいいです)
yield returnがあらわれると、値を返して、メソッド内の処理を一時中断します。
そして再びforeachによって内部でGetEnumerator()が呼ばれると前の処理から再開して次のyield returnまで実行されます。
要は、foreach(★ in ●)に対して、●を返すためにSystem.Collections.IEnumerableインターフェースを実装して、GetEnumerator()によって取得できるようにしました。また、★にデータを入れるためにSystem.Collections.IEnumeratorインターフェースを実装しました。
ところでC#にも最近流行りの反復子と呼ばれる機構が備わっています。
次の例では
10
20
30
と表示されます。
反復子を実装するメソッドには戻り値としてSystem.Collections.IEnumerableインターフェース型を返します。
反復子はyield returnで、yield単体では意味を持ちません。従ってyieldという変数名は使用可能です。(しない方がいいです)
yield returnがあらわれると、値を返して、メソッド内の処理を一時中断します。
そして再びforeachによって内部でGetEnumerator()が呼ばれると前の処理から再開して次のyield returnまで実行されます。
2013/11/14
C# Tips3 C#からC++を実行しよう
1. C++/CLIとC#の連携
C++/CLIとは、.NET上で実行するプログラムを作るために拡張した言語です。
ためしにC++/CLIでクラスを定義し、C#から呼び出してみましょう。
(1) 新しいプロジェクトを選ぶ
(2) Visual C++のなかのCLRを選び、クラスライブラリを選択。
(3) 名前を決め、OKを押す。
(4) test.cとtest.hは消して構いませんが、他は弄らない方がいいです。
まずはtest.h内に次のように書きます。注意点はpublicとrefを付ける点です。
namespace test {
public ref class Test {
public:
int x;
Test(int x);
int getX();
};
}
test.cpp内には次のように書きます。
#include "stdafx.h"
#include "test.h"
using namespace test;
using namespace std;
Test::Test(int x) {
this->x = x;
}
int Test::getX() {
return x;
}
(5) ビルドします。
(6) 新しいプロジェクトでC#のプロジェクトを作ります。
(7) 参照に(5)でできたdllファイルを追加します。/test/Debug/test.dllにあるはずです。
(8) 次のようにしてテストします。
using System;
using test;
class Program {
public static void Main() {
Test t = new Test(10);
Console.WriteLine("{0}", t.getX());
}
}
C++/CLIとは、.NET上で実行するプログラムを作るために拡張した言語です。
ためしにC++/CLIでクラスを定義し、C#から呼び出してみましょう。
(1) 新しいプロジェクトを選ぶ
(2) Visual C++のなかのCLRを選び、クラスライブラリを選択。
(3) 名前を決め、OKを押す。
(4) test.cとtest.hは消して構いませんが、他は弄らない方がいいです。
まずはtest.h内に次のように書きます。注意点はpublicとrefを付ける点です。
namespace test {
public ref class Test {
public:
int x;
Test(int x);
int getX();
};
}
test.cpp内には次のように書きます。
#include "stdafx.h"
#include "test.h"
using namespace test;
using namespace std;
Test::Test(int x) {
this->x = x;
}
int Test::getX() {
return x;
}
(5) ビルドします。
(6) 新しいプロジェクトでC#のプロジェクトを作ります。
(7) 参照に(5)でできたdllファイルを追加します。/test/Debug/test.dllにあるはずです。
(8) 次のようにしてテストします。
using System;
using test;
class Program {
public static void Main() {
Test t = new Test(10);
Console.WriteLine("{0}", t.getX());
}
}
2013/11/13
Cコンパイラの罠2
●機能レジスタのアドレスをポインタに
次にポインタにTRISAなどの機能レジスタのアドレスを代入してみました。
char* p = &PORTA;
*p |= (0x01 << 1);
のコンパイル結果は次の通りです。
movlw high(12)
movwf (main@p+1)
movlw low(12)
movwf (main@p)
movf (main@p),w
movwf fsr1l
movf (main@p+1),w
movwf fsr1h
bsf indf1+(1/8),(1)&7
(9サイクル)
これはTRISAの番地12のhighである0とlowである0x0cを取得してmain@pラベルの位置に書き込みます。
次にそのメモリの位置からFSR1L, FSR1Hレジスタにそれぞれ書き込み、INDF1レジスタから値を受け取ります。
(1/8)の意味するところは分かりません。bsf INDF1, 1によってビットの1番目をセットしています。
コンパイラのデータシートに書いてあるとおり、ビットのセット/クリアは次のように書けばビット命令でコンパイルしてくれます。
var |= (0x01 << N);
var &= ~(0x01 << N);
*pの指すバンクは事前に分かるわけですから、人間がアセンブリを書くとすれば次のように最適化されるでしょう。
movlb 0x00 ; バンクを0に
movlw 0x0c ; バンクの中のアドレス0x0cを
movwf (main@p) ; main@pラベルの位置に書き込み
movlb 0x00 ; バンクを0に
bsf (main@p), 1 ; main@pラベルのメモリにあるデータの1bit目をset
(5サイクル)
これはフリー版ですから仕方がないのですけどね。コンパイラ作りたくなってきますね。。。
次にポインタにTRISAなどの機能レジスタのアドレスを代入してみました。
char* p = &PORTA;
*p |= (0x01 << 1);
のコンパイル結果は次の通りです。
movlw high(12)
movwf (main@p+1)
movlw low(12)
movwf (main@p)
movf (main@p),w
movwf fsr1l
movf (main@p+1),w
movwf fsr1h
bsf indf1+(1/8),(1)&7
(9サイクル)
これはTRISAの番地12のhighである0とlowである0x0cを取得してmain@pラベルの位置に書き込みます。
次にそのメモリの位置からFSR1L, FSR1Hレジスタにそれぞれ書き込み、INDF1レジスタから値を受け取ります。
(1/8)の意味するところは分かりません。bsf INDF1, 1によってビットの1番目をセットしています。
コンパイラのデータシートに書いてあるとおり、ビットのセット/クリアは次のように書けばビット命令でコンパイルしてくれます。
var |= (0x01 << N);
var &= ~(0x01 << N);
*pの指すバンクは事前に分かるわけですから、人間がアセンブリを書くとすれば次のように最適化されるでしょう。
movlb 0x00 ; バンクを0に
movlw 0x0c ; バンクの中のアドレス0x0cを
movwf (main@p) ; main@pラベルの位置に書き込み
movlb 0x00 ; バンクを0に
bsf (main@p), 1 ; main@pラベルのメモリにあるデータの1bit目をset
(5サイクル)
これはフリー版ですから仕方がないのですけどね。コンパイラ作りたくなってきますね。。。
XC8コンパイラの罠
1wireのライブラリでも作ろうと思ってstructを隠蔽しました。C言語ではよく使われるテクニックで、
ヘッダーファイルtest.h内で次のように宣言します。
struct _Test; // 構造体の中は宣言しない
typedef struct _Test Test; // Testでクラスのように使えるようにする
Test* Test_initialize(char n); // コンストラクタ
char Test_get_n(Test* self, void); // nを取得
あとはtest.cで実体を宣言します。
struct _Test {
char n;
}
Test* Test_initialize(char n) {
// メモリ確保処理が終わってTest* testが割り当てられたとする
test->n = n;
return test;
}
char Test_get_n(Test* self, void) {
return self->n;
}
ところがこれはコンパイルが通りません。(メモリ確保処理は別として)どうやらXC8コンパイラでは隠蔽はできないようです。VC++ではできることを確認しました。
onewireについては後で書きます。
ヘッダーファイルtest.h内で次のように宣言します。
struct _Test; // 構造体の中は宣言しない
typedef struct _Test Test; // Testでクラスのように使えるようにする
Test* Test_initialize(char n); // コンストラクタ
char Test_get_n(Test* self, void); // nを取得
あとはtest.cで実体を宣言します。
struct _Test {
char n;
}
Test* Test_initialize(char n) {
// メモリ確保処理が終わってTest* testが割り当てられたとする
test->n = n;
return test;
}
char Test_get_n(Test* self, void) {
return self->n;
}
ところがこれはコンパイルが通りません。(メモリ確保処理は別として)どうやらXC8コンパイラでは隠蔽はできないようです。VC++ではできることを確認しました。
onewireについては後で書きます。
2013/11/11
C# Tips2 TCP/IP通信 サーバー編
こんにちは、Kです。今日はC#で非同期のTCP/IP通信を刷るプログラムを考えてみましょう。
1. IPアドレスの変換
文字列からIPAddressクラスに変換するのに、最初次のようなコードを書いていましたが、
そういえばC#では、文字列からインスタンスを生成するのにParseという名前のメソッドを使うのが一般的でしたね。逆はToStringです。
ローカルのホスト名をDns.GetHostName();で取得できますが、これで取得するとIPv6のものまで含まれてしまい、IPv4で指定した通信には使えません。そのため、ipa.AddressFamily == AddressFamily.InterNetworkと比較することにより、次のように取得しています。
1. IPアドレスの変換
文字列からIPAddressクラスに変換するのに、最初次のようなコードを書いていましたが、
string[] ipaddress_strings = host.Split('.'); byte[] apaddress_bytes = new byte[4]; for(int i = 0; i < ipaddress_strings.Length; i++) { apaddress_bytes[i] = ipaddress_strings.Parse(ipr[i]); } var ipaddress = new System.Net.IPAddress(apaddress_bytes);IPAddressクラスをリファレンスで探したところ、「Parse〔IP アドレス文字列を IPAddress インスタンスに変換します。〕」というメソッドがあることが分かりました。
そういえばC#では、文字列からインスタンスを生成するのにParseという名前のメソッドを使うのが一般的でしたね。逆はToStringです。
ローカルのホスト名をDns.GetHostName();で取得できますが、これで取得するとIPv6のものまで含まれてしまい、IPv4で指定した通信には使えません。そのため、ipa.AddressFamily == AddressFamily.InterNetworkと比較することにより、次のように取得しています。
foreach(IPAddress ipa in Dns.GetHostAddresses(host)) { if(ipa.AddressFamily == AddressFamily.InterNetwork) { ipaddress = ipa; break; } }ただし、これでは最初のIPアドレスしか取得していないことに注意が必要です。
2013/11/09
2013/11/07
C# Tips1 foreachの活用
リフレッシュスペース(部室)が使えないので、駄文を書いていきます。1年生の参考になるといいな。
コンピュータなんてものは詰まるところ、条件分岐とgotoの塊なんです。決められたとおりにしか動きませんし、決められたことなら何でもできます。プログラミング言語ではif, while(for)が使えればつまるところ何でもできますが、それはとても見づらいプログラムなんですね。
例えばコントローラーを操作するプログラムを考えましょう。 コントローラーの値を取得するために、for文で取得するとします。
コンピュータなんてものは詰まるところ、条件分岐とgotoの塊なんです。決められたとおりにしか動きませんし、決められたことなら何でもできます。プログラミング言語ではif, while(for)が使えればつまるところ何でもできますが、それはとても見づらいプログラムなんですね。
例えばコントローラーを操作するプログラムを考えましょう。 コントローラーの値を取得するために、for文で取得するとします。
「定例会は定期的にやるから定例なんだよ」、おめでとうございます
どうも、Dです。
まずは無事にシリアルサーボが動いたようで何よりです。おめでとうございます。
ふとした思い付きでロボをつくろうと思って、LPC1114のボードを製作・・・。
既に持ってるLPC1768-mbed-はすでに別のロボに載せる予定なのとLPC1114をサブCPUとして使うつもりだったのでいい機会だったのですが、ちょっとでかい( ゚Д゚)が、まあ今回の用途には問題ないからこのままGO.
安くて、何かいろいろ入ってて、mbedと同環境でライタ使わず開発できるのは、こっちに本腰入れる気が無い身にとっては、やっぱり楽。
この後はアルミとでも戯れるか。
机の上は綺麗にしよう、、パソコンが使えん
おれってばかだな
2時間で片を付けると言ってから何時間たつだろうか、晴れていたはずの空も今は雨模様。外に干しっぱなしの可愛いバスタオルも今はどうなっていることやら。Kです。
未だにシリアルサーボが動かせないでいろいろ試していました。うごいたりうごかなかったりするんだよなー。3byte送って3byte受信するから、その切り替えがうまくいっていないのかなーとか値を変えつつ送ってみるも、だめ。
そこで私は送ったデータと動作の可否をExcelに書いた。
あらやだ、ビットの数が性格に偶数でないと反応しないわ!
これはパリティのせいだと思うじゃないですか。
私はひたすらパリティをいじくった。
パリティの値を固定にしてオシロスコープを眺めてみたり、
パリティの値を反転させてみたり、
3byte目のパリティを2byte目のパリティをxorしてみたり、
毎回TX9をセットしてみたり、TX9Dをクリアしてから書き込んだり、
TXSTAを直接いじったり……・。どれも当然ながらうまくいかない。
私はUSBアダプタの信号外床が違うのかを知るためにオシロスコープを眺めた。
オシロスコープは信号の波形をしっかり記録してくれた。
確かに何か違う。データは当然同じものを送っているはずなのに、末尾が違う。パリティか? 私はこのオシロスコープの出力から送られているデータを読み取った。ストップビットがやけに長いな……。気づいてしまった。
ストップビットの長さが違っている。そこで次のようにして送信が全て完了するまで待機するようにした。
while(!TXIF);
TXREG = data;
while(!TRMT); // 送信が終わるまで待つ
TRMTは送信が全て完了したときに1になり、バッファが残っているときは0となるフラグである。TXIFはTXREGに格納できる状態の時に1, そうでなければ0となるフラグである。
TXIF, TRMT = 1のとき送信できるようにすればいいので、次のようにしてよい。
while(!(TXIF && TRMT));
TXREG = data;
受信は、送信が完了した後で有効にする必要がある。これはTXとRXを短絡しているためで、送信データを受信と見なさないようにするためである。
// 受信許可・送信不可
while(!TRMT); // 送信が全て終わるまで待つ
TXEN = 0;
CREN = 1;
for(i = 0; i < 3; i++) {
while(!RCIF);
// データの取得
g_response.data[i] = RCREG;
RCIF = 0;
}
CREN = 0;
最後に連続した3byteずつの動作を送信・受信の流れに振り分けるために
for(buff = i2c->buff, end_buff = buff + i2c->buff_size; buff < end_buff; buff += 3) {
sendSerialServoData(buff);
}
として無事動作した。めでたしめでたし。
ああ、雨が降っていて帰れない
追記
部室に置いてあった傘を借りました。今日返します。あとラベル付け忘れたので付けておきました。
未だにシリアルサーボが動かせないでいろいろ試していました。うごいたりうごかなかったりするんだよなー。3byte送って3byte受信するから、その切り替えがうまくいっていないのかなーとか値を変えつつ送ってみるも、だめ。
そこで私は送ったデータと動作の可否をExcelに書いた。
あらやだ、ビットの数が性格に偶数でないと反応しないわ!
これはパリティのせいだと思うじゃないですか。
私はひたすらパリティをいじくった。
パリティの値を固定にしてオシロスコープを眺めてみたり、
パリティの値を反転させてみたり、
3byte目のパリティを2byte目のパリティをxorしてみたり、
毎回TX9をセットしてみたり、TX9Dをクリアしてから書き込んだり、
TXSTAを直接いじったり……・。どれも当然ながらうまくいかない。
私はUSBアダプタの信号外床が違うのかを知るためにオシロスコープを眺めた。
オシロスコープは信号の波形をしっかり記録してくれた。
図a : PICの送ったデータ
図b : USBアダプタが送ったデータ
ストップビットの長さが違っている。そこで次のようにして送信が全て完了するまで待機するようにした。
while(!TXIF);
TXREG = data;
while(!TRMT); // 送信が終わるまで待つ
TRMTは送信が全て完了したときに1になり、バッファが残っているときは0となるフラグである。TXIFはTXREGに格納できる状態の時に1, そうでなければ0となるフラグである。
TXIF, TRMT = 1のとき送信できるようにすればいいので、次のようにしてよい。
while(!(TXIF && TRMT));
TXREG = data;
受信は、送信が完了した後で有効にする必要がある。これはTXとRXを短絡しているためで、送信データを受信と見なさないようにするためである。
// 受信許可・送信不可
while(!TRMT); // 送信が全て終わるまで待つ
TXEN = 0;
CREN = 1;
for(i = 0; i < 3; i++) {
while(!RCIF);
// データの取得
g_response.data[i] = RCREG;
RCIF = 0;
}
CREN = 0;
最後に連続した3byteずつの動作を送信・受信の流れに振り分けるために
for(buff = i2c->buff, end_buff = buff + i2c->buff_size; buff < end_buff; buff += 3) {
sendSerialServoData(buff);
}
として無事動作した。めでたしめでたし。
ああ、雨が降っていて帰れない
追記
部室に置いてあった傘を借りました。今日返します。あとラベル付け忘れたので付けておきました。
登録:
投稿 (Atom)