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();
}
}
ビルド/デプロイ方法は、一つ前の記事を参考にしてください。