組込み系システム開発室by藤原技研M

製作中の組込み機器や気になる開発ツールなどを中心に記事を書いています。

キッチンタイマーの製作(3)I2CでLCDの制御

表示素子にI2C接続小型キャラクタLCDモジュール 8x2行を使用します。

通信方式がI2Cなので、メニューのMCCをクリックして設定をします。

PeripheralsにI2C2(今回は2つ目)を設定します。

Device Resources ウィンドウのI2C2[PIC24 /dsPIC33 / ・・・をダブルクリックします。そうすると、Project ResourcesウィンドウのPeripherals項目にI2C2が登場します。

f:id:fujiharagiken:20180110162358p:plain

Mode Selection=Master、Baud Rate=100kHzになっていることを確認します。

f:id:fujiharagiken:20180110164045p:plain

 

回路に依存するのですが、SDAとSCLの端子は通常外部の抵抗でプルアップするのですが、今回は部品点数削減のため抵抗がありませんので、マイコンの内部のプルアップ抵抗を使用します。

Project ResourcesウィンドウのPin Moduleをクリックします。

f:id:fujiharagiken:20180110163145p:plain

WPU(Weak Pull Up)のチェックを入れます。もし、忘れていると全く動作しないので要注意です。

f:id:fujiharagiken:20171023154140p:plain

Generateボタンをクリックします。

自分で変更していたファイルがある場合にマージするかどうかの画面が表示されます。自動生成されたコードが左側で、元々のファイルが右側に出ます。

f:id:fujiharagiken:20171021123541p:plain

青や緑の所は自分が変更していた部分なのでそのままとします。

f:id:fujiharagiken:20180105110209p:plain

赤色の所だけマージします。(->ボタン)

結果、こうなります。

f:id:fujiharagiken:20171021124048p:plain

 File > Save All で保存します。Mergeウィンドウを閉じます。

これで、i2c.h と i2c.c のファイルができます。

f:id:fujiharagiken:20171021124510p:plain

コンパイルしてエラーが無いのを確認します。問題なければ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>

の追加と、プロジェクトのプロパティーチェックボックスの☑を外しておきます。

f:id:fujiharagiken:20180302145544p:plain

 

※参考: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