I2Cは、2つの物理的な接続(共通ワイヤに加えて)で動作するバスです。 インターネットについてはかなり多くのことが書かれており、 Wikipediaにも良い記事があります。 さらに、バス操作アルゴリズムについては、 ここで非常に明確に説明されています 。 つまり、バスは2線式の同期バスです。 バスには最大127個のデバイスを同時に配置できます(デバイスアドレスは7ビットです。後でこれに戻ります)。 以下は、MKをマスターとして、デバイスをi2cバスに接続する典型的な図です。
i2cの場合、すべてのデバイス(マスターとスレーブの両方)はオープンドレイン出力を使用します。 簡単に言えば、彼らはバスを地上に引くことしかできません。 プルアップ抵抗により、高いバスレベルが提供されます。 これらの抵抗の値は、通常4.7〜10 kOhmの範囲で選択されます。 i2cは、デバイスを接続する物理回線に対して十分な感度があるため、大容量の接続(たとえば、長く細いケーブルまたはシールド付きケーブル)を使用する場合、この容量の影響により信号フロントが「ぼやけ」、バスの通常の動作に干渉する可能性があります。 プルアップ抵抗が小さいほど、この静電容量は信号エッジの応答に影響を与えませんが、i2cインターフェイスの出力トランジスタにはより多くの負荷がかかります。 これらの抵抗の値は、特定の実装ごとに選択されますが、2.2 kOhms未満にしないでください。そうしないと、バスで動作するデバイスの出力トランジスタを単純に焼き付けることができます。
バスは、SDA(データライン)とSCL(クロック信号)の2つのラインで構成されています。 マスターデバイスは 、通常はMKであるバスをクロッキングしています。 SCLが高い場合、情報はデータバスから読み取られます。 SDAは、クロック信号が低い場合にのみ変更できます。 SCLレベルが高い場合、 START信号が生成されるとSDAの信号が変化し(SCLレベルが高いとSDAの信号がHighからLowに変化します)、 STOP -SCLレベルが高いとSDAの信号はLowからHighに変化します。
それとは別に、i2cではアドレスは7ビットの数値で指定されると言われるべきです。 8-最下位ビットはデータ転送の方向を示します0-スレーブがデータを送信することを意味し、1-受信します。 。 要するに、i2cを使用するためのアルゴリズムは次のとおりです。
- SDAおよびSCLの高レベル -バスは無料で、仕事を始めることができます
- マスターはSCLを1に上げ、 SDAの状態を1から0に変更します-それをグランドに引きます-START信号が生成されます
- マスターは、方向ビットで7ビットのスレーブアドレスを送信します( SDA上のデータは 、 SCLがグランドに引き下げられると公開され、解放されるとスレーブによって読み取られます)。 スレーブが前のビットを「グラブ」する時間がない場合、 SCLをグラウンドに引き込み、データバスの状態を変更する必要がないことをマスターに明らかにします。「前のビットをまだ読み取ります。」 マスターはタイヤを手放した後、 スレーブが手放したかどうかを確認します。
- アドレスの8ビットを送信した後、マスターは9番目のクロックサイクルを生成し、データバスを解放します。 スレーブがアドレスを聞いて受け入れた場合、 SDAを地面にプッシュします。 ASK信号が形成されます-受け入れられ、すべてがOKです。 奴隷が何も理解しなかった場合、または単にそこにない場合、タイヤを押す人はいません。 マスターはタイムアウトを待ち、理解されなかったことを理解します。
- アドレスを送信した後、マスターからスレーブへの方向を設定すると(アドレスの8ビットは1)、マスターはデータをスレーブに転送します。各バイトを送信した後、スレーブからのASKの存在を確認することを忘れず、スレーブが受信した情報を処理するのを待ちます。
- マスターがスレーブからデータを受信すると、マスターは各バイトを受信した後にASK信号を生成し、スレーブはその存在を制御します。 ウィザードは、通常、データを裏切る必要がないことをスレーブに知らせることにより、 STOPコマンドを送信する前にASKを送信しない場合があります。
-  マスタからデータを送信した後(書き込みモード)、スレーブからデータを読み取る必要がある場合、マスタはSTART信号を再度生成し、読み取りフラグを使用してスレーブのアドレスを送信します。  ( STARTコマンドの前にSTOPが送信されなかった場合、 RESTARTコマンドが生成されます)。 これは、マスタースレーブの通信方向を変更するために使用されます。 たとえば、レジスタアドレスをスレーブに渡し、スレーブからデータを読み取ります。 
      
 
 
- スレーブとの作業の最後に、マスターはSTOP信号を生成します。クロック信号がHighのとき、マスターは0から1へのデータバス遷移を形成します。
STM 32には、ハードウェアで実装されたi2cバストランシーバーがあります。 MKには、このようなモジュールが2つまたは3つ存在する場合がありますが、その構成には、使用されるMKのリファレンスで説明されている特別なレジスタが使用されます。
MicroCでは、i2c(および周辺機器)を使用する前に、適切に初期化する必要があります。 これを行うには、次の機能を使用します(ウィザードとしての初期化):
I2Cn_Init_Advanced(unsigned long : I2C_ClockSpeed, const Module_Struct *module);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     - nは、使用されるモジュールの番号です(例: I2C1またはI2C2) 。
- I2C_ClockSpeed-バス速度、100000(100 kbs、標準モード)または400000(400 kbs、高速モード)。 2番目は4倍高速ですが、すべてのデバイスがサポートしているわけではありません
- * module-周辺モジュールへのポインタ、たとえば&_GPIO_MODULE_I2C1_PB67 、ここでコードアシスタント ( ctrl-space )が大いに役立つことを忘れないでください。
まず、バスのフリーネスをチェックします。これには、 I2Cn_Is_Idle()関数があります。 バスが空いている場合は1、交換中の場合は0を返します。
次に、 STARTシグナルを生成します。
I2Cn_Start();
ここで、 nはマイクロコントローラーの使用済みi2cモジュールの番号です。 この関数は、バスでエラーが発生した場合は0を返し、すべてが正常であれば1を返します。
データをスレーブに転送するには、次の関数を使用します。
 I2Cn_Write(unsigned char slave_address, unsigned char *buf, unsigned long count, unsigned long END_mode);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      - n-使用されているモジュールの番号
- slave_address -7ビットのスレーブアドレス。
- * buf-データへのポインタ-バイトまたはバイトの配列。
- count-送信されたデータのバイト数。
- END_mode-スレーブにデータを送信した後の処理、 END_MODE_STOP - STOP信号の送信、またはEND_MODE_RESTARTによる STARTの再送信、 RESTART信号の生成、および機関とのセッションが終了しておらず、データがそこから読み取られることの通知。
スレーブからデータを読み取るには、次の関数を使用します。
 I2Cn_Read(char slave_address, char *ptrdata, unsigned long count, unsigned long END_mode);
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      - n-使用されているモジュールの番号
- slave_address -7ビットのスレーブアドレス。
- * buf-データを受け取る変数または配列へのポインター、charまたはshort int型
- count-受信したデータのバイト数。
- END_mode-スレーブからデータを受信した後の処理-END_MODE_STOP - STOP信号を送信するか、 END_MODE_RESTARTがRESTART信号を送信します。
MKに何かを接続してみましょう。 まず、 広く普及しているPCF8574(A)チップ。 これは、i2cバスを制御する入出力ポートエクステンダーです。 この超小型回路には、物理的な入出力ポートである内部レジスタが1つだけ含まれています。 つまり、バイトが彼女に渡されると、彼はすぐに彼女の結論にさらされます。 そこからバイトを読み取ると(読み取りフラグ、 RESTERTシグナルで STARTアドレスを送信し、データを読み取り、最終的にSTOPシグナルを生成します)、出力に論理状態が反映されます。 データシートに従ってチップを接続します。
超小型回路アドレスは、端子A0、A1、A2の状態から形成されます。 PCF8574チップの場合、アドレスは0100A0A1A2になります。 (たとえば、A0、A1、A2にはそれぞれ高レベルがあり、マイクロ回路のアドレスは0b0100 111 = 0x27になります)。 PCF8574A - 0111A0A1A2の場合、配線図ではアドレス0b0111 111 = 0x3Fになります。 たとえば、A2がグランドに接続されている場合、 PCF8574Aのアドレスは0x3Bになります。 1つのi2cバスで合計16個のチップ、それぞれ8個のPCF8574AとPCF8574を同時にハングさせることができます。
何かを転送してi2cバスを初期化し、PCF8574に転送してみましょう。
 #define PCF8574A_ADDR 0x3F //c  PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) { I2C1_Start(); //   START I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); //  1      STOP } char PCF8574A_reg; //      PCF8574 void main () { I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); //  I2C delay_ms(25); //   PCF8574A_reg.b0 = 0; //   PCF8574A_reg.b1 = 1; //    while (1) { delay_ms(500); PCF8574A_reg.b0 = ~PCF8574A_reg.b0; PCF8574A_reg.b1 = ~PCF8574A_reg.b1; //   I2C_PCF8574_WriteReg (PCF8574A_reg); //  PCF8574  } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      プログラムをコンパイルして実行すると、LEDが交互に点滅することがわかります。
LEDをカソードでPCF8574に接続しただけではありません。 問題は、ロジック0が出力に供給されると、マイクロチップは正直に出力をグランドに引き込みますが、論理1を供給すると、100μAの電流源を介して+電源に接続します。 つまり、「正直な」論理1は出力で取得できません。 また、100μAのLEDは点灯しません。 これは、PCF8574の出力を追加のレジスタなしで入力に設定するために行われます。 出力レジスタ1に書き込むだけで(実際、Vddでレッグのステータスを設定します)、単純にそれをグラウンドに短縮できます。 現在のソースでは、I / Oエクスパンダーの出力ステージが「燃え尽きる」ことはありません。 脚が地面に引っ張られている場合、地球の電位がその上にあり、論理0が読み取られます。脚が+に引っ張られている場合、論理1が読み取られます。一方で、それは単純ですが、他方では、これらの超小型回路で作業するときは常にこれを覚えておく必要があります
拡張チップのピンの状態を読み取ってみましょう。
 #define PCF8574A_ADDR 0x3F //c  PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) { I2C1_Start(); //   START I2C1_Write(PCF8574A_ADDR, &wData, 1, END_MODE_STOP); //  1      STOP } void I2C_PCF8574_ReadReg(unsigned char rData) { I2C1_Start(); //   START I2C1_Read(PCF8574A_ADDR, &rData, 1, END_MODE_STOP); //  1      STOP } char PCF8574A_reg; //     PCF8574 char PCF8574A_out; //       PCF8574 char lad_state; //     void main () { I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); //  I2C delay_ms(25); //   PCF8574A_reg.b0 = 0; //    PCF8574A_reg.b1 = 1; //    PCF8574A_reg.b6 = 1; //   6  7  . PCF8574A_reg.b7 = 1; while (1) { delay_ms(100); I2C_PCF8574_WriteReg (PCF8574A_reg); //    CF8574 I2C_PCF8574_ReadReg (PCF8574A_out); //   CF8574 if (~PCF8574A_out.b6) PCF8574A_reg.b0 = ~PCF8574A_reg.b0; //   1  (6     CF8574  0,  /  ) if (~PCF8574A_out.b7) PCF8574A_reg.b1 = ~PCF8574A_reg.b1; //   2   2  } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      ボタンをクリックして、LEDをオンまたはオフにします。 チップにはINT出力もあります。 I / Oエクスパンダのターミナルの状態が変わるたびに、インパルスが形成されます。 これをMKの外部割り込み入力に接続することにより(外部割り込みの設定方法とそれらの操作方法については、以下の記事のいずれかで説明します)。
ポートエクスパンダーを使用して、キャラクターディスプレイを接続します。 それらの多くはありますが、それらのほとんどすべては、 HD44780コントローラーチップとそのクローンに基づいて構築されています。 たとえば、LCD2004ディスプレイを使用しました。

 データシートとHD44780コントローラーは、インターネットで簡単に見つけることができます。 ディスプレイをPCF8574に、彼女をそれぞれSTM32に接続します。 
      
        
        
        
      
    
      
        
        
        
      
    
      
        
        
        
      
    
      
        
        
        
      
      HD44780は、パラレルゲートインターフェイスを使用します。 データはピンEで8(1サイクルあたり)または4(2サイクル)のゲートパルスで送信されます  (立ち下がりエッジでディスプレイコントローラーによって読み取られ、1から0に移行します)。  RS出力は、ディスプレイにデータ( RS = 1 )(実際にASCIIコードから表示される文字)またはコマンド( RS = 0 )を送信しているかどうかを示します。  RWは、データ転送、書き込み、または読み取りの方向を示します。 通常、ディスプレイにデータを書き込むため、( RW = 0 )。 抵抗R6はディスプレイのコントラストを制御します。  コントラストをグラウンドまたは電源に調整して入力を接続することはできません。そうしないと、何も表示されません。  。  VT1は、MKコマンドに従ってディスプレイのバックライトをオンまたはオフにするために使用されます。  MicroCには、パラレルインターフェースでこのようなディスプレイを操作するためのライブラリがありますが、通常、ディスプレイに8脚を費やすのは高価なので、ほとんどの場合、PC8574を使用してこのような画面を操作します。  (誰か興味があるなら、並列インターフェースを介してMicroCに組み込まれたHD44780に基づくディスプレイの操作についての記事を書きます。)交換プロトコルはそれほど複雑ではありません(4データ行を使用し、2サイクルで情報を送信します)。タイミングチャート: 
      
        
        
        
      
    
      
        
        
        
      
     ディスプレイにデータを転送する前に、サービスコマンドを送信してデータを初期化する必要があります。  (データシートに記載されている、ここでは最も使用されているもののみを示します) 
      
        
        
        
      
    
      
        
        
        
      
    
- 0x28-4行のインジケーターとの通信
- 0x0C-画像出力をオンにし、カーソル表示をオフにします
- 0x0E-画像出力を有効にし、カーソル表示を有効にします
- 0x01-インジケーターをクリアします
- 0x08-画像出力を無効にする
- 0x06-文字が表示された後、カーソルは1慣れてシフトします
このインジケーターを頻繁に使用する必要があるため、プラグインライブラリ「i2c_lcd.h」を作成します。 これを行うには、 Project Manegerの Header Filesフォルダーを右クリックし、 Add New Fileを選択します 。 ヘッダーファイルを作成します。
 #define PCF8574A_ADDR 0x3F //c  PCF8574 #define DB4 b4 //   PCF8574   #define DB5 b5 #define DB6 b6 #define DB7 b7 #define EN b3 #define RW b2 #define RS b1 #define BL b0 //  #define displenth 20 //       static unsigned char BL_status; //     (/) void lcd_I2C_Init(void); //     PCF8574 void lcd_I2C_txt(char *pnt); //     ,  -     void lcd_I2C_int(int pnt); //       ,  -   void lcd_I2C_Goto(unsigned short row, unsigned short col); //     ,  row -  ( 1  2  4    )  col - ( 1  displenth)) void lcd_I2C_cls(); //   void lcd_I2C_backlight (unsigned short int state); //  (  1   -   0  )
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      次に、関数について説明します。Sourcesフォルダーを右クリックしてProject Manegerに移動し、 Add New Fileを選択します 。 ファイル「i2c_lcd.c」を作成します。
 #include "i2c_lcd.h" //  - char lcd_reg; //      PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) //    i2c   PCF8574 { I2C1_Start(); I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); } void LCD_COMMAND (char com) //     { lcd_reg = 0; // 0    lcd_reg.BL = BL_status.b0; //       ,    lcd_reg.DB4 = com.b4; //     4     lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; lcd_reg.EN = 1; // .   1 I2C_PCF8574_WriteReg (lcd_reg); //   PCF8574,      delay_us (300); //  lcd_reg.EN = 0; //    0,    I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.DB4 = com.b0; //    4   lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; lcd_reg.EN = 1; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); } void LCD_CHAR (unsigned char com) //   (ASCII  ) { lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; //        1  RS lcd_reg.DB4 = com.b4; //   4   lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg.EN = 0; // .   0,    I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; lcd_reg.DB4 = com.b0; //   4   lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); } void lcd_I2C_Init(void) { I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); //  I2c    delay_ms(200); lcd_Command(0x28); //    4    delay_ms (5); lcd_Command(0x08); //     delay_ms (5); lcd_Command(0x01); //  delay_ms (5); lcd_Command(0x06); //       delay_ms (5); lcd_Command(0x0C); //      delay_ms (25); } void lcd_I2C_txt(char *pnt) //     { unsigned short int i; //     char tmp_str[displenth + 1]; //  ,   1    ,      v  NULL ASCII 0x00 strncpy(tmp_str, pnt, displenth); //       displenth    for (i=0; i<displenth; i++) { if (tmp_str[i] == 0) break; //  NULL , і   LCD_CHAR(tmp_str[i]); //     } } void lcd_I2C_int(int pnt) //     { char tmp_str[8]; //  unsigned short i, j; IntToStr(pnt,tmp_str); //   ,  6  + NULL  while (tmp_str[0]==32) { for (i=0; i<7; i++) { tmp_str[i]=tmp_str[i+1]; //   (ASCII  32) tmp_str[6-j]=0; } j++; } lcd_I2C_txt (tmp_str); //    } void lcd_I2C_Goto(unsigned short row, unsigned short col) //     { col--; //      0,    1  switch (row) { case 1: lcd_Command(0x80 + col); //     break; case 2: lcd_Command(0x80 + col + 0x40); break; case 3: lcd_Command(0x80 + col + 0x14); break; case 4: lcd_Command(0x80 + col + 0x54); break; } } void lcd_I2C_cls() //  { lcd_Command(0x01); delay_ms (5); } void lcd_I2C_backlight (unsigned short int state) //     { lcd_reg = 0; BL_status.b0 = state.b0; //  ,            lcd_reg.BL = state.b0; I2C_PCF8574_WriteReg (lcd_reg); delay_ms (1); }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      次に、メイン関数を使用して、新しく作成したライブラリをファイルに接続します。
 #include "i2c_lcd.h" //  - unsigned int i; //   void main() { lcd_I2C_Init(); //  lcd_I2C_backlight (1); //  lcd_I2C_txt ("Hellow habrahabr"); //    while (1) { delay_ms(1000); lcd_I2C_Goto (2,1); //  1  2  lcd_i2c_int (i); //    i++; //   } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      すべてが正しく組み立てられると、インジケーターにテキストが表示され、カウンターが1秒ごとに増加します。 一般的に、複雑なものはありません:)
次の記事では、引き続きi2cプロトコルとそれに対応するデバイスを扱います。 EEPROM 24XXメモリとMPU6050加速度計/ジャイロスコープの使用を検討してください。