キャラクタLCDをアクセス

はじめに

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

HD44780互換IC系

概要

キャラクタディスプレイといえば、この系統が主流ですね。往年の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
早速コンパイルして実行します。

# ./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 [-i] [-h]  <message> [<line#>]
引数:
message    : 半角カタカナ(SJIS)を含むASCII文字列
 line#      : 行番号(0または1)、デフォルトは0
オプション:
-i         : LCDを初期化、最初の1回実施すればOK

お疲れ様でした。


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS