Ryzen言ってたくせに、結局Intelに負けた

以前Ryzenで1台組みたいと書きましたが、結局Intelで一台組んでしまいました。 新しいRyzen(Pinnacle Ridge)を既存のマザーボードで使用しようとすると、BIOSの更新が必要になりますが、 通販で売られているものだと、果たしてPinnacle Ridgeに対応しているのかはっきりとは分かりません。

無償でBIOSアップデートをしてくれる業者もあるにはあるのですが。

AMDに押されてIntelも安くなっているようですし、Pinnacle RidgeだとGPUが必須で、 CPUとマザーボードとGPUの部分だけで1万円位の差額が出るため、安いほうに流れました。

購入したもの

部品 型番 価格
CPU Intel Core i5-8400
Mother MSI Z370 GAMING PLUS 上記2点はツクモネットショップにて33,460円
Power 玄人志向 NEXTシリーズ 80 PLUS Bronze 600W ATX電源 KRPW-N600W/85 5,640円
Memory VENGEANCE LPX Series 8GBx2 CMK16GX4M2A2666C16 20,307円
SSD Crucial SSD 500GB MX500 7mm CT500MX500SSD1/JP 14,300円
Other 2.5インチ->3.5インチアダプター 89円
Case ENERMAX BECITE Black ECB3080BB-03 送料込み4,520円
Cable MacLab Displayportケーブル 1.8m 999円

HDDは今回買いませんでしたが、手元に2.5インチの1TBがバックアップ用途にありますので、 そちらを流用し、SSDとHDDの2台構成としました。

改めて調べたら、ケースに2.5インチスロットが複数あったため、アダプタは不要でした。 加えて、マザーボードにはHDMI端子が無かったため、代わりにDisplayPortケーブルを購入しました。

“主な購入物”

久しぶりの組み立てだったので、ちょっと手こずりました。 マザーボードのコネクタのシルクがかなり見にくいので、 マザーボードの説明書を読んで、コネクタの位置をちゃんと確認して 進めました。

剃刀負けには、両刀剃刀とSchickのShave Guardの組み合わせが最強という結論に達した

これまでいろいろな髭剃りの方法を試してきましたが、 やっと自分にとって一番剃刀負けしない方法が分かりました。

それは、以前書いた両刀剃刀と、 Schick薬用シェービングフォームの組み合わせです。

“Schick薬用シェービングフォーム”

これまでは、各種シェービング用ジェルや、剃刀後のローションなどを使ってきました。 少しは改善したかな、と思う時もありましたが、良い状態はあまり長続きしなかったと思います。 でも現在は、上記二点を使うだけで、最小のコストで、最高の剃刀負け対策ができています。

明らかに他の組み合わせのときの肌の状態より、現在の方が改善しています。

なぜジェルなどよりも上記の薬用フォームが効果があるのかは不明です。 このシェービングフォームは、近くのドラッグストアでも一本400円位で売っています。

また、昨年11月に、両刀剃刀の替刃10枚を購入していましたが、 まだ替刃が残っています。つまり、10枚で半年以上持っています。 これだと、年間でも1,000円程度の替刃代で済むことになります。

さらに節約するのであれば、100枚入りで3,000円とか、 100枚で1,000円ちょっとといったものまであるので、 替刃のコストはほとんど無視できます。

この二点セットの組み合わせは、剃刀負けに悩む人は、一度は試してみることをお勧めします。

FMステレオ復調を実装

やっとFMステレオ復調処理を実装しました。処理としては、 ディジタルFMステレオ・チューナの製作 ―― 雑誌の付属基板でここまでできる(ステレオ復調回路)にある通りです。

今回の実装について以下に説明しますが、勘違いしている可能性もありますので、 ご自分で確認してください。

DDSのIPを置いて、位相を可変とした19kHzのsin, cos信号を生成します。 cos信号とFM復調信号を掛け算し、その結果をLPFに通します。 LPFは、CICでfsを1/20にして(fs=500kHzからfs=25kHzに)、続いてFIRでfc=1kHzのフィルタを掛けました。 そして、この値に応じて、DDSで発振する19kHz信号の位相を進めたり遅らせたりします。

具体的には、パイロット信号をsin 19kHzとすると、三角関数の公式により、 sin x * cos y = 1/2 * {sin(x-y)+sin(x+y)} です。今回は、sin 19kHz * cos 19kHz = 1/2 * {sin(0) + sin(38kHz)} となります。LPFを通すことでsin(38kHz)は0になりますので、sin(0)だけが残ります。 つまり、パイロット信号と同調したcos信号が得られれば、sin(0)=0となります。 ただし、sinが0となるのは、0(x=yのとき)とπ(x=y+πのとき)がありますが、今回は0になるようにします。 それで、sinが正であれば、cosの位相(y)を増やし、sinが負であれば、 cosの位相を減らして、sin(x-y)のx-yが0となるように制御しました。

このようにsinのパイロット信号に対してcosの信号が得られたら、倍角の公式 sin 2x = 2 sin x * cos x により、DDSのsin, cos信号を掛け算することで、パイロット信号に同期した38kHzの信号が得られることになります。

あとは、この38kHzの信号をFM復調信号に掛け算し、LPFを通すことでL-Rの信号が得られます。

この状態で時報信号をキャプチャしたものが次になります。

“FM横浜時報信号”

ちょっと分かりにくいですが、上側のグラフは、左右の音声を別々の色で描画しています。 時報信号だと恐らく左右同じ音声信号になってしまっているので、ほとんど重なってしまい、 分かりにくいです。

実際に聞いてみて、確かにこれまでより奥行きが感じられるようになりました。 ただ、この実装がどれほど正しいかは、上のグラフだけでは分かりません。 本来であれば左だけ、右だけの音声を復調してみて、それが正確に再生されるか確認する必要があります。 ただ、手ごろなFMトランスミッタを持ち合わせていないので、このような確認まではできていません。

Zynqで動作するWebSocketサーバから受信したデータをブラウザにてFFT表示

色々試行錯誤することで、ようやく目的の動作に少し近づきました。

Zynq上でWebSocketサーバを動作させ、ADCからキャプチャしたデータをブラウザに送り込み、 ブラウザにてFFTを行いリアルタイムに表示します。

WebSocketサーバはRustで作成しました。

まだFFTのWindow処理は行っていません。1回描画するために256kBの生データをブラウザに送っています。

FFT自体はこちらを使わせてもらっています(参考: FFTs in JavaScript)。

グラフ表示は、Chart.jsを使用しています。

まず、Zynqで動作するプログラムです。

次の記事がUIO経由のレジスタアクセスの参考になりました: 【Rust】Raspberry Pi 3でGPIOのレジスタを叩いてLチカ

read, mmapなどのシステムコール、メモリアドレスからVecへの変換、 データ取り込みスレッドとサーバスレッドとの同期方法など、 色々と苦労したノウハウが詰まっています。

/dev/memをmmapすることで、Z-turn boardに搭載されている1GBのDDRの後半512MBを仮想アドレス空間にマップしています。 PLから当該領域にAXIバス経由でADCデータは転送されます。転送を行うためのIPの設定を、 /dev/uio0にマップされた制御レジスタを通して行います。/dev/uio0にreadを行うことで、割り込み待ちも行えます。

ただし、まだデータ取り込みとブラウザへのデータ転送はオーバーラップしていません。 シーケンシャルに取り込み->転送と動作しています。

extern crate ws;
extern crate libc;

use ws::{listen, Handler, Result, Message, CloseCode, Handshake};
use ws::Message::Text;
use ws::Message::Binary;
use ws::util::Token;
use std::fs::{OpenOptions, File};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::{AsRawFd};
use std::io;
use std::io::{Write, Read, Cursor};
use std::ptr::{self, read_volatile, write_volatile};
use std::thread;
use std::sync::mpsc::channel;
use std::sync::mpsc::{Sender, Receiver};
use std::sync::Arc;
use std::slice;
use std::vec::Vec;
use std::mem;
use std::boxed::Box;

struct Server {
    out: ws::Sender,
    tx: Arc<Sender<i32>>,
    rx: Arc<Receiver<u32>>
}

const KB : libc::size_t = 1024;
const MB : libc::size_t = 1024*1024;
const MEM_SIZE : libc::size_t = 512*MB;
const MEM_OFFSET : libc::off_t = 512*MB as libc::off_t;
const ZERO_OFFSET : libc::off_t = 0;
const PAGE_SIZE : libc::size_t = 4096;

fn memmap() -> io::Result<*mut u32> {
    let mem_file = OpenOptions::new()
                    .read(true)
                    .write(true)
                    .custom_flags(libc::O_SYNC)
                    .open("/dev/mem")
                    .expect("can't open /dev/mem");
    unsafe {
        let ptr = libc::mmap(ptr::null_mut(),
                            MEM_SIZE, libc::PROT_READ | libc::PROT_WRITE,
                            libc::MAP_SHARED,
                            mem_file.as_raw_fd(),
                            MEM_OFFSET);        // second 512MB of the total of 1GB of DDR memory
        if ptr == libc::MAP_FAILED {
            Err(io::Error::last_os_error())
        }
        else
        {
            Ok(ptr as *mut u32)
        }
    }
}

fn uiomap() -> (*mut u32, File) {
    let uio_file = OpenOptions::new()
                    .read(true)
                    .write(true)
                    .custom_flags(libc::O_SYNC)
                    .open("/dev/uio0")
                    .expect("can't open /dev/uio0");
    unsafe {
        let ptr = libc::mmap(ptr::null_mut(),
                            PAGE_SIZE, libc::PROT_READ | libc::PROT_WRITE,
                            libc::MAP_SHARED,
                            uio_file.as_raw_fd(),
                            ZERO_OFFSET);
        if ptr == libc::MAP_FAILED {
            panic!("{}", io::Error::last_os_error())
        }
        (ptr as *mut u32, uio_file)
    }    
}

fn set_axi_dma_reg(reg_adr : *mut u32, size : u32, offset : u32) {
    let status_reg : *mut u32 = unsafe { reg_adr.offset(0) };
    let ien_reg : *mut u32 = unsafe { reg_adr.offset(1) };
    let done_reg : *mut u32 = unsafe { reg_adr.offset(2) };
    let offset_reg : *mut u32 = unsafe { reg_adr.offset(4) };
    let len_reg : *mut u32 = unsafe { reg_adr.offset(6) };

    unsafe {
        let status = read_volatile(status_reg); // clear ap_done
        write_volatile(ien_reg, 1);     // enable
        write_volatile(done_reg, 1);    // IP Interrupt enable reg
        write_volatile(offset_reg, offset/8);
        write_volatile(len_reg, size/8);
    }
}

fn start_axi_dma(reg_adr : *mut u32){
    let start_reg : *mut u32 = unsafe { reg_adr.offset(0) };
    unsafe {
        write_volatile(start_reg, 1);
    }
}

fn clear_interrupt(reg_adr : *mut u32){
    let intcl_reg : *mut u32 = unsafe { reg_adr.offset(3) };
    unsafe {
        write_volatile(intcl_reg, 1);
    }
}

fn wait_interrupt(uio_file : &mut File) -> (){
   let mut tmp = [0; 4];
   if 4 != uio_file.read(&mut tmp).unwrap() {    // wait interrupt
        panic!("failed read");
   }
}

impl Handler for Server {
    fn on_open(&mut self, _shake: Handshake) -> Result<()> {
        println!("on_open");
        Ok(())
    }
    fn on_timeout(&mut self, event: Token) -> Result<()> {
        println!("on_timeout");
        Ok(())
    }
    fn on_message(&mut self, msg: Message) -> Result<()> {
        match msg {
            Text(_) => {
                let vec: Vec<u8> = vec![0; 1920*1080];
                self.out.send(vec)
            },
            Binary(v) => {
                let adr = self.rx.recv().unwrap();
                let mut vec : Vec<u8> = Vec::new();
                let ary : &'static [u8] = unsafe { slice::from_raw_parts(adr as *const u8, 256*KB) };
                vec.write(ary).expect("unable to write");
                let r = self.out.send(Binary(vec));
                self.tx.send(1);
                r
            }
        }
    }
    fn on_close(&mut self, _code: CloseCode, _reason: &str) {
    }
}

fn main() {
   let mapped_ptr : *mut u32 = memmap().expect("failed mmap");
   let (uio_ptr, mut uio_file) = uiomap();
   let one : [u8; 4] = [1, 0, 0, 0];

   let (tx_web, rx_cap) = channel();
   let (tx_cap, rx_web) = channel();

   let hdl = thread::spawn(move || {
       let tx = Arc::new(tx_web);
       let rx = Arc::new(rx_web);
       listen("192.168.0.90:3012",
                |out| Server { out: out, tx: Arc::clone(&tx), rx: Arc::clone(&rx) })
                .unwrap();
   });
   loop {
        set_axi_dma_reg(uio_ptr, 256*KB as u32, 512*MB as u32);
        uio_file.write(&one).expect("write");            // enable interrupt
        start_axi_dma(uio_ptr);
        wait_interrupt(&mut uio_file);
        clear_interrupt(uio_ptr);
        tx_cap.send(mapped_ptr as u32);    // send start address
        let v = rx_cap.recv().unwrap();
   }
}

ビルド/デプロイ方法は、一つ前の記事を参考にしてください。

RustでWebSocketサーバをクロスコンパイルしてZynqで動かした

以前に、 HaskellでWebSocketのサーバを動作させ、バイナリデータをブラウザで受け取ってCanvasで描画する実験を行いました。

最終的には、ADCのデータをブラウザに送って、ブラウザ上で各種処理を行うことを目論んでいます。

現在はADCのデータは次のように取得しています。

Cで作成したTCPのサーバプログラムをZynq上で動作させ、ADCのデータをmmap等により取得し、TCP経由で送信します。 クライアント側は、C#で作成したプログラムでデータを受信して、各種処理を行っています。

これだと、どうしてもWindows専用のプログラムになってしまいます(Monoを使ってLinuxでも動くのかも知れませんが)。 近頃はかなりの事がブラウザでできるようになっていますし、 そうすればクロスプラットフォームで使えますので、そちらの方が望ましいと思います。

それで上記のような実験を行っていました。 ただ、Haskellでarmv7にクロスコンパイルするのは少し面倒そうでした。

Rustは、かなり簡単にクロスコンパイルが行えるということが分かり、 上記の目的が達成できるか、少しずつ試しています。

Rustでクロスコンパイル

上記の記事に従うことで、簡単にクロスコンパイル環境が構築できました。

次に、前回Haskellで作成したプログラムとほぼ同機能のプログラムを作りました。


extern crate ws;

use ws::{listen, Sender, Handler, Result, Message, CloseCode, Handshake};
use ws::Message::Text;
use ws::Message::Binary;
use ws::util::Token;

struct Server {
    out: Sender,
}

impl Handler for Server {
    fn on_open(&mut self, _shake: Handshake) -> Result<()> {
        println!("on_open");
        Ok(())
    }
    fn on_message(&mut self, msg: Message) -> Result<()> {
        let mut vec: Vec = vec![0; 1920*1080];
        match msg {
            Text(_) => {
                let vec: Vec = vec![0; 1920*1080];
                self.out.send(vec)
            },
            Binary(v) => {
                for j in 0..1080 {
                    for i in 0..1920 {
                        vec[j*1920+i] = (i+j+v[0] as usize) as u8;
                    }
                }
                self.out.send(Binary(vec))
            }
        }
    }
}

fn main() {
   listen("192.168.0.90:3012", |out| Server { out: out }).unwrap();
}

Cargo.tomlには、

平行線を使ってFMアンテナ製作

これまでは、次のようなアンテナでFM放送を受信していました。

“FMロッドアンテナ”

これだと、色々向きを変えたりしても、何とか聞けているかな、といった感度でした。 もう少し何とかしたいと思い、アンテナを製作してみることにしました。

次の情報を参考にしました。

製作に当たり、以下のものを購入しました。

部品代は合計886円(端子台は2個購入)でしたが、送料が486円もかかりました。 ちなみに、メール便だと上記アイテムは送れないということで、ヤマトで送られてきました。

製作したアンテナの全体は次のような感じです(設置前)。

“新しいアンテナ”

平行電線を解いて、水平に設置しました。どれほどの長さを解くか悩みましたが、 ひとまず現状は72cmにしてみました(開いて144cm)。

電線は分かれているところの根本で同軸ケーブルに接続したほうが良いのかと考えましたが、 今のところ電線を解かずに、さらに1.5mくらい伸ばして下記のように端子台に接続しています。

“端子台拡大”

端子台のプラス側に赤線を接続し、マイナス側に黒線を接続します。 また、SMA-P変換アダプタを接続して、LNA基板につながるようにします。

早速、FM放送を受信してFFTしてみました。

“FM FFT”

以前のアンテナは次のような感じでした。

“old FM FFT”

以前のアンテナだと90MHzを超えたあたりから、かなり減衰していたので、 LNA基板のBPF帯域の設定が悪かったかと思っていましたが、 そもそもアンテナで受信できていなかったように思われます。

また、これまでのアンテナでは、Zynqの搭載されている基板の電源インダクタを手で触るだけで、 かなりのノイズが入ってしまいました。一方、今回のアンテナだと、 触ってもそれほど変化ありません。そのときのFFTの様子も示します。

“FM FFT touching inductor”

ノイズフロアはかなり上がっています。

新しいアンテナだと、81.3MHz, 82.5MHz, 84.7MHz, 89.7MHz, 90.5MHz, 91.5MHzあたりが、 それなりに受信できているようです。

これくらい聴けると、ようやくステレオ復調を試みても良いかな、と思えるようになりました。 やはり、無線はアンテナ肝心という、ごく当然のことが確認できました。

最後に、またまたFMヨコハマの時報キャプチャを載せておきます。

“FM Yokohama Time Signal”

Zynq上でiperf3による帯域テスト

前々から気になっていた、Zynqでのイーサネット帯域を測定しました。

Zynqで使用しているカーネルは3.15.0-xilinx(z-turn board付属)です。 通信相手は、仮想マシンで動作しているUbuntu(カーネルは3.13.0-32-generic)です。 Windows上でのiperfと通信させても、それほど大きな違いはありませんでした。

Zynq側iperfのコンソール出力を以下に示します。

root@ubuntu-armhf:/home/ubuntu/iperf-3.1.3/src# ./iperf3 -s
warning: this system does not seem to support IPv6 - trying IPv4
-----------------------------------------------------------
Server listening on 5201
-----------------------------------------------------------
Accepted connection from 192.168.0.100, port 33239
[  5] local 192.168.0.90 port 5201 connected to 192.168.0.100 port 33240
[ ID] Interval           Transfer     Bandwidth
[  5]   0.00-1.00   sec  59.0 MBytes   495 Mbits/sec
[  5]   1.00-2.00   sec  61.8 MBytes   516 Mbits/sec
[  5]   2.00-3.00   sec  61.9 MBytes   519 Mbits/sec
[  5]   3.00-4.00   sec  61.8 MBytes   518 Mbits/sec
[  5]   4.00-5.00   sec  61.9 MBytes   518 Mbits/sec
[  5]   5.00-6.00   sec  61.6 MBytes   519 Mbits/sec
[  5]   6.00-7.00   sec  61.6 MBytes   517 Mbits/sec
[  5]   7.00-8.00   sec  61.8 MBytes   518 Mbits/sec
[  5]   8.00-9.00   sec  61.6 MBytes   515 Mbits/sec
[  5]   9.00-10.00  sec  61.6 MBytes   518 Mbits/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth
[  5]   0.00-10.00  sec  0.00 Bytes  0.00 bits/sec                  sender
[  5]   0.00-10.00  sec   615 MBytes   515 Mbits/sec                  receiver
-----------------------------------------------------------
Server listening on 5201
-----------------------------------------------------------
^Ciperf3: interrupt - the server has terminated
root@ubuntu-armhf:/home/ubuntu/iperf-3.1.3/src# ./iperf3 -c 192.168.0.100
Connecting to host 192.168.0.100, port 5201
[  4] local 192.168.0.90 port 33104 connected to 192.168.0.100 port 5201
[ ID] Interval           Transfer     Bandwidth       Retr  Cwnd
[  4]   0.00-1.01   sec  50.0 MBytes   416 Mbits/sec    0    233 KBytes
[  4]   1.01-2.02   sec  49.7 MBytes   412 Mbits/sec    0    298 KBytes
[  4]   2.02-3.02   sec  48.9 MBytes   409 Mbits/sec    1    242 KBytes
[  4]   3.02-4.00   sec  47.5 MBytes   405 Mbits/sec    0    247 KBytes
[  4]   4.00-5.02   sec  50.0 MBytes   412 Mbits/sec    0    247 KBytes
[  4]   5.02-6.02   sec  48.8 MBytes   409 Mbits/sec    0    266 KBytes
[  4]   6.02-7.02   sec  48.8 MBytes   411 Mbits/sec    0    267 KBytes
[  4]   7.02-8.00   sec  48.5 MBytes   413 Mbits/sec    0    281 KBytes
[  4]   8.00-9.02   sec  49.0 MBytes   404 Mbits/sec    0    293 KBytes
[  4]   9.02-10.00  sec  48.5 MBytes   414 Mbits/sec    0    303 KBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth       Retr
[  4]   0.00-10.00  sec   490 MBytes   411 Mbits/sec    1             sender
[  4]   0.00-10.00  sec   489 MBytes   410 Mbits/sec                  receiver

iperf Done.

通信方向によって、かなり帯域に違いがあります。サーバモードで動作しているときの方が高速のようです。

u-blox社のGPSモジュールを購入

u-blox社のNEO-6Mを搭載したGPSモジュールを購入しました。 (購入したらもう在庫が無くなっていました。後述の目的のために、もう一個AliExpressに発注しました)

早速、別途調達していたUSBシリアルコンバータと接続しました。

“接続”

電源もコンバータからとれるので便利です。自宅には半田コテさえ無かったので、こちらも 購入しました。我ながら呆れるほど下手くそです。

室内環境でしたが、あっさりと動作しました。まずはTeraTermでNMEAメッセージが出力されていることを確認しました。

次に、u-blox社のサイトから、サポート->評価用ソフトウェア->u-center Windowsとたどって、 u-centerというソフトウェアをダウンロードしました。こちらでNMEAメッセージを受信すると、いろいろ面白いです。

“u-center画面”

電波を受信できている衛星の番号や、それらの角度、もちろん現在位置などが確認できます。 Google Mapで現在位置を表示することもできます。

次に、GPSモジュールが出力するPPS信号の波形もオシロスコープで取得してみました。

“PPS信号”

当たり前ですが、1秒間に1回のパルスです。

もう一つ購入したGPSモジュールが到着したら、次の事を試してみたいと思っています。

  • 2つのモジュールのPPS信号を同時に観測し、どの程度の精度となっているのか調査する。
  • PPS信号をもとにして、製作した基板(1), (2)に搭載されているMEMS, TCXOの精度を調べる。

GPSモジュールって使い方によっては結構遊べそうです。

HaskellでのWebSocketsサーバの効率改善

前回の記事で、HTMLのCanvas要素とWebSocketsの組み合わせで画像転送と表示の実験を行いました。 想定よりもHaskellで作ったWebSocketsサーバの動作が重いため、少し調査して改善しました。

import Control.Exception (finally)
import Control.Monad (forM_, forever)

import qualified Network.WebSockets as WS
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Internal as BI

import Foreign.Ptr (plusPtr, Ptr)
import Foreign.Storable (poke)
import Data.Word (Word8)

main :: IO ()
main = WS.runServer "127.0.0.1" 9160 application

-- BI.create :: Int -> (Ptr Word8 -> IO ()) -> IO ByteString
fill :: Int32 -> Ptr Word8 -> IO ()
fill seed ptr = 
    go (0, 0) ptr
      where
        go (j, i) ptr = do
            let val = fromIntegral $ seed + i + j
            poke ptr val
            if i == 1919 then
                if j == 1079 then
                    return ()
                else
                    go (j+1, 0) $ ptr `plusPtr` 1
            else
                go (j, i+1) $ ptr `plusPtr` 1

application :: WS.ServerApp
application pending = do
    conn <- WS.acceptRequest pending
    WS.forkPingThread conn 30
    forever $ do
        bytes <- WS.receiveData conn
        let seed:_ = L.unpack bytes
        let seed2 = fromIntegral (toInteger seed)
        buf <- BI.create (1920*1080) (fill seed2)
        WS.sendBinaryData conn buf

ByteString.Internalのcreate関数を使って、転送するバッファの生成と初期化を同時に行います。 fill関数には、createで確保される領域へのポインタが渡されるので、poke関数を使って当該領域に1つずつデータを書き込みます。 go関数で、1画面分ループします。1ループごとにポインタを1進める(plusPtr)ことで、次のWord8にアクセスできます。

HTMLのCanvasとHaskellでのWebSocketsを組み合わせる

前回、HTMLでCanvasを使用した描画を試しました。 今回は、HaskellでWebSocketsのサーバを作成し、当該サーバからバイナリデータを受信してCanvasに描画するようにしました。

まずは、ブラウザで動作させるファイルです。

<html>
    <body>
        <script
            src="https://code.jquery.com/jquery-3.2.1.js"
            integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE="
            crossorigin="anonymous"></script>
        <p><div id="time">Time[ms]</div></p>
        <canvas id="canvas" width="1920" height="1080"></canvas>
        <script type="text/javascript">
            $(window).on('load', function(){
                var tm = 0;
                var canvas = $('#canvas').get(0);
                var width = canvas.width;
                var height = canvas.height;
                var ctx = canvas.getContext('2d');
                var imageData = ctx.getImageData(0, 0, width, height);

                var buf = new ArrayBuffer(imageData.data.length);
                var buf8 = new Uint8ClampedArray(buf);
                var data = new Uint32Array(buf);
                var start_ms = performance.now();
                var elapsed_ms;
                var cn = new WebSocket('ws://127.0.0.1:9160/');

                cn.addEventListener('message', function(ev){
                    var reader = new FileReader();
                    reader.addEventListener("loadend", function(){
                        var ary = new Uint8Array(reader.result);
                        //console.log(ary[0]);
                        for (var j = 0; j < height; j++){
                            for (var i = 0; i < width; i++){
                                var val = ary[j*width+i];
                                data[j*width+i] = (255<<24) | (val<<16) | (val<<8) | val;
                            }
                        }
                        imageData.data.set(buf8);
                        ctx.putImageData(imageData, 0, 0);
                        if (tm % 60 == 0){
                            var current = performance.now();
                            elapsed_ms = current - start_ms;
                            $("#time").text(elapsed_ms/60.0 + "[ms]");
                            start_ms = current;
                        }
                    });
                    reader.readAsArrayBuffer(ev.data);
                });

                var getData = function(){
                    var abuf = new ArrayBuffer(1);
                    var view = new Uint8Array(abuf);
                    view[0] = tm & 0xFF;
                    cn.send(abuf);
                    tm++;
                    setTimeout(getData, 1);
                }
                cn.addEventListener('open', function(){
                    getData();
                });
            });
        </script>
    </body>
</html>

上記のとおり、ローカルホストで動作するWebSocketサーバとの通信を開き、getData関数を繰り返し呼び出します。