Hello Worldが出力されるまで

raspberry pi と I2C LCD モニターを使ってHello World を出力するプログラムを書いたんですが,モニターにHello World が出力されるまでに何が起きているのでしょうか.→ ラズパイでモニター出力

余談ですが,今回PythonではなくC言語を使ってみたのはハードを意識してみたかったからです.結論から言うとものすごく勉強になりました.

さて,組み立てた回路の全体像,実際の出力は次のようになりました.

また,書いたプログラムも見てみましょう.

#include <stdio.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include <string.h>

int LCDAddr = 0x27;
int BLEN = 1;
int fd;

void write_word(int data){
	int temp = data;
	if ( BLEN == 1 )
		temp |= 0x08;
	else
		temp &= 0xF7;
	wiringPiI2CWrite(fd, temp);
}

void send_command(int comm){
	int buf;
	buf = comm & 0xF0;
	buf |= 0x04;			
	write_word(buf);
	delay(2);
	buf &= 0xFB;			
	write_word(buf);

	buf = (comm & 0x0F) << 4;
	buf |= 0x04;			
	write_word(buf);
	delay(2);
	buf &= 0xFB;			
	write_word(buf);
}

void send_data(int data){
	int buf;
	buf = data & 0xF0;
	buf |= 0x05;			
	write_word(buf);
	delay(2);
	buf &= 0xFB;			
	write_word(buf);

	buf = (data & 0x0F) << 4;
	buf |= 0x05;			
	write_word(buf);
	delay(2);
	buf &= 0xFB;			
	write_word(buf);
}

void init(){
	send_command(0x33);	
	delay(5);
	send_command(0x32);	
	delay(5);
	send_command(0x28);	
	delay(5);
	send_command(0x0C);	
	delay(5);
	send_command(0x01);	
	wiringPiI2CWrite(fd, 0x08);
}

void clear(){
	send_command(0x01);	
}

void write(int x, int y, char data[]){
	int addr, i;
	int tmp;
	if (x < 0)  x = 0;
	if (x > 15) x = 15;
	if (y < 0)  y = 0;
	if (y > 1)  y = 1;

	addr = 0x80 + 0x40 * y + x;
	send_command(addr);
	
	tmp = strlen(data);
	for (i = 0; i < tmp; i++){
		send_data(data[i]);
	}
}

void main(){
	fd = wiringPiI2CSetup(LCDAddr);
	init();
	write(0, 0, "HELLO WORLD");
	write(1, 1, "BY OGAWA");
}

モニターにHello World を出力するプログラム,一連の流れについて理解を深めるために,いくつかのトピックをまとめてみました.

I2C通信について

I2C通信とは,データの通信を行う同期式のシリアル通信のことです.クロックライン(SCL)とデータライン(SDA)の2本の信号線を用います.今回はraspberry pi と I2C LCDモニターを用いていますが,raspberry pi には通信を開始しクロックを生成する’マスタ’と呼ばれる役割を持たせ,I2C LCDモニターにはマスタからの指示を受けデータを送受信する’スレーブ’と呼ばれる役割を持たせています.

I2C通信の全体の流れとしては,
1.マスタが通信を開始.
2.スレーブアドレスの送信.マスタはアドレスと読み書き指示(R/Wビット)を送信.Rは1,Wは0
3.スレーブのACK.スレーブがアドレスを認識し,ACKを返す.
4.データの送受信.書き込みの場合はマスタがデータを送信し,スレーブがACKを返す.
5.通信の終了.
というような流れになっています.(らしい)

さて,I2C通信が関わっている部分のコードを見ます.

fd = wiringPiI2CSetup(LCDAddr);

wiringPiI2CSetup関数は,指定されたI2Cアドレスに基づき,デバイスを初期化します.引数となっている LCDAddr はスレーブデバイスのアドレスです.fd はI2C通信を行うに際して使用します.

wiringPiI2CWrite(fd, temp);

この関数では指定されたデータをI2Cデバイスに送信しています.fd は先ほど定義したファイルディスクリプタで,I2Cデバイスを示します.tempは送信するデータです.

void write_word(int data){
    int temp = data;
    if ( BLEN == 1 )
        temp |= 0x08;
    else
        temp &= 0xF7;
    wiringPiI2CWrite(fd, temp);
}

この関数ではデータをフォーマットし,I2Cデバイスに送信します.data に対して,バックライトがオンの時はビット3をセットし,オフの時はビット3をクリアします.そして先ほどのwiringPiI2CWrite(fd, temp)を使用してデータを送信します.

LCDディスプレイの制御

LCDディスプレイには複数の制御ビットがあります.

RSビット:コマンドとデータの判別をするビット
RWビット:読み書きの判別をするビット
ENビット:データの有効化ビット
バックライト制御ビット:バックライトのオンオフを制御するビット

関数でどんな処理が行われているか.

このコードでは,main関数以外に,write_word関数,send_command関数,send_data関数,init関数,clear関数を定義しています.それぞれの関数でどんな処理が行われているか見てみます.

void write_word(int data){
	int temp = data;
	if ( BLEN == 1 )
		temp |= 0x08;
	else
		temp &= 0xF7;
	wiringPiI2CWrite(fd, temp);
}

この関数ではBLEN(バックライト)の状況に応じてLCDに送信するデータを変更しています.BLENが1の場合はバックライトをオンにし,1でない場合にバックライトをオフにしています.temp |= 0x08, temp &= 0xF7 で第3ビットを変更しています.条件分岐でtempの値を修正し,wringPiI2CWrite関数を通じてLCDに送信し,バックライトを制御します.

void send_command(int comm){
	int buf;
	buf = comm & 0xF0;
	buf |= 0x04;			
	write_word(buf);
	delay(2);
	buf &= 0xFB;			
	write_word(buf);

	buf = (comm & 0x0F) << 4;
	buf |= 0x04;			
	write_word(buf);
	delay(2);
	buf &= 0xFB;			
	write_word(buf);
}

この関数ではI2C通信によりLCDにコマンドを送信しています.buf = comm & 0xF0;でコマンドの上位4ビットを抽出しています.また,buf |= 0x04;でRS,RW,ENビットを設定しています.そしてbufの値をwrite_word()に渡してLCDに送信します.

send_commandではLCDの動作を制御する命令を,send_dataではディスプレイに表示する文字や情報のデータを送ります.
また,init関数では,LCDのビットモードの設定,5×7ドットの文字表示をする設定,カーソルを非表示にする設定等の,基本的な設定を行っています.

このように,main関数以外の関数でLCDディスプレイとインターフェースを制御していることがわかります.

機能を追加してみよう.

機能を追加したり,動作を変更したりするにはmain関数を書き換えていきます.例としてmain関数の中身を変えて,入力した文字を出力するプログラムにしてみます.(1列のみです,すみません)

int main(){
    char input[32]; 
    fd = wiringPiI2CSetup(LCDAddr);
    init();
    printf("Enter text to display on the LCD: ");
    fgets(input, sizeof(input), stdin);
    write(0, 0, input);
    return 0;
}

C言語で遊ぶラズパイ

長々と書きました.ここでPythonで同じことをやろうとするとどうなるのか,コードを見てみます.

import LCD1602
import time

def setup():
    LCD1602.init(0x27, 1)   
    LCD1602.write(0, 0, 'Hello World')
    LCD1602.write(1, 1, 'By Ogawa')
    time.sleep(2)

def destroy():
    LCD1602.clear()

if __name__ == "__main__":
    try:
        setup()
    except KeyboardInterrupt:
        destroy()

たったこれだけになります.Python(のライブラリ),優秀ですね.

いくらPythonよりC言語が早いからといってPythonを使わない.というのではなく,処理速度を求める部分はC言語であったりハードウェアロジックを求め,複雑な部分はPythonで開発できるといいですね.

とはいえ今回C言語でI2C LCDモニターを制御したわけですが,すごく勉強になりました.