PIC16シリーズの定番、PIC16F84Aを使用してLCDへの出力を行うプログラムです。
16F84AでC言語を使用したLCD制御のサンプルは、意外と見つからないので公開しておきます。
LCDは16文字x2行表示が行えるSC1602を使用します。
また、Cコンパイラは、HI-TECH Cを使用します。HI-TECH Cは、PICC LITEとも呼ばれています。
HI-TECH CにはLCD制御用のライブラリがない為、プロトコルを確認してコマンド送信を自前で実装する必要があります。
動作確認はPIC16F84Aで行っていますが、特殊な事はしてないので16Fシリーズなら基本的にどのチップでもそのまま動くはずです。もし、16F84A以外のPICを使用する場合で、出力ピンがANxと共用になっている場合はピンがデジタル出力モードになっていることを確認してください。
LCDのピンアサイン
SC1602の代表的なピンアサインは以下の通りです。基本的にこれと同じはずですが、モノによってはVccとGND(ピン1,2)が逆になっているモノも有るので、注意してください。
また、LCDはバックライト有り/無しのものが有りますが、動作確認する上ではどちらでもかまいません。
LCD接続の回路図
SC1602のキャラクタLCDは、データバスの接続が8bitモードと4bitモードが有ります。
今回はPIC16F84Aで、ピン数が少ないので4bitモードを使用します。
また、LCDデータの読み書きを制御するのにR/Wピンが有りますが、これも使用しません。
R/Wピンを使用すると、LCDに書き込み可能か、BusyステータスをReadすることで確認できるのですが、PICの使用ピン数を減らすために接続せず、GNDに落としておきます。R/WピンがLowだと常にWriteモードになります。そうするとPIC側からLCDに書き込み可能かを知ることができないのですが、そこはデータシートで定められた時間分delayをかけることで対応します。これは、LCD制御で接続ピン数を最小にしたい場合に良く取られる手法です。
ピンアサインは、LCD.cの先頭のdefineで定義しています。
#define LCD_RS PORTAbits.RA0 #define LCD_ENABLE PORTAbits.RA2 #define LCD_DATA PORTB #define LCD_DATA_IS_LOW // PortBの下位4bitをLCDのDB4-7に接続 |
それでは、プログラムのソースを確認していきます。
メイン関数
まずは、LCD処理の呼び出し元側のmainです。特に説明することが無いほどのプログラムです。
#include <htc.h> #include "lcd.h" //-------------------------------------- // コンフィグレーションビットの指定 //-------------------------------------- __CONFIG ( FOSC_HS & WDTE_OFF & PWRTE_ON & CP_OFF ); void main( void ) { TRISA = 0x00; // RA0-7をOutputにする TRISB = 0x00; // RB0-7をOutputにする // LCD初期化 lcd_init(); // LCD画面クリア lcd_cls(); // LCDに文字列出力 lcd_locate( 0, 0 ); lcd_puts( "Hello world" ); lcd_locate( 1, 0 ); lcd_puts( "コンニチワ" ); while( 1 ) { ; // nop } } |
LCD制御用ライブラリ
次に、ライブラリです。
lcd.h
#ifndef _LCD_H_ #define _LCD_H_ void lcd_init( void ); void lcd_cls( void ); void lcd_locate( unsigned char x, unsigned char y ); void lcd_putc( char c ); void lcd_puts( const char *p ); #endif |
lcd.c
#include <htc.h> #include "lcd.h" #define _XTAL_FREQ 8000000 // __delay_ms()用クロック定義 (8MHz駆動) //----------------------------- // PIC-LCDのピンアサイン //----------------------------- #define LCD_RS PORTAbits.RA0 //#define LCD_RW PORTAbits.RA1 // 未使用 #define LCD_ENABLE PORTAbits.RA2 #define LCD_DATA PORTB #define LCD_DATA_IS_LOW // PortBの下位4bitをLCDのDB4-7に接続 // 内部関数のプロトタイプ宣言 static void lcd_write_cmd( unsigned char data ); static void lcd_write_databus( unsigned char data ); //**************************************************************************************** // Function : lcd_init // Description: LCD初期化処理(4bitモード) //**************************************************************************************** void lcd_init( void ) { //------------------- // 制御ピンの初期化 //------------------- LCD_RS = 0; LCD_ENABLE = 0; #ifdef LCD_DATA_IS_HIGH LCD_DATA = (LCD_DATA & 0x0f); #else LCD_DATA = (LCD_DATA & 0xf0); #endif __delay_ms( 20 ); // 15mSec以上ウェイト //------------------- // LCD初期化処理 //------------------- lcd_write_databus( 0x03 ); __delay_ms( 10 ); // 4.1mSec以上ウェイト lcd_write_databus( 0x03 ); __delay_us( 200 ); // 100uSec以上ウェイト lcd_write_databus( 0x03 ); // モードの指定 lcd_write_databus( 0x02 ); // 4bitモードに変更する __delay_ms( 1 ); // 40uSec以上ウェイト //-------------------- // 画面のクリア //-------------------- lcd_write_cmd( 0x28 ); // ファンクションセット(2行表示、5*7dot) __delay_ms( 1 ); // Display off lcd_write_cmd( 0x08 ); // ディスプレイ・カーソルの設定(カーソル非表示) __delay_ms( 1 ); lcd_write_cmd( 0x01 ); // clear display __delay_ms( 1 ); lcd_write_cmd( 0x06 ); // エントリモード(1:右にスクロール、0:シフトなし) __delay_ms( 1 ); // lcd_write_cmd( 0x0F ); // ディスプレイ・カーソルの設定(ディスプレイ:ON, カーソル:ON , カーソル点滅:ON) // lcd_write_cmd( 0x0E ); // ディスプレイ・カーソルの設定(ディスプレイ:ON, カーソル:ON , カーソル点滅:OFF) lcd_write_cmd( 0x0C ); // ディスプレイ・カーソルの設定(ディスプレイ:ON, カーソル:OFF, カーソル点滅:OFF) } //***************************************************************************** // Function : lcd_locate // Description : 印字位置の指定 //***************************************************************************** void lcd_locate( unsigned char x, unsigned char y ) { unsigned char writeData; writeData = 0x40 * x + y; // 表示位置を決定(2行目に出すときは,3bit目を立てる) writeData |= 0x80; // 最上位ビットを立てる lcd_write_cmd( writeData ); } //***************************************************************************** // Function : lcd_cls // Description : LCD画面クリア //***************************************************************************** void lcd_cls() { lcd_write_cmd( 0x01 ); // clear display __delay_ms( 3 ); // 1.64mSec以上ウェイト } //***************************************************************************** // Function : lcd_putc // Description : LCDに1文字出力 //***************************************************************************** void lcd_putc( char c ) { LCD_RS = 1; __delay_us( 1 ); // 40nSec以上 lcd_write_databus( (c & 0xf0) >> 4 ); lcd_write_databus( (c & 0x0f) ); } //***************************************************************************** // Function : lcd_puts // Description : LCDに文字列出力 //***************************************************************************** void lcd_puts( const char *p ) { while( *p != '\0' ) { lcd_putc( *p ); p++; } } //***************************************************************************** // Function : lcd_write_cmd // Description : 4bitモードでの命令書き込み(RS=LOW) // 命令は上位ビットから順に送る //***************************************************************************** static void lcd_write_cmd( unsigned char data ) { LCD_RS = 0; __delay_us( 1 ); // 40nSec以上 lcd_write_databus( (data & 0xf0) >> 4 ); lcd_write_databus( (data & 0x0f) ); } //***************************************************************************** // Function : lcd_write_databus // Description : dataで指定された下位4bit分のデータをDB4-7に書き込む //***************************************************************************** static void lcd_write_databus( unsigned char data ) { // まずEnableをactiveする LCD_ENABLE = 1; // 書き込むべきデータをセット #ifdef LCD_DATA_IS_HIGH LCD_DATA = (LCD_DATA & 0x0f) | ( (data<<4) & 0xf0); #else // 下位4bitにセットする LCD_DATA = (LCD_DATA & 0xf0) | (data & 0x0f); #endif __delay_us( 1 ); // 80nSec以上 // Enableをinactiveにする LCD_ENABLE = 0; __delay_us( 1 ); // 10nSec以上 // データをALL0でクリアしておく #ifdef LCD_DATA_IS_HIGH LCD_DATA = (LCD_DATA & 0x0f); // 上位4bitをクリア #else LCD_DATA = (LCD_DATA & 0xf0); // 下位4bitをクリア #endif } |
実行結果
プログラムの実行結果は、以下の様な感じになります。今回のプログラムは8MHz駆動させています。8MHz以外で使用する場合は_XTAL_FREQのdefineを変更してください。
使用メモリ
16F84はプログラムメモリが1kしかないので、C言語でプログラムするとこれだけで25%もメモリを使ってしまいます。これは、今回HI-TECH CをLiteモードで使っており、コードサイズの最適化が行われない事も影響しています。
HI-TECH C Compiler for PIC10/12/16 MCUs (Lite Mode) V9.83 Copyright (C) 2011 Microchip Technology Inc. (1273) Omniscient Code Generation not available in Lite mode (warning) Memory Summary: Program space used 106h ( 262) of 400h words ( 25.6%) Data space used Bh ( 11) of 44h bytes ( 16.2%) EEPROM space used 0h ( 0) of 40h bytes ( 0.0%) Configuration bits used 1h ( 1) of 1h word (100.0%) ID Location space used 0h ( 0) of 4h bytes ( 0.0%) |
トラブルシューティング
LCDは良く使われるデバイスでありながら、初期化シーケンスが複雑で、しかも正しく初期化出来ないとLCDは何も言ってくれないので、動作がおかしい場合は何がおかしいのかの切り分けが結構難しいです。上手く動作しない場合は、以下の点を確認してください。
1.LCDの電源/GNDピンが正しいか
キャラクタLCDは、製品によっては1,2ピンの割り当て(+5V,GND)が逆になっているものがあります。必ず購入した製品のデータシートを確認し、想定した結線になっていることを確認してください。
2.LCDのコントラスト調整が出来ているか
実際に文字出力が出来ていてもLCDのコントラストが薄すぎで表示が見えなくなっているだけの場合があります。通常だと3番ピンがコントラスト調整ですので、10kの可変抵抗を入れてコントラスト調整をしてください。Vcc,GND,コントラスト調整だけの線を接続した場合、LCDは全てのドットが黒となる表示をするはずなので、この状態で可変抵抗を調整して、ドットが薄い黒になるようにしておきます。3.配線があっているか
結線間違いは一番ありがちなミスです。プログラムで指定したピンアサインと、実際の回路があっているかをテスタで通電チェックしてください。
4.駆動電圧があっているか
LCDモジュールは5V駆動以外に3.3V駆動のモノも出回っています。3.3V駆動のものを5Vにつないでしまうと、壊れてしまうので注意してください。(品番でgoogle検索してデータシートを確認してください)
3.3V駆動のものをつないでしまうと、コントラストを目一杯絞っても真っ黒になってしまうはずです。
5.各コマンド送信後の待ち時間が有っているか
LCDのデバイスによっては、コマンド送信前後の待ち時間が異なるものが有ります。今回のプログラムは有る程度の余裕を持たせていますが、出力が文字化けする場合はタイミング調整してみてください。(遅くする分には問題ないので、全て10倍する等の極端な待ちを入れてしまってもOKです)
わかるPICマイコン製作集 16F84プログラミングの世界へ
ダウンロード
今回説明したプログラムのソースコードです。MPLABのプロジェクト形式になっています。
ダウンロード:16F84A_LCD.zip
関連記事
16F84でLCDに”Hello world”を表示させる件でソースで理解できない部分が有り、ご教授願えれば有りがたいのですが。当ソースに中で #ifdef LCD_DATA_IS_HIGH が度々使われていますが、_IS_HIGHが定義されている箇所が見当たりません。同様に_IS_LOW //PORTBの下位をLCDのDB-7に接続と有ります。古いコンパイラーと最新のものとでは異なる点があるのでしょうか。よろしくお願いします。
[…] PIC16F84Aを用いていますが、参考になるプログラムソースを公開している ここ 実際私はここのプログラムを少しいじらせてもらい、使っています。 […]