« BeagleBone Blackのハードウエア不具合 | トップページ | Windows 8.1の自動メンテナンスでSSDがデフラグされてしまう »

2014年3月 2日 (日)

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)」に以下のように図示されている。

Pruicss図の右側に並んでいる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を使えるようにするには以下を実現する必要がある。

  1. クロックを有効にしてハードウエアが動くようにする
  2. uio_prussモジュールを読み込ませる
  3. /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ならきれいな波形が得られる。

Tek0000

しかし整数分の1でない場合、サインカーブへの変換とDACを省略しているので15nsのジッターが発生してデジタルオシロの等価時間サンプリングではうまく計測できないが、 

Tek0001

アナログオシロだとジッターの存在が分る。時間軸は50ns/Divである。 

Image2993

ただし設定した周波数にフーリエスペクトラムの中心があるので、広帯域受信機などで確認すると比較的きれいな信号が受信できるはずだ。また圧電スピーカーをつないで可聴域の周波数に設定すれば、ジッターは無理だが、耳で音程の変化が確かめられる。

 

今回のまとめ

PRUを使うとハードウエアと比べてあまり遜色のないリアルタイム性とソフトウエアならではの柔軟性が得られることが確認できた。16F系のPICマイクロコントローラーのクロックが40倍速くなったような感じである。処理が多少遅くなるがARM側のメモリーやペリフェラルもアクセスできる。PICとWindows機をUSBでつないで実現していたようなことがBBB単独でできてしまう上、USBを使った場合に比べてはるかに高度な連携が可能になるはずだ。

当初情報が見つからなかったのでもっと難航すると思っていたが、PRUは意外と簡単に利用できることも分った。ただしこれは、分ってしまったから簡単と思えるだけなのかもしれない。思い返せばそれなりに手間も時間もかかった。

この記事を見た人が、私が費やしたのと同じ手間や時間をかけずにBBBのPRUを使い始められたなら幸いである。

|

« BeagleBone Blackのハードウエア不具合 | トップページ | Windows 8.1の自動メンテナンスでSSDがデフラグされてしまう »

BeagleBone Black」カテゴリの記事

IT」カテゴリの記事

趣味」カテゴリの記事

コメント

素晴らしい記事、ありがとうございます。

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分

コメントを書く



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


コメントは記事投稿者が公開するまで表示されません。



トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/543745/59226674

この記事へのトラックバック一覧です: BeagleBone BlackのPRUを試す:

« BeagleBone Blackのハードウエア不具合 | トップページ | Windows 8.1の自動メンテナンスでSSDがデフラグされてしまう »