/* ======================================================== 簡易I2C(内蔵ペリフェラル使用)通信 de JHL 藤田@開技 '09 Feb - ======================================================== */ #pragma sfr #pragma di #pragma ei #pragma nop #pragma inline // memcpy()をインライン展開する #include "incs.h" #include "i2c_mcu.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 0b0000101100001011; // DMCn #define DRS ( 1 << 6 ) // ======================================================== static void iic_mcu_send_st( ); static void iic_mcu_send_re_st( ); static void iic_mcu_send_sp( ); static err iic_mcu_send_a_byte( u8 ); static err iic_mcu_call_slave( u8 slave ); // ======================================================== bit iic_mcu_wo_dma; volatile bit iic_mcu_busy; volatile bit iic_mcu_initialized; u8 iic_send_work[4]; u8 *p_iic_send_wo_dma_dat; u8 iic_send_wo_dma_len; u8 iic_mcu_bus_status; // 一文字リードの時はデータを返す。 // ステータスが必要ならこっちを呼んで void nop8() { } /* ======================================================== スレーブからの 『1文字』 リード 返値がデータそのものです。 エラー判定ができません。 ======================================================== */ u8 iic_mcu_read_a_byte( u8 SLA, u8 adrs ) { u8 dat; if( iic_mcu_initialized == 0 ) { #ifdef _debug_ iic_mcu_start( ); #else while( 1 ) { } #endif } while( iic_mcu_busy ) { NOP( ); } iic_mcu_busy = 1; iic_mcu_bus_status = ERR_OK; // スタートコンディションとスレーブの呼び出し、レジスタアドレスの送信 if( iic_mcu_call_slave( SLA ) != 0 ) { iic_mcu_bus_status = ERR_NOSLAVE; iic_mcu_busy = 0; return ( 0 ); } // レジスタアドレスの送信 iic_mcu_send_a_byte( adrs ); // 終わるまで帰ってこない // if( err != ERR_SUCCESS )〜 // データ受信 // iic_mcu_send_re_st( ); // リスタートコンディション iic_mcu_send_a_byte( SLA | 0x01 ); // 送信完了まで戻ってきません。 ST0 = 0x0004; // 受信モードに設定を変えるのでロジック停止 SCR02 = RXE0 | 1 << SLC02 | 7 << DLS02; // 受信設定 SS0 = 0x0004; // 通信待機 SOE0 = 0x0000; // 1バイト送信なので、最後のNAKを送る IICIF10 = 0; SIO10 = 0xFF; // ダミーデータを書くと受信開始 while( IICIF10 == 0 ) { // 受信完了待ち ; } dat = SIO10; iic_mcu_send_sp( ); IICIF10 = 0; // 後を濁さないこと iic_mcu_busy = 0; return ( dat ); } /* ======================================================== スレーブからのリード 0 正常終了 1 スレーブが応答しない 2 バスが誰かに占有されていてタイムアウト 3 意味不明エラー 【注】 スレーブがウェイトコンディションを出すことは禁止です。 その場合でもエラー検出などできません ======================================================== */ err iic_mcu_read( u8 slave, u8 adrs, u8 len, u8 * dest ) { //* // 使用中なら待つ if( iic_mcu_initialized == 0 ) { #ifdef _debug_ iic_mcu_start( ); #else while( 1 ) { } #endif } while( iic_mcu_busy ) { NOP( ); } /*/ // 使用中なら帰る if( iic_mcu_initialized == 0 ){ return(0x80); } if( iic_mcu_busy != 0 ){ return( 3 ); } //*/ iic_mcu_busy = 1; // スタートコンディションとスレーブの呼び出し、レジスタアドレスの送信 if( iic_mcu_call_slave( slave ) != 0 ) { iic_mcu_busy = 0; return ( ERR_NOSLAVE ); } // レジスタアドレスの送信 iic_mcu_send_a_byte( adrs ); // 終わるまで帰ってこない // if( err != ERR_SUCCESS )〜 // データ受信 // 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 ); iic_mcu_send_sp( ); IICIF10 = 0; iic_mcu_busy = 0; return ( ERR_SUCCESS ); } /* ======================================================== スレーブへ 『1バイト』 ライト 前の転送が終わるのを待って、ライトします。 返値 iic_mcu_write に同じ ======================================================== */ err iic_mcu_write_a_byte( u8 SLA, u8 adrs, u8 dat ) { if( iic_mcu_initialized == 0 ) { #ifdef _debug_ iic_mcu_start( ); #else while( 1 ) { } #endif } while( iic_mcu_busy ) { NOP( ); } iic_mcu_busy = 1; #if 0 temp = dat; return ( iic_mcu_write( SLA, adrs, 1, &temp ) ); } #else // スタートコンディションとスレーブの呼び出し... IICMK10 = 1; if( iic_mcu_call_slave( SLA ) != 0 ) { iic_mcu_busy = 0; return ( ERR_NAK ); } iic_mcu_send_a_byte( adrs ); iic_mcu_send_a_byte( dat ); iic_mcu_send_sp( ); iic_mcu_busy = 0; return ( ERR_SUCCESS ); #endif } /* ======================================================== スレーブへライト レジスタ adrs を先頭に、 *strから len文字書きます。 0 正常終了 1 スレーブが応答しない 2 バスが誰かに占有されていてタイムアウト 3 前に指示された通信がまだ終わってない 【注】 スレーブがウェイトコンディションを出すことは禁止です。 その場合でもエラー検出などできません DMA1を使用します。 ******************************************************************************/ err iic_mcu_write( u8 slave, u8 adrs, u8 len, void * src ) { //* // 使用中なら待つ if( iic_mcu_initialized == 0 ) { #ifdef _debug_ iic_mcu_start( ); #else while( 1 ) { } #endif } while( iic_mcu_busy ) { NOP( ); } /*/ // 使用中なら帰る if( iic_mcu_initialized == 0 ){ return(0x80); } if( iic_mcu_busy != 0 ){ return( 3 ); } //*/ iic_mcu_busy = 1; // スタートコンディションとスレーブの呼び出し... IICMK10 = 1; IICIF10 = 0; if( iic_mcu_call_slave( slave ) != 0 ) { iic_mcu_busy = 0; EI( ); return ( ERR_NAK ); } IICIF10 = 0; if( !iic_mcu_wo_dma ) { // DMAを使用する(通常) // レジスタアドレスを送り、データの準備 memcpy( iic_send_work, src, 4 ); //バッファとして4バイトしか用意して無いため。 // DMAセット while( DST1 ) {; } DEN1 = 1; DSA1 = ( u8 ) ( &SIO10 ); DRA1 = ( u16 ) iic_send_work; DBC1 = len; DMC1 = DRS | 8; // RAM -> SFR, 8bit, IRQ, IIC10 DMAIF1 = 0; DMAMK1 = 0; DST1 = 1; SIO10 = adrs; // 書きっぱなし! 割り込みが発生してDMAスタート // 残りは割り込みルーチン内で } else { // DMAを使用しない // // レジスタアドレスの送信 SIO10 = adrs; IICMK10 = 0; iic_send_wo_dma_len = len; p_iic_send_wo_dma_dat = src; // 残りは割り込みルーチン内で } return ( ERR_SUCCESS ); } /* ======================================================== DMA1転送終了割り込み IIC_mcu の送信完了コールバック関数のようなもの 注:DMA転送が終わっただけで、I2Cの転送は終わってません   割り込み中などで、DMA1の処理が遅延した場合、 IIC10の割り込みの準備ができずに、割り込みを発生させられなくなる 恐れがあります。また、回避方法も特にありません。  そのため、DMA仕様の差異は、最後のバイトは送信完了を フラグのポーリングで確認します。 ======================================================== */ __interrupt void int_dma1( ) { u16 i = 0; EI(); DMAMK1 = 1; DEN1 = 0; while( ( SSR02L & TSF0 ) != 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( ); #ifdef _OVERCLOCK_ NOP( ); NOP( ); NOP( ); NOP( ); NOP( ); #endif */ SO0 = 0x0400 | TAUS_MASK; // SCL nop8(); /* NOP( ); NOP( ); NOP( ); NOP( ); #ifdef _OVERCLOCK_ NOP( ); NOP( ); NOP( ); NOP( ); NOP( ); #endif */ SO0 = 0x0404 | TAUS_MASK; } IICMK10 = 1; iic_mcu_busy = 0; } /* ======================================================== 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; // SCL nop8(); /* NOP( ); NOP( ); NOP( ); NOP( ); #ifdef _OVERCLOCK_ NOP( ); NOP( ); NOP( ); NOP( ); NOP( ); #endif */ SO0 = 0x0400 | TAUS_MASK; // SCL nop8(); /* NOP( ); NOP( ); NOP( ); NOP( ); #ifdef _OVERCLOCK_ NOP( ); NOP( ); NOP( ); NOP( ); NOP( ); #endif */ SO0 = 0x0404 | TAUS_MASK; } iic_mcu_wo_dma = 0; iic_mcu_busy = 0; } /* ======================================================== スレーブの呼び出し  スレーブアドレスを呼んで、ACKの確認。 ACK →                 返:0  NACK → ストップコンディションを出す。 返:1 ======================================================== */ static err iic_mcu_call_slave( u8 slave ) { iic_mcu_send_st( ); // SIR02 = SSR02; // NAKエラーのフラグクリア if( iic_mcu_send_a_byte( slave ) != ERR_SUCCESS ) { iic_mcu_send_sp( ); return ( ERR_NAK ); // 指定のスレーブがいない / busy } return ( ERR_SUCCESS ); } /* ======================================================== ほんとに1バイト書くのみ 書き終わるまで帰りません ======================================================== */ static err iic_mcu_send_a_byte( u8 dat ) { IICMK10 = 1; IICIF10 = 0; SIO10 = dat; while( IICIF10 == 0 ) { NOP( ); } // 通信中 if( SSR02 != 0 ) { SIR02 = SSR02; return ( ERR_NAK ); } return ( ERR_SUCCESS ); } /* ======================================================== スタートコンディションを発行 ソフトウェア制御 ======================================================== */ static void iic_mcu_send_st( ) { SO0 &= ~0x0004; // SDA nop8(); /* NOP( ); NOP( ); NOP( ); NOP( ); #ifdef _OVERCLOCK_ NOP( ); NOP( ); NOP( ); NOP( ); NOP( ); #endif */ SO0 &= ~0x0400; // SCL SOE0 = 0x0004; // ハード制御へ SCR02 = TXE0 | 1 << SLC02 | 7 << DLS02; // 送信許可、データは8ビット単位 SS0 = 0x0004; // 通信待機 } /* ======================================================== リスタート発行 ======================================================== */ static void iic_mcu_send_re_st( ) { ST0 |= 0x0004; SO0 |= 0x0400 | TAUS_MASK; // ( SDA = H ), SCL -> H nop8(); /* NOP( ); NOP( ); NOP( ); NOP( ); #ifdef _OVERCLOCK_ NOP( ); NOP( ); NOP( ); NOP( ); NOP( ); #endif */ SOE0 &= ~0x0004; // ( SCL = H ), SDA -> L nop8(); /* NOP( ); NOP( ); NOP( ); NOP( ); #ifdef _OVERCLOCK_ NOP( ); NOP( ); NOP( ); NOP( ); NOP( ); #endif */ iic_mcu_send_st( ); } /* ======================================================== ストップコンディション発行 この前に、「最後のバイトの送受信」の時に前準備が必要です。 ======================================================== */ static void iic_mcu_send_sp( ) { ST0 = 0x0004; SOE0 = 0; // 受信の時はもっと前に「も」設定してる。(NACK出力) SO0 = 0x0000 | TAUS_MASK; // SCL nop8(); /* NOP( ); NOP( ); NOP( ); NOP( ); #ifdef _OVERCLOCK_ NOP( ); NOP( ); NOP( ); NOP( ); NOP( ); #endif */ SO0 = 0x0400 | TAUS_MASK; // SCL nop8(); /* NOP( ); NOP( ); NOP( ); NOP( ); #ifdef _OVERCLOCK_ NOP( ); NOP( ); NOP( ); NOP( ); NOP( ); #endif */ SO0 = 0x0404 | TAUS_MASK; } /* ======================================================== ペリフェラルモジュールの初期化 ======================================================== */ void iic_mcu_start( ) { DST1 = 0; NOP( ); // 2clkもしくは、DSTn==0をポーリング NOP( ); DEN1 = 0; I2C_PU = 1; SAU0EN = 1; nop8(); /* NOP( ); NOP( ); NOP( ); NOP( ); #ifdef _OVERCLOCK_ NOP( ); NOP( ); NOP( ); NOP( ); NOP( ); #endif */ SPS0 = 0x0000; // シリアルユニットのクロック0。(8M/2)/1 SMR02 = bSMR0n_FIXEDBIT | bMD0n2; // 簡易I2Cに設定 #ifdef _OVERCLOCK_ SDR02 = 12 << 9; // ボーレート設定 (8M/2)/1/(x+1)/2 #else SDR02 = 5 << 9; // ボーレート設定 (8M/2)/1/(x+1)/2 #endif SO0 = 0x0404 | TAUS_MASK; // 最初はHH iic_mcu_busy = 0; iic_mcu_wo_dma = 0; // バスのリセット IICIF10 = 0; IICMK10 = 1; iic_mcu_send_st(); SIO10 = 0xFF; while( IICIF10 == 0 ){} // 通信中 iic_mcu_send_sp(); SIR02 = SSR02; iic_mcu_initialized = 1; } /* ======================================================== モジュールの停止 再度使うときは初期化が必要 ======================================================== */ void iic_mcu_stop( ) { while( iic_mcu_busy ) {; } // DMA動作中はもう少し待つ iic_mcu_send_re_st( ); // SCL,SDAをLLにする I2C_PU = 0; SAU0EN = 0; iic_mcu_initialized = 0; }