I2C通信の液晶モジュール「AQM1602(ST7032)」のカスタム文字の登録と表示方法を解説します。
AQM1602
液晶モジュール「AQM1602」は小型のキャラクターモジュールで、I2C接続なので配線も少なく使いやすい表示部品です。

この液晶モジュールのコントローラは「ST7032」で、8文字のカスタム文字を登録・表示できるようになっています。
この記事では、PICマイコンを使用したAQM1602(ST7032コントローラ)のカスタム文字の登録方法と表示方法を解説します。
AQM1602のカスタム文字仕様
AQM1602のカスタム文字の扱いについて仕様面から説明します。
文字コード表のカスタム文字登録コード
AQM1602の文字コード表は次のようになっています。

文字コードは8ビット(0x00〜0xFF)で256文字の文字を扱うことができます。
このうち、文字コードが0x00〜0x07の「———- CG RAM ———-」と書かれている部分がカスタム文字です。
文字コード0x00から0x07の8文字分は、好きな形の文字や絵を登録、表示することができるようになっています。
文字サイズは横5ドット、縦8ドットになります。サイズは小さいものの、頑張ればちょっとしたアイコンは作れそうですよね。
なお、登録したデータは電源を切ると消えてしまいますので、プログラムの最初で必要な文字を登録、そのプログラム内で使用するということになります。
文字のデータ形式
カスタム文字を登録するとき、カスタム文字の字形データをAQM1602に送ります。
その字形データの形式は次のようになっています。

例えば、❶ 字形が左側のハートマークの場合、❷ 白いセルを0、黒いセルを1に対応づけます。
このようにすると、それぞれの行は❸の2進数で表現できることになります。
実際に登録する際、それぞれの行を1バイトデータとして扱いますので、ハートマークのデータは❹のように8ビットデータの8バイトの配列で表現できることになります。
カスタム文字を登録する場合、❹の配列を使ってプログラムで処理をします。
カスタム文字データの登録
カスタム文字は1文字8バイトデータで、8文字登録できますので、カスタム文字を登録するには合計で8×8=64バイトのメモリが必要です。
AQM1602では、カスタム文字登録用にこの64バイトのメモリが搭載されていますが、アドレスは次のように0x00〜0x3Fが割り振られています。

例えば、文字コード0x00の文字データを登録する場合、字形の8バイトのデータを0x00〜0x07に記憶することになります。
文字コード0x01の場合、字形データは0x08〜0x0Fに登録することになります。
つまり一般化すると、例えば文字コードがcharCode
の場合、次のアドレスに登録することになります。
(charCode × 8) 〜 (charCode × 8 +7)
カスタム文字データを登録するとき、登録開始アドレスを指定しますので、プログラムでは(文字コード×8)という計算式を使用することにします。
カスタム文字の表示
登録したカスタム文字は、他の既存の文字と同様に扱うことができます。
例えば文字コード0x00にカスタム文字を登録した場合、その文字を表示するには文字コード0x00の文字を表示します。
カスタム文字処理のコマンド
次に、カスタム文字を登録、表示する際、どのようなコマンドをAQM1602に対して発行すれば良いか説明します。
カスタム文字の登録コマンド
AQM1602にカスタム文字を登録するには、「コマンド」に続いて「カスタム文字データ」を送信します。
「コマンド」は「0x40+登録メモリの先頭アドレス」です。
文字コードをcharCode
(uint8_t型)とした場合、コマンドは次の値になります。
0x40 + (charCode * 8)
「カスタム文字データ」は8バイトの文字形データです。
例えば、最初のハートマークであれば、次の8バイトのデータを送信すればOKです。
// カスタム文字のパターン(ハートマーク)
uint8_t heartPattern[8] = {
0b00000,
0b01010,
0b11111,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000
};
カスタム文字の表示コマンド
カスタム文字を表示するには、登録した文字の文字コードで表示できます。
AQM1602では、文字表示をする場合、制御コードとして0x40を送信したあとに文字コードを送信します。
実装例
最後に、PICマイコンとAQM1602を使用した実装例をご紹介します。
概要
動作確認として次の環境で行いました。
項目 | 内容 |
---|---|
開発環境 | MPLAB X IDE |
コンパイラ | XC v3.00 |
PICマイコン | PIC16F18857 |
電源 | PICkitより供給 |
書き込み機 | PICkit5 |
回路
回路図
動作確認のため、なるべくシンプルな回路にしました。
I2C通信はPICマイコンのRC2とRC3ポートを使用してします。

ブレッドボード回路
上の回路図は、ブレッドボードに次のように実装してみました。

プログラム
♡(ハートマーク)を登録して表示するプログラムです。
/*
* AQM1602 カスタム文字登録&表示サンプルプログラム
* (ハートマークを表示)
*/
//
// PIC16F18857コンィグレーション設定
//
// CONFIG1
#pragma config FEXTOSC = OFF // External Oscillator mode selection bits (Oscillator not enabled)
#pragma config RSTOSC = HFINT1 // Power-up default value for COSC bits (HFINTOSC (1MHz))
#pragma config CLKOUTEN = OFF // Clock Out Enable bit (CLKOUT function is disabled; i/o or oscillator function on OSC2)
#pragma config CSWEN = ON // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed)
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable bit (FSCM timer enabled)
//
// CONFIG2
#pragma config MCLRE = OFF // Master Clear Enable bit (IO)
#pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)
#pragma config LPBOREN = OFF // Low-Power BOR enable bit (ULPBOR disabled)
#pragma config BOREN = ON // Brown-out reset enable bits (Brown-out Reset Enabled, SBOREN bit is ignored)
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (VBOR) set to 1.9V on LF, and 2.45V on F Devices)
#pragma config ZCD = OFF // Zero-cross detect disable (Zero-cross detect circuit is disabled at POR.)
#pragma config PPS1WAY = ON // Peripheral Pin Select one-way control (The PPSLOCK bit can be cleared and set only once in software)
#pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a reset)
//
// CONFIG3
#pragma config WDTCPS = WDTCPS_31// WDT Period Select bits (Divider ratio 1:65536; software control of WDTPS)
#pragma config WDTE = OFF // WDT operating mode (WDT Disabled, SWDTEN is ignored)
#pragma config WDTCWS = WDTCWS_7// WDT Window Select bits (window always open (100%); software control; keyed access not required)
#pragma config WDTCCS = SC // WDT input clock selector (Software Control)
//
// CONFIG4
#pragma config WRT = OFF // UserNVM self-write protection bits (Write protection off)
#pragma config SCANE = available// Scanner Enable bit (Scanner module is available for use)
#pragma config LVP = ON // Low Voltage Programming Enable bit (Low Voltage programming enabled. MCLR/Vpp pin function is MCLR.)
//
// CONFIG5
#pragma config CP = OFF // UserNVM Program memory code protection bit (Program Memory code protection disabled)
#pragma config CPD = OFF // DataNVM code protection bit (Data EEPROM code protection disabled)
//
// ヘッダファイル
// int_t型を使用するため、stdint.hをインクルード
//
#include <xc.h>
#include <stdint.h>
//
// 定数定義
//
// I2C Ack/Nack定義
#define I2C_ACK 0x00
#define I2C_NACK 0xff
// LCDモジュールI2Cアドレス
#define LCD_I2C_ADDRESS 0x7c
//
// カスタム文字テータ
//
// カスタム文字の文字コード
const uint8_t CODE_HEART = 0x00;
// カスタム文字のパターン(ハートマーク)
uint8_t heartPattern[8] = {
0b00000,
0b01010,
0b11111,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000
};
//
// 関数プロトタイプ宣言
//
// LCDカスタム文字登録関数
void lcdRegisterCustomChar(uint8_t charCode, uint8_t pattern[]);
// LCDモジュール表示制御関数
void lcdInitialize(void); // LCD初期化
void lcdClearDisplay(void); // ディスプレイ全消去
void lcdSendCommandData(uint8_t); // コマンド送信
void lcdSendCharacterData(uint8_t); // 1文字表示
void lcdSendString(char *); // 文字列表示
void lcdLocateCursor(uint8_t,uint8_t); // カーソル位置指定
// LCDモジュールI2Cプロトコル関数
void lcdI2CProtocol(uint8_t, uint8_t, uint8_t);
// I2Cプロトコル各信号の生成関数
void i2cProtocolStart(void); // スタートコンディション生成
void i2cProtocolRepeatStart(void); // リピートスタートコンディション生成
void i2cProtocolStop(void); // ストップコンディション生成
void i2cProtocolSendData(uint8_t); // 1バイトデータ送信
uint8_t i2cProtocolReceiveData(void); // バイトデータ受信
uint8_t i2cProtocolCheckAck(void); // ACK/NACK信号チェック
void i2cProtocolSendAck(void); // ACK送信
void i2cProtocolSendNack(void); // NACK送信
// クロック周波数
// __delay_ms()関数が時間基準に使用する
#define _XTAL_FREQ 4000000
//
// main関数
//
void main(void) {
// 動作周波数設定
OSCCON1bits.NDIV = 0b0000; // 分周1:1
OSCFRQbits.HFFRQ = 0b010; // 4MHz
// ピン属性設定
ANSELA = 0b00000000;
ANSELB = 0b00000000;
ANSELC = 0b00000000;
TRISA = 0b00000000;
TRISB = 0b00000000;
TRISC = 0b01001100;
//
// I2C通信ピンのPPS設定
//
// 設定ロック解除
PPSLOCK = 0x55;
PPSLOCK = 0xAA;
PPSLOCKbits.PPSLOCKED = 0x00;
// SCL, SDAピンの割り当て
SSP1DATPPS = 0x12; // RC2をMSSP1:SDA1に設定
SSP1CLKPPS = 0x13; // RC3をMSSP1:SCL1に設定
RC3PPS = 0x14; // RC3をMSSP1:SCL1に設定
RC2PPS = 0x15; // RC2をMSSP1:SDA1に設定
// 設定ロック
PPSLOCK = 0x55;
PPSLOCK = 0xAA;
PPSLOCKbits.PPSLOCKED = 0x01;
//
// I2C通信設定
//
// SMP Standard Speed; CKE disabled;
SSP1STAT = 0x80;
// SSPEN enabled; CKP Idle:Low, Active:High; SSPM FOSC/4_SSPxADD_I2C;
SSP1CON1 = 0x28;
// SBCDE disabled; BOEN disabled; SCIE disabled; PCIE disabled; DHEN disabled; SDAHT 300ns; AHEN disabled;
SSP1CON3 = 0x00;
// Baud Rate Generator = 100kHz
SSP1ADD = 0x09;
// LCDモジュール電源安定化時間待ち
__delay_ms(100);
// LCD初期化
lcdInitialize();
// LCD表示クリア
lcdClearDisplay();
// カスタム文字の登録
lcdRegisterCustomChar(CODE_HEART, heartPattern);
// カスタム文字の表示
lcdLocateCursor(0, 0);
lcdSendCharacterData(CODE_HEART);
// 動作停止
while(1){
}
}
//
// LCD制御関数
//
// カスタム文字の登録関数
void lcdRegisterCustomChar(uint8_t charCode, uint8_t pattern[]) {
// CG RAMアドレス設定(0x40 + 文字位置*8)
lcdSendCommandData(0x40 + (charCode * 8));
// パターンデータ書き込み(8バイト)
for (int i = 0; i < 8; i++) {
lcdSendCharacterData(pattern[i]);
}
}
//
// LCDモジュールに制御コードまたはデータを送信
//
void lcdI2CProtocol(uint8_t address, uint8_t control_code, uint8_t data) {
i2cProtocolStart(); // スタートコンディション
i2cProtocolSendData(address); // アドレス送信
i2cProtocolSendData(control_code); // 制御コード送信 (動作設定=0x00/文字表示=0x40)
i2cProtocolSendData(data); // データ送信
i2cProtocolStop(); // ストップコンディション
return;
}
//
// 表示文字データ送信
// 0x40の後にデータを送信
void lcdSendCharacterData(uint8_t data){
// 表示文字のデータを送信する場合の制御コードは0x40
lcdI2CProtocol(LCD_I2C_ADDRESS, 0x40, data);
// ウエイト
// 文字表示の場合はウエイトは必要なくても動作しているが
// 表示されない場合は1ms程度のウエイトを入れる
// __delay_ms(1);
return;
}
//
// コマンド送信
// 0x00の後にコマンドを送信
//
void lcdSendCommandData(uint8_t command){
// コマンドを送信する場合の制御コードは0x00
lcdI2CProtocol(LCD_I2C_ADDRESS, 0x00, command);
// ウエイト
// データシートではウエイト時間は26.3us以上になっているが、
// それより長くしないと初期化できないケースがあったため1msのウエイトを入れている
__delay_ms(1);
return;
}
//
// ディスプレイ消去
//
void lcdClearDisplay(void){
lcdSendCommandData(0x01);
return;
}
//
// カーソル位置移動
// 引数は水平方向右側プラスのX軸、垂直方向下側プラスのY軸で、それぞれ0から開始
// 左上の座標が(x=0, y=0)
//
void lcdLocateCursor(uint8_t position_x, uint8_t position_y){
// 文字表示位置指定コマンド送信
lcdSendCommandData( 0x80 + 0x40 * position_y + position_x );
return;
}
//
// 文字列を送信
//
void lcdSendString(char *str){
// strの文字列を*strが0になるまでLCDモジュールに送信
while(*str) {
lcdSendCharacterData(*str);
str++;
}
return;
}
//
// LCDモジュール初期化
//
void lcdInitialize(void){
// 初期化コマンド送信
lcdSendCommandData(0x38); // 2行モードに設定
lcdSendCommandData(0x39); // 拡張コマンド選択
lcdSendCommandData(0x14); // 内部クロック周波数設定
lcdSendCommandData(0x70); // コントラスト設定(C3:C0 = 0b0000に設定)
lcdSendCommandData(0x56); // 電源電圧が3.3VなのでBooster=ON、コントラスト設定(C5:C4 = 0b10に設定)
lcdSendCommandData(0x6c); // オペアンプのゲイン設定
// モジュール内電源安定化のための時間待ち
__delay_ms(200);
// 初期化コマンド続き
lcdSendCommandData(0x38); // 通常コマンド選択
lcdSendCommandData(0x01); // ディスプレイ表示内容クリア
lcdSendCommandData(0x0c); // ディスプレイ表示
return;
}
//
// I2Cプロトコル制御関数
//
// スタートコンディション生成
void i2cProtocolStart() {
// SSP1CON2レジスタのSENビットを1に設定すると
// スタートコンディションが生成される
// 発行が完了するとSSP1IFが1になるのでwhile文で待つ
SSP1IF = 0;
SSP1CON2bits.SEN = 1;
while (SSP1IF == 0) {}
SSP1IF = 0;
return;
}
// リピートスタートコンディション生成
void i2cProtocolRepeatStart() {
SSP1IF = 0;
SSP1CON2bits.RSEN = 1;
while (SSP1IF == 0) {}
SSP1IF = 0;
return;
}
// ストップコンディション生成
void i2cProtocolStop() {
// SSP1CON2レジスタのPENビットを1に設定すると
// ストップコンディションが生成される
// 発行が完了するとSSP1IFが1になるのでwhile文で待つ
SSP1IF = 0;
SSP1CON2bits.PEN = 1;
while (SSP1IF == 0) {}
SSP1IF = 0;
return;
}
// 1バイトデータ送信
void i2cProtocolSendData(uint8_t data) {
// SSP1BUFに送信したいデータをセットすると、そのデータが送信される
// 発行が完了するとSSP1IFが1になるのでwhile文で待つ
SSP1IF = 0;
SSP1BUF = data;
while (SSP1IF == 0) {}
SSP1IF = 0;
return;
}
// 1バイトデータ受信
uint8_t i2cProtocolReceiveData() {
SSP1IF = 0;
SSP1CON2bits.RCEN = 1;
while (SSP1IF == 0) {}
SSP1IF = 0;
return SSP1BUF;
}
// Ack/Nackチェック
uint8_t i2cProtocolCheckAck() {
uint8_t ackStatus;
if (SSP1CON2bits.ACKSTAT) {
ackStatus = I2C_NACK;
} else {
ackStatus = I2C_ACK;
}
return ackStatus;
}
// Ack送信
void i2cProtocolSendAck() {
// ACKDTにACKをセット(負論理なので0を設定)
SSP1CON2bits.ACKDT = 0;
// NACK信号生成
SSP1CON2bits.ACKEN = 1;
while (SSP1CON2bits.ACKEN) {}
return;
}
// Nack送信
void i2cProtocolSendNack() {
// ACKDTにNACKをセット(負論理なので1を設定)
SSP1CON2bits.ACKDT = 1;
// NACK信号生成
SSP1CON2bits.ACKEN = 1;
while (SSP1CON2bits.ACKEN) {}
return;
}
変更履歴
日付 | 内容 |
2025.4.8 | 新規投稿 |