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行目
と表示されると思います。