yoshiyuki's blog

Arduino/Teensy/Raspberry pi pico を利用した I2C, SPI 通信アプリを紹介します

Teensy 4.0 / I2C ができるまで (7)

前回までで 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