連載
» 2011年01月21日 00時00分 公開

「動き」のセンシングと制御マイコン制御基礎の次(7)(2/2 ページ)

[みわよしこ(執筆協力:山本 栄一、小椋 秀一、宇野 雄騎),@IT MONOist]
前のページへ 1|2       

 それでは、以上の流れを頭に置いて、今回の「招き猫」の制御プログラムをあらためて眺めてみよう(リスト1)。

 長大に見えるので、背筋に寒気が走る方もおられるかもしれないが、特に難しいプログラムではないので心配しないでいただきたい。

 コメントは「くどい!」といわれそうなほど書き込んであるので、眺めれば大体、どういうプログラムであるかをご理解いただけると思う。


;Copyright 2008 Denno Neko Yashiki
;written by Shuichi Ogura
;
;PortD 7ビット目からスイッチの信号を入力して、
;スイッチがn回押されたら、招きをやめる。
.include "m16def.inc"
;.equ MOTOR0_OFF   = 0b00000000
;.equ MOTOR0_ON    = 0b00000001
;.equ LED1_ON   = 0b00000011
;.equ LED1_OFF  = 0b00000001
.equ ALL_PORT_OUTPUT = 0xFF
.equ ALL_PORT_INPUT  = 0x00
.equ PUD_SET = 0b00000100 ;2ビット目のPUDビットをセットする。
;.equ BIT7 = 0b10000000
.equ LOOP_LENGTH1 = 0xFF
.equ LOOP_LENGTH2 = 0xFF
.equ MANEKI_TIMES = 3 ;招く回数。
.def Temp      = R16
.def COUNT1    = R17
.def COUNT2    = R18
.def MainCount = R19
;---------------------------------------------
;おまじない。ここに、割り込みベクタが入る。
;---------------------------------------------
.org 0x0000
        rjmp RESET
;---------------------------------------------
;サブルーチン群
;---------------------------------------------
;-----------------------------
;Delay1:
;1倍ループ。
;-----------------------------
Delay1:
        ldi     COUNT2, LOOP_LENGTH2
Loop2:
        ldi     COUNT1, LOOP_LENGTH1 
Loop1:
        dec     COUNT1          ;カウンタ値をマイナス1、結果が0になるとSREG:Z=1
        brbc    1, Loop1        ;SREGのビット1(Z)がクリアされていればLoop2へブランチ
        dec     COUNT2
        brbc    1, Loop2
        ret
;---------------------------------------------------
;Switch関連サブルーチン(処理が込み入ってきたら、ここに書く)。
;負論理のLEDを、PORTBの1ビット目に取り付けること。
;---------------------------------------------------
;-----------------------------
;WaitToReleseSwitch:
;スイッチが離れるまで待つ。
;モータが回っていない+スイッチが押されている状態で実行すると、
;強制的にスイッチを離してやるまで無限ループになる。
;-----------------------------
WaitToReleseSwitch:
                sbis PIND,7      
                rjmp WaitToReleseSwitch   ;7ビット目がHigh(Off)のときにはスキップ。
                ret     ;7ビット目がHigh(Off)になったら、サブルーチン終了。
;---------------------------------------------------
;LED関連サブルーチン
;正論理のLEDを、PORTBの1ビット目に取り付けること。
;---------------------------------------------------
;-----------------------------
;LED_On:
;LEDをOnする。
;-----------------------------
LED_On:
                sbi PORTB,1
                ret
;-----------------------------
;LED_Off:
;LEDをOffする。
;-----------------------------
LED_Off:
                cbi PORTB,1
                ret
;---------------------------------------------------
;MOTOR関連サブルーチン
;正論理のMOTOR I/Fを、PORTBの0ビット目に取り付けること。
;---------------------------------------------------
;-----------------------------
;MOTOR_On:
;MOTORをOnする。
;-----------------------------
MOTOR_On:
                sbi PORTB,0
                ret
;-----------------------------
;MOTOR_Off:
;MOTORをOffする。
;-----------------------------
MOTOR_Off:
                cbi PORTB,0
                ret
;-----------------------------
;ここから、プログラムスタート
;-----------------------------
RESET:
;-----------------------------
;初期化部
;-----------------------------
;スタックの初期化
;RAMの最終番地をスタックポインタにセットする。
;RAMの最終番地は、インクルード(.def)ファイルに"RAMEND"として定義されている
;AVRは1回に8ビットしか扱えないので、LowとHighに分けてセットする。
                ldi     Temp, low(RAMEND)
                out     SPL, Temp
                ldi     Temp, high(RAMEND)
                out     SPH, Temp
;PORT B の初期化(全ビットを出力にセット)
                ldi     Temp, ALL_PORT_OUTPUT
                out     DDRB, Temp
;PORT D の初期化(全ビットを入力にセット)
                ldi     Temp, ALL_PORT_INPUT
                out     DDRD, Temp
;PUD(プルアップを禁止)ビットをセットし、プルアップを禁止する。
;               ldi     Temp, PUD_SET
;               out     SFIOR, Temp
;-------------------------------------------------------------------
;メインルーチン
;PINDの7ビット目に付けたスイッチ(負論理)の状態をチェックし、
;・まず、スイッチが離れるまで、モータを回しながら待機。
;・その後、招く回数を、ループカウンタに設定し、
; スイッチが押されたときに、ループカウンタを1減らす。
;・指定回数だけ招き、ループカウンタが0になったらモータを止めて、終了。
;
;-------------------------------------------------------------------
MAIN:
                ;モータを始動させる。
                rcall   MOTOR_On
                ;PINDの7ビット目に付けたスイッチ(負論理)の状態をチェックし、
                ;スイッチが離れるまで、モータを回しながら待機。
                ;これにより、スイッチが押されていても、招きはじめる……はず。
                rcall WaitToReleseSwitch
                ;招く回数を、レジスタに設定。
                ldi MainCount,MANEKI_TIMES
MainLoop:
        ;PINDの7ビット目に付けてあるスイッチ(負論理)の状態をチェック。
                sbis PIND,7
                rjmp Switch_On   ;7ビット目がHigh(On)のときにはスキップ。
                rjmp Switch_Off  ;7ビット目がHigh(Off)のときに実行。
;===
Switch_On:
                ;スイッチが押されたときは、ループカウントを1減らす。
                dec MainCount 
                ;デバッグ用に、PORTBの1ビット目のLEDを点灯させる。
                rcall LED_On 
                rcall WaitToReleseSwitch
                ;ループが終わったら、デバッグ用にPORTB1ビット目のLEDを消灯する。
                rcall LED_Off
;===
Switch_Off:
                ;スイッチがOffなら、そのままループを続ける。
                rjmp WhileZero
;===
WhileZero:
                ;MainCountの値をチェックして、0じゃなければループする。
                tst MainCount ;フラグ分岐をする前に、評価命令が必要!
                brne MainLoop
;===
;指定回数だけ招いたら、PORTBの0ビット目をクリアして、回転を止めて終了。
                rcall MOTOR_Off
Endless:
                rjmp Endless
;END
;処理系によってバグることがあるので、この後には何行か空行を入れておくとよい。
リスト1 「招き猫」のプログラム

 LEDフラッシャは、周囲の状況に関係なく勝手に点滅する。しかし、この招き猫ロボットでは一歩進み、自分自身の動作をセンシングして自分の“招き”動作を制御する。ここから、外界の情報をセンシングして自分の動作を制御するまでの距離はそれほど長くはない。外界の情報をセンシングしての動作が可能になれば、例えば、

「戦う必要がないときには、静かにしていて体力を蓄える」
「戦う必要が発生したときに、全力で反撃する」

といった制御も可能になるだろう。そうすれば、受動的ではあるけれども、能動性を含んだ受動性へと一歩前進させることができる。センシングが行えるようになることの意義は、“世界が変わるほどに大きい”といえる。

 では、そのセンシングは、どのように扱われているのだろうか。

 当たり前の話だが、まず、信号がマイコンに「入力」として取り込まれる必要がある。

入力を扱うには?

 マイコンの入出力ピンを「入力」に使用するには、最低限“入力である”を設定する必要がある。

 このプログラムで、“入力である”の設定を行っている部分を抜き出してみよう。

.equ ALL_PORT_INPUT = 0x00
(中略) 
;PORT D の初期化(全ビットを入力にセット)
                ldi     Temp, ALL_PORT_INPUT
                out     DDRD, Temp

 実はこの3行だけである。

 続いて考慮すべき問題は、「最初の入力は、HighかLowか分からない」ということである。このことは、「温度計を設置する前には温度は分からない」という事実から容易に理解されるであろう。

 最初の入力が何であっても(HighでもLowでも)問題が起こらないようにするには、“最初の入力に無関係に動作するよう考慮したプログラムとする”必要がある。このプログラムでは、「招き猫」の腕の上下を判断するためのスイッチの初期状態には「ON」「OFF」の両方があり得るので、

「初期状態に無関係にモータの回転(招き猫の腕の移動)を開始し、スイッチが『ON→OFF』『OFF→ON→OFF』のどちらかのパターンで『OFF』になったことを確認してから制御を開始する」

という方法で回避している。

 本プログラム内では、以下の部分でその「OFFになってから制御」を実現している。サブルーチンコールを行う「rcall」で呼ばれている各ルーチンを問題にしなければ、該当するのは以下の2行だけだ。

MAIN:
                ;モータを始動させる。
                rcall   MOTOR_On
                ;PINDの7ビット目に付けたスイッチ(負論理)の状態をチェックし、
                ;スイッチが離れるまで、モータを回しながら待機。
                ;これにより、スイッチが押されていても、招きはじめる……はず。
                rcall WaitToReleseSwitch

 以降は、基本的にLEDフラッシャと同じプログラムである。「なぁんだ!」と拍子抜けされたことであろう。実際の数々の競技や競争での複雑極まりない攻守に比べれば、「守る」も「守って攻める」も容易なのである。



 次回は、どのようにして「決まった回数の『招き』動作を行わせるか」を詳細に見ていき、原理的には原始的な「LEDフラッシャの延長でのモータ制御」の理解へ至ることを目標としよう。(次回に続く)


前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.