Teknologian jakaminen

STM32-I2C

2024-07-12

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

Tämä sisältö perustuuJiangxie Technology STM32Koottu videotutkimuksen jälkeen.

1. I2C-viestintä

1.1 Johdatus I2C-viestintään

  • I2C (Inter IC Bus) on Philipsin kehittämä yleistietoväylä
  • Kaksi tietoliikennelinjaa: SCL (Serial Clock) sarjakellolinja, SDA (Serial Data) sarjadatalinja
  • Synkroninen, puolidupleksi, yksipää, monilaite
  • Vastaa tiedoilla
  • Tukee useiden laitteiden asentamista väylään (yksi isäntä ja useita orjia, useita isäntiä ja useita orjia)
    • Yksi isäntä, useita orjia: Mikro-ohjain toimii isäntänä ja hallitsee I2C-väylän toimintaa. Kaikki I2C-väylään asennetut ulkoiset moduulit ovat orjayksiköitä ilman lupaa I2C-väylän välttämiseksi.
    • Moniisäntä ja moniorja: Mikä tahansa väylän moduuli voi aktiivisesti hypätä ulos ja toimia isäntänä. Kun väyläkonflikti tapahtuu, I2C-protokolla suorittaa välimiesmenettelyn. Osapuoli, joka voittaa välimiesmenettelyn, saa väylän hallinnan, ja hävinneestä osapuolesta tulee automaattisesti orja.

image.png

1.2 Laitteistopiiri

  • Kaikkien I2C-laitteiden SCL on kytketty toisiinsa ja SDA on kytketty yhteen.
  • Laitteen sekä SCL että SDA on konfiguroitava avoimeen ulostulotilaan.
  • Lisää vetovastus kumpaankin SCL:ään ja SDA:han, resistanssiarvo on yleensä noin 4,7 KΩ

Kuvio 1kuva 2
image.png

  • Yksi isäntä ja useita orjia: CPU on yksisiruinen mikrotietokone, joka sisältää SCL-linjan täyden hallinnan milloin tahansa. Lisäksi lepotilassa isäntä voi aktiivisesti aloittaa SDA:n ohjauksen Vain kun orja lähettää tietoja ja orja vastaa, isäntä siirtää SDA:n hallinnan orjalle.
  • Ohjattu IC on I2C-väylään asennettu orja, joka voi olla asentoanturi, OLED, muisti, kellomoduuli jne. Orjan teho on suhteellisen pieni SCL-kellolinjalla se voi lukea vain passiivisesti milloin tahansa. SDA-datalinjalla orja ei saa aktiivisesti aloittaa SDA:n ohjausta. Vasta sen jälkeen, kun isäntä on lähettänyt komennon lukea orja tai kun orja vastaa, orja voi saada hetken aikaa SDA:n hallintaan.
  • Kuva 2: SCL vasemmalla ja SDA oikealla. Kaikki tiedot voidaan syöttää datapuskurin tai Schmitt-triggerin kautta.
    • Koska tulo ei vaikuta piiriin, millä tahansa laitteella voi olla tulo milloin tahansa.
    • Lähtö käyttää avoimen tyhjennyksen lähtökonfiguraatiota, kun lähtö on alhainen, kytkin on päällä ja nasta on kytketty suoraan maahan, mikä on voimakas ulosveto, kun lähtö on korkea, kytkin on pois päältä ei ole kytketty mihinkään ja on kelluvassa tilassa, joten kaikki laitteet voivat tuottaa vain matalan tason, mutta eivät korkean tason aiheuttaman kellumisen välttämiseksi, SCL:ssä ja SDA:ssa on oltava ulkoinen vetovastus väylä, vastuksen kautta vedetty korkealle, joten se on heikko veto. Tällä tavalla ensinnäkin se eliminoi täysin tehooikosulun ilmiön ja varmistaa piirin turvallisuuden, toiseksi se välttää nastamoodien toistuvan vaihdon. Avoimessa tilassa korkean tason ulostulo vastaa nastan irrottamista, joten voit tulostaa korkean tason suoraan ennen syöttämistä. Kolmanneksi tässä tilassa on "langallinen AND" -ilmiö Niin kauan kuin yksi tai useampi laite tuottaa matalan tason. Vain kun kaikki laitteet tuottavat korkean tason. . Siksi I2C voi hyödyntää tätä ilmiötä suorittaakseen kellon synkronoinnin ja väylän sovittelun moniisäntätilassa. Joten vaikka SCL tässä voi käyttää push-pull-lähtöä yhdessä isäntä- ja useiden orjien tilassa, se käyttää silti avoimen tyhjennyksen ja ulosvedettävää lähtötilaa.

1.3 I2C-ajastuksen perusyksikkö

1.3.1 Lähtö- ja lopetusehdot

  • aloitustilanne: SCL:n korkean tason aikana SDA vaihtaa korkealta tasolta matalalle
  • Päättymisehto: SCL:n korkean tason aikana SDA vaihtaa matalalta tasolta korkealle tasolle

image.png

  • alkuolosuhteissa : Kun I2C-väylä on lepotilassa, sekä SCL että SDA ovat korkean tason tilassa, eli mikään laite ei kosketa SCL:ää ja SDA:ta vedetään korkealle tasolle ulkoisilla vetovastuksilla, ja väylä on hiljaisessa tilassa. Kun isäntäkoneen on lähetettävä ja vastaanotettava dataa, sen on ensin katkaistava väylän hiljaisuus ja luotava aloitusehto, eli SCL on korkealla tasolla koskematta siihen, ja vedä sitten SDA alas laskevan reunan luomiseksi. Kun orja kaappaa SCL:n korkean tason ja SDA:n laskevan reunan signaalin, se nollaa itsensä ja odottaa isännän kutsua. SDA:n laskevan reunan jälkeen isäntä tarvitsee SCL:n uudelleen alas. Toisaalta se varaa väylän ja toisaalta helpottaa myös perusyksikön liittämistä. Myöhemmin varmistetaan, että aloitus- ja pysäytysehtoja lukuun ottamatta jokaisen peräkkäisen yksikön SCL alkaa matalalla tasolla ja päättyy matalalle tasolle.
  • Lopetustilan tilassa : SCL päästää irti ensin ja pomppaa korkealle tasolle, sitten SDA päästää irti ja pomppaa korkealle tasolle luoden nousevan reunan, joka laukaisee lopetusehdon. Samanaikaisen lopetustilan jälkeen sekä SCL että SDA ovat korkeat ja palaavat alkuperäiseen rauhalliseen tilaan.
    Isäntä generoi käynnistyksen ja pysäytyksen, eikä orja saa luoda käynnistystä ja pysäytystä. Siksi, kun väylä on tyhjäkäynnillä, orjan on aina vapautettava kätensä, eikä hän saa hypätä ulos ja koskettaa väylää.

1.3.2 Lähetä tavu

  • Lähetä tavu: SCL:n matalan tason aikana isäntä asettaa databitit SDA-riville järjestyksessä (korkea bitti ensin) ja sitten vapauttaa SCL:n. Orja lukee databitit korkean SCL-tason aikana, joten SDA ei ole sallittu saada tietoja korkean SCL-tason aikana.

Matalan tason isäntä laittaa dataa ja korkean tason orja lukee dataa.
image.png
Aloitusehdon jälkeen isännän on lähetettävä myös ensimmäinen tavu. Kun SCL on alhainen, jos isäntä haluaa lähettää 0, se vetää SDA:n matalaksi, jos se haluaa lähettää 1, se päästää irti ja SDA palautuu korkealle tasolle. SCL:n matalan tason aikana SDA:n tason sallitaan muuttua Kun tiedot on sijoitettu, isäntä vapauttaa kellolinjan ja SCL palautuu korkealle tasolle. Korkean tason aikana se on aika, jolloin orja lukee SDA:ta, joten korkean tason aikana SDA ei saa muuttua. Kun SCL on korkealla tasolla, orjan on luettava SDA mahdollisimman nopeasti. Yleensä orja on suorittanut lukemisen SCL:n nousevalla reunalla. Koska isäntä ohjaa kelloa, orja ei tiedä milloin laskeva reuna esiintyy, joten orja lukee tiedot SCL:n nousevasta reunasta. Kun isäntä on päästänyt irti SCL:stä jonkin aikaa, se voi jatkaa SCL:n pienentämistä ja lähettää seuraavan bitin. Isännän on myös asetettava tiedot SDA:han mahdollisimman pian SCL:n laskevan reunan jälkeen. Mutta isäntä hallitsee kelloa, joten sen tarvitsee vain laittaa tietoja SDA:han milloin tahansa, kun alhainen taso on alhainen. Kun tiedot on vapautettu, isäntä vapauttaa SCL:n uudelleen, SCL on korkea ja orja lukee tämän bitin. Kierrä tämä prosessi: isäntä vetää SCL:n matalaksi, asettaa tiedot SDA:han, isäntä vapauttaa SCL:n ja orja lukee SDA-tiedot. SCL:n synkronoinnissa isäntä lähettää ja orja vastaanottaa järjestyksessä 8 jakson jälkeen 8-bittistä dataa, joka on yksi tavu.
Koska se on korkean kertaluvun bitti ensin, ensimmäinen bitti on tavun korkein bitti B7 ja alin bitti B0 lähetetään viimeisenä.

1.3.3 Vastaanota tavu

  • vastaanottaa tavun: SCL:n matalan tason aikana orja asettaa databitit SDA-riville järjestyksessä (korkea bitti ensin) ja vapauttaa sitten SCL:n Isäntä lukee databitit korkean SCL-tason aikana, joten SDA ei ole sallittu saada tietoja korkean SCL-tason aikana.

Matalan tason orja laittaa tiedot, korkean tason isäntä lukee tiedot
image.png
SDA-linja: Isäntäkoneen on vapautettava SDA:n hallinta palautuu korkealle tasolle. Matala taso muuntaa tiedot, korkea taso lukee tiedot. Yhtenäinen viiva edustaa isäntälaitteen ohjaamaa tasoa ja katkoviiva edustaa orjalaitteen ohjaamaa tasoa. Isäntä hallitsee SCL:ää koko prosessin ajan, ja SDA-isäntä on vapautettava ennen vastaanottamista ja luovutettava orjalle ohjattavaksi. Koska isäntä ohjaa SCL-kelloa, orjan datamuunnos suoritetaan periaatteessa SCL:n laskevalla reunalla ja isäntä voi lukea milloin tahansa, kun SCL on korkea.

1.3.4 Lähetä vastaus ja vastaanota vastaus

  • Lähetä vastaus: Vastaanotettuaan yhden tavun isäntä lähettää yhden bitin dataa seuraavalla kellolla Data 0 ilmaisee vastauksen ja data 1 ilmaisee vastausta.
  • saada vastaus: Kun isäntä on lähettänyt tavun, se vastaanottaa hieman dataa seuraavalla kellolla määrittääkseen, vastaako orja Data 0 osoittaa vastauksen, data 1 osoittaa, ettei vastaus ole (isäntäkoneen on vapautettava SDA ennen vastaanottamista).

image.png
Toisin sanoen tavun lähetysajoituksen kutsumisen jälkeen on seurattava vastaanottovasteen kutsumisen ajoitus, jonka perusteella määritetään, onko orja vastaanottanut sille juuri antaman datan. Jos orja vastaanottaa sen, niin vastausbitissä, kun isäntä vapauttaa SDA:n, orjan tulee välittömästi vetää SDA alas, ja sitten korkean SCL-tason aikana isäntä lukee vastausbitin. Jos vastausbitti on 0, se tarkoittaa, että orja on todellakin vastaanottanut sen.
Kun vastaanotat tavun, sinun on kutsuttava lähetysvastaus. Vastauksen lähettämisen tarkoitus on kertoa orjalle, haluatko jatkaa lähettämistä. Jos orjakone vastaanottaa vastauksen isännältä tiedon lähettämisen jälkeen, orjakone jatkaa lähettämistä. Jos orjakone ei saa vastausta isäntäkoneelta, orjakone luulee, että osa dataa on saanut Lähetetty, mutta isäntäkone ei huomioi minua. Ehkä isäntä ei halua sitä. Tällä hetkellä orja vapauttaa SDA:n ja luovuttaa SDA:n hallinnan estääkseen häiriöt isännän myöhemmissä toiminnoissa.

1.4 I2C ajoitus

1.4.1 Määritä osoite, johon kirjoitetaan

  • Määritä osoite, johon haluat kirjoittaa
  • Kirjoita määritetylle laitteelle (Slave Address) määritetyt tiedot (Data) määritettyyn osoitteeseen (Reg Address) (eli määritetyn laitteen rekisteriosoitteeseen)

image.png
käsitellä asiaa:
(1) Käynnistysehdot
(2) Tavun lähettämisen ajoitus - 0xD0 (orjaosoite (7 bittiä) + kirjoitus (1 bitti) - 0) (1101 0000)
(3) Vastaanota vastaus: RA = 0 (vastaanota vastaus orjalta)
(4) Määritetty osoite: 0x19 (0001 1001)
(5) Vastaanota vastaus: RA = 0 (vastaanota vastaus orjalta)
(6) Kirjoita määritetyt tiedot: 0xAA (1010 1010)
(7) Vastaanota vastaus: RA = 0
(8) Pysäytysbitti P (pääteehto)

  • Aloitusehdon jälkeen tulee olla tavun lähettämisen ajoitus. Tavun sisällön tulee olla orjaosoite + luku- ja kirjoitusbitit. Slave-osoite on 7 bittiä ja luku- ja kirjoitusbitit ovat 1 bittiä 8 bittiä. Orja-osoitteen lähettäminen määrittää viestintäobjektin ja luku- ja kirjoitusbitin lähettäminen vahvistaa, kirjoitetaanko vai luetaanko seuraavaksi. Nyt isäntä lähettää datan Tavun sisältö muunnetaan heksadesimaaliksi, joka on 0xD0. Seuraava yksikkö on vastaanottavan orjan vastausbitti. kirjoitusbitti päättyy ja SCL vedetään alas. Tämän jälkeen isäntäkoneen on vapautettava SDA ja sen jälkeen kuittausbitti RA.
  • Korkean tason vastausbitin RA päättymisen jälkeen orja luovuttaa SDA:n hallinnan Koska orja haluaa vaihtaa tietoja mahdollisimman pian SCL:n matalalla tasolla. SCL:n putoava reuna tapahtui lähes samanaikaisesti.
  • Kun vastaus on valmis, jos jatkat yhden tavun lähettämistä, toinen tavu voidaan lähettää määritetyn laitteen sisäpuolelle. Orjalaite voi määrittää toisen itse ja sitä seuraavat tavut. Yleensä toinen tavu voi olla rekisteriosoite tai käskyn ohjaussana jne., ja kolmas tavu on sisältö, jonka isäntä haluaa kirjoittaa rekisteriosoitteeseen (toinen tavu).
  • P on pysäytysbitti.

Tämän tietokehyksen tarkoitus on: Slave-osoitteen 1101000 määrittävälle laitteelle kirjoitetaan tiedot 0xAA sen sisäiseen rekisteriin osoitteeseen 0x19.
0 tarkoittaa: isäntä suorittaa kirjoitustoiminnon seuraavassa ajoituksessa;
1 tarkoittaa: isäntä suorittaa lukutoiminnon seuraavassa ajoitussekvenssissä;

1.4.2 Nykyisen osoitteen lukema

  • Nykyinen osoite luettu
  • Lue määritetyn laitteen (Slave Address) orjatiedot (Data) nykyisen osoiteosoittimen osoittamasta osoitteesta.

image.png
käsitellä asiaa:
(1) Käynnistysehdot
(2) Tavun lähettämisen ajoitus — 0xD1 (orjaosoite (7 bittiä) + luku (1 bitti) -1) (1101 0001)
(3) Vastaanota vastaus: RA = 0 (vastaanota vastaus orjalta)
(4) Lue orjatiedot: 0x0F (0000 1111)
(7) Lähetä vastaus: SA = 0
(8) Pysäytysbitti P (pääteehto)

  • Luku- ja kirjoitusbitti on 1, mikä tarkoittaa, että seuraava lukutoiminto on suoritettava. Kun orja vastaa (RA=0), tiedonsiirtosuunta vaihtuu. Isäntä haluaa luovuttaa SDA:n ohjauksen orjalle, ja isäntä kutsuu tavun vastaanottoajoituksen suorittaakseen vastaanottotoiminnon.
  • Toisessa tavussa orja saa luvan isännältä ja voi kirjoittaa SCL:lle korkean SCL-tason aikana 8 bittiä, vastaanotetaan yksi tavu orjan lähettämää dataa, joka on 0x0F. Mutta mikä orjan rekisteri on 0x0F? Lukuajoituksessa I2C-protokolla määrää, että kun isäntä on osoitettamassa, luku- ja kirjoituslippu on asetettu arvoon 1. Seuraava tavu vaihtaa välittömästi lukuajoitukseen. Siksi isäntä alkaa vastaanottaa ennen kuin se ehtii määrittää, mitä rekisteriä se haluaa lukea, joten tässä ei ole linkkiä osoitteen määrittämiseen. Orjakoneessa kaikki rekisterit on allokoitu lineaariselle alueelle, ja siellä on erillinen osoitinmuuttuja, joka ilmaisee yhden rekisteristä. Tämä osoitin on oletusarvoisesti päällä, yleensä osoittaa osoitteeseen 0, ja aina kun tavu kirjoitetaan ja Tavun lukemisen jälkeen osoitin kasvaa automaattisesti kerran ja siirtyy seuraavaan kohtaan. Sitten kun isäntä ei määritä, mitä osoitetta luetaan, orja palaa osoitteen osoittamaan rekisteriin. nykyinen osoittimen arvo.

1.4.3 Lue määritetyssä osoitteessa

  • Määritä osoite luettavaksi
  • Lue määritetyn laitteen (Slave Address) orjatiedot määritetyn osoitteen (Reg Address) alta (Data)

image.png
Aloita ensin, toista aloitus ja lopeta sitten
käsitellä asiaa:
(1) Käynnistysehdot
(2) Tavun lähettämisen ajoitus - 0xD0 (orjaosoite (7 bittiä) + kirjoitus (1 bitti) - 0) (1101 0000)
(3) Vastaanota vastaus: RA = 0 (vastaanota vastaus orjalta)
(4) Määritetty osoite: 0x19 (0001 1001)
(5) Vastaanota vastaus: RA = 0 (vastaanota vastaus orjalta)
(6) Toista aloitusehto
(7) Tavun lähettämisen ajoitus — 0xD1 (orjaosoite (7 bittiä) + luku (1 bitti) -1) (1101 0001)
(8) Vastaanota vastaus: RA = 0
(9) Lue orjatiedot: 0xAA (1010 1010)
(10) Lähetä vastaus: SA = 0
(11) Pysäytysbitti P (pääteehto)

  • Ensimmäinen osa on kirjoittaa määritettyyn osoitteeseen, mutta vain osoite on määritetty, eikä ole aikaa kirjoittaa, toinen osa on lukea nykyinen osoite, koska osoite on juuri määritetty, joten luettu osoite on soitti uudestaan.
  • Määritetty orja-osoite on 1101000, ja kirjoitustoiminto suoritetaan, kun orja vastaa, kirjoitetaan toinen tavu 0x19 osoiteosoitin, eli kun orja on vastaanottanut tiedot, sen rekisteriosoitin osoittaa sijaintiin 0x19.
  • Sr on toistuva aloitusehto, joka vastaa uuden ajoituksen aloittamista Koska määritetty luku- ja kirjoituslippu voi seurata vain aloitusehdon ensimmäistä tavua, joten jos haluat vaihtaa luku- ja kirjoitussuunnan, voit käyttää vain sitä. toinen aloitusehto.
  • Sitten aloitusehdon jälkeen osoite uudelleen ja määritä luku-kirjoituslippubitti. Tällä hetkellä luku-kirjoituslippubitti on 1, mikä osoittaa, että se on luettava. Sitten isäntä vastaanottaa tavun, joka on data 0xAA osoitteessa 0x19.

2. MPU6050

2.1 MPU6050:n esittely

  • MPU6050 on 6-akselinen asentoanturi, joka voi mitata sirun omien X-, Y- ja Z-akselien kiihtyvyys- ja kulmanopeusparametreja tasapainottavat ajoneuvot, lentokoneet jne., joiden on tunnistettava itsensä
  • 3-akselinen kiihtyvyysanturi (Accelerometer): mittaa X-, Y- ja Z-akselien kiihtyvyyden
  • 3-akselinen gyro-anturi (gyroskooppi): mittaa X-, Y- ja Z-akselien kulmanopeuden

image.png

  • Esimerkkinä lentokoneen rungon Euler-kulma on lentokoneen rungon ja kolmen alkuperäisen akselin välinen kulma.
    • lentokoneLentokoneen nokka kallistuu alas tai ylös, tämän akselin välistä kulmaa kutsutaanPiki
    • lentokoneRunko rullaa vasemmalle tai oikealle, tämän akselin välistä kulmaa kutsutaanRullaa
    • lentokonePidä rungon tasoKäännä lentokoneen nokka vasemmalle tai oikealle, tämän akselin välistä kulmaa kutsutaanYaw
    • Euler-kulma edustaa lentokoneen asentoa tällä hetkellä riippumatta siitä, onko se kallistettu ylös tai alas, kallistettuna vasemmalle tai oikealle.
  • Yleisimmät tietojen yhdistämisalgoritmit sisältävät yleensä täydentävän suodatuksen, Kalman-suodatuksen jne. sekä asenteen laskemisen inertianavigaatiossa.
  • Kiihtyvyysmittari : Keskellä oleva katkoviiva on induktioakseli. Keskellä on pieni liukusäädin, jolla on tietty massa, joka voi liukua vasemmalle ja oikealle sitä vasten. Kun liukusäädin liikkuu, se ajaa sen päällä olevan potentiometrin liikkeelle. Tämä potentiometri on jännitteen jakava vastus. Tämä kiihtyvyysmittari on itse asiassa jousidynamometri. Newtonin toisen lain mukaan F = ma Jos haluat mitata kiihtyvyyden a, voit löytää kohteen, jonka massa on F. Siinä se. Jokaisella X-, Y- ja Z-akselilla on kiihtyvyysanturi. Kiihtyvyysantureilla on staattinen vakaus, mutta ei dynaaminen vakaus.
  • Gyro-anturi : Keskellä on pyörivä pyörä, jolla on tietty massa. Kun pyörivä pyörä pyörii suurella nopeudella, pyörivällä pyörällä on taipumus säilyttää alkuperäinen kulmaliike pyörimisakselin suunta ennallaan. Kun ulkoisen esineen suunta pyörii, sisäisen pyörimisakselin suunta ei pyöri, mikä aiheuttaa kulmapoikkeaman tasapainorenkaan liitännässä. Jos laitat pyörivän potentiometrin liitäntään ja mittaat potentiometrin jännitteen, saat pyörimiskulman. Gyroskoopin pitäisi pystyä määrittämään kulma suoraan, mutta tämän MPU6050:n gyroskoopilla ei voi mitata kulmaa suoraan. Se mittaa kulmanopeutta eli X-akselin, Y-akselin ja Z-akselin ympäri pyörivän sirun kulmanopeutta. -akseli. Kulmanopeuden integraali on kuitenkin kulma, kun kohde on paikallaan, kulmanopeuden arvoa ei voida täysin palauttaa nollaan. Integraalien jatkuvan kertymisen jälkeen tämä pieni kohina aiheuttaa lasketun kulman ajautumisen. joka on kulma, joka saadaan integroimalla kulmanopeus. Se ei kestä ajan koetta, mutta tämä kulma ei ole ongelma, onko se paikallaan tai liikkuva, eikä siihen vaikuta kohteen liike. Gyroskoopeilla on dynaaminen vakaus, ei staattista vakautta.
  • Kiihtyvyysmittarin mukaan, jolla on staattista vakautta, mutta jolla ei ole dynaamista vakautta, nämä kaksi ominaisuutta, joten voimme oppia toistensa vahvuuksista ja täydentää toisiamme , voimme integroida sekä staattisen että dynaamisen vakauden. Asento on hankala.

2.2 MPU6050-parametrit

  • 16-bittinen ADC kerää anturin analogisen signaalin, kvantisointialue: -32768~32767
  • Kiihtyvyysmittarin täyden asteikon valinta: ±2, ±4, ±8, ±16 (g) (1g = 9,8m/s2)
  • Gyroskoopin täyden mittakaavan valinta: ±250, ±500, ±1000, ±2000 (°/s, aste/sekunti, kulmanopeuden yksikkö, kuinka monta kiertoastetta sekunnissa) (mitä suurempi täyden mittakaavan valinta, sitä leveämpi mittausalue mitä pienempi koko mitta-alue on, sitä suurempi on mittausresoluutio.
  • Konfiguroitava digitaalinen alipäästösuodatin: Rekisteri voidaan konfiguroida valitsemaan lähtötietojen alipäästösuodatus.
  • Muokattava kellolähde
  • Konfiguroitava näytteenottotaajuusjako: Kellolähde voidaan jakaa taajuudenjakajalla, jolloin saadaan kelloja AD-muunnoksia ja muita sisäisiä piirejä varten. Säätämällä taajuusjakokerrointa voit hallita AD-muunnoksen nopeutta.
  • I2C-orjaosoite: 1101000 (AD0=0) tai 1101001 (AD0=1)
    • 110 1000 muunnetaan heksadesimaaliksi, joka on 0x68, joten jotkut sanovat, että MPU6050:n orjaosoite on 0x68. Mutta I2C-viestinnässä ensimmäisen tavun korkeat 7 bittiä ovat orjaosoite ja alin bitti on luku- ja kirjoitusbitti. Siksi, jos luulet, että 0x68 on orjaosoite, sinun on ensin vaihdettava ensimmäistä tavua 0x68 Siirrä vasemmalle 1 bittiä (0x68 << 1), lue ja kirjoita bittejä bitti kerrallaan tai ylöspäin, lue 1 ja kirjoita 0.
    • Toinen tapa on siirtää 0x68:n dataa vasemmalle 1 bitin verran (0x68 << 1) orjaosoitteeksi, joka on 0xD0. Tässä tapauksessa MPU6050:n orjaosoite on 0xD0. Tällä hetkellä lähettäessäsi ensimmäistä tavua, jos haluat kirjoittaa, käytä ensimmäisenä tavuna 0xD0 tai 0x01 (0xD0 | 0x01), eli 0xD1 . Tämä esitys ei vaadi siirtoa vasemmalle, tai toisin sanoen tämä esitys integroi luku- ja kirjoitusbitit orjaosoitteeseen. 0xD0 on kirjoitusosoite ja 0xD1 on lukuosoite.

2.3 Laitteistopiiri

image.png

pinToiminto
VCC, GNDvirtalähde
SCL, SDAI2C-tiedonsiirtonasta
XCL, XDAIsäntä I2C-tietoliikennenastat
AD0Orja-osoitteen alin bitti
INTKeskeytä signaalin ulostulo
  • LDO: matalan putoamisen lineaarinen jännitesäädin, 3,3 V jännitesäädin.
  • SCL ja SDA: Ne ovat I2C-tiedonsiirtonastat Moduulissa on sisäänrakennettu kaksi 4.7K vetovastusta, joten kytke SDA ja SCL suoraan GPIO-porttiin .
  • XCL, XDA: Host I2C -tiedonsiirtonastat Nämä kaksi nastaa on suunniteltu laajentamaan sirun toimintoja. Yleensä käytetään ulkoisiin magnetometreihin tai barometreihin. Kun nämä laajennuspiirit on kytketty, MPU6050:n isäntäliitäntä voi suoraan käyttää näiden laajennussirujen tietoja MPU6050:ssä asennelaskenta.
    AD0-nasta: Se on orja-osoitteen alin bitti, jos se on kytketty matalalle tasolle, 7-bittinen orjaosoite on 1101000, jos se on kytketty korkealle tasolle. Piirikaaviossa on vastus, joka on oletusarvoisesti vedetty heikosti alas, joten jos tappi jätetään kellumaan, se on matalalla tasolla ja vedä se voimakkaasti korkealle tasolle.
  • INT: Keskeytyksen lähtönasta Voit määrittää joitain tapahtumia sirun sisällä laukaisemaan keskeytysnastan lähdön, kuten data ready, I2C isäntävirhe jne.
  • Sirussa on myös sisäänrakennettu: vapaan pudotuksen tunnistus, liikkeentunnistus, nollaliiketunnistus jne. Nämä signaalit voivat laukaista INT-nastan luomaan tasosiirtymän, ja keskeytyssignaalit voidaan konfiguroida tarvittaessa.
  • MPU6050-sirun virtalähde on 2,375-3,46 V, joka on 3,3 V:n virtalähde eikä sitä voi kytkeä suoraan 5 V:iin. Siksi 3,3 V jännitteensäädin lisätään, ja tuloliittimen jännite VCC_5V voi olla välillä 3,3 V ja 5 V. Sitten 3,3 V:n jännitteensäädin tuottaa vakaan 3,3 V:n jännitteen niin kauan kuin 3,3 V:n liittimessä on virtaa , Virran merkkivalo syttyy.

2.4 MPU6050 lohkokaavio

image.png

  • CLKIN ja CLKOUT ovat kellon tulonastat ja kellon lähtönastat, mutta käytämme yleensä sisäistä kelloa.
  • Harmaa osa: on anturi sirun sisällä, kiihtyvyysanturi XYZ-akselilla ja gyroskooppi XYZ-akselilla.
  • Siinä on myös sisäänrakennettu lämpötila-anturi, jota voidaan käyttää lämpötilan mittaamiseen.
  • Nämä anturit vastaavat olennaisesti muuttuvia vastuksia jännitteen jakamisen jälkeen, ja ne suorittavat sitten analogia-digitaalimuunnoksen ADC:n kautta rekisteri, joka saadaan lukemalla tietorekisteri Anturin mittaama arvo. Kaikki tämän sirun muunnokset ovat täysin automatisoituja.
  • Jokaisessa anturissa on itsetestiyksikkö, jolla varmistetaan sirun laatu Kun itsetesti käynnistetään, siru simuloi anturiin kohdistuvaa ulkoista voimaa tavallista suurempi. Itsetestausprosessi: Voit ottaa itsetestin käyttöön ensin, lukea tiedot, sitten ottaa itsetestin käyttöön, lukea tiedot, vähentää kaksi dataa, ja tuloksena olevaa dataa kutsutaan itsetestivasteeksi. Käsikirja antaa tälle itsetestausvasteelle alueen. Jos se on tällä alueella, sirussa ei ole ongelmia.
  • Latauspumppu: Se on latauspumppu tai latauspumppu.
  • CPOUT-nasta vaatii ulkoisen kondensaattorin.
  • Keskeytystilarekisteri: voi hallita, mitkä sisäiset tapahtumat lähetetään keskeytysnastalle,
  • FIFO: First-in-first-out-rekisteri, joka voi tallentaa tietovirran välimuistiin.
  • Konfigurointirekisteri: Voit konfiguroida erilaisia ​​sisäisiä piirejä
  • Anturirekisteri: Datarekisteri, joka tallentaa kunkin anturin tiedot.
  • Tehdaskalibroitu: Tämä tarkoittaa, että sisällä olevat anturit on kalibroitu.
  • Digitaalinen liikeprosessori: Lyhyesti DMP, se on sirun sisällä oleva asenteen laskemiseen tarkoitettu laitteistoalgoritmi. Sitä voidaan käyttää asenteen laskemiseen virallisen DMP-kirjaston kanssa.
  • FSYNC: Kehyssynkronointi.

3. 10-1 Ohjelmisto I2C:n lukemiseen ja kirjoittamiseen MPU6050

3.1 Laitteistoliitäntä

Ohjelmiston I2C-viestinnän avulla voit lukea ja kirjoittaa MPU6050-sirun sisällä olevat rekisterit Lue tiedot näytetään OLED-näytössä laitteen tunnusnumero. Tämän MPU6050:n tunnusnumero on kiinteä. Alla olevat kolme vasemmalla ovat kiihtyvyysanturin lähtötiedot, jotka ovat X-akselin, Y-akselin ja Z-akselin kiihtyvyys. Oikealla olevat kolme ovat gyroskoopin anturin lähtötiedot. jotka ovat X-akselin, Y-akselin ja Z-akselin kulmanopeus.
SCL on kytketty STM32:n PB10-nastaan ​​ja SDA on kytketty PB11-nastaan. Koska ohjelmistotason vaihto on toteutettu tässä, kaksi GPIO-porttia voidaan kytkeä haluttaessa.

3.2 Toiminnan tulokset

IMG_20240406_155156.jpg

3.3 Koodikulku

STM32 on isäntä ja MPU6050 on orja, joka on isäntä-orja-tila.

  1. Perustele I2C-viestintäkerroksen .c- ja .h-moduulit
    1. Kirjoita I2C:n taustalla oleva GPIO-alustus
    2. 6 perusajastusyksikköä: aloita, lopeta, lähetä tavu, vastaanota tavu, lähetä vastaus, vastaanota vastaus
  2. Luo MPU6050:n .c- ja .h-moduulit
    1. Perustuen I2C-viestintämoduuliin, se toteuttaa lukemisen määritetyssä osoitteessa, kirjoittamisen määritettyyn osoitteeseen, kirjoittaa rekistereitä sirun konfiguroimiseksi ja lukee rekistereitä anturitietojen saamiseksi.
  3. main.c
    1. Soita MPU6050-moduuliin, alusta, hanki tiedot ja näytä tiedot

3.4 Koodi

  1. I2C koodi:
#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 koodi:
#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-oheislaitteet

4.1 I2C-oheislaitteiden esittely

  • STM32 integroi laitteiston I2C-lähetin-vastaanotinpiirin, joka voi automaattisesti suorittaa toimintoja, kuten kellon generointi, aloitus- ja lopetusehtojen luominen, vastebittien lähetys ja vastaanotto sekä tiedonsiirto ja vastaanotto laitteistolla, mikä vähentää prosessorin kuormitusta.
  • Tukee monen isäntämallia
  • Tukee 7-bittistä/10-bittistä osoitetilaa
  • Tukee erilaisia ​​tiedonsiirtonopeuksia, vakionopeus (jopa 100 kHz), nopea (jopa 400 kHz)
  • Tuki DMA
  • Yhteensopiva SMBus-protokollan kanssa
  • STM32F103C8T6 laitteiston I2C-resurssit: I2C1, I2C2

4.2 I2C-lohkokaavio

image.png

  • Vasemmalla ovat tiedonsiirtonastat: SMBus käyttää SDA:ta ja SMBALERTia;
    Yleisistä oheislaitteista johdetut nastat yhdistetään yleensä ulkomaailmaan GPIO-portin multipleksointitilan kautta (katso taulukko)
  • Yllä oleva on tiedon ohjausosa: SDA Tiedonsiirron ja vastaanoton ydinosa on datarekisteri DR (DATA REGISTER) ja datasiirtorekisteri. Kun dataa on lähetettävä, tietorekisteriin DR voidaan kirjoittaa yksi tavu. Vaihtoprosessin aikana seuraava tieto voidaan laittaa suoraan tietorekisteriin ja odottaa. Kun edellinen tiedonsiirto on valmis, seuraava data voidaan yhdistää saumattomasti ja jatkaa lähettämistä. Kun dataa siirretään tietorekisteristä siirtorekisteriin, tilarekisterin TXE-bitiksi asetetaan 1, mikä osoittaa, että lähetysrekisteri on tyhjä.
  • Vastaanotto: Tulotiedot siirretään nastasta siirtorekisteriin bitti kerrallaan Kun yksi tavu dataa kerätään, tiedot siirretään siirtorekisteristä tietorekisteriin kokonaisuutena ja RXNE-lippu asetetaan kohtaan. Rekisteri ei ole tyhjä, jolloin tiedot voidaan lukea tietorekisteristä. Mitä tulee vastaanoton ja lähetyksen ajankohtiin, sinun on kirjoitettava vastaavat bitit ohjausrekisteriin toimintaa varten. Aloitusehdot, lopetusehdot, vastausbitit jne. täydennetään tietojen ohjauksella.
  • Komparaattoria ja osoiterekisteriä käytetään orjatilassa.
  • SCL: Kellon ohjausta käytetään SCL-linjan ohjaamiseen. Kirjoita vastaava bitti kellon ohjausrekisteriin, niin piiri suorittaa vastaavan toiminnon. Ohjauslogiikkapiiri, ohjausrekisteriin kirjoittaminen voi ohjata koko piiriä. Piirin toimintatila voidaan tietää lukemalla tilarekisteri.
  • Lähetettäessä ja vastaanotettaessa useita tavuja DMA:ta voidaan käyttää tehokkuuden parantamiseen.

4.3 I2C perusrakenne

image.png

  • SDA: Koska I2C on korkealuokkainen ensimmäinen, tämä siirtorekisteri siirtyy vasemmalle. Lähetettäessä korkea bitti siirretään ensin ulos, sitten toinen korkea bitti. SCL-kelloa siirretään kerran ja siirretään 8 kertaa, ja SDA-linjalle voidaan sijoittaa 8 tavua korkeasta bitistä matalaan bittiin. Vastaanottaessa tiedot siirretään sisään oikealta GPIO-portin kautta ja lopuksi 8 kertaa vastaanotetaan yksi tavu. Lähtötiedot lähetetään porttiin GPIO-portin kautta. Tulotiedot syötetään siirtorekisteriin GPIO-portin kautta.
  • GPIO-portti on konfiguroitava multipleksoimaan avoimen valunnon lähtötila. Jopa avoimessa ulostulotilassa GPIO-portti voidaan syöttää.
  • SCL: Kelloohjain ohjaa kellolinjaa GPIO:n kautta.
    image.png

4.4 Isäntä lähettää

image.png
Kun STM32 haluaa kirjoittaa tiettyyn osoitteeseen, sen on noudatettava lähettimen lähetysjärjestyskaaviota.

  • 7-bittinen osoite: Aloitusehdon jälkeinen tavu on osoitettu
  • 10-bittinen osoite: Aloitusehdon jälkeiset kaksi tavua ovat osoitus Puhdas 8-bittinen osoite.
  • 7-bittinen prosessi: aloitus, orjaosoite, vastaus, data, vastaus, data, vastaus... Pysäytä
  1. Alustamisen jälkeen väylä siirtyy oletusarvoisesti lepotilaan ja STM oletusarvoisesti orjatilaan. Alkutilan luomiseksi STM32:n on kirjoitettava ohjausrekisteriin (CR1), kirjoitettava 1 ja sitten STM32 vaihtaa orjatilasta isäntätilaan. .

image.png

  1. EV5-tapahtumaa voidaan pitää lippubittinä, joka osoittaa laitteiston tilan SB=1 tarkoittaa, että aloitusehto on lähetetty.

image.png

  1. Sitten voit lähettää orjaosoitteen tavun. Slave-osoite on kirjoitettava tietorekisteriin DR. lähetetään I2C-väylään, ja sitten laitteisto vastaanottaa automaattisesti vastauksen ja arvioi, jos vastausta ei ole, laitteisto asettaa vastauksen epäonnistumisen lipun ja sitten lippu voi hakea keskeytystä muistuttamaan meitä.
  2. Kun osoitus on valmis, tapahtuu EV6-tapahtuma ja ADDR-lipun bitti on 1. Tämä lippubitti ilmaisee osoitteensiirron päättymisen master-tilassa.

image.png

  1. EV8_1-tapahtuma tarkoittaa, että TxE-lippu on 1, siirtorekisteri on tyhjä ja tietorekisteriin DR on kirjoitettava lähettääksesi tiedot, koska siirtorekisteri on tyhjä siirtyy välittömästi vaihtoon rekisteröidy lähettääksesi. EV8-tapahtuma tapahtuu. Siirtorekisteri ei ole tyhjä ja tietorekisteri on tyhjä, mikä tarkoittaa, että vuororekisteri lähettää dataa. Tällä hetkellä data 2 kirjoitetaan tietorekisteriin ja odottaa vastausbitin vastaanottamisen jälkeen databitit siirretään siirtorekisteriin. Tila on tällä hetkellä, että siirtorekisteri ei ole tyhjä tietorekisteri on tyhjä, joten tällä hetkellä EV8-tapahtuma toistui.
  2. Sitten lähetetään dataa 2, mutta tällä kertaa seuraava data on kirjoitettu tietorekisteriin ja odottaa. Kun EV8-tapahtuma on havaittu, seuraava data voidaan kirjoittaa.
  3. Lähetettävän tiedon kirjoittamisen jälkeen tietorekisteriin ei kirjoiteta uutta tietoa Kun vuororekisterin nykyinen tiedonsiirto on suoritettu, vuororekisteri on tyhjä ja myös tietorekisteri on tyhjä. EV8_2 tapahtuma, TxE=1 tarkoittaa, että siirtorekisteri on tyhjä, tietorekisteri tyhjä, BTF: tavun lähetyksen lopetuslippu, kun lähetettäessä lähetetään uutta dataa eikä tietorekisteriin ole kirjoitettu uudella tiedolla . Kun EV8_2 havaitaan, voidaan generoida lopetusehto Stop. Pääteehdon generoimiseksi ohjausrekisterissä pitäisi luonnollisesti olla vastaavat bitit, joita voidaan ohjata. Tällä tavalla lähetysjakso on ohi.

4.5 Isäntävastaanotto

image.png
7-bittinen isäntävastaanotto: aloitus, orjaosoite + lue, vastaanota vastaus, vastaanota tiedot, lähetä vastaus... vastaanota tietoja, ei vastausta, lopeta

  1. Kirjoita ensin ohjausrekisterin aloitusbitti alkuehdon luomiseksi ja odota sitten EV5-tapahtumaa (joka osoittaa, että aloitusehto on lähetetty).
  2. Osoittamisen jälkeen vastaanotetaan vastaus ja lopun jälkeen generoidaan EV6-tapahtuma (osoitus, että osoitus on valmis).
  3. Data 1 tarkoittaa, että dataa syötetään siirtorekisterin kautta.
  4. EV6_1 ilmaisee, että dataa siirretään edelleen Vastauksen saatuaan, se tarkoittaa, että siirtorekisteri on siirtynyt onnistuneesti yhden tavun dataa 1. Tällä hetkellä siirretty tavu siirretään tietorekisteriin kokonaisuutena. RxNE-lippu asetetaan samaan aikaan, mikä osoittaa, että tietorekisteri ei ole tyhjä, eli yksi tavu on vastaanotettu. Tila on EV7-tapahtuma, RxNE=1 tyhjentää tapahtuman että tiedot on vastaanotettu Kun olemme lukeneet tiedot, tapahtuma Ei ole enää.
  5. Tietenkin, kun dataa 1 ei ole luettu, data 2 voidaan siirtää suoraan siirtorekisteriin. Tämän jälkeen data 2 on siirretty loppuun, data 2 vastaanotetaan, EV7-tapahtuma luodaan, data 2 luetaan ja data 2 on suoritettu. EV7-tapahtuma on poissa.
  6. Kun vastaanottoa ei enää tarvita, vastausbitin ohjausrekisteri ACK on asetettava arvoon 0 etukäteen, kun viimeinen ajoitusyksikkö esiintyy, ja asetetaan lopetusehtopyyntö eli EV7_1-tapahtuma Jälkeenpäin ei-vastaava NA Koska STOP-bitti on asetettu, syntyy lopetusehto.

4.6 Ohjelmiston/laitteiston aaltomuotojen vertailu

image.png

image.png

5. 10-2 Laitteiston I2C luku- ja kirjoitus MPU6050

5.1 I2C-kirjaston toiminnot


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 lukea ja kirjoittaa MPU6050 toteutus

5.2.1 Laitteistoyhteys

SCL on kytketty STM32:n PB10-nastaan ​​ja SDA on kytketty PB11-nastaan. Koska ohjelmistotason vaihto on toteutettu tässä, kaksi GPIO-porttia voidaan kytkeä haluttaessa.
OLEDin päätiedot ovat laitteen tunnusnumero. Tämän MPU6050:n tunnusnumero on kiinteä 0x68. Alla olevat kolme vasemmalla ovat kiihtyvyysanturin lähtötiedot, jotka ovat X-akselin, Y-akselin ja Z-akselin kiihtyvyys. Oikealla olevat kolme ovat gyroskoopin anturin lähtötiedot. jotka ovat X-akselin, Y-akselin ja Z-akselin kulmanopeus.

5.2.2 Toiminnan tulokset

IMG_20240406_172128.jpg

5.2.3 Koodin käyttöönottoprosessi

  1. Määritä I2C-oheislaitteet, alusta I2C-oheislaitteet, korvaa MyI2C_Init
    (1) Kytke päälle I2C-oheislaitteen kello ja vastaava GPIO-portti,
    (2) Alusta I2C-oheislaitetta vastaava GPIO-portti multipleksoituun avoimeen tilaan
    (3) Käytä rakennetta koko I2C:n konfigurointiin
    (4) I2C_Cmd, ota I2C käyttöön
  2. Ohjaa oheispiirejä, toteuttaa määritettyihin osoitteisiin kirjoittamisen ajoitus ja korvaa WriteReg
  3. Ohjaa oheispiiriä tunnistaaksesi määritetyn osoitteen lukemisen ajoitus ja vaihda ReadReg

5.2.4 Koodi

  1. MPU6050 koodi:
#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. main.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