/* ======================================================== 簡易I2C(内蔵ペリフェラル使用)通信 de JHL 藤田@開技 '09 Feb - ======================================================== */ #pragma sfr #pragma di #pragma ei #pragma nop #pragma inline // memcpy()をインライン展開する #include "incs.h" #include "i2c_mcu.h" // ======================================================== // SSR0n #define bit_TSF0 6 // SIR0n #define PECT0 ( 1 << 1 ) // SSR0n #define PEF0 ( 1 << 1 ) #define TXE0 ( 1 << 15 ) #define RXE0 ( 1 << 14 ) #define SLC02 4 #define DLS02 0 #define TSF0 ( 1 << 6 ) #define DRS ( 1 << 6 ) #define TAUS_MASK 0b0000101100001011; // ======================================================== 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; // 一文字リードの時はデータを返す。 // ステータスが必要ならこっちを呼んで /* ======================================================== スレーブからの 『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_NAK ); } // レジスタアドレスの送信 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, u8* 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; if( iic_mcu_call_slave( slave ) != 0 ){ iic_mcu_busy = 0; EI(); return( ERR_NAK ); } 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を使用しない // // レジスタアドレスの送信 IICIF10 = 0; 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の転送は終わってません ======================================================== */ __interrupt void int_dma1(){ u8 hoge; hoge = IICIF10; DMAMK1 = 1; DEN1 = 0; IICMK10 = 0; while(( SSR02 & TSF0 ) != 0 ){ NOP(); } // 最後のバイト転送後、I2C割り込みが発生する } /* ======================================================== IIC MCUのバイト送出完了割り込み ======================================================== */ __interrupt void int_iic10(){ if( iic_mcu_wo_dma ){ // DMA使用せず、転送途中 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; // ISR中で外の関数を呼ぶのは都合が悪いので展開 // iic_mcu_send_sp(); { ST0 = 0x0004; SOE0 = 0; // 受信の時はもっと前に「も」設定してる。(NACK出力) SO0 = 0x0000 | TAUS_MASK; // SCL NOP(); NOP(); NOP(); NOP(); SO0 = 0x0400 | TAUS_MASK; // SCL NOP(); NOP(); NOP(); NOP(); 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 ) != 0 ){ iic_mcu_send_sp(); return( ERR_NAK ); // 指定のスレーブがいない / busy } return( ERR_SUCCESS ); } /* ======================================================== ほんとに1バイト書くのみ 書き終わるまで帰りません ======================================================== */ static err iic_mcu_send_a_byte( u8 dat ){ 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 NOP(); NOP(); NOP(); NOP(); 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 NOP(); NOP(); NOP(); NOP(); SOE0 &= ~0x0004; // ( SCL = H ), SDA -> L NOP(); NOP(); NOP(); NOP(); iic_mcu_send_st(); } /* ======================================================== ストップコンディション発行 この前に、「最後のバイトの送受信」の時に前準備が必要です。 ======================================================== */ static void iic_mcu_send_sp(){ ST0 = 0x0004; SOE0 = 0; // 受信の時はもっと前に「も」設定してる。(NACK出力) SO0 = 0x0000 | TAUS_MASK; // SCL NOP(); NOP(); NOP(); NOP(); SO0 = 0x0400 | TAUS_MASK; // SCL NOP(); NOP(); NOP(); NOP(); SO0 = 0x0404 | TAUS_MASK; } /* ======================================================== バスのリセット (ストップコンディションが出せそうだったらすかさず出す。 ======================================================== */ void iic2m_bus_reset(){ /* u8 count; for( count = 19; count != 0; count-- ){ iics_sda_H; iics_scl_H; PM1.1 = 1; // SDA if( iics_sda != 0 ){ PM1.1 = 0; iic_mcu_send_sp; return; } PM1.1 = 0; iics_scl_L; } return; */ } /* ======================================================== ペリフェラルモジュールの初期化 ======================================================== */ void iic_mcu_start(){ I2C_PU = 1; SAU0EN = 1; NOP(); // 4clkあける NOP(); NOP(); NOP(); SPS0 = 0x0000; // シリアルユニットのクロック0。(8M/2)/1 SMR02 = 0 << 15 | 0 << 14 | 0 << 7 | 0 << 5 | 1 << 4 | 1 << 2; // I2Cとそのクロックなど設定 SDR02 = 5 << 9; // ボーレート設定 (8M/2)/1/(x+1)/2 SO0 = 0x0404 | TAUS_MASK; // 最初はHH iic_mcu_busy = 0; iic_mcu_wo_dma = 0; 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; }