キャラクタLCDをアクセス

はじめに

Raspberry Pi 3でCentOS7からのキャラクタLCDへのアクセス方法をまとめました。
秋月電子で扱っているI2Cインタフェースかつ3.3V品ということで、AE-AQM1602A(KIT)を購入してみました。
結果的には、まさかこんなものでというぐらい、大変苦労することになりました。

HD44780互換IC系 その1 Sitronix ST7032

概要

キャラクタディスプレイといえば、この系統が主流ですね。往年の68系バスに接続するインタフェース仕様です。このチップのインタフェースをI2Cに変換するものが、世の中にいくつかあります。
今回は、Sitronix ST7032というチップのものです。パラレル、I2C、SPI何でも来い!的な石です。

K-08896.jpg
ST7032-1.jpg

ハードウェア

  • ピンアサイン

電源はRaspberryPi?の3.3Vを使用します。PINのラッチアップ等を避けるため、同一電源とします。

Raspberry PiLCD
SignalPin#SignalPin#
3V301,173V301
GND34,39GND04
I2C SDA.103SDA03
I2C SCL.105SCL02

ソフトウェア

  • 概要

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: -- -- -- -- -- -- -- --
#

何かのパラメータがちがうのでしょうか?

  1. クロックを変えてみます。ボーレート指定でさらに下げたり、あげたり:NG
  2. 10Kプルアップ:NG
  3. 双方向レベル変換: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には厳しいです。
ということで、抵抗をはずしましょう。でも、抵抗が違うようです。
一応、回路図を調べると、

bcm2837-1.jpg

R23,R24です。コネクタの近くのA面にありますので、退場してもらいます。

結果

動きました!

LCD

LCD_OK-2.jpg

ツール化

せっかくなので、コマンドラインより文字を表示できるツールに仕立てます。

  • コマンド形式
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:		ヘルプ表示引数:

filelcd_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基盤の金具にショートしそうなこと意外は製品は特に問題はありませんでした。(中華製なので仕方ありません)

sainsmart-01.jpg

3.3V/5Vレベル変換

秋月電子のPCA9306を使用したモジュールを使用しました。

PCA9306とLCD制御LSIの接続

PCA9306の8ビット出力とLSIのピンは下表の接続です。

PCA9306 パラレル入出力LCD制御LSI信号
D7D7
D6D6
D5D5
D4D4
D3BackLight?
D2E
D1R_W
D0RS

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:		ヘルプ表示

ファイル:filelcd_exp.c

結果

動きました

16x2.jpg

HD44780互換IC系 その2 LCDモジュール(20 x 4) + I2CExpander

20x4(4行)タイプをこのICを使用しI2Cバスに接続するLCDモジュールが多数存在します。
基本、「HD44780互換IC系 その2」と同じでDDRAM(テキストビデオバッファ)のみことなりますが、互換性があります。

DDRAM

資料がないため、推測を含みますが以下のようでした。

LINE#ADDR
0102030405060708091011121314151617181920
1000102030405060708090A0B0C0D0E0F10111213
2404142434445464748494A4B4C4D4E4F50515253
31415161718191A1B1C1D1E1F2021222324242627
45455565758595A5B5C5D5E5F6061626364656667

結果

  • 動かない!

最初の火入れで、何も表示されず、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モジュールの金具とショートしています。

short.jpg

とりあえず、コネクタを外し、基盤を浮かせた状態で再度半田付けします。(ピンはカットするので、再利用不可)

remove.jpg

rebuild.jpg

  • 再度挑戦でOK!

今度は動きました。

2004-ok.jpg

お疲れ様でした。


添付ファイル: filelcd_exp.c 1227件 [詳細] filelcd_st7032.c 1571件 [詳細] fileshort.jpg 1233件 [詳細] file2004-ok.jpg 1288件 [詳細] fileremove.jpg 1313件 [詳細] filerebuild.jpg 1329件 [詳細] file16x2.jpg 1367件 [詳細] filesainsmart-01.jpg 1336件 [詳細] fileLCD_OK-2.jpg 1227件 [詳細] filebcm2837-1.jpg 1377件 [詳細] fileST7032-1.jpg 1473件 [詳細] fileK-08896.jpg 1175件 [詳細]

トップ   編集 編集(GUI) 凍結解除 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-08-10 (土) 16:58:50 (1780d)