目次

汎用I/O操作

 ATmegaシリーズには、ポートB、C、Dの汎用I/Oポートがあります。
 (40ピンのATmega644、ATmega1280には、ポートAもあり。)

 汎用I/Oポートを扱うことは、DDR(Data Direction Register)、INPUT、OUTPUT
 の3レジスタを操作することと等価。

 各レジスタは、SFR(Sepecial Function Register)として扱われ
 memory mapped registerのメモリ空間に配置されています。



 I/Oで扱うときには、()の中にあるアドレスを利用。

 AVRチップでは、汎用レジスタr0からr31を、0x00から0x1fに
 アサインしてI/Oで使えるため、0x20より上のアドレスに
 SFRがマッピングされています。

 ポートB、C、DのDDR、INPUT、OUTPUTは、シンボルを使って
 操作した方が動作をイメージしやすいので、「constant」
 ワードを利用して定義。

 アドレスの並びは、下からINPUT、DDR、OUTPUTであると
 気がつけば、次のように定義できます。

$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

 ワードをひとつずつタイプするのは、面倒なのでテキストエディタで
 操作ワードを入力後、クリップボード経由で端末ソフトによるPaste
 を実行。これで、パラメータおよび操作ワードがATmegaチップに転送
 されます。



 ATmegaでは、DDRにポートピンの入出力方向を設定してから
 INPUT、OUTPUTを使わなければなりません。

 論理値の'1'を与えると出力、'0'を与えると入力。

 Arduino基板では、ポートBの5ビット目にLEDが接続されてます。



 LEDを手動で点滅させてみます。
 次の手順で、ワードを定義して動作を確認していきます。

 各処理にワード名をつけておきます。
 ワード名を与えることで、動作を明確にすることは
 テスト、デバッグにとって重要。

 出力に設定するには、ポートBのDDRを操作します。

 ワード「constant」でアドレスは定義済み。
 それを利用すると、次のように何種か考えられます。

%00100000 DDRB c!{enter}
$20 DDRB c!{enter}
#32 DDRB c!{enter}
DDRB c@ $20 or DDRB c!{enter}
1 5 lshift DDRB c!{enter}

 ワード「c!」は、16ビット中の下位8ビットを利用した
 パラメータ格納が可能。上位8ビットは扱いません。

 2進数、10進数、16進数で数値を指定したいとき
 surfixの%、#、$を使います。

 ワード「lshift」は、数値を指定したビット数だけ
 左に論理シフトしてスタックトップに格納。

 LEDの点灯、消灯は、次のように定義できます。

PORTB c@ $20 or PORTB c!{enter}
PORTB c@ $df and PORTB c!{enter}

 ビット操作でレジスタPORTBの5ビット目を
 セット、クリア。論理和、論理積を使うと
 他のビットに影響を与えず、対象ビットを
 操作できるようになっています。

 端末からポートBを扱う操作をして、LEDの
 状態がどうなるかを見てみましょう。



 動作が確認できたなら、それぞれをワード定義。

: init_led DDRB c@ $20 or DDRB c! ;
: led_on PORTB c@ $20 or PORTB c! ;
: led_off PORTB c@ $df and PORTB c! ;

 Forthのプログラミングは、ビルトインまたは
 自分で定義したワードを組合わせること。

 点滅というワードを定義し、より理解が容易な内容にしていきます。

 ワード「led_blink」を定義。

: led_blink led_on 500 ms led_off 500 ms ;

 ワード「led_blink」を拡張して、何かキーが
 入力されるまで、点滅を繰返す「led_flush」
 ワードを定義します。

: led_test 1 + init_led 0 do i . led_blink loop ;

 反復に使う構文は、3種あります。

begin ... again
begin ... flag until
begin ... flag while ... repeat

 無限ループ構成とループを脱出する処理の2種に
 分かれています。

 flag untilは、条件成立でループを抜け
 flag while ... repeatは、条件成立で
 ループ内部の処理を実行となります。

 ワード「led_flush」では、キー入力があれば
 ループを抜けるようにしています。

 ポートCに接続したスイッチの論理値をポートBにある
 LEDに反映させるワードを定義してみます。



 ポートB、Cの入出力方向を設定。

DDRB c@ $ff or DDRB c!{enter}
DDRC c@ $00 and DDRC c!{enter}

 DDRBレジスタの値を取り出して、16進数FFと論理和を
 求めるので、スタックに16進数FFが残っています。
 それを再びDDRBレジスタの値として書き込み。

 DDRCレジスタの値を取り出して、16進数00と論理積を
 求めるので、スタックに16進数00が残っています。
 それを再びDDRCレジスタの値として書き込み。

 レジスタの値をスタックに転送し、加工してから
 再びレジスタの値にするのが、I/O操作の基本と
 考えます。

 ポートCからの入力は、PINCで扱います。
 入力値を確認するには、以下の様にキー
 をタイプしてみればよいでしょう。

PINC c@ .{enter}

 入力した値をスタックにおき、それをポートBに
 転送すれば、仕様を満足するはず。

PINC c@ PORTB c!{enter}

 スタックがデータを入れるバッファの役割を
 しているとわかれば、1行処理にまとまります。

 正論理、負論理の混在したシステムでは、論理
 反転を追加して対応。

PINC c@ $ff xor PORTB c!{enter}

 モータを動かすために半田付けした基板があります。



 マルチプロセッサを実現するために作成した基板。

 サーボモータ、DCモータを制御するプロセッサに
 PIC12F1501を利用。ATmega328からPICに情報伝達
 するたには、74HC165を経由。

 他にセンサーからの4ビット情報を入手するため
 74HC157をATmega328に接続。



 各ポートの信号割り当ては、以下。

  PORTB
    PB5 (output)LED
    PB4 (output)
    PB3 (output)D3
    PB2 (output)D2
    PB1 (output)D1
    PB0 (output)D0

  PORTC
    PC5 (output)command(PIC)
    PC4 (output)select
    PC3 (input )sensor_3(sensor_7)
    PC2 (input )sensor_2(sensor_6)
    PC1 (input )sensor_1(sensor_5)
    PC0 (input )sensor_0(sensor_4)

  PORTD
    PD7 (output)D7
    PD6 (output)D6
    PD5 (output)D5
    PD4 (output)D4
    PD3 (output)nLOAD_LEFT
    PD2 (output)nLOAD_RIGHT
    PD1 (output)TxD
    PD0 (input) RxD

 ポートB、C、Dの初期化が必要なのでワードを定義。

: init_port
 $00 PORTB c! (PB value)
 $f0 PORTC c! (PC value)
 $0c PORTD c! (PD value)
 $ff DDRB c! (PB data direction)
 $f0 DDRC c! (PC data direction)
 $fe DDRD c! (PD data direction)
;

 2種のセンサーから、各4ビットのデータを取得するため
 変数を定義して、それらに格納すればよいでしょう。

variable SENSOR
variable OTHERS

: get_sensor ( -- )
 PORTC c@ $e0 and PORTC c!
 PINC c@ $0f and SENSOR c!
;

: get_other ( -- )
 PORTC c@ $10 or PORTC c!
 PINC c@ $0f and OTHERS c!
;

 PICに情報を渡すには、74HC165にデータを渡した後
 トリガーを与えます。

 74HC165に1バイトを渡すワードを定義して、その
 ワードにトリガーを与える処理を追加していきます。

: put_byte (x -- )
  dup 
  4 rshift $0f and swap
  $0f and 4 lshift $f0 and
  PORTD c@ $0f and or PORTD c!
  PORTB c@ $f0 and or PORTB c!
;

: snd_byte (s x -- )
  put_byte =0 if $fb else $f7 then
  dup PORTD c@ and PORTD c!
  $ff xor PORTD c@ or PORTD c!
;

: snd_trg (x1 x0 -- )
  0 swap snd_byte 1 swap snd_byte ;
  PORTC c@ $20 or PORTC c!
  PORTC c@ $df and PORTC c!
;

 74HC165に8ビットデータを転送するときに、nLOADピンに
 負パルスを与えます。こういうときは、1ビットの論理値
 指定ができると便利。

 Forthでは、「無いものは作る」という思想なので、元締め
 となるワードを定義し、動作を指定するパラメータを渡す
 方針で、トリガー用ワードを書いてみます。

: pdo.set 1 swap lshift PORTD c@ or PORTD c! ;
: pdo.clr 1 swap lshift $ff xor PORTD c@ and PORTD c! ;

: snd_htrg 3 pdo.clr 3 pdo.set ;
: snd_ltrg 2 pdo.clr 2 pdo.set ;

 ワード数は増えますが、名称で何をするのか判読しやすくなります。

 ポートCでは、ビット4、5で論理値を出力する操作が
 必要なので、ポートCの各ビットを操作する汎用ワード
 を定義し、それを使うラッパーワードを書いてみます。

: pco.set 1 swap lshift PORTC c@ or PORTC c! ;
: pco.clr 1 swap lshift $ff xor PORTC c@ and PORTC c! ;

: snd_ptrg 4 pco.set 4 pco.clr ;
: sel_a 5 pco.clr ;
: sel_b 5 pco.set ;

 ワードの定義の中に、他のワードを指定することで
 よりわかりやすくできます。

 既存のワードを利用して、より特化した機能をもつ
 ワードを定義して、システムを使いやすくできると
 なると、ワード定義、即実行の特徴が活きてきます。


目次

inserted by FC2 system