LPC810メモ:I2C-bus ROM API、slave送信->master受信
前回同様、I2C-bus ROM APIを利用して、I2Cのslaveとして動作させる例。ただし今回は、master側がslave側からdataを受信する。LPC810を2個使って、それぞれmaster/slaveとして機能させる点は同じ。
前提とする環境はこちら→LPC810メモ 共通の準備
前回コメントでいろいろ教わったので、今回は最初から固定長データとして割り切り(2byte送受信)、余計なコードがないのでバイナリサイズは1264byteでわりと小さい。master、slave兼用でこれなら、片方なら1Kbyteくらいにできそうな気がする(未検証)。
- LPC810を2個使う。
- 1つはmaster、もう片方はslave。挙動は下記のとおりで、おおむね前回と逆。
- プログラムは共通。開始直後に8番pinの入力を読み込み、HIGHならmaster、LOWならslaveとして動作する仕組み。前回と逆。
- slave側の8番にプッシュボタンをつけておいて、HIGH、LOWの状態に応じてmaster側にI2Cで送信。前回と逆。
- HIGHなら{11,22}の2byteを送信しつつ、自分の2番pinをHIGH(LED点灯)
- LOWなら{22,11}の2byteを送信しつつ、自分の2番pinをLOW(LED消灯)
- master側も、受信データに応じて点滅
- 結果として、slave側のプッシュボタンを押す/離すことにより、master,slave両方の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) } // master側の記述ここから // APIを使う前提となる変数を用意 static I2C_HANDLE_T *i2cHandleMaster; static uint32_t i2cMasterHandleMEM[0x20]; // num byteのデータを受信 static void masterRecvData(uint8_t addr, uint8_t data[], uint32_t num) { uint8_t buf[I2C_BUFF_SZ]; // 受信用バッファ buf[0] = addr; // 0byte目はslaveのアドレスを入れる I2C_PARAM_T param; I2C_RESULT_T result; param.num_bytes_rec= num + 1; // addrの分を含めてnum + 1 byteを指定。 param.buffer_ptr_rec = buf; param.stop_flag = 1; // 受信終わったらstop result.n_bytes_recd = 0; // タイムアウト設定 // UM10601 24.4.18 I2C time-out value LPC_I2CD_API->i2c_set_timeout(i2cHandleMaster, 100000); // 受信 // UM10601 24.4.3 I2C Master Receive Polling LPC_I2CD_API->i2c_master_receive_poll(i2cHandleMaster, ¶m, &result); // 受信 memcpy(data, &buf[1], num); // 引数のdataに書き戻す。 // 0byte目はslaveのアドレスなので、[1]から先がデータ本体。 } // master側の初期設定 static void masterMain() { // 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 while(1) { uint8_t data[I2C_BUFF_SZ]; // 定期的にslave側(アドレス0xc0)からデータを受信 masterRecvData(0xc0, data, 2); // 受信した結果が・・ if(data[0] == 11 && data[1] == 22) { // {11, 22}なら2番pinをHIGHに(master側のledを点灯) LPC_GPIO_PORT->SET0 |= (1 << 4); } else if(data[0] == 22 && data[1] == 11) { // {22, 11}なら2番pinをLOWに(master側のledを消灯) LPC_GPIO_PORT->CLR0 |= (1 << 4); } // 適当に待つ。 for(volatile int i = 0; i < 500000; i++) {} } } // master側の記述ここまで // slave側の記述ここから // APIを使う前提となる変数を用意 static I2C_HANDLE_T *i2cHandleSlave; static uint32_t i2cSlaveHandleMEM[0x20]; static I2C_PARAM_T slaveParam; static I2C_RESULT_T slaveResult; // 送信の準備 static void slaveSetupSend(uint8_t addr, uint8_t data[], uint32_t num, I2C_CALLBK_T func) { // slaveのアドレスを設定 // UM10601 24.4.12 I2C Set Slave Address LPC_I2CD_API->i2c_set_slave_addr(i2cHandleSlave, addr, 0x00); // コールバック関数を設定 slaveParam.func_pt = func; // 受信用のバッファを設定 slaveParam.buffer_ptr_send = data; // 送信バイト数 slaveParam.num_bytes_send = num; // 読み込んだ文字数をリセット slaveResult.n_bytes_sent = 0; // i2c slaveの送信、割り込みモード // 完了したらコールバックが呼ばれる仕組み // UM10601 24.4.11 I2C Slave Transmit Interrupt LPC_I2CD_API->i2c_slave_transmit_intr(i2cHandleSlave, &slaveParam, &slaveResult); } // 受信完了時に呼ばれるコールバック static void slaveCompleteRecv(uint32_t err_code, uint32_t n) { // 特になにもしない } // I2C割り込みハンドラ extern "C" { void I2C_IRQHandler(void) { // 処理をrom apiに委譲 LPC_I2CD_API->i2c_isr_handler(i2cHandleSlave); } } static void slaveMain() { // 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); while(1) { if(LPC_GPIO_PORT->PIN0 & (1<<0)) { // 8番pinがHIGH // {11, 22}の2byteを送信 uint8_t data[] = {11, 22}; slaveSetupSend(0xc0, data, 2, slaveCompleteRecv); // 2番pinをHIGHに(slave側のledを点灯) LPC_GPIO_PORT->SET0 |= (1 << 4); } else { // 8番pinがLOW // {22, 11}の2byteを送信 uint8_t data[] = {22, 11}; slaveSetupSend(0xc0, data, 2, slaveCompleteRecv); // 2番pinをLOWに(slave側のledを消灯) LPC_GPIO_PORT->CLR0 |= (1 << 4); } // 適当に待つ for(volatile int i = 0; i < 200000; i++) {} } } // slave側の記述ここまで 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); LPC_GPIO_PORT->CLR0 |= (1<<4); if(LPC_GPIO_PORT->PIN0 & (1<<0)) { // 開始時点で8番がHIGH -> masterとして動作 masterMain(); } else { // 開始時点で8番がLOW -> slaveとして動作 slaveMain(); } return 0 ; }
接続
- 上のLPC810がmaster、下がslave。
- 上は8番pinが常にHIGHになっているので、前述のプログラムより、masterとして動作する。
- I2Cは、3番pinがSCL、4番pinがSDA。pull up抵抗は適当に3KΩ。
- LEDは前回同様に写真ではわかりにくいが、2番pinについてる。
- slaveの8番についてるボタンを押すと、master,slaveの両方のLEDが点灯、離すと消灯。
- 前回と違って、タイムラグあり。masterからslaveに通信する間隔を長めにしているため。
補足/雑感
- i2c_set_slave_addrの第3引数は、前回教わった0xffではなく、とりあえず0で続行。
- master側の受信用のバッファ(サンプルではuint8_t buf[I2C_BUFF_SZ])、0byte目は受信用じゃなくてslaveのアドレスを入れておく必要がある。実際に読み込まれるデータは、[1]以降になる。
- 受信byte数を指定するはずのnum_bytes_recも、そのぶんの1を足さなくてはならない。2byte受信したければ3を指定する。
- APIと名乗るわりに、I2Cの仕組みを隠蔽する気がなさそう。使いやすくはないし汚い気がするが、こういうところが面白い気がする。
- とはいっても、受信だからといって最下位bitを1にしておく必要はない模様。
- 一応、master,slaveで指定する送受信byte数が異なった場合の実験結果(厳密な検証ではない)
- masterのbyte数が大きい場合
- masterはタイムアウト発生するが、そこまでのデータはbuffer_ptr_recに読み込まれる模様。
- slaveは、正常に送信した、とみなされる模様。
- slaveのbyte数が大きい場合
- masterは、正常に受信した、とみなされる模様。
- slaveは、どうなってるのかよくわからないが、例によってコールバックはいつまでも呼ばれない。
- masterのbyte数が大きい場合
- この際、ROM APIのソースコードを見れたら良いのにと思う。