はじめに †
Raspberry Pi 3でCentOS7からのキャラクタLCDへのアクセス方法をまとめました。
秋月電子で扱っているI2Cインタフェースかつ3.3V品ということで、AE-AQM1602A(KIT)を購入してみました。
結果的には、まさかこんなものでというぐらい、大変苦労することになりました。
HD44780互換IC系 その1 Sitronix ST7032 †
概要 †
キャラクタディスプレイといえば、この系統が主流ですね。往年の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
早速コンパイルして実行します。
※2019-08-10追記:i2c-toolsというrpmパッケージがありました。こちらを使用します。
この場合、ドライバー経由になるため、/boot/config.txt に dtparam=i2c_arm=on と dtparam=i2c1=on を指定します。
i2c_devが存在することをlsmodで確認します。(必要に応じ、/etc/modules-load.d/i2c.conf に i2c-bcm2708 と i2c-dev を追加)
# ./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上のアドレス(デフォルト:0x3e)
-i: LCDの初期化を行う、LCDを初期化、最初の1回実施すればOK
-h: ヘルプ表示引数:
lcd_st7032.c
HD44780互換IC系 その2 HD44780/SC1602 LCDモジュール(16 x 2) + I2CExpander †
I2CExpander †
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基盤の金具にショートしそうなこと意外は製品は特に問題はありませんでした。(中華製なので仕方ありません)

3.3V/5Vレベル変換 †
秋月電子のPCA9306を使用したモジュールを使用しました。
PCA9306とLCD制御LSIの接続 †
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
結果 †
動きました
HD44780互換IC系 その2 LCDモジュール(20 x 4) + I2CExpander †
20x4(4行)タイプをこのICを使用しI2Cバスに接続するLCDモジュールが多数存在します。
基本、「HD44780互換IC系 その2」と同じでDDRAM(テキストビデオバッファ)のみことなりますが、互換性があります。
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モジュールの金具とショートしています。
とりあえず、コネクタを外し、基盤を浮かせた状態で再度半田付けします。(ピンはカットするので、再利用不可)


今度は動きました。
お疲れ様でした。