« 1G ADCボードで波形が取れた | トップページ | ST39VF801CというROMのCFIは間違っている »

2017.10.05

Pythonを使ってZYNQをコントロール

Cosmo-Zの2017年度アップグレードと称して、ファームウェアの大幅な改良を行っています。

最大の目的は、お客様がアプリを簡単に作れるようにすること。

その目的の達成のために、Cosmo-Zのさまざまな機能をPythonやEPCS、RT-MiddleWare、SKPIなどで操作できるようにしたいと考えています。

今日はまず、Cosmo-Zを操作するプログラムを、ライブラリと、アプリ層に分けました。

Cszapi1

上の図で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 <cosmoz.h>
#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, in 

    import cosmoz ImportError: dynamic module does not define init function (initcosmoz)

となって、「initモジュール名」の初期化関数が見当たらないとなってしまいます。

PythonのラッパはCで作りましょう。

sun

次に、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で呼び出せたので、ちょっと感動しました。

sun

最後にこれをネットワーク経由で、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()

と打ってみると、

Py1

見事につながりました。

ただ、ちょっと遅いですね。

Wiresharkで見ると、Windows PC(192.168.1.2)がZYNQ(192.168.1.10)にPOSTのリクエストを送ってから応答が返るまで10秒かかっています。

Py3  

その間にはMDNSという見慣れない、なんとなく邪悪な感じのするマルチキャストが飛び交っています。

このZYNQ UbuntuはMDNSというプロトコルのパケットを送って、応答が返るまで次のTCPの返事を出さないようです。MDNSというのはよくわかりませんが、avahiというデーモンが何かをしているようなので、

/etc/init.d/avahi-daemon stop

で止めてしまいます。

すると、20ms弱で応答するようになりました。

Py2

一つの関数の呼び出しに20msは遅いかもしれませんが、これで良しとしましょう。

sun

わかったことをまとめますと、

  • 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には戻れないですね。

|

« 1G ADCボードで波形が取れた | トップページ | ST39VF801CというROMのCFIは間違っている »

コメント

コメントを書く



(ウェブ上には掲載しません)




« 1G ADCボードで波形が取れた | トップページ | ST39VF801CというROMのCFIは間違っている »