前回の記事で I2C通信を制御するライブラリを作成したので、今回はそれを使って LCD に文字を表示させてみたいと思います。
前回の記事はこちら↓
LCD
まず初めに LCD について簡単に説明したいと思います。
LCD とは、Liquid Crystal Display すなわち液晶ディスプレイのことです。おそらく大半の人が液晶と聞くとテレビ等を思い浮かべると思いますが、画素を細かくして RGB の3色を使っているだけであって基本的な表示の原理は同じなんですね。
電子工作で使う LCD には、キャラクタLCD とグラフィックLCD の2種類があります。
キャラクタLCD は LCD を制御する IC に文字の情報を送るだけでその文字を表示してくれるので、簡単に扱うことができます。
グラフィックLCD は文字単位ではなく、すべてのドットの白黒を制御することができます。そのため、処理は複雑になりますが、漢字や絵なども自由に表示することができます。
今回使うのはこちらの秋月電子で販売されている8×2の小型LCDモジュールです。表示できる文字数が少ないですが、そんなに必要ないときにはコンパクトで安くてとても良いです。
接続方法
このモジュールの回路図がこちらです。
右側の CN2 は内部の IC に繋がっていますが、そこは気にする必要はありません。モジュールから出ている左側の CN1 とマイコンを接続します。
VDD と GND は電源ライン(ちなみに定格電圧は 3.3 V ですが、 5 V でも大丈夫です。)に、SCL と SDA はそれぞれマイコンの SCL と SDA に繋ぎます。
I2C通信の信号はオープンドレインですので、使うこの2本の線はプルアップ抵抗が必要になります。このモジュールには基板にあらかじめ抵抗がついているので、ジャンパパッドにはんだを盛ることでプルアップ抵抗を接続できますが、今回は外部に自分で接続したいと思います。
RESETは Low にするとリセットするピンですが、特にソフトウェアでリセットする用が無ければ、モジュール上でプルアップされているので、無接続で構いません。
マイコンは前回に引き続き PIC16F18326 で MSSP2モジュールを使います。
というわけでこんな感じ↓
制御方法
続いては LCD の制御方法を確認するために、データシートを読み解いていきましょう。
データ送信関数
■データとコマンドのWRITE方法■ということで↓の図が載っています。これを参考にデータとコマンドを書き込む関数を作りましょう。
上にある一列に並んだのが I2C通信でやりとりされるデータです。これによると、最初にスレーブアドレスを送信し、その後はコントロールバイトとデータバイトを交互に送信することになります。
複数のデータを送信するときには、コントロールバイトで最終データかどうかを指定しますが、今回はデータを1つだけ送信する関数を作ります。複数のデータを送りたければその関数を何度も呼び出します。
スレーブアドレスは R/W を含めて 0x7C の定数です。
コントロールバイトはデータ1つの場合必ず最終データなので Co = 0で、コマンドの場合は RS = 0 、データの場合は RS = 1 となります。
というわけで次のような関数を作りました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void lcd_INST(char command){ I2C_Master_Start(); I2C_Master_Write(0x7C); //SlaveAdress + Write I2C_Master_Write(0x00); //Instruction I2C_Master_Write(command); //data byte I2C_Master_Stop(); __delay_us(30); } void lcd_DATA(char data){ I2C_Master_Start(); I2C_Master_Write(0x7C); //SlaveAdress + Write I2C_Master_Write(0x40); //Data I2C_Master_Write(data); //data byte I2C_Master_Stop(); __delay_us(30); } |
遅延については後ほど説明します。
コマンド
データ送信関数ができたので、次は送信するコマンドについて見ていきます。データシートには■初期化設定例■として↓の図が載っています。
先程の遅延はこのコマンドの送信後に必要だったというわけです。この通りにコマンドを送信すれば特に問題なく初期化できるはずですが、きちんと理解するために1つずつコマンドを確認していきましょう。コマンドは↓の表にまとまっています。
これだけだと説明が不十分なので、Instruction Description を参考に詳しく見ていきましょう。
Clear Display
このコマンドはディスプレイをまっさらな状態にするコマンドです。
DDRAM( Display Data RAM : ディスプレイの文字を記憶しているメモリ)にすべて’ ‘(スペース)を書き込みまっさらにして、カーソルを左上に戻してくれます。
初期化例の一番最後に呼び出されていますね。
Return Home
カーソルの位置とディスプレイの位置(後述)を初期状態に戻してくれます。カーソルを任意の位置に移動させるコマンドは後に出てくるのでそちらの方をよく使います。
Entry Mode Set
文字を入力したときのカーソルとディスプレイの動く方向を設定できます。
I/D : Increment / Decrement of DDRAM Address
1 : カーソルを右に移動
0 : カーソルを左に移動
S : Shift of Entire Display
1 : ディスプレイを移動させる
0 : ディスプレイを移動させない
ディスプレイを移動させると下図のように流れる電光掲示板のような感じで表現できます。
Display ON/OFF
ディスプレイ、カーソル、点滅の設定ができます。
D : Display ON/OFF control bit
1 : ディスプレイON
0 : ディスプレイOFF
C : Cursor ON/OFF control bit
1 : カーソルON
0 : カーソルOFF
B : Blink ON/OFF control bit
1 : 点滅ON
0 : 点滅OFF
ディスプレイは全体を表示させるかどうか、カーソルは現在のカーソルの位置に’_’を表示させるかどうか、点滅は現在のカーソルの位置の文字を点滅させるかどうかを設定可能です。
Cursor or Display Shift
カーソルやディスプレイを移動させるコマンドです。ISは拡張コマンドの設定ビットで、詳しくは後述します。
S/C : Screen/Cursor select bit
1 : ディスプレイを移動
0 : カーソルを移動
R/L : Right/Left
1 : 右に移動
0 : 左に移動
移動量は1文字だけです。ディスプレイを移動させたときにはカーソルも同じ方向に一緒に動きます。
Function Set
色々な設定をするコマンドです。
DL : interface Data Length control bit
1 : 8-bit モード
0 : 4-bit モード
N : display line Numbercontrol bit
1 : 2-line モード
0 : 1-line モード
DH : Double Height font type control bit
1 : フォントサイズ 5×16
0 : フォントサイズ 5×8
IS : normal/extension Instruction Select
1 : 拡張コマンド使用
0 : 通常コマンド使用
DLは I2C制御ではない通常の LCD を使うときに設定するもので、I2C制御の場合は1にしておきます。
DHを1にすると下図のように2行で大きく1文字を表示できます。但しもともと少ない文字数をさらに減らすことになるので使い道は限られると思います。
ここまでのコマンドを見て最初に1が登場するビットの位置でコマンドが決められているのが分かるかと思いますが、8種類だと少し足りないので、拡張コマンドが用意されています。拡張コマンドを使うときにはこの IS を設定します。
Set CGRAM Adress
キャラクタLCD はあらかじめ文字のパターンが用意されているので自由なパターンを表示することは出来ないと初めに紹介しましたが、少しだけ自由に設定できる領域があります。
この表の CGRAM (Character Generator RAM) と書かれている6文字に任意のパターンを設定することができます。
今回はそのやり方は省略しますが、小さい絵を表示させたい場合などは挑戦してみてください。
Set DDRAM Adress
DDRAMアドレス、すなわちカーソルの位置を指定するコマンドです。アドレスと表示位置の関係は以下のようになっています。
実際に表示できるのは8文字だけですが、より大きな LCD でも同じ操作で制御できるように余分にアドレスが割り当てられています。
Power/ICON control/Contrast set (high byte)
Ion : set ICON display on/off
1 : アイコンディスプレイON
0 : アイコンディスプレイOFF
Bon : switch Booster circuit
1 : ブースター回路ON
0 : ブースター回路OFF
C5,C4 : Contrast set (high byte)
このモジュールにはアイコンの機能はついていないので Ion = 0 とします。ブースター回路は 3.3 V の低電圧で LCD を駆動するために必要となります。
コントラストについては次のコマンドと合わせて設定します。
Contrast set (low byte)
先程のコマンドと合わせて、コントラストを 6 bits = 64段階で設定できます。コントラストが小さいと真っ白になり、高いと真っ黒になるのでちょうどいいところに設定します。
このほかにもいくつかコマンドがありますが、正直そこまで理解しなくてもいいと思いますし初期化例の通りに設定しておけばいいので省略します。
というわけで、LCD を初期化する関数とカーソルを任意の位置に設定する関数を作成しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#define CONTRAST 0x18 void lcd_Init(){ __delay_ms(40); I2C_Master_Init(100000); lcd_INST(0x38); //Function Set lcd_INST(0x39); //Function Set lcd_INST(0x14); //Internal OSC Frequency lcd_INST(0x70 + (CONTRAST & 0x0f)); //Contrast Set lcd_INST(0x54 + ((CONTRAST & 0xf0)>>4)); //Power/ICON/Contrast control lcd_INST(0x6c); //Follower control __delay_ms(200); lcd_INST(0x38); //Function Set lcd_INST(0x0c); //Display ON/OFF control lcd_INST(0x01); //Clear Display __delay_ms(2); } const char lcd_DDRAM[4] = {0x00, 0x40, 0x14, 0x54}; void lcd_SetCursor(char row,char column){ char d = lcd_DDRAM[row] + column; lcd_INST(0x80+d); } |
コントラストは初期化例のままだと少し濃かったので、小さくしてあります。色々調整してみて丁度いい値を探してみてください。
文字列、変数の表示
先程作ったデータ送信関数で
lcd_DATA(‘A’);
とすれば LCD に「A」が表示できますが、実際に使うときには1文字ずつではなく文字列をまとめて表示したいですよね。また、時間を表示したり変数の中身を表示させたいことも多いと思います。
そこで、一手間加えるだけでこれらの問題をまとめて解決することができます。
それはこれを追加するだけ!
1 2 3 |
void putch (char c){ lcd_DATA(c); } |
なんとこれだけでお馴染みの printf関数が使えちゃいます!
文字列や変数の表示も
printf(“count=%d”,count);
みたいな感じで簡単にできます。
これもライブラリの中に入れても良いですが、PC とシリアル通信をするときなど、LCD 以外に文字を出力する状況も考えられるので main.c の中に入れたいと思います。
動作確認
実際に LCD に文字を表示させてみました。定番の Hello World と右上にはカウンタを表示させています。
ソースコード↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#pragma config FEXTOSC = OFF // FEXTOSC External Oscillator mode Selection bits (Oscillator not enabled) #pragma config RSTOSC = HFINT1 // Power-up default value for COSC bits (HFINTOSC (1MHz)) #pragma config CLKOUTEN = OFF // Clock Out Enable bit (CLKOUT function is disabled; I/O or oscillator function on OSC2) #pragma config CSWEN = ON // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed) #pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled) #pragma config MCLRE = ON // Master Clear Enable bit (MCLR/VPP pin function is MCLR; Weak pull-up enabled) #pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled) #pragma config WDTE = OFF // Watchdog Timer Enable bits (WDT disabled; SWDTEN is ignored) #pragma config LPBOREN = OFF // Low-power BOR enable bit (ULPBOR disabled) #pragma config BOREN = OFF // Brown-out Reset Enable bits (Brown-out Reset disabled) #pragma config BORV = LOW // Brown-out Reset Voltage selection bit (Brown-out voltage (Vbor) set to 2.45V) #pragma config PPS1WAY = ON // PPSLOCK bit One-Way Set Enable bit (The PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle) #pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a Reset) #pragma config DEBUG = OFF // Debugger enable bit (Background debugger disabled) #pragma config WRT = OFF // User NVM self-write protection bits (Write protection off) #pragma config LVP = OFF // Low Voltage Programming Enable bit (High Voltage on MCLR/VPP must be used for programming.) #pragma config CP = OFF // User NVM Program Memory Code Protection bit (User NVM code protection disabled) #pragma config CPD = OFF // Data NVM Memory Code Protection bit (Data NVM code protection disabled) #include <xc.h> #include <stdio.h> #include "i2c.h" #include "lcd_i2c.h" #define _XTAL_FREQ 4000000 void init(){ OSCFRQ = 0x03; //4MHz OSCCON1bits.NDIV = 0x0; //Clock Divider 1/1 TRISA = 0b00001000; TRISC = 0b00110000; ANSELA = 0b00000000; ANSELC = 0b00000000; WPUA = 0b00000000; WPUC = 0b00000000; PORTA = 0b00000000; PORTC = 0b00000000; } void putch(char c){ lcd_DATA(c); } int main() { init(); I2C_Master_Init(100000); lcd_Init(); lcd_SetCursor(0,0); printf("Hello"); lcd_SetCursor(1,1); printf("World"); char count = 0; while(1){ lcd_SetCursor(5,0); printf("%3d",count); count++; __delay_ms(1000); } } |
I2Cライブラリ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include<xc.h> #define _XTAL_FREQ 4000000 void I2C_Master_Init(const unsigned long c){ SSP2CON1 = 0x28; //SSP2 Module as Master SSP2CON2 = 0x00; SSP2CON3 = 0x00; SSP2ADD = (_XTAL_FREQ/(4*c))-1; //Setting Clock Speed SSP2STAT = 0x80; SSP2CLKPPS = 0x14; //PPS Settings SSP2DATPPS = 0x15; RC4PPS = 0x1a; RC5PPS = 0x1b; } void I2C_Master_Wait(){ while ((SSP2STAT & 0x04) || (SSP2CON2 & 0x1F)); //Transmit is in progress } void I2C_Master_Start(){ I2C_Master_Wait(); SSP2CON2bits.SEN = 1; //Initiate start condition } void I2C_Master_RepeatedStart(){ I2C_Master_Wait(); SSP2CON2bits.RSEN = 1; //Initiate repeated start condition } void I2C_Master_Stop(){ I2C_Master_Wait(); SSP2CON2bits.PEN = 1; //Initiate stop condition } void I2C_Master_Write(unsigned d){ I2C_Master_Wait(); SSP2BUF = d; //Write data to SSP2BUF } unsigned short I2C_Master_Read(unsigned short a){ unsigned short temp; I2C_Master_Wait(); SSP2CON2bits.RCEN = 1; I2C_Master_Wait(); temp = SSP2BUF; //Read data from SSP2BUF I2C_Master_Wait(); SSP2CON2bits.ACKDT = a; //Acknowledge bit SSP2CON2bits.ACKEN = 1; //Acknowledge sequence return temp; } |
1 2 3 4 5 6 7 |
void I2C_Master_Init(const unsigned long c); void I2C_Master_Wait(); void I2C_Master_Start(); void I2C_Master_RepeatedStart(); void I2C_Master_Stop(); void I2C_Master_Write(unsigned d); unsigned short I2C_Master_Read(unsigned short a); |
LCDライブラリ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include "i2c.h" #include <xc.h> #define _XTAL_FREQ 4000000 #define CONTRAST 0x18 //for 3.3V //#define CONTRAST 0x08 //for 5V const char lcd_DDRAM[4] = {0x00, 0x40, 0x14, 0x54}; void lcd_INST(char command){ I2C_Master_Start(); I2C_Master_Write(0x7C); //SlaveAdress + Write I2C_Master_Write(0x00); //Instruction I2C_Master_Write(command); //data byte I2C_Master_Stop(); __delay_us(30); } void lcd_DATA(char data){ I2C_Master_Start(); I2C_Master_Write(0x7C); //SlaveAdress + Write I2C_Master_Write(0x40); //Data I2C_Master_Write(data); //data byte I2C_Master_Stop(); __delay_us(30); } void lcd_Init(){ __delay_ms(40); lcd_INST(0x38); //Function Set lcd_INST(0x39); //Function Set lcd_INST(0x14); //Internal OSC Frequency lcd_INST(0x70 + (CONTRAST & 0x0f)); //Contrast Set lcd_INST(0x54 + ((CONTRAST & 0xf0)>>4)); //Power/ICON/Contrast control lcd_INST(0x6c); //Follower control __delay_ms(200); lcd_INST(0x38); //Function Set lcd_INST(0x0c); //Display ON/OFF control lcd_INST(0x01); //Clear Display __delay_ms(2); } void lcd_SetCursor(char row,char column){ char d = lcd_DDRAM[row] + column; lcd_INST( 0x80+d); } |
1 2 3 4 5 |
void lcd_INST(char command); void lcd_DATA(char data); void putch(char c); void lcd_Init(); void lcd_SetCursor(char row,char column); |
今回 I2C で LCD にデータを送信することができたので、次回はセンサーの値の読み取りを行いたいと思います。
このページを参考にしてSSD1306が表示できました。サンクス