« 2020年4月 | トップページ | 2020年7月 »

2020.05.12

Visual C#に対応したUSBテストプログラムを公開しました

Artix-7ボードのページの中で、Visual C#に対応したUSBテストプログラムを公開しました。

ボードをお持ちの型はソースコードもダウンロード可能です。

http://www.tokudenkairo.co.jp/art7/usbddrtest.html

1589245930_usbddrtest_1

| | コメント (0)

2020.05.11

C#で配列のキャストを行うには

大きな配列に何かのデータを詰めたり、比較したりするときに、より広いデータ幅で比較したり代入したほうが高速に実行できると考えられます。

CやC++ならば、

void hoge(unsigned char *received_data) {
unsigned long *buffer = (unsigned long *)received_data;
・・・・
}

というふうにキャストを使うことで、配列を1バイト単位でアクセスしたり4バイト単位でアクセスしたり、自由自在なサイズでアクセスできました。(ここではアラインメントが合わない場合とかは考えない)

ところが、これをC#でやろうとすると難しくなります。

やりたいことはByte[]型の配列に乱数を詰めたいだけなのですが、乱数は32bit単位で生成されてくるので、4バイト単位で詰められれば4倍速くなるだろうということです。

試行錯誤の末、以下のようになりました。

public void FillArray (Byte[] buffer, int bufSize)
{
    unsafe // この中ではポインタが使えるよ
    {
        fixed (byte* pb = buffer) // bufferがGCされないようにする
        {
        UInt32* p = (UInt32*)pb; // byte型ポインタをUInt32型ポインタに変換
        for (int i = 0; i < bufSize / 4; i++)
        {
        UInt32 t = x ^ (x << 11); // XorShiftという乱数生成アルゴリズム
            x = y; y = z; z = w;
            w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
            p[i] = w;
        }
}
}
}

このコードはbufferというByte[]型の配列に乱数を詰めるというものですがUInt32型のポインタに変換して4バイト単位で詰めていきます。

unsafeやfixedを使うのはいいのですが、fixedではポインタの型までは変えられないようなので、fixedではbyte型のポインタにして次の分でキャストしています。

実際に本当に速くなるのか試してみたら、256MByteの配列を埋めるのに、

  • 1バイト単位の場合
    • Debugビルド・・・1270ms
    • Releaseビルド・・・599ms
  • 4バイト単位の場合
    • Debugビルド・・・320ms
    • Releaseビルド・・・143ms

と、ちょうど4倍速くなっていました。

 

次に、USBでFPGAに送って戻ってきたデータをコンペアする関数を作りたいと思います。

C#で配列の中身を比較するのは、Enumerable.SequenceEqualというメソッドを使うようなのですが、これが遅い。256MByteのデータを比較するのに3200msほどかかっています。ReleaseビルドでもDebugビルドでもあまり差はありません。

unsafeとfixedの中でfor文を回して、8バイト単位で比較するプログラムを作ってみました。

// 8バイト単位で調べて、一致していればtrue、一致していなければfalseを返す
unsafe private bool UnsafeMemEqual8(byte[] s1, byte[] s2)
{
fixed (byte* pb1 = s1)
{
fixed (byte* pb2 = s2)
{
UInt64* p1 = (UInt64*)pb1;
UInt64* p2 = (UInt64*)pb2;
int len = Math.Min(s1.Length, s2.Length) / 8;
for(int i=0;i<len;i++)
{
if (*p1++ != *p2++) return false;
}
}
return true;
}
}

この関数の結果は、

  • Debugビルド・・・108ms
  • Releaseビルド・・・48ms

と、Enumerable.SequenceEqualの3200msに比べて圧倒的に高速でした。

もしかすると、Enumerable.SequenceEqualは高度な関数なので、配列の比較のような単純なことに使うべきではないのかもしれません。

 

最後にC#で作ったUSB3.0 DDR3テストプログラムと、Borland C++ Builder 6版での実行時間をまとめます。

コンパイラ Visual C# 2019 (x64) Borland C++ Builder 6
ビルド Debug Release Debug Release
乱数データ生成 320 143 224 147
USB3.0 送信 803
806 787 788
USB3.0 受信 745 749 728 728
データ比較 108 48 112 114
合計 1976 1746 1851 1777

※数字は[ms]
※データサイズは256MByte

いろいろ試してきましたが、unsafeとfixedを使うと、配列に(ガベージコレクトされないという意味で)安全に高速アクセスできることがわかりました。

実行時間もほとんど差がありません。

ハードウェアにアクセスするDLLを使ったり画像を生成したり、C言語と同じようにアクセスできることがわかったので、これからは組み込みのユーザインタフェースプログラムはC#で作ろうかなと思います。

Cs_usb3

Cs_usb3_2

 

| | コメント (0)

2020.05.10

C#で高速にビットマップ画像を描く方法

FPGAを使ったカメラなどを作る場合、USBやイーサから受信してきたデータは配列に入っているので、これを画像として表示したいわけですが、C#で高速にやる方法がわかりましたのでここに書きます。

まず、Formに表示させたい領域にPictureBoxを貼り付けます。

一番safeで正攻法なやり方は、

Bitmap img = new Bitmap(pictureBox1.Width, pictureBox1.Height);

でBitmap型のオブジェクトを作り、

for(int y = 0; y < pictureBox1.Height; y++) {
for(int x = 0; y < pictureBox1.Width; x++) {
int val = buffer[y * pictureBox1.Width + x];
img.SetPixel(x, y, Color.FromArgb(val));
}
}

でポチポチと点を打っていく方法です。当然ながら遅すぎます。

高速化するには、

  • PictureBoxとは別に、メモリ内にBitmapを作る
  • Bitmap内の画像バッファのポインタを得て、書き込む
  • PictureBoxに貼り付ける

というやり方をします。ダブルバッファです。

Formのコンストラクタなどで

private Bitmap bmp;

を宣言したら、コンストラクタなどでBitmapを作っておきます。

bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);

実際の描画ではbmpData.Scan0というプロパティを使って、画像データ配列の先頭のポインタを得て、そのポインタに対して書き込みをします。ここでもunsafeが大活躍します。

public void Draw(byte[] buffer)
{
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly, bmpPixelFormat);
private Rectangle rect = new Rectangle(Point.Empty, bmp.Size);
private System.Drawing.Imaging.PixelFormat bmpPixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppRgb;
IntPtr ptr = bmpData.Scan0;
unsafe
{
int* dst = (int*)ptr.ToPointer();
for (int i = 0; i < pictureBox1.Width * pictureBox1.Height; i++)
{
if (i >= buffer.Length) break;
byte val = buffer[i];
*dst++ = (val & 0xff) << 16 | (val & 0xff) << 8 | (val & 0xff);
}
pictureBox1.Image = bmp;
}
bmp.UnlockBits(bmpData);
}

unsafeを使うとだいぶんCっぽく書けるようになりますね。それに、ポインタの実体に整数を入れるという素直な代入によって、Color.FromArgbとかしなくていいので気分的に楽です。

配列にセットし終わったらpictureBox1.Image = bmp;で画像表示して終了です。

これでかなり高速に描画できるようになりました。

 

| | コメント (0)

2020.05.09

Cで書いたUSB3.0用DLLをC#から呼び出して配列を渡す方法

特殊電子回路からArtix-7評価ボードというのが発売されています。

Art7

このボードはUSB3.0とDDR3メモリとArtix-7 FPGAを搭載していて、USB3.0 SuperSpeedで毎秒300MB以上の速度でデータをやりとりできます。このボードのテストプログラムは今までBorland C++ Builder 6という古いツールで開発していたのですが、このたびようやく重い腰を上げてVisual C#に移植することにいたしました。

Borland C++ Builderで開発したプログラムがこちらです。

Bcb

シンプルなアプリですが、IN方向で約370MB/s、OUT方向で約340MB/s出ているのが測れます。また、このアプリはFPGAをカメラ化するため受信したデータを画像として表示する機能もあります。今回はXorShiftで作った乱数をやりとりしているため、このような砂嵐が表示されます。

Bcb2

これをVisual C#に移植していくのですが、C++で書かれていてDLLを呼び出してハードウェアを操作するようなプログラムなので、困難が予想されます。

ポイントとなるのは、

  • C/C++で書かれたハードウェア操作DLLを呼び出し、配列でデータを渡すこと
  • 受け取った配列のデータを高速で画面に描画すること
  • 乱数データを高速に生成すること

などです。

まずは、C++のDLLにデータを受け渡すプログラムを見ていきます。

DLLにはUSBWriteData()とUSBReadData()という関数があり、Cでは以下のようにプロトタイプが宣言されています。

TKUSBFX3_API int   WINAPI USBWriteData(unsigned long addr,unsigned char *data,int len,unsigned short flag);
TKUSBFX3_API int WINAPI USBReadData(unsigned long addr,unsigned char *data,int len,unsigned short flag);

これをC#から使うには以下のようにしてクラスの中で宣言します。

[DllImport("tkusbfx3.dll")]
protected static extern int USBWriteData(uint addr, IntPtr data, int len, ushort flag);
[DllImport("tkusbfx3.dll")]
protected static extern int USBReadData(uint addr, IntPtr data, int len, ushort flag);

引数は以下のように変換すればよいようです。 

  • unsigned long → uint    ※C#のintは32bit!!
  • unsigned char * → IntPtr
  • int → int   ※C#のintは32bit!!
  • unsigned short → ushort 

これでC#とDLLの間でデータのやりとりができます。

実際に送信する(OUT方向の転送)C#の関数は

public int Write(uint addr, byte[] data, int flag)
{
int trlen = (int)((data.Length + 7) / 8) * 8; // 8の単位で切り上げる
int ret; // 戻り値
unsafe
{
fixed (byte* p = data)
{
IntPtr ptr = (IntPtr)p;
ret = TKUSBFX3.USBWriteData(addr, ptr, trlen, (ushort)(unchecked(flag)));
}
}
return ret;
}

でうまくいきました。

unsafeを使ってポインタを使えるようにし、fixedで囲むことで実行中にdataがガベージコレクトされないようにしています。

なお、プログラム中にuncheckedされているflagという引数がありますが、このArtix-7ボード用の転送オプションなので気にしなくていいです。

もし、清く正しいプログラムならば、Marshalを使って

public int WriteSafe(uint addr, byte[] data, int flag)
{
int trlen = (int)((data.Length + 7) / 8) * 8; // 8の単位で切り上げる
int ret; // 戻り値
IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(byte)) * trlen);
Marshal.Copy(data, 0, ptr, data.Length);
ret = USBWriteData(addr * sizeof(byte), ptr, trlen, (ushort)(unchecked(flag)));
Marshal.FreeCoTaskMem(ptr);
return ret;
}

とやるのだと思いますが、実行速度を測ってみると、

  • unsafe版 ・・・803ms・・・334MB/s
  • Marshal版 ・・975ms・・・275MB/s

となり、大きく差が付きました。Marshal版だと安全なメモリを確保してコピーするのでどうしても遅くなります。

同様に受信(IN方向)のプログラムは、

public int Read(uint addr, byte[] data, int flag)
{
int trlen = (int)((data.Length + 7) / 8) * 8; // 8の単位で切り上げる
int ret = 0;
unsafe
{
fixed (byte* p = data)
{
IntPtr ptr = (IntPtr)p;
ret = TKUSBFX3.USBReadData(addr, ptr, trlen, (ushort)(unchecked(flag)));
}
}
return ret;
}

となります。

安全なMarshal版だと、

public int ReadSafe(uint addr, byte[] data, int flag)
{
int trlen = (int)((data.Length + 7) / 8) * 8; // 8の単位で切り上げる
int ret = 0;
IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(byte)) * trlen);
ret = USBReadData(addr , ptr, trlen, (ushort)(unchecked(flag)));
Marshal.Copy(ptr, data, 0, data.Length);
Marshal.FreeCoTaskMem(ptr);
return ret;
}

となります。

結果は、

  • unsafe版・・・745ms・・・360MB/s
  • Marshal版・・・815ms・・・329MB/s

でした。

Marshal版はメモリを確保してコピーするので、どうしても遅くなりますし、メモリも食います。

C++で書かれたDLLを呼び出す時点でunsafeなわけですから、あまりMarshalとかこだわらずに、スパッとポインタ渡しでいきたいですね。

 

| | コメント (0)

2020.05.06

Unityでガーバファイルを走り回るアプリを作っています

昨日からUnityの勉強を始めました。

プリント基板のガーバデータを読み込んで、Unityの世界に再現して、パターンの上を走り回るゲームを作りたいと思っています。

こんな感じです。

U1

空から眺めているところ。まだこの時点では斜めの線が作るのが面倒臭かったので縦横のパターンのみでした。

U2

斜めの線を作るにはatan2を使えばよいことがわかり、斜めの線も実装できました。

U3

BGAのランドの上を飛び石渡したりして遊べます。

U4

ミアンダ配線から落ちないようにそーっと歩いたり。

U5

作った基板を空から眺めるのは気持ちがいいものです。

U6

線と線が斜めに交差するところでは欠けが出てしまうので、線の端っこにアパーチャを置くことでこの切れ目をなくしました。

U7

だいぶん、プリント基板のそれっぽくなってきました。

U8

FPGAの上からDDR3方面を眺めてみます。

U9

 

Unityは面白いですね。

一つハマったところは、円柱(cyllinder)の上に乗ろうとすると弾かれること。円柱のコライダーがデフォルトで回転楕円体になっているのと、円柱の高さが指定した高さの2倍になってしまうのが原因のようでした。

円柱には、BGAのパッドと、穴の開いたViaがありますが、Viaは円柱ではなくどちらかというと土管なので、どうやって穴の開いた円柱を作ればよいのか悩むところです。

次は多層基板に対応します。実は多層基板も試してはいるのですが、上の層が影になってしまうのと、ベタパターンが反転パターンなのをどうやって作ればよいのか、悩むところです。サーマルはガーバのマクロ機能も使ってそうだし、いろいろと勉強しなければいけないことが多すぎます。

 

| | コメント (0)

2020.05.05

Unityの勉強をはじめました

子供が休校なのでプログラムでも勉強しろと「Unity 3D 超入門講座」という本を買ったのですが、一向にやらないので、まずは親が何かを作ってみることにしました。

本を読みながらポチポチやっていたのですが、ColliderとRigid Bodyでドミノ倒しが作れるあたりから、自分の中で何かが覚醒しました。

Unity1

Prefabというので雛形を作っておいて、それをC#のスクリプトで動的にたくさん作れることがわかってきました。

Unity2

あまりにも高いところから落下すると、スピードが付きすぎているためか、床をすり抜けてしまって当たり判定が出ないようなのです。奈落に落ちてしまった場合の判別をどうすればよいかまだわかりません。

 

WebGLでビルドすれば、そのままWebブラウザで動くし、Unity凄すぎ!

 

 

| | コメント (0)

2020.05.03

Age of Zにハマっています

不覚にもAge of Zとかいうゲームにはまってしまいました。

街の中を発展させて、外で戦って、同盟を組む。ビビットアーミーとだいたい同じだ・・

しかも、副官、欠片制度、長時間の放置と加速システム。ゲームの世界に慣れるまでに時間はかかりませんでした。

自分でもこんなゲームを作りたいなぁと思いながらプレイしています。

| | コメント (0)

« 2020年4月 | トップページ | 2020年7月 »