Raspberry Pi 3でCentOS7からのキャラクタLCDへのアクセス方法をまとめました。
秋月電子で扱っているI2Cインタフェースかつ3.3V品ということで、AE-AQM1602A(KIT)を購入してみました。
結果的には、まさかこんなものでというぐらい、大変苦労することになりました。
キャラクタディスプレイといえば、この系統が主流ですね。往年の68系バスに接続するインタフェース仕様です。このチップのインタフェースをI2Cに変換するものが、世の中にいくつかあります。
今回は、Sitronix ST7032というチップのものです。パラレル、I2C、SPI何でも来い!的な石です。
電源はRaspberryPi?の3.3Vを使用します。PINのラッチアップ等を避けるため、同一電源とします。
Raspberry Pi LCD Signal Pin# Signal Pin# 3V3 01,17 3V3 01 GND 34,39 GND 04 I2C SDA.1 03 SDA 03 I2C SCL.1 05 SCL 02
Read/Write用の関数を作成してアクセスすることになります。
たとえばvoid lcd_init( void );
int lcd_write_command( char command );
int lcd_write_data( char data);を基本として、各種のコマンドを作成することとします。上記の関数を利用して
void lcd_clear( void );
void lcd_print( int line, char *str );位を用意すれば良いでしょう。
テストのつもりでコーディングします。
//------------------------------------------------------------------------------ // ST7032 LCDモジュール(16 x 2) 秋月版 制御TP // このTPでは、2行表示に対して、これぞれの行に文字を表示します。 // 基本的に16文字固定表示でスクロールはしません。 // // コンパイル方法 // gcc -o i2c_lcd_test_st7032 i2c_lcd_test_st7032.c -l bcm2835 -lrt // // copyright (c) 2016 Kazuhiro WATANABE All right reserved. // mailto:jj1req@ca.mbn.or.jp // //------------------------------------------------------------------------------ #include <bcm2835.h> #include <stdio.h> #include <stdlib.h> #include <string.h> // BCM2835のI2Cクロック周波数の設定値 // BCM2835_I2C_CLOCK_DIVIDER_2500 2500 = 10us = 100 kHz(IIC spec.) // BCM2835_I2C_CLOCK_DIVIDER_626 622 = 2.504us = 399.3610 kHz // BCM2835_I2C_CLOCK_DIVIDER_15 150 = 60ns = 1.666 MHz (default at reset) // BCM2835_I2C_CLOCK_DIVIDER_148 148 = 59ns = 1.689 MHz #define CLK_DIV BCM2835_I2C_CLOCK_DIVIDER_2500 //#define CLK_DIV BCM2835_I2C_CLOCK_DIVIDER_148 //#define CLK_DIV BCM2835_I2C_CLOCK_DIVIDER_626 // // キャラクタ液晶ディスプレーへの接続 #define LCD_ADDRESS 0x3e // Write Address(Write Only) // // First Byte of I2C Data // Co RS 0 0 0 0 0 0 #define LCD_COMMAND 0x00 // Following data is command with single bye #define LCD_DATA 0x40 // Following data is data byte with single byte // // Commands // Function Set // 0 0 1 DL N DH 0 IS // DL : Interface data length control bit // 1: 8-bit bus mode with MPU. // 0: 4-bit bus mode with MPU. // When in 4-bit bus mode, it needs to transfer 4-bit data by two times. // N : Display line number control bit // 1: 2-line display mode is set. // 0: 1-line display mode. // DH : Double height font type control bit // 1: double height mode(5x16 dot),RAM address can only use 00H~27H. // 0: normal (5x8 dot). // IS : normal/extension instruction select // 1: extension instruction be selected (refer extension instruction table) // 0: normal instruction be selected (refer normal instruction table) #define LCD_FUNCTION_SET_NORM 0x38 // 8bit bus, 2 line, normal font, following normal instructions #define LCD_FUNCTION_SET_EXT 0x39 // 8bit bus, 2 line, normal font, following extension instructions // // Bias selection/Internal OSC frequency adjust, BS will be invalid when external bias resistors are used // 0 0 0 1 BS F2 F1 F0 // BS: bias selection // 1: the bias will be 1/4 // 0: the bias will be 1/5 // F2,F1,F0 : Internal OSC frequency adjust, When CLS connect to high, that instruction can adjust OSC and Frame frequency. // F2 F1 F0 3.0V 5.0V // 0 0 0 122 120 // 0 0 1 131 133 // 0 1 0 144 149 // 0 1 1 161 167 // 1 0 0 183 192 // 1 0 1 221 227 // 1 1 0 274 277 // 1 1 1 347 347 #define LCD_OSC 0x14 // Internal OSC Frequency, BIAS=1/4, 183Hz at 3.3V 2line // // Contrast set(low byte) // 0 1 1 1 C3 C2 C1 C0 // C3,C2,C1,C0:Contrast set(low byte) #define LCD_CONTRAST_L 0x73 // Low byte of contrast // // Power/ICON control/Contrast set(high byte) // 0 1 0 1 ION BON C5 C4 // Ion: set ICON display on/off // 1: ICON display on. // 0: ICON display off. // Bon: switch booster circuit, Bon can only be set when internal follower is used (OPF1=0, OPF2=0). // 1: booster circuit is turn on. // 0: booster circuit is turn off. // C5,C4 : Contrast set(high byte) // C5,C4,C3,C2,C1,C0 can only be set when internal follower is used (OPF1=0,OPF2=0). // They can more precisely adjust the input reference voltage of V0 generator. // The details please refer to the supply voltage for LCD driver. #define LCD_CONTRAST 0x56 // Power/ICON control/Contrast // // Follower control // 0 1 1 0 FON Rab2 Rab1 Rab0 // Fon: switch follower circuit, Fon can only be set when internal follower is used // 1: internal follower circuit is turn on. // 0: internal follower circuit is turn off. // Rab2,Rab1,Rab0 : V0 generator amplified ratio, Rab2,Rab1,Rab0 can only be set when internal follower is used #define LCD_FOLLOWER 0x6c // // Clear Display // 0 0 0 0 0 0 0 1 #define LCD_CLEAR 0x01 // // Display ON/OFF // 0 0 0 0 1 D C B // D : Display ON/OFF control bit // 1: entire display is turned on. // 0: display is turned off, but display data is remained in DDRAM. // C : Cursor ON/OFF control bit // 1: cursor is turned on. // 0: cursor is disappeared in current display, but I/D register remains its data. // B : Cursor Blink ON/OFF control bit // 1: cursor blink is on, that performs alternate between all the high data and display character at the cursor position. // 0: blink is off. #define LCD_ON 0x0f #define LCD_OFF 0x08 // // Command Recovery Time //#define LCD_RECOVERY 100LL // 100us #define LCD_RECOVERY 1000LL // 1000us #define ON 1 #define OFF 0 //****************************************************************************** //* I2Cアクセス時のエラー要因を表示します。 //* //* Function Name //* void get_reaosn( bcm2835I2CReasonCodes res, char *str ) //* Arguments //* bcm2835I2CReasonCodes res error code //* char str message buffer //* Return //* none //* //****************************************************************************** void get_reaosn( bcm2835I2CReasonCodes res, char *str ) { switch( res ) { case BCM2835_I2C_REASON_OK: strcpy( str, "Success" ); break; case BCM2835_I2C_REASON_ERROR_NACK: strcpy( str, "Received a NACK" ); break; case BCM2835_I2C_REASON_ERROR_CLKT: strcpy( str, "Received Clock Stretch Timeout" ); break; case BCM2835_I2C_REASON_ERROR_DATA: strcpy( str, "Not all data is sent / received" ); break; default: strcpy( str, "Unknown error" ); } } //****************************************************************************** //* LCDモジュールにコマンドの書き込みを行います //* //* Function Name //* int lcd_write_command( char data ) //* Arguments //* char data byte data //* Return //* int res 0: normal end //* 1: error //* //****************************************************************************** int lcd_write_command( char data ) { bcm2835I2CReasonCodes res; char str[80]; char data_array[2]; data_array[0] = LCD_COMMAND; data_array[1] = data; if( res=bcm2835_i2c_write( data_array, 2 ) ) { get_reaosn( res, str ); printf( "Write error: %s[%d]\n", str, res ); bcm2835_delayMicroseconds( LCD_RECOVERY ); return( 1 ); } bcm2835_delayMicroseconds( LCD_RECOVERY ); return( 0 ); } //****************************************************************************** //* LCDモジュールにデータの書き込みを行います //* //* Function Name //* int lcd_write_data( char data ) //* Arguments //* char data byte data //* Return //* int res 0: normal end //* 1: error //* //****************************************************************************** int lcd_write_data( char data ) { bcm2835I2CReasonCodes res; char str[80]; char data_array[2]; data_array[0] = LCD_DATA; data_array[1] = data; if( res=bcm2835_i2c_write( data_array, 2 ) ) { get_reaosn( res, str ); printf( "Write error: %s[%d]\n", str, res ); bcm2835_delayMicroseconds( LCD_RECOVERY ); return( 1 ); } bcm2835_delayMicroseconds( LCD_RECOVERY ); return( 0 ); } //****************************************************************************** //* LCDモジュールに対して表示のON/OFFを行います //* //* Function Name //* void lcd_on_off( int flag ) //* Arguments //* flag 0: OFF //* 1: ON //* Return //* none //* //****************************************************************************** void lcd_on_off( int flag ) { if( flag ) lcd_write_command( LCD_ON ); else lcd_write_command( LCD_OFF ); } //****************************************************************************** //* LCDモジュールに対して表示のクリアを行います //* //* Function Name //* void lcd_clear( void ) //* Arguments //* none //* Return //* none //* //****************************************************************************** void lcd_clear( void ) { lcd_write_command( LCD_CLEAR ); bcm2835_delay( 10 ); } //****************************************************************************** //* LCDモジュールに対して初期化を行います //* //* Function Name //* void lcd_init( void ) //* Arguments //* none //* Return //* none //* //****************************************************************************** void lcd_init( void ) { // ライブラリ初期化 if( !bcm2835_init() ) { printf( "bcm2835 library initialize error!!\n" ); exit(1); } // I/O ピンの初期化 if( !bcm2835_i2c_begin() ) { printf( "I2C initialize error!!\n" ); exit(1); } // I2C設定 bcm2835_i2c_setSlaveAddress( LCD_ADDRESS ); //bcm2835_i2c_set_baudrate( 50000 ); bcm2835_i2c_setClockDivider( CLK_DIV ); bcm2835_delay( 200 ); // 初期化フェーズ 1 lcd_write_command( LCD_FUNCTION_SET_NORM ); bcm2835_delay( 200 ); // 初期化フェーズ 2 lcd_write_command( LCD_FUNCTION_SET_EXT ); // 初期化フェーズ 3 lcd_write_command( LCD_OSC ); // 初期化フェーズ 4 lcd_write_command( LCD_CONTRAST_L ); // 初期化フェーズ 5 lcd_write_command( LCD_CONTRAST ); // 初期化フェーズ 6 lcd_write_command( LCD_FOLLOWER ); bcm2835_delay( 400 ); // 初期化フェーズ 7 lcd_write_command( LCD_FUNCTION_SET_NORM ); // Display CLEAR & ON lcd_clear(); lcd_on_off( ON ); } //****************************************************************************** //* I2Cインタフェースを開放します。。 //* //* Function Name //* void lcd_close( void ) //* Arguments //* none //* Return //* none //* //****************************************************************************** void lcd_close( void ) { bcm2835_i2c_end(); bcm2835_close(); } //****************************************************************************** //* LCDモジュールに文字列を表示します。 //* //* Function Name //* void lcd_print( int line, char *str ) //* Arguments //* int line target position 0:first line, 1:second line //* char *str string data //* Return //* none //* //****************************************************************************** void lcd_print( int line, char *str ) { int i; int len; char data; data = (line * 0x40) | 0x80; lcd_write_command( data ); for( i=0; i<16; i++) { lcd_write_data( 0x20 ); } data = (line * 0x40) | 0x80; lcd_write_command( data ); len = strlen(str); if( len > 16 ) len = 16; for( i=0; i<len; i++ ) { lcd_write_data( str[i] ); } } int main(int argc, char **argv) { lcd_init(); lcd_print( 0, "Minkycute.homeip.net" ); lcd_print( 1, "* I2C LCD 7032 *" ); lcd_close(); return( 0 ); }
以下でコンパイルして、実行します。
# gcc -o i2c_lcd_test_st7032 i2c_lcd_test_st7032.c -l bcm2835 -lrt
※-l rt はreal-time extensionsです。(RTカーネルではないので、意味ないかもしれませんが)
結果はまったく動きません。NACKが返ります。
実にこまりました。
といことで調査をします。この世界では、有名なi2cdetectでスキャンしてみましょう
BCM2835用のコードがないか、調べます。ありました。
https://github.com/vanvught/OpenILDA/blob/master/bw_i2cdetect/src/i2cdetect.c
早速コンパイルして実行します。# ./i2cdetect 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- #やはり、ツールでもNGです。しかし時々読めます。
# ./i2cdetect 0x3E : 0x7C : Unknown device 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 3e -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- #何かのパラメータがちがうのでしょうか?
- クロックを変えてみます。ボーレート指定でさらに下げたり、あげたり:NG
- 10Kプルアップ:NG
- 双方向レベル変換:NGです。
Google先生に聞いてみます。ありました。
https://strawberry-linux.com/support/27001/619011
http://www.takunoko.com/blog/aqm1602xa-rn-gbw%E3%82%92raspberry-pi%E3%81%A7%E4%BD%BF%E3%81%86/
といことで、なんと、LSIの内蔵PullUp?/Downnoほかに、外部でご丁寧に1.8kでPullUp?しているとのこと。たぶんMhz動作させる為なんでしょうね。
根性なしのLCDには厳しいです。
ということで、抵抗をはずしましょう。でも、抵抗が違うようです。
一応、回路図を調べると、R23,R24です。コネクタの近くのA面にありますので、退場してもらいます。
動きました!
LCD
せっかくなので、コマンドラインより文字を表示できるツールに仕立てます。
lcd_st7032 [-a <addr>] [-i] [-h] <message> [<line#>]
パラメータ <message> ASCII文字(SJIS:カナ) <line#> 行番号: 0 or 3 オプション -a <addr>: I2C上のアドレス(デフォルト:0x27) -i: LCDの初期化を行う、LCDを初期化、最初の1回実施すればOK -h: ヘルプ表示引数:
Philips社よりI2C接続のパラレルポートICとして、PCF8574が製造されていますが、このICを使用しI2Cバスに接続するLCDモジュールが多数存在します。
また、このICは5V/3.3Vに対応しています。ただし、残念ながら、多くのモジュールはLCD本体が5V電源のため、RaspberryPi?との接続は、3.3V/5Vレベル変換を必要とします。
今回はAmazonで「サインスマート(SainSmart?) IIC/I2C/TWI 16*02 LCD液晶 モジュール For Arduino UNO MEGA R3 青発光 」を購入しました。
Amazonに在庫があり、翌日には手に入りました。
I2C Expander基盤がLCD基盤の金具にショートしそうなこと意外は製品は特に問題はありませんでした。(中華製なので仕方ありません)#ref(): File not found: "sainsmart-01.jpg " at page "キャラクタLCDをアクセス"
秋月電子のPCA9306を使用したモジュールを使用しました。
PCA9306の8ビット出力とLSIのピンは下表の接続です。
PCA9306 パラレル入出力 LCD制御LSI信号 D7 D7 D6 D6 D5 D5 D4 D4 D3 BackLight? D2 E D1 R_W D0 RS
LCD制御ICは4bitバスモードで接続することになります。
制御用にソフトを作成します。コマンドの仕様は以下のとおりです。
一応、後述する20x4タイプと共用できるよう、4行仕様にしてあります。
# lcd_exp [-a ] [-i] [-h] <text> [<line#>]
パラメータ <text> ASCII文字(SJIS:カナ) <line#> 行番号: 0 or 3 オプション -a <addr>: I2C上のアドレス(デフォルト:0x27) -i: LCDの初期化を行う。LCDを初期化、最初の1回実施すればOK -h: ヘルプ表示
ファイル:lcd_exp.c
動きました
20x4(4行)タイプをこのICを使用しI2Cバスに接続するLCDモジュールが多数存在します。
基本、「HD44780互換IC系 その2」と同じでDDRAM(テキストビデオバッファ)のみことなりますが、互換性があります。
資料がないため、推測を含みますが以下のようでした。
LINE# ADDR 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 1 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 2 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 3 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 24 26 27 4 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67
最初の火入れで、何も表示されず、i2cdetectの結果は以下のとおりです。
# ./i2cdetect 0x03 : 0x06 : Unknown device 0x04 : 0x08 : Unknown device 0x05 : 0x0A : Unknown device 0x06 : 0x0C : Unknown device (中略) 0x76 : 0xEC : Unknown device 0x77 : 0xEE : Unknown device 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70: 70 71 72 73 74 75 76 77 #つまり、いつもACKが返ってますね。
で調べると、Expander基盤のSDAピンがLCDモジュールの金具とショートしています。とりあえず、コネクタを外し、基盤を浮かせた状態で再度半田付けします。(ピンはカットするので、再利用不可)
今度は動きました。
お疲れ様でした。