Technologieaustausch

STM32-I2C

2024-07-12

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

Dieser Inhalt basiert aufJiangxie-Technologie STM32Zusammengestellt nach Videostudie.

1. I2C-Kommunikation

1.1 Einführung in die I2C-Kommunikation

  • I2C (Inter IC Bus) ist ein von Philips entwickelter universeller Datenbus
  • Zwei Kommunikationsleitungen: serielle Taktleitung SCL (Serial Clock), serielle Datenleitung SDA (Serial Data).
  • Synchron, Halbduplex, Single-Ended, Multi-Device
  • Mit Daten antworten
  • Unterstützt die Montage mehrerer Geräte am Bus (ein Master und mehrere Slaves, mehrere Master und mehrere Slaves)
    • Ein Master, mehrere Slaves: Der Mikrocontroller fungiert als Host und dominiert den Betrieb des I2C-Busses. Alle am I2C-Bus montierten externen Module sind Slaves, die den I2C-Bus nur nach Benennung durch den Host steuern und nicht berühren können ohne Erlaubnis. I2C-Bus, um Konflikte zu vermeiden.
    • Multi-Master und Multi-Slave: Jedes Modul am Bus kann aktiv herausspringen und als Master fungieren. Wenn ein Buskonflikt auftritt, führt das I2C-Protokoll eine Schlichtung durch. Die Partei, die die Schlichtung gewinnt, erhält die Kontrolle über den Bus und die unterlegene Partei wird automatisch zum Slave.

bild.png

1.2 Hardware-Schaltung

  • Die SCL aller I2C-Geräte sind miteinander verbunden, und der SDA ist miteinander verbunden.
  • Sowohl SCL als auch SDA des Geräts müssen im Open-Drain-Ausgabemodus konfiguriert sein.
  • Fügen Sie SCL und SDA jeweils einen Pull-up-Widerstand hinzu. Der Widerstandswert beträgt im Allgemeinen etwa 4,7 kΩ

Abbildung 1Figur 2
bild.png

  • Ein Master und mehrere Slaves: Die CPU ist ein Single-Chip-Mikrocomputer. Als Master des Busses verfügt sie jederzeit über die volle Kontrolle über die SCL-Leitung. Darüber hinaus kann der Host im Ruhezustand die Steuerung von SDA aktiv initiieren. Nur wenn der Slave Daten sendet und der Slave antwortet, überträgt der Host die Steuerung von SDA auf den Slave.
  • Der gesteuerte IC ist ein am I2C-Bus montierter Slave, der ein Lagesensor, OLED, Speicher, Uhrenmodul usw. sein kann. Die Leistung des Slaves ist für die SCL-Taktleitung relativ gering und kann jederzeit nur passiv gelesen werden. Der Slave darf die SCL-Leitung nicht steuern. Für die SDA-Datenleitung ist es dem Slave nicht gestattet, aktiv die Steuerung von SDA einzuleiten. Erst nachdem der Master einen Befehl zum Lesen des Slaves gesendet hat oder wenn der Slave antwortet, kann der Slave kurzzeitig die Kontrolle über den SDA erlangen.
  • Abbildung 2: SCL links und SDA rechts. Alle Daten können über einen Datenpuffer oder einen Schmitt-Trigger eingegeben werden.
    • Da der Eingang keinen Einfluss auf die Schaltung hat, kann jedes Gerät jederzeit einen Eingang haben.
    • Der Ausgang verwendet eine Open-Drain-Ausgangskonfiguration. Wenn der Ausgang niedrig ist, ist der Schalter eingeschaltet und der Pin ist direkt mit Masse verbunden. Wenn der Ausgang hoch ist, ist der Stift ausgeschaltet ist mit nichts verbunden und befindet sich in einem schwebenden Zustand, sodass alle Geräte nur einen niedrigen Pegel, aber keinen hohen Pegel ausgeben können. Um ein durch hohen Pegel verursachtes Schweben zu vermeiden, müssen SCL und SDA einen externen Pull-up-Widerstand haben Der Bus wird durch einen Widerstand auf einen hohen Pegel gezogen, es handelt sich also um einen schwachen Pull-up. Auf diese Weise wird erstens das Phänomen des Stromkurzschlusses vollständig eliminiert und die Sicherheit des Stromkreises gewährleistet; zweitens wird ein häufiges Umschalten der Pin-Modi vermieden. Im Open-Drain-Modus entspricht die Ausgabe eines hohen Pegels dem Trennen des Pins, sodass Sie vor der Eingabe direkt einen hohen Pegel ausgeben können. Drittens weist dieser Modus ein „Wired AND“-Phänomen auf. Solange ein oder mehrere Geräte einen niedrigen Pegel ausgeben, ist der Bus auf einem hohen Pegel. . Daher kann I2C dieses Phänomen nutzen, um Taktsynchronisierung und Busarbitrierung im Multi-Master-Modus durchzuführen. Obwohl SCL hier den Push-Pull-Ausgang in einem Master- und mehreren Slave-Modus verwenden kann, verwendet es dennoch den Open-Drain-Plus-Pull-Out-Ausgangsmodus.

1.3 I2C-Timing-Grundeinheit

1.3.1 Startbedingungen und Endbedingungen

  • Ausgangsbedingung: Während SCL-High-Pegel schaltet SDA von High-Pegel auf Low-Pegel um
  • Beendigungsbedingung: Während SCL-High-Pegel wechselt SDA von Low-Pegel auf High-Pegel

bild.png

  • unter Anfangsbedingungen : Wenn sich der I2C-Bus im Leerlaufzustand befindet, befinden sich sowohl SCL als auch SDA auf einem hohen Pegel, d in einem ruhigen Zustand. Wenn der Host Daten senden und empfangen muss, muss er zuerst die Stille des Busses durchbrechen und eine Startbedingung generieren, dh SCL befindet sich auf einem hohen Pegel, ohne ihn zu berühren, und dann SDA nach unten ziehen, um eine fallende Flanke zu erzeugen. Wenn der Slave den hohen SCL-Pegel und das fallende SDA-Flankensignal erfasst, setzt er sich selbst zurück und wartet auf den Anruf des Masters. Nach der fallenden Flanke von SDA muss der Host SCL erneut herunterziehen, um einerseits den Bus zu belegen und andererseits auch das Spleißen der Basiseinheit zu erleichtern. Später wird sichergestellt, dass mit Ausnahme der Start- und Stoppbedingungen der SCL jeder sequentiellen Einheit mit einem niedrigen Pegel beginnt und mit einem niedrigen Pegel endet.
  • Im Zustand der Beendigungsbedingung : SCL lässt zuerst los und springt auf den hohen Pegel zurück, dann lässt SDA los und springt auf den hohen Pegel zurück, wodurch eine steigende Flanke erzeugt wird, die die Abbruchbedingung auslöst. Nach der gleichzeitigen Beendigungsbedingung sind sowohl SCL als auch SDA hoch und kehren in den anfänglichen ruhigen Zustand zurück.
    Start und Stopp werden vom Host generiert, und der Slave darf keinen Start und Stopp generieren. Daher muss der Slave im Ruhezustand des Busses immer seine Hände loslassen und darf nicht herausspringen und den Bus berühren.

1.3.2 Ein Byte senden

  • Senden Sie ein Byte: Während des niedrigen SCL-Pegels legt der Host die Datenbits der Reihe nach auf die SDA-Leitung (hohes Bit zuerst) und gibt dann SCL frei. Der Slave liest die Datenbits während des hohen SCL-Pegels, sodass SDA nicht zulässig ist Um während der hohen SCL-Ebene Daten zu erhalten, führen Sie den oben genannten Vorgang achtmal durch, um ein Byte zu senden.

Der Low-Level-Host stellt Daten ein und der High-Level-Slave liest Daten.
bild.png
Nach der Startbedingung muss auch das erste Byte vom Host gesendet werden. Wenn SCL niedrig ist und der Host 0 senden möchte, zieht er SDA auf niedrig; wenn er 1 senden möchte, lässt er los und SDA springt auf hohen Pegel zurück. Während der niedrigen SCL-Stufe darf sich die SDA-Stufe ändern. Nachdem die Daten platziert wurden, gibt der Host die Taktleitung frei und SCL springt auf die hohe Stufe zurück. Während des High-Pegels ist es die Zeit, in der der Slave SDA liest, sodass sich SDA während des High-Pegels nicht ändern darf. Nachdem SCL einen hohen Pegel erreicht hat, muss der Slave SDA so schnell wie möglich lesen. Im Allgemeinen hat der Slave den Lesevorgang bei der steigenden Flanke von SCL abgeschlossen. Da die Uhr vom Master gesteuert wird, weiß der Slave nicht, wann die fallende Flanke auftritt, sodass der Slave die Daten an der steigenden Flanke von SCL liest. Nachdem der Host SCL für eine gewisse Zeit losgelassen hat, kann er SCL weiterhin auf Low ziehen und das nächste Bit übertragen. Der Host muss außerdem Daten so schnell wie möglich nach der fallenden Flanke von SCL auf SDA übertragen. Da der Host jedoch die Kontrolle über die Uhr hat, muss er nur dann Daten auf den SDA übertragen, wenn der niedrige Pegel niedrig ist. Nachdem die Daten freigegeben wurden, gibt der Host SCL erneut frei, SCL ist hoch und der Slave liest dieses Bit. Schleifen Sie diesen Prozess ab: Der Host zieht SCL auf Low, legt die Daten auf SDA, der Host gibt SCL frei und der Slave liest SDA-Daten. Bei der SCL-Synchronisation sendet der Master nacheinander und der Slave empfängt nach 8 Zyklen 8-Bit-Daten, also ein Byte.
Da es das höherwertige Bit zuerst ist, ist das erste Bit das höchste Bit B7 eines Bytes und das niedrigste Bit B0 wird zuletzt gesendet.

1.3.3 Ein Byte empfangen

  • ein Byte erhalten: Während des niedrigen SCL-Pegels legt der Slave die Datenbits der Reihe nach auf die SDA-Leitung (hohes Bit zuerst) und gibt dann SCL frei. Der Host liest die Datenbits während des hohen SCL-Pegels, sodass SDA nicht zulässig ist Um während der hohen SCL-Ebene Daten zu haben, führen Sie den oben genannten Vorgang achtmal durch, um ein Byte zu empfangen (der Host muss SDA vor dem Empfang freigeben).

Low-Level-Slave stellt Daten ein, High-Level-Host liest Daten
bild.png
SDA-Leitung: Der Master muss SDA freigeben, bevor er empfängt. Wenn der Slave 0 senden muss, zieht er SDA auf Low springt auf hohes Niveau zurück. Low-Level konvertiert Daten, High-Level liest Daten. Die durchgezogene Linie stellt den vom Master gesteuerten Pegel dar, und die gepunktete Linie stellt den vom Slave gesteuerten Pegel dar. SCL wird während des gesamten Prozesses vom Host gesteuert. Der SDA-Host muss vor dem Empfang freigegeben und zur Steuerung an den Slave übergeben werden. Da der SCL-Takt vom Host gesteuert wird, erfolgt die Datenkonvertierung des Slaves grundsätzlich an der fallenden Flanke von SCL, und der Host kann jederzeit lesen, wenn SCL hoch ist.

1.3.4 Antwort senden und Antwort empfangen

  • Antwort senden: Nach dem Empfang eines Bytes sendet der Host beim nächsten Takt ein Datenbit. Daten 0 zeigt eine Antwort an, und Daten 1 zeigen eine Nichtantwort an.
  • Antwort erhalten: Nachdem der Host ein Byte gesendet hat, empfängt er beim nächsten Takt ein Datenbit, um zu bestimmen, ob der Slave antwortet. Daten 0 zeigen eine Antwort an, Daten 1 zeigen eine Nichtantwort an (der Host muss SDA vor dem Empfang freigeben).

bild.png
Das heißt, nach dem Aufruf des Sendezeitpunkts eines Bytes muss der Zeitpunkt des Aufrufs der Empfangsantwort folgen, anhand derer ermittelt wird, ob der Slave die ihm gerade übergebenen Daten empfangen hat. Wenn der Slave es empfängt, sollte der Slave im Antwortbit, wenn der Master SDA freigibt, SDA sofort herunterziehen, und dann, während der SCL-High-Pegel ist, liest der Host das Antwortbit. Wenn das Antwortbit 0 ist, bedeutet dies, dass der Slave es tatsächlich empfangen hat.
Wenn Sie ein Byte empfangen, müssen Sie die Sendeantwort aufrufen. Der Zweck des Sendens einer Antwort besteht darin, dem Slave mitzuteilen, ob Sie mit dem Senden fortfahren möchten. Wenn der Slave-Computer nach dem Senden eines Datenelements eine Antwort vom Master erhält, sendet der Slave-Computer weiterhin. Wenn der Slave-Computer keine Antwort vom Master-Computer erhält, geht der Slave-Computer davon aus, dass ein Datenelement vorhanden ist wurde gesendet, aber der Master-Computer ignoriert mich möglicherweise. Der Host möchte dies nicht. Zu diesem Zeitpunkt gibt der Slave gehorsam die Kontrolle über SDA frei, um eine Beeinträchtigung der nachfolgenden Vorgänge des Hosts zu verhindern.

1.4 I2C-Timing

1.4.1 Geben Sie die zu schreibende Adresse an

  • Geben Sie die zu schreibende Adresse an
  • Schreiben Sie für das angegebene Gerät (Slave-Adresse) die angegebenen Daten (Daten) an die angegebene Adresse (Reg-Adresse) (d. h. die Registeradresse des angegebenen Geräts).

bild.png
Verfahren:
(1) Startbedingungen
(2) Zeitpunkt des Sendens eines Bytes – 0xD0 (Slave-Adresse (7 Bit) + Schreiben (1 Bit)-0) (1101 0000)
(3) Antwort empfangen: RA = 0 (Antwort vom Slave empfangen)
(4) Angegebene Adresse: 0x19 (0001 1001)
(5) Antwort empfangen: RA = 0 (Antwort vom Slave empfangen)
(6) Spezifizierte Daten schreiben: 0xAA (1010 1010)
(7) Antwort empfangen: RA = 0
(8) Stoppbit P (Abbruchbedingung)

  • Nach der Startbedingung muss der Zeitpunkt zum Senden eines Bytes sein. Der Inhalt des Bytes muss die Slave-Adresse + Lese- und Schreibbits sein. Die Slave-Adresse beträgt 7 Bits und die Lese- und Schreibbits sind genau 1 Bit 8 Bit. Das Senden der Slave-Adresse dient der Bestimmung des Kommunikationsobjekts und das Senden des Lese- und Schreibbits dient der Bestätigung, ob als nächstes geschrieben oder gelesen werden soll. Jetzt sendet der Host ein Datenelement, das zuerst in ein Hexadezimalformat umgewandelt wird, nämlich 0xD0. Die folgende Einheit ist das Antwortbit (RA) des empfangenden Slaves. Das Schreibbit endet und SCL wird auf Low gesetzt. Danach muss der Host SDA freigeben, gefolgt vom Bestätigungsbit RA.
  • Der High-Pegel nach dem Ende des Antwortbits RA wird durch die Freigabe von SDA durch den Slave erzeugt. Da der Slave so schnell wie möglich Daten austauschen möchte, ist die steigende Flanke von SDA erforderlich Die fallende Flanke von SCL erfolgte fast gleichzeitig.
  • Wenn Sie nach Abschluss der Antwort weiterhin ein Byte senden, kann das zweite Byte an das Innere des angegebenen Geräts gesendet werden. Das Slave-Gerät kann die Verwendung des zweiten Selbst und nachfolgender Bytes definieren. Im Allgemeinen kann das zweite Byte eine Registeradresse oder ein Befehlssteuerwort usw. sein, und das dritte Byte ist der Inhalt, den der Host in die Registeradresse schreiben möchte (zweites Byte).
  • P ist das Stoppbit.

Der Zweck dieses Datenrahmens besteht darin, für das Gerät, das die Slave-Adresse 1101000 angibt, die Daten 0xAA in sein internes Register an der Adresse 0x19 zu schreiben.
0 bedeutet: Der Host führt im nachfolgenden Timing einen Schreibvorgang aus;
1 bedeutet: Der Host führt in der nachfolgenden Zeitsequenz einen Auslesevorgang durch;

1.4.2 Aktuelle Adresse lesen

  • Aktuelle Adresse gelesen
  • Lesen Sie für das angegebene Gerät (Slave-Adresse) die Slave-Daten (Daten) an der Adresse, die durch den aktuellen Adresszeiger angegeben wird.

bild.png
Verfahren:
(1) Startbedingungen
(2) Zeitpunkt des Sendens eines Bytes – 0xD1 (Slave-Adresse (7 Bit) + Lesen (1 Bit)-1) (1101 0001)
(3) Antwort empfangen: RA = 0 (Antwort vom Slave empfangen)
(4) Slave-Daten lesen: 0x0F (0000 1111)
(7) Antwort senden: SA = 0
(8) Stoppbit P (Abbruchbedingung)

  • Das Lese- und Schreibbit ist 1 und zeigt an, dass der nächste Lesevorgang ausgeführt werden soll. Nachdem der Slave geantwortet hat (RA=0), wird die Richtung der Datenübertragung umgekehrt. Der Master möchte die Kontrolle über SDA an den Slave übergeben, und der Master ruft den Zeitpunkt des Empfangs eines Bytes auf, um den Empfangsvorgang auszuführen.
  • Im zweiten Byte erhält der Slave die Erlaubnis vom Master und kann auf SCL schreiben. Der Master liest SDA auf der hohen SCL-Ebene 8 Bit, ein Byte der vom Slave gesendeten Daten wird empfangen, nämlich 0x0F. Aber welches Register des Slaves ist 0x0F? Beim Lesetiming legt das I2C-Protokoll fest, dass bei der Adressierung des Hosts das Lese- und Schreibflag einmal auf 1 gesetzt ist. Das nächste Byte wechselt sofort zum Lesetiming. Daher beginnt der Host mit dem Empfang, bevor er angeben kann, welches Register er lesen möchte. Daher gibt es hier keinen Link zur Angabe der Adresse. In der Slave-Maschine sind alle Register einem linearen Bereich zugeordnet, und es gibt eine separate Zeigervariable, die eines der Register angibt. Dieser Zeiger zeigt standardmäßig auf die Adresse 0 und jedes Mal, wenn ein Byte geschrieben wird Nach dem Lesen eines Bytes erhöht sich der Zeiger automatisch einmal und bewegt sich zur nächsten Position. Wenn der Host dann den Zeitpunkt des Lesens der aktuellen Adresse nicht angibt, kehrt der Slave zu dem Register zurück, auf das er zeigt aktueller Zeigerwert.

1.4.3 An angegebener Adresse lesen

  • Geben Sie die Adresse zum Lesen an
  • Lesen Sie für das angegebene Gerät (Slave-Adresse) unter der angegebenen Adresse (Reg-Adresse) die Slave-Daten (Daten).

bild.png
Zuerst starten, dann den Start wiederholen und dann stoppen
Verfahren:
(1) Startbedingungen
(2) Zeitpunkt des Sendens eines Bytes – 0xD0 (Slave-Adresse (7 Bit) + Schreiben (1 Bit)-0) (1101 0000)
(3) Antwort empfangen: RA = 0 (Antwort vom Slave empfangen)
(4) Angegebene Adresse: 0x19 (0001 1001)
(5) Antwort empfangen: RA = 0 (Antwort vom Slave empfangen)
(6) Wiederholen Sie die Ausgangsbedingung
(7) Zeitpunkt des Sendens eines Bytes – 0xD1 (Slave-Adresse (7 Bit) + Lesen (1 Bit)-1) (1101 0001)
(8) Antwort empfangen: RA = 0
(9) Slave-Daten lesen: 0xAA (1010 1010)
(10) Antwort senden: SA = 0
(11) Stoppbit P (Abbruchbedingung)

  • Der erste Teil besteht darin, an die angegebene Adresse zu schreiben, es wird jedoch nur die Adresse angegeben, und es bleibt keine Zeit zum Schreiben. Der zweite Teil besteht darin, die aktuelle Adresse zu lesen, da die Adresse gerade angegeben wurde, sodass die aktuelle Adresse gelesen wird noch einmal angerufen.
  • Die angegebene Slave-Adresse ist 1101000, das Lese-/Schreib-Flag ist 0, und nachdem der Slave geantwortet hat, wird ein weiteres Byte (das zweite Byte) geschrieben, um die Adresse 0x19 anzugeben Adresszeiger, das heißt, nachdem der Slave die Daten empfangen hat, zeigt sein Registerzeiger auf die Position 0x19.
  • Sr ist eine wiederholte Startbedingung, die dem Starten eines neuen Timings entspricht. Da das angegebene Lese- und Schreibflag nur dem ersten Byte der Startbedingung folgen kann, ist dies nur möglich, wenn Sie die Lese- und Schreibrichtung ändern möchten eine weitere Startbedingung.
  • Nach der Startbedingung wird das Lese-Schreib-Flag-Bit erneut adressiert und angegeben. Zu diesem Zeitpunkt ist das Lese-Schreib-Flag-Bit 1, was darauf hinweist, dass es gelesen werden soll. Dann empfängt der Host ein Byte, bei dem es sich um die Daten handelt 0xAA an der Adresse 0x19.

2. MPU6050

2.1 Einführung in MPU6050

  • MPU6050 ist ein 6-Achsen-Lagesensor, der die Beschleunigungs- und Winkelgeschwindigkeitsparameter der eigenen X-, Y- und Z-Achsen messen kann. Durch Datenfusion kann der Lagewinkel (Euler-Winkel) weiter ermittelt werden Balancieren von Fahrzeugen, Flugzeugen usw., die sich selbst in der Gestenszene erkennen müssen
  • 3-Achsen-Beschleunigungsmesser (Beschleunigungsmesser): Misst die Beschleunigung der X-, Y- und Z-Achse
  • 3-Achsen-Gyrosensor (Gyroskop): Misst die Winkelgeschwindigkeit der X-, Y- und Z-Achse

bild.png

  • Am Beispiel des Flugzeugrumpfs ist der Euler-Winkel der Winkel zwischen dem Flugzeugrumpf und den ersten drei Achsen.
    • FlugzeugDie Nase des Flugzeugs neigt sich nach unten oder oben, der Winkel zwischen dieser Achse heißtTonhöhe
    • FlugzeugDer Rumpf rollt nach links oder rechts, der Winkel zwischen dieser Achse heißtRollen
    • FlugzeugHalten Sie den Rumpf geradeDrehen Sie die Nase des Flugzeugs nach links oder rechts, der Winkel zwischen dieser Achse heißtGieren
    • Der Euler-Winkel stellt die Fluglage des Flugzeugs zu diesem Zeitpunkt dar, unabhängig davon, ob es nach oben oder unten, nach links oder rechts geneigt ist.
  • Zu den gängigen Datenfusionsalgorithmen gehören im Allgemeinen Komplementärfilterung, Kalman-Filterung usw. sowie die Lageberechnung in der Trägheitsnavigation.
  • Beschleunigungsmesser : Die gestrichelte Linie in der Mitte ist die Induktionsachse. In der Mitte befindet sich ein kleiner Schieber mit einer bestimmten Masse, der links und rechts dagegen gleiten kann. Wenn sich der Schieber bewegt, wird das darauf befindliche Potentiometer in Bewegung gesetzt. Dieses Potentiometer ist ein Spannungsteilerwiderstand. Durch Messen der vom Potentiometer ausgegebenen Spannung können Sie den Beschleunigungswert des kleinen Schiebers ermitteln. Dieser Beschleunigungsmesser ist eigentlich ein Federkraftmesser, F = ma. Wenn Sie die Beschleunigung a messen möchten, können Sie ein Objekt mit der Einheitsmasse finden und die Kraft F messen. Auf jeder der X-, Y- und Z-Achsen befindet sich ein Beschleunigungsmesser. Beschleunigungsmesser haben statische Stabilität, aber keine dynamische Stabilität.
  • Gyrosensor : In der Mitte befindet sich ein rotierendes Rad mit einer bestimmten Masse. Wenn sich das rotierende Rad mit hoher Geschwindigkeit dreht, hat das rotierende Rad die Tendenz, seinen ursprünglichen Drehimpuls beizubehalten Richtung der Rotationsachse unverändert. Wenn sich die Richtung des externen Objekts dreht, dreht sich die Richtung der internen Rotationsachse nicht, was zu einer Winkelabweichung an der Verbindung des Ausgleichsrings führt. Wenn man am Anschluss ein Drehpotentiometer anbringt und die Spannung des Potentiometers misst, erhält man den Drehwinkel. Das Gyroskop sollte in der Lage sein, den Winkel direkt zu ermitteln, aber das Gyroskop dieser MPU6050 kann den Winkel nicht direkt messen. Es misst die Winkelgeschwindigkeit, also die Winkelgeschwindigkeit des Chips, der sich um die X-Achse, Y-Achse und Z dreht -Achse. Das Integral der Winkelgeschwindigkeit ist der Winkel. Wenn das Objekt jedoch stationär ist, kann der Wert der Winkelgeschwindigkeit aufgrund von Rauschen nicht vollständig auf Null zurückgesetzt werden. Nach der kontinuierlichen Anhäufung von Integralen führt dieses kleine Rauschen dazu, dass der berechnete Winkel langsam driftet. Dies ist der Winkel, der durch Integration der Winkelgeschwindigkeit erhalten wird. Er kann den Test der Zeit nicht bestehen, aber dieser Winkel stellt kein Problem dar, egal ob er stationär oder bewegt ist, und wird durch die Bewegung des Objekts nicht beeinflusst. Gyroskope haben dynamische Stabilität, keine statische Stabilität.
  • Dem Beschleunigungsmesser zufolge verfügt er über statische Stabilität, aber nicht über dynamische Stabilität. Diese beiden Eigenschaften können wir aus den Stärken des anderen lernen und die Schwächen des anderen ergänzen Wir können sowohl statische als auch dynamische Stabilität integrieren. Die Haltung ist unangenehm.

2.2 MPU6050-Parameter

  • 16-Bit-ADC sammelt das analoge Signal des Sensors, Quantisierungsbereich: -32768~32767
  • Auswahl des Vollausschlags des Beschleunigungsmessers: ±2, ±4, ±8, ±16 (g) (1 g = 9,8 m/s2)
  • Vollskalenauswahl des Gyroskops: ±250, ±500, ±1000, ±2000 (°/s, Grad/Sekunde, Winkelgeschwindigkeitseinheit, wie viele Grad Drehung pro Sekunde) (je größer die Vollskalenauswahl, desto breiter Je kleiner der Messbereich ist, desto höher ist die Messauflösung.
  • Konfigurierbarer digitaler Tiefpassfilter: Ein Register kann konfiguriert werden, um die Tiefpassfilterung der Ausgangsdaten auszuwählen.
  • Konfigurierbare Taktquelle
  • Konfigurierbare Sampling-Frequenzteilung: Die Taktquelle kann durch den Frequenzteiler geteilt werden, um Takte für die AD-Umwandlung und andere interne Schaltkreise bereitzustellen. Durch die Steuerung des Frequenzteilungskoeffizienten können Sie die Geschwindigkeit der AD-Umwandlung steuern.
  • I2C-Slave-Adresse: 1101000 (AD0=0) oder 1101001 (AD0=1)
    • 110 1000 wird in Hexadezimalzahl umgewandelt, also 0x68, daher sagen einige, dass die Slave-Adresse von MPU6050 0x68 ist. Bei der I2C-Kommunikation sind jedoch die oberen 7 Bits des ersten Bytes die Slave-Adresse und das niedrigste Bit das Lese- und Schreibbit. Wenn Sie also denken, dass 0x68 die Slave-Adresse ist, müssen Sie beim Senden des ersten Bytes zuerst ändern 0x68 Um 1 Bit nach links verschieben (0x68 << 1), dann Bits bitweise oder nach oben lesen und schreiben, 1 lesen und 0 schreiben.
    • Eine andere Methode besteht darin, die Daten von 0x68 als Slave-Adresse um 1 Bit nach links zu verschieben (0x68 << 1), also 0xD0. In diesem Fall ist die Slave-Adresse von MPU6050 0xD0. Wenn Sie zu diesem Zeitpunkt tatsächlich das erste Byte senden möchten, verwenden Sie einfach 0xD0 als erstes Byte. Wenn Sie lesen möchten, verwenden Sie 0xD0 oder 0x01 (0xD0 | 0x01), dh 0xD1 . Diese Darstellung erfordert keine Linksverschiebungsoperation, oder mit anderen Worten, diese Darstellung integriert die Lese- und Schreibbits in die Slave-Adresse. 0xD0 ist die Schreibadresse und 0xD1 ist die Leseadresse.

2.3 Hardware-Schaltung

bild.png

StiftFunktion
VCC, GNDStromversorgung
SCL, SDAI2C-Kommunikationspin
XCL, XDAHost-I2C-Kommunikationspins
AD0Das niedrigste Bit der Slave-Adresse
INTSignalausgabe unterbrechen
  • LDO: Linearer Spannungsregler mit niedrigem Dropout, 3,3-V-Spannungsregler.
  • SCL und SDA: Es handelt sich um I2C-Kommunikationspins. Das Modul verfügt über zwei integrierte 4,7-K-Pull-up-Widerstände. Bei der Verkabelung müssen Sie also einfach SDA und SCL direkt an den GPIO-Port anschließen. Es ist nicht erforderlich, externe Pull-up-Widerstände anzuschließen .
  • XCL, XDA: Host-I2C-Kommunikationspins Diese beiden Pins dienen zur Erweiterung der Chipfunktionen. Wird normalerweise für externe Magnetometer oder Barometer verwendet. Wenn diese Erweiterungschips angeschlossen sind, kann die Host-Schnittstelle des MPU6050 direkt auf die Daten dieser Erweiterungschips zugreifen und diese über eine DMP-Einheit einlesen Haltungsberechnung.
    AD0-Pin: Dies ist das niedrigste Bit der Slave-Adresse. Wenn es mit einem niedrigen Pegel verbunden ist, lautet die 7-Bit-Slave-Adresse 1101000. Wenn es mit einem hohen Pegel verbunden ist, lautet die 7-Bit-Slave-Adresse 1101001. Im Schaltplan gibt es einen Widerstand, der standardmäßig schwach auf den niedrigen Pegel heruntergezogen wird. Wenn der Pin also schwebend bleibt, liegt er auf niedrigem Pegel. Wenn Sie ihn auf den hohen Pegel verbinden möchten, können Sie AD0 direkt an VCC anschließen und ziehen Sie ihn kräftig auf ein hohes Niveau.
  • INT: Interrupt-Ausgangspin Sie können einige Ereignisse innerhalb des Chips konfigurieren, um die Ausgabe des Interrupt-Pins auszulösen, z. B. Daten bereit, I2C-Hostfehler usw.
  • Der Chip verfügt außerdem über integrierte Funktionen: Freifallerkennung, Bewegungserkennung, Nullbewegungserkennung usw. Diese Signale können den INT-Pin auslösen, um einen Pegelübergang zu erzeugen, und bei Bedarf können Interrupt-Signale konfiguriert werden.
  • Die Stromversorgung des MPU6050-Chips beträgt 2,375–3,46 V, was einem 3,3-V-Stromversorgungsgerät entspricht und nicht direkt an 5 V angeschlossen werden kann. Daher wird ein 3,3-V-Spannungsregler hinzugefügt, und die Eingangsklemmenspannung VCC_5V kann zwischen 3,3 V und 5 V liegen. Dann gibt der 3,3-V-Spannungsregler eine stabile 3,3-V-Spannung aus, um den Chip mit Strom zu versorgen , Die Betriebskontrollleuchte leuchtet auf.

2.4 MPU6050-Blockdiagramm

bild.png

  • CLKIN und CLKOUT sind Takteingangspins und Taktausgangspins, wir verwenden jedoch im Allgemeinen den internen Takt.
  • Der graue Teil: ist der Sensor im Chip, der Beschleunigungsmesser auf der XYZ-Achse und das Gyroskop auf der XYZ-Achse.
  • Es gibt auch einen eingebauten Temperatursensor, mit dem die Temperatur gemessen werden kann.
  • Diese Sensoren entsprechen im Wesentlichen variablen Widerständen, geben eine analoge Spannung aus und führen dann eine Analog-Digital-Wandlung durch. Nach Abschluss der Wandlung werden die Daten dieser Sensoren einheitlich in die Daten eingefügt Register, das durch Auslesen des Datenregisters abgerufen werden kann. Der vom Sensor gemessene Wert. Alle Konvertierungen innerhalb dieses Chips erfolgen vollständig automatisiert.
  • Jeder Sensor verfügt über eine Selbsttesteinheit, mit der die Qualität des Chips überprüft wird. Wenn der Selbsttest gestartet wird, simuliert der Chip eine auf den Sensor ausgeübte externe Kraft größer als üblich. Selbsttestprozess: Sie können zuerst den Selbsttest aktivieren, die Daten lesen, dann den Selbsttest aktivieren, die Daten lesen, die beiden Daten subtrahieren und die resultierenden Daten als Selbsttestantwort bezeichnen. Für diese Selbsttest-Antwort gibt das Handbuch einen Bereich an. Liegt er innerhalb dieses Bereichs, bedeutet dies, dass kein Problem mit dem Chip vorliegt.
  • Ladungspumpe: Es handelt sich um eine Ladungspumpe oder Ladungspumpe. Die Ladungspumpe ist eine Boost-Schaltung.
  • Der CPOUT-Pin erfordert einen externen Kondensator.
  • Interrupt-Statusregister: Kann steuern, welche internen Ereignisse an den Interrupt-Pin ausgegeben werden.
  • FIFO: First-In-First-Out-Register, das den Datenstrom zwischenspeichern kann.
  • Konfigurationsregister: Sie können verschiedene interne Schaltkreise konfigurieren
  • Sensorregister: Datenregister, das die Daten jedes Sensors speichert.
  • Werkskalibriert: Dies bedeutet, dass die Sensoren im Inneren kalibriert sind.
  • Digitaler Bewegungsprozessor: Kurz DMP, ein Hardware-Algorithmus zur Lageberechnung, der im Chip enthalten ist. Er kann zur Lageberechnung mit der offiziellen DMP-Bibliothek verwendet werden.
  • FSYNC: Frame-Synchronisation.

3. 10-1 Software-I2C-Lesen und Schreiben von MPU6050

3.1 Hardware-Anschluss

Durch Software-I2C-Kommunikation können Sie die Register im MPU6050-Chip lesen und schreiben. Durch Lesen des Datenregisters können Sie die Daten des Plug-in-Moduls abrufen Die gelesenen Daten werden auf OLED angezeigt. Die obersten Daten sind die ID-Nummer des Geräts. Die ID-Nummer dieses MPU6050 ist auf 0x68 festgelegt. Unten sind die drei auf der linken Seite die Ausgangsdaten des Beschleunigungssensors, nämlich die Beschleunigung der X-Achse, der Y-Achse und der Z-Achse. Die drei auf der rechten Seite sind die Ausgangsdaten des Gyroskopsensors. Dies sind die Winkelgeschwindigkeiten der X-Achse, der Y-Achse und der Z-Achse.
SCL ist mit dem PB10-Pin von STM32 verbunden und SDA ist mit dem PB11-Pin verbunden. Da hier das Software-Level-Flipping implementiert ist, können zwei GPIO-Ports beliebig verbunden werden.

3.2 Operationsergebnisse

IMG_20240406_155156.jpg

3.3 Codefluss

STM32 ist der Host und MPU6050 ist der Slave, was einem Master-Slave-Modus entspricht.

  1. Richten Sie die Module .c und .h der I2C-Kommunikationsschicht ein
    1. Schreiben Sie die zugrunde liegende GPIO-Initialisierung von I2C
    2. 6 grundlegende Zeiteinheiten: Start, Ende, Byte senden, Byte empfangen, Antwort senden, Antwort empfangen
  2. Erstellen Sie die .c- und .h-Module von MPU6050
    1. Basierend auf dem I2C-Kommunikationsmodul implementiert es das Lesen an der angegebenen Adresse, das Schreiben an der angegebenen Adresse, das Schreiben von Registern zum Konfigurieren des Chips und das Lesen der Register zum Erhalten von Sensordaten.
  3. Haupt c
    1. Rufen Sie das MPU6050-Modul auf, initialisieren Sie es, rufen Sie die Daten ab und zeigen Sie die Daten an

3.4 Code

  1. I2C-Code:
#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-Code:
#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-Peripheriegeräte

4.1 Einführung in I2C-Peripheriegeräte

  • STM32 integriert eine Hardware-I2C-Transceiverschaltung, die automatisch Funktionen wie Taktgenerierung, Start- und Endbedingungsgenerierung, Übertragung und Empfang von Antwortbits sowie Datenübertragung und -empfang durch die Hardware ausführen kann, wodurch die Belastung der CPU verringert wird.
  • Unterstützt das Multi-Host-Modell
  • Unterstützt den 7-Bit/10-Bit-Adressmodus
  • Unterstützt verschiedene Kommunikationsgeschwindigkeiten, Standardgeschwindigkeit (bis zu 100 kHz), schnell (bis zu 400 kHz)
  • Unterstützen Sie DMA
  • Kompatibel mit dem SMBus-Protokoll
  • STM32F103C8T6 Hardware-I2C-Ressourcen: I2C1, I2C2

4.2 I2C-Blockdiagramm

bild.png

  • Auf der linken Seite befinden sich die Kommunikationspins: SDA und SCL werden von SMBus verwendet;
    Die von allgemeinen Peripheriegeräten abgeleiteten Pins werden normalerweise über den Multiplexmodus des GPIO-Ports mit der Außenwelt verbunden (siehe Tabelle).
  • Das Obige ist der Datensteuerungsteil: SDA Der Kernteil der Datenübertragung und des Datenempfangs ist das Datenregister DR (DATA REGISTER) und das Datenschieberegister. Wenn Daten gesendet werden müssen, kann ein Datenbyte in das Datenregister DR geschrieben werden. Wenn das Schieberegister keine zu verschiebenden Daten hat, wird der Wert des Datenregisters weiter an das Schieberegister übertragen. Während des Schiebevorgangs können die nächsten Daten direkt im Datenregister abgelegt und abgewartet werden. Sobald die vorherige Datenverschiebung abgeschlossen ist, können die nächsten Daten nahtlos verbunden und weiterhin gesendet werden. Wenn Daten vom Datenregister zum Schieberegister übertragen werden, wird das TXE-Bit des Statusregisters auf 1 gesetzt, was anzeigt, dass das Senderegister leer ist.
  • Empfangen: Die Eingangsdaten werden Stück für Stück vom Pin zum Schieberegister verschoben. Wenn ein Datenbyte erfasst ist, werden die Daten als Ganzes vom Schieberegister zum Datenregister übertragen und das Flag RXNE wird gesetzt Gleichzeitig wird angezeigt, dass das Register nicht leer ist, dann können die Daten aus dem Datenregister ausgelesen werden. Für den Zeitpunkt des Empfangs und des Sendens müssen die entsprechenden Bits in das Steuerregister geschrieben werden. Die Startbedingungen, Beendigungsbedingungen, Antwortbits usw. werden durch die Datensteuerung vervollständigt.
  • Der Komparator und das Adressregister werden im Slave-Modus verwendet.
  • SCL: Die Taktsteuerung dient zur Steuerung der SCL-Leitung. Schreiben Sie das entsprechende Bit in das Taktsteuerregister, und die Schaltung führt die entsprechende Funktion aus. Steuerlogikschaltung: Durch Schreiben in das Steuerregister kann die gesamte Schaltung gesteuert werden. Der Betriebsstatus der Schaltung kann durch Lesen des Statusregisters ermittelt werden.
  • Beim Senden und Empfangen vieler Bytes kann DMA zur Effizienzsteigerung eingesetzt werden.

4.3 I2C-Grundstruktur

bild.png

  • SDA: Da I2C zuerst hochrangig ist, verschiebt sich dieses Schieberegister nach links. Beim Senden wird zuerst das High-Bit herausgeschoben, dann das zweite High-Bit. Ein SCL-Takt wird einmal und achtmal verschoben, und auf der SDA-Leitung können 8 Bytes vom High-Bit zum Low-Bit platziert werden. Beim Empfang werden die Daten von rechts über den GPIO-Port eingezogen und schließlich achtmal ein Byte empfangen. Die Ausgabedaten werden über den GPIO-Port an den Port ausgegeben. Die Eingabedaten werden über den GPIO-Port in das Schieberegister eingegeben.
  • Der GPIO-Port muss für den Multiplex-Open-Drain-Ausgabemodus konfiguriert werden. Multiplexing bedeutet, dass der Status des GPIO-Ports durch On-Chip-Peripheriegeräte gesteuert wird und die Open-Drain-Ausgabe die vom I2C-Protokoll geforderte Portkonfiguration ist. Selbst im Open-Drain-Ausgabemodus kann der GPIO-Port eingegeben werden.
  • SCL: Der Taktcontroller steuert die Taktleitung über GPIO.
    bild.png

4.4 Host sendet

bild.png
Wenn STM32 an eine bestimmte Adresse schreiben möchte, muss es dem Übertragungssequenzdiagramm des Senders folgen.

  • 7-Bit-Adresse: Das eine Byte nach der Startbedingung wird adressiert
  • 10-Bit-Adresse: Die beiden Bytes nach der Startbedingung sind die Adressierung. Das erste Byte ist der Frame-Header und der Inhalt ist das 5-Bit-Flag-Bit 11110 + 2-Bit-Adresse + 1 Lese-/Schreibbit Reine 8-Bit-Adresse.
  • 7-Bit-Prozess: Start, Slave-Adresse, Antwort, Daten, Antwort, Daten, Antwort ... Stopp
  1. Nach der Initialisierung wechselt der Bus standardmäßig in den Ruhezustand und STM in den Slave-Modus. Um eine Startbedingung zu generieren, muss STM32 in das Steuerregister (CR1) schreiben, 1 schreiben und dann wechselt STM32 vom Slave-Modus in den Master-Modus .

bild.png

  1. Das EV5-Ereignis kann als Flag-Bit betrachtet werden. SB ist ein Bit des Statusregisters, das den Status der Hardware anzeigt. SB=1 bedeutet, dass die Startbedingung gesendet wurde.

bild.png

  1. Anschließend können Sie ein Byte der Slave-Adresse in das Datenregister DR schreiben. Nach dem Schreiben in das DR überträgt die Hardwareschaltung das Adressbyte automatisch in das Schieberegister und überträgt dann das Wort Der Knoten an den I2C-Bus gesendet, und dann empfängt die Hardware automatisch die Antwort und beurteilt. Wenn keine Antwort erfolgt, setzt die Hardware das Antwortfehler-Flag, und dann kann das Flag einen Interrupt anfordern, um uns daran zu erinnern.
  2. Wenn die Adressierung abgeschlossen ist, tritt das EV6-Ereignis auf und das ADDR-Flag-Bit ist 1. Dieses Flag-Bit zeigt das Ende der Adressübertragung im Master-Modus an.

bild.png

  1. Das EV8_1-Ereignis bedeutet, dass das TxE-Flag 1 ist, das Schieberegister leer ist und das Datenregister leer ist. Wir müssen in das Datenregister DR schreiben, um Daten nach DR zu senden, da das Schieberegister leer ist wechselt sofort zum Register zum Senden. Das EV8-Ereignis wird auftreten. Das Schieberegister ist nicht leer und das Datenregister ist leer, was bedeutet, dass das Schieberegister hier Daten 1 generiert. In diesem Moment werden die Daten 2 in das Datenregister geschrieben und warten darauf, dass die Datenbits zur Übertragung an das Schieberegister übertragen werden. Zu diesem Zeitpunkt ist das Schieberegister nicht leer Das Datenregister ist leer, daher ist der EV8-Vorfall zu diesem Zeitpunkt erneut aufgetreten.
  2. Dann werden Daten 2 gesendet, aber dieses Mal wurden die nächsten Daten in das Datenregister geschrieben und warten. Sobald das EV8-Ereignis erkannt wird, können die nächsten Daten geschrieben werden.
  3. Nachdem die Daten, die Sie senden möchten, geschrieben wurden, werden keine neuen Daten in das Datenregister geschrieben. Wenn die aktuelle Datenverschiebung im Schieberegister abgeschlossen ist, ist das Schieberegister leer und das Datenregister ist ebenfalls leer. EV8_2-Ereignis, TxE = 1 bedeutet, dass das Schieberegister leer ist, das Datenregister leer ist, BTF: Byte-Übertragungsende-Flag, wenn beim Senden neue Daten gesendet werden und das Datenregister nicht mit neuen Daten beschrieben wurde. Wenn EV8_2 erkannt wird, kann die Abbruchbedingung Stop generiert werden. Um eine Beendigungsbedingung zu erzeugen, müssen natürlich entsprechende Bits im Steuerregister vorhanden sein, die gesteuert werden können. Damit ist die Sendesequenz beendet.

4.5 Empfang des Gastgebers

bild.png
7-Bit-Master-Empfang: Start, Slave-Adresse + Lesen, Antwort empfangen, Daten empfangen, Antwort senden ... Daten empfangen, keine Antwort, beenden

  1. Schreiben Sie zunächst das Startbit des Steuerregisters, um eine Startbedingung zu generieren, und warten Sie dann auf das EV5-Ereignis (das anzeigt, dass die Startbedingung gesendet wurde).
  2. Nach der Adressierung wird die Antwort empfangen und nach dem Ende ein EV6-Ereignis generiert (das anzeigt, dass die Adressierung abgeschlossen ist).
  3. Daten 1 bedeutet, dass Daten über das Schieberegister eingegeben werden.
  4. EV6_1 zeigt an, dass die Daten noch verschoben werden. Nach dem Empfang der Antwort bedeutet dies, dass das Schieberegister ein Datenbyte erfolgreich verschoben hat. Zu diesem Zeitpunkt wird das verschobene Byte als Ganzes in das Datenregister übertragen Gleichzeitig wird das RxNE-Flag gesetzt, was darauf hinweist, dass das Datenregister nicht leer ist, d. h. ein Byte Daten empfangen wurde. Das Lesen des DR-Registers löscht das Ereignis dass die Daten empfangen wurden. Nachdem wir die Daten gelesen haben, gibt es kein Ereignis mehr.
  5. Wenn Daten 1 nicht gelesen wurden, können Daten 2 natürlich direkt in das Schieberegister verschoben werden. Danach ist die Verschiebung von Daten 2 abgeschlossen, Daten 2 werden empfangen, ein EV7-Ereignis wird generiert, Daten 2 werden gelesen und Das EV7-Event ist weg.
  6. Wenn kein Empfang mehr erforderlich ist, muss das Antwortbit-Steuerregister ACK vorab auf 0 gesetzt werden, wenn die letzte Zeiteinheit auftritt, und die Beendigungsbedingungsanforderung wird festgelegt, d. h. das Ereignis EV7_1. Danach erfolgt eine Nichtantwort-NA Da das STOP-Bit gesetzt ist, wird eine Beendigungsbedingung generiert.

4.6 Software-/Hardware-Wellenformvergleich

bild.png

bild.png

5. 10-2 Hardware I2C liest und schreibt MPU6050

5.1 I2C-Bibliotheksfunktionen


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 Hardware-I2C-Lese- und Schreib-MPU6050-Implementierung

5.2.1 Hardware-Anschluss

SCL ist mit dem PB10-Pin von STM32 verbunden und SDA ist mit dem PB11-Pin verbunden. Da hier das Software-Level-Flipping implementiert ist, können zwei GPIO-Ports beliebig verbunden werden.
Die obersten Daten von OLED sind die ID-Nummer des Geräts. Die ID-Nummer dieses MPU6050 ist auf 0x68 festgelegt. Unten sind die drei auf der linken Seite die Ausgangsdaten des Beschleunigungssensors, nämlich die Beschleunigung der X-Achse, der Y-Achse und der Z-Achse. Die drei auf der rechten Seite sind die Ausgangsdaten des Gyroskopsensors. Dies sind die Winkelgeschwindigkeiten der X-Achse, der Y-Achse und der Z-Achse.

5.2.2 Operationsergebnisse

IMG_20240406_172128.jpg

5.2.3 Code-Implementierungsprozess

  1. I2C-Peripheriegeräte konfigurieren, I2C-Peripheriegeräte initialisieren, MyI2C_Init ersetzen
    (1) Schalten Sie die Uhr des I2C-Peripheriegeräts und des entsprechenden GPIO-Ports ein.
    (2) Initialisieren Sie den GPIO-Port, der dem I2C-Peripheriegerät entspricht, für den Multiplex-Open-Drain-Modus
    (3) Verwenden Sie die Struktur, um den gesamten I2C zu konfigurieren
    (4) I2C_Cmd, I2C aktivieren
  2. Steuern Sie periphere Schaltkreise, erkennen Sie den Zeitpunkt des Schreibens an bestimmte Adressen und ersetzen Sie WriteReg
  3. Steuern Sie die Peripherieschaltung, um den Zeitpunkt des Lesens der angegebenen Adresse zu erkennen und ReadReg zu ersetzen

5.2.4 Code

  1. MPU6050-Code:
#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. Haupt 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