目次

AMFORTHで車を動かす

 AMFORTHは、Arduinoのハードウエアで動く
 Forthインタプリタ。

 AMFORTHが要求するATmegaシリーズは、ATmega328
 以上です。ATmega644、ATmega1280を利用している
 Arduinoであれば、AMFORTHを動かせます。

 Forthインタプリタを使うと、テストと実働を区別
 せずにワードの定義を積み重ねることでシステム
 を組み上げられます。

 Arduinoでは、PC上に開発環境が必要ですが
 AMFORTHでは、開発環境と実機は、同一。

 端末ソフトをインストールしたPCがあれば
 他に必要なものは、AMFORTHインタプリタの
 基板と電源くらいのもんです。

 ATmega328を使ったAMFORTHで車を動かしてみます。



 最初に、利用するポートに対して理解しやすい
 ラベル名をつけておきましょう。

$23 constant PINB
$24 constant DDRB
$25 constant PORTB
$26 constant PINC
$27 constant DDRC
$28 constant PORTC
$29 constant PIND
$2a constant DDRD
$2b constant PORTD
$80 constant TCCR1A
$81 constant TCCR1B
$82 constant TCCR1C
$84 constant TCNT1L
$85 constant TCNT1H
$86 constant ICR1L
$87 constant ICR1H
$88 constant OCR1AL
$89 constant OCR1AH
$8a constant OCR1BL
$8b constant OCR1BH

 車にDCモータを接続して、モータで移動できる
 ようにするためATmega328内蔵タイマーカウンタ
 を利用します。

 ポートBの1、2ビットからはPWM波形を出力
 できるので、モータの回転速度をPWMで制御。

 ポートBの1、2ビットに関係するレジスタには
 ラベルをつけています。これらのレジスタへの
 パラメータ設定を考えていきます。

 モード選択

  ATmega328のタイマー1は、様々なモードで
  使えるように設計されています。

  PWM波形生成には、高速PWMモードを選択

  具体的な動作は、次の図で理解できます。



  タイマー1のカウンタであるTCNT1は
  0から値をひとつずつ増やしていき
  ICR1と一致すると、ゼロに戻ります。
  これを繰り返し。

  TCNT1とOCR1の値が一致すると、出力は
  0、1、トグルのいずれかにできます。

  モータの回転数を制御するには、0と1の
  割合を変えればよいので、値が一致では
  0にするか、1にするかを指定します。

  次の回路でモータを回転させるならば
  一致したときに、論理値の'0'を出力
  させることに。



  OCR1は、OCR1AとOCR1Bの2レジスタが
  あるので、2個のモータに独立でPWM
  波形を出力できます。

  モードを、高速PWMとするときは
  レジスタTCCR1Aの下位2ビットには
  2進数で10を設定し、レジスタTCCR1B
  4、3ビットに2進数で11を設定。

  レジスタTCCR1Aには、カウンタの値と
  OCR1AおよびORC1Bの値が一致したとき
  の出力ピンの論理値を指定します。
  一致したときに、論理値の'0'を印加
  するときには、次のように指定。

$a2 TCCR1A c!

  レジスタTCCR1Bの下位3ビットには
  システムクロックの分周比を設定する
  ので、1/1、1/4、1/8、1/256、1/1024
  のいずれかになるように値を設定。

  1/1024とするには、以下とします。

3 3 lshift 5 or TCCR1B c!

 16ビット値設定

  タイマー1では、TCNT1、ICR1、OCR1A、OCR1Bは
  16ビットなので、値設定の専用ワードを定義して
  使いやすくします。

: set.tcnt1
  256 /mod TCNT1H c! TCNT1L c!
;

: set.icr1
  256 /mod ICR1H c! ICR1L c!
;

: set.ocr1a
  256 /mod OCR1AH c! OCR1AL c!
;

: set.ocr1b
  256 /mod OCR1BH c! OCR1BL c!
;

 ここまでで、PWM波形をモータドライブ回路に
 出力するワードが定義できました。

 Arduino基板にLEDを接続し、動作を確認したい
 ので、ブレッドボードにLEDと抵抗を入れて
 ワイヤーを出しておきます。



 AMFORTHインタプリタ基板とブレッドボード上の
 LEDを接続して、端末を使い、動作を確認。



 16MHzを1024分周して、タイマー1のカウンタ用
 クロックを生成すると、約16kHzに。

 ICR1に1000を設定し、OCR1A、OCR1Bに10、100、200
 500とすると、LEDの輝度が変化するとともに消灯を
 目視で確認できました。

 消灯が確認できなかったのは、分周比を1:1、1:4、1:8と
 したときでした。

 モータを動かすためにワードを定義。

: set.motor ( left_duty right_duty -- )
  set.ocr1b set.ocr1a
;

 左と右のモータに与えるPWM波形のDUTY比を
 指定します。

 車として動かすには、コースの状態を入力する
 センサーが必要。次のセンサーを利用します。



 このセンサーは、床面のライン情報を
 8ビットで出力することができます。

 Arduinoは、ポートCを利用すると6ビット入力が
 できるので、MSBとLSBを使わないようにします。

 ポートCを入力で使うので、6ビットのセンサー
 情報を扱うワードを定義して対応。

variable LFWS

: get.sensor
  0 DDRC c! PINC c@ LFWS c!
;

 ポートCを入力に設定するため、SFRのDDRCに
 入出力の指定パラメータを与えておきます。
 PINCでポートCの値を入力し、変数LFWSに格納
 して、後で使えるようにしています。

 センサーとArduinoの接続は、以下。

 1 Vcc             : Vcc
 2 sensor_7(MSB)
 3 sensor_6        : PORTC.B5
 4 sensor_5        : PORTC.B4
 5 sensor_4        : PORTC.B3
 6 sensor_3        : PORTC.B2
 7 sensor_2        : PORTC.B1
 8 sensor_1        : PORTC.B0
 9 sensor_0(LSB)
10 GND             : GND

 コースを見てみます。



 中央に白線があり、左右に黒面があり、両端に
 白線が配置されています。

 コースを見て動かし方を考えます。

 床面の情報を端で見て、白を検出したとき、反対方向に
 動くように制御すれば、ラインに沿って移動できる。

 床面の情報を2進数でリストして、端に白を検出するパターンを抽出。

0 0 0 0 0 1 : mark
0 0 0 0 1 1 : mark
0 0 0 1 0 1
0 0 0 1 1 1 : mark
0 0 1 0 0 1
0 0 1 0 1 1
0 0 1 1 0 1
0 0 1 1 1 1
0 1 0 0 0 1
0 1 0 0 1 1
0 1 0 1 0 1
0 1 0 1 1 1
0 1 1 0 0 1
0 1 1 0 1 1
0 1 1 1 0 1
0 1 1 1 1 1
1 0 0 0 0 0 : mark
1 0 0 0 0 1
1 0 0 0 1 0
1 0 0 0 1 1
1 0 0 1 0 0
1 0 0 1 0 1
1 0 0 1 1 0
1 0 0 1 1 1
1 0 1 0 0 0
1 0 1 0 0 1
1 0 1 0 1 0
1 0 1 0 1 1
1 0 1 1 0 0
1 0 1 1 0 1
1 0 1 1 1 0
1 0 1 1 1 1
1 1 0 0 0 0 : mark
1 1 0 0 0 1
1 1 0 0 1 0
1 1 0 0 1 1
1 1 0 1 0 0
1 1 0 1 0 1
1 1 0 1 1 0
1 1 0 1 1 1
1 1 1 0 0 0 : mark
1 1 1 0 0 1
1 1 1 0 1 0
1 1 1 0 1 1
1 1 1 1 0 0
1 1 1 1 0 1
1 1 1 1 1 0
1 1 1 1 1 1

 2進数を10進数で表現し、DUTY比を表にします。

 反対方向の移動には、白を検出した側のモータの
 回転数を下げるようにして、移動を実現します。

0 0 0 0 0 1 :  1 => left 45 right 50
0 0 0 0 1 1 :  3 => left 40 right 50
0 0 0 1 1 1 :  7 => left 35 right 50
1 0 0 0 0 0 : 32 => left 50 right 45
1 1 0 0 0 0 : 48 => left 50 right 40
1 1 1 0 0 0 : 56 => left 50 right 35

 移動するために時間が必要なので、DUTY比を
 指定して1秒後に元のDUTY比に戻します。

 動作シーケンスを作成。
  1. 路面情報取得
  2. 1ならばDUTY比を(45,50)に設定
  3. 3ならばDUTY比を(40,50)に設定
  4. 7ならばDUTY比を(35,50)に設定
  5. 32ならばDUTY比を(50,45)に設定
  6. 48ならばDUTY比を(50,40)に設定
  7. 56ならばDUTY比を(50,35)に設定
  8. 上記以外ならばDUTY比を(50,50)に設定
  9. 1秒待ち
  10. ステート1に戻る
 これをForthで記述します。 : run.lfw init.robot begin 1 while get.sensor LFWS c@ 1 = if 45 50 set.motor else LFWS c@ 3 = if 40 50 set.motor else LFWS c@ 7 = if 35 50 set.motor else LFWS c@ 32 = if 50 45 set.motor else LFWS c@ 48 = if 50 40 set.motor else LFWS c@ 56 = if 50 35 set.motor else 50 50 set.motor then then then then then then 1000 MS repeat ;  実際の走行は、紙で作ったコースで確認します。  時間調整には、タイマー割込みを利用した方が楽と  考えて、タイマー0を使っての操作をやってみます。  タイマー0に関係するレジスタのアドレスを  ワード「constant」で定義。 $44 constant TCCR0A $45 constant TCCR0B $46 constant TCNT0 $47 constant OCR0A $48 constant OCR0B  ATmegaのタイマーは多機能ですが、CTCモードを利用して  タイマーカウンタTCNT0の値が、OCR0Aに一致したときに  ゼロクリアするようにします。  CTCモードに設定するには、TCCR0Aの下位2ビットに  2進数で10を設定し、TCCR0Bの3ビット目に0を指定。  分周比は、TCCR0Bの下位3ビットに設定します。  Arduinoのハードウエアでは、16MHzを使うので  64分周して、タイマーカウンタのクロックには  250kHzを設定。250分周すれば1kHzを得られます。  TCCR0A、TCCR0B、OCR0AおよびTCNT0のパラメータ  設定は、以下とします。 2 TCCR0A c!{enter} 3 TCCR0B c!{enter} 250 OCR0A c!{enter} 0 TCNT0 c!{enter}  実際にタイマー0に関係するレジスタのアドレス  を定義して、CTCモードの動作を確認してみました。  TCNT0の値を、連続して読み出して表示すると  変化がわかります。250を超えたならゼロに戻り  再度カウントアップを続けていると理解できます。  変数SYSTIMを用意して、1kHzでカウントアップすると  SYSTIMの値を利用して、インターバルをms単位で指定  できるはず。  タイマー割込みを使い、変数SYSTIMの値を操作してみます。  タイマー0の割込みに関係するレジスタのアドレスを定義。 $35 constant TIFR0 $6e constant TIMSK0 variable SYSTIM  TIFR0、TIMSK0の構成は、以下。  まずは、タイマー0の割込みの種別を選択。  OCR0AとTCNT0の値が一致したときに、割込みが発生する  ように設定するには、TIMSK0の1ビット目をセットする  とよいのでしょう。  これは、簡単に論理値を設定。 1 1 lshift TIMSK0 c!{enter}  割込みが発生したときには、TIFR0の1ビット目がセット  されます。タイマー割込みハンドラは、TIFR0の1ビット  をクリアして、変数SYSTIMをインクリメント。 : timer0.isr 0 TIFR0 c! SYSTIM @ 1+ SYSTIM ! ;  残る作業は、割込みハンドラをシステムに登録するだけ。  ATmega328は、割込みベクター方式を採用しているので  タイマー0のコンペアマッチ割込みが発生したときに  どのアドレスに入っているコードを利用するのかを  指定アドレスに書いておかなくてはなりません。  ベクターのリストは、以下。  リストからアドレスが判るので、ワード「constant」で  設定して使えるようにします。 $001c constant TIMER0_COMPA  このアドレスとワードtimer0.isrを紐付け。 ['] timer0.isr TIMER0_COMPA int!  タイマー0の割込み、レジスタの初期化をまとめます。 : timer0.init 0 TCNT0 c! \ clear couunter 250 OCR0A c! \ max = 250 0 SYSTIM ! \ clear system timer ['] timer0.isr TIMER0_COMPA int! \ assign handler 2 TCCR0A c! \ select CTC mode 3 TCCR0B c! \ 1/64 ;

目次

inserted by FC2 system