/* ======================================================== 簡易I2C(内蔵ペリフェラル使用)通信 de JHL 藤田@開技 '09 Feb - $Id$ ======================================================== */ #ifndef _WIN32 #pragma sfr #pragma di #pragma ei #pragma nop #pragma inline // memcpy()をインライン展開する(の方が小さい!) #endif #include "incs_loader.h" #include "i2c_mcu.h" #include "loader.h" #include "util_funcs.h" #include "i2c_mcu_sub.h" // ======================================================== // レジスタのビット名 // プリフィックスbだが、一部のビット名がレジスタ名にかぶるため... // SMR0n #define bCKS0 ( 1 << 15 ) #define bCCS0 ( 1 << 14 ) #define bSTS0 ( 1 << 8 ) #define bSIS0 ( 1 << 6 ) #define bMD0n2 ( 1 << 2 ) #define bMD0n1 ( 1 << 1 ) #define bMD0n0 ( 1 << 0 ) #define bSMR0n_FIXEDBIT ( 1 << 5 ) // SSR0n #define bit_TSF0 6 #define PEF0 ( 1 << 1 ) // SIR0n #define PECT0 ( 1 << 1 ) // SCR0n #define TXE0 ( 1 << 15 ) #define RXE0 ( 1 << 14 ) #define SLC02 4 #define DLS02 0 #define TSF0 ( 1 << 6 ) // SOn #define TAUS_MASK 0x0B0B; // DMCn #define DRS ( 1 << 6 ) // ======================================================== static void iic_mcu_send_st( ); // *subからしか呼ばない void iic_mcu_send_re_st( ); void iic_mcu_send_sp( ); i2c_err iic_mcu_send_a_byte( u8 ); i2c_err iic_mcu_call_slave( u8 slave ); // ======================================================== bit iic_mcu_wo_dma; volatile bit iic_mcu_busy; static volatile bit iic_mcu_initialized; static u8 iic_send_work[4]; static u8 *p_iic_send_wo_dma_dat; static u8 iic_send_wo_dma_len; // データエラー(ROHM製加速度センサ)時リトライのため /// とりあえず、DMA使用マルチバイトライト の時にしか機能しない static u8 last_slave, last_reg_adrs, last_size; i2c_err iic_mcu_result; // 一文字リードの時はデータを返す。 #ifdef i2c_timeout_test extern bit i2c_mcu_time_out_error; #endif /********************************************//** ***********************************************/ void nop8() { // ここに来る call に3clk, return に 6clk } /********************************************//** 他の通信が終わるのを待つ。 タイムアウト有り ***********************************************/ static i2c_err iic_mcu_wait_free() { u16 tot = 0; iic_mcu_start( ); while( 1 ) { DI_wt_chk(); if( !iic_mcu_busy ) { iic_mcu_busy = true; EI(); break; } EI(); if( ++tot == 0 ) { #ifdef i2c_timeout_test i2c_mcu_time_out_error = true; #endif return( I2C_ERR_TIMEOUT ); } } return( I2C_ERR_OK ); } /********************************************//** スレーブからの 『1文字』 リード 返値がデータそのものです。 エラーコードは iic_mcu_result に入っています ***********************************************/ u8 iic_mcu_read_a_byte( u8 SLA, u8 adrs ) { u8 dat; iic_mcu_result = iic_mcu_read( SLA, adrs, 1, &dat ); if( iic_mcu_result != I2C_ERR_OK ) { dat = 0xff; } return ( dat ); } /********************************************//** スレーブからのリード 【注】 スレーブがウェイトコンディションを出すことは禁止です。 その場合でもエラー検出などできません ***********************************************/ i2c_err iic_mcu_read( u8 slave, u8 adrs, u8 len, u8 * dest ) { #if 1 if( iic_mcu_wait_free() != I2C_ERR_OK ) { // (タイムアウト) return( I2C_ERR_TIMEOUT ); } #else // 使用中なら帰る #endif // スタートコンディションとスレーブの呼び出し、レジスタアドレスの送信 if( iic_mcu_call_slave( slave ) != 0 ) { iic_mcu_busy = false; return ( I2C_ERR_NOSLAVE ); } // レジスタアドレスの送信 iic_mcu_send_a_byte( adrs ); // 終わるまで帰ってこない // 絶対にNAKが帰ってこない前提 // データ受信 // iic_mcu_send_re_st( ); // リスタートコンディション iic_mcu_send_a_byte( slave | 0x01 ); // 送信完了まで戻ってきません。 // データ受信 ST0 = 0x0004; // 受信モードに設定を変えるのでロジック停止 SCR02 = RXE0 | 1 << SLC02 | 7 << DLS02; // 受信設定 SS0 = 0x0004; // 通信待機 do { if( len == 1 ) { SOE0 = 0x0000; // 最後のNAK } IICIF10 = 0; SIO10 = 0xFF; // ダミーデータを書くと受信開始 while( IICIF10 == 0 ) {;} // 受信完了待ち *dest = SIO10; dest++; len--; } while( len != 0 ); if( slave == IIC_SLA_CODEC ) { codec_dummy_write(); } iic_mcu_send_sp( ); IICIF10 = 0; iic_mcu_busy = false; return ( I2C_ERR_OK ); } /********************************************//** スレーブへ 『1バイト』 ライト 前の転送が終わるのを待って、ライトします。 返値 iic_mcu_write に同じ ***********************************************/ i2c_err iic_mcu_write_a_byte( u8 SLA, u8 adrs, u8 dat ) { // 1文字の時はDMAとか起動しないでさっさと終わらせる if( iic_mcu_wait_free() != I2C_ERR_OK ) { return( I2C_ERR_TIMEOUT ); } // スタートコンディションとスレーブの呼び出し... IICMK10 = 1; if( iic_mcu_call_slave( SLA ) != I2C_ERR_OK ) { iic_mcu_busy = false; return( I2C_ERR_NOSLAVE ); } iic_mcu_send_a_byte( adrs ); iic_mcu_send_a_byte( dat ); iic_mcu_send_sp( ); iic_mcu_busy = false; return ( I2C_ERR_OK ); } /********************************************//** スレーブへライト レジスタ adrs を先頭に、 *strから len文字書きます。 【注】 スレーブがウェイトコンディションを出すことは禁止です。 その場合でもエラー検出などできません DMA1を使用します。 ***********************************************/ i2c_err iic_mcu_write( u8 slave, u8 adrs, u8 len, void * src ) { if( iic_mcu_wait_free() != I2C_ERR_OK ) { return( I2C_ERR_TIMEOUT ); } #if 0 // rengeが真のマルチタスクになった暁には // 使用中なら帰る #endif // スタートコンディションとスレーブの呼び出し... IICMK10 = 1; IICIF10 = 0; if( iic_mcu_call_slave( slave ) != I2C_ERR_OK ) { iic_mcu_busy = false; return ( I2C_ERR_NOSLAVE ); } IICIF10 = 0; if( !iic_mcu_wo_dma ) { // DMAを使用する(通常)// // レジスタアドレスを送り、データの準備 memcpy( iic_send_work, src, 4 ); //バッファとして4バイトしか用意して無いため。 // リトライ時のため last_slave = slave; last_reg_adrs = adrs; last_size = len; // DMAセット while( DST1 ) {;} DEN1 = 1; DSA1 = (u8)( &SIO10 ); DRA1 = (u16)( &iic_send_work[0] ); DBC1 = len; DMC1 = DRS | 8; // RAM -> SFR, 8bit, IRQ, IIC10 DMAIF1 = 0; DMAMK1 = 0; DST1 = 1; // DEN1 = 1から2clk以上開け SIO10 = adrs; // 書きっぱなし! 割り込みが発生してDMAスタート // 残りは割り込みルーチン内で } else { // DMAを使用しない // // レジスタアドレスの送信 IICMK10 = 0; SIO10 = adrs; iic_send_wo_dma_len = len; p_iic_send_wo_dma_dat = (u8*)src; // 残りは割り込みルーチン内で } return ( I2C_ERR_OK ); } /********************************************//** DMA1転送終了割り込み IIC_mcu の送信完了コールバック関数のようなもの 注:DMA転送が終わっただけで、I2Cの転送は終わってません   割り込み中などで、DMA1の処理が遅延した場合、 IIC10の割り込みの準備ができずに、割り込みを発生させられなくなる 恐れがあります。また、回避方法も特にありません。  そのため、DMA仕様の差異は、最後のバイトは送信完了を フラグのポーリングで確認します。 ***********************************************/ __interrupt void int_dma1( ) { static bit in_retry; EI(); // 最後のバイトの送信完了待ち while( ( SSR02L & TSF0 ) != 0 ) { u16 i = 0; if( ++i == 0 ) // タイムアウト? { break; } } // ストップコンディション発行 // iic_mcu_send_sp(); // ISR中で外の関数を呼ぶのは都合が悪い(汎用レジスタ待避が発生する)ので展開 { ST0 = 0x0004; SOE0 = 0; // 受信の時はもっと前に「も」設定してる。(NACK出力) SO0 = 0x0000 | TAUS_MASK; // SCL nop8(); //. もう何も怖くない。外の関数呼ぶ /* NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); */ SO0 = 0x0400 | TAUS_MASK; // SCL nop8(); /* NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); */ SO0 = 0x0404 | TAUS_MASK; } IICMK10 = 1; // データの途中で NAK だったら、一度だけリトライする。 /// 手抜き実装 if( SIR02 != 0 ) { SIR02 = SSR02; if( !in_retry ) { in_retry = true; IICIF10 = 0; iic_mcu_call_slave( last_slave ); // ここでNAKは今ノンケア while( DST1 ){;} DRA1 = (u16)( &iic_send_work[0] ); // 自動インクリメントされてしまっているので再セット DBC1 = last_size; // 自動デクリメントされてしまっているので再セット // ほかの設定は前回のまま DMAIF1 = 0; DMAMK1 = 0; DST1 = 1; SIO10 = last_reg_adrs; // 書きっぱなし! 割り込みが発生してDMAスタート return; // おしまい } else { // エラー二度目。もう知らない dbg_nop(); // そのまま終了処理へ } } // 正常終了 in_retry = false; DMAMK1 = 1; DEN1 = 0; iic_mcu_busy = false; } /********************************************//** IIC MCUのバイト送出完了割り込み ※DMA使用時は使用されません。  他の割り込み処理中でDMAの割り込みにすぐ飛ばない場合、  IIC割り込みのセットが間に合わず困ることがあります。 ***********************************************/ __interrupt void int_iic10( ) { EI(); if( iic_send_wo_dma_len != 0 ) // まだ送信しきっていない? { SIO10 = *p_iic_send_wo_dma_dat; p_iic_send_wo_dma_dat ++; iic_send_wo_dma_len --; return; // おしまい // } // 最後のバイト送信完了、ストップコンディション発行 IICMK10 = 1; // iic_mcu_send_sp(); // ISR中で外の関数を呼ぶのは都合が悪い(汎用レジスタ待避が発生する)ので展開 { ST0 = 0x0004; SOE0 = 0; // 受信の時はもっと前に「も」設定してる。(NACK出力) SO0 = 0x0000 | TAUS_MASK; // clear SCL NOP(); NOP(); NOP(); NOP(); // NOP8等も呼んではだめ NOP(); NOP(); NOP(); NOP(); SO0 = 0x0400 | TAUS_MASK; // set SCL NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); SO0 = 0x0404 | TAUS_MASK; // set CSL adn SDA } iic_mcu_wo_dma = false; iic_mcu_busy = false; } /********************************************//** スレーブの呼び出し  スレーブアドレスを呼んで、ACKの確認。 ACK →                 返:I2C_ERR_OK  NACK → ストップコンディションを出す。 返:I2C_ERR_NOSLAVE ***********************************************/ i2c_err iic_mcu_call_slave( u8 slave ) { iic_mcu_send_st( ); /* // dbg if( SSR02 != 0 ) { P1.5 = P1.0 = P1.3 = 1; } */ SIR02 = SSR02; // NAKエラーのフラグクリア if( iic_mcu_send_a_byte( slave ) != I2C_ERR_OK ) { iic_mcu_send_sp( ); return ( I2C_ERR_NOSLAVE ); // 指定のスレーブがいない } return ( I2C_ERR_OK ); } /********************************************//** ほんとに1バイト書くのみ 書き終わるまで帰りません ***********************************************/ i2c_err iic_mcu_send_a_byte( u8 dat ) { IICMK10 = 1; IICIF10 = 0; SIO10 = dat; while( IICIF10 == 0 ) { // NOP( ); } // 通信中 if( SSR02 != 0 ) // 何らかエラー発生? { SIR02 = SSR02; // エラークリア return( I2C_ERR_NAK ); } return( I2C_ERR_OK ); } /********************************************//** スタートコンディションを発行 ソフトウェア制御 ***********************************************/ static void iic_mcu_send_st( ) { SO0 &= ~0x0004; // SDA nop8(); SO0 &= ~0x0400; // SCL SOE0 = 0x0004; // ハード制御へ SCR02 = TXE0 | 1 << SLC02 | 7 << DLS02; // 送信許可、データは8ビット単位 SS0 = 0x0004; // 通信待機 } /********************************************//** リスタート発行 ***********************************************/ void iic_mcu_send_re_st( ) { ST0 |= 0x0004; SO0 |= 0x0400 | TAUS_MASK; // ( SDA = H ), SCL -> H nop8(); SOE0 &= ~0x0004; // ( SCL = H ), SDA -> L nop8(); iic_mcu_send_st( ); } /********************************************//** ストップコンディション発行 この前に、「最後のバイトの送受信」の時に前準備が必要です。 ***********************************************/ void iic_mcu_send_sp( ) { ST0 = 0x0004; SOE0 = 0; // 受信の時はもっと前に「も」設定してる。(NACK出力) SO0 = 0x0000 | TAUS_MASK; // SCL nop8(); SO0 = 0x0400 | TAUS_MASK; // SCL nop8(); SO0 = 0x0404 | TAUS_MASK; } /********************************************//** ペリフェラルモジュールの初期化 ***********************************************/ void iic_mcu_start( ) { if( iic_mcu_initialized ) { return; } iic_mcu_busy = true; // DST1 = 0; I2C_PU_on(); // DEN1 = 0; // DST1 = 0 から 2clkもしくは、DSTn==0をポーリングしてから wait_ms( 1 ); // 立ち上がるのに50us位かかる SAU0EN = 1; nop8(); SPS0 = 0x0000; // シリアルユニットのクロック0。(8M/2)/1 SMR02 = bSMR0n_FIXEDBIT | bMD0n2; // 簡易I2Cに設定 SDR02 = 10 << 9; // ボーレート設定 8M/1/(x+1)/2 SO0 = 0x0404 | TAUS_MASK; // 最初はHH iic_mcu_wo_dma = false; // バスのリセット { IICIF10 = 0; IICMK10 = 1; iic_mcu_send_st(); SIO10 = 0xFF; while( IICIF10 == 0 ){;} // 通信中 iic_mcu_send_sp(); SIR02 = SSR02; } iic_mcu_busy = false; iic_mcu_initialized = true; } /********************************************//** モジュールの停止 再度使うときは初期化が必要 ***********************************************/ void iic_mcu_stop( ) { while( iic_mcu_busy ) {;} // DMA動作中はもう少し待つ iic_mcu_send_re_st( ); // SCL,SDAをLLにする I2C_PU_off(); SAU0EN = 0; iic_mcu_initialized = false; }