技術共有

STM32-I2C

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

このコンテンツは以下に基づいています江謝テクノロジー STM32ビデオ学習後に編集されました。

1.I2C通信

1.1 I2C 通信の概要

  • I2C (Inter IC Bus) はフィリップスによって開発されたユニバーサル データ バスです。
  • 2つの通信ライン: SCL (Serial Clock) シリアルクロックライン、SDA (Serial Data) シリアルデータライン
  • 同期、半二重、シングルエンド、マルチデバイス
  • データで返信する
  • バス上での複数のデバイスのマウントをサポート (1 つのマスターと複数のスレーブ、複数のマスターと複数のスレーブ)
    • 1 つのマスター、複数のスレーブ: マイクロコントローラーはホストとして機能し、I2C バスの動作を制御します。スレーブはホストによって指定された後にのみ I2C バスを制御でき、I2C バスに触れることができません。競合を防ぐために許可なく I2C バスを使用します。
    • マルチマスターとマルチスレーブ: バス上のどのモジュールもアクティブに飛び出してマスターとして機能できます。バスの競合が発生すると、I2C プロトコルによって調停が行われ、調停に勝った側がバスの制御を獲得し、負けた側は自動的にスレーブになります。

画像.png

1.2 ハードウェア回路

  • すべての I2C デバイスの SCL は相互に接続され、SDA も相互に接続されます。
  • デバイスの SCL と SDA は両方ともオープン ドレイン出力モードに設定する必要があります。
  • SCL と SDA にそれぞれプルアップ抵抗を追加します。抵抗値は一般的に約 4.7KΩです。

図1図2
画像.png

  • 1 つのマスターと複数のスレーブ: CPU はシングルチップ マイクロコンピューターであり、バスのマスターとして、いつでも SCL ラインを完全に制御できます。さらに、アイドル状態では、スレーブがデータを送信し、スレーブが応答した場合にのみ、ホストが SDA の制御をアクティブに開始できます。これがホストの権限です。
  • 制御される IC は、I2C バスに取り付けられたスレーブであり、姿勢センサー、OLED、メモリ、クロック モジュールなどになります。 SCL クロック ラインの場合、スレーブの電力は比較的小さく、いつでも受動的に読み取ることしかできません。 SDA データ ラインの場合、スレーブは SDA の制御をアクティブに開始することはできません。マスターがスレーブを読み取るコマンドを送信した後、またはスレーブが応答したときにのみ、スレーブは一時的に SDA の制御を取得できます。
  • 図 2: 左側が SCL、右側が SDA。すべてのデータはデータ バッファまたはシュミット トリガを介して入力できます。
    • 入力は回路に影響を与えないため、いつでもどのデバイスでも入力を得ることができます。
    • 出力はオープンドレイン出力構成を使用しており、出力がローの場合、スイッチはオンになり、ピンはグランドに直接接続されます。これは、出力がハイの場合、スイッチはオフになり、ピンはグランドに接続されます。は何も接続されておらずフローティング状態になっているため、すべてのデバイスはハイ レベルによるフローティングを回避するために、外部にプルアップ抵抗を設ける必要があります。抵抗を介してバスに接続され、ハイレベルにプルアップされるため、弱いプルアップになります。このようにして、第一に、電源短絡現象を完全に排除し、回路の安全性を確保し、第二に、ピンモードの頻繁な切り替えを回避します。オープンドレインモードでは、ハイレベルを出力することは端子を切り離すことと同等となるため、入力前に直接ハイレベルを出力することができます。第三に、このモードには「ワイヤード AND」現象があります。1 つ以上のデバイスがロー レベルを出力する限り、バスはすべてのデバイスがハイ レベルを出力する場合にのみハイ レベルになります。 。したがって、I2C はこの現象を利用して、マルチマスター モードでクロック同期とバス アービトレーションを実行できます。したがって、ここでの SCL は 1 つのマスターおよび複数のスレーブ モードでプッシュプル出力を使用できますが、依然としてオープン ドレイン プラス プルアウト出力モードを使用します。

1.3 I2C タイミング基本ユニット

1.3.1 開始条件と終了条件

  • 開始条件: SCL ハイレベル中に、SDA がハイレベルからローレベルに切り替わります。
  • 終了条件: SCL ハイレベル中に、SDA がローレベルからハイレベルに切り替わります。

画像.png

  • 初期条件下で : I2C バスがアイドル状態の場合、SCL と SDA は両方ともハイ レベル状態になります。つまり、SCL に触れるデバイスはなく、SCL と SDA は外部プルアップ抵抗によってハイ レベルにプルされ、バスは動作します。静かな状態。ホストがデータを送受信する必要がある場合、最初にバスの沈黙を破って開始条件を生成する必要があります。つまり、SCL はタッチせずに High レベルになり、次に SDA をプルダウンして立ち下がりエッジを生成します。スレーブが SCL ハイ レベルと SDA の立ち下がりエッジ信号を捕捉すると、スレーブは自身をリセットし、マスターの呼び出しを待ちます。 SDA の立ち下がりエッジの後、ホストはバスを占有する一方で、基本ユニットの接続を容易にするために SCL を再度プルダウンする必要があります。スタート条件とストップ条件を除いて、各シーケンシャル ユニットの SCL はロー レベルで開始し、ロー レベルで終了することが後で確認されます。
  • 終了条件状態の場合 : SCL が最初に手放してハイ レベルにリバウンドし、次に SDA が手放してハイ レベルにリバウンドして立ち上がりエッジを生成し、終了条件をトリガーします。同時終了条件の後、SCL と SDA は両方とも High になり、最初の穏やかな状態に戻ります。
    スタートとストップはホストによって生成され、スレーブはスタートとストップを生成できません。したがって、バスがアイドル状態のときは、スレーブは常に手を放さなければならず、飛び出してバスに触れることはできません。

1.3.2 バイトを送信する

  • バイトを送る: SCL が Low レベルの間、ホストは SDA ラインにデータ ビットを順番に配置し (High ビットが最初)、その後 SCL を解放します。SCL が High レベルの間、スレーブはデータ ビットを読み取るため、SDA は許可されません。 SCL の High レベル中にデータを保持するには、データが変化したときに、上記のプロセスを 8 回繰り返して 1 バイトを送信します。

低レベルのホストがデータを書き込み、高レベルのスレーブがデータを読み取ります。
画像.png
開始条件の後、最初のバイトもホストによって送信される必要があります。 SCL がローの場合、ホストが 0 を送信したい場合は SDA をローに引き下げ、1 を送信したい場合は解放し、SDA はハイ レベルに戻ります。 SCL がロー レベルの間、SDA のレベルは変更できます。データが配置された後、ホストはクロック ラインを解放し、SCL はハイ レベルに戻ります。ハイ レベルの間はスレーブが SDA を読み取る時間であるため、ハイ レベルの間は SDA を変更することはできません。 SCL が High レベルになった後、スレーブはできるだけ早く SDA を読み取る必要があります。通常、スレーブは SCL の立ち上がりエッジで読み取りを完了します。クロックはマスターによって制御されるため、スレーブは立ち下がりエッジがいつ発生するかを知りません。そのため、スレーブは SCL の立ち上がりエッジでデータを読み取ります。ホストが一定期間 SCL を解放した後も、SCL を Low にプルし続けて次のビットを送信できます。ホストは、SCL の立ち下がりエッジ後、できるだけ早くデータを SDA に配置する必要もあります。ただし、ホストはクロックを制御できるため、Low レベルが Low のときに SDA にデータを配置するだけで済みます。データが解放された後、ホストは SCL を再び解放し、SCL は High になり、スレーブはこのビットを読み取ります。このプロセスをループします。ホストは SCL を Low にプルし、データを SDA に置き、ホストは SCL を解放し、スレーブは SDA データを読み取ります。 SCL の同期により、マスターが送信、スレーブが受信を順に行い、8 サイクル後に 1 バイトである 8 ビットのデータが送信されます。
上位ビットからなので、最初のビットはバイトの最上位ビット B7 で、最下位ビット B0 が最後に送信されます。

1.3.3 バイトの受信

  • バイトを受け取る: SCL が Low レベルの間、スレーブは SDA ラインにデータ ビットを順番に入力し (High ビットが最初)、その後 SCL を解放します。ホストは SCL が High レベルの間データ ビットを読み取るため、SDA は許可されません。 SCL のハイ レベル中にデータを取得するには、データが変化したときに、上記のプロセスを 8 回繰り返して 1 バイトを受信します (ホストは受信する前に SDA を解放する必要があります)。

低レベルのスレーブがデータを書き込み、高レベルのホストがデータを読み取る
画像.png
SDA ライン: マスターは、受信する前に SDA を解放する必要があります。この時点で、スレーブが 0 を送信する必要がある場合、スレーブは SDA をローに下げ、スレーブは 1 を送信する必要があります。高水準に回復します。低レベルはデータを変換し、高レベルはデータを読み取ります。実線はマスターによって制御されるレベルを表し、点線はスレーブによって制御されるレベルを表します。 SCL はプロセス全体を通じてホストによって制御され、SDA ホストは受信する前に解放され、制御のためにスレーブに渡される必要があります。 SCL クロックはホストによって制御されるため、スレーブのデータ変換は基本的に SCL の立ち下がりエッジで実行され、ホストは SCL が High であればいつでも読み出すことができます。

1.3.4 レスポンスの送信とレスポンスの受信

  • 返信を送信: ホストは 1 バイトを受信した後、次のクロックで 1 ビットのデータを送信します。データ 0 は応答を示し、データ 1 は非応答を示します。
  • 返信を受け取る: ホストはバイトを送信した後、次のクロックでデータのビットを受信して​​、スレーブが応答するかどうかを判断します。データ 0 は応答を示し、データ 1 は非応答を示します (ホストは受信する前に SDA を解放する必要があります)。

画像.png
つまり、バイト送信のタイミングを呼び出した後、スレーブが与えられたデータを受信したかどうかを判断するための受信レスポンスを呼び出すタイミングが続く必要があります。スレーブがそれを受信した場合、応答ビットで、マスターが SDA を解放すると、スレーブは直ちに SDA をプルダウンし、SCL がハイ レベルの間にホストが応答ビットを読み取ります。応答ビットが 0 の場合、スレーブが実際に応答ビットを受信したことを意味します。
バイトを受信する場合は、送信応答を呼び出す必要があります。応答を送信する目的は、送信を続行するかどうかをスレーブに伝えることです。スレーブ マシンがデータを送信した後にマスターから応答を受信した場合、スレーブ マシンはマスター マシンから応答を受信しなかった場合、データが送信されたとみなして送信を続けます。が送信されましたが、マスター マシンは私を無視します。おそらくホストはそれを望んでいません。このとき、スレーブはホストのその後の動作への干渉を防ぐために、素直に SDA を解放し、SDA の制御を引き渡します。

1.4 I2C タイミング

1.4.1 書き込むアドレスを指定する

  • 書き込むアドレスを指定してください
  • 指定されたデバイス(Slave Address)に対して、指定されたアドレス(Reg Address)(つまり、指定されたデバイスのレジスタアドレス)に指定されたデータ(Data)を書き込みます。

画像.png
プロセス:
(1) 開始条件
(2) バイト送信タイミング 0xD0 (スレーブアドレス(7bit) + ライト(1bit)-0) (1101 0000)
(3) レスポンス受信:RA = 0(スレーブからのレスポンスを受信)
(4) 指定アドレス:0x19(0001 1001)
(5) レスポンス受信:RA = 0(スレーブからのレスポンスを受信)
(6) 指定データ書き込み:0xAA(1010 1010)
(7) 受信レスポンス:RA = 0
(8) ストップビットP(終了条件)

  • スタートコンディション後はバイトを送信するタイミングになります。バイトの内容はスレーブアドレス+リード、ライトビットで、スレーブアドレスは7ビット、リード、ライトビットはちょうど1ビットになります。 8ビット。スレーブアドレスの送信は通信対象を決定するため、リードライトビットの送信は次にライトするかリードするかを確認するためです。ホストはデータを送信します。バイトの内容は 16 進数に変換され、次の単位が受信スレーブの応答ビット (RA) になります。書き込みビットが終了し、SCL が Low に引き下げられると、ホストは SDA を解放し、続いてアクノリッジ ビット RA を解放する必要があります。
  • 応答ビット RA が終了した後のハイ レベルは、スレーブが SDA の制御を引き渡すことによって生成されます。これは、スレーブが SCL のロー レベル、および SDA の立ち上がりエッジでできるだけ早くデータを交換したいためです。 SCL の立ち下がりエッジはほぼ同時に発生しました。
  • 応答完了後、1 バイトを送信し続けると、スレーブデバイスは 2 バイト目以降の使用を指定デバイス内に送信できます。一般に、2 バイト目はレジスタ アドレスや命令制御ワードなどで、3 バイト目はホストがレジスタ アドレスに書き込みたい内容 (2 バイト目) です。
  • P はストップビットです。

このデータ フレームの目的は、スレーブ アドレス 1101000 を指定するデバイスの場合、アドレス 0x19 の内部レジスタにデータ 0xAA を書き込むことです。
0 は、ホストが次のタイミングで書き込み動作を実行することを意味します。
1 は、ホストが後続のタイミング シーケンスで読み出し動作を実行することを意味します。

1.4.2 現在アドレスの読み出し

  • 現在アドレス読み出し
  • 指定したデバイス(Slave Address)に対して、カレントアドレスポインタが示すアドレスのスレーブデータ(Data)を読み出します。

画像.png
プロセス:
(1) 開始条件
(2) バイト送信タイミング—0xD1 (スレーブアドレス(7bit) + リード(1bit)-1) (1101 0001)
(3) レスポンス受信:RA = 0(スレーブからのレスポンスを受信)
(4) スレーブデータの読み出し:0x0F (0000 1111)
(7) レスポンス送信:SA=0
(8) ストップビットP(終了条件)

  • 読み取りおよび書き込みビットは 1 で、次の読み取り操作が実行されることを示します。スレーブが応答した後 (RA=0)、データ送信方向は逆になります。マスターはSDAの制御をスレーブに渡したいので、マスターはバイトを受信するタイミングを呼び出して受信動作を実行します。
  • 2 番目のバイトでは、スレーブは SCL のローレベル中に SCL に書き込むことができます。最後に、マスターは SCL のハイレベル中に順番に読み取ります。 8 ビット、スレーブによって送信された 1 バイトのデータ (0x0F) が受信されます。しかし、スレーブのどのレジスタが 0x0F なのでしょうか?読み取りタイミングでは、I2C プロトコルでは、ホストがアドレス指定しているときに、読み取りおよび書き込みフラグが 1 に設定されると規定されています。次のバイトはすぐに読み取りタイミングに切り替わります。したがって、ホストは、読み出したいレジスタを指定する前に受信を開始するため、ここにはアドレスを指定するリンクはありません。スレーブ マシンでは、すべてのレジスタが線形領域に割り当てられ、レジスタの 1 つを示す別のポインタ変数が存在します。このポインタは電源投入時のデフォルトで、通常はアドレス 0 を指し、バイトが書き込まれるたびに、 1 バイトの読み取り後、ポインタは自動的に 1 回インクリメントされ、次の位置に移動します。その後、現在のアドレスを読み取るタイミングを呼び出すときに、ホストがどのアドレスを読み取るかを指定しない場合、スレーブはそのアドレスが指すレジスタに戻ります。現在のポインタの値。

1.4.3 指定アドレスの読み取り

  • 読み込むアドレスを指定してください
  • 指定したデバイス(Slave Address)に対して、指定したアドレス(Reg Address)配下のスレーブデータ(Data)を読み出します。

画像.png
最初に開始し、次に開始と停止を繰り返します
プロセス:
(1) 開始条件
(2) バイト送信タイミング 0xD0 (スレーブアドレス(7bit) + ライト(1bit)-0) (1101 0000)
(3) レスポンス受信:RA = 0(スレーブからのレスポンスを受信)
(4) 指定アドレス:0x19(0001 1001)
(5) レスポンス受信:RA = 0(スレーブからのレスポンスを受信)
(6) 開始条件を繰り返す
(7) バイト送信のタイミング — 0xD1 (スレーブアドレス (7 ビット) + リード (1 ビット) - 1) (1101 0001)
(8) 受信レスポンス:RA = 0
(9) スレーブデータの読み取り: 0xAA (1010 1010)
(10) レスポンス送信:SA=0
(11) ストップビットP(終了条件)

  • 最初の部分は、指定されたアドレスに書き込むことですが、指定されているのはアドレスだけであり、書き込む時間がありません。2 番目の部分は、現在のアドレスを読み取ることです。アドレスが指定されたばかりであるため、現在のアドレスの読み取りは次のようになります。再び呼ばれた。
  • 指定されたスレーブ アドレスは 1101000、読み取り/書き込みフラグは 0 で、スレーブが応答した後、アドレス 0x19 を指定する別のバイト (2 番目のバイト) がスレーブに書き込まれます。アドレス ポインタ、つまりスレーブがデータを受信した後、そのレジスタ ポインタは位置 0x19 を指します。
  • Sr は繰り返し開始条件であり、新しいタイミングを開始するのと同じです。指定された読み取りおよび書き込みフラグは開始条件の最初のバイトにのみ続くため、読み取りと書き込みの方向を切り替えたい場合は、次の条件のみを使用できます。別の開始条件。
  • 次に、開始条件の後に、読み取り/書き込みフラグ ビットを再アドレス指定して指定します。このとき、読み取り/書き込みフラグ ビットは、読み取り対象であることを示します。その後、ホストはデータであるバイトを受信します。アドレス 0x19 の 0xAA。

2. MPU6050

2.1 MPU6050 の概要

  • MPU6050は、チップ自身のX軸、Y軸、Z軸の加速度および角速度パラメータを計測できる6軸姿勢センサであり、データ融合によりさらに姿勢角(オイラー角)を求めることができます。ジェスチャーシーンを検出する必要がある車両、航空機などのバランスをとる。
  • 3 軸加速度計 (加速度計): X、Y、Z 軸の加速度を測定します。
  • 3軸ジャイロセンサー(ジャイロスコープ):X、Y、Z軸の角速度を測定します

画像.png

  • 航空機の胴体を例にとると、オイラー角は航空機の胴体と最初の 3 軸の間の角度です。
    • 飛行機飛行機の機首が下または上に傾く、この軸間の角度は次のように呼ばれます。ピッチ
    • 飛行機機体が左右にロールする、この軸間の角度は次のように呼ばれます。ロール
    • 飛行機機体を水平に保つ飛行機の機首を左または右に回転させます、この軸間の角度は次のように呼ばれます。ヨー
    • オイラー角はそのときの飛行機の姿勢、上に傾いているか下に傾いているか、左に傾いているか右に傾いているかを表します。
  • 一般的なデータ融合アルゴリズムには、一般に相補フィルタリング、カルマン フィルタリングなど、および慣性航法における姿勢計算が含まれます。
  • 加速度計 :真ん中の点線が誘導軸で、真ん中にある程度の質量を持った小さなスライダーがあり、それに対して左右にスプリングが付いています。スライダが動くと、その上のポテンショメータが動き、このポテンショメータが出力する電圧を測定することで、小さなスライダの加速度値を得ることができます。この加速度計は、実際にはバネ力計です。ニュートンの第 2 法則によれば、加速度 a を測定したい場合は、単位質量を持つ物体を見つけて、力 F を測定することができます。 X、Y、Z 軸のそれぞれに加速度計があります。加速度センサーには静的な安定性がありますが、動的な安定性はありません。
  • ジャイロセンサー : 中央には一定の質量を持つ回転ホイールがあり、回転ホイールが高速で回転すると、回転ホイールは元の角運動量を維持する傾向があります。回転軸の方向は変わりません。外部物体の方向が回転しても、内部の回転軸の方向は回転しないため、バランスリングの接続部に角度ずれが生じます。接続部に回転ポテンショメータを置き、ポテンショメータの電圧を測定すると、回転角度を取得できます。ジャイロスコープは角度を直接取得できるはずですが、この MPU6050 のジャイロスコープは角速度、つまり X 軸、Y 軸、Z 軸の周りを回転するチップの角速度を測定します。 -軸。角速度の積分値は角度ですが、物体が静止している場合、角速度値はノイズにより完全にゼロに戻ることができず、積分を継続すると、この小さなノイズにより計算された角度がゆっくりと変動します。角速度を積分して得られる角度です。時間の経過には耐えられませんが、この角度は静止していても動いていても問題なく、物体の動きの影響を受けません。ジャイロスコープには静的な安定性ではなく、動的な安定性があります。
  • 加速度センサーは静的安定性を持ちますが、ジャイロスコープは動的安定性を持ちません。これら 2 つの特性は、相補的なフィルター処理を実行することで、互いの長所を学び、互いの短所を補うことができます。 、静的安定性と動的安定性の両方を統合できます。

2.2 MPU6050パラメータ

  • 16 ビット ADC がセンサーのアナログ信号を収集、量子化範囲: -32768 ~ 32767
  • 加速度計フルスケール選択:±2、±4、±8、±16(g)(1g=9.8m/s2)
  • ジャイロスコープのフルスケール選択: ±250、±500、±1000、±2000 (°/秒、度/秒、角速度単位、1 秒あたりの回転角度) (フルスケールの選択が大きいほど、範囲が広くなります)フルスケールレンジが小さいほど、測定分解能は高くなります)
  • 構成可能なデジタル ローパス フィルター: 出力データのローパス フィルターを選択するようにレジスターを構成できます。
  • 設定可能なクロックソース
  • 設定可能なサンプリング分周: クロック ソースを分周器で分周して、AD 変換やその他の内部回路にクロックを供給できます。分周係数を制御することでAD変換の速度を制御できます。
  • I2C スレーブアドレス: 1101000 (AD0=0) または 1101001 (AD0=1)
    • 110 1000 を 16 進数に変換すると 0x68 となるため、MPU6050 のスレーブアドレスは 0x68 であるという説もあります。ただし、I2C 通信では、最初のバイトの上位 7 ビットがスレーブ アドレスで、最下位ビットが読み取りおよび書き込みビットであるため、0x68 がスレーブ アドレスであると考える場合は、最初のバイトを送信するときに最初に変更する必要があります。 0x68 1 ビット左にシフトし (0x68 << 1)、ビット単位または上方向にビットの読み取りと書き込みを行い、1 を読み取り、0 を書き込みます。
    • もう 1 つの方法は、0x68 のデータをスレーブ アドレスとして 1 ビット左にシフトする (0x68 << 1) ことであり、これは 0xD0 です。この場合、MPU6050 のスレーブ アドレスは 0xD0 です。このとき、実際に最初のバイトを送信する場合、書き込みの場合は 0xD0 を最初のバイトとして使用し、読み取りの場合は 0xD0 または 0x01 (0xD0 | 0x01)、つまり 0xD1 を最初のバイトとして使用します。 。この表現は左シフト演算を必要としません。つまり、この表現は読み取りビットと書き込みビットをスレーブ アドレスに統合します。 0xD0 は書き込みアドレス、0xD1 は読み取りアドレスです。

2.3 ハードウェア回路

画像.png

ピン関数
電源電圧、接地電圧電源
SCL、SDAI2C通信ピン
XCL、XDAホスト I2C 通信ピン
0 日付スレーブアドレスの最下位ビット
内部割り込み信号出力
  • LDO: 低ドロップアウトのリニア電圧レギュレータ、3.3V 電圧レギュレータ。
  • SCL と SDA: モジュールには 2 つの 4.7K プルアップ抵抗が内蔵されているため、配線の際は SDA と SCL を GPIO ポートに直接接続するだけで、外部プルアップ抵抗を接続する必要はありません。 。
  • XCL、XDA: ホスト I2C 通信ピン これらの 2 つのピンは、チップの機能を拡張するために設計されています。通常、これらの拡張チップが接続されている場合、MPU6050 のホスト インターフェイスはこれらの拡張チップのデータにアクセスし、MPU6050 にデータ フュージョンを実行してデータを読み込むことができます。姿勢計算。
    AD0 ピン: スレーブ アドレスの最下位ビットです。ロー レベルに接続すると 7 ビットのスレーブ アドレスは 1101000、ハイ レベルに接続すると 7 ビットのスレーブ アドレスは 1101001 になります。回路図には抵抗があり、デフォルトではローレベルに弱くプルダウンされているため、ピンをフローティングのままにするとローレベルになります。ハイレベルに接続したい場合は、AD0を直接VCCに接続できます。そして高い位置まで強く引き上げます。
  • INT: 割り込み出力ピン。データ準備完了、I2C ホスト エラーなど、割り込みピンの出力をトリガーするいくつかのイベントをチップ内で設定できます。
  • このチップには、自由落下検出、動き検出、ゼロ動き検出なども組み込まれています。これらの信号は INT ピンをトリガーしてレベル遷移を生成することができ、必要に応じて割り込み信号を設定できます。
  • MPU6050チップの電源は2.375~3.46Vですが、これは3.3V電源デバイスであり、5Vに直接接続することはできません。したがって、3.3V 電圧レギュレータが追加され、入力端子電圧 VCC_5V は 3.3V から 5V の間になることができ、3.3V 電圧レギュレータは、3.3V 端子に電力が供給されている限り、安定した 3.3V 電圧を出力します。 , 電源表示ランプが点灯します。

2.4 MPU6050のブロック図

画像.png

  • CLKINとCLKOUTはクロック入力端子とクロック出力端子ですが、通常は内部クロックを使用します。
  • 灰色の部分: チップ内のセンサー、XYZ 軸上の加速度計、XYZ 軸上のジャイロスコープです。
  • 温度センサーも内蔵されており、温度を測定することができます。
  • これらのセンサーは本質的には可変抵抗器と同等であり、電圧を分圧した後、アナログ電圧を出力し、変換が完了した後、これらのセンサーからのデータがデータに均一に配置されます。データレジスタを読み取ることで取得できるレジスタ。センサーによって測定された値。このチップ内のすべての変換は完全に自動化されています。
  • 各センサーにはセルフテスト ユニットがあり、チップの品質を検証するために使用されます。セルフテストが開始されると、チップはセンサーに加えられる外力をシミュレートし、センサー データを生成します。通常より大きい。セルフテスト プロセス: 最初にセルフテストを有効にしてデータを読み取り、次にセルフテストを有効にしてデータを読み取り、2 つのデータを減算することができます。結果のデータはセルフテスト応答と呼ばれます。このセルフテストの応答については、マニュアルに範囲が記載されています。この範囲内であれば、チップに問題がないことを意味します。
  • Charge Pump: チャージポンプまたはチャージポンプです。チャージポンプは昇圧回路です。
  • CPOUT ピンには外付けコンデンサが必要です。
  • 割り込みステータスレジスタ: どの内部イベントを割り込みピンに出力するかを制御できます。
  • FIFO: データ ストリームをキャッシュできる先入れ先出しレジスタ。
  • コンフィギュレーションレジスタ: さまざまな内部回路を設定できます
  • センサーレジスタ:各センサーのデータを格納するデータレジスタ。
  • 工場出荷時校正済み: これは、内部のセンサーが校正されていることを意味します。
  • デジタル モーション プロセッサ: 略して DMP。チップ内に組み込まれた姿勢計算用のハードウェア アルゴリズムです。公式の DMP ライブラリを使用して姿勢計算に使用できます。
  • FSYNC: フレーム同期。

3. 10-1 ソフトウェア I2C 読み書き MPU6050

3.1 ハードウェア接続

ソフトウェア I2C 通信により、MPU6050 チップ内のレジスタを読み書きすることで、プラグイン モジュールを設定できます。 データ レジスタを読み出すことで、プラグイン モジュールのデータを取得できます。 OLED では、先頭のデータがデバイスの ID 番号として表示されます。この MPU6050 の ID 番号は 0x68 に固定されています。以下、左の3つは加速度センサーの出力データで、それぞれX軸、Y軸、Z軸の加速度、右の3つはジャイロセンサーの出力データです。これらは、X 軸、Y 軸、Z 軸の角速度です。
SCL は STM32 の PB10 ピンに接続され、SDA は PB11 ピンに接続されます。ここではソフトウェアレベルの反転が実装されているため、2 つの GPIO ポートを自由に接続できます。

3.2 運用結果

画像を拡大

3.3 コードの流れ

STM32 がホスト、MPU6050 がスレーブとなるマスター/スレーブ モードです。

  1. I2C 通信層の .c および .h モジュールを確立する
    1. I2C の基礎となる GPIO 初期化を記述します。
    2. 6 つの基本的なタイミング単位: 開始、終了、バイトの送信、バイトの受信、応答の送信、応答の受信
  2. MPU6050の.cおよび.hモジュールを作成する
    1. I2C 通信モジュールに基づいて、指定されたアドレスでの読み取り、指定されたアドレスでの書き込み、チップを構成するためのレジスタへの書き込み、およびセンサー データを取得するためのレジスターの読み取りを実装します。
  3. メイン.c
    1. MPU6050モジュールを呼び出し、初期化、データ取得、データ表示

3.4 コード

  1. I2Cコード:
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10,(BitAction)BitValue);
	Delay_us(10);
}

void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11,(BitAction)BitValue);
	Delay_us(10);
}

uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}

void MyI2C_Init(void)
{
/*
软件I2C初始化:
	1. 把SCL和SDA都初始化为开漏输出模式;
	2. 把SCL和SDA置高电平;
输入时,先输出1,再直接读取输入数据寄存器就行了;
初始化结束后,调用SetBits,把GPIOB的Pin_10和Pin_11都置高电平,
此时I2C总线处于空闲状态
*/	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
	
}

/*
起始条件:SCL高电平期间,SDA从高电平切换到低电平。
如果起始条件之前,SDA和SCL都已经是高电平了,那先释放哪一个是一样的效果。
但是这个Start还要兼容重复起始条件Sr,Sr最开始,SCL是低电平,SDA电平不敢确定,
所以为保险起见,在SCL低电平时,先确保释放SDA,再释放SCL。
这时SDA和SCL都是高电平,然后再拉低SDA、拉低SCL。
这样这个Start就可以兼容起始条件和重复起始条件了。
*/
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}

/*
终止条件:SCL高电平期间,SDA从低电平切换到高电平
如果Stop开始时,SCL和SDA都已经是低电平了,那就先释放SCL,再释放SDA。
但在这个时序单元开始时,SDA并不一定是低电平,所以为了确保之后释放
SDA能产生上升沿,要在时序单元开始时,先拉低SDA,然后再释放SCL、释放SDA。
*/
void MyI2C_Stop(void)// 终止条件
{
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

/*
发送一个字节:发送一个字节时序开始时,SCL是低电平。
除了终止条件SCL以高电平结束,所有的单元都会保证SCL以低电平结束。
SCL低电平变换数据;高电平保持数据稳定。由于是高位先行,所以变换数据的时候,
按照先放最高位,再放次高位,...,最后最低位的顺序,依次把每一个字节的每一位放在SDA线上,
每放完一位后,执行释放SCL,拉低SCL的操作,驱动时钟运转。
程序:趁SCL低电平,先把Byte的最高位放在SDA线上,
*/

void MyI2C_SendByte(uint8_t Byte) // 发送一个字节
{
	uint8_t i;
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));// 右移i位
	    MyI2C_W_SCL(1);
	    MyI2C_W_SCL(0);
	}
}

/*
接收一个字节:时序开始时,SCL低电平,此时从机需要把数据放到SDA上,
为了防止主机干扰从机写入数据,主机需要先释放SDA,释放SDA相当于切换为输入模式,
那在SCL低电平时,从机会把数据放到SDA上,如果从机想发1,就释放SDA,想发0,就拉低SDA,
主机释放SCL,在SCL高电平期间,读取SDA,再拉低SCL,低电平期间,从机就会把下一位数据放到SDA上,重复8次,
主机就能读到一个字节了。
SCL低电平变换数据,高电平读取数据,实际上是一种读写分离的操作,低电平时间定义为写的时间,高电平时间定义为读的时间,

*/
uint8_t MyI2C_ReceiveByte(void) // 接收一个字节
{
	uint8_t i, Byte = 0x00;
	MyI2C_W_SDA(1);
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SCL(1); // 主机读取数据
	    if (MyI2C_R_SDA() == 1) // 如果if成立,接收的这一位为1,
	    {
		    Byte |= (0x80 >> i);   // 最高位置1
	    }
        MyI2C_W_SCL(0);	
	}
	return Byte;
}
/*
问题:反复读取SDA,for循环中又没写过SDA,那SDA读出来应该始终是一个值啊?
回答:I2C是在进行通信,通信是有从机的,当主机不断驱动SCL时钟时,
从机就有义务去改变SDA的电平,所以主机每次循环读取SDA的时候,
这个读取到的数据是从机控制的,这个数据也正是从机想要给我们发送的数据,
所以这个时序叫做接收一个字节。
*/

void MyI2C_SendAck(uint8_t AckBit) // 发送应答
{
	// 函数进来,SCL低电平,主机把AckBit放到SDA上,
	MyI2C_W_SDA(AckBit);
	MyI2C_W_SCL(1);  // 从机读取应答
	MyI2C_W_SCL(0);  // 进入下一个时序单元
	
}

uint8_t MyI2C_ReceiveAck(void) // 接收应答
{
	// 函数进来,SCL低电平,主机释放SDA,防止从机干扰
	uint8_t AckBit;
	MyI2C_W_SDA(1);  // 主机释放SDA
	MyI2C_W_SCL(1);  // SCL高电平,主机读取应答位
	AckBit = MyI2C_R_SDA(); 
	MyI2C_W_SCL(0);	 // SCL低电平,进入下一个时序单元
	return AckBit;
}

/*问题:在程序里,主机先把SDA置1了,然后再读取SDA,
这应答位肯定是1啊,
回答:第一,I2C的引脚是开漏输出+弱上拉的配置,主机输出1,
并不是强制SDA为高电平,而是释放SDA,
第二,I2C是在通信,主机释放了SDA,从机是有义务在此时把SDA再拉低的,
所以,即使主机把SDA置1了,之后再读取SDA,读到的值也可能是0,
读到0,代表从机给了应答,读到1,代表从机没给应答,这就是接收应答的流程。


*/

  • 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
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  1. MPU6050コード:
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H

// 宏定义: 寄存器的名称   对应的地址

#define	MPU6050_SMPLRT_DIV		0x19  // 采样率分频
#define	MPU6050_CONFIG			0x1A  // 配置外部帧同步(FSYNC)引脚采样和数字低通滤波器(DLPF)设置
#define	MPU6050_GYRO_CONFIG		0x1B  // 触发陀螺仪自检和配置满量程
#define	MPU6050_ACCEL_CONFIG	0x1C  // 触发加速度计自检和配置满量程

#define	MPU6050_ACCEL_XOUT_H	0x3B  // 存储最新的加速度计测量值
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40
#define	MPU6050_TEMP_OUT_H		0x41  // 存储最新的温度传感器测量值
#define	MPU6050_TEMP_OUT_L		0x42
#define	MPU6050_GYRO_XOUT_H		0x43  // 存储最新的陀螺仪测量值
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48

#define	MPU6050_PWR_MGMT_1		0x6B  // 电源管理寄存器1
#define	MPU6050_PWR_MGMT_2		0x6C  // 电源管理寄存器2
#define	MPU6050_WHO_AM_I		0x75  // 用于验证设备身份

#endif

  • 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
  • 31
#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"

// 宏定义:从机地址
#define MPU6050_ADDRESS  0xD0

// 指定地址写
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);// 发送从机地址后,接收应答
	MyI2C_ReceiveAck();// 寻址找到从机之后,继续发送下一个字节
	MyI2C_SendByte(RegAddress); // 指定寄存器地址,存在MPU6050的当前地址指针里,用于指定具体读写哪个寄存器
	MyI2C_ReceiveAck();
	MyI2C_SendByte(Data);// 指定写入指定寄存器地址下的数据
	MyI2C_ReceiveAck();
	MyI2C_Stop();
}

// 指定地址读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck();
	MyI2C_SendByte(RegAddress); // 指定地址:就是设置了MPU6050的当前地址指针
	MyI2C_ReceiveAck();
	// 转入读的时序,重新指定读写位,就必须重新起始
	MyI2C_Start();// 重复起始条件
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);// 指定从机地址和读写位,0xD0是写地址,或上0x01变为0xD1,读写位为1,接下来要读从机的数据
	MyI2C_ReceiveAck(); // 接收应答后,总线控制权就正式交给从机了,从机开始发送一个字节
	Data = MyI2C_ReceiveByte();// 主机接收一个字节,该函数返回值就是接收到的数据
	// 主机接收一个字节后,要给从机发送一个应答
	MyI2C_SendAck(1);// 参数为0,就是给从机应答,参数给1,就是不给从机应答
	// 如果想继续读多个字节,就要给应答,从机收到应答之后,就会继续发送数据,如果不想继续读了,就不能给从机应答了。
	// 主机收回总线的控制权,防止之后进入从机以为你还想要,但你实际不想要的冲突状态,
	// 这里,只需要读取1个字节,所以就给1,不给从机应答,
	MyI2C_Stop();
	return Data;
}

void MPU6050_Init(void)
{
	MyI2C_Init();
	// 写入一些寄存器对MPU6050硬件电路进行初始化配置
	// 电源管理寄存器1:设备复位:0,不复位;睡眠模式:0,解除睡眠:循环模式:0,不循环;无关位i:0;温度传感器失能:0,不失能;最后三位选择时钟:000,选择内部时钟,001,选择x轴的陀螺仪时钟,
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);// 解除睡眠,选择陀螺仪时钟
	// 电源管理寄存器2:前两位,循环模式唤醒频率:00,不需要;后6位,每一个轴的待机位:全为0,不需要待机;
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00); // 均不待机
	// 采样率分频:该8位决定了数据输出的快慢,值越小越快
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);// 采样分频:10分频
	// 配置寄存器:外部同步:全为0,不需要;数字低通滤波器:110,最平滑的滤波
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);// 滤波参数给最大
	// 陀螺仪配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位无关位:为0
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);// 陀螺仪和加速度计都选最大量程
	// 加速度计配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位高通滤波器:用不到,为000
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
		
}

// 获取芯片的ID号
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

// 获取寄存器数据的函数,返回6个int16_t的数据,分别表示XYZ的加速度值和陀螺仪值
// 指针地址传递的方法,返回多值
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
	                 int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;
	// 加速度计X
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL; // 高8位左移8位,再或上低8位,得到加速度计X轴的16位数据
	// 加速度计Y
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	// 加速度计Z
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	// 陀螺仪X
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	// 陀螺仪Y
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	// 陀螺仪Z
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}
  • 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
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyI2C.h"
#include "MPU6050.h"

uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;// 接收XYZ轴的加速度值和陀螺仪值



int main(void)
{
	OLED_Init();
//	MyI2C_Init();
	MPU6050_Init();
//	
	OLED_ShowString(1,1,"ID:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1, 4, ID, 2);
	
//	// 指定地址写
//	MyI2C_Start(); // 产生起始条件,开始一次传输
//	// 主机首先发送一个字节,内容是从机地址+读写位,进行寻址
//	MyI2C_SendByte(0xD0);  // 1101 000 0,0代表即将进行写入操作
//	// 发送一个字节后,要接收一下应答位,看看从机有没有收到刚才的数据
//	uint8_t Ack = MyI2C_ReceiveAck();
//	// 接收应答之后,要继续发送一个字节,写入寄存器地址
//	MyI2C_Stop();
//	
//	OLED_ShowNum(1, 1, Ack, 3);
	
//	// 指定地址读
//	uint8_t ID = MPU6050_ReadReg(0X75);// 返回值是0x68
//	OLED_ShowHexNum(1, 1, ID, 2);
	
//	// 指定地址写,需要先解除睡眠模式,否则写入无效
//	// 睡眠模式是电源管理寄存器1的这一位SLEEP控制的,把该寄存器写入0x00,解除睡眠模式
//	// 该寄存器地址是0x6B
//	MPU6050_WriteReg(0x6B, 0x00);
//	// 采样率分频寄存器,地址是0x19,值的内容是采样分频
//	MPU6050_WriteReg(0x19, 0xAA);
//	
//	uint8_t ID = MPU6050_ReadReg(0X19);
//	OLED_ShowHexNum(1, 1, ID, 2);//显示0x19地址下的内容,应该是0xAA
	
	while(1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}

  • 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
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

4. I2C ペリフェラル

4.1 I2C ペリフェラルの概要

  • STM32 はハードウェア I2C トランシーバ回路を統合しており、クロック生成、開始条件と終了条件の生成、応答ビットの送受信、データ送受信などの機能をハードウェアで自動的に実行できるため、CPU の負担が軽減されます。
  • マルチホストモデルをサポート
  • 7ビット/10ビットアドレスモードをサポート
  • 標準速度(最大100 kHz)、高速(最大400 kHz)の異なる通信速度をサポート
  • DMAをサポート
  • SMBusプロトコルに対応
  • STM32F103C8T6 ハードウェア I2C リソース: I2C1、I2C2

4.2 I2C ブロック図

画像.png

  • 左側は通信ピンです。SDA と SCL は SMBus によって使用されます。
    一般的なペリフェラルから派生したピンは、通常、GPIO ポートの多重化モードを通じて外部に接続されます (表を確認してください)。
  • 以上がデータ制御部:SDAであり、データ送受信の核となる部分はデータレジスタDR(DATA REGISTER)とデータシフトレジスタです。データを送信する必要がある場合、データ レジスタ DR に 1 バイトのデータを書き込むことができます。シフト レジスタにシフトするデータがない場合は、データ レジスタの値がさらにシフト レジスタに転送されます。シフトプロセス中に、次のデータをデータレジスタに直接配置して待機することができます。前のデータ移行が完了すると、次のデータをシームレスに接続して送信し続けることができます。データがデータ レジスタからシフト レジスタに転送されると、ステータス レジスタの TXE ビットが 1 に設定され、送信レジスタが空であることを示します。
  • 受信時:入力データをピンからシフトレジスタへ1ビットずつ移動し、1バイトのデータが集まるとデータをシフトレジスタからデータレジスタへ一括転送し、フラグRXNEをセットします。同時に、レジスタが空ではないことを示し、データをデータレジスタから読み出すことができます。いつ受信するか、いつ送信するかについては、制御レジスタに対応するビットを書き込んで動作させる必要があり、開始条件、終了条件、応答ビットなどはデータ制御によって完了します。
  • コンパレータとアドレスレジスタはスレーブモードで使用します。
  • SCL: クロック制御は SCL ラインの制御に使用されます。クロック制御レジスタ内の対応するビットを書き込むと、回路は対応する機能を実行します。制御ロジック回路は、コントロールレジスタに書き込むことで回路全体を制御することができます。回路の動作状態はステータスレジスタを読み取ることで知ることができます。
  • 多くのバイトを送受信する場合、DMA を使用すると効率が向上します。

4.3 I2Cの基本構造

画像.png

  • SDA: I2C は上位から優先されるため、このシフト レジスタは左にシフトします。送信時には、最初に上位ビットが移動され、次に 2 番目の上位ビットが移動されます。 SCL クロックは 1 回シフトされ、8 回シフトされ、上位ビットから下位ビットまで 8 バイトを SDA ラインに配置できます。受信時には、データは GPIO ポートを介して右から移動され、最終的に 8 回、1 バイトが受信されます。出力データは GPIO ポートを介してポートに出力されます。入力データは GPIO ポートを介してシフト レジスタに入力されます。
  • GPIO ポートは多重化オープン ドレイン出力モードに設定する必要があります。多重化とは、GPIO ポートのステータスがオンチップ ペリフェラルによって制御されることを意味し、オープン ドレイン出力は I2C プロトコルで必要なポート構成です。オープンドレイン出力モードでもGPIOポートへの入力は可能です。
  • SCL: クロック コントローラーは GPIO を通じてクロック ラインを制御します。
    画像.png

4.4 ホスト送信

画像.png
STM32 が指定されたアドレスに書き込みたい場合は、トランスミッターの送信シーケンス図に従う必要があります。

  • 7 ビットアドレス: スタートコンディションがアドレス指定された後の 1 バイト
  • 10 ビット アドレス: 開始条件の後の 2 バイトはアドレス指定であり、その内容は 5 ビットのフラグ ビット 11110 + 2 ビット アドレス + 1 読み取り/書き込みビットです。純粋な 8 ビット アドレス。
  • 7ビットプロセス:スタート、スレーブアドレス、レスポンス、データ、レスポンス、データ、レスポンス...ストップ
  1. 初期化後、バスはデフォルトでアイドル状態になり、STM はデフォルトでスレーブ モードになります。スタート コンディションを生成するには、STM32 が制御レジスタ (CR1) に書き込み、1 を書き込む必要があります。その後、STM32 はスレーブ モードからマスター モードに変わります。 。

画像.png

  1. EV5 イベントはステータス レジスタのビットであり、SB=1 はスタート コンディションが送信されたことを意味します。

画像.png

  1. 次に、スレーブ アドレスのバイトを送信できます。スレーブ アドレスはデータ レジスタ DR に書き込まれる必要があります。DR に書き込んだ後、ハードウェア回路はアドレス バイトをシフト レジスタに自動的に転送し、その後、ノードが転送されます。が I2C バスに送信され、ハードウェアが自動的に応答を受信し、応答がない場合はハードウェアが応答失敗フラグを設定し、そのフラグが割り込みを適用して通知します。
  2. アドレッシングが完了すると、EV6 イベントが発生し、ADDR フラグ ビットが 1 になります。このフラグ ビットは、マスター モードでのアドレス送信の終了を示します。

画像.png

  1. EV8_1 イベントは、TxE フラグが 1、シフト レジスタが空、データ レジスタが空であることを意味します。DR に書き込んだ後、シフト レジスタが空であるため、データ レジスタ DR に書き込む必要があります。すぐに送信するシフトレジスタに切り替わります。 EV8 イベントが発生します。シフト レジスタは空ではなく、データ レジスタは空です。これは、シフト レジスタがデータを送信していることを意味します。したがって、このプロセスではデータ 1 のタイミングが生成されます。この時点で、データ 2 がデータ レジスタに書き込まれ、応答ビットを受信した後、送信のためにデータ ビットがシフト レジスタに転送され、待機しています。このときの状態は、シフト レジスタが空ではないことです。データレジスタは空なので、このとき、再びEV8事件が発生しました。
  2. 次にデータ 2 が送信されますが、今度は次のデータがデータレジスタに書き込まれて待機しています。 EV8 イベントが検出されると、次のデータを書き込むことができます。
  3. 送信したいデータが書き込まれた後は、新しいデータはデータ レジスタに書き込まれません。シフト レジスタ内の現在のデータ シフトが完了すると、シフト レジスタは空になり、データ レジスタも空になります。 EV8_2 イベント、TxE=1 は、シフト レジスタが空、データ レジスタが空であることを意味します。BTF: バイト送信終了フラグ。送信時、新しいデータが送信され、データ レジスタに新しいデータが書き込まれていないとき。 EV8_2 を検出すると、終了条件 Stop を生成できます。終了条件を生成するには、当然ながら、制御可能な対応するビットが制御レジスタ内に存在する必要があります。以上で送信シーケンスが終了する。

4.5 ホスト受信

画像.png
7ビットマスタ受信:スタート、スレーブアドレス+リード、レスポンス受信、データ受信、レスポンス送信…データ受信、無応答、終了

  1. まず、制御レジスタの Start ビットを書き込んでスタート コンディションを生成し、EV5 イベント (スタート コンディションの送信を示す) を待ちます。
  2. アドレッシング後、レスポンスを受信し、終了後(アドレッシングが完了したことを示す)EV6イベントが発生します。
  3. データ 1 は、データがシフト レジスタを介して入力されていることを意味します。
  4. EV6_1 は、応答を受信した後、データがまだシフト中であることを示します。これは、シフト レジスタがデータ 1 の 1 バイトを正常に移動したことを意味します。このとき、シフトされたバイトはデータ レジスタ全体に転送され、同時に RxNE フラグがセットされ、データ レジスタが空ではないこと、つまり 1 バイトのデータが受信されたことを示します。これは、DR レジスタを読み取るとイベントがクリアされることを意味します。データが受信されたことを確認します。データを読み取った後は、イベントは発生しません。
  5. もちろん、データ 1 が読み込まれていない場合は、データ 2 をシフトレジスタに直接移動することもできます。その後、データ 2 のシフトが完了し、データ 2 を受信し、EV7 イベントが発生し、データ 2 が読み出され、データ 2 が読み出されます。 EV7イベントはなくなりました。
  6. これ以上受信する必要がない場合は、最後のタイミングユニットが発生し、終了条件要求、つまり EV7_1 イベントが設定されたときに、応答ビット制御レジスタ ACK を事前に 0 に設定する必要があります。その後、無応答 NA になります。 STOP ビットがセットされているため、終了条件が生成されます。

4.6 ソフトウェア/ハードウェア波形の比較

画像.png

画像.png

5. 10-2 ハードウェア I2C 読み書き MPU6050

5.1 I2C ライブラリ関数


void I2C_DeInit(I2C_TypeDef* I2Cx);
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);

// 生成起始条件、终止条件
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);

// 配置CR1的ACK这一位,0:无应答,1:应答
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);

// 发送数据,把Data数据直接写入到DR寄存器
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
// 读取DR,接收数据
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

// Address参数也是通过DR发送的,但在发送之前,设置了Address最低位的读写位,
// I2C_Direction不是发送,是把Address的最低位置1(读),否则最低位清0(写)
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

5.2 ハードウェア I2C 読み取りおよび書き込み MPU6050 の実装

5.2.1 ハードウェア接続

SCL は STM32 の PB10 ピンに接続され、SDA は PB11 ピンに接続されます。ここではソフトウェアレベルの反転が実装されているため、2 つの GPIO ポートを自由に接続できます。
OLEDの先頭データはデバイスのID番号です。このMPU6050のID番号は0x68で固定です。以下、左の3つは加速度センサーの出力データで、それぞれX軸、Y軸、Z軸の加速度、右の3つはジャイロセンサーの出力データです。これらは、X 軸、Y 軸、Z 軸の角速度です。

5.2.2 運用結果

20240406_172128.jpg より

5.2.3 コードの実装プロセス

  1. I2C ペリフェラルの構成、I2C ペリフェラルの初期化、MyI2C_Init の置き換え
    (1) I2C ペリフェラルと対応する GPIO ポートのクロックをオンにします。
    (2) I2C ペリフェラルに対応する GPIO ポートを多重化オープンドレイン モードに初期化します。
    (3) この構造体を使用して I2C 全体を構成する
    (4) I2C_Cmd、I2C を有効にする
  2. 周辺回路の制御、指定アドレスへの書き込みタイミングの実現、WriteRegの置き換え
  3. 指定したアドレスを読み出すタイミングを実現し、ReadRegを置き換えるよう周辺回路を制御します。

5.2.4 コード

  1. MPU6050コード:
#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"

/*
1. 配置I2C外设,对I2C外设进行初始化,替换MyI2C_Init
   (1)开启I2C外设和对应GPIO口的时钟,
   (2)把I2C外设对应的GPIO口初始化为复用开漏模式
   (3)使用结构体,对整个I2C进行配置
   (4)I2C_Cmd,使能I2C
2. 控制外设电路,实现指定地址写的时序,替换WriteReg
3. 控制外设电路,实现指定地址读的时序,替换ReadReg
*/


// 宏定义:从机地址
#define MPU6050_ADDRESS  0xD0
 
// 超时退出
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t TimeOut;
	TimeOut = 10000;
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS) 
	{   
		TimeOut --;
		if (TimeOut == 0)
		{
			break;// 跳出循环,直接执行后面的程序
		}
	}
}

// 指定地址写:发送器传送时序
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
//	MyI2C_Start();
//	MyI2C_SendByte(MPU6050_ADDRESS);// 发送从机地址后,接收应答
//	MyI2C_ReceiveAck();// 寻址找到从机之后,继续发送下一个字节
//	MyI2C_SendByte(RegAddress); // 指定寄存器地址,存在MPU6050的当前地址指针里,用于指定具体读写哪个寄存器
//	MyI2C_ReceiveAck();
//	MyI2C_SendByte(Data);// 指定写入指定寄存器地址下的数据
//	MyI2C_ReceiveAck();
//	MyI2C_Stop();
	
	I2C_GenerateSTART(I2C2, ENABLE); // 起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //EV5事件
	
	// 发送从机地址,接收应答。该函数自带了接收应答,如果应答错误,硬件会通过标志位和中断来提示我们
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //EV6事件
	
	// 直接写入DR,发送数据
	I2C_SendData(I2C2, RegAddress);
	// 写入了DR,DR立刻转移到移位寄存器进行发送,EV8事件出现的非常快,基本不用等。因为有两级缓存,
	// 第一个数据写进DR了,会立刻跑到移位寄存器,这时不用等第一个数据发完,第二个数据就可以写进去等着了。
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); //EV8事件
	
	I2C_SendData(I2C2, Data);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //EV8_2事件
	
	I2C_GenerateSTOP(I2C2, ENABLE);
}

// 指定地址读:接收器传送序列
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
//	MyI2C_Start();
//	MyI2C_SendByte(MPU6050_ADDRESS);
//	MyI2C_ReceiveAck();
//	MyI2C_SendByte(RegAddress); // 指定地址:就是设置了MPU6050的当前地址指针
//	MyI2C_ReceiveAck();
//	// 转入读的时序,重新指定读写位,就必须重新起始
//	MyI2C_Start();// 重复起始条件
//	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);// 指定从机地址和读写位,0xD0是写地址,或上0x01变为0xD1,读写位为1,接下来要读从机的数据
//	MyI2C_ReceiveAck(); // 接收应答后,总线控制权就正式交给从机了,从机开始发送一个字节
//	Data = MyI2C_ReceiveByte();// 主机接收一个字节,该函数返回值就是接收到的数据
//	// 主机接收一个字节后,要给发送从机一个应答
//	MyI2C_SendAck(1);// 参数为0,就是给从机应答,参数给1,就是不给从机应答
//	MyI2C_Stop();
	
	
	I2C_GenerateSTART(I2C2, ENABLE); // 起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //EV5事件
	
	// 发送从机地址,接收应答。该函数自带了接收应答,如果应答错误,硬件会通过标志位和中断来提示我们
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //EV6事件
	
	// 直接写入DR,发送数据
	I2C_SendData(I2C2, RegAddress);
	// 写入了DR,DR立刻转移到移位寄存器进行发送,EV8事件出现的非常快,基本不用等。因为有两级缓存,
	// 第一个数据写进DR了,会立刻跑到移位寄存器,这时不用等第一个数据发完,第二个数据就可以写进去等着了。
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //EV8_2事件
	
	I2C_GenerateSTART(I2C2, ENABLE);// 重复起始条件
	
	// 主机接收
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //EV5事件
	// 接收地址
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver); // 函数内部就自动将该地址MPU6050_ADDRESS的最低位置1
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); //EV6事件
	
	// 在最后一个数据之前就要把应答位ACK置0,同时把停止条件生成位STOP置1
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED); //EV7事件
	// 等EV7事件产生后,一个字节的数据就已经在DR里面了。
	// 读取DR就可拿出该字节
	Data = I2C_ReceiveData(I2C2); // 返回值就是DR的数据
	// 在接收函数的最后,要恢复默认的ACK = 1。
	// 默认状态下ACK就是1,给从机应答,在收最后一个字节之前,临时把ACK置0,给非应答,
	// 所以在接收函数的最后,要恢复默认的ACK = 1,这个流程是为了方便指定地址收多个字节。
	I2C_AcknowledgeConfig(I2C2, ENABLE);
	
	return Data;
}

void MPU6050_Init(void)
{
	
//	MyI2C_Init();
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; // 模式
	I2C_InitStructure.I2C_ClockSpeed = 50000; // 时钟速度,最大400kHz的时钟频率
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	// 时钟占空比,只有在时钟频率大于100kHz,也就是进入到快速状态时才有用,小于100kHz,占空比是固定的1:1,
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // STM32作为从机,可以响应几位的地址
	I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 自身地址1,也是作为从机使用,
	I2C_Init(I2C2, &I2C_InitStructure); 
	
	I2C_Cmd(I2C2,ENABLE);
	
	// 写入一些寄存器对MPU6050硬件电路进行初始化配置
	// 电源管理寄存器1:设备复位:0,不复位;睡眠模式:0,解除睡眠:循环模式:0,不循环;无关位i:0;温度传感器失能:0,不失能;最后三位选择时钟:000,选择内部时钟,001,选择x轴的陀螺仪时钟,
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);// 解除睡眠,选择陀螺仪时钟
	// 电源管理寄存器2:前两位,循环模式唤醒频率:00,不需要;后6位,每一个轴的待机位:全为0,不需要待机;
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00); // 均不待机
	// 采样率分频:该8位决定了数据输出的快慢,值越小越快
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);// 采样分频:10分频
	// 配置寄存器:外部同步:全为0,不需要;数字低通滤波器:110,最平滑的滤波
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);// 滤波参数给最大
	// 陀螺仪配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位无关位:为0
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);// 陀螺仪和加速度计都选最大量程
	// 加速度计配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位高通滤波器:用不到,为000
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
		
}

// 获取芯片的ID号
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

// 获取寄存器数据的函数,返回6个int16_t的数据,分别表示XYZ的加速度值和陀螺仪值
// 指针地址传递的方法,返回多值
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
	                 int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;
	// 加速度计X
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL; // 高8位左移8位,再或上低8位,得到加速度计X轴的16位数据
	// 加速度计Y
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	// 加速度计Z
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	// 陀螺仪X
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	// 陀螺仪Y
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	// 陀螺仪Z
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

  • 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
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  1. メイン.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;// 接收XYZ轴的加速度值和陀螺仪值



int main(void)
{
	OLED_Init();
	MPU6050_Init();
	
	OLED_ShowString(1,1,"ID:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1, 4, ID, 2);
	
//	// 指定地址写
//	MyI2C_Start(); // 产生起始条件,开始一次传输
//	// 主机首先发送一个字节,内容时从机地址+读写位,进行寻址
//	MyI2C_SendByte(0xD0);  // 1101 000 0,0代表即将进行写入操作
//	// 发送一个字节后,要接收一下应答位,看看从机有没有收到刚才的数据
//	uint8_t Ack = MyI2C_ReceiveAck();
//	// 接收应答之后,要继续发送一个字节,写入寄存器地址
//	MyI2C_Stop();
//	
//	OLED_ShowNum(1, 1, Ack, 3);
	
//	// 指定地址读
//	uint8_t ID = MPU6050_ReadReg(0X75);// 返回值是0x68
//	OLED_ShowHexNum(1, 1, ID, 2);
	
//	// 指定地址写,需要先解除睡眠模式,否则写入无效
//	// 睡眠模式是电源管理寄存器1的这一位SLEEP控制的,把该寄存器写入0x00,解除睡眠模式
//	// 该寄存器地址是0x6B
//	MPU6050_WriteReg(0x6B, 0x00);
//	// 采样率分频寄存器,地址是0x19,值的内容是采样分频
//	MPU6050_WriteReg(0x19, 0xAA);
//	
//	uint8_t ID = MPU6050_ReadReg(0X19);
//	OLED_ShowHexNum(1, 1, ID, 2);//显示0x19地址下的内容,应该是0xAA
	
	
	
	while(1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}
  • 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
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57