2017年10月
Cosmo-ZをPythonで遠隔操作してグラフ描画
2017.10.12
Cosmo-ZをPythonで遠隔操作して、波形のグラフを描くことができるようになりました。
初めてのプログラムですが、すぐにこんなことができるって、Pythonすごい。
# encoding:utf-8
import sys
import argparse
import datetime
# 定数の設定
convert_xy=1
server="192.168.2.100"
points = int(sys.argv[1])
freq = 100
# 引数の解釈
if(len(sys.argv) <= 1):
print "Usage: python cszcli.py capture_length [freq=FREQUENCY] [server=SERVER_NAME]"
sys.exit()
# NumPyライブラリのインポート(遅い)
print "import NumPy and MatPlotLib"
import numpy as np
from matplotlib import pyplot
print "done"
# XMLRPCのインポート
print "import XMLRPC lib"
try:
import xmlrpclib as xmlrpc_client
except ImportError:
import xmlrpc.client as xmlrpc_client
print "done"
# サーバに接続
print "Connect serer.."
cosmoz = xmlrpc_client.ServerProxy('http://' + server + ':21068')
print "done"
# Cosmo-Zの操作
cosmoz.open()
adc_vrange = cosmoz.adc_vrange()
adc_info = cosmoz.adc_info()
cosmoz.adc_setfreq(freq)
while(1):
cosmoz.capture(0xff,points,"auto")
print "capture done."
print "Data transfer start"
# データの取得
time = np.array(map(int,cosmoz.getdata(0).split(",")))
ch1 = np.array(map(int,cosmoz.getdata(1).split(",")))
ch2 = np.array(map(int,cosmoz.getdata(2).split(",")))
ch3 = np.array(map(int,cosmoz.getdata(3).split(",")))
ch4 = np.array(map(int,cosmoz.getdata(4).split(",")))
ch5 = np.array(map(int,cosmoz.getdata(5).split(",")))
ch6 = np.array(map(int,cosmoz.getdata(6).split(",")))
ch7 = np.array(map(int,cosmoz.getdata(7).split(",")))
ch8 = np.array(map(int,cosmoz.getdata(8).split(",")))
print "Data transfer end"
if(convert_xy):
# 時間へ変換
adc_freq = adc_info["sampling_freq_mhz"];
if(adc_freq != 0):
samp_period = 1 / adc_freq
else:
samp_period = 1;
time = time * samp_period
# 電圧へ変換
vmax = adc_vrange["max_input_voltage"]
vmin = adc_vrange["min_input_voltage"]
resolution = float(adc_vrange["adc_resolution"])
ch1 = ch1 / resolution * (vmax - vmin) + vmin
ch2 = ch2 / resolution * (vmax - vmin) + vmin
ch3 = ch3 / resolution * (vmax - vmin) + vmin
ch4 = ch4 / resolution * (vmax - vmin) + vmin
ch5 = ch5 / resolution * (vmax - vmin) + vmin
ch6 = ch6 / resolution * (vmax - vmin) + vmin
ch7 = ch7 / resolution * (vmax - vmin) + vmin
ch8 = ch8 / resolution * (vmax - vmin) + vmin
else:
print time
# 波形の描画
print "Plot start"
pyplot.cla()
pyplot.clf()
# pyplot.figure()
pyplot.title('Cosmo-Z ' + " captuer " + str(points) + 'points. ' + str(datetime.datetime.now()))
pyplot.plot(time, ch1, color='brown', label='ch1')
pyplot.plot(time, ch2, color='red', label='ch2')
pyplot.plot(time, ch3, color='orange', label='ch3')
pyplot.plot(time, ch4, color='yellow', label='ch4')
pyplot.plot(time, ch5, color='green', label='ch5')
pyplot.plot(time, ch6, color='blue', label='ch6')
pyplot.plot(time, ch7, color='fuchsia', label='ch7')
pyplot.plot(time, ch8, color='gray', label='ch8')
pyplot.text(0,max(max(ch1),max(ch2),max(ch3),max(ch4),max(ch5),max(ch6),max(ch7),max(ch8)), "Sampling freq " + str(freq) + "MHz",fontsize=11 , color="skyblue")
if(convert_xy):
pyplot.ylabel("Voltage [V]")
pyplot.xlabel("Time [us]")
else:
pyplot.ylabel("ADC Value [LSB]")
pyplot.xlabel("Sampling point")
pyplot.legend()
pyplot.pause(.01)
raw_input('Hit any key to capture next wave')
Cosmo-Zのサーバからは2044,2045,2057・・・みたいなテキストで計測データが送られてくるので、それをカンマで区切って配列に入れて、配列をmapでintに変換して、それをNumPyの配列に変換しています。
そして、NumPyの配列の各要素にADC値→電圧に変換するための係数をかけて、グラフにして表示しています。
上の波形はNaIシンチレータから受信したパルスですが、pyplotはマウスで拡大できて便利です。
実行時のコンソール。
サーバ側のCosmo-ZのLinuxイメージは明日にはでもリリースしたいと思います。
Cosmo-Z ライブラリ 2.0のマニュアルを執筆開始
2017.10.11
Cosmo-ZライブラリのマニュアルはDoxygenで書くことにしました。
http://www.tokudenkairo.co.jp/cosmoz/apidoc
長久しぶりのDoxygenですが、なんとか書き方を思い出して書いています。
整ったマニュアルが手軽に生成できるのはいいですね。
Doxygenを使う上でハマった点を書きます。
ファイルの最初に@fileがないとドキュメント化されないようなのですが、
/****************************************************************************** @file document.h @brief ライブラリマニュアルのメインページを記述している ******************************************************************************/
のように@briefの前に何もないとだめで、
/****************************************************************************** * @file document.h * @brief ライブラリマニュアルのメインページを記述している ******************************************************************************/
と、* が必要なようです。
ZYNQのPSからのDDR3メモリアクセス速度
2017.10.09
ZYNQ搭載のADCボード「Cosmo-Z」用の、汎用計測ファームウェア V2.0を開発しています。
いままでは、root権限でプログラムを動かして、/dev/memを開いてメインメモリを直接操作していたのですが、これでは一般ユーザが使えません。
そこで、デバイスドライバをちゃんと作って、一般ユーザの権限でもソフトウェアを使えるようにしようとしています。
Linuxのデバイスドライバでは、FPGAやDDR3メモリなどのリソースの物理アドレスがわかっていれば、
request_mem_region(物理アドレス, サイズ ,name);
でメモリマップI/Oの空間を予約して、
void *__iomem vaddr = ioremap(物理アドレス, サイズ);
で仮想アドレスを割り当てます。
割り当てられた仮想アドレスに対して読み書きすれば、指定した物理アドレスに値が読み書きされます。
ぶっちゃけ、まどろっこしいことせずに memcpy(vaddr, user_buf, 100); とやれば、DDR3メモリやFPGAのレジスタに書き込みができてしまうのですが、割り当てた物理アドレスに存在したリソースがページアウトされている可能性があるので、安全なプログラムとはいえません。
まぁ、OS管理外のDMA用メモリなのでページアウトされることはないのですが、カーネルではmemcpyは使ってはいけないことになっているようなので、正しく作らなければなりません。
カーネルモードで、ユーザバッファとの間でデータをコピーするには、copy_from_user()や、copy_to_user()といった関数を使います。
copy_to_userは、カーネルのバッファからユーザのバッファにデータをコピーする際に使います。
remain = copy_to_user(buf, p_dev->mapped_capture_addr + p_dev->offset, count);
この関数の戻り値は0なら成功です。0でなければ、コピーできなかったバイト数を返すという仕様なのですが、今日、実験しているときに0でない値が帰ってくることがありました。
ネットを探しても、copy_to_userで失敗する人はそういません。原因は単純なミスでした。
newで確保したバッファのサイズ以上の転送をしようとしたのです。具体的には、
unsigned char buf = new unsigned char[4096];
で確保したバッファに対して16Mバイトくらいの転送をしようとしていたのです。
この単純なミスに気が付かずに、ページサイズの関係は原因かも、とか、copy_to_userのアセンブラソースを読んだりして8時間くらい無駄に悩んでいました。
![]()
newでユーザメモリ空間に256Mバイトのバッファを確保し、物理アドレス0x20000000に対して転送した場合の速度を測りました。
ZYNQのCPUからみれば、Linux管理下のメモリから読み出して、Linux管理外のメモリに転送するという、メモリ間の転送なのですが、書き込みは550~600MBytes/s、読み出しは200~240MByte/secという速度が出ました。
つまり、同じDDR3メモリから読み出して書いているので、実際にはこの2倍の速度で動いていることになるのでしょう。
なぜ読み書きで速度が異なるのかはわかりません。また、書き込む内容が全部ゼロよりも、乱数を書いておいたほうが読み出しが早くなる、という傾向がみられました。原因はわかりません。
Pythonを使ってZYNQをコントロール
2017.10.05
Cosmo-Zの2017年度アップグレードと称して、ファームウェアの大幅な改良を行っています。
最大の目的は、お客様がアプリを簡単に作れるようにすること。
その目的の達成のために、Cosmo-Zのさまざまな機能をPythonやEPCS、RT-MiddleWare、SKPIなどで操作できるようにしたいと考えています。
今日はまず、Cosmo-Zを操作するプログラムを、ライブラリと、アプリ層に分けました。
上の図でcosmozapiというのがライブラリ、cszmainというのがテストアプリです。
cosmozapiの中ではZYNQのAXI Liteで作ったメモリマップレジスタに様々な機能にアクセスしています。
今回はスタティックライブラリ(.a)として作っていますが、意外とハマったのが、XILINXのSDKが作るデフォルトのライブラリプロジェクトではだめで、コンパイルオプションに-fpicを追加しなければなりません。
これをしないと、後でPython用のライブラリ(so)を作る際に、
/usr/bin/ld: ../libcosmozapi.a(cosmoz.o): relocation R_ARM_MOVW_ABS_NC against `a local symbol' can not be used when making a shared object; recompile with -fPIC ../libcosmozapi.a: could not read symbols: Bad value collect2: ld returned 1 exit status
というエラーが出てしまいます。
次に、このライブラリをPythonから呼び出せるようにラッパすることです。
cosmozPy.c
#include#include "Python.h" PyObject* cosmoz_open(PyObject* self, PyObject* args) { BOOL status = csz_open(CSZ_CONNECTION_TYPE_DIRECT,NULL); return Py_BuildValue("i",status); } PyObject* cosmoz_firm(PyObject* self, PyObject* args) { return Py_BuildValue("{s:s,s:s,s:s}", "date",csz_firmdate(), "description",csz_firmdescr(), "version",csz_firmver() ); } PyObject* cosmoz_xadc(PyObject* self, PyObject* args) { int x, y, g; double tempe,vccint,vccaux,vccddro; csz_xadc(&tempe,&vccint,&vccaux,&vccddro); return Py_BuildValue("{s:d,s:d,s:d,s:d}","vccint",vccint,"vccaux",vccaux,"vccddro",vccddro,"temp",tempe); } static PyMethodDef cosmozmethods[] = { {"open", cosmoz_open, METH_NOARGS}, {"xadc", cosmoz_xadc, METH_NOARGS}, {"firm", cosmoz_firm, METH_NOARGS}, {NULL}, }; void initcosmoz() { Py_InitModule("cosmoz", cosmozmethods); }
これを以下のようにしてコンパイルします。なお、Py_InitModuleの第一引数のモジュール名と、最後の関数の「initモジュール名」と、出来上がったファイルのsoの前の名前は合わせておく必要があります。それ以外はファイル内でのみ参照されるものなので任意です。
gcc -fpic -o cosmoz.o -c cosmoz.c -I/usr/include/python2.7 -I. gcc -shared cosmoz.o -lcosmozapi -L.. -o cosmoz.so
このプログラムをビルドするには、Phthon.hが必要なのですが、これはubuntuに最初は入っていなかったので、apt-get install python-devやapt-get install python3-devで入れました。
それからハマったのは
- Py_InitModuleはPython2の関数なので、Python3では使えない
- このプログラムはCで書くこと。C++だと関数の名前が修飾されてしまってリンカエラになる。extern "C"とかやればいいんだろうが、これくらいならCで書いたほうがかえって気持ちが良い。
C++で書くと、コンパイル時に以下のようなエラーが出ることがあります。
gcc -fpic -o cosmoz.o -c cosmoz.cc -I/usr/include/python2.7 -I.
cosmoz.cc:75:1: error: invalid conversion from ‘PyObject* (*)(PyObject*, PyObject*, PyObject*) {aka _object* (*)(_object*, _object*, _object*)}’ to ‘PyCFunction {aka _object* (*)(_object*, _object*)}’ [-fpermissive]
これは PyObject* func1(PyObject* self, PyObject* args, PyObject* kw) のように文字列を引数として受け取る(つまりMETH_KEYWORDS型)の関数を、PyMethodDefで列挙しているところで型が合わなくなるためです。PyCObject*かなにかに無理やりキャストすれば通りますが、それでも名前が修飾されてしまっているので、モジュールをロードして実行してみると、
root@cosmoz:/home/share/py# python cosmoz_server.py Traceback (most recent call last): File "cosmoz_server.py", line 1, inimport cosmoz ImportError: dynamic module does not define init function (initcosmoz)
となって、「initモジュール名」の初期化関数が見当たらないとなってしまいます。
PythonのラッパはCで作りましょう。
![]()
次に、Pythonのプログラムを作って動かしてみます。
cosmoz.py
import cosmoz
print('Cosmo-Z Open Status => ' + str(cosmoz.open()))
print("Firmware => " + str(cosmoz.firm()));
print("Get XADC => " + str(cosmoz.xadc()));
実行結果は
Cosmo-Z Open Status => 1
Firmware => {'date': 'Oct 5 2017 17:34:51', 'version': '2.0.0', 'description': 'Cosmo-Z Firmware Version 2 Pre-Alpha'}
Get XADC => {'temp': 59.06008300781252, 'vccaux': 1.811279296875, 'vccint': 0.990966796875, 'vccddro': 1.491943359375}
となりました。
Cで作ったプログラムがPythonで呼び出せたので、ちょっと感動しました。
![]()
最後にこれをネットワーク経由で、Windowsマシンから呼び出せるようにしましょう。
まず、XML-RPCというサーバを動かします。
cosmoz_server.py
import cosmoz
import SimpleXMLRPCServer as xmlrpc_server
def open():
return cosmoz.open()
def firm():
return cosmoz.firm()
def xadc():
return cosmoz.xadc()
server = xmlrpc_server.SimpleXMLRPCServer(('192.168.1.10', 21068))
server.register_function(open)
server.register_function(firm)
server.register_function(xadc)
server.register_introspection_functions()
server.serve_forever()
これで192.168.1.10として待ち受けるサーバができます。このアドレスの書き方はよくわかりません。
そうして、Windowsマシンに入れたPythonで
import xmlrpclib as xmlrpc_client
client = xmlrpc_client.ServerProxy('http://cosmoz:21068')
print client.open()
print client.firm()
print client.xadc()
と打ってみると、
見事につながりました。
ただ、ちょっと遅いですね。
Wiresharkで見ると、Windows PC(192.168.1.2)がZYNQ(192.168.1.10)にPOSTのリクエストを送ってから応答が返るまで10秒かかっています。
その間にはMDNSという見慣れない、なんとなく邪悪な感じのするマルチキャストが飛び交っています。
このZYNQ UbuntuはMDNSというプロトコルのパケットを送って、応答が返るまで次のTCPの返事を出さないようです。MDNSというのはよくわかりませんが、avahiというデーモンが何かをしているようなので、
/etc/init.d/avahi-daemon stop
で止めてしまいます。
すると、20ms弱で応答するようになりました。
一つの関数の呼び出しに20msは遅いかもしれませんが、これで良しとしましょう。
![]()
わかったことをまとめますと、
- XILINX SDKのスタティックライブラリのプロジェクトで作った.aをリンクしてsoを作ることはできない。コンパイルオプションに-fpicを付けること。
- PythonのラッパはCで作る。
- XML-RPCサーバを作るときには、SimpleXMLRPCServer(('192.168.1.10', 21068)) のように第一引数に自分のIPアドレスを入れるようだ。127.0.0.1では他のマシンからアクセスできなかった。
- avahi-daemonを止めておかないとレスポンスが遅すぎる
- ZYNQ上のUbuntuでPythonを動かし、XML-RPCを動かした場合の、1回のRPC呼び出しの所要時間は20ms弱だった
ということです。
実は、「Pythonなんて若いもんが使う軽量言語だろ、興味ないね。」と、今まで軽視して触りもしなかったのですが、勉強してみると、本格的なオブジェクト指向ができるのに記述が簡単で、非常に洗練されていて、ライブラリが充実した言語であることがわかりました。
Pythonを触って2日目でここまで来れましたが、もうC++&STLには戻れないですね。













