LPC810メモ:I2C-bus ROM API、同じLPC810でI2C slaveとmasterの両機能を使う

master送信、slave受信の例や、master受信、slave送信の例と同様、LPC810を2個使って、それぞれmaster/slaveとして機能させる例。

前回までは役割固定で、I2C slaveとなったLPC810はslaveとしてのみ機能(masterも同様)としていたが、今回はmasterとslaveが動的に入れ替わるようにする実験。

前提とする環境はこちら→LPC810メモ 共通の準備

  • 通信の内容と仕掛けは、master送信、slave受信の例と同じ。文字列("led off", "led on")を送受信する。
  • LPC810を2個使うのも同じ。ただし、それぞれ以下のように設定される。
    • 電源on時に8番がHIGH(ボタン押下)の方が、I2c slaveアドレス0xc0になる。
    • 電源on時に8番がLOWの方が、I2c slaveアドレス0xc2になる。
    • 従って、電源onの時、どちらか片方のボタンを押しておく必要あり。
  • プッシュボタンを押す度に、自分のLEDの点灯/消灯をトグルしつつ、I2C masterとして、もう片方のLPC810に"led on","led off"を送信する。
  • もう片方のLPC810は、I2C slaveとしてそれを受け取り、LEDを点灯/消灯する。
  • 結果として、どちらのボタンを押しても、I2C slave/master双方のLEDが点灯/消灯がトグルする。

サンプルコード

#include "LPC8xx.h"
#include<string.h>

#define I2C_BUFF_SZ  16

// UM10601 24.4 API description
// に相当すると思われるプロトタイプ宣言を、
// LPCOpenのヘッダから持ってきた。
extern "C" {
    typedef int ErrorCode_t; // 本当はenumだけど今回は値は見ない
    typedef int CHIP_I2C_MODE_T; // これも本当はenum
    typedef void *I2C_HANDLE_T;
    typedef void (*I2C_CALLBK_T)(uint32_t err_code, uint32_t n);

    typedef struct I2C_PARAM {
        uint32_t        num_bytes_send;
        uint32_t        num_bytes_rec;
        uint8_t         *buffer_ptr_send;
        uint8_t         *buffer_ptr_rec;
        I2C_CALLBK_T    func_pt;
        uint8_t         stop_flag;
        uint8_t         dummy[3];
    } I2C_PARAM_T;

    typedef struct I2C_RESULT {
        uint32_t n_bytes_sent;
        uint32_t n_bytes_recd;
    } I2C_RESULT_T;

    typedef struct  I2CD_API {
        void (*i2c_isr_handler)(I2C_HANDLE_T *handle);
        ErrorCode_t (*i2c_master_transmit_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
        ErrorCode_t (*i2c_master_receive_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
        ErrorCode_t (*i2c_master_tx_rx_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
        ErrorCode_t (*i2c_master_transmit_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
        ErrorCode_t (*i2c_master_receive_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
        ErrorCode_t (*i2c_master_tx_rx_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
        ErrorCode_t (*i2c_slave_receive_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
        ErrorCode_t (*i2c_slave_transmit_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
        ErrorCode_t (*i2c_slave_receive_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
        ErrorCode_t (*i2c_slave_transmit_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
        ErrorCode_t (*i2c_set_slave_addr)(I2C_HANDLE_T *handle, uint32_t slave_addr_0_3, uint32_t slave_mask_0_3);
        uint32_t        (*i2c_get_mem_size)(void);
        I2C_HANDLE_T *  (*i2c_setup)( uint32_t  i2c_base_addr, uint32_t * start_of_ram);
        ErrorCode_t     (*i2c_set_bitrate)(I2C_HANDLE_T *handle, uint32_t  p_clk_in_hz, uint32_t bitrate_in_bps);
        uint32_t        (*i2c_get_firmware_version)(void);
        CHIP_I2C_MODE_T (*i2c_get_status)(I2C_HANDLE_T *handle);
        ErrorCode_t     (*i2c_set_timeout)(I2C_HANDLE_T *handle, uint32_t timeout);
    } I2CD_API_T;

    // 「UM10601 21.5.2 ROM-based APIs」に相当するstruct
    // 本当はI2C以外も定義されているが今回は省略
    typedef struct ROM_API {
        const uint32_t    dummy1[5];
        const I2CD_API_T  *pI2CD;
        const uint32_t    dummy2[4];
    } LPC_ROM_API_T;

    // UM10601 24.3 General description
    // より、アドレスの設定
#define LPC_I2CD_API ((*(LPC_ROM_API_T * *) 0x1FFF1FF8UL)->pI2CD)
}

// slave側の記述ここから
//  APIを使う前提となる変数を用意
static I2C_HANDLE_T *i2cHandleSlave;
static uint32_t i2cSlaveHandleMEM[0x20];

// 受信用のバッファ
static uint8_t rxBuff[I2C_BUFF_SZ];

// 割り込みを使う都合上、APIで使うパラメータをファイルスコープで用意
static I2C_PARAM_T slaveParam;
static I2C_RESULT_T slaveResult;

// 何文字読んだか、rom apiの外側で記録するカウンタ
static uint32_t slaveRecvCounter = 0;

// 受信の準備
// 最初の1回だけは、slaveのアドレスとコールバックを設定して呼び出すようにした。
static void slaveSetupRecv(uint8_t addr = 0, I2C_CALLBK_T  func = 0) {
    if(addr != 0) {
        // slaveのアドレスを設定
        // UM10601 24.4.12 I2C Set Slave Address
        LPC_I2CD_API->i2c_set_slave_addr(i2cHandleSlave, addr, 0);
    }

    // コールバック関数を設定
    if(func != 0) { slaveParam.func_pt = func;   }

    // 受信用のバッファを設定
    slaveParam.buffer_ptr_rec = rxBuff;

    // 最大受信バイト数
    slaveParam.num_bytes_rec = sizeof(rxBuff);

    // 読み込んだ文字数をリセット
    slaveResult.n_bytes_recd = 0;
    slaveRecvCounter = 0; // 読み込んだ文字数を、rom apiの外側でも記録

    // なぜかrom apiが使わない、Slave Deselectの割り込みを有効化
    // 16.6.3 Interrupt Enable Set and read register
    LPC_I2C->INTENSET |= (1 << 15);

    // i2c slaveの受信、割り込みモード
    // 完了したらコールバックが呼ばれる仕組み
    // UM10601 24.4.10 I2C Slave Receive Interrupt
    LPC_I2CD_API->i2c_slave_receive_intr(i2cHandleSlave, &slaveParam, &slaveResult);
}

// 受信完了時に呼ばれるコールバック
static void slaveCompleteRecv(uint32_t err_code, uint32_t n) {
    static char buffer[I2C_BUFF_SZ];

    if (slaveResult.n_bytes_recd > 1) {
        // 1byteより多く読み込まれた場合
        // なお、0byte目はslave addressが入るので、1byteジャストだと
        // 実質読み込まれてない。

        // slave addressを除いたrxBuff[1] 以降を取り出す
        strncpy(buffer, (char *)(&rxBuff[1]), sizeof(buffer));
        buffer[sizeof(buffer) - 1] = 0; // null終端の保証

        // masterから送信された文字列が・・・
        if(strcmp(buffer, "led on") == 0) {
            // "led on"なら、2番pinをHIGHに(slave側のledを点灯)
            LPC_GPIO_PORT->SET0 |= (1 << 4);
        } else if(strcmp(buffer, "led off") == 0) {
            // "led on"なら、2番pinをLOWに(slave側のledを点灯)
            LPC_GPIO_PORT->CLR0 |= (1 << 4);
        }
    }

    // 次回の受信準備
    slaveSetupRecv();
}

// I2C割り込みハンドラ
extern "C" {
    void I2C_IRQHandler(void)
    {
        // slave pending状態で、slave receive.の場合、読み込み文字数を1増やす
        // UM10601 16.6.7 I2C Interrupt Status register
        // UM10601 16.6.2 I2C Status register
        if(
           (LPC_I2C->INTSTAT & (1 << 8)) &&
           ((LPC_I2C->STAT & (0x3 << 9)) == (0x1 << 9))) {
            slaveRecvCounter++;
        }

        // 処理をrom apiに委譲
        LPC_I2CD_API->i2c_isr_handler(i2cHandleSlave);

        // Slave Deselectedの割り込みを処理
        // UM10601 16.6.7 I2C Interrupt Status register
        if(LPC_I2C->INTSTAT & (1 << 15)) {
            if(slaveRecvCounter > 0) {
                // 1byte以上読み込んでいたら、rom apiに代わってコールバック関数を呼ぶ
                slaveResult.n_bytes_recd = slaveRecvCounter;
                slaveCompleteRecv(0,0);
            }
            LPC_I2C->STAT |= (1 << 15);
        }
    }
}

static void slaveMain(uint8_t addr) {
    //  I2C-bus ROM APIを使って初期設定
    // UM10601 24.4.14 I2C Setup
    i2cHandleSlave =LPC_I2CD_API->i2c_setup(LPC_I2C_BASE, i2cSlaveHandleMEM);

    // タイムアウト設定
    // UM10601 24.4.18 I2C time-out value
    LPC_I2CD_API->i2c_set_timeout(i2cHandleSlave, 100000);

    //  Enable/disable the I2C interrupt in interrupt slots #8 in the NVIC.
    NVIC->ISER[0] = (0x1 << 8);

    // 受信の準備
    slaveSetupRecv(addr, slaveCompleteRecv);
}
// slave側の記述ここまで

// master側の記述ここから
//  APIを使う前提となる変数を用意
static I2C_HANDLE_T *i2cHandleMaster;
static uint32_t i2cMasterHandleMEM[0x20];

// num byteのデータを送信
static void masterSendData(uint8_t addr, uint8_t data[], uint32_t num)
{
    uint8_t d[I2C_BUFF_SZ];
    d[0] = addr; // 0byte目はslaveのアドレスを入れる
    memcpy(&d[1], data, num); // [1]から先がデータ本体
    I2C_PARAM_T param;
    I2C_RESULT_T result;

    param.num_bytes_send  = num + 1; // addrの分を含めてnum + 1 byte送信
    param.buffer_ptr_send  = &d[0];
    param.num_bytes_rec  = 0; // 受信はしない(byte)
    param.stop_flag   = 1; // 送信終わったらstop

    // タイムアウト設定
    // UM10601 24.4.18 I2C time-out value
    LPC_I2CD_API->i2c_set_timeout(i2cHandleMaster, 100000);

    // 送信
    // UM10601 24.4.2 I2C Master Transmit Polling
    LPC_I2CD_API->i2c_master_transmit_poll(i2cHandleMaster, &param, &result);
}

// ボタン押されたときの処理
// (初回呼び出し時はslaveアドレスの設定)
static void masterHandlePushDown(uint32_t addr = 0) {
    static uint8_t slaveAddr = 0;
    static bool flag = true;
    if(addr != 0) {
        slaveAddr = addr;
    } else {
        if(flag) {
            // slaveに文字列"led on"を送信
            uint8_t data[] = "led on";
            masterSendData(slaveAddr, data, sizeof(data));

            //  2番pinをHIGHに(master側のledを点灯)
            LPC_GPIO_PORT->SET0 |= (1 << 4);
        } else {
            // 8番pinがHIGH
            // slaveに文字列"led off"を送信
            uint8_t data[] = "led off";
            masterSendData(slaveAddr, data, sizeof(data));

            //  2番pinをLOWに(master側のledを消灯)
            LPC_GPIO_PORT->CLR0 |= (1 << 4);
        }
        flag = !flag;

        // ★slaveとしての受信を再設定
        // ★一回masterとして送信してしまったので、待ち受け状態はキャンセル
        // ★されている模様。従って再設定する。
        slaveSetupRecv();
    }
}

extern "C" {
    // ボタン押したときのハンドラ
    // cr_startup_lpc8xx.cpp で定義されている名称。
    void PININT0_IRQHandler(void)
    {
        // 割り込みをクリア
        LPC_PIN_INT->IST = (1<<0);

        // ボタンが押された場合の処理
        masterHandlePushDown();
    }
}

// master側の初期設定
static void masterMain(uint8_t addr) {
    //  I2C-bus ROM APIを使って初期設定
    // UM10601 24.4.14 I2C Setup
    i2cHandleMaster = LPC_I2CD_API->i2c_setup(LPC_I2C_BASE, i2cMasterHandleMEM);

    // UM10601 24.4.15 I2C Set Bit Rate
    LPC_I2CD_API->i2c_set_bitrate(i2cHandleMaster, 12000000, 100000); // 速度100KHz

    // ボタン押下に対する割り込み設定
    LPC_SYSCON->PINTSEL[0] = 0; // PIO0_0(8番pin)をpin interruptの0番に
    NVIC->ISER[0] = (0x1 << 24); //  Pin interrupt 0の割り込み有効化
    LPC_PIN_INT->SIENR = (1 << 0); // rising edge(本例では押した時)に割り込み発生

    // ボタンが押された時の処理を初期化(slaveアドレスの設定)
    masterHandlePushDown(addr);
}
// master側の記述ここまで

int main(void) {
    // SWMのclockを有効化。
    // UM10601 4.6.13 System clock control register
    // → SWMは7bit目
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7);

    // SDAを4番pinに
    // UM10601  9.5.8 Pin assign register 7
    LPC_SWM->PINASSIGN7 = 0x02ffffffUL;

    // SCLを3番pinに
    // UM10601  9.5.9 Pin assign register 8
    LPC_SWM->PINASSIGN8 = 0xffffff03UL;

    // fixed pin functionはreset以外全部disable
    // UM10601  9.5.10 Pin enable register 0
    LPC_SWM->PINENABLE0 = 0xffffffbfUL;

    // UM10601  16.3 Basic configuration
    // にしたがってI2Cの準備
    // In the SYSAHBCLKCTRL register, set bit 5 (Table 30) to enable the clock to theregister interface.
    LPC_SYSCON->SYSAHBCLKCTRL |= (0x1<<5);

    // Clear the I2C peripheral reset using the PRESETCTRL register (Table 19).
    LPC_SYSCON->PRESETCTRL &= ~(0x1<<6);
    LPC_SYSCON->PRESETCTRL |= (0x1<<6);

    // GPIO clockを有効化
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);

    // 2番pinを出力に設定
    LPC_GPIO_PORT->DIR0 |= (1<<4);

    if(LPC_GPIO_PORT->PIN0 & (1<<0)) {
        // 開始時点で5番がHIGH -> slave address 0xc0で動作
        masterMain(0xc2); // 0xc2のslaveに送信するmasterとして機能
        slaveMain(0xc0); // 0xc0のslaveとして機能
    } else {
        // 開始時点で5番がLOW -> slave address 0xc2で動作
        masterMain(0xc0); // 0xc0のslaveに送信するmasterとして機能
        slaveMain(0xc2); // 0xc2のslaveとして機能
    }
    return 0 ;
}

接続

  • ボタンは8番pin、LEDは2番pin
  • 電源on時に、必ずどちらかのボタンを押しておかなくてはならない。ボタンが押されてた方のI2C slaveアドレスが0xc0、押されてない方が0xc2になる。
  • その後は、どちらのボタンを押しても、両方のLEDのLEDの点灯/消灯が入れ替わる。
  • 6,7番pin(Vdd,GND)についている青いのはパスコン、0.1μFのセラミックコンデンサ

補足/雑感

  • http://d.hatena.ne.jp/mikkabo/20150509/1431123991:pin interruptを早速利用。
  • master受信、slave送信の例を試した時に、slave側から送信する手順は少し面倒な気がしたので、どうせLPC810同士(どちらもI2C slaveにもmasterになれる)なら「常に受ける方がslave」でも良いのでは、と思って実験。
  • サンプルコード中の「★」の行について。それまでi2c_slave_receive_intrでslave側として割り込み待ちをしていたとしても、master側のAPI(i2c_master_transmit_poll)を使ったら無効になってしまうようなので、再度i2c_slave_receive_intrを呼んでおく必要があるようだ。
  • これまでの例でパスコンとか付けてなかったが、原因不明でうまく動かないことがあり、気休めとしてつけたら安定するようになった気がする。効いたんだろうか。

みっかぼの無料Androidアプリはこちら。