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に倒し、リセットボタンを押して再起動しておくこと。

回路のTop Pageへ