Propeller Basic プログラミング
''原題:'BASIC Propeller Programming'


状態:この文書の翻訳作業は完了しています

原文:nvp5.pdf

翻訳者:caskaz

対象読者:PropBASICの初歩知識(この文書は初期[2010年 3月]のPropBASICを対象にしているので現在のバージョンとは違う記述があります)

ParallaxとBASICはそれ自体素晴らしいけどそれがマカロニとチーズみたいに一緒になった。これを使うとまるで魔法使いみたいな気分になれるよ。

PropellerではBASICを使えないので失望している人が少なからずいた。
私のようにずっとBASICを使ってきた人達や組込みプロジェクトに多くの時間を割いてきたグループはPBASICに感謝している。
今やSpinは私にとってオーケーであり、全く心地よいものとなった。
30年ほどBASICや他の言語でプログラミングしてきたがやはりBASICは挫折する人が少ないといえる。

さて、よいおしらせだ。”Bean”の名で知られるTerry Hittが率いたSX/B開発チームがPropeller専用のBASICコンパイラ(PropBASIC)を作った。
もう「BASICが使えないからPropellerは使わないよ」という言い訳はできないよ。

PropBASICとはなにか

はっきりしている事はPropBASICはアセンブラコードに変換する1パスコンパイラなのでBASICよりも速いということだ。

そのコードは一つ以上のSPINファイルだ。Version1でメインプログラムコードはひとつのCogに限定されているがこれで困ることはない。
Propellerアセンブラは強力なので他のCPUで数行のアセンブラコードを必要とするプロセスはPropellerでは一つのアセンブラコードですむからだ。
そのうえ、まだ7つのCogが残っていて、PropBASICはそれぞれにタスクを割り振る事ができる。そのうちにPropBASICはHub RAMのメモリを使って1個のCogメモリ2Kバイトの壁を越えるようコンパイルできるBill HenningのLMM(large memory model)アーキテクチャも実装するかもしれない。

SX/Bを使ったことのある人にとってはPropBASICはとてもよくわかり、少しの変更で今あるプロジェクトを移植することができるし、もしBASIC Stampを使ったことがあればPBASICに似ている印象を持つと思うよ。
私を含めて多くの人にとってBASIC StampからSX/Bへ乗り換えるのは大変なことなんだ。
でもBASIC StampからPropBASICに乗り換えるのはとっても簡単で楽しいことなんだ。
少し新しい事を学習しなくてはいけないけれど、すぐにマルチコアプロセッサを使えるようになるよ。

こんにちはPropBASIC

どの学習書でも必ず”Hello World”で始まるのがお決まりになってるけど、大きなプログラミングと格闘する前に基礎を覚えるのは大事だ。
マイクロコントローラの分野でそれに相当するのはLEDの点滅だろ?
LEDの点滅さえもできないのに、どうやってGPSを使った多軸ロボット制御なんてできるんだい?

ちらかって汚いプログラムリストは大嫌いだ。最後まできちんとしたプログラムをかく簡単な方法はテンプレートを使うことだ。沢山のプロジェクトを始めるのに使ったtemplate.pbasをダウンロードできる。
template.pbasを使ってPropBASICプログラムの内容を紹介するhello.pbasを書いてみたが、それはあとで披露しよう。
あえて言おう。
SX/BやBASIC Stampを使った事のある人ならPropBASICに乗り換えるのはとっても簡単だ。

デバイス設定

DEVICE		P8X32A, XTAL1, PLL16X
XIN		5_000_000

上記はPropBASICでは定番だ。
これは16倍PLLを使った外部クロックを入力するプロセッサをP8X32Aを宣言している。
外部クロック周波数は5MHzでシステムクロックは80MHzだ(5 x 16)

もちろん定番以外もある。時間にシビアでない用途ならRCモードを選べる.こいつは低パワーというおまけもついてる。
RCモードのシステムクロックはRCFASTで大体12MHz、RCSLOWで大体20kHzだ。
RCFASTの設定は

DEVICE		P8X32A

RCFASTの設定の場合はなにも指定しないけれどRCSLOWの場合はRCSLOWを指定しなければならない。
RCモードでは必ずPLLは1倍でXINは無視される。
繰り返すが、RC発信周波数はチップ単位で異なるのでRCモードは時間にシビアでない用途で使うべきだ。例えばシリアル通信が必要なプログラムではRCモードを使わないのが正しい。
スポックが今の文章を読んだら眉毛を少しつり上げるかもしれない。(カーク船長やドクター・マッコイに対してするように)
RCモードがシリアル通信に不向きなら、RCモードで動作しているPropellerチップはどうやってIDE(Propeller Tool)と通信しているのか?と。
よい質問だ。
実際IDEはダウンロードプロトコルの役割としてPropellerと拍子を合わせて、個々のチップに適応するためにIDEからボーレートを調整している。
だから、消費電流を減らすために低クロックスピードにしてシリアル通信を使いたい場合は下記のステップが必要になる。
・RCモードのクロックスピードを測定(FREQディレクティブはなし)
 オシロを使って出力パルスの巾を測定して計算
・XINの替わりにFREQディレクティブでRC周波数を修正

10msecのパルスを出力するプログラムを作って(speed_test.pbas)オシロでパルスを計ってみた。−やってみるとパルス巾は9.06msecだった。
これはプロセッサは予想より少し速いクロックで動作しているということだ。
以下のように測定したパルス巾を予想していた10msecで割った値をscale factorとして計算しよう。

Scale = 10.0/measured

私の場合、scale factorは1.1038だった。クロック周波数20kHzのRCSLOWを選択しているのでプログラムの先頭に下記の調整値を付け加える

DEVICE		P8X32A, RCSLOW
FREQ		22_075

FREQ設定でコンパイルすることにより、PAUSEやSEROUTでも正しい時間管理がされる。

定数”

テンプレートの次の項目は定数定義だ。数字とキャラクタ文字は同じように扱える。

OnTime		CON	250
OffTime	CON	750
IsOn		CON	1
IsOff		CON	0
Asterisk	CON	“*”

プログラムでSERIN,SEROUTを使う場合はボーモード定数の設定もここで扱う。

Baud		CON	“T9600”

上記ではSERIN,SEROUTをTrueモードで使う場合の定義を示している。
PropBASICでは定数はグローバルとして扱われ、どのCogでも使用できる。

I/Oピン

LED		PIN	16 LOW

PropBASICではI/O ピンの宣言はPBASIC2.5やリセット後にピン状態を定義しておくSX/Bとよく似ている。ピンのオプションはINPUT(default),OUTPUT,LOW,Highなどである。
通信プログラムでこのピン定義を使用している。

RX		PIN	31 INPUT
TX		PIN	30 HIGH

HIGHオプションの使用に注意:シリアル通信のTXピンをアイドル状態(output,high)にしておくようにスタートアップコードにおいている。

PropBASICはI/Oピンのグループを定義することもできる。例えばPropellerDemoBoard?のLEDはP16からP23に接続されている。これらのLEDをグループとして定義している。

LEDs		PIN	23..16 LOW

ピングループ定義で最初の数字がグループの最上位ピン(MSB)、2番目の数字がグループの最下位ピン(LSB)となっている。数字の順番は重要でグループが入力としてセットされている場合は読み出し、出力でセットされている場合は書き込みのビットが影響をうける。

PropBASIC1.0では出力ピングループにバイナリを割り当てることもできる。

 LEDs = %100000

上記はOK、しかし

 LEDs = 1 << position

はNG。これは将来実装されるかもしれない。現在のところは下記のようにする。

 __temp = 1 << position
 LEDs = __temp

変数の共有

PropBASICでは変数及び配列をHubに保存しており、それらはどのCogからもアクセスできる。
Hubの変数はbytes,words,longs,全部のタイプのarraysで定義される。

rxHead		HUB	Byte = 0
rxTail		HUB	Byte = 0
rxBuffer	HUB	Byte(64) = 0

PropBASICではHub変数の初期値を設定できるが、初期値設定されない場合はゼロにセットされる。(配列の場合は全部の要素)
Hub変数は通常の変数のようには扱えず、それらのread/writeには必ずRDxxxx/WRxxxxを使用する。(xxxxは変数のサイズ)

データの共有

変数と同じようにHubにあるデータも共有できる、そして32KバイトのRAMには大きなテーブルデータも作れてどのCogからもアクセスできる。
Hubのデータはbytes,words,longsとして、それぞれDATA,WORD,LDATAディレクティブで宣言する。

Zip4		DATA	%0001
		DATA	%0010
		DATA	%0100
		DATA	%1000

Hub変数のようにRDxxxx/WRxxxxを使ってHubのデータにアクセスする。
大きなテーブルデータの場合はFILEディレクティブを使って外部データを取り入れる。

SFX1		FILE	“BABY.WAV”
		DATA	0

タスク定義

PropBASICで2個以上のCogを使用して「裏プロセス」の処理を実行するにはTASK定義が必要です。BASIC Stampを使った経験があったらシリアルデータの入力データのwait処理で悩んだことがあると思う。SX/Bではシリアル入力データ処理の為に割り込みを使っていました。しかし、もう気にすることはない。

PropellerではHub変数を使ってシリアル入力処理を別のプロセッサに行わせます。TASK定義は単純だがタスクが起動する前に設定する必要がある。

SERIAL_RX	TASK

ローカル(Cog)変数

プログラムやタスクで使う変数はCogにとってローカルでLongとしてのみ定義できます。
Hub変数として配列宣言や初期値設定ができる。

Idx		VAR	Long

PropBASICはコンパイラによって生成されるアセンブラコードで使用されるいくつかのローカル変数(サブルーチンや関数内部で使うパラメータのような)を生成する。
コンパイル出力を見ると5個の変数__temp1〜__temp5があります。これらはPropBASICキーワード用に生成されたコードで使われます。又作成するPropBASICプログラムでこれらの変数を使用できますがその為には注意が必要だ。これらの変数はキーワードが使われたら変化してしまうから。
変数__param1〜__param4ぐらいは利用してもいいでしょう。これらはパラメータ渡しの時だけ使い、PropBASICキーワード用に生成されたコードで修正されたものを使ってはいけない。

サブルーチンと関数の宣言

始めに述べたようにPropBASICは1パスコンパイラでアセンブラコードを最適化しません。
良いニュースは出力コードを検査することでアセンブラを習得でき、コードスペースを最適化できることだ。
SX/Bの初期には意味がないと思われるコードを削除していたものだ。(PropBASICでもきっと同じ事が行われていると思う)
例えばPAUSEはよく使われる、このPAUSE 100のPASMが下記だ

	mov		__temp1, cnt
	adds		__temp1, _1msec
	mov		__temp2, #100
__L001
	waitcnt	__temp1, _1msec
	djnz		__temp2, #__L001

オーケー、もしPAUSEが出現する度にアセンブラコードを生成したら酷いコードになるだろう。これはサブルーチンにPAUSEをカプセル化したら解決できる。
SX/BのようにDELAY_MSという名前のシェルは下記

DELAY_MS	SUB	1

DELAY_MSは1個の引数が必要だ。Propeller変数は32bitなのでそれはとても長い遅延時間でもOKだ。
SX/Bを知ってる人にはPropBASICのFUNC宣言は少し違うと思うだろう。
1月にPropeller ForumメンバーがSIRCSのPropBASICへの移植について質問していた。
SIRCSオブジェクトはIRコードビットをビットカウントのようにリターンするものだ。
PropBASICは関数が返すパラメータ数を気にしない。(通常は1個)
それ故、定義は下記

SIRCS_RX	FUNC	0

メインプログラム

LED blinkerプログラム本体は大体下記のようになる

PROGRAM Start
Start:
	FOR idx = 1 TO 3
	  LED = IsOn
	  DELAY_MS OnTime
	  LED = IsOff
	  DELAY_MS OffTime
	NEXT
	DELAY_MS 1_000
	GOTO Start

PROGRAMディレクティブはユーザが作ったプログラムの始点と、さらに時度生成start-upコードの位置も示している。
Propellerではstart-upコードはディレクティブの指すラベルへジャンプする前にI/Oピンの入力・出力と状態の設定も簡単にやってのけている。

見てのとうり、コードはまったくのBASICだ。PBASICやSX/B、他の言語を扱った人達にとっても簡単だろうと思う。

サブルーチンと関数の中身を作る

サブルーチンと関数のコードはリストの最後に書くことになっているのでDELAY_MSという名前のサブルーチンをリストの前部で定義してコードを書いてみた。

SUB DELAY_MS
 PAUSE __param1
 ENDSUB

他の言語同様にサブルーチンは別のサブルーチンをcallすることもできる。ターミナルへのデータ送信のプログラムで2つのサブルーチンを書いた。

SUB TX_STR
 strAddr	VAR	__param2
 strChar	VAR	__param3
 strAddr = __param1
 DO
   RDBYTE	strAddr, strChar
     IF	strChar = 0 THEN EXIT
     TX_BYTE strChar
     INC	strAddr
 LOOP
 ENDSUB
SUB TX_BYTE
 SEROUT	TX, Baud, __param1
 ENDSUB

2番目のサブルーチンはSEROUTのシェルで、最初のサブルーチンはシリアルポートに文字を送信するのに使う。
文字のアドレスを引数にしてTX_STRをcallするとその引数は__param1に渡される。
TX_BYTEに文字を渡すのに__param1が必要なのでサブルーチン内部で__param2,__param3を使っている。
新しい変数を宣言しない理由はPropellerでは全部がRAMにあるからだ。
その為、できるだけ__paramx変数を使い変数宣言を少なくしてアセンブラコードスペースを節約するのはよい習慣だ。
多分キャラクタコードを得るのにRDBYTEを使ってるコードに気付いてると思うがPropBASICでは全ての文字がHub RAMにある。もちろん、DATAディレクティブで文字列の定義もできる。

Banner		DATA	“PropBASIC”, 0

関数のコードはサブルーチンコードと比較するとFUNC..ENDFUNCブロックと関数の呼出し側に1つ以上の戻り値(ENDFUNCの直前にRETURNをおく)があることを除けばよく似ている。下記はPropBASICで書いたSIRCS受信関数だ

FUNC GET_SIRCS
 irCode	VAR	__param1
 irBits	VAR	__param2
 COUNTERA NEG_DETECT, IR, 0, 1
 COUNTERB FREE_RUN, 0, 0, 1
Wait_Start:
 WAITPEQ IR, IR
 PHSA = 0
 WAITPNE IR, IR
 PHSB = 0
 WAITPEQ IR, IR 
 IF PHSA < BIT_S THEN Wait_Start
 irCode = 0
 irBits = 0
Check_Frame
 IF PHSB > MS_044 THEN IR_Done
Wait_Bit:
 IF IR = 1 THEN Check_Frame
 PHSA = 0
 WAITPEQ IR, IR
 irCode = irCode >> 1
Measure_Bit:
 IF PHSA > BIT_1 THEN
   irCode = irCode | $8000_0000
 ENDIF
 INC irBits
 IF irBits = 20 THEN IR_Done
 GOTO Check_Frame
IR_Done
 __temp1 = 32 &#8211; irBits
 irCode = irCode >> __temp1
 RETURN irCode, irBits
 ENDFUNC

まったくそのまんまのコードだと思う。これはアセンブリコードに変換されるので大変速い。だからRC-5のような別のIRプロトコルで実験したい場合はhigh-level-code(sircs_rx.pbas参照)を使った方がいいよ。

PropBASICはSIRCSの例のようにPASMにあるWAITPEQ、WAITPNEなどのSpinやPASMのキーワードもいくつか持っていることを指摘しておくよ。

タスクの中身を作る

タスクは別のCogで動いてるのでその構成はサブルーチンや関数よりも少し込み入ってる。
実際、タスクはそれ自身が1つのプログラムなのでその中でのみ定義されるサブルーチンや関数があったりもする。
これらの宣言やコードの全部がTASK..ENDTASKブロック内にある。

TASK SERIAL_RX
 rxb		VAR	__param1
 hPntr	VAR	__param2
 DO
   SERIN	RX, Baud, rxb
   RDBYTE	rxHead, hPntr
   WRBYTE	rxBuffer(hPntr), rxb
   INC	hPntr
   hPntr = hPntr & $3F
   WRBYTE	rxHead, hPntr
 LOOP
 ENDTASK

そのとうり、これが全部だ。タスクはCON,PIN,HUB宣言もできるしこれら全部の要素を含んでいる。
DO..LOOPの最初でSERINを使ってバイトデータを読んでいる。- PBASICやSX/Bでやってるように。
違いはこのタスクはそれ自身のCogで起動されシリアルデータを待っていてもメインプログラムの邪魔をしないことだ。
現在のヘッドポインタのデータをRDBYTEで読み出しrxBufferにそのオフセットを加えてWRBYTEで新しいシリアルデータを保存できる。
ヘッドポインタを+1してバッファ内に収まるように$3FとANDして、呼出し側が使っているHubメモリに書き戻している。

タスクをスタートするには2種類あるがたいていCOGSTARTを使う。

COGSTART	SERIAL_RX

別のCogが立ち上がり準備が整うまで十分な時間は必要なので、タスクで供給したデータにアクセスしようとする前に数ミリ秒待たせたいこともあるかもしれない。

PropBASICコンパイラはタスクの名前でSpinファイルも作れるので、同一フォルダにあるかもしれないSpinプログラムを上書きしないようにタスクの名前に注意する必要がある。
オーケー。シリアルデータを受信しバッファリングする方法はわかった。ではメインプログラムでそのデータを使うにはどうしたらいいのだろう?
バッファからデータを取り出す関数を書いたよ。

FUNC RX_BYTE
 rxh		VAR	__param1
 rxt		VAR	__param2
 rxchar	VAR	__param3
 DO
   RDBYTE	rxHead, rxh
   RDBYTE	rxTail, rxt
 LOOP UNTIL rxh <> rxt
 RDBYTE 	rxBuffer(rxt), rxchar
 INC	rxt
 rxt = rxt & $3F
 WRBYTE	rxTail, rxt
 RETURN	rxchar
 ENDFUNC

最初のループでヘッドとテイルポインタの値を比較してデータを得ている。
バッファが空だとこの値は等しく、この値が違うとバッファインデックスとしてテイルポインタを使って受信データを得る。そしてヘッドポインタで行ったようにテイルポインタを+1してHubメモリに書き戻している。

別のCogがシリアルバッファにデータを保存するやっかい事をやってくれているのでバッファが空でもメインプログラムの邪魔をする事はない。
シリアルバッファで待つバイト数を戻す関数があるよ。
もしゼロならバッファが空なので受信データをただ待つだけのRX_BYTEをスキップする。

FUNC RX_CHECK
 head		VAR	__param1
 tail		VAR	__param2
 bufcnt	VAR	__param3
 RDBYTE	rxHead, head
 RDBYTE	rxTail, tail
 IF head >= tail THEN
   bufcnt = head &#8211; tail
 ELSE
   bufcnt = tail &#8211; head
 ENDIF
 RETURN bufcnt
 ENDFUNC

これはバッファの先頭と最後尾間の差をリターンする。バッファはリング形式でtailはheadに追随し、headポインタがtailポインタより小さくなって負数にならないようにIF..THENを使っている。

誰でもアセンブラ?

難解なアセンブラ言語から解放してくれて速いプログラムをコーディングできるので沢山の人がこのコンパイラを楽しむと思う。
でもそれでもアセンブラうを使うのはやっぱり便利だ。
PropBASICでは複数行ならASM..ENDASMブロック、1行なら”\”を使ってコーディングできる。PropBASICはとってもナイスなコードを作ってくれるのであまりインラインアセンブラを使う必要はないけれど知っておいて損はないと思うよ。

まとめよう

ここだけで多様な機能をもつPropBASICを述べる事はできない。でもPropBASICとは何かとか始め方はわかってもらえたと思う。SX/B同様、条件付きコンパイルディレクティブもあるしPropBASICファイルに外部のアセンブラコードを読み込ませることもできる。
アプリケーション初期バージョンとしては本当によくできている。しかもプライスレスだ。

PropBASICの将来

一言でいえばワクワクしているんだ。
SX/Bを使ってた人達はもちろん、初心者にとってもこんなに便利なツールでプログラミングを始められることを伝えたい。
PropBASIC1.0の機能はSX/Bのよりも少し進んでいる。
アセンブラ言語なみのの速度で走る大きなプログラムを作れるLMMへの移行などBeanに色んな提案をしてるけれども言語を拡張するには時間がかかる。


トップ   編集 凍結解除 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2012-01-04 (水) 08:10:06 (2208d)