はじめに

「SKBasic」は、8bit, 16bitマイコンでの動作が可能な省リソース設計の組み込みBasicインタプリタです。今回は、これを無線プロトコルスタックと結合して、「インタプリタ内蔵無線モジュール」を実現しました。

従来の無線モジュール製品では、外部のMCUからUARTやSPIといったインターフェイスを介して、無線通信を制御する必要がありました。SKBasicを組み込むことで、無線モジュール内部でBasicプログラムが実行可能になり、制御用の外部MCUが必要なくなります。

またSKBasicはインタプリタなので、プログラムを変更するたびにフラッシュにオブジェクトを書き直す必要はありません。UARTのテキストインターフェイスを使って、内蔵MCU上で簡単にプログラムを作成、実行、変更できます。さらに簡易ファイルシステムも装備しており、コードの保存やロード、自動実行が可能です。

これまでは、操作を待つだけの受動的な動きしかできなかった無線モジュールですが、「SKBasic」の登場で、ようやく、スタンドアロンでの自律的な動作という自由を手にいれました。

リポジトリ

SKBasicはGitHubで公開中です。 https://github.com/umeda-skyley/SKBasic-for-STM32-MbedOS

起動と終了

SKコマンドモードとSKBasicモードがあります。起動直後はSKコマンドモードになっており、以下のように入力することでSKBasicモードへ遷移します。

SKBASIC(enter)

SKBasic v0.9
READY
>

SKBasicモードでexit命令を実行するとSKコマンドモードへ戻ります。

> exit(enter)
OK

ダイレクトコマンド

SKBasicモードのプロンプトから実行可能な命令をダイレクトコマンドと言います。主にBasicプログラムに対する表示、実行、保存などの命令があります。
また既存のSKコマンドは従来の書式のまま実行が可能です。
コマンドは大文字・小文字の区別がありません。
コマンドサンプル中の"(enter)"はEnterキーの押下を表します。

list

プログラムリストの表示

>list(enter)

110 '  Test of KLBasic functions and operators
120 '
1020 dim arr(10)
1030 failed = 0
1100 data -5, -4, -3, -2, -1, 0
1105 data 1, 2, 3, 4, 5
1110 data 6, 7, 8, 9, 10, 11, 12, -100
.
.
.

list vars

使用中のプログラム変数の表示

>list vars(enter)

List of variables --
arr()               failed              c                   n
x

>

list ports

全組み込み変数の表示

>list ports(enter)

List of ports --
s01           s02           s03           s04           s05
s06           s07           s08           s09           s0a
s0b           s0c           s0d           s0e           s0f
s10           s11           s12           s13           s14
s15           s16           s17           s18           s19
s1a           s1b           s1c           s1d           s1e
s1f           s20           s21           s22           s23
s24           s25           s26           s27           s28
sff           timer         erxorigin     erxdst        erxmsgid
erxselector   erxrssi       erxlen        eackstatus    eackdst
eackmsgid


>

run

プログラムの実行

new

プログラムの消去

>new(enter)

This will erase the current program!  Are you sure? y

Program erased.
>

cont

STOP文で中断されたプログラムを再開します。

clear

変数の内容を消去します。

>list vars(enter)

List of variables --
arr()               failed              c                   n
x

>? c(enter)
2

READY
>? n(enter)
10

READY
>? x(enter)
5

READY
>clear(enter)

>list vars(enter)

List of variables --
arr()               failed              c                   n
x

>? c(enter)
0

READY
>? n(enter)
0

READY
>? x(enter)
0

READY
>

free

空きメモリの表示

>free(enter)

Program memory: 8191 bytes free
Variable memory: 2047 bytes free
Dynamic memory pool (arrays): 1000 bytes free

>

load

flashディスクに保存されているプログラムの読み込み

>load label_test1.bas(enter)

>

save

flashディスクにプログラムを保存します ファイル名をautorun.basで保存した場合、次回SKBasic起動時に自動的に実行します。

>save SKBasicTest.bas(enter)
>save autorun.bas(enter)

delete

flashディスクに保存されているプログラムの削除

>delete autorun.bas(enter)

format

flashディスクの消去

files

flashディスクに保存されているプログラムの表示

>files(enter)

SKBasicTest2.bas
SKBasicTest3.bas
rxdata_ack_test2.bas
SKBasicTest4.bas
SKBasicTest5.bas
autorun.bas
wait_test.bas
label_test1.bas
SKBasicTestLabel.bas

95232 bytes available.
>

SKコマンド 

既存のSKコマンドは従来の書式そのままで入力を受け付けます。

>SKINFO(enter)
EINFO 0002 FFFF EA60 21

プログラムコマンド

Basicプログラム中で実行可能な命令をプログラムコマンドと呼びます。

data

プログラム中に数値データを列挙します。
後述のread文で読み込みます。

10 data 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
20 restore
30 read first
40 print "first =";first
50 read second
60 print "second =";second

read

data文で列挙したデータを変数に読み込みます。
読み込み前に必ずrestoreする必要があります。

restore

data文で列挙したデータの読み込み位置を先頭にセットします。
restore文実行後は必ずdata文の先頭から読み込むのでプログラム中に何回でも再読み込みが可能です。

label

行番号にラベルを付与します。GOTO文やGOSUB文の参照先に指定出来ます。

100 label "jump_to"

goto

プログラムの実行位置を指定された行番号・またはラベル名に変更します。

10 goto 100
10 goto "jump_to"

gosub

プログラムの現在の実行位置をスタックに置いた上で実行位置を指定された行番号・またはラベル名に変更します。(サブルーチンコール)

10 gosub 9000

return

サブルーチンから戻ります。

9000 return

if then else

条件分岐をします。

if 条件式 then 行番号 else 行番号
if 条件式 then 命令文
>list

100 for i=1 to 10
110   if (i - ((i / 2) * 2)) = 0 then print "num ";i;" is even"
120 next i

>run

num 2  is even
num 4  is even
num 6  is even
num 8  is even
num 10  is even

input

コンソールから数値を入力し変数に格納します。

>input a(enter)
? 3


READY
>? a(enter)
3

READY
>

print

文字列や変数値をコンソールに出力します。セミコロン(;)を使用する事により連結出力が可能です。

print "a = ";a

?

printの省略形です

? "a =";a

for next

開始値から終了値まで繰り返します。
next文の変数は省略出来ません。

10 'test1
20 for i = 1 to 10
30   sum = sum + i
40 next i
50 print "sum = ";sum

stop

実行中のプログラムを中断します。CONTコマンドにより再開します。

end

プログラムの終了を宣言します。

rem

プログラム中にコメントを記述します。プログラムの実行に何の影響も与えません。

'

remの省略形です。

10 'test1
20 for i = 1 to 10
30   sum = sum + i
40 next i
50 print "sum = ";sum

dim

数値変数の配列を宣言します。

10 dim attr(10)
20 for i=1 to 10
30   attr(i)=i
40 next i

tron

現在実行しているプログラムの行番号を逐一表示します。

troff

プログラムの行番号表示を停止します。

>list

10 tron
20 for i = 1 to 10
30   sum = sum + i
40 next i
50 troff

>run

[20] [30] [40] [30] [40] [30] [40] [30] [40] [30] [40] [30] [40] [30] [40] [30] [40] [30] [40] [30] [40] [50]

>

while endwh

条件式が偽になるまで繰り返します。

>list

10 c=0
20 while c < 10
30   input c
40 endwh

>run

? 1

? 5

? 10



>

wait

指定した時間だけプログラムの実行を待機します(単位はミリ秒で精度は10ミリ秒)。
待機時間中もプロトコルは動作を継続していますので、途中でデータを受信した場合はRXDATAハンドラが呼ばれます。

10 on RXDATA gosub 9000
20 wait 10000
30 print "receive ";count;"packets"
40 end
9000 '
9010 print "rxdata ";erxmsgid
9020 count = count + 1
9030 return

sleep

指定時間のスリープをします(単位は秒)
スリープ中はプロトコルも動作が停止し、送受信は行われなくなります。

10 print "sleep 10 secs"
20 sleep 10
30 print "wake"

strcat

文字列の連結をします

strind

文字列の指定バイト目の値を設定します。

>a$="abcdefghijklmn"

READY
>strind(a$, 5)=103

READY
>? a$
abcdegghijklmn

READY
>

on gosub

割り込みサブルーチンを指定します。
現在2つの識別子をサポートしています。

on rxdata gosub

データ受信割り込みのサブルーチンを指定します。

on ack gosub

ACK受信割り込みのサブルーチンを指定します。

SKSEND

SKSEND
<ACK>
1=送信相手からのAckを要求します。
0=Ackを要求しません。
<SELECTOR>
セレクタ番号
$3e8(=1000)以上の値が指定可能
<ADDR>
送信相手先ID($0001 - $ffef)
<DATALEN>
送信データ長(省略可能)
<DATA>
送信データ
100 msgid = SKSEND 1 1001 3 "abc"
100 SKSEND 1 1001 "abc"

SKBC

SKBC
<RADIUS>
ブロードキャストの最大転送ホップ数(0 - 15)
<SELECTOR>
セレクタ番号
$3e8(=1000)以上の値が指定可能
<DATALEN>
送信データ長(省略可能)
<DATA>
送信データ
100 msgid = SKBC 2 1001 3 "abc"
100 SKBC 2 1001 "abc"

SKFLASH

SKFLASH
<SELECTOR>
セレクタ番号
$3e8(=1000)以上の値が指定可能
<ADDR>
送信相手先ID($0001 - $ffef)
<DATALEN>
送信データ長(省略可能)
<DATA>
送信データ

例:$0003へ5バイトのデータを委譲送信、$0003から送信確認を受諾

>skflash 1001 $0003 "abcde"
6F59 OK
READY
>ERXDATA 0003 0002 6F59 03E9 50 00
OK

SKINQ

周辺の端末を能動的に探索して発見します。発見された端末はEDETECTイベントで通知されます。

SKPAIR

SKPAIR
<ADDR>
ペアリングを実行する相手ID($0001 – $FFEF、または$FFFF)
$FFFFを指定するとワイルドカード

SKUNPAIR

SKUNPAIR
<ADDR>
ペアリングを実行する相手ID($0001 – $FFEF、または$FFFF)

SKNOW

>sknow(enter)
ECLOCK 3 9 24 700 0

関数

abs()

整数の絶対値を計算します。

sqrt()

整数の平方根を計算します。
結果は四捨五入された整数で返されます。

>? sqrt(2)
1

READY
>? sqrt(4)
2

READY
>? sqrt(3)
2

READY
>

rnd()

指定値未満の乱数値を返します。

>? rnd(65535)
12134

READY
>? rnd(65535)
20941

READY
>? rnd(65535)
40891

READY
>

sgn()

整数の符号を返します。

tab()

TAB文字を印字します。引数が必ず必要ですが使っていません。

>? tab(1);";"
        ;

READY
>? tab(2);";"
        ;

READY
>

strcmp()

二つの文字列を比べます。

strind()

文字列の指定バイト目の値を参照します。

>a$="abcdefghijklmn"

READY
>? strind(a$, 4)
101

READY
>? strind(a$, 5)
102

READY
>

strlen()

文字列の長さを返します。

chr$()

数値の文字を印字します。PRINT文でのみ使用可能です。

>? chr$(105)
i

READY
>? chr$(106)
j

READY
>

hex()

数値を16進数の8桁で印字します。PRINT文でのみ使用可能です。

hex2()

数値を16進数の2桁で印字します。PRINT文でのみ使用可能です。

hex4()

数値を16進数の4桁で印字します。PRINT文でのみ使用可能です。

>? hex($12345678)
12345678

READY
>? hex2($12345678)
78

READY
>? hex4($12345678)
5678

READY
>

組み込み変数

仮想レジスタ

SKBasicプログラム内から仮想レジスタの設定と参照が可能です。
自端末IDの設定と参照の例:

READY
>S01 = 1

READY
>print S01
1

timer

timer
分解能10msのダウンカウンタ

rxdata

erxorigin
受信したデータの送信元ID
erxdst
受信したデータの宛先ID
erxmsgid
受信したデータの識別番号
erxselector
宛先セレクタ
erxrssi
受信RSSI
erxlen
受信データ長
erxdata$
受信データ
組み込み変数は数値変数のみという制限があるため、組み込み変数ではなくプログラム変数として実装しています。

ack

eackstatus
1=Ackの受信に成功
0=Ack待ちタイムアウト発生
eackdst
Ackの宛先ID
eackmsgid
Ack識別子

定数

10進数

10

16進数

$10

文字列

"skbasic"

16進データ

$"023456789abcdef"

サンプルコード

for next, data, read, restore

>list

10 data 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
20 sum=0:fact=1
30 restore
40 for i=1 to 10
50   read x
60   sum = sum + x
70 next i
80 print "sum =";sum
90 restore
100 for i=1 to 10
110   read x
120   fact = fact * x
130 next i
140 print "fact =";fact

>run

sum =55
fact =3628800


>

print, hex(), hex2, hex4(), gosub, label

>list

2100 c = 1 : gosub "print_hex"
2110 c = -1 : gosub "print_hex"
2120 c = $7fffffff : gosub "print_hex"
2130 c = $7fffffff + 1 : gosub "print_hex"
2140 c = $12345678 : gosub "print_hex"
8900 end
9300 '
9310 ' print c in various forms of hex
9320 label "print_hex"
9330 '
9350 print "c = ";c; " HEX() = "; hex(c); " HEX4() = "; hex4(c); " HEX2() = "; hex2(c)
9360 return

>run

c = 1  HEX() = 00000001  HEX4() = 0001  HEX2() = 01
c = -1  HEX() = FFFFFFFF  HEX4() = FFFF  HEX2() = FF
c = 2147483647  HEX() = 7FFFFFFF  HEX4() = FFFF  HEX2() = FF
c = -2147483648  HEX() = 80000000  HEX4() = 0000  HEX2() = 00
c = 305419896  HEX() = 12345678  HEX4() = 5678  HEX2() = 78

on RXDATA, on ACK, SKSEND, wait

自端末IDを0x0002として、ID=0x0001に対してAck要求付きユニキャストを20回送信します。0x0001からAckが戻ると、ACKハンドラにより9100行以降が実行されます。

1000 on RXDATA gosub 9000
1010 on ACK gosub 9100
1020 S01=2
1030 for i=1 to 20
1040   msgid=SKSEND 1 $1000 $0003 "abc"
1050   if msgid < 0 then 1040
1060   print "SKSEND ";hex4(msgid)
1070   wait 200
1080 next i
1090 end
9000 '
9010 print "RXDATA ";hex4(erxmsgid);" ";erxlen;"byte";erxdata$
9090 return
9100 '
9110 print "ACK ";eackstatus;" ";hex4(eackmsgid)
9290 return

30秒毎にSKSENDをAck要求ありで送信、待機中はスリープ

100 label "start"
110 on ACK gosub 2000
200 S01 = $1000

1000 label "repeat"
1010 eventack = 0
1100 msgid = SKSEND 1 6000 1 4 "test"
1110 while eventack = 0
1120 endwh
1300 print "sleep start"
1400 sleep 30
1500 print "sleep end"
1600 goto "repeat"

2000 label "ackrecv"
2010 print "ack recv"
2020 eventack = 1
2030 return

基本コマンドを網羅したテストコード

110 '  Test of KLBasic functions and operators
120 '
1020 dim arr(10)
1030 failed = 0
1100 data -5, -4, -3, -2, -1, 0
1105 data 1, 2, 3, 4, 5
1110 data 6, 7, 8, 9, 10, 11, 12, -100
1120 if 3000 * 2 <> 6000 then print "Fail in multiplication" : gosub "fail"
1130 if 3000 / 2 <> 1500 then print "Fail in division" : gosub "fail"
1140 if -3000 * 2 <> -6000 then print "Fail in multiplication (2)" : gosub "fail"
1150 if -3000 / 2 <> -1500 then print "Fail in division (2)" : gosub "fail"
1160 if 67 mod 10 <> 7 then print "Fail in MOD" : gosub "fail"
1170 if -67 mod 10 <> -7 then print "Fail in MOD (2)" : gosub "fail"
1200 if $40 <> 64 then print "Fail in hex notation" : gosub "fail"
1210 if ($55 and $ff) <> $55 then print "Fail in AND" : gosub "fail"
1220 if ($55 and $00) <> $00 then print "Fail in AND (2)" : gosub "fail"
1230 if ($55 or $ff) <> $ff then print "Fail in OR" : gosub "fail"
1240 if ($55 or $00) <> $55 then print "Fail in OR (2)" : gosub "fail"
1250 if ($55 eor $ff) <> $aa then print "Fail in EOR" : gosub "fail"
1260 if ($55 eor $00) <> $55 then print "Fail in EOR (2)" : gosub "fail"
1280 if not $55 <> $ffffffaa then print "Fail in NOT" : gosub "fail"
1300 if -$55 <> $ffffffab then print "Fail in negation" : gosub "fail"
1400 c = 0 : for n = 1 to 10 : c = c + n : next n
1410 if c <> 55 then print "Fail in FOR loop; c = "; c : gosub "fail"
1420 c = 0 : for n = 10 to 1 step -1 : c = c + n : next n
1430 if c <> 55 then print "Fail in FOR loop, negative STEP" : gosub "fail"
1440 c = 0 : for n = -1 to -10 step -1 : c = c + n : next n
1450 if c <> -55 then print "Fail in FOR loop, negative STEP (2)" : gosub "fail"
1500 c = 0 : n = 1
1502 while n < 11
1504   c = c + n : n = n + 1
1506 endwh
1510 if c <> 55 then print "Fail in WHILE loop" : gosub "fail"
1600 print "Using GOSUB to add values in a loop."
1605 c = 0 : for n = 1 to 10 : gosub "sub"
1610 next n : print
1630 if c <> 55 then print "Fail in FOR/GOSUB loop" : gosub "fail"
1700 print "Restoring data (first time)..." : restore
1702 print "Reading data and summing values"
1705 c = 0 : for n = 0 to 10 : read x : c = c + x : next n
1710 if n <> 11 then print "Fail in FOR/READ loop, index is wrong" : gosub "fail"
1720 if c <> 0 then print "Fail in FOR/READ loop, sum is wrong" : gosub "fail"
1800 print "Restoring data (second time)..." : restore
1802 print "Reading data and summing values"
1810 c = 0 : for n = 0 to 10 : read x : c = c + x : next n
1820 if n <> 11 then print "Fail in RESTORE/FOR/READ loop, index is wrong" : gosub "fail"
1830 if c <> 0 then print "Fail in RESTORE/FOR/READ loop, sum is wrong" : gosub "fail"
1900 print "Turning on trace"
1910 tron
1920 for n = 0 to 3
1930   c = 0
1940 next n
1950 troff
1960 print : print "Trace is now off"
2000 print "Testing ON-GOTO"
2010 print "ON-GOTO not implemented, test skipped"
2100 c = 1 : gosub "print_hex"
2110 c = -1 : gosub "print_hex"
2120 c = $7fffffff : gosub "print_hex"
2130 c = $7fffffff + 1 : gosub "print_hex"
2140 c = $12345678 : gosub "print_hex"
2400 print "Filling array with 0-9"
2410 for n = 0 to 9 : arr(n) = n : next n
2420 for n = 0 to 9 : print arr(n); : next n
2430 print
2500 print "Input a number"; : input c
2510 print "You entered "; c
2520 input "(Using input() with string) Input a number", c
2530 print "You entered "; c
2600 print "Program will now stop; enter CONT to continue."
2610 stop
2620 print "Program resumes..."
2700 if abs(1) <> 1 then print "Fail in ABS(1)" : gosub "fail"
2710 if abs(-1) <> 1 then print "Fail in ABS(-1)" : gosub "fail"
2720 if abs(-$7fffffff) <> 1 then print "Fail in ABS(-$7fffffff)" : gosub "fail"
2730 if abs(-555) <> 555 then print "Fail in ABS(-555)" : gosub "fail"
2800 if sgn(0) <> 0 then print "Fail in SGN(0)" : gosub "fail"
2810 if sgn(20) <> 1 then print "Fail in SGN(20)" : gosub "fail"
2820 if sgn(-33) <> -1 then print "Fail in SGN(-33)" : gosub "fail"
2900 print "Sleep 10 secs start"
2910 sleep 10
2930 print "Sleep end"
8000 '
8010 ' end of mainline program; put subroutines below here
8020 '
8910 print "Test is complete, fail count = "; failed
8999 end
9000 '
9010 ' Common code for counting failures
9020 label "fail"
9100 failed = failed + 1
9110 return
9200 '
9210 ' helper subroutine for testing loops
9220 label "sub"
9230 c = c + n
9235 print ".";
9240 return
9300 '
9310 ' print c in various forms of hex
9320 label "print_hex"
9350 print "c = ";c; " HEX() = "; hex(c); " HEX4() = "; hex4(c); " HEX2() = "; hex2(c)
9360 return

トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2022-06-08 (水) 22:59:25