LCDサンプル †回路図 †まずは配線に悩む。 あとは、busyフラグをチェックするかどうか。 となると、必要なピンは、RS、R/W、E、DB4〜DB7で、全部で7ピンか。 4bitモードでは、DB0〜DB3は使用しないので、オープンにしておく。 Voは液晶の濃度調整を分圧でやってて、Vcc5Vの1/(10+1)の、約0.45Vがかかる。 ソースコード †定義とウェイト関数 †まずは、includeに各種定数定義と、LEDでも使った待ち関数。 もう一つ、データ書き込み時に、コマンドモードかデータモードかを示すRSの値だけ書き換えれば、他の処理は共通で済むため、引数で渡すためのモードをenumで定義。 #include <avr/io.h> #include <util/delay.h> #define LCD_PORT PORTD // LCD表示に使用する出力レジスタ #define LCD_PIN PIND // LCD表示に使用する入力レジスタ #define LCD_DDR DDRD // LCD表示に使用する方向レジスタ #define LCD_D4 (1<<0) // D4ビット位置 #define LCD_D5 (1<<1) // D5ビット位置 #define LCD_D6 (1<<2) // D6ビット位置 #define LCD_D7 (1<<3) // D7ビット位置 #define LCD_E (1<<5) // Eビット位置 #define LCD_RW (1<<6) // R/Wビット位置 #define LCD_RS (1<<7) // RSビット位置 #define LCD_DATMASK 0x0F // データビットマスク // RSモード列挙型定義 typedef enum _tagRSMode{ RS_MODE_COMMAND, // コマンドモード RS_MODE_DATA, // データモード } RSMode; void delay_ms( int time ) { // 指定ms分ループ while( time-- ){ _delay_ms( 1 ); } } 基本動作 †で、まずはLCDを初期化しなければならないわけだ。 まず基本的な動作として、RS(Register Select)はコマンドモード(0)/データモード(1)の切り替えに使用し、R/Wはライトモード(0)/リードモード(1)の切り替えに。 これらコマンド通りに、出力/入力ポートの設定をして、その状態でE(Enable Signal)をLow→High→Lowとすることにより、コマンドが実行される。 この表では、RSとR/Wのセットアップ時間が0nsとなっているため、基本的に待つ必要はないらしい。 で、これをプログラムで実際にどれくらい意識する必要があるのかということだが。 AVRは、最速(レジスタ間計算等)で1MHz/1MIPSであり、この場合、1クロックサイクルで1命令が実行される。 では、8MHz駆動の場合は、どれくらい待てばいいのか。 ちなみにこれは、コマンド発行のタイミングの話であって、実際にコマンドが処理される時間の話ではないはず。 ライトモードであれば、EのHigh→Low→Highの間のデータをLCDがその瞬間に読み取って、実際にそれを処理する間Busyとなるというイメージなんだけど、あってるんだろうか? 初期化シーケンス †Lcd_initという初期化関数を作成し、この中でLCDの初期化シーケンスを実行。 今回は4bitモードなので、下の方。 まず、PowerONして起動してから、15ms以上待つ。 確実に8bitモードになったところで、ここでまたファンクションセットを使用し、4bitモードの設定を1回だけ行う。 どこのページにも書いてあることだけど、ここまではBusyフラグは使えない。 あとは、これらのコマンドを4bit単位で送ってやるので、4bitコマンド送信関数を作って、引数にコマンドを書いてやることにしよう。 void Lcd_init( void ) { // 最初は全てのポートを出力に設定 LCD_DDR |= ( LCD_E | LCD_RW | LCD_RS | LCD_DATMASK ); // 信号もLowにしておく LCD_PORT &= ~( LCD_E | LCD_RW | LCD_RS | LCD_DATMASK ); // 最初はBusyチェックが効かないので、時間待ちする delay_ms( 15 ); // 15ms待ち lcd_setcmd4( RS_MODE_COMMAND, 0x3 ); // 8bitモード設定 delay_ms( 5 ); // 4.1ms以上待ちなので、5ms待ち lcd_setcmd4( RS_MODE_COMMAND, 0x3 ); // 8bitモード設定 delay_ms( 1 ); // 100μs以上待ちなので、とりあえず1ms待っておく lcd_setcmd4( RS_MODE_COMMAND, 0x3 ); // 8bitモード設定 delay_ms( 1 ); // ファンクションセットは39μs以上待ちなので、とりあえず1ms待っておく // 初期化シーケンスに書いてある通り、4bitの設定は4bitのみ渡せれば有効なので、1回のみ lcd_setcmd4( RS_MODE_COMMAND, 0x2 ); // 4bitモード設定 delay_ms( 1 ); // ファンクションセットは39μs以上待ちなので、とりあえず1ms待っておく // ここからは4bitモード動作なので、8bitの命令を4bitずつ2回に分けて送ってやる lcd_setcmd4( RS_MODE_COMMAND, 0x2 ); // ファンクションセット、4bitモード lcd_setcmd4( RS_MODE_COMMAND, 0x8 ); // 2Line表示、5*8ドットフォント delay_ms( 1 ); // ファンクションセットは39μs以上待ちなので、とりあえず1ms待っておく lcd_setcmd4( RS_MODE_COMMAND, 0x0 ); // Display ON/OFF設定 lcd_setcmd4( RS_MODE_COMMAND, 0xF ); // Display ON / Cursor ON / Blinking ON delay_ms( 1 ); // Display ON/OFF設定は39μs以上待ちなので、とりあえず1ms待っておく lcd_setcmd4( RS_MODE_COMMAND, 0x0 ); // Clear Displayコマンド lcd_setcmd4( RS_MODE_COMMAND, 0x1 ); // スペースでDDRAMが埋められ、アドレスカウンタがDDRAMの00H(先頭)に移動 delay_ms( 2 ); // Clear Displayコマンドは1.53ms以上待ちなので、2ms待ち lcd_setcmd4( RS_MODE_COMMAND, 0x0 ); // Entry Mode lcd_setcmd4( RS_MODE_COMMAND, 0x6 ); // インクリメントモード / ディスプレイシフトなし delay_ms( 1 ); // Entry Modeコマンドは39μs以上待ちなので、とりあえず1ms待っておく } 最初に、ポートが変な状態になっていないように、入出力状態と信号レベルを初期化しておく。 では次に、4bitコマンド送信関数lcd_setcmd4の中身。 void lcd_setcmd4( RSMode mode, uint8_t data ) { uint8_t tmpval = LCD_PORT; // ポート状態を一旦変数に入れる // 書き込むポートを一旦落とす tmpval &= ~( LCD_RW | LCD_RS | LCD_DATMASK ); // RSビットコマンドモードであればクリアのまま if( RS_MODE_DATA == mode ){ tmpval |= LCD_RS; // RSビットはデータモードであれば立てる } // RWビットはクリアでWriteモードなのでそのまま // データビットセット(下位4bitのみセットするのマスク) tmpval |= ( LCD_DATMASK & data ); // ポートに書き込み LCD_PORT = tmpval; // イネーブルパルス出力 LCD_PORT |= LCD_E; // 高速駆動時はここでイネーブルパルス幅以上待つ必要があるはず LCD_PORT &= ~LCD_E; // 高速駆動時はここでイネーブルサイクル時間を稼ぐ必要があるはず } I/Oレジスタの中身を、一時変数(汎用レジスタ)内に持っている理由は、なんとなく。 とはいえ、この方法だと、変数に保存してから書き戻すまでに、割り込み等で今回使用しているポートの一部が書き換えられたりとかしたら、書き戻し時にそこをリセットしてしまうので、本当はあまりよろしくないのかも。 文字表示 †LCDには、以下のようにフォントテーブルが定義されている。 というわけで、文字列を引数に取る関数と、文字を引数に取る関数を作って、前者から後者を呼び、後者から前述4bit送信関数をコールする。 void Lcd_setchar( char data ) { // 上位4bit送信 lcd_setcmd4( RS_MODE_DATA, LCD_DATMASK & ( data >> 4 )); // 下位4bit送信 lcd_setcmd4( RS_MODE_DATA, LCD_DATMASK & data ); // Busyチェック lcd_busywait(); } void Lcd_setstr( char *str ) { // 終了(NULL文字までループ) while( *str ){ // 1文字ずつ設定 Lcd_setchar( *str ); str++; } } 初期化の時はコマンドモードを設定したけど、今回は文字の出力なので、データモードを指定する。 で、次にBusyチェック関数。 void lcd_busywait( void ) { uint8_t bf = LCD_D7; // Busyフラグが立った状態の変数を初期化 // RSビットはクリアでコマンドモード LCD_PORT &= ~( LCD_RS ); // ReadモードはRWビットを立てる LCD_PORT |= ( LCD_RW ); // データビットを入力に設定 LCD_DDR &= ~( LCD_DATMASK ); // Busyフラグが落ちるまでループ while( bf ){ // イネーブルパルス出力(上位4bit) LCD_PORT |= LCD_E; // 高速駆動時はここでイネーブルパルス幅以上待つ必要があるはず bf = LCD_PIN & ( 1 << LCD_D7 ); // Busyフラグの読み出し LCD_PORT &= ~LCD_E; // 高速駆動時はここでイネーブルサイクル時間を稼ぐ必要があるはず // イネーブルパルス出力(下位4bit/4bitモードなので2回送る必要があるがここでは特にすることはない) LCD_PORT |= LCD_E; // 高速駆動時はここでイネーブルパルス幅以上待つ必要があるはず LCD_PORT &= ~LCD_E; // 高速駆動時はここでイネーブルサイクル時間を稼ぐ必要があるはず } // データビットを出力に戻す LCD_DDR |= ( LCD_DATMASK ); } とりあえず、以上で文字表示までは出来るようになった。 文字出力位置 †さっき、文字表示用に、1バイトの文字を4bitずつ2回に分けて送るLcd_setcharという関数を作ったので、同様にコマンドを送るLcd_setcmdという関数も作っておく。 void Lcd_setcmd( uint8_t cmd ) { // 上位4bit送信 lcd_setcmd4( RS_MODE_COMMAND, LCD_DATMASK & ( cmd >> 4 )); // 下位4bit送信 lcd_setcmd4( RS_MODE_COMMAND, LCD_DATMASK & cmd ); // Busyチェック lcd_busywait(); } この関数を使用して、表示位置を指定できる関数Lcd_setposを作る。 void Lcd_setpos( int8_t x, int8_t y ) { int8_t val = 0x80; // コマンドコード設定 if( x ) val |= 0x40; // 行指定が0(1行目)じゃなかったら、2行目bitを立てる val |= ( y & 0x0f ); // 列指定の4bitを設定する // コマンド送信 Lcd_setcmd( val ); } メイン関数 †最後に、今までの関数を使用して、上の動作写真のように文字表示をさせるメイン関数。 int main( void ) { Lcd_init(); Lcd_setstr( "LCDサンプル" ); Lcd_setpos( 1, 0 ); // 2行目の1列目(0ベース) Lcd_setstr( "ヒョウジテストチュウ" ); return 0; } まぁ、ここはもう説明する必要はないねw |