液晶モジュールに温湿度・気圧データを表示するプログラムを作成する前に、動作確認目的として簡単な文字列を表示するプログラムを作成します。
動作確認で行うこと
温湿度・気圧センサから読み取った値を液晶モジュールに表示する前に、簡単な文字列を液晶モジュールに表示するプログラムを作成して動作確認します。
この記事では、固定の文字列(例えば「Hello!」など)を、液晶ディスプレイの左上の位置から表示するプログラムを作成してみます。作成したプログラムをベースに、ご自分で表示文字列や表示位置を変更して、液晶モジュールの制御方法の理解を深めていただければと思います。
関数作成
実践編第40回でI2C通信の関数を作成しました。今回はそれらのI2C通信関数を使用して順番に機能関数を作成していきます。
I2C通信関数
最初に作成する関数は、以下のI2C通信手順を実現する関数です。
なお、ACK/NACKについてはチェックしないことにします。というのは、ACK/NACKチェックをしてNACK(了解できなかった)という場合のPICマイコン側での対処方法が難しいためです。
このI2C通信手順を実現する関数名は「lcdI2CProtocol」とし、引数は、スレーブアドレス(address)、データ種別を示す1バイト(control_code)、データ1バイト(data)にします。
ACK/NACKチェックはしませんので、関数は以下のように簡単に作成してみました。
void lcdI2CProtocol(uint8_t address, uint8_t control_code, uint8_t data) {
i2cProtocolStart(); // スタートコンディション
i2cProtocolSendData(address); // アドレス送信
i2cProtocolSendData(control_code); // 制御コード送信 (動作設定=0x00/文字表示=0x40)
i2cProtocolSendData(data); // データ送信
i2cProtocolStop(); // ストップコンディション
return;
}
液晶モジュールのスレーブアドレスは0x7cですので、例えば表示文字として0x41(=’A’)を送りたい場合は、
lcdI2CProtocol(0x7c, 0x40, 0x41);
と記述すればOKです。
動作設定関数と文字表示関数
動作設定や文字表示を行う場合、先ほどのように「lcdI2CProtocol(0x7c, 0x40, 0x41);」などと関数コールすればとりあえず文字表示できますが、このようにするとプログラム記述がちょっと面倒になります。
というのは、動作設定を行う場合、動作設定のためのデータを液晶モジュールに送信したあと、液晶モジュール側で設定処理が行われるためちょっとの時間待ってあげる必要があります。動作確認したところ、動作設定の場合は1つの設定を行うたびに1ms程度時間待ちをした方が安定して動作するようです。また文字表示の場合は待ち時間なしで動作しました。
実はデータシートを見ると、以下のようにそれぞれの動作設定後は26.3μsより長い時間待つ必要があります。
ただ、いろいろな条件で試したところ、50μs〜70μsの処理待ちをしてもうまく動作しないケースがありました。特に深い意味はありませんが、動作設定後の待ち時間は1msでプログラムを作成しました。
先ほどのlcdI2CProtocol関数を使用して、待ち時間込みの関数として「lcdSendCommandData」という関数を作成します。引数は動作設定データの1バイトとしました。
void lcdSendCommandData(uint8_t command){
// コマンドを送信する場合の制御コードは0x00
lcdI2CProtocol(LCD_I2C_ADDRESS, 0x00, command);
// ウエイト
// データシートではウエイト時間は26.3us以上になっているが、
// それより長くしないと初期化できないケースがあるため1msのウエイトを入れる
__delay_ms(1);
return;
}
また文字表示ですが、こちらは色々試したところ表示文字データを液晶モジュールに送信したあと、特に待ち時間なしでも問題なく表示されていました。ただ電源の状態によっては問題が発生する可能性がありますので、コメントとして記述しておきました。関数名は「lcdSendCharacterData」とし、引数は表示文字データの1バイトとしました。
void lcdSendCharacterData(uint8_t data){
// 表示文字のデータを送信する場合の制御コードは0x40
lcdI2CProtocol(LCD_I2C_ADDRESS, 0x40, data);
// ウエイト
// 文字表示の場合はウエイトを入れなくても動作しているが
// 表示されない場合は1ms程度のウエイトを入れる
// __delay_ms(1);
return;
}
液晶モジュール初期化関数
液晶モジュールの初期化関数は、データシートを元に作成しました。関数名は「lcdInitialization」として引数はありません。先ほどの初期化処理を参考に以下のように作成しました。
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;
}
なお、コントラスト設定はC5〜C0の6ビットで行います。上のプログラムの設定は0b100000(=32)としていますが、濃度調整をしたい場合はこの値を変更してみてください。
また、0x6cの設定のあとはデータシートによると200ms以上のウエイトが必要となっていますので、200msのウエイトを入れてあります。
表示内容クリア関数
表示内容をクリアする関数も作成しておきます。関数にするまでもないですが、わかりやすくしたいので「lcdClearDisplay」という関数を作成しておきます。表示内容をクリアするには、動作設定の0x01を液晶モジュールに送信するだけでOKです。以下のように作成しました。
void lcdClearDisplay(void){
lcdSendCommandData(0x01);
return;
}
表示位置指定関数
文字の表示位置を指定する関数も作成しておきます。前回の記事で説明しましたが、以下のように座標位置指定できるようにしておきます。
関数名は「lcdLocateCursor」とします。引数は座標(x, y)として、position_xとposition_yとしました。
表示位置はアドレスで指定できることを説明しましたが、アドレスの計算は以下のようになります(前回の説明内容です)。
- yが0の場合
アドレス = 0x00 + x - yが1の場合
アドレス = 0x40 + x
アドレスがわかったら、コマンドは第7ビットを1にすればいいので、コマンドは以下のようになります。
コマンド = アドレス + 0x80
または、
コマンド = アドレス | 0x80
(|は論理和)
これをこのままプログラムにすると以下のようになります。
void lcdLocateCursor(uint8_t position_x, uint8_t position_y){
uint8_t address; // 文字表示位置(アドレス)
uint8_t command; // 表示位置指定コマンド
// Y座標位置のアドレス算出
switch( position_y ) {
// 1行目の場合
case 0:
address = 0x00;
break;
// 2行目の場合
case 1:
address = 0x40;
break;
}
// X座標を加えてアドレスを算出する
address += position_x;
// 第7ビットを1にしてコマンドにする
command = address + 0x80;
// 文字表示位置指定コマンド送信
lcdSendCommandData( command );
return;
}
ただ、このようにプログラムを作成すると冗長になってしまいますので、短くしたいと思います。
y座標が0の時は0x00、1のときは0x40です。これは条件判断で算出するのではなく、「0x40 * y」を計算すれば求めることができます。
0x40 * 0 = 0x00
0x40 * 1 = 0x40
となりますので、ここまで計算できれば、あとはx座標を加算して、コマンドにするためにさらに0x80を加算すればアドレスが求められます。これらのことから、表示位置指定の関数は以下のように作成してみました。
void lcdLocateCursor(uint8_t position_x, uint8_t position_y){
lcdSendCommandData( 0x80 + 0x40 * position_y + position_x );
return;
}
文字列表示
文字を表示する場合、1文字の場合は簡単です。例えば「A」という文字を表示したい場合、
lcdSendCharacterData(0x41);
または、’A’は0x41ですので、
lcdSendCharacterData('A');
とすれば、現在設定されている表示位置に「A」が表示されます。
文字列を表示した場合はどうすればよいでしょうか。「Hello!」と表示したい場合は、一文字ずつ上のようにlcdSendCharacterData関数で表示すればよいですが、この方法ですと面倒です。
ちょっと話が飛びますが、C言語を勉強された時、一番最初に出てきたのは「printf」だったのではないでしょうか。「printf(“Hello, world.”);」というようなプログラムが出てきたのではないかと思います。このprintfを使って液晶モジュールに文字列が表示できれば便利ですよね。
XCコンパイラでは、このprintf関数の出力先をカスタマイズできるようになっています。printf関数の引数の文字列は、通常ディスプレイなどに表示されます。XCコンパイラのprintf関数は、文字表示の部分はputch関数を使用しており、自分でputch関数を定義することにより、出力先をカスタマイズすることができるようになっています。
putchは引数がchar型の1バイトですので、以下のようにputc関数を記述しておくと液晶モジュールに文字列を表示することができます。
void putch(char character) {
lcdSendCharacterData(character);
return;
}
この関数をプログラム内に記述しておけば、通常のprintf関数の表示先が液晶モジュールになります。printf関数ですので、文字列だけではなく、以下のように数字を表示することが可能となります。
printf("Humidity = %d", 57);
printf("Temp. = %2.1f", 27.5);
実行結果 :
Humidity = 57
Temp. = 27.5
次回の記事では、温湿度と気圧データを表示するプログラムを作成しますが、数値の表示はこのようなprintf関数を使用します。
動作確認プログラム
これで必要な関数ができました。動作確認のプログラムは以下のようになります。注意点としては、電源投入後は液晶モジュールの動作が安定するまで100ms1程度ウエイトします。
/*
* 液晶モジュール動作確認プログラム
*
* https://tool-lab.com/
*
* 更新
* 2018. 7.28: 新規作成
*
*/
//
// 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>
#include <stdio.h>
//
// 定数
//
// I2C Ack/Nack定義
#define I2C_ACK 0x00
#define I2C_NACK 0xff
// LCDモジュール
#define LCD_I2C_ADDRESS 0x7c // LCDモジュールのI2Cアドレス
//
// 関数プロトタイプ宣言
//
// LCDモジュール表示制御関数
void lcdInitialize(void); // LCD初期化
void lcdClearDisplay(void); // ディスプレイ全消去
void lcdSendCommandData(uint8_t); // コマンド送信
void lcdSendCharacterData(uint8_t); // 1文字表示
void lcdLocateCursor(uint8_t,uint8_t); // カーソル位置指定
// LCDモジュールI2Cプロトコル関数
void lcdI2CProtocol(uint8_t, uint8_t, uint8_t);
// I2Cプロトコル各信号の生成関数
void i2cProtocolStart(void); // スタートビット生成
void i2cProtocolStop(void); // ストップビット生成
void i2cProtocolSendData(uint8_t); // 1バイトデータ送信
uint8_t i2cProtocolCheckAck(void); // ACK信号チェック
// クロック周波数
// __delay_ms()関数が時間基準に使用する
#define _XTAL_FREQ 4000000
//
// main関数
//
void main(void) {
// 動作周波数設定
OSCCON1bits.NDIV = 0b0000; // 分周1:1
OSCFRQbits.HFFRQ = 0b010; // 4MHz
// ピン属性設定
ANSELC = 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();
// LCD表示位置を左上に設定
lcdLocateCursor(0, 0);
// 表示位置からtest_文字列を表示
printf("Hello!");
// 動作ストップ
while(1){
}
}
//
// 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;
}
//
// printf関数の文字出力部分
//
void putch(char character) {
lcdSendCharacterData(character);
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 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;
}
// Ack/Nackチェック
uint8_t i2cProtocolCheckAck() {
uint8_t ackStatus;
if (SSP1CON2bits.ACKSTAT) {
ackStatus = I2C_NACK;
} else {
ackStatus = I2C_ACK;
}
return ackStatus;
}
更新履歴
日付 | 内容 |
---|---|
2018.7.29 | 新規投稿 |
毎回、しっかりと学習させていただいています。
参考コードをそのままコピペすれば確実に作動しますが、
学習のためと思い、1から自分で入力してみました。
しかし、次のようなエラーが出ます。
make -f nbproject/Makefile-default.mk SUBPROJECTS= .build-conf
make[1]: Entering directory ‘C:/Users/yasug/MPLABXProjects/kimu_/18857_I2C_Saidokakunin_R6.10.30.X’
make -f nbproject/Makefile-default.mk dist/default/debug/18857_I2C_Saidokakunin_R6.10.30.X.debug.elf
make[2]: Entering directory ‘C:/Users/yasug/MPLABXProjects/kimu_/18857_I2C_Saidokakunin_R6.10.30.X’
“C:\Program Files\Microchip\xc8\v2.46\bin\xc8-cc.exe” -mcpu=16F18857 -c -D__DEBUG=1 -mdebugger=none -mdfp=”C:/Program Files/Microchip/MPLABX/v6.10/packs/Microchip/PIC16F1xxxx_DFP/1.18.352/xc8″ -fno-short-double -fno-short-float -O0 -fasmfile -maddrqual=ignore -xassembler-with-cpp -mwarn=-3 -Wa,-a -DXPRJ_default=default -msummary=-psect,-class,+mem,-hex,-file -ginhx32 -Wl,–data-init -mno-keep-startup -mno-osccal -mno-resetbits -mno-save-resetbits -mno-download -mno-stackcall -mno-default-config-bits -std=c99 -gdwarf-3 -mstack=compiled:auto:auto -o build/default/debug/newmain.p1 newmain.c
newmain.c:147:5: warning: implicit declaration of function ‘lcdLocateCursor’ is invalid in C99 [-Wimplicit-function-declaration]
lcdLocateCursor(0, 0);
^
newmain.c:222:6: error: conflicting types for ‘lcdLocateCursor’
void lcdLocateCursor(uint8_t position_x, uint8_t position_y) {
^
newmain.c:147:5: note: previous implicit declaration is here
lcdLocateCursor(0, 0);
^
1 warning and 1 error generated.
(908) exit status = 1
make[2]: *** [build/default/debug/newmain.p1] Error 1
make[1]: *** [.build-conf] Error 2
make: *** [.build-impl] Error 2
nbproject/Makefile-default.mk:92: recipe for target ‘build/default/debug/newmain.p1’ failed
make[2]: Leaving directory ‘C:/Users/yasug/MPLABXProjects/kimu_/18857_I2C_Saidokakunin_R6.10.30.X’
nbproject/Makefile-default.mk:85: recipe for target ‘.build-conf’ failed
make[1]: Leaving directory ‘C:/Users/yasug/MPLABXProjects/kimu_/18857_I2C_Saidokakunin_R6.10.30.X’
nbproject/Makefile-impl.mk:39: recipe for target ‘.build-impl’ failed
BUILD FAILED (exit value 2, total time: 769ms)
ご質問どうもありがとうございます。
ソースコードが不明なので見当違いの回答になってしまっていたら申し訳ございません。
エラーの内容は、lcdLocateCursor関数の宣言と定義が違う、というものです。
lcdLocateCursor関数の定義はソースコードの222行目以降に書かれていると思いますが、145行目の「lcdLocateCursor」が関数の宣言と捉えられているようなメッセージ内容になっています。
そこでご確認いただきたいのですが、プログラムの最初の方(main関数より前)に、次のようにlcdLocateCursor関数のプロトタイプ宣言はされていますでしょうか?
void lcdLocateCursor(uint8_t position_x, uint8_t position_y);
最初にこの点をご確認いただければと思います。
早速ご回答頂き、有り難う御座います。
エラーがでるのは、みすたいぷしているのではと、何回も入力し直したりしていました。
入力時にコメントを入れるときに全角文字にして、半角に戻さずリターンして、
全角スペースが残ってしまっているとも考え、再入力などもしてみました。
再度、慎重に再入力してみます。
早速のご確認、コメントどうもありがとうございました。
この投稿記事の最後のプログラムの75行目にlcdLocateCursor関数のプロトタイプ宣言があります。
ご自身で入力されたプログラムの付近の行を最初にご確認いただくのが良いかな、と思います。
再確認や再入力でまた問題が出ましたらコメントいただければと思います。
結果のご報告です。
結果的には、ご指摘の通り、ミス入力をしていたことでした。
朝から、全角文字を使わずに入力したり、ミスの出た行の周辺を徹底的に再入力したりしていました。
質問したエラーの出たサンプルコードもその周辺を再度入力し直して、OKとなりました。
今入力していたのは、この回(第44回のサンプルコードです)。
有り難う御座いました。
わざわざご報告いただきましてどうもありがとうございました。
無事に動作したとのことでよかったです!
プログラムの写経は勉強になることも多いので、入力しながらなぜこのようになるのか、なども考えながら進めていただければと思います。
他にご質問など出てきましたコメント欄からお問い合わせいただければと思います。
おはよう御座います。
そうか、写経なんだ。心込めて一文字ずつ打ち込まなければいけないのだ。
エラーメッセージは、打ち込んだ人間のエラーも示してくれているので、しっかりと確認しなければならないことを学びました。
もう少し、実践編でI2C通信で楽しみます。
有り難う御座いました。
プログラムを自分で1行1行入力することを業界では写経って呼んでいるようです。
実際に(考えながら)写経をすると勉強になることもあるので、初学者の方に勧められているようですね。
ただ、規模が大きいプログラムでは長すぎて写経の効果が失われることもあるようです。
この投稿記事のプログラムの長さであれば写経の効果もあると思いますので、今後も工夫して楽しみながらスキルを習得していっていただければと思います!
入門編から学ばせて頂き有り難う御座います。
実践編で、I2C通信に出会い、楽しませて頂いています。
さて、今回の第44回のI2C通信の動作確認プログラムでは、沢山の関数を作成してI2C通信によりLCDに文字表示ができるようになりましたが、沢山作成された関数がどのように結びついているのかが気になりました。
メイン関数で、I2Cの初期設定を行った後、サンプルプログラムの138行のLCD初期化というコメントの次の lcdInitialize(); と言う関数をたどって行っても、スタートコンディション生成などの一連の関数にたどり着けません。 どこか間違っているのだと思いますが、それが解りません。
よろしくお願いいたします。
ご質問どうもありがとうございます。
I2C通信はちょっと複雑ですのでわかりづらいですよね。
記事内にも十分な説明がなく申し訳ございません。
ご質問いただきました lcdInitialize関数ですが、関数内ではlcdSendCommandData関数を呼び出しながら、必要なコマンドをLCDモジュールに送っています。
lcdSendCommandData関数は189行目で定義しています。この関数は、lcdI2CProtocolを呼び出しています。
lcdI2CProtocol関数は158行目で定義しています。
この関数では、実際にI2Cプロトコルを使いながら実際にデータをLCDモジュールに送信する部分です。この関数では次のような処理をしています。
i2cProtocolStart(); // スタートコンディション
i2cProtocolSendData(address); // アドレス送信
i2cProtocolSendData(control_code); // 制御コード送信 (動作設定=0x00/文字表示=0x40)
i2cProtocolSendData(data); // データ送信
i2cProtocolStop(); // ストップコンディション
つまり、ここでスタートコンディションを作って、制御コード、データを送り最後はストップコンディションを生成しています。
実際のPICマイコンのレジスタの制御は、それぞれの関数内で処理しています。
例えば、i2cProtocolStart関数は具体的には
SSP1IF = 0;
SSP1CON2bits.SEN = 1;
while (SSP1IF == 0) {}
SSP1IF = 0;
のように、ESSPのレジスタを操作してスタートコンディションを生成しています。
このように細かい階層でわけてしまいましたが、このようにすると他のプロジェクトでも利用できると思います。
言葉のみの説明でわかりづらいところが多々あると思います。不明点ございましたらお手数ですが再度コメント欄からご質問いただければと思います。
早速の回答有り難う御座います。
どこかで、I2C通信の基本関数を呼び出しているとはそれなりに理解しているつもりでも、関数を追いかけると迷ってしまう未熟さで質問しました。
今はもう、よれよれですので、明日、じっくりと確認させて頂きます。
自分が解っていると他の人もこれで解るだろうと思ってしまうようです。
ですが、そうでも無いことが世の常ですね。
早速のお返事どうもありがとうございました。
機能のレベルで関数を階層にすると、流用しやすくなりますが、その反面理解しづらくなることもあります。(こちらを立てればあちらが立たず、という世の常ですかね…)
今回のご質問をいただき、プログラムが複雑になった場合、関数の関係図も説明すると良いかな、と思いました。貴重なご質問をいただきどうもありがとうございます。
今回の動作確認のプログラムで今後も不明点がありましたらコメント欄からご質問いただければと思います。
こんにちは。現在、pic16f1827を用いて、LCDモジュール「AQM1602Y」を使おうとしているのですが、一瞬文字が出たと思ったらすぐに ◾️◾️◾️◾️◾️
◾️◾️◾️◾️◾️
のように文字が潰されてしまいます。原因がわからず困っているのですが、お力添えをいただけないでしょうか?
以下、プログラム全文です(ほぼ他人のもののコピペですが)。
//ヘッダファイルの読み込み
//————————————————————–
#include <xc.h>
#include <stdio.h> // printfを使うために必要なライブラリ
// クロック周波数指定
//————————————————————–
#define _XTAL_FREQ 32000000
// LCDディスプレイ(AE-AQM1602A)のアドレス
//————————————————————–
#define LCD_addr 0x7C
// プロトタイプ宣言
//————————————————————–
void I2C_init(void);
void I2C_start(void);
void I2C_stop(void);
void I2C_write(unsigned char dat);
void LCD_cmd(unsigned char cmd);
void LCD_char(unsigned char dat);
void LCD_init(void);
void LCD_clear(void);
void LCD_home(void);
void LCD_cursor(unsigned x,unsigned y);
void putch(char text);
// ============================================================
//
// メインプログラム
//
// ============================================================
void main() {
//———————————————————-
// PICマイコン初期設定
//———————————————————-
OSCCON = 0b01110000; // 内部クロック周波数:8MHz
ANSELA = 0b00000000; // 全てデジタルモード
ANSELB = 0b00000000;
TRISA = 0b00000000;
TRISB = 0b00010010; // SCL、SDAを入力ピンに設定
//———————————————————-
// シリアル通信(I2C)初期設定
//———————————————————-
SSP1STAT = 0b10000000; // 標準モード(100kHz)
SSP1CON1 = 0b00101000; // マスター(制御する側)に設定
SSP1ADD = 0b01001111; // 通信速度 = Fosc/(4*Clock)-1 = 32MHz/(4*100kHz)-1 = 79(10進数)
//———————————————————-
// LCDディスプレイと通信
//———————————————————-
// ディスプレイの初期化
LCD_init();
// 1列目に表示
LCD_cmd(0x80);
printf(“Hello World!”);
// 2列目に表示
LCD_cmd(0xC0);
printf(“0123456789”);
}
// ============================================================
//
// I2C 通信開始
//
// ============================================================
void I2C_start() {
SSP1CON2bits.SEN = 1;
while(SSP1CON2bits.SEN);
}
// ============================================================
//
// I2C 通信終了
//
// ============================================================
void I2C_stop() {
SSP1IF = 0;
SSP1CON2bits.PEN = 1;
while(SSP1CON2bits.PEN);
SSP1IF = 0;
}
// ============================================================
//
// I2C データ送信
//
// ============================================================
void I2C_write(unsigned char dat) {
SSP1IF = 0;
SSP1BUF = dat;
while(!SSP1IF);
}
// ============================================================
//
// LCDディスプレイの初期化
//
// ============================================================
void LCD_init() {
__delay_ms(40); //40ms wait
LCD_cmd(0x38); //8bit,2line
LCD_cmd(0x39); //IS=1 : extention mode set
LCD_cmd(0x14); //Internal OSC Frequency
LCD_cmd(0x7A); //Contrast set
LCD_cmd(0x56); //Power/ICON/Contrast Control
LCD_cmd(0x6C); //Follower control
__delay_ms(200);//200ms wait
LCD_cmd(0x38); //IS=0 : extention mode cancel
LCD_cmd(0x0C); //Display ON
LCD_cmd(0x01); //Clear Display
__delay_ms(2); //wait more than 1.08ms
}
// ============================================================
//
// LCDへコマンドを送信
//
// ============================================================
void LCD_cmd(unsigned char cmd) {
I2C_start();
I2C_write(LCD_addr);
I2C_write(0x00); // 0x00(コマンドの送信を示すbit)
I2C_write(cmd);
I2C_stop();
}
// ============================================================
//
// LCDへ表示するデータの送信
// printf文を使用するためにputch関数を使用する
//
// ============================================================
void putch(char text) {
I2C_start();
I2C_write(LCD_addr);
I2C_write(0x40); // 0x40(データの送信を示すbit)
I2C_write(text);
I2C_stop();
}
すみません、自己解決しました。単純にコントラストの設定が高すぎて文字が潰れているだけでした。お騒がせして申し訳ないです
すっかりお返事が遅くなって申し訳ございませんでした。
さらに自己解決されたとのことでよかったです!
四角が表示されると、このような文字が表示されてる、と思ってしまいますよね。まずは無事解決できてよかったです!
このサイトを毎日見てマイコンの勉強をしています!
I2Cに関しての質問なのですが、お力添えいただけますでしょうか。
LCDセグメントドライバについての質問です。
開発環境:MPLAB X IDE
開発言語:C
CPU:PIC16F18346
ドライバ:bu9797afuv
I2C通信で全点灯コマンドを入力しているのですが、動作しません。
以下、プログラムです。
PIC16F18346データシート
[https://akizukidenshi.com/download/ds/microchip/pic16f183x6.pdf]
bu9797afuvデータシート
[https://fscdn.rohm.com/jp/products/databook/datasheet/ic/driver/lcd_segment/bu9797afuv-j.pdf]
マイコン、LCDドライバ、LCD表示器の接続は完了していて
電源を入れ、上記のプログラムを書き込みましたがLCD表示器の動作は確認できませんでした。
よろしくお願いします。
ご回答が遅くなり申し訳ございませんでした。
使ったことのないコントロールICなので、仕様書ベースで気になる点を回答いたします。
・lcdInitialize()関数内のAPCTLコマンド
引数が0xFAになっていますが、仕様書のフォーマットでは0b1111_1110 = 0xFEではないかと思います。
・lcdInitialize()関数内のMODESETコマンド
引数が0xA8になっていますが、仕様書のフォーマットでは0b1100_1000 = 0xC8ではないかと思います。
他のI2Cコマンドシーケンスについては確認したところ問題ないように思いますが、ざっと確認した程度ですので、まずは上記コマンド値をご確認いただければと思います。