KC's notebook

I/O

I/Oとは?

I/Oとはinput(入力)とoutput(出力)の頭文字を取ったもので入出力という意味となります。ここでいうI/Oとはマイコンから論理信号を入出力させることです。論理信号?ナニソレ?って方は、そこまで難しく考える必要はありません。マイコンに興味をもっておられる方なら、「コンピュータは1と0の世界で出来ている」みたいな謳い文句を訊いたことがあるのではないのでしょうか?実はその通りで、コンピュータは1と0で処理をしています。電気が流れている(1)、電気が流れていない(0)で計算等の処理を行っており、このように0と1で表す手法を2進法と呼ばれています。では論理信号とはなんなのかというと、先に述べた「電気が流れている(1)、電気が流れていない(0)」のことです。論理信号で表す場合は1を「HIGH」、0を「LOW」と表現する場合があります。つまるところ、ONなのかOFFなのか、ただそれだけです。

レジスタの説明

使用するマイコンはMega88なので、まずはMega88のデータシートを見てみましょう。検索エンジンで探せばすぐ出てくるはずです。本来は英語で書いてあるのですが、有志の方々によって日本語訳されているデータシートもありますので、そちらも参考にしてみると良いかと思います。

まず、AVRのI/Oを使うには、3つのレジスタを理解する必要があります。ここで、レジスタという謎のワードが出てきましたがこれも難しく考える必要がありません。レジスタとはただのメモリです。マイコンはこのレジスタ(メモリ)に書いてある値を読み取って動作をします。各レジスタにはそれぞれ役割が割り当てられていて、あるレジスタは受け取ったデータを保持していたり、別のレジスタでは1を書き込むとタイマーをスタートさせることが出来たり……とこんな感じです。

閑話休題、話を本筋に戻しましょう。理解する必要があるレジスタとは、

の3つのレジスタです。小文字のxは、B・C・Dが入ります。

では、各レジスタを細かく見ていきましょう。

DDRxレジスタ

DDRxレジスタは、「Port x Data Direction Register(ポートx方向レジスタ)」の略で、入出力の方向を決めるレジスタです。もう少しわかり易く言うと、入力設定にするのかい、出力設定にするのかい、どっちなんだいっ!ということです。出力設定にする場合は1を、入力設定にする場合は0を書き込みます。

PORTxレジスタ

PORTxレジスタは、「Port x Data Register(ポートx出力レジスタ)」の略です。

PORTxレジスタは特殊で、DDRxレジスタの値により機能が変化します。DDRxが1(HIGH)のとき出力設定となり、1を書き込むとHIGHに、0を書きこむとLOWになります。DDRxが0(LOW)の場合は、内部プルアップ抵抗の有効/無効の切り替えとなり、有効にする場合は1に、無効にする場合は0に設定します。

PINxレジスタ

PINxレジスタは、「Port x Input Addres(ポートx入力レジスタ)」の略で、入力のデータを得られるレジスタです。目的のピンがLOWになっている場合0を、其れ以外の時は1を得ることができます。

では、実際にC言語のプログラムを見てみましょう。

点滅プログラム

#include "avr/io.h"
#define F_CPU 1000000UL
#include "util/delay.h"

int main( void ){

    //初期化
    DDRD = 0b00000010;          //1bit目を1,その他を0に
    PORTD = 0b00000000;         //全ビットを0に

    //点滅
    while( 1 ){
        PORTD = 0b00000010;     //1bit目をHIGHに
        _delay_ms( 500 );       //500msec wait
        PORTD = 0b00000000;     //1bit目をLOWに
        _delay_ms( 500 );       //500msec wait
    }

    return 0;
}

上記のプログラムはポートDの1ピンを500msecでON/OFFするプログラムです。プログラム自体はとても簡素で理解しやすいかと思います。ですが簡単に解説をしましょう。

まず1行目ではio.hをインクルードしています。詳しい話は割愛させていただきますが、これによりPORTDやDDRDといったレジスタ名をプログラム上で使うことができます。2行目のdefineは、F_CPUの値を定義しています。F_CPUとは、マイコンのクロック周波数を定義するものであり、これを正確に定義しないと、3行目でインクルードしているdelay.hで使えるdelayの関数群のwait時間が狂ってしまいます。

main関数内では最初に初期化を行い、次に点滅処理を行っています。まず、DDRDの1bit目をHIGHにして、出力設定にしています。次に、保険でPORTDをすべてLOWにしています。点滅処理では、PORTDの1bit目をHIGHにしてポートDの1ピンをHIGHにしています。その後500ミリ秒waitを挟み、次にPORTDをすべてLOWにすることで、ポートDの1ピンをLOWにしています。もう一度500ミリ秒waitをいれてからループの先頭に戻ります。

ポートDの1ピンを1kΩ以上の抵抗(感覚的にそのくらいですが、実際繋げる時はその時の電圧とLEDの最大定格電流を考えて抵抗値を求めて下さい)を挟んでLEDのアノードに繋ぎ、LEDのカソードをGNDに接続してあげると、LEDが点滅するかと思います。

では次に、入力を使ったプログラムを書いてみましょう。

入出力プログラム

#include "avr/io.h"
#define F_CPU 1000000UL
#include "util/delay.h"

int main( void ){

    //初期化
    DDRD =  0b00000010;             //1bit目を1,その他を0に
    PORTD = 0b00000100;             //2bit目をHIGHに

    //押されたら光るよ
    while( 1 ){
        if( ( PIND >> 2 ) & 1 ){    //もし、ポートDの2ピンがHIGHなら
            PORTD &= 0b11111101;    //PORTDと0b11111101の論理積をとる
        }else{                      //もし、ポートDの2ピンがLOWなら
            PORTD |= 0b00000010;    //PORTDと0b00000010の論理和をとる
        }
    }

    return 0;
}

上記のプログラムは、入力ピンの変化によって出力ピンを変化させるプログラムです。こちらは先程のプログラムに比べて理解しづらいかと思います。なので焦らずに一個一個解説していきましょう。

まず、ヘッダのインクルードは先と変わらないので大丈夫でしょう。問題はメイン関数です。最初のDDRDは先のプログラムと一緒ですが、PORTDの設定から少し毛色が違ってきます。先のプログラムでは全ビットを0にしていたのですが、今回は2bit目を1にしています。これは、内部プルアップ抵抗をONにするためです。先程説明しましたがDDRxを0に設定したとき、PORTxはプルアップ抵抗のON/OFFの設定になります。

さて、次のif文内を見てみると、なにやら良く分からないプログラムが出てきました。

 ( PIND >> 2 ) & 1 

これは、PINDの値を2bit右にシフトして、0bit目のビットマスクをとっています。言葉で説明しても「???」となってしますので、順を追って解説しましょう。

…の前に、今後の説明で01001010(2)のような表記が出てきます。これは、括弧内の進数で表わされる数値となります。01001010(2)は2進数の数値であり、10進数に直すと74(10)となります。今後の説明でバンバン使いますので、覚えておいてください。

まず、最初の2bit右シフトですが、仮にPINDの値が10100101(2)の場合、右に2bitシフトすると

10100101|
    ↓
00101001|01

となり、0bit目の位置に元の2bit目の値がきます。次に1と論理和をとると

  00101001
&)00000001
----------
  00000001

となり、1を得ることができます。では、次にPINDの値が10100001(2)となった場合を考えてみると、

10100001|
    ↓
00101000|01

となり、0bit目の位置に元の2bit目の値がきます。次に1と論理和をとると

  00101000
&)00000001
----------
  00000000

となり、0を得ることができます。つまり先程のプログラムは2bit目の値で1(真)/0(偽)になるということです。

では、次の行を見てみましょう。

PORTD &= 0b11111101;

これもわかりづらいですね。ですが、これも先に説明したビットマスクと同じです。これは1bit目の値を強制的に0にし、他ビットの値を反映させるために、1bit目以外のビットを1にしています。例として00110011(2)という値に論理積をとってみると、

  00110011
&)11111101
----------
  00110001

となり、見事に1bit目だけを0にし、残りはそのままに出来ました。これはどのような意図があるかというと、最初の点滅プログラムだと全体を0にしても問題が無かったのですが、今回のプログラムの場合は、プルアップ抵抗をONにするために2bit目を1に設定してあるため、全体を0にするとプルアップ抵抗の設定もOFFになってしまいます。これを避けるために0にしたいビットだけを0にし、其れ以外はそのままにする方法をとっています。

では、次に進みましょう。else内にある、

PORTD |= 0b00000010;

は、どのような意味があるかわかりますか?

これは先程の「0にしたいビットを0にし、他ビットはそのまま反映させる方法」の逆バージョンで、「1にしたいビットを1にし、他ビットはそのまま反映させる方法」です。これは11001100(2)という値を使って説明しましょう。

  11001100
|)00000010
----------
  11001110

このように、1bit目を1にして、其れ以外はそのまま残しているのがわかりますね。これらのビット演算を用いた任意のビットを変更する方法は、AVRのような8bitマイコンでは必ず使うことになると思います。

なんとなく理解できたでしょうか?

このプログラムは、ポートDの2ピンをスイッチと繋げGNDに落とし、ポートDの1ピンを先程のように抵抗とLEDを繋いであげると、スイッチを押している間だけLEDが点灯する、といった動作になると思います。

余談ですが、よくcbi()やsbi()、_BV()といったマクロをみかけるのですが、そのマクロの意味と動作が理解できているなら良いのですが、そうでないなら自力で書いていったほうが良いかと思います。勉強にもなりますし、ビット演算に対する理解も深まるかと。

追記

このページを読み返してみると、なんの脈絡もなく2進表記が出てきていますね(汗)

ビット演算というとこがあるのでそこで詳しく読んで頂戴!

AVR C言語 制御 その他
inserted by FC2 system