Partage de technologie

STM32-I2C

2024-07-12

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

Ce contenu est basé surTechnologie Jiangxie STM32Compilé après étude vidéo.

1. Communication I2C

1.1 Introduction à la communication I2C

  • I2C (Inter IC Bus) est un bus de données universel développé par Philips
  • Deux lignes de communication : ligne d'horloge série SCL (Serial Clock), ligne de données série SDA (Serial Data).
  • Synchrone, semi-duplex, asymétrique, multi-périphériques
  • Répondre avec des données
  • Prend en charge le montage de plusieurs appareils sur le bus (un maître et plusieurs esclaves, plusieurs maîtres et plusieurs esclaves)
    • Un maître, plusieurs esclaves : le microcontrôleur sert d'hôte et domine le fonctionnement du bus I2C. Tous les modules externes montés sur le bus I2C sont des esclaves. Les esclaves ne peuvent contrôler le bus I2C qu'après avoir été nommés par l'hôte et ne peuvent pas le toucher. sans autorisation. Bus I2C pour éviter les conflits.
    • Multi-maître et multi-esclave : n'importe quel module sur le bus peut activement sauter et agir en tant que maître. Lorsqu'un conflit de bus survient, le protocole I2C effectue un arbitrage. La partie qui remporte l'arbitrage obtient le contrôle du bus et la partie perdante devient automatiquement esclave.

image.png

1.2 Circuit matériel

  • Les SCL de tous les appareils I2C sont connectés ensemble et le SDA est connecté ensemble.
  • Le SCL et le SDA de l'appareil doivent être configurés en mode de sortie à drain ouvert.
  • Ajoutez une résistance de rappel à chacun des SCL et SDA, la valeur de la résistance est généralement d'environ 4,7 KΩ

Figure 1Figure 2
image.png

  • Un maître et plusieurs esclaves : le CPU est un micro-ordinateur monopuce. En tant que maître du bus, il inclut le contrôle total de la ligne SCL. Le maître a le contrôle total de la ligne SCL à tout moment. De plus, à l'état inactif, l'hôte peut activement lancer le contrôle du SDA. Ce n'est que lorsque l'esclave envoie des données et que l'esclave répond que l'hôte transfère le contrôle du SDA à l'esclave.
  • Le circuit intégré contrôlé est un esclave monté sur le bus I2C, qui peut être un capteur d'attitude, un OLED, une mémoire, un module d'horloge, etc. La puissance de l'esclave est relativement faible. Pour la ligne d'horloge SCL, il ne peut lire que passivement à tout moment. L'esclave n'est pas autorisé à contrôler la ligne SCL. Pour la ligne de données SDA, l'esclave n'est pas autorisé à lancer activement le contrôle du SDA. Ce n'est qu'après que le maître envoie une commande pour lire l'esclave, ou lorsque l'esclave répond, que l'esclave peut brièvement prendre le contrôle du SDA.
  • Figure 2 : SCL à gauche et SDA à droite. Toutes les données peuvent être saisies via un tampon de données ou un déclencheur Schmitt.
    • Étant donné que l’entrée n’a aucun effet sur le circuit, n’importe quel appareil peut avoir une entrée à tout moment.
    • La sortie adopte une configuration de sortie à drain ouvert. Lorsque la sortie est faible, l'interrupteur est allumé et la broche est directement connectée à la terre, ce qui constitue un fort tirage vers le bas. Lorsque la sortie est élevée, l'interrupteur est éteint et la broche est éteinte. n'est connecté à rien et est dans un état flottant, de sorte que tous les appareils ne peuvent produire qu'un niveau bas mais pas un niveau élevé. Afin d'éviter le flottement causé par un niveau élevé, SCL et SDA doivent avoir une résistance de rappel externe à l'extérieur. le bus, à travers une résistance tiré à un niveau élevé, c'est donc un pull-up faible. De cette manière, premièrement, il élimine complètement le phénomène de court-circuit d'alimentation et assure la sécurité du circuit. Deuxièmement, il évite les commutations fréquentes des modes de broches. En mode drain ouvert, émettre un niveau élevé équivaut à déconnecter la broche, vous pouvez donc directement émettre un niveau élevé avant l'entrée. Troisièmement, ce mode présente un phénomène « ET câblé ». Tant qu'un ou plusieurs appareils émettent un niveau bas, le bus est à un niveau bas. Ce n'est que lorsque tous les appareils émettent un niveau élevé que le bus est à un niveau élevé. . Par conséquent, I2C peut profiter de ce phénomène pour effectuer la synchronisation d’horloge et l’arbitrage de bus en mode multi-maître. Ainsi, bien que SCL puisse ici utiliser la sortie push-pull en mode maître et plusieurs esclaves, il utilise toujours le mode de sortie open-drain plus pull-out.

1.3 Unité de base de synchronisation I2C

1.3.1 Conditions de départ et conditions de fin

  • condition de départ: Pendant le niveau haut SCL, SDA passe du niveau haut au niveau bas
  • Condition de résiliation: Pendant le niveau haut du SCL, le SDA passe du niveau bas au niveau haut

image.png

  • dans des conditions initiales : Lorsque le bus I2C est à l'état inactif, SCL et SDA sont tous deux dans un état de niveau élevé, c'est-à-dire qu'aucun périphérique ne touche SCL et SCL et SDA sont tirés à un niveau élevé par des résistances de rappel externes, et le bus l'est. dans un état calme. Lorsque l'hôte a besoin d'envoyer et de recevoir des données, il doit d'abord rompre le silence du bus et générer une condition de départ, c'est-à-dire que SCL est à un niveau élevé sans le toucher, puis tirer SDA vers le bas pour générer un front descendant. Lorsque l'esclave capture le niveau haut SCL et le signal de front descendant SDA, il se réinitialise et attend l'appel du maître. Après le front descendant de SDA, l'hôte doit à nouveau abaisser SCL. D'une part, il occupe le bus, et d'autre part, c'est également pour faciliter l'épissage de l'unité de base. On s'assurera par la suite que, hormis les conditions de démarrage et d'arrêt, le SCL de chaque unité séquentielle démarre par un niveau bas et se termine par un niveau bas.
  • Dans l'état de condition de terminaison : SCL lâche prise en premier et rebondit au niveau haut, puis SDA lâche prise et rebondit au niveau haut, générant un front montant, qui déclenche la condition de terminaison. Après la condition de terminaison simultanée, SCL et SDA sont tous deux élevés et reviennent à l'état calme initial.
    Le démarrage et l'arrêt sont générés par l'hôte et l'esclave n'est pas autorisé à générer du démarrage et de l'arrêt. Par conséquent, lorsque le bus est inactif, l'esclave doit toujours lâcher ses mains et n'est pas autorisé à sauter et à toucher le bus.

1.3.2 Envoyer un octet

  • Envoyer un octet: Pendant le niveau bas de SCL, l'hôte place les bits de données sur la ligne SDA en séquence (bit haut en premier), puis libère SCL. L'esclave lira les bits de données pendant le niveau haut de SCL, donc SDA n'est pas autorisé. pour avoir des données pendant le niveau élevé de SCL. Lorsque les données changent, effectuez le processus ci-dessus 8 fois pour envoyer un octet.

L'hôte de bas niveau met les données et l'esclave de haut niveau lit les données.
image.png
Après la condition de démarrage, le premier octet doit également être envoyé par l'hôte. Lorsque SCL est faible, si l'hôte veut envoyer 0, il tire SDA bas ; s'il veut envoyer 1, il lâche prise et SDA rebondit à un niveau élevé. Pendant le niveau bas de SCL, le niveau de SDA peut changer. Une fois les données placées, l'hôte libère la ligne d'horloge et SCL rebondit au niveau haut. Pendant le niveau haut, c'est le moment où l'esclave lit SDA, donc pendant le niveau haut, SDA n'est pas autorisé à changer. Une fois que SCL est à un niveau haut, l'esclave doit lire SDA le plus rapidement possible. Généralement, l'esclave a terminé la lecture sur le front montant de SCL. Comme l'horloge est contrôlée par le maître, l'esclave ne sait pas quand le front descendant se produit, donc l'esclave lira les données sur le front montant du SCL. Une fois que l'hôte a abandonné SCL pendant un certain temps, il peut continuer à abaisser SCL et à transmettre le bit suivant. L'hôte doit également mettre les données sur SDA dès que possible après le front descendant de SCL. Mais l'hôte contrôle l'horloge, il n'a donc besoin de mettre les données sur SDA qu'à tout moment lorsque le niveau bas est bas. Une fois les données libérées, l'hôte libère à nouveau SCL, SCL est élevé et l'esclave lit ce bit. Bouclez ce processus : l'hôte abaisse le SCL, place les données sur SDA, l'hôte libère SCL et l'esclave lit les données SDA. Sous la synchronisation de SCL, le maître transmet et l'esclave reçoit en séquence. Après 8 cycles, des données de 8 bits, soit un octet, sont envoyées.
Puisqu'il s'agit du bit de poids fort en premier, le premier bit est le bit le plus élevé B7 d'un octet et le bit le plus bas B0 est envoyé en dernier.

1.3.3 Recevoir un octet

  • recevoir un octet: Pendant le niveau bas de SCL, l'esclave place les bits de données sur la ligne SDA en séquence (bit haut en premier), puis libère SCL. L'hôte lira les bits de données pendant le niveau haut de SCL, donc SDA n'est pas autorisé. pour avoir des données pendant le niveau élevé de SCL. Lorsque les données changent, effectuez le processus ci-dessus 8 fois pour recevoir un octet (l'hôte doit libérer SDA avant de recevoir).

L'esclave de bas niveau met les données, l'hôte de haut niveau lit les données
image.png
Ligne SDA : le maître doit libérer SDA avant de recevoir. À ce moment, l'esclave obtient le contrôle de SDA. Si l'esclave doit envoyer 0, il tire SDA vers le bas. Si l'esclave doit envoyer 1, il lâche prise. rebondit à un niveau élevé. Le niveau bas convertit les données, le niveau élevé lit les données. La ligne continue représente le niveau contrôlé par le maître et la ligne pointillée représente le niveau contrôlé par l'esclave. SCL est contrôlé par l'hôte tout au long du processus, et l'hôte SDA doit être libéré avant de recevoir et remis à l'esclave pour contrôle. Étant donné que l'horloge SCL est contrôlée par l'hôte, la conversion des données de l'esclave est essentiellement effectuée sur le front descendant de SCL, et l'hôte peut lire à tout moment lorsque SCL est élevé.

1.3.4 Envoyer une réponse et recevoir une réponse

  • Envoyer la réponse: Après avoir reçu un octet, l'hôte envoie un bit de données à l'horloge suivante. Les données 0 indiquent une réponse et les données 1 indiquent une non-réponse.
  • recevoir une réponse: Une fois que l'hôte a envoyé un octet, il reçoit un peu de données à l'horloge suivante pour déterminer si l'esclave répond. Les données 0 indiquent une réponse, les données 1 indiquent une non-réponse (l'hôte doit libérer SDA avant de recevoir).

image.png
Autrement dit, après avoir appelé le timing d'envoi d'un octet, il doit être suivi du timing d'appel de la réponse de réception, qui est utilisé pour déterminer si l'esclave a reçu les données qui viennent de lui être fournies. Si l'esclave le reçoit, alors dans le bit de réponse, lorsque le maître libère SDA, l'esclave doit immédiatement abaisser SDA, puis pendant le niveau haut de SCL, l'hôte lit le bit de réponse. Si le bit de réponse est à 0, cela signifie que l'esclave l'a bien reçu.
Lors de la réception d'un octet, vous devez appeler la réponse d'envoi. Le but de l'envoi d'une réponse est d'indiquer à l'esclave si vous souhaitez continuer l'envoi. Si la machine esclave reçoit une réponse du maître après l'envoi d'une donnée, la machine esclave continuera à envoyer. Si la machine esclave ne reçoit pas de réponse de la machine maître, la machine esclave pensera qu'une donnée a été envoyée. a été envoyé, mais la machine maître m'ignore. Peut-être que l'hôte n'en veut pas. À ce moment-là, l'esclave libérera docilement SDA et lui confiera le contrôle pour éviter toute interférence avec les opérations ultérieures de l'hôte.

1.4 Synchronisation I2C

1.4.1 Spécifier l'adresse à écrire

  • Préciser l'adresse à écrire
  • Pour le périphérique spécifié (Slave Address), écrivez les données spécifiées (Data) à l'adresse spécifiée (Reg Address) (c'est-à-dire l'adresse de registre du périphérique spécifié)

image.png
processus:
(1) Conditions de départ
(2) Moment d'envoi d'un octet — 0xD0 (adresse esclave (7 bits) + écriture (1 bit) -0) (1101 0000)
(3) Recevoir la réponse : RA = 0 (recevoir la réponse de l'esclave)
(4) Adresse spécifiée : 0x19 (0001 1001)
(5) Recevoir la réponse : RA = 0 (recevoir la réponse de l'esclave)
(6) Écrire les données spécifiées : 0xAA (1010 1010)
(7) Réception de la réponse : RA = 0
(8) Bit d'arrêt P (condition de terminaison)

  • Après la condition de démarrage, il doit y avoir le timing pour envoyer un octet. Le contenu de l'octet doit être l'adresse de l'esclave + les bits de lecture et d'écriture. L'adresse de l'esclave est de 7 bits et les bits de lecture et d'écriture sont de 1 bit, ce qui est exactement. 8 bits. L'envoi de l'adresse esclave consiste à déterminer l'objet de communication, et l'envoi des bits de lecture et d'écriture consiste à confirmer s'il faut écrire ou lire ensuite. Maintenant, l'hôte envoie une donnée. Le contenu de l'octet est converti en hexadécimal. Le bit de poids fort est 0xD0. L'unité suivante est le bit de réponse (RA) de l'esclave récepteur. Le bit d'écriture se termine et SCL est tiré au niveau bas. Après cela, l'hôte doit libérer SDA, suivi du bit d'accusé de réception RA.
  • Le niveau haut après la fin du bit de réponse RA est généré par l'esclave libérant SDA. L'esclave cède le contrôle de SDA car l'esclave souhaite échanger des données le plus rapidement possible au niveau bas de SCL, le front montant de SDA et. le front descendant de SCL s’est produit presque simultanément.
  • Une fois la réponse terminée, si vous continuez à envoyer un octet, le deuxième octet peut être envoyé à l'intérieur du périphérique désigné. Le périphérique esclave peut définir l'utilisation du deuxième octet et des octets suivants. Généralement, le deuxième octet peut être une adresse de registre ou un mot de contrôle d'instruction, etc., et le troisième octet est le contenu que l'hôte souhaite écrire dans l'adresse de registre (deuxième octet).
  • P est le bit d'arrêt.

Le but de cette trame de données est : pour l'appareil qui spécifie l'adresse esclave 1101000, écrire la donnée 0xAA dans son registre interne à l'adresse 0x19.
0 signifie : l'hôte effectuera une opération d'écriture dans le timing suivant ;
1 signifie : l'hôte effectuera une opération de lecture dans la séquence de synchronisation suivante ;

1.4.2 Lecture de l'adresse actuelle

  • Adresse actuelle lue
  • Pour l'appareil spécifié (Slave Address), lisez les données de l'esclave (Data) à l'adresse indiquée par le pointeur d'adresse actuel.

image.png
processus:
(1) Conditions de départ
(2) Moment d'envoi d'un octet — 0xD1 (adresse esclave (7 bits) + lecture (1 bit) -1) (1101 0001)
(3) Recevoir la réponse : RA = 0 (recevoir la réponse de l'esclave)
(4) Lire les données de l'esclave : 0x0F (0000 1111)
(7) Envoyer la réponse : SA = 0
(8) Bit d'arrêt P (condition de terminaison)

  • Le bit de lecture et d'écriture est 1, indiquant que la prochaine opération de lecture doit être effectuée. Après la réponse de l'esclave (RA=0), le sens de transmission des données sera inversé. Le maître veut confier le contrôle de SDA à l'esclave, et le maître appelle le timing de réception d'un octet pour effectuer l'opération de réception.
  • Dans le deuxième octet, l'esclave obtient l'autorisation du maître et peut écrire dans SCL pendant le niveau bas de SCL. Enfin, le maître lit en séquence pendant le niveau haut de SCL. 8 bits, un octet de données envoyé par l'esclave est reçu, soit 0x0F. Mais quel registre de l’esclave est 0x0F ? Dans le timing de lecture, le protocole I2C stipule que lorsque l'hôte effectue l'adressage, une fois l'indicateur de lecture et d'écriture mis à 1. L'octet suivant passera immédiatement au timing de lecture. Par conséquent, l'hôte commencera à recevoir avant d'avoir le temps de spécifier quel registre il souhaite lire, il n'y a donc pas de lien pour spécifier l'adresse ici. Dans la machine esclave, tous les registres sont alloués à une zone linéaire, et il y aura une variable de pointeur distincte indiquant l'un des registres. Ce pointeur est par défaut mis sous tension, pointe généralement vers l'adresse 0, et chaque fois qu'un octet est écrit et. Après avoir lu un octet, le pointeur incrémentera automatiquement une fois et passera à la position suivante. Ensuite, lors de l'appel du timing de lecture de l'adresse actuelle, si l'hôte ne précise pas quelle adresse lire, l'esclave reviendra au registre pointé par le. valeur actuelle du pointeur.

1.4.3 Lire à l'adresse spécifiée

  • Spécifiez l'adresse à lire
  • Pour l'appareil spécifié (Slave Address), sous l'adresse spécifiée (Reg Address), lisez les données de l'esclave (Data)

image.png
Commencez d'abord, puis répétez le démarrage, puis arrêtez
processus:
(1) Conditions de départ
(2) Moment d'envoi d'un octet — 0xD0 (adresse esclave (7 bits) + écriture (1 bit) -0) (1101 0000)
(3) Recevoir la réponse : RA = 0 (recevoir la réponse de l'esclave)
(4) Adresse spécifiée : 0x19 (0001 1001)
(5) Recevoir la réponse : RA = 0 (recevoir la réponse de l'esclave)
(6) Répétez la condition de départ
(7) Moment d'envoi d'un octet — 0xD1 (adresse esclave (7 bits) + lecture (1 bit) -1) (1101 0001)
(8) Réception de la réponse : RA = 0
(9) Lire les données de l'esclave : 0xAA (1010 1010)
(10) Envoyer la réponse : SA = 0
(11) Bit d'arrêt P (condition de terminaison)

  • La première partie consiste à écrire à l'adresse spécifiée, mais seule l'adresse est spécifiée, et il n'y a pas de temps pour écrire ; la deuxième partie consiste à lire l'adresse actuelle, car l'adresse vient d'être spécifiée, donc l'adresse actuelle lue est ; appelé à nouveau.
  • L'adresse de l'esclave spécifiée est 1101000, l'indicateur de lecture-écriture est 0 et l'opération d'écriture est effectuée après la réponse de l'esclave, un autre octet (le deuxième octet) est écrit pour spécifier l'adresse 0x19 dans l'esclave. pointeur d'adresse, c'est-à-dire qu'après que l'esclave a reçu les données, son pointeur de registre pointe vers la position 0x19.
  • Sr est une condition de démarrage répétée, ce qui équivaut à démarrer un nouveau timing. Parce que l'indicateur de lecture et d'écriture spécifié ne peut suivre que le premier octet de la condition de démarrage, donc si vous souhaitez changer le sens de lecture et d'écriture, vous ne pouvez avoir. une autre condition de départ.
  • Ensuite, après la condition de démarrage, réadressez et spécifiez le bit d'indicateur de lecture-écriture. À ce moment, le bit d'indicateur de lecture-écriture est 1, indiquant qu'il doit être lu. Ensuite, l'hôte reçoit un octet, qui correspond aux données. 0xAA à l'adresse 0x19.

2. MPU6050

2.1 Présentation du MPU6050

  • Le MPU6050 est un capteur d'attitude à 6 axes qui peut mesurer les paramètres d'accélération et de vitesse angulaire des propres axes X, Y et Z de la puce, grâce à la fusion de données, l'angle d'attitude (angle d'Euler) peut être obtenu davantage. Équilibrer les véhicules, les avions, etc. qui doivent se détecter eux-mêmes.
  • Accéléromètre 3 axes (Accéléromètre) : mesure l'accélération des axes X, Y et Z
  • Capteur gyroscopique à 3 axes (Gyroscope) : mesure la vitesse angulaire des axes X, Y et Z

image.png

  • En prenant le fuselage de l'avion comme exemple, l'angle d'Euler est l'angle entre le fuselage de l'avion et les trois axes initiaux.
    • avionLe nez de l'avion s'incline vers le bas ou vers le haut, l'angle entre cet axe est appeléPas
    • avionLe fuselage roule à gauche ou à droite, l'angle entre cet axe est appeléRouler
    • avionGardez le fuselage à niveauTournez le nez de l'avion vers la gauche ou la droite, l'angle entre cet axe est appeléEmbardée
    • L'angle d'Euler représente l'attitude de l'avion à cet instant, qu'il soit incliné vers le haut ou vers le bas, incliné vers la gauche ou vers la droite.
  • Les algorithmes courants de fusion de données incluent généralement le filtrage complémentaire, le filtrage de Kalman, etc., et le calcul d'attitude en navigation inertielle.
  • Accéléromètre : La ligne pointillée au milieu est l'axe d'induction. Au milieu se trouve un petit curseur avec une certaine masse qui peut glisser à gauche et à droite. Lorsque le curseur bouge, il fera bouger le potentiomètre. Ce potentiomètre est une résistance de division de tension en mesurant la tension de sortie par le potentiomètre, vous pouvez obtenir la valeur d'accélération du petit curseur. Cet accéléromètre est en fait un dynamomètre à ressort selon la deuxième loi de Newton, F = ma. Si vous souhaitez mesurer l'accélération a, vous pouvez trouver un objet de masse unitaire et mesurer la force F. C'est tout. Il y a un accéléromètre sur chacun des axes X, Y et Z. Les accéléromètres ont une stabilité statique mais pas de stabilité dynamique.
  • Capteur gyroscopique : Au milieu se trouve une roue en rotation avec une certaine masse. Lorsque la roue en rotation tourne à grande vitesse, selon le principe de conservation du moment cinétique, la roue en rotation a tendance à conserver son moment cinétique d'origine. direction de l’axe de rotation inchangée. Lorsque la direction de l'objet externe tourne, la direction de l'axe de rotation interne ne tournera pas, ce qui produira une déviation angulaire au niveau de la connexion de l'anneau d'équilibrage. Si vous placez un potentiomètre rotatif au niveau de la connexion et mesurez la tension du potentiomètre, vous pouvez obtenir l'angle de rotation. Le gyroscope devrait pouvoir obtenir directement l'angle, mais le gyroscope de ce MPU6050 ne peut pas mesurer directement l'angle. Il mesure la vitesse angulaire, c'est-à-dire la vitesse angulaire de la puce tournant autour de l'axe X, de l'axe Y et de Z. -axe. L'intégrale de la vitesse angulaire est l'angle. Cependant, lorsque l'objet est stationnaire, la valeur de la vitesse angulaire ne peut pas être complètement remise à zéro en raison du bruit. Ensuite, après une accumulation continue d'intégrales, ce petit bruit fera dériver lentement l'angle calculé. qui est l'angle obtenu en intégrant la vitesse angulaire. Il ne résiste pas à l'épreuve du temps, mais cet angle ne pose aucun problème qu'il soit stationnaire ou en mouvement, et ne sera pas affecté par le mouvement de l'objet. Les gyroscopes ont une stabilité dynamique et non statique.
  • Selon l'accéléromètre, qui a une stabilité statique mais pas de stabilité dynamique ; le gyroscope a une stabilité dynamique mais n'a pas de stabilité statique, nous pouvons donc apprendre des forces de chacun et compléter les faiblesses de chacun. , nous pouvons intégrer à la fois la stabilité statique et dynamique. La posture est inconfortable.

2.2 Paramètres du MPU6050

  • L'ADC 16 bits collecte le signal analogique du capteur, plage de quantification : -32768 ~ 32767
  • Sélection pleine échelle de l'accéléromètre : ±2, ±4, ±8, ±16 (g) (1g = 9,8 m/s2)
  • Sélection à grande échelle du gyroscope : ±250, ±500, ±1 000, ±2 000 (°/sec, degré/seconde, unité de vitesse angulaire, nombre de degrés de rotation par seconde) (plus la sélection à grande échelle est grande, plus la plage de mesure. Plus la plage pleine échelle est petite, plus la résolution de mesure sera élevée.)
  • Filtre passe-bas numérique configurable : un registre peut être configuré pour sélectionner le filtrage passe-bas des données de sortie.
  • Source d'horloge configurable
  • Division de fréquence d'échantillonnage configurable : la source d'horloge peut être divisée par le diviseur de fréquence pour fournir des horloges pour la conversion AD et d'autres circuits internes. En contrôlant le coefficient de division de fréquence, vous pouvez contrôler la vitesse de conversion AD.
  • Adresse esclave I2C : 1101000 (AD0=0) ou 1101001 (AD0=1)
    • 110 1000 est converti en hexadécimal, soit 0x68, donc certains disent que l'adresse esclave du MPU6050 est 0x68. Mais dans la communication I2C, les 7 bits forts du premier octet sont l'adresse de l'esclave, et le bit le plus bas est le bit de lecture et d'écriture. Par conséquent, si vous pensez que 0x68 est l'adresse de l'esclave, lors de l'envoi du premier octet, vous devez d'abord la modifier. 0x68 Décalage vers la gauche de 1 bit (0x68 << 1), puis lecture et écriture des bits par bit ou vers le haut, lecture de 1 et écriture de 0.
    • Une autre méthode consiste à décaler les données de 0x68 vers la gauche de 1 bit (0x68 << 1) comme adresse esclave, qui est 0xD0. Dans ce cas, l'adresse esclave du MPU6050 est 0xD0. À ce stade, lors de l'envoi du premier octet, si vous souhaitez écrire, utilisez simplement 0xD0 comme premier octet ; si vous souhaitez lire, utilisez 0xD0 ou 0x01 (0xD0 | 0x01), c'est-à-dire 0xD1 comme premier octet. . Cette représentation ne nécessite pas d'opération de décalage vers la gauche, ou en d'autres termes, cette représentation intègre les bits de lecture et d'écriture dans l'adresse de l'esclave. 0xD0 est l'adresse d'écriture et 0xD1 est l'adresse de lecture.

2.3 Circuit matériel

image.png

épingleFonction
VCC, GNDsource de courant
SCL, SDABroche de communication I2C
XCL, XDABroches de communication hôte I2C
AD0Le bit le plus bas de l'adresse de l'esclave
INTSortie de signal d'interruption
  • LDO : régulateur de tension linéaire à faible chute, régulateur de tension 3,3 V.
  • SCL et SDA : ce sont des broches de communication I2C. Le module dispose de deux résistances de rappel 4,7 K intégrées, donc lors du câblage, connectez simplement SDA et SCL directement au port GPIO. Il n'est pas nécessaire de connecter des résistances de rappel externes. .
  • XCL, XDA : broches de communication hôte I2C. Ces deux broches sont conçues pour étendre les fonctions de la puce. Habituellement utilisé pour les magnétomètres ou baromètres externes lorsque ces puces d'extension sont connectées, l'interface hôte du MPU6050 peut accéder directement aux données de ces puces d'extension et lire les données de ces puces d'extension dans le MPU6050 dispose d'une unité DMP et effectue une fusion de données. calcul d'attitude.
    Broche AD0 : c'est le bit le plus bas de l'adresse esclave. S'il est connecté à un niveau bas, l'adresse esclave 7 bits est 1101000 ; s'il est connecté à un niveau haut, l'adresse esclave 7 bits est 1101001. Il y a une résistance dans le schéma de circuit, qui est faiblement abaissée au niveau bas par défaut, donc si la broche reste flottante, elle est de niveau bas. Si vous souhaitez la connecter au niveau haut, vous pouvez directement conduire AD0 à VCC. et tirez-le fortement jusqu'à un niveau élevé.
  • INT : broche de sortie d'interruption Vous pouvez configurer certains événements à l'intérieur de la puce pour déclencher la sortie de la broche d'interruption, tels que les données prêtes, l'erreur de l'hôte I2C, etc.
  • La puce intègre également : détection de chute libre, détection de mouvement, détection de mouvement nul, etc. Ces signaux peuvent déclencher la broche INT pour générer une transition de niveau, et les signaux d'interruption peuvent être configurés si nécessaire.
  • L'alimentation de la puce MPU6050 est de 2,375 à 3,46 V, ce qui est un dispositif d'alimentation de 3,3 V et ne peut pas être directement connecté à 5 V. Par conséquent, un régulateur de tension de 3,3 V est ajouté et la tension de la borne d'entrée VCC_5V peut être comprise entre 3,3 V et 5 V. Ensuite, le régulateur de tension de 3,3 V produit une tension stable de 3,3 V pour alimenter la puce tant que la borne de 3,3 V est alimentée. , Le voyant d'alimentation s'allumera.

2.4 Schéma fonctionnel du MPU6050

image.png

  • CLKIN et CLKOUT sont des broches d'entrée d'horloge et des broches de sortie d'horloge, mais nous utilisons généralement l'horloge interne.
  • La partie grise : c'est le capteur à l'intérieur de la puce, l'accéléromètre sur l'axe XYZ et le gyroscope sur l'axe XYZ.
  • Il existe également un capteur de température intégré qui peut être utilisé pour mesurer la température.
  • Ces capteurs sont essentiellement équivalents à des résistances variables. Après avoir divisé la tension, ils produisent une tension analogique, puis effectuent une conversion analogique-numérique via l'ADC. Une fois la conversion terminée, les données de ces capteurs sont uniformément placées dans les données. registre, qui peut être obtenu en lisant le registre de données. La valeur mesurée par le capteur. Toutes les conversions au sein de cette puce sont entièrement automatisées.
  • Chaque capteur dispose d'une unité d'auto-test, qui est utilisée pour vérifier la qualité de la puce. Lorsque l'auto-test est démarré, la puce simulera une force externe exercée sur le capteur. Cette force externe entraînera la transmission des données du capteur. plus grand que d'habitude. Processus d'auto-test : vous pouvez d'abord activer l'auto-test, lire les données, puis activer l'auto-test, lire les données, soustraire les deux données, et les données résultantes sont appelées réponse d'auto-test. Pour cette réponse d'autotest, le manuel donne une plage. Si elle se situe dans cette plage, cela signifie qu'il n'y a pas de problème avec la puce.
  • Pompe de charge : Il s'agit d'une pompe de charge ou pompe de charge. La pompe de charge est un circuit boost.
  • La broche CPOUT nécessite un condensateur externe.
  • Registre d'état d'interruption : peut contrôler quels événements internes sont émis vers la broche d'interruption,
  • FIFO : registre premier entré, premier sorti, qui peut mettre en cache le flux de données.
  • Registre de configuration : vous pouvez configurer différents circuits internes
  • Registre des capteurs : registre de données, qui stocke les données de chaque capteur.
  • Calibré en usine : cela signifie que les capteurs à l’intérieur sont calibrés.
  • Processeur de mouvement numérique : DMP en abrégé, il s'agit d'un algorithme matériel de calcul d'attitude intégré à la puce. Il peut être utilisé pour le calcul d'attitude avec la bibliothèque officielle DMP.
  • FSYNC : Synchronisation des trames.

3. 10-1 Logiciel I2C lecture et écriture MPU6050

3.1 Connexion matérielle

Grâce à la communication logicielle I2C, lisez et écrivez les registres à l'intérieur de la puce MPU6050. En écrivant dans le registre de configuration, vous pouvez configurer le module enfichable. En lisant le registre de données, vous pouvez obtenir les données du module enfichable. les données lues seront affichées. Sur OLED, les données supérieures sont le numéro d'identification de l'appareil. Le numéro d'identification de ce MPU6050 est fixé à 0x68. Ci-dessous, les trois à gauche sont les données de sortie du capteur d'accélération, qui sont respectivement l'accélération des axes X, Y et Z. Les trois à droite sont les données de sortie du capteur gyroscope, qui sont la vitesse angulaire des axes X, Y et Z.
SCL est connecté à la broche PB10 du STM32 et SDA est connecté à la broche PB11. Puisque le retournement de niveau logiciel est implémenté ici, deux ports GPIO peuvent être connectés à volonté.

3.2 Résultats de l'opération

IMG_20240406_155156.jpg

3.3 Flux de codes

STM32 est l'hôte et MPU6050 est l'esclave, qui est un mode maître-esclave.

  1. Établir les modules .c et .h de la couche de communication I2C
    1. Écrire l'initialisation GPIO sous-jacente d'I2C
    2. 6 unités de synchronisation de base : démarrer, terminer, envoyer un octet, recevoir un octet, envoyer une réponse, recevoir une réponse
  2. Créez les modules .c et .h du MPU6050
    1. Basé sur le module de communication I2C, il implémente la lecture à l'adresse spécifiée, l'écriture à l'adresse spécifiée, l'écriture de registres pour configurer la puce et la lecture des registres pour obtenir les données du capteur.
  3. principal c
    1. Appelez le module MPU6050, initialisez, récupérez les données et affichez les données

3.4 Codes

  1. Code 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. Code 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. Périphériques I2C

4.1 Introduction aux périphériques I2C

  • STM32 intègre un circuit émetteur-récepteur matériel I2C, qui peut exécuter automatiquement des fonctions telles que la génération d'horloge, la génération de conditions de début et de fin, la transmission et la réception de bits de réponse, ainsi que la transmission et la réception de données par le matériel, réduisant ainsi la charge sur le processeur.
  • Prise en charge du modèle multi-hôtes
  • Prise en charge du mode d'adresse 7 bits/10 bits
  • Prend en charge différentes vitesses de communication, vitesse standard (jusqu'à 100 kHz), rapide (jusqu'à 400 kHz)
  • Prise en charge du DMA
  • Compatible avec le protocole SMBus
  • Ressources matérielles I2C STM32F103C8T6 : I2C1, I2C2

4.2 Schéma fonctionnel I2C

image.png

  • À gauche se trouvent les broches de communication : SDA et SCL ; SMBALERT est utilisé par SMBus ;
    Les broches dérivées des périphériques généraux sont généralement connectées au monde extérieur via le mode multiplexage du port GPIO (consultez le tableau)
  • Ce qui précède est la partie de contrôle des données : SDA La partie centrale de la transmission et de la réception des données est le registre de données DR (DATA REGISTER) et le registre à décalage de données. Lorsque des données doivent être envoyées, un octet de données peut être écrit dans le registre de données DR. Lorsque le registre à décalage n'a pas de données à décaler, la valeur du registre de données sera ensuite transférée vers le registre à décalage. Pendant le processus de décalage, les données suivantes peuvent être directement placées dans le registre de données et attendues. Une fois le transfert de données précédent terminé, les données suivantes peuvent être connectées de manière transparente et continuer à être envoyées. Lorsque les données sont transférées du registre de données au registre à décalage, le bit TXE du registre d'état est mis à 1, indiquant que le registre de transmission est vide.
  • Réception : les données d'entrée sont déplacées de la broche vers le registre à décalage bit par bit Lorsqu'un octet de données est collecté, les données sont transférées du registre à décalage vers le registre de données dans son ensemble, et l'indicateur RXNE est défini sur le. en même temps, indiquant la réception. Le registre n'est pas vide, alors les données peuvent être lues à partir du registre de données. Quant à savoir quand recevoir et quand envoyer, vous devez écrire les bits correspondants dans le registre de contrôle pour le fonctionnement. Les conditions de démarrage, les conditions de fin, les bits de réponse, etc. sont complétés par le contrôle des données.
  • Le comparateur et le registre d'adresses sont utilisés en mode esclave.
  • SCL : le contrôle de l'horloge est utilisé pour contrôler la ligne SCL. Écrivez le bit correspondant dans le registre de contrôle d'horloge et le circuit exécutera la fonction correspondante. Circuit logique de contrôle, l'écriture dans le registre de contrôle peut contrôler l'ensemble du circuit. L'état de fonctionnement du circuit peut être connu en lisant le registre d'état.
  • Lors de l’envoi et de la réception de nombreux octets, le DMA peut être utilisé pour améliorer l’efficacité.

4.3 Structure de base I2C

image.png

  • SDA : Comme I2C est d'ordre élevé en premier, ce registre à décalage se décale vers la gauche. Lors de l'envoi, le bit haut est déplacé en premier, puis le deuxième bit haut. Une horloge SCL est décalée une fois et décalée 8 fois, et 8 octets peuvent être placés sur la ligne SDA du bit haut au bit bas. Lors de la réception, les données sont déplacées de la droite via le port GPIO, et finalement déplacées 8 fois, un octet est reçu. Les données de sortie sont sorties vers le port via le port GPIO. Les données d'entrée sont entrées dans le registre à décalage via le port GPIO.
  • Le port GPIO doit être configuré pour multiplexer le mode de sortie à drain ouvert ; le multiplexage signifie que l'état du port GPIO est contrôlé par les périphériques sur puce, et la sortie à drain ouvert est la configuration du port requise par le protocole I2C. Même en mode de sortie à drain ouvert, le port GPIO peut être entré.
  • SCL : le contrôleur d'horloge contrôle la ligne d'horloge via GPIO.
    image.png

4.4 Envois par l'hôte

image.png
Lorsque STM32 souhaite écrire à une adresse spécifiée, il doit suivre le diagramme de séquence de transmission de l'émetteur.

  • Adresse 7 bits : l'octet après la condition de démarrage est adressé
  • Adresse 10 bits : les deux octets après la condition de démarrage sont l'adressage. Le premier octet est l'en-tête de trame et le contenu est le bit indicateur 5 bits 11110 + l'adresse 2 bits + 1 bit de lecture-écriture ; Adresse pure de 8 bits.
  • Processus 7 bits : démarrage, adresse esclave, réponse, données, réponse, données, réponse... Stop
  1. Après l'initialisation, le bus passe par défaut à l'état inactif et STM passe par défaut en mode esclave. Afin de générer une condition de démarrage, STM32 doit écrire dans le registre de contrôle (CR1), écrire 1, puis STM32 passe du mode esclave au mode maître. .

image.png

  1. L'événement EV5 peut être considéré comme un bit d'indicateur SB est un bit du registre d'état, indiquant l'état du matériel SB=1 signifie que la condition de démarrage a été envoyée.

image.png

  1. Ensuite, vous pouvez envoyer un octet d'adresse esclave. L'adresse esclave doit être écrite dans le registre de données DR. Après avoir écrit dans DR, le circuit matériel transférera automatiquement l'octet d'adresse au registre à décalage, puis transférera le mot Le nœud est. Envoyé au bus I2C, puis le matériel recevra automatiquement la réponse et jugera s'il n'y a pas de réponse, le matériel définira l'indicateur d'échec de réponse, puis l'indicateur pourra demander une interruption pour nous le rappeler.
  2. Lorsque l'adressage est terminé, l'événement EV6 se produira et le bit indicateur ADDR sera 1. Ce bit indicateur indique la fin de la transmission de l'adresse en mode maître.

image.png

  1. L'événement EV8_1 signifie que l'indicateur TxE est 1, le registre à décalage est vide et le registre de données est vide. Nous devons écrire dans le registre de données DR pour envoyer des données après avoir écrit dans DR, car le registre à décalage est vide, DR. passera immédiatement au registre à décalage pour envoyer. L'événement EV8 se produira. Le registre à décalage n'est pas vide et le registre de données est vide, ce qui signifie que le registre à décalage envoie des données. Par conséquent, la synchronisation des données 1 est générée ici dans le processus. À ce moment, les données 2 seront écrites dans le registre de données et sont en attente. Après avoir reçu le bit de réponse, les bits de données sont transférés vers le registre à décalage pour la transmission. L'état à ce moment est que le registre à décalage n'est pas vide et le. Le registre de données est vide, donc à ce moment-là, l'incident EV8 s'est reproduit.
  2. Ensuite, les données 2 sont envoyées, mais cette fois, les données suivantes ont été écrites dans le registre de données et sont en attente. Une fois l'événement EV8 détecté, les données suivantes peuvent être écrites.
  3. Une fois les données que vous souhaitez envoyer, aucune nouvelle donnée n'est écrite dans le registre de données. Lorsque le décalage des données en cours dans le registre à décalage est terminé, le registre à décalage est vide et le registre de données est également vide. Événement EV8_2, TxE=1 signifie que le registre à décalage est vide, le registre de données est vide, BTF : indicateur de fin de transmission d'octet, lors de la transmission, lorsqu'une nouvelle donnée sera envoyée et que le registre de données n'a pas été écrit avec de nouvelles données. Lorsque EV8_2 est détecté, la condition de terminaison Stop peut être générée. Pour générer une condition de terminaison, il doit évidemment y avoir des bits correspondants dans le registre de contrôle qui peuvent être contrôlés. De cette façon, la séquence d'envoi est terminée.

4.5 Accueil de l'hôte

image.png
Réception maître 7 bits : démarrage, adresse esclave + lecture, réception réponse, réception données, envoi réponse... réception données, non réponse, fin

  1. Tout d'abord, écrivez le bit Start du registre de contrôle pour générer une condition de démarrage, puis attendez l'événement EV5 (indiquant que la condition de démarrage a été envoyée).
  2. Après l'adressage, la réponse est reçue et un événement EV6 est généré après la fin (indiquant que l'adressage est terminé).
  3. Les données 1 signifient que les données sont entrées via le registre à décalage.
  4. EV6_1 indique que les données sont toujours en cours de décalage. Après avoir reçu la réponse, cela signifie que le registre à décalage a réussi à déplacer un octet de données 1. À ce moment, l'octet décalé est transféré au registre de données dans son ensemble, et l'indicateur RxNE est défini en même temps, indiquant que le registre de données n'est pas vide, c'est-à-dire qu'un octet de données a été reçu. L'état est un événement EV7, RxNE = 1. La lecture du registre DR efface l'événement, ce qui signifie. que les données ont été reçues. Après avoir lu les données, l'événement Il n'y en a plus.
  5. Bien entendu, lorsque les données 1 n'ont pas été lues, les données 2 peuvent être directement déplacées dans le registre à décalage. Après cela, le décalage des données 2 est terminé, les données 2 sont reçues, un événement EV7 est généré, les données 2 sont lues et. l'événement EV7 a disparu.
  6. Lorsqu'aucune réception supplémentaire n'est nécessaire, le registre de contrôle des bits de réponse ACK doit être mis à 0 à l'avance lorsque la dernière unité de synchronisation se produit, et la demande de condition de terminaison est définie, c'est-à-dire l'événement EV7_1. Ensuite, une non-réponse NA. sera donné. Puisque le bit STOP est activé, une condition de terminaison est donc générée.

4.6 Comparaison de forme d'onde logiciel/matériel

image.png

image.png

5. 10-2 Matériel I2C lecture et écriture MPU6050

5.1 Fonctions de la bibliothèque 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 Implémentation matérielle I2C de lecture et d'écriture du MPU6050

5.2.1 Connexion matérielle

SCL est connecté à la broche PB10 du STM32 et SDA est connecté à la broche PB11. Puisque le retournement de niveau logiciel est implémenté ici, deux ports GPIO peuvent être connectés à volonté.
Les données principales de l'OLED sont le numéro d'identification de l'appareil. Le numéro d'identification de ce MPU6050 est fixé à 0x68. Ci-dessous, les trois à gauche sont les données de sortie du capteur d'accélération, qui sont respectivement l'accélération des axes X, Y et Z. Les trois à droite sont les données de sortie du capteur gyroscope, qui sont la vitesse angulaire des axes X, Y et Z.

5.2.2 Résultats de l'opération

IMG_20240406_172128.jpg

5.2.3 Processus de mise en œuvre du code

  1. Configurer les périphériques I2C, initialiser les périphériques I2C, remplacer MyI2C_Init
    (1) allumez l'horloge du périphérique I2C et le port GPIO correspondant,
    (2) Initialisez le port GPIO correspondant au périphérique I2C en mode open-drain multiplexé
    (3) Utilisez la structure pour configurer l'ensemble de l'I2C
    (4) I2C_Cmd, activez I2C
  2. Contrôler les circuits périphériques, réaliser le timing d'écriture sur des adresses spécifiées et remplacer WriteReg
  3. Contrôler le circuit périphérique pour réaliser le timing de lecture de l'adresse spécifiée et remplacer ReadReg

5.2.4 Codes

  1. Code 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. principal 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