C言語(SDCC)を使って、STC8G1K08AでキャラクタLCDを制御したいと思います。

以前から使用しているキャラクタLCDの「KKHMF DC 5V 1602 LCD ディスプレイモジュール 16×2キャラクタ LCDブルーブラックライト」と「EasyWordMall 1602 LCD ブラック IIC/I2C/TWI/SPI シリアル インタフェース ボード モジュール」(ともにamazonです)を使用します。

EasyWordMall 1602 LCD ブラック IIC/I2C/TWI/SPI シリアル インタフェース ボード モジュールでは、SDA、SCLともプルアップされているようなので、前回の「8051コアに萌えたい STC8G1K08AでI2Cアドレスサーチ(SDCC)」の回路のVCC、GND、SDA、SCLに接続します。

前回のアドレスサーチでI2Cアドレスをサーチすると次のようになります。(A0,A1,A2はともにオープン)
I2Cインターフェイスの型番がPCF8574T="0x27"、手持ちはないですがPCF8574A=”0x3F"となるようです。

LCD ディスプレイモジュール とI2C シリアル インタフェース ボードモジュールとは次のような接続になっています。
LCDの制御のために、RS/RW/EN/LEDの各端子のビット位置を設定します。ちなみに LCDへのデータ転送は、4BitモードになっていますのでD7~D4までの4Bitで設定します。

    ;; lcd 1602 port
    ;;
    ;;      D0 > RS
    ;;      D1 > R/W
    ;;      D2 > E
    ;;      D3 > LED on/off
    ;;      D4 > DB4
    ;;      D5 > DB5
    ;;      D6 > DB6
    ;;      D7 > DB7  

LCDへの書き込みは4ビットごとに書き込みします。
書き込みには、コマンドデータと表示用データの書き込みの2種類があります。
モードの切り替えは RS端子で行います。
コマンドデータ >> RS=0
表示用データ  >> RS=1

書き込みと読み出しのモード切替はRW端子で行います。
読み出し >> RW=1
書き込み >> RW=0
今回の場合は、読み出しは行わないので、RW端子は常に 0のままです。

実際の書き込みタイミングは、ENを正論理でパルスを送ります。
つまり、 EN >> 0>1>0のタイミングの1発だけのパルスを生成します。

バックライトは常にONにしています
パックライトON >> LED=1
バックライトOFF>> LED=0

I2C経由の書き込みには、新たにi2c_outputという関数を作成しました。
引数は、i2c_addrとi2c_dataでI2Cアドレスと書き込むデータを渡します。

I2CNSCR =0x01で、I2Cをスタートさせ、続いて、I2CTXDにI2Cアドレスと書き込みモードでデータをセットしI2CMSCR =0x02でI2C送信します。
I2CMSCR=0x03でACKを受信した後、I2CTXDに書き込みデータを書き込み、I2C送信します。
I2CMSCR=0x03でACKを受信した後、I2CMSCR =0x06でI2Cを終了します。

//I2C 1byte データ 出力関数

void i2c_output(uint8_t i2c_addr,uint8_t i2c_data){

    I2CMSCR = 0x01;                     //I2C スタート
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;   
    
    I2CTXD = ((i2c_addr << 1) | 0x00);  //I2C_ADDR + write 送信
    I2CMSCR = 0x02;
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;

    I2CMSCR = 0x03;                     //ACK 受信
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;       
        
    I2CTXD = i2c_data;                  //I2C_data送信
    I2CMSCR = 0x02;
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;       
        
    I2CMSCR = 0x03;                     //ACK 受信
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;       

    I2CMSCR = 0x06;                     //I2C ストップ
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;       
}

LCDへのコマンドデータの4ビットの書き込みは下記のようになりました。

// 4bitコマンド出力関数

void lcd4bitwrite(uint8_t i2c_addr,uint8_t command){
    command <<= 4;
    command |= (uint8_t)0b00001000;
    command &= (uint8_t)0b11111000;
    i2c_output(i2c_addr,command);
    command |= (uint8_t)0b00001100;
    command &= (uint8_t)0b11111100;
    i2c_output(i2c_addr,command);
    command |= (uint8_t)0b00001000;
    command &= (uint8_t)0b11111000;
    i2c_output(i2c_addr,command);  
}

同じような手順で、表示データの4ビットの書き込みは下記のようになります。
コマンドデータの書き込みと統合するとプログラムは短くなりますが、あえてわかりやすいように(ちょっと言い訳)分けてみました。

//4bitデータ出力関数

void data_lcd4bitwrite(uint8_t i2c_addr,uint8_t command){

    command <<= 4;
    command |= (uint8_t)0b00001001;
    command &= (uint8_t)0b11111001;
    i2c_output(i2c_addr,command);
    command |= (uint8_t)0b00001101;
    command &= (uint8_t)0b11111101;
    i2c_output(i2c_addr,command);
    command |= (uint8_t)0b00001001;
    command &= (uint8_t)0b11111001;
    i2c_output(i2c_addr,command);   
}

8ビット単位の書き込みは、上位4ビットを書き込み続いて下位4ビットを書き込みしています。
コマンド書き込みと表示データ書き込みは次のようになります。

// 8bitコマンド出力関数

void lcd8bitwrite(uint8_t i2c_addr,uint8_t command){

    uint8_t command2;
    
    command2 = command;
    command >>= 4;
    lcd4bitwrite(i2c_addr,command);
    lcd4bitwrite(i2c_addr,command2);
}

// 8bitデータ出力関数

void data_lcd8bitwrite(uint8_t i2c_addr,uint8_t command){

    uint8_t command2;
    
    command2 = command;
    command >>= 4;
    data_lcd4bitwrite(i2c_addr,command);
    data_lcd4bitwrite(i2c_addr,command2);
    
    
}

合わせて、LCD書き込みタイミング用にwait関数を作成します。最小単位1msのwait_1ms(void)を作成し、必要な時間を設定できるようにwait_time(uint16_t wait_data)を作成しました。

//
// 時間待ち関数
//
void wait_1ms(void)
{
    __asm

    delay1ms:
        mov r0,#0xa0
        mov r1,#0x10
    delay:
        djnz    r0,delay
        djnz    r1,delay
        ret
    __endasm;
}
void wait_time(uint16_t wait_data)          //時間待ち関数(1ms*wait_data)
{
    uint16_t    i;                          //forループによる時間待ち
    for(i=0;i < wait_data ;i++){
        wait_1ms();
    }
}   

全体のプログラムは下記のとおりです。

// STC8G1K08A I2C LCD C言語版
//
// STC8G1K08A   MPU
// Flash ROM 8Kbyte
// RAM       1Kbyte
// 
// SDCC 4.0.0
//
// file name STC8G1K08A_I2C_lcd.c
// $ sdcc -mmcs51 --model-small --xram-size 0x400 --xram-loc 0x0000 --code-size 0x2000 STC8G1K08A_I2C_lcd.c
// $ packihx STC8G1K08A_I2C_lcd.ihx > STC8G1K08A_I2C_lcd.hex
// $ stcgal -P stc8g -t 22118.4 -p /dev/ttyUSB0 STC8G1K08A_I2C_lcd.hex
//

#include <8052.h>
#include <stdio.h>
#include <stdint.h>

//
// XFR(拡張RAM領域特殊機能レジスタ) 設定
//

#define CLKDIV (*(unsigned char volatile __xdata *)0xfe01)

#define I2CCFG (*(unsigned char volatile __xdata *)0xfe80)
#define I2CMSCR (*(unsigned char volatile __xdata *)0xfe81)
#define I2CMSST (*(unsigned char volatile __xdata *)0xfe82)
#define I2CSLCR (*(unsigned char volatile __xdata *)0xfe83)
#define I2CSLST (*(unsigned char volatile __xdata *)0xfe84)
#define I2CSLADR (*(unsigned char volatile __xdata *)0xfe85)
#define I2CTXD (*(unsigned char volatile __xdata *)0xfe86)
#define I2CRXD (*(unsigned char volatile __xdata *)0xfe87)

//BYTE 指定可能なレジスター

__sfr __at  0x8e AUXR  ;
__sfr __at  0xba P_SW2  ;

__sfr __at  0xb2 P3M0  ;    
__sfr __at  0xb1 P3M1  ;    
__sfr __at  0xca P5M0  ;    
__sfr __at  0xc9 P5M1  ;

__sbit __at 0xb3 SDA;
__sbit __at 0xb2 SCL;

//
// uart 関連関数
//
    
// uart 初期化
void uart_init(void) {
    
    P_SW2 = 0x80;
    CLKDIV = 0x02;          //システムクロック分周値を 1/2にセット
    P_SW2 = 0x00;
    
    AUXR = 0xc0;            //SYSCLK/1 timer0 or timer1 clock souce 
    SCON  = 0x50;           //UARTはモード1に設定、受信イネーブル
                            //SCON=01010000B
    TMOD |= 0x20;           //タイマー1を8ビット自動リロードモードに設定
                            //TMOD=00100000B
    TH1   = 0xf7;           //タイマーカウンターセット
    TL1   = 0xf7;
    TR1   = 1;              // タイマー1スタート

}

// 1文字シリアル出力
int putchar(int data) {
    TI = 0;                 //SCONのTI(1bit目)をケリア
    SBUF = data;            //dataからSBUFバッファーに格納
    while (!TI);            //SCONのTI(1bit目)が"1"なるまでループ
    return data;
}

// 1文字シリアル入力
int getchar(void) {
    while (!RI);            //SCONのRI(0bit目)が"1"なるまでループ
    RI = 0;                  //SCONのRI(0bit目)をクリア
    return SBUF;            // 受信データ返却
}   

//
// I2C 関連関数 
//

//I2C 1byte データ 出力関数

void i2c_output(uint8_t i2c_addr,uint8_t i2c_data){

    I2CMSCR = 0x01;                     //I2C スタート
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;   
    
    I2CTXD = ((i2c_addr << 1) | 0x00);  //I2C_ADDR + write 送信
    I2CMSCR = 0x02;
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;

    I2CMSCR = 0x03;                     //ACK 受信
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;       
        
    I2CTXD = i2c_data;                  //I2C_data送信
    I2CMSCR = 0x02;
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;       
        
    I2CMSCR = 0x03;                     //ACK 受信
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;       

    I2CMSCR = 0x06;                     //I2C ストップ
    while(!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;       
}

//
// 時間待ち関数
//
void wait_1ms(void)
{
    __asm

    delay1ms:
        mov r0,#0xa0
        mov r1,#0x10
    delay:
        djnz    r0,delay
        djnz    r1,delay
        ret
    __endasm;
}
void wait_time(uint16_t wait_data)          //時間待ち関数(1ms*wait_data)
{
    uint16_t    i;                          //forループによる時間待ち
    for(i=0;i < wait_data ;i++){
        wait_1ms();
    }
}   

//
// LCD操作関数
//

// 4bitコマンド出力関数

void lcd4bitwrite(uint8_t i2c_addr,uint8_t command){
    command <<= 4;
    command |= (uint8_t)0b00001000;
    command &= (uint8_t)0b11111000;
    i2c_output(i2c_addr,command);
    command |= (uint8_t)0b00001100;
    command &= (uint8_t)0b11111100;
    i2c_output(i2c_addr,command);
    command |= (uint8_t)0b00001000;
    command &= (uint8_t)0b11111000;
    i2c_output(i2c_addr,command);  
}

// 8bitコマンド出力関数

void lcd8bitwrite(uint8_t i2c_addr,uint8_t command){

    uint8_t command2;
    
    command2 = command;
    command >>= 4;
    lcd4bitwrite(i2c_addr,command);
    lcd4bitwrite(i2c_addr,command2);
}

//4bitデータ出力関数

void data_lcd4bitwrite(uint8_t i2c_addr,uint8_t command){

    command <<= 4;
    command |= (uint8_t)0b00001001;
    command &= (uint8_t)0b11111001;
    i2c_output(i2c_addr,command);
    command |= (uint8_t)0b00001101;
    command &= (uint8_t)0b11111101;
    i2c_output(i2c_addr,command);
    command |= (uint8_t)0b00001001;
    command &= (uint8_t)0b11111001;
    i2c_output(i2c_addr,command);   
}

// 8bitデータ出力関数

void data_lcd8bitwrite(uint8_t i2c_addr,uint8_t command){

    uint8_t command2;
    
    command2 = command;
    command >>= 4;
    data_lcd4bitwrite(i2c_addr,command);
    data_lcd4bitwrite(i2c_addr,command2);
    
    
}

//
// main関数
//

void main(void)                                 //main関数
{
    P3M0 = 0x00;                                //P3ポートを準双方向モードに
    P3M1 = 0x00;
    P5M0 = 0x00;                                //P5ポートを準双方向モードに
    P5M1 = 0x00;    

    uart_init();
    
//  printf_tiny("STC8G1K08A I2C LCD Test !\r\n");

    P_SW2 = 0x80;                               //XFRアクセス許可、I2C(P3.2(SCL),P3.3(SDA)スイッチ切り替え
    I2CCFG = 0xe0;                              //I2Cマスターモード
    I2CMSST = 0x00;

    const uint8_t i2c_addr = 0x27;              //LCD I2Cアドレス

    uint8_t message1[]=" **STC8G1K08A** ";      //LCD出力メーッセージ
    uint8_t message2[]="Hello SDCC World";

    //LCD 初期化
    uint8_t data_set;

    data_set=0x03;
    lcd4bitwrite(i2c_addr,data_set);
    wait_time(5);
    lcd4bitwrite(i2c_addr,data_set);
    lcd4bitwrite(i2c_addr,data_set);
    data_set=0x02;
    lcd4bitwrite(i2c_addr,data_set);            //4bit mode
    data_set=0x28;
    lcd8bitwrite(i2c_addr,data_set);            //function set 4bit bus,2 line ,1 line=8
    data_set=0x0c;
    lcd8bitwrite(i2c_addr,data_set);            //disp on,under cursor off,block
    data_set=0x01;
    lcd8bitwrite(i2c_addr,data_set);            //disp clr
    wait_time(2);
    data_set=0x06;
    lcd8bitwrite(i2c_addr,data_set);            //disp address incrmant on,disp shift off
    data_set=0x02;
    lcd8bitwrite(i2c_addr,data_set);            //cursor home set
    wait_time(2);
    
    //LCDメーセージ書き込み
    
    for (int i=0;message1[i] != '\0';i++){
        data_lcd8bitwrite(i2c_addr,message1[i]);
    }
    data_set = 0xc0;                            //x80 + 0x40(2 line top address)
    lcd8bitwrite(i2c_addr,data_set);            
    for (int i=0;message2[i] != '\0';i++){
        data_lcd8bitwrite(i2c_addr,message2[i]);
    }; 
    while(1);

}

以上のCプログラムを 「STC8G1K08A_I2C_lcd.c] として新たに作成し保存します。

コンパイルします。

$ sdcc -mmcs51 --model-small --xram-size 0x400 --xram-loc 0x0000 --code-size 0x2000 STC8G1K08A_I2C_lcd.c

ihxからインテルHEXコードhexに変換します。

$ packihx STC8G1K08A_I2C_lcd.ihx > STC8G1K08A_I2C_lcd.hex
packihx: read 35 lines, wrote 60: OK.

STC8G1K08Aに書き込みします。

$  stcgal -P stc8g -t 22118.4 -p /dev/ttyUSB0 STC8G1K08A_I2C_lcd.hex
Waiting for MCU, please cycle power: done
Target model:
  Name: STC8G1K08A-8PIN
  Magic: F794
  Code flash: 8.0 KB
  EEPROM flash: 4.0 KB
Target frequency: 22.109 MHz
Target BSL version: 7.3.13U
Target wakeup frequency: 36.800 KHz
Target ref. voltage: 1185 mV
Target mfg. date: 2024-07-01
Target options:
  reset_pin_enabled=False
  clock_gain=high
  watchdog_por_enabled=False
  watchdog_stop_idle=True
  watchdog_prescale=64
  low_voltage_reset=False
  low_voltage_threshold=2
  eeprom_erase_enabled=True
  bsl_pindetect_enabled=False
  por_reset_delay=long
  rstout_por_state=high
  uart1_remap=False
  uart2_passthrough=True
  uart2_pin_mode=push-pull
  epwm_open_drain=True
  program_eeprom_split=8192
Loading flash: 847 bytes (Intel HEX)
Target frequency: Target 22.118 MHz
Adjusted frequency: 22.116 MHz(-0.011%)
Switching to 115200 baud: done
Erasing flash: done
Writing flash: 1088 Bytes [00:00, 6463.16 Bytes/s]
Finishing write: done
Setting options: done
Target UID: F794C4161F25D0
Disconnected!

キャラクターLCDには
**STC8G1K08A**   1行目
Hello SDCC World    2行目

と表示されると思います。

おすすめの記事