目次
前
次
汎用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の5ビット目を出力に設定
- ポートBの5ビット目を論理値の'1'を出力
- ポートBの5ビット目を論理値の'0'を出力
各処理にワード名をつけておきます。
ワード名を与えることで、動作を明確にすることは
テスト、デバッグにとって重要。
- init_led ポートBの5ビット目を出力に設定
- led_on ポートBの5ビット目を論理値の'1'を出力
- led_off ポートBの5ビット目を論理値の'0'を出力
出力に設定するには、ポート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 ;
ワードの定義の中に、他のワードを指定することで
よりわかりやすくできます。
既存のワードを利用して、より特化した機能をもつ
ワードを定義して、システムを使いやすくできると
なると、ワード定義、即実行の特徴が活きてきます。
目次
前
次