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, ¶m, &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 ; }
接続
補足/雑感
- 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を呼んでおく必要があるようだ。
- これまでの例でパスコンとか付けてなかったが、原因不明でうまく動かないことがあり、気休めとしてつけたら安定するようになった気がする。効いたんだろうか。