キッチンタイマーの製作(3)I2CでLCDの制御
表示素子にI2C接続小型キャラクタLCDモジュール 8x2行を使用します。
通信方式がI2Cなので、メニューのMCCをクリックして設定をします。
PeripheralsにI2C2(今回は2つ目)を設定します。
Device Resources ウィンドウのI2C2[PIC24 /dsPIC33 / ・・・をダブルクリックします。そうすると、Project ResourcesウィンドウのPeripherals項目にI2C2が登場します。
Mode Selection=Master、Baud Rate=100kHzになっていることを確認します。
回路に依存するのですが、SDAとSCLの端子は通常外部の抵抗でプルアップするのですが、今回は部品点数削減のため抵抗がありませんので、マイコンの内部のプルアップ抵抗を使用します。
Project ResourcesウィンドウのPin Moduleをクリックします。
WPU(Weak Pull Up)のチェックを入れます。もし、忘れていると全く動作しないので要注意です。
Generateボタンをクリックします。
自分で変更していたファイルがある場合にマージするかどうかの画面が表示されます。自動生成されたコードが左側で、元々のファイルが右側に出ます。
青や緑の所は自分が変更していた部分なのでそのままとします。
赤色の所だけマージします。(->ボタン)
結果、こうなります。
File > Save All で保存します。Mergeウィンドウを閉じます。
これで、i2c.h と i2c.c のファイルができます。
コンパイルしてエラーが無いのを確認します。問題なければOKです。
これで、I2Cのドライバーが準備できました。
<(自動的に提供される)i2c.cの関数のまとめ>
// =================================================ここから
//マイコンのI2C関係のレジスタの初期化関数
void I2C2_Initialize(void);
//nバイト書き込み関数
void I2C2_MasterWrite(
uint8_t *pdata,
uint8_t length,
uint16_t address,
I2C2_MESSAGE_STATUS *pstatus);
// nバイト読み込み関数
void I2C2_MasterRead(
uint8_t *pdata,
uint8_t length,
uint16_t address,
I2C2_MESSAGE_STATUS *pstatus);
// 転送要求キューに、転送データを追加する関数
void I2C2_MasterTRBInsert(
uint8_t count,
I2C2_TRANSACTION_REQUEST_BLOCK *ptrb_list,
I2C2_MESSAGE_STATUS *pflag);
// 連続データを読み出す関数
void I2C2_MasterReadTRBBuild(
I2C2_TRANSACTION_REQUEST_BLOCK *ptrb,
uint8_t *pdata,
uint8_t length,
uint16_t address);
// 連続データを書き込む関数
void I2C2_MasterWriteTRBBuild(
I2C2_TRANSACTION_REQUEST_BLOCK *ptrb,
uint8_t *pdata,
uint8_t length,
uint16_t address);
// キューが空か求める関数
bool I2C2_MasterQueueIsEmpty(void);
// キューの状態を求める関数類
bool I2C2_MasterQueueIsFull(void);
void I2C2_BusCollisionISR( void );
void I2C2_ISR ( void );
// =================================================ここまで
<main.cに新規に作成・追加する関数>
// =================================================ここから
#define I2CLCD_AQM0802A 0x3E
/**
* コマンド書き込み
* @param device_address:I2Cデバイスのアドレス
* @param controlByte:コントロールバイト
* @param cmdData:コマンド
* @return
*/
uint8_t I2C2_write (uint8_t device_address, uint8_t controlByte, uint8_t cmdData) {
I2C2_MESSAGE_STATUS status = I2C2_MESSAGE_PENDING;
uint8_t write_buffer[2];
write_buffer[0] = controlByte;
write_buffer[1] = cmdData;
I2C2_MasterWrite(write_buffer, 2, device_address, &status);
int cnt = 0;
while (status == I2C2_MESSAGE_PENDING && cnt++ <10){
__delay_us(100);
}
return (status == I2C2_MESSAGE_COMPLETE);
}
/**
* LCDの指定アドレスに表示する
* @param str: 表示したい文字列
* @param num:表示バイト数
* @param LcdAddr LCDのDDRAM表示アドレス
*/
void WireLcdDisplay (char *str, int num, uint8_t LcdAddr) {
uint8_t dt[17]; //16文字まで対応
int i;
I2C2_MESSAGE_STATUS status = I2C2_MESSAGE_PENDING;
// Set DDRAM addressコマンドをセットする
dt[0] = 0x40;
// 文字列をアスキーコードに変換して配列に格納する
for (i = 0; i < num; i++) {
dt[i+1] = (uint8_t) str[i];
}
// Slaveへ制御コード:0x00
// コマンド:0x80 + DDRAMのアドレス
I2C2_write(I2CLCD_AQM0802A, 0x00, 0x80 | LcdAddr);
I2C2_MasterWrite(dt, num+1, I2CLCD_AQM0802A, &status);
int cnt = 0;
while (status == I2C2_MESSAGE_PENDING && cnt++ <10) {
__delay_us(100);
}
}
// これによって出力されるデータは以下の通りになります
// 【I2C2_writeによって出力されるデータ】
// 0x7C :LCDのアドレス
// 0x00 :コントロールバイト(RS=0:コマンド)※次はコマンドを送る
// 0x80 :コマンドバイト(SetDDRAMaddress + DDRAMアドレス)
//
// 【I2C2_MasterWriteによって出力されるデータ】
// 0x7C :LCDアドレス
// 0x40 :コントロールバイト(RS=1:データ)※次からデータを送る
// 表示データ(num個)
/**
* LCDの初期化処理
*/
void WireLcdInit (void) {
I2C2_MESSAGE_STATUS status = I2C2_MESSAGE_PENDING;
uint8_t sendData[ ] = { 0x38, 0x39, 0x14, 0x70, 0x56, 0x6C, 0x38, 0x0C, 0x01};
I2C2_MasterWrite(sendData, sizeof(sendData)+1, I2CLCD_AQM0802A, &status);
int cnt = 0;
while(status == I2C2_MESSAGE_PENDING && cnt++ <10){
__delay_us(100);
}
}
// これによって出力されるデータは以下の通りになります
// 【I2C2_MasterWriteによって出力されるデータ】
// 0x7C :LCDアドレス
// 0x38, 0x39, 0x14, 0x70, 0x56, 0x6C, 0x38, 0x0C, 0x01 :コマンド
/**
* LCD表示処理サンプル(事前にWireLcdInit ()を呼び出しておくこと)
*/
void LcdDisp (void) {
char str1[ ] = "Kitchen ";
char str2[ ] = "Timer ";
//------[ 表示(1行目)]------
WireLcdDisplay(str1, 8, 0x00);
//------[ 表示(2行目)]------
WireLcdDisplay(str2, 8, 0x40);
}
// =================================================ここまで
【メインルーチン】
int main(void) {
// initialize the device
SYSTEM_Initialize();
__delay_ms(40);
// LCD初期化と初期表示処理
WireLcdInit(); // LCD初期化
__delay_ms(1); // ウェイト
gMode = 0; //グローバル変数で別途定義する
LcdDisp();
__delay_ms(1000);
gMode =1;
gCount = 0; //グローバル変数で別途定義する
int loopCnt = 1;
//永久ループ
while (1) {
SwScan(); // スイッチの取り込みとチャタリング除去処理(別途定義する)
switch (loopCnt) {
case 1:
// SWが押された時のLED点灯処理
swOperation(); //(別途定義する)
break;
case 2:
// ○○○処理
break;
case 3:
// ○○○処理
break;
case 4:
default:
// LCD表示処理
LcdDisp();
loopCnt = 0;
break;
}
loopCnt++; // ループカウンタの更新
__delay_ms(10); // 10msウェイト
}
return 1;
}
LCD表示時にsnprintf関数を使うと思うので、
#include <stdio.h>
の追加と、プロジェクトのプロパティーのチェックボックスの☑を外しておきます。
※参考:https://electronza.com/xpress-mpl3115a2-xc8-i2c/2/
※参考:http://kazhat.at.webry.info/201606/article_11.html
続きは(4)へ http://fujiharagiken.hatenablog.com/entry/2017/12/11/134558