前回までで I2C通信に関して一通りの動作は実現できたので、まとめです。
Teensy 4.0 で I2C通信をやるための流れ
- どの LPI2C モジュールを使うか決める
- LPI2C モジュールに繋がっている SCL/SDA 端子を確認する。選択肢がある場合はどの端子を使うか決める
- 端子側でも、繋ぎ先を LPI2C モジュールに設定する
- 端子を Open-Drain に設定する。内蔵Pull-up を有効にするか無効にするかも決める
- LPI2C モジュールを動作させる Clock を選び、開通させる
- LPI2C モジュールの設定を行う
- LPI2C モジュールにコマンドを送り込んだり、読み出したデータを取り出したりする
Teensy 4.0 専用 I2C 関数
ここまで色々やった内容をまとめて関数を作りました。
// I2C function for Teensy 4.0 byte ADR; byte inDAT[256]; byte outDAT[256]; byte RC; // Variable for I2C IMXRT_LPI2C_t *port_i2c; IMXRT_REGISTER32_t *port_iomuxc; IMXRT_REGISTER32_t *port_iomuxc_b; // Const for I2C const int CONST_TIMEOUT = 100; const byte SC_SUCCESS = 0xFF; const byte SC_TIMEOUT = 0x80; const byte SC_NACK = 0x00; void setup() { // Setup for I2C port_i2c = &IMXRT_LPI2C1; //0x403F0000 port_iomuxc = &IMXRT_IOMUXC; //0x401F8000 port_iomuxc_b = &IMXRT_IOMUXC_b; //0x401F8400 // #19 = SCL, GPIO_AD_B1_00 port_iomuxc->offset0FC = 0x00000013; // SION = 1, MUX_MODE = 011(LPI2C_SCL) // port_iomuxc->offset2EC = 0x0000F861; // Pull-up = 22kohm, PUS = 11 (22kohm), PUE = 1, PKE = 1, ODE = 1, SPEED = 01, DSE = 100, SRE = 1 port_iomuxc->offset2EC = 0x0000E861; // Pull-up = none, PUS = 11 (22kohm), PUE = 1, PKE = 0, ODE = 1, SPEED = 01, DSE = 100, SRE = 1 port_iomuxc_b->offset0CC = 0x00000001; // DAISY = 1 // #18 = SDA, GPIO_AD_B1_01 port_iomuxc->offset100 = 0x00000013; // SION = 1, MUX_MODE = 11(LPI2C_SDA) // port_iomuxc->offset2F0 = 0x0000F861; // Pull-up = 22kohm port_iomuxc->offset2F0 = 0x0000E861; // Pull-up = none port_iomuxc_b->offset0D0 = 0x00000001; // DAISY = 1 // enable Clock for LPI2C1 CCM_CSCDR2 = (CCM_CSCDR2 & 0xFE03FFFF) | 0x00040000; CCM_CCGR2 |= 0x000000C0; // CG3 = 11, enable LPI2C1 clock // Setup for LPI2C1 i2c_set_freq(); // Enable LPI2C1 port_i2c->MCR = 0x00000001; } void loop() { // Example of I2C Write // write 0xAA and 0xBB to Register address 0x02-0x03 of Slave device 0x24. ADR = 0x24; // 7-bit Slave address inDAT[0] = 0x02; inDAT[1] = 0xAA; inDAT[2] = 0xBB; RC = i2c_write(ADR, inDAT, 3); // byte i2c_write(byte slave_adr, byte *data, int data_length) // Example of I2C Read // read Register address 0x01-0x04 (4-byte) of Slave device 0x24. ADR = 0x24; // 7-bit Slave address inDAT[0] = 0x01; RC = i2c_read(ADR, inDAT, 1, outDAT, 4); // byte i2c_read(byte slave_adr, byte *reg_adr, int reg_adr_length, byte *read_data, int read_data_length) // RC = SC_SUCCESS = 0xFF: i2c_write/read succeeded. // RC = SC_NACK = 0x00: NACK // RC = SC_TIMEOUT = 0x80: I2C command was suspended. delay(1000); } byte i2c_write(byte slave_adr, byte *data, int data_length) { byte status_code; status_code = i2c_main(slave_adr, data, data_length); if (status_code == SC_SUCCESS) { if (i2c_wait_FIFO_TX(CONST_TIMEOUT, 4)) { i2c_stop(); if (i2c_master_done(CONST_TIMEOUT)) { status_code = i2c_status_code(); } else { status_code = SC_TIMEOUT; } } else { status_code = SC_TIMEOUT; } } if (status_code != SC_SUCCESS) { i2c_master_reset(); } return status_code; } byte i2c_read(byte slave_adr, byte *reg_adr, int reg_adr_length, byte *read_data, int read_data_length) { byte status_code; status_code = i2c_main(slave_adr, reg_adr, reg_adr_length); if (i2c_wait_FIFO_TX(CONST_TIMEOUT, 1)) { status_code = i2c_status_code(); if (status_code == SC_SUCCESS) { status_code = i2c_read_start(slave_adr, read_data, read_data_length); } if (status_code != SC_SUCCESS) { i2c_master_reset(); } } else { status_code = i2c_status_code(); i2c_master_reset(); } return status_code; } byte i2c_main(byte slave_adr, byte *data, int data_length) { byte status_code; status_code = i2c_start(slave_adr, 0); if (status_code == SC_SUCCESS) { status_code = i2c_send_data(data, data_length); } return status_code; } //rw = 0: Write, 1: Read byte i2c_start(byte slave_adr, int rw) { byte status_code; uint32_t pageTMP = 0x00000400; if (i2c_master_done(CONST_TIMEOUT)) { i2c_clear(); if (i2c_bus_available(CONST_TIMEOUT)) { pageTMP = pageTMP | (slave_adr << 1) | rw; port_i2c->MTDR = pageTMP; status_code = SC_SUCCESS; } else { status_code = SC_TIMEOUT; } } else { status_code = SC_TIMEOUT; } return status_code; } void i2c_stop() { port_i2c->MTDR = 0x00000200; } byte i2c_send_data(byte *data, int data_length) { byte status_code = SC_SUCCESS; int i_i2c; uint32_t pageTMP; for (i_i2c = 0; i_i2c < data_length; i_i2c++) { if (i2c_wait_FIFO_TX(CONST_TIMEOUT, 4)) { pageTMP = data[i_i2c]; port_i2c->MTDR = pageTMP; } else { status_code = SC_TIMEOUT; break; } } return status_code; } byte i2c_read_start(byte slave_adr, byte *read_data, int read_data_length) { int cnt_timeout = 0; int cnt_RX = 0; int cnt_TX = 0; uint32_t pageTMP = 0x00000400; byte status_code; pageTMP = pageTMP | (slave_adr << 1) | 1; port_i2c->MTDR = pageTMP; if (i2c_wait_FIFO_TX(CONST_TIMEOUT, 1)) { status_code = i2c_status_code(); if (status_code == SC_SUCCESS) { while (cnt_RX < read_data_length) { if (cnt_TX < read_data_length) { if ((port_i2c->MFSR & 0x00000007) < 4) { port_i2c->MTDR = 0x00000100; cnt_TX++; cnt_timeout = 0; } } if ((port_i2c->MFSR & 0x00070000) > 0) { pageTMP = port_i2c->MRDR; read_data[cnt_RX] = pageTMP & 0xFF; cnt_RX++; cnt_timeout = 0; } delayMicroseconds(1); cnt_timeout++; if (cnt_timeout == 1000) { status_code = SC_TIMEOUT; break; } } if (status_code == SC_SUCCESS) { if (i2c_wait_FIFO_TX(CONST_TIMEOUT, 4)) { i2c_stop(); if (i2c_master_done(CONST_TIMEOUT)) { status_code = i2c_status_code(); } else { status_code = SC_TIMEOUT; } } else { status_code = SC_TIMEOUT; } } } } else { status_code = SC_TIMEOUT; } return status_code; } void i2c_master_reset() { port_i2c->MCR = 0x00000002; delay(1); port_i2c->MCR = 0x00000000; i2c_set_freq(); port_i2c->MCR = 0x00000001; } void i2c_clear() { i2c_clear_FIFO(); i2c_clear_FLAG(); } void i2c_clear_FIFO() { port_i2c->MCR = 0x00000301; // reset Tx/Rx FIFO } uint32_t i2c_clear_FLAG() { uint32_t pageTMP = port_i2c->MSR; port_i2c->MSR = 0x00007F00; return pageTMP; } // cnt_TX = 1, 2, 3 or 4. bool i2c_wait_FIFO_TX(int timeout, uint32_t cnt_TX) { int cnt_timeout = 0; while (cnt_timeout < timeout) { delayMicroseconds(100); if ((port_i2c->MFSR & 0x00000007) < cnt_TX) { return true; } cnt_timeout++; } return false; } bool i2c_wait_FIFO_RX(int timeout) { int cnt_timeout = 0; while (cnt_timeout < timeout) { delayMicroseconds(100); if ((port_i2c->MFSR & 0x00070000) > 0) { return true; } cnt_timeout++; } return false; } bool i2c_master_done(int timeout) { int cnt_timeout = 0; while ((port_i2c->MSR & 0x01000000) > 0) { delayMicroseconds(100); if (cnt_timeout == timeout) { return false; } cnt_timeout++; } return true; } bool i2c_bus_available(int timeout) { int cnt_timeout = 0; while ((LPI2C1_MSR & 0x02000000) > 0) { delayMicroseconds(100); if (cnt_timeout == timeout) { i2c_clear(); return false; } cnt_timeout++; } return true; } void i2c_set_freq() { // I2C Freq = 100 kHz port_i2c->MCFGR1 = 0x00000001; port_i2c->MCFGR2 = 0x05050BB8; // FILTSDA = 5 (0.2 us), FILTSCL = 5 (0.2 us), BUSIDLE = 3000 (250 us) port_i2c->MCCR0 = 0x1928373B; // DATAVD = 25 (2.2 us), SETHOLD = 40 (3.4 us), CLKHI = 55 (4.7 us), CLKLO = 59 (5 us) // Others port_i2c->MCCR1 = port_i2c->MCCR0; } byte i2c_status_code() { uint32_t pageTMP; delayMicroseconds(100); pageTMP = port_i2c->MSR; if ((pageTMP & 0x00000400) > 0) { // NACK return SC_NACK; } if ((pageTMP & 0x00002000) > 0) { // Pin Low Timeout return SC_TIMEOUT; } if ((pageTMP & 0x00001000) > 0) { // FIFO Error Flag return SC_TIMEOUT; } if ((pageTMP & 0x00000800) > 0) { // Arbitration Lost Flag return SC_TIMEOUT; } return SC_SUCCESS; }
loop の中身が関数本体と使用例です。
RC = i2c_write(ADR, inDAT, 3); // byte i2c_write(byte slave_adr, byte *data, int data_length)
I2C Write 関数、i2c_write の使用例です。
引数の1個目には 7-bit Slave address を置きます。ここでは Byte型変数の ADR に Slave address を代入したものを置いています。
引数の2個目には Slave device に書き込むデータを代入した Byte型配列のポインタを置きます。ここでは Byte型配列 inDAT[256] の [2:0] に値を代入して、そのポインタである inDAT を置いています。なお、ここに置く配列の長さは、Slave device に書き込むデータの長さよりも長ければいくら長くても大丈夫です。
引数の3個目には Slave device に書き込むデータの個数を置きます。ここでは 3 を置いているので送るデータは 3個であり、2個目の引数で指定した inDAT の [2:0] に代入されたデータを書き込むことを意味します。
i2c_write は処理を完了すると Byte型の値を結果として返してくるので、Byte型変数の RC で受け取っています。結果は成功 (0xFF)、タイムアウト (0x80)、NACK (0x00) の 3種類です。
RC = i2c_read(ADR, inDAT, 1, outDAT, 4); // byte i2c_read(byte slave_adr, byte *reg_adr, int reg_adr_length, byte *read_data, int read_data_length)
I2C Read 関数、i2c_read の使用例です。
引数の 1-3 個目までは i2c_write と同じです。Read を行う前に Register address 等を Slave device に書き込む必要がある場合はここに書き込むデータを置きます。事前の書き込みが不要な場合、2個目の引数には適当な配列のポインタを置いて 3個目の引数に 0 を置けば事前の書き込みを行いません。(この場合、2個目の引数で指定した配列には一切アクセスしません)
引数の4個目には Slave device から読み出したデータを格納する配列のポインタを置きます。ここでは Byte型配列 outDAT[256] のポインタである outDAT を置いています。当然、ここに置く配列の長さは読み出そうとするデータの長さよりも長い必要があります。
引数の5個目には Slave device から読み出すデータの個数を置きます。ここでは 4 を置いているので、読み出すデータは 4-byte となります。
i2c_read も処理を完了すると Byte型の値を結果として返してきます。結果は成功 (0xFF)、タイムアウト (0x80)、NACK (0x00) の 3種類です。
以上で Teensy 4.0 で I2C 通信を行うまでの長い道のりは終わりです。
最後まで繰り返しますが、I2C通信をやりたいなら Wire ライブラリを使った方が遥かに簡単です!
おまけ。I2C に加えて SPI と GPIO も制御する Sketch とツールも作成しました。
ysin1128.hatenablog.com