PIC使用 LCD SC1602のI2C化

投稿日 2016/09/30

マイコン開発で頻繁にお世話になるキャラクタLCDですが、そのキャラクタLCD(以下LCD)には、4/8ビット・パラレル・インターフェース方式のものと、I2Cインターフェース方式のものがあります。

 

殆どのマイコンがI2Cをサポートする現在、LCDをこれから購入するとしたら、やはり使用ピン数の少ないI2Cインターフェース方式のLCDに軍配が上がります。

 

しかし部品箱には昔購入したパラレル・インターフェース方式のLCDが使われないまま眠っています。

 

そこで、それらパラレル・インターフェース方式のLCDを有効利用すべく、I2C化を試みました。

 

LCDを使用するマイコン側(マスタ側)から見ればI2Cインターフェースでつながりますが、中身はパラレル・インターフェースのLCDです。PICを使用してI2C -> パラレル変換を行います。I2Cで言えばマスタとスレーブの関係です。

LCD_I2C化1.jpg

LCD側のPICはI2Cをサポートしていれば何でもよく、またパラレル・インターフェースに使用するGPIOが6本(RS, E, DB7, DB6, DB5, DB4)あれば事足ります。候補はいろいろありますが、今回は手持ちの関係でPIC 16F1823を使用しました。8ピンPIC 12F1822のピン数を14ピンにしてGPIOを増やした強化バージョンが16F1823です。

 

プログラムの開発はMPLAB X IDE v3.40 XC8 V1.83で行いました。クロックとGPIO設定の生成のみMCC(MIcrochip Code Configurator)を利用しました。

 

主な仕様

 

キャラクタLCD: 4/8ビットパラレルLCD SC1602BBWB-XA-LB-G 3.3V

PIC: 16F1823

I2C: スレーブ アドレス 0xA0 (プログラムで自由に変更可) 

プルアップ抵抗は実験中は付けていますが、本番使用では外し(マスタ側で)外付けとします。

電源: 3.3V

 

 

マスタ側

LCD_I2C化2.jpg

マスタ側の予備試験

Microchip社 DM240415(PIC 24FJ256GB110搭載)

ACM1602 I2C LCDで動作確認しておく

 

マスタ側のマイコンはI2Cマスタになれればなんでもよいのですが、今回はPIC 24FJ256GB110搭載のデモボードDM240415を使用しました。DM240415にI2CのLCD ACM1602を繋いで、I2C LCDの動作を確認しておきました。ACM1602のハンドラを少し改造して使用します。

 

I2C LCDは、基本的なインターフェース・フォーマットは4/8ビットパラレル方式のLCDに似ていますが、コマンド(動作設定や文字表示位置、画面クリアなど)とデータ(表示する文字コード)を区別するため、コントロール・コードを前置して2バイト構成で制御します。I2C LCDとはいえ、中身はパラレル・インターフェースのLCDの流用と思われるので、このようなインターフェース仕様になっているものと思われます。

 

(0x00, コマンド)   または、(0x80, データ)

 

実際にはスレーブアドレスを前置して、例えば(Slave Address, 0x00, コマンド) という形になります。(今回、SC1602の仕様によりコントロール・コード0x01を追加しています。)

 

マスタ側はこの形式を踏襲するのでACM1602用のハンドラを流用しますが、少し改造が必要です。ACM1602とSC1602では初期設定のコマンドが異なるのと、その初期設定コマンドにニブルで送らなければならないコマンドがあるためです。ニブルとはバイト(8bit)の半分、4bitのことです。

 

マスタ側の制御関数(AQM1602用の関数を利用)

 

マスタ側で使用する制御関数は、以前ACM1602用に作ったハンドラの流用ですが、一部変更しています。(後述のプログラム・コード参照)

 

i2c_lcd_ioctl(0x00 or 0x01 or 0x80, Command or Data) <- 変更なし

lcd_init() <- SC1602に合わせて変更、ニブル・コマンド対応(0x01)

lcd_position(x, y) <- 変更なし

lcd_str(string) <- 変更なし

lcd_write(x, y, string) <- 変更なし

lcd_clear() <- 変更なし

 

lcd_init()の内容のみSC1602の初期化コマンドに合わせて変更する必要がありますが、その他の関数はそのまま使用しました。

 

 

マスタ側 プログラムコード(PIC 24FJ256GB110用)部分

 

I2C LCDハンドリング部分のみを抜き出しています。

Delayは多めに設定してあります。チューニングすれば早くなります。

 

#define LCD_I2C_ADDR 0b10100000 //0xA0

 

const int lcd_adrbase[2] = { 0x80, 0xC0 };

 

int i2c_lcd_IOCtl(unsigned char control, unsigned char data){
    I2C1CONbits.SEN = 1; // start condition
    while(I2C1CONbits.SEN == 1);

    I2C1TRN = LCD_I2C_ADDR; // Address of the I2C LCD
    while(I2C1STATbits.TRSTAT);
    if (I2C1STATbits.ACKSTAT){ // ACK?
        I2C1CONbits.PEN = 1; // NACK
        while(I2C1CONbits.PEN); // Send the stop condition
        return(-1); 
    }
    Delay_ms(1);
    I2C1TRN = control;
    while(I2C1STATbits.TRSTAT); 
    if (I2C1STATbits.ACKSTAT){
        I2C1CONbits.PEN = 1;
        while(I2C1CONbits.PEN);
        return(-1); 
    }
    Delay_ms(1); 
    I2C1TRN = data;
     while(I2C1STATbits.TRSTAT);
    if (I2C1STATbits.ACKSTAT){
        I2C1CONbits.PEN = 1;
        while(I2C1CONbits.PEN);
        return(-1); 
    }
    Delay_ms(1); 
    I2C1CONbits.PEN = 1;
    while(I2C1CONbits.PEN);
    return(1);
}

 

void lcd_init(void){ //SC1602用
    Delay_ms(15);
    i2c_lcd_IOCtl(0x01, 0x30); //ニブル・コマンド
    Delay_ms(5);
    i2c_lcd_IOCtl(0x01, 0x30); //ニブル・コマンド
    Delay_ms(1);
    i2c_lcd_IOCtl(0x01, 0x30); //ニブル・コマンド
    Delay_ms(1);
    i2c_lcd_IOCtl(0x01, 0x20); //ニブル・コマンド
    Delay_ms(1);
    i2c_lcd_IOCtl(0x00, 0x2E);
    Delay_ms(1);
    i2c_lcd_IOCtl(0x00, 0x08);
    Delay_ms(1);
    i2c_lcd_IOCtl(0x00, 0x01);
    Delay_ms(2);
    i2c_lcd_IOCtl(0x00, 0x06);
    Delay_ms(1);
    i2c_lcd_IOCtl(0x00, 0x0E);
    Delay_ms(1);
}

 

void lcd_position(unsigned char xpos, unsigned char ypos){
    i2c_lcd_IOCtl(0x00, lcd_adrbase[ypos] + xpos );
}

 

void lcd_write(unsigned char xpos, unsigned char ypos, const unsigned char* ptr){
    lcd_position(xpos, ypos);
    while(*ptr != 0x00) i2c_lcd_IOCtl(0x80, *ptr++);
}

 

void lcd_clear(void){
    i2c_lcd_IOCtl(0x00, 0x01);
    Delay_ms(10);
}

 

void lcd_str(const unsigned char* ptr){
    while(*ptr != 0)
    i2c_lcd_IOCtl(0x80, *ptr++);
}

 

 

 

スレーブ側

LCD_I2C化3.jpg

スレーブ側

3.3V 4/8ビット・パラレル・キャラクタLCD SC1602BBWB-XA-LB-G(上)

これはバックライト付きのLCD

PIC 16F1823使用のI2C -> パラレル変換基板

LCD_I2C化4.jpg

スレーブ側回路図

PIC 16F1823 + SC1602 LCD

LEDはデバッグ用(本番では使用しない)

他に書き込み環境がある場合は、ICSPは組み込まなくてもよい

電源 3.3Vはマスタ側から供給

I2Cの信号 SDAとSCLには回路図には無いがプルアップ抵抗が必要

 

スレーブ側はPIC 16F1823と4/8ビット・パラレルLCDを組み合わせた簡単な回路です。

 

まず16F1823からパラレルLCD制御ハンドラで表示できることを確認しておきます。

 

次に、16F1823にI2Cスレーブをサポートさせます。7ビットアドレス使用のスレーブです。I2Cからのデータ受信のハンドリングは割り込みで行います。

 

マスタ側がアドレス0xA0を指定してi2c_lcd_ioctl(0x00 or 0x01 or 0x80, Command or Data)を実行すると、スレーブ・アドレス、0x00 or 0x01 or 0x80, Command or Dataの順で3バイト送られてきます。その際、アドレスマッチングが行われ、0xA0であれば割り込み処理ルーチンに入ります。割り込み処理ルーチンでは、アドレスを空読みします。アドレスの空読みでマスタ側にACKが送られるので、マスタは次のバイトを送り再び割り込みがかかるので2バイト目を読み込みます。このとき16F1823の処理が追いつくようマスタ側を待たせなければならないので、Clock Strachingモードを使います。3バイト目も同じです。つまり割り込みが3回かかります。

 

読み込んだデータの2バイトのうち、最初が0x00であればバイト長のコマンド、0x01であればニブル長のコマンド、0x80であればデータとして扱います。SC1602は4バイトモードで使用するので、バイト長のコマンド、またはデータは前半4ビットと後半4ビットを分けてSC1602に送ります。ニブル長のコマンドは上位4ビットのみ送ります。

 

やっていることは単純で、上記の繰り返しです。CKPビットを使ってうまくClock Strachingさせるのがミソです。(プログラム・コード参照)

LCD_I2C化5.jpg

今回はマスタ側(左)にDM240415(PIC 24FJ256GB110搭載)を使用

マスタ側はI2Cマスタになれればなんでもよい

スレーブ側(右) PIC 16F1823 + SC1602 LCD

SC1602 LCDは本来パラレルLCDだがマスタ側からI2Cで制御している

これであまり使われなくなったパラレルLCDを活用できる

 

 

スレーブ側プログラム(部分)

 

I2CとSC1602のハンドリング部分のみ抜き出しています。

 

void i2c_init(void){ //I2C初期化ルーチン(スレーブ)
    SSP1CON1bits.SSPM = 0x6; ////I2C Slave mode, 7-bit address
    SPBRGL = 9;   // 100KHz I2C
    SSP1ADD = I2C_SLAVE_ADDR;  //Slave Address 0xA0

    PIE1bits.SSP1IE = 1;  //SSP2 割り込み許可
    PIR1bits.SSP1IF = 0;  //SSP2 割り込みフラグクリア

    SSP1CON2bits.SEN = 1; //Clock Stretching mode
    SSP1CON1bits.SSPEN = 1;    // Enable the I2C2 module
}

 

void interrupt isr(void) { //割り込み処理ルーチン
    if (PIR1bits.SSP1IF == 1) {
        LED = 1;
        PIR1bits.SSP1IF = 0;
        //Address
        if (SSP1STATbits.BF == 1 && SSP1STATbits.D_nA == 0) {
            I2C_adr = SSP1BUF;
            SSP1CON1bits.CKP = 1; 
        //Data
        } else if (SSP1STATbits.BF == 1 && SSP1STATbits.D_nA == 1) {
            if(byte_cnt == 0) {
                Command_byte = SSP1BUF;
                byte_cnt++;
                SSP1CON1bits.CKP = 1;
            } else {
                Data_byte = SSP1BUF; 
                byte_cnt = 0;
                i2c_get_flag = 1;
            }
        }
        LED = 0;
    }
}

 

#define RS PORTAbits.RA5
#define E PORTAbits.RA4
//DB4 RC2
//DB5 RC3
//DB6 RC4
//DB7 RC5

 

void lcd_out(int code, int flag) {
    LATC = (LATC & 0xC3) | ((code & 0xF0) >> 2); //出力データ上位4ビット 
    if (flag == 0)//表示データかコマンドか
        RS = 1; //表示データの場合RS=1
    else
        RS = 0; //コマンドデータの場合RS=0
    __delay_us(10); //Nop(); Nop();    //NOP スキュー確保 60ns以上
    E = 1; //STB ON
    __delay_us(10);
    E = 0;
    __delay_us(10); //STB OFF
}

 

void lcd_data(int asci) {
    lcd_out(asci, 0); //上位4ビット出力
    lcd_out(asci << 4, 0); //下位4ビット出力
    __delay_ms(1); //50μsec待ち
}

 

void lcd_cmd(int cmd) {
    lcd_out(cmd, 1); //上位4ビット出力
    lcd_out(cmd << 4, 1); //下位4ビット出力
    if ((cmd == 0x01) || (cmd == 0x02))
        __delay_ms(2); //2msec待ち
    else
        __delay_us(50); //50usec待ち
}

 

 

void main(void){ //メインルーチン
     SYSTEM_Initialize();

    
    i2c_init();

    
    i2c_get_flag = 0;
    byte_cnt = 0;
    
    INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;

   
    while (1) {
         if(i2c_get_flag == 1){
            if(Command_byte == 0x01) { //ニブル・コマンド
                lcd_out(Data_byte, 1);
            }else if(Command_byte == 0x00) { //バイト・コマンド
                lcd_cmd(Data_byte);
            } else if(Command_byte == 0x80) { //データ
                lcd_data(Data_byte);
            }
            i2c_get_flag = 0;
            SSP1CON1bits.CKP = 1;
        }
    }

 

 

省電力化はやっていません。

 

 

Projects/16F1823_PLCD_TO_I2CLCD.X

Projects/24FJ256GB110_PLCD_SC1602_MASTER.X

MPLAB X IDE v3.40  XC8 v1,83 XC16 v1.21 MCC v3.16 PICKIT3

 

 

 

(JF1VRR)