delayやwhileを使わないI2C液晶表示プログラム

delayやwhileを使わないI2C液晶表示プログラム

概要

秋月やaitendoで売っているI2C液晶を使っている人は多い。しかし、ほとんどのサンプルプログラムは、I2Cの通信をwhile文などにより送信完了待ちしていて、その間に(割り込みを除く)他のプログラムが実行できない。そこで今回は、I2C液晶に表示させる内容を送信バッファに入れておけば自動で送信してくれるプログラムを書く。

秋月の液晶もaitendoの液晶も共通の液晶制御ICを使っている。その型番が「ST7032」なので、液晶のこともその名前で呼ぶ。
秋月のI2C液晶(AQM0802)

AQM0802

AQM0802

ロジック・アナライザで一目瞭然!

実際にプログラムが動作している様子をロジック・アナライザで確かめた。
上の2つのデータがI2C、一番下のデータがメインループのループ回数を表している。

ダメな方(I2C通信をしている間は、メインループが止まってしまっている。)

old program

old program

良い方(I2C通信をしながらメインループもしっかり動いている。)

new program

new program

※双方のメインループに停止時間があるが、これは、送信リングバッファに送る処理に時間がかかっているだけなので今回は無視して欲しい。

プログラム

僕は普段からPICを使っているので、PIC18F27J53用のプログラムを書いた。

ダメな方
PIC18F27J53-I2C-LCD-old.zip
良い方
PIC18F27J53-I2C-LCD-new.zip
(MPLAB.Xプロジェクトファイルなので、そのままMPLABXで読み込めます。)

以下はプログラムの要点だけ抜粋
mainループにST7032_task();があり、中のswitch文で状態遷移をしていく。何もアクションがなければすぐに関数から抜ける。割り込み関数内にはST7032_ISR();があり、送信完了割り込みなどで任意のフラグを上げる。ST7032_task();ではフラグがあがるのを監視しているが、while文での監視ではなく、何もなければすぐ関数からbreakして、なにかフラグが上がっていた時だけ、その瞬間にできる仕事をだけをしてまた関数から抜ける。

使い方
main.c

#include "My_ST7032.h"
void interrupt ISR(void){
	ST7032_ISR();
}
int main(void){
	Hardware_initialize();// いろいろ初期設定する
	ST7032_init();

	ST7032_cursor(0,0);
	ST7032_puts("PIC18F27J53");

	// main loop
	while(1){
		ST7032_task();
	}
	return 0;
}

My_ST7032.h←リンク

typedef enum ST7032_STATE {
	ST7032_IDLE,
	ST7032_START,
	ST7032_SEND,
	ST7032_STOP,
	ST7032_WAIT
} st7032_state_t;

My_ST7032.c←リンク

#include "My_ST7032.h"

st7032_state_t st7032_state;
ringbuf_t lcd_tx;
uint8_t st7032_flag;

void ST7032_put(char c) {
	ringbuf_put(&lcd_tx, (ST7032_ADDRESS << 1) + W_0);
	ringbuf_put(&lcd_tx, 0b11000000); // control byte
	ringbuf_put(&lcd_tx, c); // data byte 
	ringbuf_put(&lcd_tx, 0); // stop bit
	ringbuf_put(&lcd_tx, WAIT_26US); // wait
}
void ST7032_ISR(void) {
	if (PIE1bits.SSP1IE && PIR1bits.SSP1IF) {
		PIR1bits.SSP1IF = 0;
		st7032_flag = 1;
	}
	if (PIE2bits.BCL1IE && PIR2bits.BCL1IF) {
		PIR2bits.BCL1IF = 0;
	}
}

void ST7032_task(void) {
	if ((SSP1CON2 & 0x1F) | (SSP1STAT & 0x05)) {
		// SSP is busy
		return;
	}
	static uint16_t wait;
	static st7032_state_t st7032_state = ST7032_IDLE;

	switch (st7032_state) {
		case ST7032_IDLE:
			if (ringbuf_num(&lcd_tx)) {
				st7032_state = ST7032_START;
			}
			break;
		case ST7032_START:
			st7032_flag = 1;
			SSP1CON2bits.SEN = 1;
			st7032_state = ST7032_SEND;
			break;
		case ST7032_SEND:
			if (!st7032_flag) {
				// no SSPIF
				return;
			}
			char data = ringbuf_pop(&lcd_tx);
			if (data != 0) {
				st7032_flag = 0;
				SSP1BUF = data;
			} else {
				wait = ringbuf_pop(&lcd_tx);
				switch (wait) {
					case WAIT_0:
						wait = 0;
						break;
					case WAIT_26US:
						wait = 2;
						break;
					case WAIT_1MS:
						wait = 25;
						break;
					case WAIT_40MS:
						wait = 800;
						break;
					default:
						wait = 1;
						break;
				}
				st7032_state = ST7032_STOP;
			}
			break;
		case ST7032_STOP:
			SSP1CON2bits.PEN = 1;
			st7032_state = ST7032_WAIT;
			break;
		case ST7032_WAIT:
			if (wait == 0) st7032_state = ST7032_IDLE;
			else wait--;
			break;
	}
}