フォト
2017年9月
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
無料ブログはココログ

« LPC810始めました | トップページ | IchigoJamにプログラムをアップロード/ダウンロードするGUIツールを作成しました »

2015年4月 5日 (日)

LPC810でI2Cでフリーズする

ここ数日、8ピンDIP マイコン LPC810を使って試行錯誤しています。

作業効率を上げるため、実験するボードを作成しました。
まあ、秋月電子のLPC810の商品情報の
「ボクのLPC810工作ノート発売決定!(株式会社ラトルズ)」の実験ボートを
まねて作っただけですけどね(この書籍は早速、予約注文しました)。

Dscn3713

開発は、LPCXpresso v7.6.2 を使っています。
トラ技2014年2月号と、開発ツールのサンプルライブラリソースを利用して
タイマー、Lチカ、シリアル通信(UART)、I2Cとなんとか理解して動かせるようになりました。

ただし、I2Cの振舞が今一納得がいかず悩んでいました。
LPC810でI2Cでフリーズする
って問題にぶち当たりました。

正常にデバイスが動作している場合は問題ないのですが、
バス上に指定したアドレスのデバイスがなかったり、デバイスの調子が悪く
ACKを返さない場合、フリーズするようです。

LPCXpressoのサンプルライブリを使って発生しました。

トラ技2014年2月号のLPC810でI2Cのサンプルソースを
使っても同じ症状が発生しました。

まあ、正常時は問題ないのですが、
さすがに、バス上に存在しないアドレスを指定するとフリーズするのは
無視できないですね。

色々と調べて、次の記事に答えを見つけました。
LPC812 i2c freezes http://www.lpcware.com/content/forum/lpc812-i2c-freezes

問題発生の原因は、
From what I can see, the I2C_MstSend function doesn't look for NACKs.
It checks for errors and then attempts retransmits, but for NACKs it loops busily,
and will never exit. Here's some code that might help you to understand how
nacks are detected in the I2C.


LPCXpressoのI2CサンプルライブリのMstSendがNACKを見ていないとのこと。
エラーは検出して再送信を繰り返しているようですが、NACKの場合のは
処理を終了する必要があります。

このため、存在しないデバイスのI2Cアドレスを指定すると
無限ループになってしまうようです。

とりあえず、この記事内容の対応方法を参考にしてソースを修正しました。

//void I2C_MstSend( LPC_I2C_TypeDef *I2Cx, uint32_t addr, uint8_t *tx, uint32_t Length )
int I2C_MstSend( LPC_I2C_TypeDef *I2Cx, uint32_t addr, uint8_t *tx, uint32_t Length )
{
  uint32_t i;

  I2Cx->MSTDAT = addr;
  I2Cx->MSTCTL = CTL_MSTSTART;

  // <<-- 2015/04/05 add by Tamakichi,
  // 参考:http://www.lpcware.com/content/forum/lpc812-i2c-freezes
  while(!(I2Cx->STAT & STAT_MSTPEND)); // wait for master pending
  if((I2Cx->STAT & MASTER_STATE_MASK) == STAT_MSTNACKADDR) // check for nack on address
	  return -1; // slave nacked address
  // -->>

同じように3か所修正しました。
また、上記のほかに、I2C_MstReceive、I2C_MstSendRcvも修正しました。

これで試してみると、フリーズが発生しなくなりました。


2015/04/15 追記
作成した実験ボートですが、重大な欠陥がありました。
FlashMagicでの書き込みで自動リセットを行うためにUARTのDTR,RTSの信号を
使っているのですが、マイコンでシリアル通信を使う機能を実装し、
TeraTermでテストを行おうとすると、TeraTermでのデータの送受信が出来ません。
TeraTermがDTR,RTSの処理をちゃんとしていないようで、マイコンがリセット状態
のままになってしまいます。

この現象、当初は自作したUARTライブラリのミスと思ってちょっとはまりました。

利用したライブラリのlpc800_driver_libですが、
I2Cライブラリはフリーズする問題を含め、エラー発生時に無限ループ箇所があり、
また再送信で再起呼び出し等でスタックを消費する等、色々と問題がありそうなので、
使うのは辞めました。
自分が理解できる範囲で大幅修正(ほぼ自作)しちゃいました。

lpc800_driver_libのI2Cライブラリでは、割り込み処理で状態をチェックしている作りですが、
直接レジスタ見てループして待つようにしました。

また、アイドル処理や、タイムアウト処理が今一わからないので、
自分が分かる単純なものに置き換えました。

こんな感じに大幅修正(ほぼ自作)です。

#include "LPC8xx.h"
#include "i2c.h"
#include "uart.h"
#include "mrt.h"
#include "lpc_types.h"

// I2C送受信レディ待ち
// 500msecでタイムアウト
bool i2c_waitReady() {
	uint32_t cnt = mrt_read_counter()+500;
	while(!(LPC_I2C->STAT & STAT_MSTPEND)) {
		if (mrt_read_counter() > cnt) {
			return false;
		}
	}
	return true;
}

// I2Cアドレス送信
int i2c_write_address(uint32_t addr) {
  // レディ待ち
  if (i2c_waitReady() == false) {
	  return I2C_ERR_TIMEOUT;
  }

  // START + I2Cアドレス送信
  LPC_I2C->MSTDAT = addr;
  LPC_I2C->MSTCTL = CTL_MSTSTART;
  if (i2c_waitReady() == false) {
	  // タイムアウト(処理を終了する)
	  LPC_I2C->MSTCTL = CTL_MSTSTOP;
	  return I2C_ERR_TIMEOUT;
  }

  // スレーブからのアドレス送信 ACK/NACKのチェック
  if((LPC_I2C->STAT & MASTER_STATE_MASK) == STAT_MSTNACKADDR) {
	  return I2C_ERR_ADDRSND; // (CTL_MSTSTOPは自動送信される)
  }
  return 0;
}

// 1バイト送信
int i2c_write_byte(uint8_t b) {

	// 送信可能か?
	if(!(LPC_I2C->STAT & STAT_MSTTX)) {
			LPC_I2C->MSTCTL = CTL_MSTSTOP;
			return I2C_ERR_DATASND;
	 }

	// 1バイト送信
	LPC_I2C->MSTDAT = b;
	LPC_I2C->MSTCTL = CTL_MSTCONTINUE; // 処理を継続

	if (i2c_waitReady() == false) {
	  // タイムアウト(処理を終了する)
	  LPC_I2C->MSTCTL = CTL_MSTSTOP;
	  return I2C_ERR_TIMEOUT;
	}

	// スレーブからのデータ送信ACK/NACKのチェック
	if((LPC_I2C->STAT & MASTER_STATE_MASK) == STAT_MSTNACKTX)
		return I2C_ERR_DATASND; // (CTL_MSTSTOPは自動送信される)

	return 0;
}

// 1バイト受信
int i2c_read_byte(bool flgrpt) {
	int rc;

	// データ受信済チェック
	if (i2c_waitReady() == false) {
	  // タイムアウト(処理を終了する)
	  LPC_I2C->MSTCTL = CTL_MSTSTOP;
	  return I2C_ERR_TIMEOUT;
	}

	// データ受信可能?
	if(!(LPC_I2C->STAT & STAT_MSTRX)) {
		LPC_I2C->MSTCTL = CTL_MSTSTOP;
		return I2C_ERR_DATARCV;
	}

	// データ受信
	rc = LPC_I2C->MSTDAT;

	// 継続受信?
	if (flgrpt) {
		LPC_I2C->MSTCTL = CTL_MSTCONTINUE;
		if (i2c_waitReady() == false) {
		  LPC_I2C->MSTCTL = CTL_MSTSTOP;
		  return I2C_ERR_TIMEOUT;
		}
	}
	return rc;
}

//
// I2Cマルチバイトデータ送信
//   addr  : i2cスレーブアドレス(8ビット)
//   tx    : 送信データ格納アドレス
//   Length: 送信データサイズ
// 戻り値   0:正常終了   0以外:異常終了
//
int i2c_msend(uint32_t addr, uint8_t *tx, uint32_t Length ) {
  uint32_t i;
  int rc;

  // アドレス送信(START+I2Cスレーブアドレス送信、ACK/NACK受信、タイムアウト)
  if ( (rc = i2c_write_address(addr)) )
	  return rc;

  // データ送信(データ送信、ACK/NACK受信、タイムアウト)
  for ( i = 0; i < Length; i++ ) {
	  if ( (rc = i2c_write_byte(tx[i])) ) {
		  return rc;
	  }
  }

  // 処理の終了(ストップコンディション)
  LPC_I2C->MSTCTL = CTL_MSTSTOP;
  if (i2c_waitReady() == false) {
	  return I2C_ERR_TIMEOUT;
  }
  return 0;
}

// コマンド送信(2バイトデータ送信)
uint8_t i2c_send(uint8_t i2c_addr, uint8_t ctrl , uint8_t data ) {
	uint8_t sndbuf[2];
	sndbuf[0] = ctrl;
	sndbuf[1] = data;
	return i2c_msend(i2c_addr, sndbuf, 2 );
}

//
// I2Cマルチバイトデータ受信
//  addr : i2cスレーブアドレス
//  rx   : 受信データ格納アドレス
// Length: 受信データ長さ
// 戻り値   0:正常終了   0以外:異常終了
// 補足 アドレスにはR/Wビットをつけないこと。関数内部で設定する
//
int i2c_mreceive(uint32_t addr, uint8_t *rx, uint32_t rxlen ) {
  uint32_t i;
  int rc;

  // アドレス送信(START+I2Cスレーブアドレス送信、ACK/NACK受信、タイムアウト)
  if ( (rc = i2c_write_address(addr|RD_BIT)) )
	  return rc;

  // データ受信(受信、ACK/NACK送信)
  for ( i = 0; i < rxlen; i++ ) {
	  if ( (rc = i2c_read_byte(i != rxlen -1)) < 0 )
		  return rc;
	  *rx++ = (uint8_t)rc;
  }

  // 処理の終了(ストップコンディション)
  LPC_I2C->MSTCTL = CTL_MSTSTOP;
  if (i2c_waitReady() == false) {
	  return I2C_ERR_TIMEOUT;
  }

  return 0;
}

//
// I2Cマルチバイトデー送受信
//  addr : i2cスレーブアドレス
//  tx   : 送信データ格納アドレス
//  txlen : 送信データサイズ
//  rx   : 受信データ格納アドレス
//  rxlen: 受信データ長さ
// 戻り値   0:正常終了   0以外:異常終了
// 補足 アドレスにはR/Wビットをつけないこと。関数内部で設定する
//
int i2c_msendRcv(uint32_t addr, uint8_t *tx, uint32_t txlen, uint8_t *rx, uint32_t rxlen ) {

  uint32_t i;
  int rc;

  // アドレス送信(START+I2Cスレーブアドレス送信、ACK/NACK受信、タイムアウト)
  if ( (rc = i2c_write_address(addr)) )
	  return rc;

  // データ送信(データ送信、ACK/NACK受信、タイムアウト)
  for ( i = 0; i < txlen; i++ ) {
	  if ( (rc = i2c_write_byte(tx[i])) ) {
		  return rc;
	  }
  }

  // アドレス送信(START+I2Cスレーブアドレス送信、ACK/NACK受信、タイムアウト)
  if ( (rc = i2c_write_address(addr|RD_BIT)) )
	  return rc;

  // データ受信(受信、ACK/NACK送信)
  for ( i = 0; i < rxlen; i++ ) {
	  if ( (rc = i2c_read_byte(i != rxlen -1)) < 0 )
		  return rc;
	  *rx++ = (uint8_t)rc;
  }

  // 処理の終了(ストップコンディション)
  LPC_I2C->MSTCTL = CTL_MSTSTOP;
  if (i2c_waitReady() == false) {
	  return I2C_ERR_TIMEOUT;
  }
  return 0;
}

//
// I2C初期化
//  LPC810用(呼び出し前に スイッチマトリックスで PIN02:SDA , PIN03:SCLとすること)
//  通信速度 100kbps,割り込みは利用していない
//
void i2c_init() {
	// オープンドレイン設定(LPC810のPIN2,PIN3をI2Cに設定)
	LPC_IOCON->PIO0_2 |= (0x1<<10);
	LPC_IOCON->PIO0_3 |= (0x1<<10);

	// クロック供給開始
	LPC_SYSCON->SYSAHBCLKCTRL |= (1<<5);

	// I2Cリセット
	LPC_SYSCON->PRESETCTRL &= ~(0x1<<6);
	LPC_SYSCON->PRESETCTRL |= (0x1<<6);

	// 条件設定(割り込みは使わない)
	LPC_I2C->DIV = I2C_SMODE_PRE_DIV;
	LPC_I2C->CFG &= ~(CFG_MSTENA);
	LPC_I2C->MSTTIME = TIM_MSTSCLLOW(0x00) | TIM_MSTSCLHIGH(0x00);
	NVIC_DisableIRQ(I2C_IRQn);
	LPC_I2C->CFG |= CFG_MSTENA;
}

« LPC810始めました | トップページ | IchigoJamにプログラムをアップロード/ダウンロードするGUIツールを作成しました »

ARM」カテゴリの記事

LPC810」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/571408/61393750

この記事へのトラックバック一覧です: LPC810でI2Cでフリーズする:

« LPC810始めました | トップページ | IchigoJamにプログラムをアップロード/ダウンロードするGUIツールを作成しました »