091204

|


LPC2388のI2Cモジュールの枠組み、なんとかできた。

こんな感じで。i2c_cmd*でステートマシンを先にi2c_commandに作っておき、 それをi2c0_executeで割込み駆動で実行する形式。
void i2c0_init (void);
void i2c0_fini (void);
void i2c0_execute (struct i2c_command *);

// Prepare state machine.
void i2c_cmd_slave_addr (struct i2c_command *, uint8_t, uint8_t);
void i2c_cmd_write (struct i2c_command *, uint8_t, enum i2c_control_option);
void i2c_cmd_read (struct i2c_command *, uint8_t *, enum i2c_control_option);


こんな感じで使います。KXP84は最初のレジスタの書き込み、読み込みからデー
タを読み書き毎に内部レジスタをインクリメントするので一括で処理が可能。
読み込みの場合にステートマシンを作っておけば、実行時に一切の負担がない
ようにしたいというのが目的(だったのだけど、実際は実行時に結構手間がかか
ることになってしまった)

void
kxp84_reg_write_6 (struct i2c_command *cmd, uint8_t reg_addr, uint8_t *reg_data)
{
  // Prepare state machine.
  cmd->current = 0;
  cmd->n_command = 0;
  cmd->busy = FALSE;

I2Cのスレーブアドレスを設定して、マスタ送信モードに。
  i2c_cmd_slave_addr (cmd, 0x18, I2C_DIR_WRITE);
KXP84のレジスタアドレスを送信。
  i2c_cmd_write (cmd, reg_addr, I2C_CONTINUE);
  i2c_cmd_write (cmd, *reg_data++, I2C_CONTINUE);
  i2c_cmd_write (cmd, *reg_data++, I2C_CONTINUE);
  i2c_cmd_write (cmd, *reg_data++, I2C_CONTINUE);
  i2c_cmd_write (cmd, *reg_data++, I2C_CONTINUE);
  i2c_cmd_write (cmd, *reg_data++, I2C_CONTINUE);
最後はストップ条件を発行。
  i2c_cmd_write (cmd, *reg_data++, I2C_STOP);
}

void
kxp84_reg_read (struct i2c_command *cmd, uint8_t reg_addr, uint8_t *reg_data)
{
  // Prepare state machine.
  cmd->current = 0;
  cmd->n_command = 0;
  cmd->busy = FALSE;

I2Cのスレーブアドレスを設定して、マスタ送信モードに。
  i2c_cmd_slave_addr (cmd, 0x18, I2C_DIR_WRITE);
KXP84のレジスタアドレスを送信して、反復スタート条件に。
  i2c_cmd_write (cmd, reg_addr, I2C_START);
I2Cのスレーブアドレスを設定して、マスタ受信モードに。
  i2c_cmd_slave_addr (cmd, 0x18, I2C_DIR_READ);
  i2c_cmd_read (cmd, reg_data, I2C_CONTINUE);
  i2c_cmd_read (cmd, reg_data + 1, I2C_CONTINUE);
  i2c_cmd_read (cmd, reg_data + 2, I2C_CONTINUE);
  i2c_cmd_read (cmd, reg_data + 3, I2C_CONTINUE);
  i2c_cmd_read (cmd, reg_data + 4, I2C_CONTINUE);
最後はストップ条件を発行。
  i2c_cmd_read (cmd, reg_data + 5, I2C_STOP);
}

これでステートマシンができあがるので、使う時にこれをi2c_executeします。
I2Cモジュールの方は、

void
i2c0_init ()
{
  // I2C0
電源を入れて、クロックを供給します。
  mcu_peripheral_power (PCONP_PCI2C0, TRUE);
  mcu_peripheral_clock (PCLK_SPI, CCLK4);	//72/4=18MHz
  // Set pin to I2C function.
ピンは多重化されているので、I2Cモードにします。
  mcu_pin_select (0, 27, 1); //SDA0
  mcu_pin_select (0, 28, 1); //SCL0
400KHzになるように設定。
  // Setting the I2C data rate and duty cycle.
  // 18MHz / (22+23) = 400KHz
  *I2C_SCLH (0) = 22;
  *I2C_SCLL (0) = 23;
以前の状態をクリアします。ここでAA(Assert Acknowledge)もクリアしている
ので、このモジュールはマスタです。AAをクリアしなければ、他のマスタから
のスレーブアドレスや、一般呼出しに答えることができるのでスレーブになれ
る。
  // Disable I2C module with clear pending status.
  *I2C_CONCLR (0) = CONCLR_VALID_BITS;
割込みがとれるようにして
  // Interrupt Enable
  *VICIntSelect &= ~VIC_I2C0;	// IRQ
  *VICIntEnable |= VIC_I2C0;
I2Cを動かします。今回マスタ送信/受信しか使わないので、自分自身のスレーブ
アドレスは設定しません。(設定しても使われない)
  // Enable I2C interface.
  *I2C_CONSET (0) = CONSET_I2EN;
  udelay (100);
}

void
i2c0_fini ()
{

  // Disable I2C module.
  *I2C_CONCLR (0) = CONCLR_VALID_BITS;
  // Power down.
  mcu_peripheral_power (PCONP_PCI2C0, FALSE);
}

void
i2c_cmd_set_opt_func (struct i2c_state *ctx, enum i2c_control_option opt)
{

  switch (opt)
    {
    case I2C_CONTINUE:
      ctx->opt_func = __i2c_continue;
      break;
    case I2C_START:
      ctx->opt_func = __i2c_start;
      break;
    case I2C_STOP:
      ctx->opt_func = __i2c_stop;
      break;
    }
}

void
__i2c_start ()
{
  DPRINTF ("\n");
スタート条件を設定
  *I2C_CONSET (0) = CONSET_STA;
}

void
__i2c_stop ()
{
  DPRINTF ("\n");
ストップ条件を設定
  *I2C_CONSET (0) = CONSET_STO;
}

void
__i2c_continue ()
{
  DPRINTF ("\n");
  // Nothing to do.
}


void
i2c_cmd_slave_addr (struct i2c_command *cmd, uint8_t slave_addr, uint8_t dir)
{
  struct i2c_state *state = cmd->state + cmd->current++;
  cmd->n_command = cmd->current;
  assert (state);
通信するスレーブアドレスと、通信方向を設定。(dirは読み込みが1です)
  state->func = __i2c_slave_addr;
  state->data = (slave_addr << 1) | dir;
}

これは実際に割込みハンドラから呼ばれるルーチン。
bool
__i2c_slave_addr (struct i2c_command *cmd, uint32_t status)
{
  struct i2c_state *state = cmd->state + cmd->current++;
これが呼ばれていい条件はスタート条件と、反復スタート条件が発行し終った
時だけ。ここでターゲットとするスレーブと通信方向を送信します。
  if (!(status == 0x08 ||	// START condition.
	status == 0x10))	// Repeated START condition.
    return FALSE;

  DPRINTF ("slave=%x dir=%x\n", state->data >> 1, state->data & 1);
  // Set Slave address and transfer direction.
  *I2C_DAT (0) = state->data;

  return TRUE;
}

void
i2c_cmd_write (struct i2c_command *cmd, uint8_t data,
	       enum i2c_control_option opt)
{
  struct i2c_state *state = cmd->state + cmd->current++;
  cmd->n_command = cmd->current;
  assert (state);

  state->func = __i2c_write;
  state->option = opt;
  state->data = data;
  i2c_cmd_set_opt_func (state, opt);
}

bool
__i2c_write (struct i2c_command *cmd, uint32_t status)
{
  struct i2c_state *state = cmd->state + cmd->current;
これが呼ばれていいのは、スレーブと通信方向を送信して、そのスレーブから
ACKがやってきた時(0x18)と、前回の送信が終了してスレーブからACKが返って
きた時だけ。
  if (!(status == 0x18 ||	// after SLA+W's ACK.
	status == 0x28))		// previous data transfer's ACK.
    return FALSE;

  if (status == 0x18)
    {
ここでスタート条件をクリアします。(しないと延々とスタート条件を発行することに
なるので反復スタート条件終了の0x10での割込みが延々と入る)
      // Clear Start condition.
      *I2C_CONCLR (0) = CONCLR_STAC;
書き込みの場合、ここで最初のデータを送信できます。
      *I2C_DAT (0) = state->data;
送信終了後の割込みをハンドルするためにステートは次に進みません。
      // don't increment cmd->current. wait until next ACK
      DPRINTF ("data=%x\n", state->data);
    }
  else
    {
これは何らかのデータをスレーブに送信して、スレーブからACKが戻ってきた時。
      cmd->current++;
データ送信が継続送信であったなら、次のデータを送信します。
      if (state->opt_func == __i2c_continue)	// previous transmit.
	{
	  *I2C_DAT (0) = cmd->state[cmd->current].data;
	  DPRINTF ("data=%x\n", cmd->state[cmd->current].data);
	}
      else
	{
もう次のデータがないのであれば、反復スタート条件、あるいはストップ条件を
発行します。
	  state->opt_func ();	// start or stop or continue.
	}
    }

  return TRUE;
}

void
i2c_cmd_read (struct i2c_command *cmd, uint8_t *buffer,
	      enum i2c_control_option opt)
{
  struct i2c_state *state = cmd->state + cmd->current++;
  cmd->n_command = cmd->current;
  assert (state);

  state->func = __i2c_read;
  state->buffer = buffer;
  i2c_cmd_set_opt_func (state, opt);
}

読み込みの際は、データ読み込み終了後、ACKかNOT ACKをマスタからスレーブに
返すことで、転送の終了/継続を伝える必要がある。
void
__i2c_issue_ack (struct i2c_command *cmd)
{
  if (cmd->state[cmd->current].opt_func == __i2c_continue)
    *I2C_CONSET (0) = CONSET_AA;	// next ACK
  else
    *I2C_CONCLR (0) = CONCLR_AAC;	// next NOT ACK
ACKであれば、スレーブはさらにデータを送ってくるし、NOT ACKであれば
送信終了と理解して、データは送ってこない。
}

bool
__i2c_read (struct i2c_command *cmd, uint32_t status)
{
  struct i2c_state *state = cmd->state + cmd->current;

  if (!(status == 0x40 ||	// after SLA+R's ACK
	status == 0x50 ||	// will return ACK
	status == 0x58))	// will return NOT ACK
    return FALSE;
  assert (state->buffer);

  switch (status)
    {
    case 0x40:
スレーブアドレスと通信方向がスレーブに伝わった後の割込み。スタート条件を
クリアします。読み込みの場合は、まだここでデータを読むことはできない。
読めるのはこの次の0x50の割込み。
      // Clear Start condition.
      *I2C_CONCLR (0) = CONCLR_STAC;
ここで、NOT ACKなら一切のデータは読めない。ACKにするので次にデータが来る。
      __i2c_issue_ack (cmd);
      break;

    case 0x50:	// ACK (continue)
スレーブからデータが来た。
      *state->buffer = *I2C_DAT (0);
      cmd->current++;
ここで、次のステートのACKかNOT ACKかを設定する。どちらにしても次のデー
タは来る。そのデータが来た時に0x50(ACK)か0x58(NOT ACK)になるかの選択。
つまり、データが来てからACKかNOT ACKを発行するのではない。先に用意して
おく。
      __i2c_issue_ack (cmd);
      break;
    case 0x58:	// NACK (last)
この読み込みでNOT ACKが発行されることになっている。そしてその後に反復スタート
条件か、ストップ条件を発行する。
      cmd->current++;
      *state->buffer = *I2C_DAT (0);
      state->opt_func ();
    }
  DPRINTF ("data=%x\n", *state->buffer);

  return TRUE;
}

void
i2c_status ()
{
 スタート条件は明示的にクリアしないといけないけれど、ストップ条件、ACKは
自動的にクリアされる。

  uint32_t status = *I2C_STAT (0);
  uint32_t con = *I2C_CONSET (0);
  DPRINTF ("%s %x %s %s %s\n", __FUNCTION__, status,
	   con & CONSET_STA ? "STA" : "",
	   con & CONSET_STO ? "STO" : "",
	   con & CONSET_AA ? "AA" : ""
	   );
}

void
i2c0_execute (struct i2c_command *cmd)
{
指定されたステートマシンで通信をします。これはとりあえず版。できれば寝
て、タイムアウトが欲しい。

  assert (!cmd->busy);
  cpu_status_t s = intr_suspend ();
  __protocol = cmd;
  __protocol->success = FALSE;
  __protocol->current = 0;
  __protocol->busy = TRUE;
  intr_resume (s);

  DPRINTF ("kick!\n");
  i2c_status ();
  __i2c_start ();
  while (cmd->busy)
    ;
  s = intr_suspend ();
  __protocol = NULL;
  intr_resume (s);
}

void
i2c0_intr ()
{
順々に実行していきます。
  assert (__protocol->current < __protocol->n_command);

  uint32_t status = *I2C_STAT (0);
  i2c_status ();
  struct i2c_state *state = __protocol->state + __protocol->current;
  assert (state->func);
  DPRINTF ("func=%x\n", state->func);

  if (state->func (__protocol, status))
    {
      if (__protocol->current == __protocol->n_command)
	{
	  DPRINTF ("SUCCESS\n");
	  __protocol->success = TRUE;
	  __protocol->busy = FALSE;
	}
    }
  else
    {
      // Failed.
      DPRINTF ("command failed %x\n", status);
      assert (0);
      __protocol->busy = FALSE;
      __i2c_stop ();
    }

  // Clear interrupt
  *I2C_CONCLR (0) = CONCLR_SIC;
}