BeagleBone BlackのPRUを試す
以前の記事のまとめに「Ti AM3359には、ADCのContinuous modeと呼ばれる動作モードやPRU (Programmable Real-Time Unit: 200MIPSのリアルタイム処理用マイクロコントローラー) と呼ばれるI/Oサブシステムがあるが、現在のカーネル3.8では利用できない。」と書いた。ADCのContinuous modeがカーネル3.8で利用できないのは概ね正しいと思うが、PRUについては誤りだった。比較的簡単に利用できる。
今回はPRUを使えるようにする方法を紹介する。正しく理解しているかどうか確信の持てない内容も一部含んでいるが、ご容赦いただきたい。利用した環境はカーネル3.8.13-bone28が動いているDebian wheezyである。他のディストリビューションでも本質的に同じだと思うが、カーネルのリリースが異なるとPRUをサポートする機能の仕様が違っていたり、あるいは仕様が同じでも不具合があったりする可能性がある点に留意して欲しい。
/*----------- -----------*/
PRU、PRUSSおよびPRU-ICSSについて
3つの呼び名が混在していて紛らわしいので、先ず整理しておく。これら3つのフルスペルは以下の通りだ。
- PRU: Programmable Real-time Unit
- PRUSS: Programmable Real-Time Unit Subsystem
- PRU-ICSS: Programmable Real-Time Unit and Industrial Communication Subsystem
PRU-ICSSは、後で触れる「am335x_pru_package」に収められた資料「AM335x PRU-ICSS Reference Guide (Literature Number: SPRUHF8A May 2012 –Revised June 2013)」に以下のように図示されている。
図の右側に並んでいるMII_RTやIEPなどが産業向け通信 (Industrial Communication) 用インターフェイスだが、今回はこれらについて深入りしない。PRUSSはこれら産業向け通信用インターフェイスを除いた部分を指しているか、あるいはそこまで厳密に区別せずにPRU-ICSSと同じ意味で使われているようだ。
一方PRUは、図に「PRUn Core (n=[0|1])」と示されているように、32ビットマイクロコントローラーそのものを指している。
BBBには自分で書いたプログラムを動かすことのできるCPUが3つ存在しているわけだ。それらはLinuxが動いているARM Cortex A8と2つのPRUである。今後これらを区別する必要がある場合、便宜上A8をARM、2つのPRUをPRU0とPRU1、または単にPRUと呼ぶことにする。
PRU-ICSSを有効化する
PRUを使えるようにするには以下を実現する必要がある。
- クロックを有効にしてハードウエアが動くようにする
- uio_prussモジュールを読み込ませる
- /sys/class/uioディレクトリに必要な項目をエクスポートさせる
上記の 2. と 3. は必須ではないのかもしれないが、後述するprussdrvドライバーを使うのに必要である。
modprobe uio_prussを行えと言っている資料を見かけるが、これは古いカーネルで有効だった手段のようだ。私の試した環境だと、これだけではうまく動かなかった。いくつか試した結果、target = <&pruss>; を記述したフラグメントを含むDTO (Device Tree Overlay) をCape Managerに読ませてやれば良いことが分った。
debian@arm:~/pru_dto$ lsmod
Module Size Used by
g_multi 56923 0
libcomposite 17447 1 g_multi
binfmt_misc 6560 1
debian@arm:~/pru_dto$ ls -l /sys/class/uio
total 0
debian@arm:~/pru_dto$ cat BB-P8-12-PRU-00A0.dts
/*
* pru dts file BB-P8-12-PRU-00A0.dts
*/
/dts-v1/;
/plugin/;
/ {
compatible = "ti,beaglebone", "ti,beaglebone-black";
/* identification */
part-number = "BB-P8-12-PRU";
version = "00A0";
exclusive-use =
"P8.12",
"pruss";
fragment@0 {
target = <&am33xx_pinmux>;
__overlay__ {
mygpio: pinmux_mygpio{
pinctrl-single,pins = < 0x30 0x0E >;
};
};
};
fragment@1 {
target = <&pruss>;
__overlay__ {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&mygpio>;
pru_p8_12 {
pin-names = "GPIO:PRU-P8.12";
gpios = < &gpio1 12 0 >;
};
};
};
};
debian@arm:~/pru_dto$ dtc -O dtb -o BB-P8-12-PRU-00A0.dtbo -b 0 -@ BB-P8-12-PRU-00A0.dts
debian@arm:~/pru_dto$ sudo -s
[sudo] password for debian:
root@arm:/home/debian/pru_dto# cp BB-P8-12-PRU-00A0.dtbo /lib/firmware/
root@arm:/home/debian/pru_dto# echo BB-P8-12-PRU > /sys/devices/bone_capemgr.*/slots
root@arm:/home/debian/pru_dto# exit
exit
debian@arm:~/pru_dto$ lsmod
Module Size Used by
uio_pruss 4284 0
g_multi 56923 0
libcomposite 17447 1 g_multi
binfmt_misc 6560 1
debian@arm:~/pru_dto$
debian@arm:~/pru_dto$ ls -l /sys/class/uio
total 0
lrwxrwxrwx 1 root root 0 Mar 2 13:52 uio0 -> ../../devices/ocp.3/4a300000.pruss/uio/uio0
lrwxrwxrwx 1 root root 0 Mar 2 13:52 uio1 -> ../../devices/ocp.3/4a300000.pruss/uio/uio1
lrwxrwxrwx 1 root root 0 Mar 2 13:52 uio2 -> ../../devices/ocp.3/4a300000.pruss/uio/uio2
lrwxrwxrwx 1 root root 0 Mar 2 13:52 uio3 -> ../../devices/ocp.3/4a300000.pruss/uio/uio3
lrwxrwxrwx 1 root root 0 Mar 2 13:52 uio4 -> ../../devices/ocp.3/4a300000.pruss/uio/uio4
lrwxrwxrwx 1 root root 0 Mar 2 13:52 uio5 -> ../../devices/ocp.3/4a300000.pruss/uio/uio5
lrwxrwxrwx 1 root root 0 Mar 2 13:52 uio6 -> ../../devices/ocp.3/4a300000.pruss/uio/uio6
lrwxrwxrwx 1 root root 0 Mar 2 13:52 uio7 -> ../../devices/ocp.3/4a300000.pruss/uio/uio7
これはPRU-ICSSを有効にし、PRU0に直結したEGPIO信号pr1_pru0_pru_r30_14がBBBのヘッダーP8.12で使えるようにするものだ。上で分るようにDTOを読み込ませることによりuio_prussモジュールが自動的に読み込まれ、/sys/class/uioディレクトリに必要な項目がエクスポートされている。なお.dtsファイルの書き方をちゃんと理解しているかどうか確信が持てないので、これで一応動いていることは確認しているが、何か潜在的な問題を含んでいるかもしれない。
なおBBBのヘッダーピン配置情報で、PRU-ICSS固有のI/Oがヘッダーのどこに出ているのか記載されていないものも少なくない。私が試した内では、ここにあるExcelスプレッドシート形式の情報が見やすいので良いと思う。
am335x_pru_packageの入手
後述するprussdrvやpasmなど、PRU-ICSS関連のツールやドキュメント一式を含むパッケージである。ビルドなどの作業は全てBBB上で実行可能なので、BBBのmicroSDにパッケージをcloneする。
debian@arm:/tmp$ git clone https://github.com/beagleboard/am335x_pru_package.git Cloning into 'am335x_pru_package'... remote: Reusing existing pack: 678, done. remote: Counting objects: 7, done. remote: Compressing objects: 100% (7/7), done. remote: Total 685 (delta 0), reused 0 (delta 0) Receiving objects: 100% (685/685), 8.23 MiB | 1.70 MiB/s, done. Resolving deltas: 100% (318/318), done. debian@arm:/tmp$
記事を準備するために /tmp ディレクトリ内にcloneした。パッケージにはドキュメントなどずっと取っておきたいファイルも含まれているが、/tmp ディレクトリの内容はLinux起動のタイミングでクリーンアップされてしまう可能性があるので都合が悪い。実際にはホームディレクトリなど勝手に消えない場所を選ぶべきである。
prussdrvドライバーの準備
BBBでCのプログラムがビルドできるように準備されていることが前提である。
debian@arm:/tmp$ cd am335x_pru_package/pru_sw/app_loader/interface/ debian@arm:/tmp/am335x_pru_package/pru_sw/app_loader/interface$ make CROSS_COMPILE="" gcc -I. -Wall -I../include -c -g -O0 -D__DEBUG -o debug/prussdrv.o prussdrv.c ar rc ../lib/libprussdrvd.a debug/prussdrv.o gcc -I. -Wall -I../include -c -O3 -mtune=cortex-a8 -march=armv7-a -o release/prussdrv.o prussdrv.c ar rc ../lib/libprussdrv.a release/prussdrv.o gcc -I. -Wall -I../include -c -fPIC -g -O0 -D__DEBUG -o debug/prussdrv_PIC.o prussdrv.c gcc -shared -o ../lib/libprussdrvd.so debug/prussdrv_PIC.o gcc -I. -Wall -I../include -c -fPIC -O3 -mtune=cortex-a8 -march=armv7-a -o release/prussdrv_PIC.o prussdrv.c gcc -shared -o ../lib/libprussdrv.so release/prussdrv_PIC.o debian@arm:/tmp/am335x_pru_package/pru_sw/app_loader/interface$ sudo make install [sudo] password for debian: install -m 0755 -d /usr/local/lib install -m 0755 -d /usr/local/include install -m 0644 ../lib/* /usr/local/lib install -m 0644 ../include/prussdrv.h ../include/pruss_intc_mapping.h /usr/local/include debian@arm:/tmp/am335x_pru_package/pru_sw/app_loader/interface$ sudo ldconfig -v -l /usr/local/lib/libprussdrv.so libprussdrv.so -> libprussdrv.so debian@arm:/tmp/am335x_pru_package/pru_sw/app_loader/interface$
pasmアセンブラーの準備
pasmはPRU上で動かすプログラムのためのアセンブラーである。元のままpasmをビルドすると、一部サンプルプログラムのビルドに失敗するので /tmp/am335x_pru_package/pru_sw/utils/pasm_source/ pasm.h に以下の修正を加える。
--- a/pru_sw/utils/pasm_source/pasm.h
+++ b/pru_sw/utils/pasm_source/pasm.h
@@ -73,7 +73,8 @@ typedef struct _LABEL {
} LABEL;
/* Source File Record */
-#define SOURCE_NAME 64
+/* #define SOURCE_NAME 64 */
+#define SOURCE_NAME 128
#define SOURCE_BASE_DIR 256
typedef struct _SOURCEFILE {
struct _SOURCE *pParent; /* The file that included this file */
修正の後ビルドする。
debian@arm:/tmp/am335x_pru_package/pru_sw/app_loader/interface$ cd ../../utils/pasm_source/ debian@arm:/tmp/am335x_pru_package/pru_sw/utils/pasm_source$ ./linuxbuild debian@arm:/tmp/am335x_pru_package/pru_sw/utils/pasm_source$ cd .. debian@arm:/tmp/am335x_pru_package/pru_sw/utils$ ln -s pasm pasm_2 debian@arm:/tmp/am335x_pru_package/pru_sw/utils$
パッケージに含まれるサンプルプログラムのMakefileでpasm_2を指定している場合があるので、シンボリックリンクを作っておく。
PRUのプログラム開発環境はアセンブラーのみである。将来的にCが使えるようになるかもしれないが、リアルタイム性を充分活かすにはどのような機械語が生成されるか把握しておく必要があるので、Cの利用可能範囲は限定的だろう。
サンプルプログラムによる確認
今までの作業が正常に行えたかどうか、パッケージに準備されているサンプルプログラムをビルド・実行して確かめてみる。
debian@arm:/tmp/am335x_pru_package/pru_sw/utils$ cd ../example_apps/ debian@arm:/tmp/am335x_pru_package/pru_sw/example_apps$ make CROSS_COMPILE="" (中略) debian@arm:/tmp/am335x_pru_package/pru_sw/example_apps$ cd bin debian@arm:/tmp/am335x_pru_package/pru_sw/example_apps/bin$ sudo ./PRU_PRUtoPRU_Interrupt INFO: Starting PRU_PRUtoPRU_Interrupt example. INFO: Initializing example. INFO: Executing example on PRU0. INFO: Executing example on PRU1. INFO: Waiting for HALT command. INFO: PRU0 completed transfer. INFO: Waiting for HALT command. INFO: PRU1 completed transfer. Example executed succesfully. debian@arm:/tmp/am335x_pru_package/pru_sw/example_apps/bin$
上のように「...succesfully.」と表示されればOKだ。
サンプルプログラムは他にPRU_memAccess_DDR_PRUsharedRAMとPRU_memAccessPRUDataRamがある。
実際に動かせるサンプルはこれら3つだけのようだ。他にpru_sw/old_example/ ディレクトリに多数サンプルがあり、ターゲットのSoCが異なるのでこのままビルド・実行はできないが、PRUプログラムコーディングの参考資料になりそうである。
PRU用デバッガー
アセンブラーで書いたプログラムで問題が起きたとき、デバッガーが無いとかなり辛い。パッケージに含まれているドキュメント「04-CCS_PRU_Debugger-training.pdf」にCCS 5.xが紹介されているが、デバッグに使うには有償版のツールとJTAG接続するためのハードウエアが要るようだ。
もっと手軽に利用できるものが無いかと探すと「Prudebug」と言うデバッガーが見つかった。これはBBB上で動かすものでsourceforgeから入手可能である。
debian@arm:/tmp$ wget http://cznic.dl.sourceforge.net/project/prudebug/prudebug-0.24.tar --2014-03-02 15:22:52-- http://cznic.dl.sourceforge.net/project/prudebug/prudebug-0.24.tar Resolving cznic.dl.sourceforge.net (cznic.dl.sourceforge.net)... 217.31.202.30, 2001:1488:ffff::30 Connecting to cznic.dl.sourceforge.net (cznic.dl.sourceforge.net)|217.31.202.30|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 71680 (70K) [application/octet-stream] Saving to: `prudebug-0.24.tar' 100%[================================================>] 71,680 128K/s in 0.5s 2014-03-02 15:22:53 (128 KB/s) - `prudebug-0.24.tar' saved [71680/71680] debian@arm:/tmp$ tar -xf prudebug-0.24.tar debian@arm:/tmp$
軽量だが必要な機能は一応そろっている。欲を言えばレジスターの値を書き換える機能が欲しい。ターゲット上で動かしているので反応が早く、コマンドラインの操作が苦にならないなら充分使えるだろう。
2014/3/3追記:
デバッガーのHaltコマンドやシングルステップでPRUを止めた状態にして、31あるPRU_ICSS_PRU_DEBUGレジスターの内容を書き換えるとPRU本体のレジスターに反映されるようだ。上記「欲を言えばレジスターの値を書き換える機能が欲しい。」はこのことに気付かずに書いていたので、的外れだった。ただしIP (Instruction Pointer) の値を書き換える簡単な方法は無さそうで、これはデバッガーの問題と言うよりもPRUの仕組みがそうなっている、と言うことである。
ただし現在のVersion 0.24には一部命令のディスアセンブルが正常に行われない問題と、一部のターミナルでバックスペースキーが正しく扱われない問題がある。そのうち修正されると思うが、当面は以下の修正を行った上ビルドすればよいはずだ。
--- cmdinput.c 2013-06-24 10:48:27.000000000 +0900
+++ cmdinput.c 2014-03-01 15:40:01.573356803 +0900
@@ -34,7 +34,7 @@
c = getchar();
// check for backspace
- if (c == 0x08) {
+ if (c == 0x08 || c == 0x7F) {
if (i != 0) {
putchar(0x08);
putchar(' ');
--- da.c 2013-06-22 11:06:07.000000000 +0900
+++ da.c 2014-03-01 14:41:58.055778263 +0900
@@ -40,7 +40,8 @@
// disassemble the inst instruction and place string in str
void disassemble(char *str, unsigned int inst)
{
- unsigned char OP, ALUOP, Rs2Sel, Rs2, Rs1Sel, Rs1, RdSel, Rd, IO, Imm, Imm2, SUBOP, Test;
+ unsigned short Imm;
+ unsigned char OP, ALUOP, Rs2Sel, Rs2, Rs1Sel, Rs1, RdSel, Rd, IO, Imm2, SUBOP, Test;
unsigned char LoadStore, BurstLen, RxByteAddr, Rx, Ro, RoSel, Rb;
short BrOff;
char tempstr[50];
@@ -172,13 +173,13 @@
if (IO) {
if (Test == 7) {
- sprintf(str, "QB%s %d", f5_inst[Test], BrOff);
+ sprintf(str, "QB%s %d", f4_inst[Test], BrOff);
} else {
sprintf(str, "QB%s %d, R%u%s, %u", f4_inst[Test], BrOff, Rs1, sis[Rs1Sel], Imm);
}
} else {
if (Test == 7) {
- sprintf(str, "QB%s %d", f5_inst[Test], BrOff);
+ sprintf(str, "QB%s %d", f4_inst[Test], BrOff);
} else {
sprintf(str, "QB%s %d, R%u%s, R%u%s", f4_inst[Test], BrOff, Rs1, sis[Rs1Sel], Rs2, sis[Rs2Sel]);
}
比較的コンパクトで読みやすいソースなので、必要なら自分で機能を足すことを考えても良いだろう。
debian@arm:/tmp/prudebug-0.24$ make CC=gcc gcc -c -o prudbg.o prudbg.c gcc -c -o cmdinput.o cmdinput.c gcc -c -o cmd.o cmd.c gcc -c -o printhelp.o printhelp.c gcc -c -o da.o da.c gcc -c -o uio.o uio.c gcc prudbg.o cmdinput.o cmd.o printhelp.o da.o uio.o -o prudebug debian@arm:/tmp/prudebug-0.24$ sudo ./prudebug PRU Debugger v0.24 (C) Copyright 2011, 2013 by Arctica Technologies. All rights reserved. Written by Steven Anderson Using UIO PRUSS device. Processor type AM335x PRUSS memory address 0x4a300000 PRUSS memory length 0x00040000 offsets below are in 32-bit word addresses (not ARM byte addresses) PRU Instruction Data Ctrl 0 0x0000d000 0x00000000 0x00008800 1 0x0000e000 0x00000800 0x00009000 PRU0> r Register info for PRU0 Control register: 0x00000001 Reset PC:0x0000 STOPPED, FREE_RUN, COUNTER_DISABLED, NOT_SLEEPING, PROC_DISABLED Program counter: 0x0000 Current instruction: LBCO R0, C4, 4, 4 R00: 0x00000000 R08: 0xe47a8e93 R16: 0xf7446e17 R24: 0x6f77f437 R01: 0x0002202c R09: 0xf30928bf R17: 0x00000012 R25: 0x2d6f7b4d R02: 0xf5593981 R10: 0x9f2a2b3e R18: 0xbbba5ba8 R26: 0x7f295bac R03: 0xa7455f3a R11: 0x3dc484e6 R19: 0x3b62e27d R27: 0x7bcd97cc R04: 0xeb23982e R12: 0xa0e6779c R20: 0x57d1297d R28: 0x72b6f356 R05: 0xbd16a75f R13: 0x972a0529 R21: 0xfce00fde R29: 0x07b90c8f R06: 0x3aeae05f R14: 0xd892854f R22: 0xe4214f7b R30: 0xff8d63e3 R07: 0xb2168a17 R15: 0xfd7fce7b R23: 0xfb9eb5bf R31: 0x00000000 PRU0> q Goodbye. debian@arm:/tmp/prudebug-0.24$
Pythonから使えるようにする
am335x_pru_packageにはctypesモジュールを利用してprussdrvをPythonから使えるようにするインターフェイスが pru_sw/app_loader/python/prussdrv/ ディレクトリに用意されているが、あまりメンテナンスされていないようだ。最新のprussdrvに合わせるためいくつか手直しが必要である。
--- a/pru_sw/app_loader/python/prussdrv/clib.py +++ b/pru_sw/app_loader/python/prussdrv/clib.py @@ -47,7 +47,8 @@ prototype( 'pru_enable', [c_uint] ) prototype( 'pru_write_memory', [c_uint, # pru_ram_id c_uint, # wordoffset POINTER(c_uint),# memarea - c_uint] ) # bytelength + c_uint], # bytelength + c_uint ) # wordlength prototype( 'pruintc_init', [POINTER(tpruss_intc_initdata)] ) prototype( 'get_event_to_channel_map', [c_uint], c_short ) prototype( 'get_channel_to_host_map', [c_uint], c_short ) @@ -72,6 +73,6 @@ prototype( 'exec_program', [c_int, c_char_p] ) # non-pthread capable Cython implementation of python is used). # See prussdrv.InterruptHandler for a valid way to use Python as a callback for # an interrupt handler. -prototype( 'start_irqthread', [c_uint, # host_interrupt - c_int, # priority - prussdrv_function_handler] ) +##prototype( 'start_irqthread', [c_uint, # host_interrupt +## c_int, # priority +## prussdrv_function_handler] )
pru_write_memoryは書き込んだワード数を返すがそれに対応できていないので、ゼロ以外の値が返された場合の処理で実行時エラーになってしまう。またstart_irqthreadはprussdrvからは無くなったようだ。コメントアウトしておかないとこれも実行時エラーになる。
インストーラーは準備されていないので、setup.pyを書いてインストールする。
debian@arm:/tmp/am335x_pru_package/pru_sw/app_loader/python$ cat setup.py #!/usr/bin/env python # prussdrv setup script print "Installing prussdrv" try: from distutils.core import setup setup(name='prussdrv', version='1.0', packages=['prussdrv'], ) print "Finished installing!" except Exception, e: print "Install failed with exception:\n%s" % e debian@arm:/tmp/am335x_pru_package/pru_sw/app_loader/python$ sudo python setup.py install Installing prussdrv running install (中略) Finished installing! debian@arm:/tmp/am335x_pru_package/pru_sw/app_loader/python$
Pythonで少し実用的なプログラムを書いてみる
サンプルプログラムはコンソールにメッセージを表示するだけなので、実際にPRUが動いているかどうかイマイチ確信が持てなかった。先に準備したDTOで使えるようにしたEGPIO信号pr1_pru0_pru_r30_14 (P8.12) に任意の周波数のパルスを出せるようにしてみよう。これはDDS (Direct Digital Synthesizer) からサインカーブへの変換とDACを省略したものである。
PRU用のプログラムは至ってシンプルだ。
debian@arm:~/python/pru_examples/dds$ cat dds.p // 1 bit DDS: Each loop iteration takes 3 instruction cycles = 15ns, i.e. 66.666MHz .origin 0 .entrypoint START START: LBCO r0, C4, 4, 4 // Load SYSCFG reg. CLR r0, r0, 4 // Clear STANDBY_INIT -- 0 = Enable OCP master ports. SBCO r0, C4, 4, 4 // Save new content to SYSCFG reg. LDI r0, 0 // Setup r0 to point the data ram base address MOV r3, 0x00020280 // Setup r3 to point SECR0 MOV r4, 1 << 22 // Load event bit for ARM_PRU0_INTERRUPT to r4 LDI r1, 0 // Set initial frequency to zero NEXT: ADD r2, r2, r1 // Add frequency value to phase accumulator LSR r30, r2, 31-14 // Copy r2[31:17] to r30[14:0] -- only bit 14 to be used QBBC NEXT, r31, 31 // Loop until an event comes from ARM // Received an event! LBBO r1, r0, 0, 4 // load the new frequency value SBBO r4, r3, 0, 4 // Clear the ARM event by writing r4 content to SECR0 QBA NEXT // Continue the loop debian@arm:~/python/pru_examples/dds$ cat Makefile COMPILER=/tmp/am335x_pru_package/pru_sw/utils/pasm -b FILENAME=dds .PHONY: clean all all: $(COMPILER) $(FILENAME).p clean: rm -rf $(FILENAME).bin debian@arm:~/python/pru_examples/dds$ make /tmp/am335x_pru_package/pru_sw/utils/pasm -b dds.p PRU Assembler Version 0.84 Copyright (C) 2005-2013 by Texas Instruments Inc. Pass 2 : 0 Error(s), 0 Warning(s) Writing Code Image of 15 word(s) debian@arm:~/python/pru_examples/dds$
中核となる処理は NEXT: ラベルに続く3命令である。どれも1クロックで実行可能な命令なので、DDSのクロック周波数は200 ÷ 3 = 66.66...MHz だ。また32ビットの周波数分解能があるので、最小周波数ステップは 200e6 ÷ 3 ÷ 4294967296 = 0.015522Hz である。
PRUインターフェイス用のオブジェクトをdds.pyモジュールに定義した。
debian@arm:~/python/pru_examples/dds$ cat dds.py import ctypes, prussdrv, os, sys class bbb_dds: def __init__(self): binpath = os.path.join(sys.path[0], "dds.bin") prussdrv.init() prussdrv.open(prussdrv.PRU_EVTOUT_0) prussdrv.pruintc_init(prussdrv.constants.getPRUSS_INTC_INITDATA() ) prussdrv.exec_program(0, binpath) def set_freq(self, Hz): B32 = 0x100000000 # 2^32 B31 = B32 / 2 # 2^31 FS = 200e6 / 3 # sampling frequency = 66.666667MHz ifreq = int(round(B32 * Hz / FS) ) % B32 afreq = (ctypes.c_ulong*1)() afreq[0] = ifreq if ifreq < B31 else B32 - ifreq prussdrv.pru_write_memory( 0, 0, ctypes.cast(afreq, ctypes.POINTER(ctypes.c_ulong) ), 4) prussdrv.pru_send_event(22) # 22 = event number for ARM_PRU0_INTERRUPT return afreq[0] * FS / B32 def cleanup(self): prussdrv.pru_reset(0) prussdrv.pru_disable(0) prussdrv.exit() class open_dds: """Usage: with open_dds() as...""" def __enter__(self): self.dds_obj = bbb_dds() return self.dds_obj def __exit__(self, type, value, traceback): self.dds_obj.cleanup() debian@arm:~/python/pru_examples/dds$
こうすればメインのプログラムはユーザーインターフェイスに専念できるので、見通しの良いコードにできる。
debian@arm:~/python/pru_examples/dds$ cat dds_test.py
import dds, sys
with dds.open_dds() as d:
while True:
print "Enter frequency in Hz (suffix k=1e3, M=1e6; 0 to quit): ",
reply = sys.stdin.readline().strip()
r = reply[:-1]
if reply.endswith('k'):
multi = 1e3
elif reply.endswith('M'):
multi = 1e6
else:
multi = 1.0
r = reply
try:
freq = multi * float(r)
if freq <= 0:
break
print " New frequency set to %.6eHz" % d.set_freq(freq)
except ValueError:
print " Invalid frequency format: %s" % reply
except:
raise
debian@arm:~/python/pru_examples/dds$
wxpythonなど適当なGUIフレームワークを使えば現代的で見栄えの良い画面にできると思うが、とりあえず機能が確認できれば良いのでCLIである。
debian@arm:~/python/pru_examples/dds$ sudo python dds_test.py Enter frequency in Hz (suffix k=1e3, M=1e6; 0 to quit): 33.33333333M New frequency set to 3.333333e+07Hz Enter frequency in Hz (suffix k=1e3, M=1e6; 0 to quit): 10M New frequency set to 1.000000e+07Hz Enter frequency in Hz (suffix k=1e3, M=1e6; 0 to quit): 0 debian@arm:~/python/pru_examples/dds$
周波数が66.66...MHzの整数分の1ならきれいな波形が得られる。
しかし整数分の1でない場合、サインカーブへの変換とDACを省略しているので15nsのジッターが発生してデジタルオシロの等価時間サンプリングではうまく計測できないが、
アナログオシロだとジッターの存在が分る。時間軸は50ns/Divである。
ただし設定した周波数にフーリエスペクトラムの中心があるので、広帯域受信機などで確認すると比較的きれいな信号が受信できるはずだ。また圧電スピーカーをつないで可聴域の周波数に設定すれば、ジッターは無理だが、耳で音程の変化が確かめられる。
今回のまとめ
PRUを使うとハードウエアと比べてあまり遜色のないリアルタイム性とソフトウエアならではの柔軟性が得られることが確認できた。16F系のPICマイクロコントローラーのクロックが40倍速くなったような感じである。処理が多少遅くなるがARM側のメモリーやペリフェラルもアクセスできる。PICとWindows機をUSBでつないで実現していたようなことがBBB単独でできてしまう上、USBを使った場合に比べてはるかに高度な連携が可能になるはずだ。
当初情報が見つからなかったのでもっと難航すると思っていたが、PRUは意外と簡単に利用できることも分った。ただしこれは、分ってしまったから簡単と思えるだけなのかもしれない。思い返せばそれなりに手間も時間もかかった。
この記事を見た人が、私が費やしたのと同じ手間や時間をかけずにBBBのPRUを使い始められたなら幸いである。
| 固定リンク
「趣味」カテゴリの記事
- カメラのファインダーについて考える(2016.02.29)
- 空間フィルターを使った天体写真の画質改善(2015.01.22)
- 水星と金星のランデブー(2015.01.10)
- カメラのノイズと解像感(2015.01.09)
- Fujifilm X-M1 とそのアクセサリー(2014.12.14)
「IT」カテゴリの記事
- DSDはピアノ・ピアニッシモを奏でられるのか?(2016.08.01)
- SATAアダプターを交換した(2014.06.25)
- ReFSを使う(2014.06.22)
- PSUを交換したらメモリーの問題も解決した(2014.06.16)
- Windows 8/8.1: SATAモード変更 (IDE⇔AHCI⇔RAID) に再インストールもregeditも要らない(2014.06.15)
「BeagleBone Black」カテゴリの記事
- DSDはピアノ・ピアニッシモを奏でられるのか?(2016.08.01)
- 最終回: microSD カードの寿命を調べる(2015.07.27)
- 続報3: microSD カードの寿命を調べる(2015.05.02)
- 続報2: microSD カードの寿命を調べる(2015.03.01)
- 続報: microSD カードの寿命を調べる(2015.02.21)
この記事へのコメントは終了しました。





コメント
素晴らしい記事、ありがとうございます。
RPiかBBBで少し気持ちが揺れていたのですが、PRUの存在、いやこの記事が決定打になりました。得難い情報をご丁寧に説明してくださって感謝の言葉もございません。
投稿: くらはし | 2014年3月 7日 (金) 18時52分
くらはし さん、
そう言っていただくと記事を書いた甲斐があります。
PRUはかなり強力ですね。有効活用できる用途があれば凄いことができると思うのですが、ddsモドキの次のアイデアが出てきません...
ちなみにRPは音声付動画を表示させるような用途だとかなりイイのではないかと思います。使っているSoCがセットトップボックス用だったはずなので。
投稿: エンジニア | 2014年3月 7日 (金) 21時54分
ご返事ありがとうございます。
PRUを音声信号処理方面に使おうと思っていたのですが、RPi、特にRPi+オーディオカードはいろいろ遊べそうですね。私は耳が悪いので音楽鑑賞に耐えるようなダイナミックレンジコンプレッサを作りたいのですがFFTとFIRフィルタを一つで済ませるのはしんどいのでFFTだけでもdsPICを外付けしようか…などと考えているところです。
今後の記事も楽しみにしております。お礼が遅くなり申しわけありませんでした。
投稿: くらはし | 2014年5月 4日 (日) 19時16分