BASCOM-AVR 参考プログラム
6章の説明に添付しておいたプログラムなどを解読する際に参考になる、プログラム構造やテクニックについて解説します。各命令文については、BASCOMのマニュアルを参照してください。
(実は、筆者は大学に来る前の経験として、以前にいた会社ではハードの設計と、それに関するドライバー・プログラムを作成していたことがあって、また、CQ出版社の某雑誌にも複数回、記事が載ったことがある、いわば、ハードやソフトのプロでした。それらのやり方などを採用したAVRのプログラムを以下に記します。要は、プロのプログラムの書き方の参考?です。(けっこう汚い書き方をしていますが、効率重視です。)たぶん、普通のプロならこんな書き方をするのでは? まー、いろいろ人によって流儀はありますが...。より複雑なプログラムの例は6章のいろいろなAVR採用機器の添付ファイルにありますので、そちらを参照してください。)
1. メインループ
メインループには、いろいろなやり方があると思いますが、幾つか実際の例を紹介します。
1-1 ロータリーエンコーダー(以下、REと略)やスイッチ(以下、SWと略)の処理
普通はパネルのSWやREの入力を待つように作ります。 REの処理の仕方によって、2つのやり方に分けられます。第1の方法はREをメインループの中心に据え、REが変化するまで、そこで待たせる方法です。以下に構造の例を示します。
プログラム1
Dim B As Byte
' Rotary encoder LOOP (Do / Loop ループを永遠に回ります。)
Do
B = Encoder(pind.0 , Pind.1 , Rencl , Rencr , 1) ' REが回るまで、ここで待ちます。
Waitms 10
Loop
End
Rem rotary encoder Left (REが左に回ると、ここに飛ぶ。)
Rencl:
If B <> 1 Then Return
If Sw2_flg = 1 Then ' SW2の状態を見て、処理を決める。
Decr Digit
Else ' Number set
F = F - Delta
End If
Return ' メインルーチンに戻る。
Rem rotary encoder Right (REの右回転の処理)
Rencr:
If B <> 0 Then Return
If Sw2_flg = 1 Then ' Digit set
Incr Digit
Else ' Number set
F = F + Delta
End If
Return
スイッチはスナップスイッチを使うと、状態が固定されていますので、REの回転処理ルーチン中でSWを読み取り、分岐出来ます。いずれにせよ、REの回転処理ルーチンからはReturn命令でメインループに戻ります。戻った先でスイッチの処理を行ってもよい。
プログラム1では、REが回転するまで何も起こらないわけですので、SWを変化させてもRE変化まで待たされてしまいます。それを回避するには、スイッチをインタラプトで扱います。以下に例を示します。
プログラム2
Rem SW delay [ms]
Const Dly1 = 10
Const Dly2 = 100
Dim B As Byte
Rem Interrupt definition
Config Int0 = Change
On Int0 Sw1int
Enable Int0
Enable Interrupts
' Rotary encoder LOOP
Do
B = Encoder(pind.0 , Pind.1 , Rencl , Rencr , 1)
Waitms Dly1
Loop
End
Rem Interrupt handller
Sw1int:
Waitms Dly2
Sw1_flg = Sw1
Led = Not Sw1_flg
If Sw1_flg = 1 Then ' STOP
If Old_sw1 = 0 Then
Gosub Dds_stop
End If
Old_sw1 = Sw1_flg
Else ' START
If Old_sw1 = 1 Then
Gosub Fcalc
Gosub Dds_start
End If
Old_sw1 = Sw1_flg
End If
Return
SW1が変化したとたん、インタラプト処理ルーチン Sw1int へ飛びます。したがって、REが変化しなくてもSW変化で処理が始まります。SWの数だけ、インタラプト処理ルーチンを追加します。
注意:インタラプト処理ルーチンではReturn文は1カ所のみしか許されません。もし、別の If 文などでリターンしたければ、最後のリターン分の前にラベルを入れ、そこへ Goto します。BASCOMでは、インタラプト処理ルーチンに2カ所以上 Returnを入れると、最初のReturn 文はRTI (Return from interrupt)にコンパイルされ、次のReturnは普通のRET命令(Gosub / Retrun のReturn)にコンパイルされるので、正しい動作が保証されません。
1-2 REを待たない方式
B = Encoder(pind.0 , Pind.1 , Rencl , Rencr , 1)とせず、
B = Encoder(pind.0 , Pind.1 , Rencl , Rencr , 0)とすると、回転を待たずに次へ進みます。回転していた場合のみ、Rencl かRencrサブルーチンへ飛びます。以下にプログラム例を示します。
プログラム3
Dim B As Byte
' MAIN LOOP
' always loops this routine.
' control flags set by interrupts make branch the program.
Do
If Modeflg = 1 Then ' Modeflg is set/reset by SW3 interrupt.
Gosub Mode_set ' enter into MODE-SET mode
Else ' Data set
If Usben = 0 Then
On Pmode Gosub Mode0 , Mode1 , Mode2 , Mode3
Else
Gosub Usbcontrol
End If
End If
Loop
End
'
'-------------------------------------------------------------
'
' Mode Selection Routines
'
'-------------------------------------------------------------
'
Rem MODE-SET mode
Mode_set:
Old_pmode = Pmode
B = Encoder(pind.0 , Pind.1 , Renclm , Rencrm , 0) ' rotary encoder sense
' Renclm and Rencrm decrement/increment mode_number (Pmode)
' When mode is changed, Mschgflg (Mode_Set_Change_flag) = 1 is returned.
If Mschgflg = 1 Then ' change in MODE-SET mode
Cls
Upperline
Lcd "MODE-SET. mode = " ; Pmode
Gosub Msdisp ' mode display in mode_set mode
Reset Mschgflg
If Old_pmode <> Pmode Then
Xpmode = Pmode ' memory the mode into EEPROM
End If
End If
Reset Pm_change
Return
Rem rotary encoder Left in MODE SET
' Name Renclm = Rotary ENCoder Left Mode_set
Renclm:
If B <> 1 Then Return
Decr Pmode
If Pmode > &H80 Then Pmode = 3
' decrement of Byte variable produces &HFF (not minus 1),
' since Byte var. is interpretted as unsigned data in BASCOM-AVR.
Waitms Dly1
Set Mschgflg
Return
Rem rotary encoder Right in MODE SET
Rencrm:
If B <> 0 Then Return
Incr Pmode
If Pmode > 3 Then Pmode = 0
Waitms Dly1
Set Mschgflg
Return
このプログラムではメインループから呼び出されるサブルーチン中でREの回転がセンスされています。メインから呼び出されるサブルーチンなので、メインに戻る必要があり、REの回転待ちをしない方式が採用されます。REに回転が有った場合には、フラグをのセットや変数の書き換えをしてから、メインループに戻りますので、メインループ側でそれを参照して処理を行います。
** ロータリーエンコーダーのチャッタリング
ジャンク的なロータリーエンコーダーを使った場合、スイッチのチャッタリング(接触の不良など)によって左回りや右回りが正常に認識されない事があります。その場合は以下のようにすると認識できるようになる事が有ります。
Dim B As Byte
Rem Rotary encoder rotates to Left[counter-clockwise]
Rencl:
If B <> &B00 Then Return
' neglect the chattering
' Write program when the rotary encoder rotates left
Return
Rem Rotary encoder to Right[clockwise]
Rencr:
If B <> &B01 Then Return
' neglect the chattering
' Write program when the rotary encoder rotates right
Return
If文の判定条件が前出のものとは逆な事に注意してください。
1-3 USBインターフェース(以下、USB IFと略)から命令文を受け取る方式
共立電子製のABL128やABL168のように、USB IFを備えたAVRボードを使う際、USB経由でコマンドをPCから与えてAVRボードを制御できる。メインルーチンの例を以下に示す。
プログラム4
Rem USART & Communication defs.
$baud = 9600 ' 通信速度を定義=9600ボー(defaultでは、8-bit non-parity)
Config Input = Cr , Echo = Cr
Echo Off ' PCから来たコマンドのエコーバックを止めておく。
Rem ここからメインループ
L_start:
I = 1
J = 1
Input "Command?" , S1
If Asc(s1) = &H0A Then S1 = Mid(s1 , 2)
If Asc(s1) = &H43 Then
' &H43 = character C
Print
Print "hit"
Else
Goto L_start
End If
S2 = Mid(s1 , 2 , 2)
Cn = Val(s2)
' Print "#" ; Cn
If Cn >= 10 Then Goto L_g10
On Cn Goto L0 , L1 , L2 , L3 , L4 , L5 , L6 , L7 , L8 , L9
L_g10:
If Cn >= 20 Then Goto L_g20
Cn = Cn - 10
On Cn Goto L10 , L11 , L12 , L13 , L14 , L15 , L16 , L17 , L18 , L19
L_g20:
Cn = Cn - 20
On Cn Goto L20 , L21 , L22 , L23 , L24 , L25 , L26 , L27 , L28 , L29
If Cn >= 30 Then Goto L_skip
L_skip:
Print
Print "No command"
Lret:
Print
Print "OK"
Goto L_start
Rem メインループはここまで。
' Command C0 = brink LED
L0:
Print "LED toggle"
Reset Portf.5
For K = 1 To 5
Toggle Portf.5
Waitms 500
Next K
Goto Lret
' Command C1 number1, number 2 = Motor #1 data input
L1:
Gosub Getarg2 ' I=step counts (+/- means right/left), J = rate
If J = 0 Then Goto Lret
Print "Bipolar motor1 data"
Mts(1) = I
Div(1) = J
Goto Lret
L28:
L29:
Print "Others"
Goto Lret
Rem subroutines
' get arg 1 into I
Getarg1:
I = 1 ' default value
S2 = Mid(s1 , 4)
I = Val(s2)
Return
' get arg 2 into I, J (Int)
Getarg2:
I = 1 'default value
J = 1 'default value
S2 = Mid(s1 , 4)
I = Val(s2)
K = Instr(s2 , ",")
K = K + 1
S1 = Mid(s2 , K)
J = Val(s1)
Return
このプログラムでは、メインルーチンは L_start ラベルから始まり、Goto L_startでループしている。また、各コマンドへの分岐は On Goto 文で行っている。Gosub を使わなかったのには以下の理由がある。各コマンドで飛んだ先でエラーが出た場合は、単純にGoto L_skip などとしてメインルーチンに戻れる。もし、Gosub文で分岐していた場合は、エラーフラグ等をセットして、それをメイン側で処理しなければならず、手間がかかる。したがって、少々プログラムが汚くなるが、Goto 型の方が楽である。
AVRボードへのコマンドはPC側のハイパーターミナルというソフトで与えられる。AVRボードのUSBドライバーがインストール済みであれば、AVRボードに接続時にCOMポートとして認識されているので、それをハイパーターミナルから開き、9600 baud, 8-bit, non-parityをセットする。詳しくは6-4章参照。
BASCOMが便利なのは、Print 文で出力したのがAVR のUSARTに出ることである。USB IFを持ったAVRボードは、だいたいUSART0がUSB IFにつながっているので、USB経由でPCに伝送される。また、Input 文でUSB経由でPCからのASCII文字列を入力できる。なお、ABL128やABL168ではプログラミングにもUSB IFを使うので、ボード上のスイッチをRUNに倒し、リセットボタンを押して再起動しておくこと。