Condivisione della tecnologia

STM32-I2C

2024-07-12

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

Questo contenuto è basato suTecnologia Jiangxie STM32Compilato dopo lo studio video.

1. Comunicazione I2C

1.1 Introduzione alla comunicazione I2C

  • I2C (Inter IC Bus) è un bus dati universale sviluppato da Philips
  • Due linee di comunicazione: linea orologio seriale SCL (Serial Clock), linea dati seriale SDA (Serial Data).
  • Sincrono, half-duplex, single-ended, multi-dispositivo
  • Rispondi con i dati
  • Supporta il montaggio di più dispositivi sul bus (un master e più slave, più master e più slave)
    • Un master, più slave: il microcontrollore funge da host e domina il funzionamento del bus I2C. Tutti i moduli esterni montati sul bus I2C sono slave. Gli slave possono controllare il bus I2C solo dopo essere stati nominati dall'host e non possono toccarlo senza autorizzazione. bus I2C per prevenire conflitti.
    • Multimaster e multislave: qualsiasi modulo sul bus può saltare fuori attivamente e fungere da master. Quando si verifica un conflitto sul bus, il protocollo I2C condurrà l'arbitrato. La parte che vince l'arbitrato ottiene il controllo del bus e la parte perdente diventa automaticamente una schiava.

immagine.png

1.2 Circuito hardware

  • Gli SCL di tutti i dispositivi I2C sono collegati insieme e l'SDA è collegato insieme.
  • Sia SCL che SDA del dispositivo devono essere configurati in modalità di uscita open-drain.
  • Aggiungi un resistore pull-up a ciascuno tra SCL e SDA, il valore della resistenza è generalmente di circa 4,7 KΩ

Figura 1figura 2
immagine.png

  • Un master e più slave: la CPU è un microcomputer a chip singolo, in quanto master del bus, include il controllo completo della linea SCL in qualsiasi momento. Inoltre, nello stato inattivo, l'host può avviare attivamente il controllo di SDA. Solo quando lo slave invia dati e lo slave risponde, l'host trasferirà il controllo di SDA allo slave.
  • L'IC controllato è uno slave montato sul bus I2C, che può essere un sensore di assetto, un OLED, una memoria, un modulo orologio, ecc. La potenza dello slave è relativamente piccola. Per la linea di clock SCL, può solo leggere passivamente in qualsiasi momento. Allo slave non è consentito controllare la linea SCL. Per la linea dati SDA, allo slave non è consentito avviare attivamente il controllo di SDA. Solo dopo che il master invia un comando di lettura allo slave, o quando lo slave risponde, lo slave può acquisire brevemente il controllo dell'SDA.
  • Figura 2: SCL a sinistra e SDA a destra. Tutti i dati possono essere immessi tramite un buffer di dati o un trigger Schmitt.
    • Poiché l'ingresso non ha alcun effetto sul circuito, qualsiasi dispositivo può avere ingresso in qualsiasi momento.
    • L'uscita utilizza una configurazione di uscita a drain aperto. Quando l'uscita è bassa, l'interruttore è acceso e il pin è collegato direttamente a terra, che è un forte pulldown; quando l'uscita è alta, l'interruttore è spento e il pin non è collegato a nulla ed è in uno stato fluttuante, in modo che tutti i dispositivi possano emettere solo un livello basso ma non un livello alto. Per evitare il fluttuazione causato dal livello alto, SCL e SDA devono avere un resistore pull-up esterno il bus, attraverso un resistore, è tirato ad un livello alto, quindi è un pull-up debole. In questo modo, in primo luogo, si elimina completamente il fenomeno del cortocircuito di alimentazione e si garantisce la sicurezza del circuito, in secondo luogo si evita la commutazione frequente delle modalità dei pin; In modalità open-drain, emettere un livello alto equivale a scollegare il pin, quindi è possibile emettere direttamente un livello alto prima dell'immissione. In terzo luogo, questa modalità ha un fenomeno "AND cablato". Finché uno o più dispositivi emettono un livello basso, il bus è a un livello basso. Solo quando tutti i dispositivi emettono un livello alto, il bus è a un livello alto. . Pertanto, I2C può trarre vantaggio da questo fenomeno per eseguire la sincronizzazione dell'orologio e l'arbitraggio del bus in modalità multi-master. Quindi, sebbene SCL qui possa utilizzare l'uscita push-pull in modalità un master e più slave, utilizza comunque la modalità di uscita open-drain più pull-out.

1.3 Unità base di temporizzazione I2C

1.3.1 Condizioni iniziali e condizioni finali

  • condizione di partenza: Durante il livello alto SCL, SDA passa dal livello alto al livello basso
  • Condizione di cessazione: Durante il livello alto SCL, SDA passa dal livello basso al livello alto

immagine.png

  • nelle condizioni iniziali : Quando il bus I2C è in stato inattivo, sia SCL che SDA sono in uno stato di livello alto, ovvero nessun dispositivo tocca SCL e SDA e SDA vengono portati a livello alto da resistori pull-up esterni e il bus lo è in uno stato tranquillo. Quando l'host deve inviare e ricevere dati, deve prima rompere il silenzio del bus e generare una condizione di avvio, ovvero SCL è a un livello alto senza toccarlo, quindi abbassare SDA per generare un fronte di discesa. Quando lo slave cattura il livello alto SCL e il segnale del fronte di discesa SDA, si resetterà e attenderà la chiamata del master. Dopo il fronte di discesa di SDA, l'host deve abbattere nuovamente SCL, da un lato per occupare il bus e, dall'altro, anche per facilitare la giunzione dell'unità di base. Successivamente verrà garantito che, ad eccezione delle condizioni di avvio e arresto, l'SCL di ciascuna unità sequenziale inizia con un livello basso e termina con un livello basso.
  • Nello stato della condizione di terminazione : SCL lascia andare per primo e rimbalza al livello alto, poi SDA lascia andare e rimbalza al livello alto, generando un fronte di salita, che innesca la condizione di terminazione. Dopo la condizione di terminazione simultanea, sia SCL che SDA sono elevati e ritornano allo stato calmo iniziale.
    L'avvio e l'arresto sono generati dall'host e allo slave non è consentito generare l'avvio e l'arresto. Pertanto, quando l'autobus è inattivo, lo schiavo deve sempre rilasciare le mani e non gli è consentito saltare fuori e toccare l'autobus.

1.3.2 Invia un byte

  • Invia un byte: Durante il livello basso di SCL, l'host inserisce i bit di dati sulla linea SDA in sequenza (prima il bit alto), quindi rilascia SCL. Lo slave leggerà i bit di dati durante il livello alto di SCL, quindi SDA non è consentito per avere tutti i dati durante il livello alto di SCL. Quando i dati cambiano, ripetere il processo sopra 8 volte per inviare un byte.

L'host di basso livello inserisce i dati e lo slave di alto livello legge i dati.
immagine.png
Dopo la condizione di avvio, anche il primo byte deve essere inviato dall'host. Quando SCL è basso, se l'host vuole inviare 0, abbassa SDA, se vuole inviare 1, lascia andare e SDA ritorna al livello alto; Durante il livello basso di SCL, il livello di SDA può cambiare Dopo che i dati sono stati inseriti, l'host rilascia la linea dell'orologio e SCL ritorna al livello alto. Durante il livello alto, è il momento in cui lo slave legge SDA, quindi durante il livello alto, SDA non può cambiare. Dopo che SCL ha raggiunto un livello alto, lo slave deve leggere SDA il più rapidamente possibile. Generalmente, lo slave ha completato la lettura sul fronte di salita di SCL. Poiché l'orologio è controllato dal master, lo slave non sa quando si verifica il fronte di discesa, quindi leggerà i dati sul fronte di salita di SCL. Dopo che l'host ha lasciato andare SCL per un periodo di tempo, può continuare a abbassare SCL e trasmettere il bit successivo. L'host deve inoltre inserire i dati su SDA il prima possibile dopo il fronte di discesa di SCL. Ma l'host ha il controllo sull'orologio, quindi deve solo inserire i dati su SDA in qualsiasi momento quando il livello basso è basso. Dopo che i dati sono stati rilasciati, l'host rilascia nuovamente SCL, SCL è alto e lo slave legge questo bit. Ripeti questo processo: l'host abbassa SCL, inserisce i dati su SDA, l'host rilascia SCL e lo slave legge i dati SDA. Nella sincronizzazione di SCL, il master trasmette e lo slave riceve in sequenza. Dopo 8 cicli, vengono inviati dati a 8 bit, ovvero un byte.
Poiché è il bit di ordine più alto per primo, il primo bit è il bit più alto B7 di un byte e il bit più basso B0 viene inviato per ultimo.

1.3.3 Ricevere un byte

  • ricevere un byte: Durante il livello basso di SCL, lo slave mette i bit di dati sulla linea SDA in sequenza (prima il bit alto), quindi rilascia SCL. L'host leggerà i bit di dati durante il livello alto di SCL, quindi SDA non è consentito per avere tutti i dati durante il livello alto di SCL Quando i dati cambiano, ripetere il processo sopra 8 volte per ricevere un byte (l'host deve rilasciare SDA prima di ricevere).

Lo slave di basso livello inserisce i dati, l'host di alto livello legge i dati
immagine.png
Linea SDA: il master deve rilasciare SDA prima di ricevere. In questo momento, lo slave ottiene il controllo di SDA. Se lo slave deve inviare 0, abbassa SDA. Se lo slave deve inviare 1, lascia andare e SDA rimbalza ad alti livelli. Il livello basso converte i dati, il livello alto legge i dati. La linea continua rappresenta il livello controllato dal master, mentre la linea tratteggiata rappresenta il livello controllato dallo slave. SCL è controllato dall'host durante l'intero processo e l'host SDA deve essere rilasciato prima di ricevere e consegnato allo slave per il controllo. Poiché l'orologio SCL è controllato dall'host, la conversione dei dati dello slave viene sostanzialmente eseguita sul fronte di discesa di SCL e l'host può leggere in qualsiasi momento quando SCL è alto.

1.3.4 Invia risposta e ricevi risposta

  • Invia risposta: Dopo aver ricevuto un byte, l'host invia un bit di dati all'orologio successivo. I dati 0 indicano la risposta e i dati 1 indicano la mancata risposta.
  • ricevere risposta: Dopo che l'host ha inviato un byte, riceve un bit di dati all'orologio successivo per determinare se lo slave risponde. Dati 0 indica risposta, dati 1 indica mancata risposta (l'host deve rilasciare SDA prima di ricevere).

immagine.png
Cioè, dopo aver chiamato il tempo di invio di un byte, deve essere seguito il tempo di chiamata della risposta di ricezione, che serve per determinare se lo slave ha ricevuto i dati appena forniti. Se lo slave lo riceve, nel bit di risposta, quando il master rilascia SDA, lo slave dovrebbe immediatamente abbassare SDA e quindi durante il livello alto di SCL, l'host legge il bit di risposta. Se il bit di risposta è 0 significa che lo slave lo ha effettivamente ricevuto.
Quando si riceve un byte, è necessario chiamare la risposta di invio. Lo scopo dell'invio di una risposta è dire allo slave se si desidera continuare a inviare. Se la macchina slave riceve una risposta dal master dopo aver inviato un dato, la macchina slave continuerà a inviare. Se la macchina slave non riceve una risposta dalla macchina master, la macchina slave penserà che sia presente un dato stato inviato, ma la macchina master mi ignora. Forse L'host non lo vuole In questo momento, lo slave rilascerà obbedientemente SDA e cederà il controllo di SDA per evitare interferenze con le operazioni successive dell'host.

1.4 Tempistiche I2C

1.4.1 Specificare l'indirizzo da scrivere

  • Specificare l'indirizzo a cui scrivere
  • Per il dispositivo specificato (indirizzo slave), scrivere i dati specificati (dati) all'indirizzo specificato (indirizzo registro) (ovvero, l'indirizzo del registro del dispositivo specificato)

immagine.png
processi:
(1) Condizioni iniziali
(2) Momento di invio di un byte: 0xD0 (indirizzo slave (7 bit) + scrittura (1 bit)-0) (1101 0000)
(3) Ricevi risposta: RA = 0 (ricevi la risposta dallo slave)
(4) Indirizzo specificato: 0x19 (0001 1001)
(5) Ricevi risposta: RA = 0 (ricevi la risposta dallo slave)
(6) Scrivi i dati specificati: 0xAA (1010 1010)
(7) Ricevere risposta: RA = 0
(8) Bit di stop P (condizione di terminazione)

  • Dopo la condizione di avvio, deve essere il momento di inviare un byte. Il contenuto del byte deve essere l'indirizzo dello slave + i bit di lettura e scrittura. L'indirizzo dello slave è di 7 bit e i bit di lettura e scrittura sono di 1 bit, che è esattamente 8 bit. L'invio dell'indirizzo dello slave serve per determinare l'oggetto di comunicazione, mentre l'invio del bit di lettura e scrittura serve per confermare se scrivere o leggere successivamente. Ora l'host invia un pezzo di dati. Il contenuto del byte viene convertito in esadecimale. Il bit di ordine superiore è 0xD0. L'unità successiva è il bit di risposta (RA) dello slave ricevente. il bit di scrittura termina e SCL viene abbassato. Successivamente, l'host deve rilasciare SDA, seguito dal bit di riconoscimento RA.
  • Il livello alto dopo la fine del bit di risposta RA è generato dallo slave che rilascia SDA. Lo slave passa il controllo di SDA poiché lo slave vuole scambiare dati il ​​prima possibile al livello basso di SCL, il fronte di salita di SDA e il fronte di discesa di SCL è avvenuto quasi simultaneamente.
  • Una volta completata la risposta, se si continua a inviare un byte, il secondo byte può essere inviato all'interno del dispositivo designato. Il dispositivo slave può definire l'utilizzo del secondo sé e dei byte successivi. Generalmente, il secondo byte può essere un indirizzo di registro o una parola di controllo di istruzione, ecc., e il terzo byte è il contenuto che l'host desidera scrivere nell'indirizzo di registro (secondo byte).
  • P è il bit di stop.

Lo scopo di questo frame di dati è: per il dispositivo che specifica l'indirizzo slave 1101000, scrivere i dati 0xAA nel suo registro interno all'indirizzo 0x19.
0 significa: l'host eseguirà un'operazione di scrittura nelle tempistiche successive;
1 significa: l'host eseguirà un'operazione di lettura nella sequenza temporale successiva;

1.4.2 Lettura indirizzo corrente

  • Indirizzo attuale letto
  • Per il dispositivo specificato (indirizzo slave), leggere i dati dello slave (dati) all'indirizzo indicato dal puntatore dell'indirizzo corrente.

immagine.png
processi:
(1) Condizioni iniziali
(2) Momento di invio di un byte: 0xD1 (indirizzo slave (7 bit) + lettura (1 bit)-1) (1101 0001)
(3) Ricevi risposta: RA = 0 (ricevi la risposta dallo slave)
(4) Leggi i dati dello slave: 0x0F (0000 1111)
(7) Invia risposta: SA = 0
(8) Bit di stop P (condizione di terminazione)

  • Il bit di lettura e scrittura è 1, indicando che deve essere eseguita la successiva operazione di lettura. Dopo la risposta dello slave (RA=0), la direzione di trasmissione dei dati verrà invertita. Il master vuole cedere il controllo di SDA allo slave e il master chiama il tempo di ricezione di un byte per eseguire l'operazione di ricezione.
  • Nel secondo byte, lo slave ottiene il permesso dal master e può scrivere su SCL durante il livello basso di SCL. Il master legge SDA durante il livello alto di SCL. Infine, il master legge in sequenza durante il livello alto di SCL 8 bit, viene ricevuto un byte di dati inviato dallo slave, che è 0x0F. Ma quale registro dello slave è 0x0F? Nei tempi di lettura, il protocollo I2C stabilisce che quando l'host si sta indirizzando, una volta che il flag di lettura e scrittura è impostato su 1. Il byte successivo passerà immediatamente alla temporizzazione di lettura. Pertanto, l'host inizierà a ricevere prima di avere il tempo di specificare quale registro desidera leggere, quindi non è presente alcun collegamento per specificare l'indirizzo qui. Nella macchina slave, tutti i registri sono allocati in un'area lineare e ci sarà una variabile puntatore separata che indica uno dei registri. Questo puntatore per impostazione predefinita è all'accensione, generalmente punta all'indirizzo 0 e ogni volta che viene scritto un byte e Dopo aver letto un byte, il puntatore si incrementerà automaticamente una volta e si sposterà alla posizione successiva. Quindi, quando si richiama il tempo di lettura dell'indirizzo corrente, se l'host non specifica quale indirizzo leggere, lo slave tornerà al registro puntato dal. puntatore corrente.

1.4.3 Leggi all'indirizzo specificato

  • Specificare l'indirizzo da leggere
  • Per il dispositivo specificato (Slave Address), sotto l'indirizzo specificato (Reg Address), leggere i dati dello slave (Data)

immagine.png
Inizia prima, poi ripeti l'avvio, quindi fermati
processi:
(1) Condizioni iniziali
(2) Momento di invio di un byte: 0xD0 (indirizzo slave (7 bit) + scrittura (1 bit)-0) (1101 0000)
(3) Ricevi risposta: RA = 0 (ricevi la risposta dallo slave)
(4) Indirizzo specificato: 0x19 (0001 1001)
(5) Ricevi risposta: RA = 0 (ricevi la risposta dallo slave)
(6) Ripetere la condizione iniziale
(7) Momento di invio di un byte: 0xD1 (indirizzo slave (7 bit) + lettura (1 bit)-1) (1101 0001)
(8) Ricevere risposta: RA = 0
(9) Leggi i dati dello slave: 0xAA (1010 1010)
(10) Invia risposta: SA = 0
(11) Bit di stop P (condizione di terminazione)

  • La prima parte è scrivere all'indirizzo specificato, ma viene specificato solo l'indirizzo e non c'è tempo per scrivere; la seconda parte è leggere l'indirizzo corrente, poiché l'indirizzo è appena stato specificato, quindi l'indirizzo corrente è letto; chiamato di nuovo.
  • L'indirizzo dello slave specificato è 1101000, il flag di lettura-scrittura è 0 e l'operazione di scrittura viene eseguita dopo che lo slave ha risposto, viene scritto un altro byte (il secondo byte) per specificare l'indirizzo 0x19 nello slave puntatore dell'indirizzo, vale a dire, dopo che lo slave ha ricevuto i dati, il suo puntatore del registro punta alla posizione 0x19.
  • Sr è una condizione di avvio ripetuta, che equivale ad avviare una nuova temporizzazione. Poiché il flag di lettura e scrittura specificato può seguire solo il primo byte della condizione di avvio, quindi se si desidera invertire la direzione di lettura e scrittura, è possibile avere solo. un'altra condizione iniziale.
  • Quindi, dopo la condizione di avvio, reindirizzare e specificare il bit del flag di lettura-scrittura. A questo punto, il bit del flag di lettura-scrittura è 1, indicando che deve essere letto. Quindi l'host riceve un byte, che rappresenta i dati 0xAA all'indirizzo 0x19.

2. MPU6050

2.1 Introduzione all'MPU6050

  • MPU6050 è un sensore di assetto a 6 assi in grado di misurare i parametri di accelerazione e velocità angolare degli assi X, Y e Z del chip. Attraverso la fusione dei dati, è possibile ottenere ulteriormente l'angolo di assetto (angolo di Eulero). bilanciare veicoli, aerei, ecc. che devono rilevare la scena del gesto
  • Accelerometro a 3 assi (Accelerometro): misura l'accelerazione degli assi X, Y e Z
  • Sensore giroscopico a 3 assi (giroscopio): misura la velocità angolare degli assi X, Y e Z

immagine.png

  • Prendendo come esempio la fusoliera dell'aereo, l'angolo di Eulero è l'angolo tra la fusoliera dell'aereo e i tre assi iniziali.
    • aereoIl muso dell'aereo si inclina verso il basso o verso l'alto, si chiama l'angolo compreso tra questo assePece
    • aereoLa fusoliera rotola a sinistra o a destra, si chiama l'angolo compreso tra questo asseRotolo
    • aereoMantenere la fusoliera a livelloGirare il muso dell'aereo a sinistra o a destra, si chiama l'angolo compreso tra questo asseImbardata
    • L'angolo di Eulero rappresenta l'assetto dell'aereo in questo momento, sia che sia inclinato verso l'alto o verso il basso, inclinato a sinistra o a destra.
  • Gli algoritmi comuni di fusione dei dati includono generalmente il filtraggio complementare, il filtraggio di Kalman, ecc. e il calcolo dell'assetto nella navigazione inerziale.
  • Accelerometro : La linea tratteggiata al centro è l'asse di induzione. Al centro c'è un piccolo cursore con una certa massa che può scorrere a destra e a sinistra. Contro di esso c'è una molla. Quando il cursore si muove, farà muovere il potenziometro su di esso. Questo potenziometro è un resistore divisore di tensione. Misurando la tensione in uscita dal potenziometro, è possibile ottenere il valore di accelerazione del piccolo cursore. Questo accelerometro è in realtà un dinamometro a molla. Secondo la seconda legge di Newton, F = ma, se vuoi misurare l'accelerazione a, puoi trovare un oggetto con massa unitaria e misurare la forza F. Questo è tutto. C'è un accelerometro su ciascuno degli assi X, Y e Z. Gli accelerometri hanno stabilità statica ma non stabilità dinamica.
  • Sensore giroscopico : Al centro c'è una ruota rotante con una certa massa. Quando la ruota rotante gira ad alta velocità, secondo il principio di conservazione del momento angolare, la ruota rotante ha la tendenza a mantenere il suo momento angolare originale direzione dell'asse di rotazione invariata. Quando la direzione dell'oggetto esterno ruota, la direzione dell'asse di rotazione interna non ruoterà, il che produrrà una deviazione angolare in corrispondenza della connessione dell'anello di bilanciamento. Se si inserisce un potenziometro rotante sul collegamento e si misura la tensione del potenziometro, è possibile ottenere l'angolo di rotazione. Il giroscopio dovrebbe essere in grado di ottenere direttamente l'angolo, ma il giroscopio di questo MPU6050 non può misurare direttamente l'angolo. Misura la velocità angolare, ovvero la velocità angolare del chip che ruota attorno all'asse X, all'asse Y e all'asse Z. -asse. L'integrale della velocità angolare è l'angolo. Tuttavia, quando l'oggetto è fermo, il valore della velocità angolare non può essere completamente riportato a zero a causa del rumore. Quindi, dopo l'accumulo continuo di integrali, questo piccolo rumore causerà una lenta deriva dell'angolo calcolato. che è l'angolo ottenuto integrando la velocità angolare. Non può resistere alla prova del tempo, ma questo angolo non è un problema se è fermo o in movimento e non sarà influenzato dal movimento dell'oggetto. I giroscopi hanno stabilità dinamica, non stabilità statica.
  • Secondo l'accelerometro, che ha stabilità statica ma non ha stabilità dinamica; il giroscopio ha stabilità dinamica ma non ha stabilità statica. Queste due caratteristiche possono quindi imparare dai reciproci punti di forza e integrarsi a vicenda eseguendo filtri complementari , possiamo integrare sia la stabilità statica che quella dinamica. La postura è scomoda.

2.2 Parametri dell'MPU6050

  • L'ADC a 16 bit raccoglie il segnale analogico del sensore, intervallo di quantizzazione: -32768~32767
  • Selezione fondo scala accelerometro: ±2, ±4, ±8, ±16 (g) (1g = 9,8 m/s2)
  • Selezione fondo scala giroscopio: ±250, ±500, ±1000, ±2000 (°/sec, gradi/secondo, unità di velocità angolare, quanti gradi di rotazione al secondo) (maggiore è la selezione fondo scala, più ampia è la intervallo di misurazione. Quanto più piccolo è il campo di fondo scala, tanto maggiore sarà la risoluzione della misurazione).
  • Filtro passa-basso digitale configurabile: è possibile configurare un registro per selezionare il filtraggio passa-basso dei dati di uscita.
  • Sorgente orologio configurabile
  • Divisione della frequenza di campionamento configurabile: la sorgente del clock può essere divisa dal divisore di frequenza per fornire clock per la conversione AD e altri circuiti interni. Controllando il coefficiente di divisione della frequenza, è possibile controllare la velocità della conversione AD.
  • Indirizzo slave I2C: 1101000 (AD0=0) o 1101001 (AD0=1)
    • 110 1000 viene convertito in esadecimale, ovvero 0x68, quindi alcuni dicono che l'indirizzo slave di MPU6050 è 0x68. Ma nella comunicazione I2C, i 7 bit alti del primo byte sono l'indirizzo dello slave e il bit più basso è il bit di lettura e scrittura. Pertanto, se si pensa che 0x68 sia l'indirizzo dello slave, quando si invia il primo byte, è necessario prima modificarlo 0x68 Sposta a sinistra di 1 bit (0x68 << 1), quindi leggi e scrivi bit bit a bit o in alto, leggi 1 e scrivi 0.
    • Un altro metodo consiste nello spostare i dati di 0x68 a sinistra di 1 bit (0x68 << 1) come indirizzo slave, che è 0xD0. In questo caso, l'indirizzo slave di MPU6050 è 0xD0. A questo punto, quando si invia effettivamente il primo byte, se si desidera scrivere, utilizzare semplicemente 0xD0 come primo byte; se si desidera leggere, utilizzare 0xD0 o 0x01 (0xD0 | 0x01), ovvero 0xD1 come primo byte . Questa rappresentazione non richiede un'operazione di spostamento a sinistra o, in altre parole, questa rappresentazione integra i bit di lettura e scrittura nell'indirizzo dello slave. 0xD0 è l'indirizzo di scrittura e 0xD1 è l'indirizzo di lettura.

2.3 Circuito hardware

immagine.png

spilloFunzione
VCC, GNDAlimentazione elettrica
SCL, SDAPin di comunicazione I2C
XCL, XDAPin di comunicazione Host I2C
Annuncio0Il bit più basso dell'indirizzo dello slave
INTERNOUscita del segnale di interruzione
  • LDO: regolatore di tensione lineare a bassa caduta, regolatore di tensione 3,3 V.
  • SCL e SDA: sono pin di comunicazione I2C Il modulo ha due resistori pull-up integrati da 4,7K, quindi durante il cablaggio è sufficiente collegare SDA e SCL direttamente alla porta GPIO. Non è necessario collegare resistori pull-up esterni .
  • XCL, XDA: pin di comunicazione Host I2C Questi due pin sono progettati per espandere le funzioni del chip. Solitamente utilizzato per magnetometri o barometri esterni. Quando questi chip di espansione sono collegati, l'interfaccia host di MPU6050 può accedere direttamente ai dati di questi chip di espansione e leggere i dati di questi chip di espansione in MPU6050 dotato di un'unità DMP calcolo dell'atteggiamento.
    Pin AD0: è il bit più basso dell'indirizzo dello slave se è collegato a un livello basso, l'indirizzo dello slave a 7 bit è 1101000; se è collegato a un livello alto, l'indirizzo dello slave a 7 bit è 1101001; C'è un resistore nello schema elettrico, che per impostazione predefinita è debolmente abbassato al livello basso, quindi se il pin viene lasciato flottante, è a livello basso. Se vuoi collegarlo al livello alto, puoi collegare direttamente AD0 a VCC e tirarlo su con forza fino al livello alto.
  • INT: pin di uscita di interruzione È possibile configurare alcuni eventi all'interno del chip per attivare l'uscita del pin di interruzione, come dati pronti, errore host I2C, ecc.
  • Il chip dispone inoltre di: rilevamento caduta libera, rilevamento movimento, rilevamento movimento zero, ecc. Questi segnali possono attivare il pin INT per generare una transizione di livello e, se necessario, è possibile configurare i segnali di interruzione.
  • L'alimentazione del chip MPU6050 è 2,375-3,46 V, che è un dispositivo di alimentazione da 3,3 V e non può essere collegato direttamente a 5 V. Pertanto, viene aggiunto un regolatore di tensione da 3,3 V e la tensione del terminale di ingresso VCC_5V può essere compresa tra 3,3 V e 5 V. Quindi il regolatore di tensione da 3,3 V emette una tensione stabile da 3,3 V per alimentare il chip finché il terminale da 3,3 V è alimentato , La spia di alimentazione si accenderà.

2.4 Schema a blocchi dell'MPU6050

immagine.png

  • CLKIN e CLKOUT sono pin di ingresso e uscita del clock, ma generalmente utilizziamo il clock interno.
  • La parte grigia: è il sensore all'interno del chip, l'accelerometro sull'asse XYZ e il giroscopio sull'asse XYZ.
  • C'è anche un sensore di temperatura integrato che può essere utilizzato per misurare la temperatura.
  • Questi sensori sono essenzialmente equivalenti a resistori variabili. Dopo aver diviso la tensione, emettono una tensione analogica e quindi eseguono la conversione da analogico a digitale attraverso l'ADC. Una volta completata la conversione, i dati provenienti da questi sensori vengono inseriti in modo uniforme nei dati registro, che può essere ottenuto leggendo il registro dati. Il valore misurato dal sensore. Tutte le conversioni all'interno di questo chip sono completamente automatizzate.
  • Ogni sensore ha un'unità di autotest, che viene utilizzata per verificare la qualità del chip. Quando viene avviato l'autotest, il chip simulerà una forza esterna esercitata sul sensore. Questa forza esterna farà sì che i dati del sensore vengano rilevati più grande del solito. Processo di autotest: è possibile abilitare prima l'autotest, leggere i dati, quindi abilitare l'autotest, leggere i dati, sottrarre i due dati e i dati risultanti vengono chiamati risposta all'autotest. Per questa risposta di autotest, il manuale fornisce un intervallo. Se rientra in questo intervallo, significa che non ci sono problemi con il chip.
  • Pompa di carica: è una pompa di carica o pompa di carica. La pompa di carica è un circuito di boost.
  • Il pin CPOUT richiede un condensatore esterno.
  • Registro dello stato di interruzione: può controllare quali eventi interni vengono inviati al pin di interruzione,
  • FIFO: registro first-in-first-out, che può memorizzare nella cache il flusso di dati.
  • Registro di configurazione: è possibile configurare vari circuiti interni
  • Registro sensori: registro dati, che memorizza i dati di ciascun sensore.
  • Calibrato in fabbrica: ciò significa che i sensori interni sono calibrati.
  • Processore di movimento digitale: DMP in breve, è un algoritmo hardware per il calcolo dell'assetto integrato nel chip. Può essere utilizzato per il calcolo dell'assetto con la libreria DMP ufficiale.
  • FSYNC: sincronizzazione dei frame.

3. 10-1 Software I2C di lettura e scrittura MPU6050

3.1 Collegamento hardware

Attraverso la comunicazione I2C del software, leggere e scrivere i registri all'interno del chip MPU6050. Scrivendo nel registro di configurazione, è possibile configurare il modulo plug-in. Leggendo il registro dati, è possibile ottenere i dati del modulo plug-in verranno visualizzati i dati letti. Sull'OLED, i dati in alto sono il numero ID del dispositivo. Il numero ID di questo MPU6050 è fisso su 0x68. Sotto, i tre a sinistra sono i dati di output del sensore di accelerazione, che sono rispettivamente l'accelerazione dell'asse X, dell'asse Y e dell'asse Z. I tre a destra sono i dati di output del sensore giroscopico. che sono la velocità angolare dell'asse X, dell'asse Y e dell'asse Z.
SCL è collegato al pin PB10 di STM32 e SDA è collegato al pin PB11. Poiché qui è implementato il ribaltamento a livello software, è possibile collegare due porte GPIO a piacimento.

3.2 Risultati dell'operazione

IMG_20240406_155156.jpg

3.3 Flusso del codice

STM32 è l'host e MPU6050 è lo slave, che è una modalità master-slave.

  1. Stabilire i moduli .c e .h del livello di comunicazione I2C
    1. Scrivi l'inizializzazione GPIO sottostante di I2C
    2. 6 unità di temporizzazione di base: inizio, fine, invia un byte, ricevi un byte, invia risposta, ricevi risposta
  2. Creare i moduli .c e .h di MPU6050
    1. Basato sul modulo di comunicazione I2C, implementa la lettura all'indirizzo specificato, la scrittura all'indirizzo specificato, la scrittura dei registri per configurare il chip e la lettura dei registri per ottenere i dati del sensore.
  3. principale.c
    1. Richiama il modulo MPU6050, inizializza, ottieni i dati e visualizza i dati

3.4 Codice

  1. Codice 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. Codice 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. Periferiche I2C

4.1 Introduzione alle periferiche I2C

  • STM32 integra un circuito ricetrasmettitore hardware I2C, che può eseguire automaticamente funzioni quali generazione di clock, generazione di condizioni di inizio e fine, trasmissione e ricezione di bit di risposta e trasmissione e ricezione di dati da parte dell'hardware, riducendo il carico sulla CPU.
  • Supporta il modello multi-host
  • Supporta la modalità indirizzo a 7 bit/10 bit
  • Supporta diverse velocità di comunicazione, velocità standard (fino a 100 kHz), veloce (fino a 400 kHz)
  • Supporta DMA
  • Compatibile con il protocollo SMBus
  • Risorse hardware I2C STM32F103C8T6: I2C1, I2C2

4.2 Schema a blocchi I2C

immagine.png

  • A sinistra ci sono i pin di comunicazione: SDA e SCL sono utilizzati da SMBus;
    I pin derivati ​​dalle periferiche generiche sono solitamente collegati al mondo esterno attraverso la modalità multiplexing della porta GPIO (vedi tabella)
  • Quanto sopra è la parte di controllo dei dati: SDA La parte centrale della trasmissione e della ricezione dei dati è il registro dati DR (DATA REGISTER) e il registro a scorrimento dei dati. Quando i dati devono essere inviati, un byte di dati può essere scritto nel registro dati DR. Quando il registro a scorrimento non ha dati da spostare, il valore del registro dati verrà ulteriormente trasferito al registro a scorrimento. Durante il processo di spostamento, i dati successivi possono essere inseriti direttamente nel registro dati e attesi. Una volta completato lo spostamento dei dati precedente, i dati successivi possono essere collegati senza problemi e continuare a essere inviati. Quando i dati vengono trasferiti dal registro dati al registro a scorrimento, il bit TXE del registro di stato viene impostato a 1, indicando che il registro di trasmissione è vuoto.
  • Ricezione: i dati di ingresso vengono spostati dal pin al registro a scorrimento bit per bit. Quando viene raccolto un byte di dati, i dati vengono trasferiti dal registro a scorrimento al registro dati nel suo complesso e il flag RXNE viene impostato su. contemporaneamente, indicando la ricezione. Il registro non è vuoto, i dati possono essere letti dal registro dati. Per quanto riguarda quando ricevere e quando inviare, è necessario scrivere i bit corrispondenti nel registro di controllo per il funzionamento. Le condizioni di avvio, le condizioni di terminazione, i bit di risposta, ecc. vengono completati tramite il controllo dei dati.
  • Il comparatore e il registro degli indirizzi vengono utilizzati in modalità slave.
  • SCL: il controllo dell'orologio viene utilizzato per controllare la linea SCL. Scrivi il bit corrispondente nel registro di controllo dell'orologio e il circuito eseguirà la funzione corrispondente. Circuito logico di controllo, la scrittura nel registro di controllo può controllare l'intero circuito. Lo stato di funzionamento del circuito può essere conosciuto leggendo il registro di stato.
  • Quando si inviano e ricevono molti byte, è possibile utilizzare DMA per migliorare l'efficienza.

4.3 Struttura base I2C

immagine.png

  • SDA: poiché I2C è il primo di ordine superiore, questo registro a scorrimento si sposta a sinistra. Durante l'invio viene spostato prima il bit alto, poi il secondo bit alto. Un clock SCL viene spostato una volta e poi spostato 8 volte e 8 byte possono essere posizionati sulla linea SDA dal bit alto al bit basso. Durante la ricezione, i dati vengono spostati da destra attraverso la porta GPIO e infine viene ricevuto un byte per 8 volte. I dati di output vengono inviati alla porta tramite la porta GPIO. I dati di input vengono immessi nel registro a scorrimento tramite la porta GPIO.
  • La porta GPIO deve essere configurata per la modalità di uscita multiplex a drain aperto. Multiplexing significa che lo stato della porta GPIO è controllato da periferiche su chip e l'uscita a drain aperto è la configurazione della porta richiesta dal protocollo I2C. Anche in modalità di output open-drain, è possibile inserire la porta GPIO.
  • SCL: il controller dell'orologio controlla la linea dell'orologio tramite GPIO.
    immagine.png

4.4 L'host invia

immagine.png
Quando STM32 desidera scrivere a un indirizzo specificato, deve seguire il diagramma della sequenza di trasmissione del trasmettitore.

  • Indirizzo a 7 bit: il byte successivo all'indirizzamento della condizione di avvio
  • Indirizzo a 10 bit: i due byte dopo la condizione di avvio vengono indirizzati. Il primo byte è l'intestazione del frame e il contenuto è il bit flag a 5 bit 11110 + indirizzo a 2 bit + 1 bit di lettura-scrittura è il byte successivo Indirizzo puro a 8 bit.
  • Processo a 7 bit: avvio, indirizzo slave, risposta, dati, risposta, dati, risposta... Stop
  1. Dopo l'inizializzazione, il bus passa allo stato inattivo e STM passa alla modalità slave. Per generare una condizione di avvio, STM32 deve scrivere nel registro di controllo (CR1), scrivere 1, quindi STM32 passa dalla modalità slave alla modalità master. .

immagine.png

  1. L'evento EV5 può essere considerato un bit di flag. SB è un bit del registro di stato, indica lo stato dell'hardware. SB=1 significa che la condizione di avvio è stata inviata.

immagine.png

  1. Quindi è possibile inviare un byte dell'indirizzo dello slave. L'indirizzo dello slave deve essere scritto nel registro dati DR. Dopo aver scritto nel DR, il circuito hardware trasferirà automaticamente il byte dell'indirizzo nel registro a scorrimento, quindi trasferirà la parola Il nodo è inviato al bus I2C, quindi l'hardware riceverà automaticamente la risposta e giudicherà se non c'è risposta, l'hardware imposterà il flag di errore di risposta, quindi il flag potrà richiedere un'interruzione per ricordarcelo.
  2. Una volta completato l'indirizzamento, si verificherà l'evento EV6 e il bit del flag ADDR sarà 1. Questo bit del flag indica la fine della trasmissione dell'indirizzo in modalità master.

immagine.png

  1. L'evento EV8_1 significa che il flag TxE è 1, il registro a scorrimento è vuoto e il registro dati è vuoto. Dobbiamo scrivere nel registro dati DR per inviare i dati. Dopo aver scritto a DR, poiché il registro a scorrimento è vuoto, DR passerà immediatamente al registro a scorrimento per inviare. Si verificherà l'evento EV8. Il registro a scorrimento non è vuoto e il registro dati è vuoto, il che significa che il registro a scorrimento sta inviando dati. Pertanto, nel processo viene generata la temporizzazione dei dati 1. In questo momento, i dati 2 verranno scritti nel registro dati e saranno in attesa. Dopo aver ricevuto il bit di risposta, i bit di dati verranno trasferiti al registro a scorrimento per la trasmissione. Lo stato in questo momento è che il registro a scorrimento non è vuoto e il registro dei dati è vuoto, quindi in questo momento l'incidente dell'EV8 si è verificato di nuovo.
  2. Quindi viene inviato il dato 2, ma questa volta il dato successivo è stato scritto nel registro dati ed è in attesa. Una volta rilevato l'evento EV8, è possibile scrivere i dati successivi.
  3. Dopo che i dati che si desidera inviare sono stati scritti, non vengono scritti nuovi dati nel registro dati. Quando lo spostamento dei dati corrente nel registro a scorrimento è completato, il registro a scorrimento è vuoto e anche il registro dati è vuoto, ovvero. Evento EV8_2, TxE=1 significa che il registro a scorrimento è vuoto, il registro dati è vuoto, BTF: flag di fine trasmissione byte, durante la trasmissione, quando verranno inviati nuovi dati e il registro dati non è stato scritto con nuovi dati. Quando viene rilevato EV8_2, è possibile generare la condizione di terminazione Stop. Per generare una condizione di terminazione, ovviamente, dovrebbero esserci dei bit corrispondenti nel registro di controllo che possono essere controllati. In questo modo la sequenza di invio è terminata.

4.5 Accoglienza dell'ospite

immagine.png
Ricezione master a 7 bit: avvio, indirizzo slave + lettura, ricezione risposta, ricezione dati, invio risposta... ricezione dati, mancata risposta, terminazione

  1. Innanzitutto, scrivere il bit Start del registro di controllo per generare una condizione di avvio, quindi attendere l'evento EV5 (che indica che la condizione di avvio è stata inviata).
  2. Dopo l'indirizzamento viene ricevuta la risposta e al termine viene generato un evento EV6 (che indica che l'indirizzamento è completato).
  3. Dati 1 significa che i dati vengono immessi tramite il registro a scorrimento.
  4. EV6_1 indica che i dati sono ancora in fase di spostamento Dopo aver ricevuto la risposta, significa che il registro a scorrimento è stato spostato con successo in un byte di dati 1. In questo momento, il byte spostato viene trasferito nel registro dati nel suo insieme e. contemporaneamente viene impostato il flag RxNE, che indica che il registro dati non è vuoto, ovvero è stato ricevuto un byte di dati, lo stato è un evento EV7, RxNE=1 che i dati sono stati ricevuti Dopo aver letto i dati, l'evento non c'è più.
  5. Naturalmente, quando i dati 1 non sono stati letti, i dati 2 possono essere spostati direttamente nel registro a scorrimento. Successivamente, lo spostamento dei dati 2 viene completato, i dati 2 vengono ricevuti, viene generato un evento EV7, i dati 2 vengono letti e. l'evento EV7 è scomparso.
  6. Quando non è più necessaria alcuna ricezione, il registro di controllo del bit di risposta ACK deve essere impostato a 0 in anticipo quando si verifica l'ultima unità di temporizzazione e viene impostata la richiesta della condizione di terminazione, ovvero l'evento EV7_1 Successivamente, una mancata risposta NA verrà fornito Poiché il bit STOP è impostato, viene generata una condizione di terminazione.

4.6 Confronto delle forme d'onda software/hardware

immagine.png

immagine.png

5. 10-2 Hardware I2C lettura e scrittura MPU6050

5.1 Funzioni della libreria 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 Hardware I2C lettura e scrittura Implementazione MPU6050

5.2.1 Collegamento hardware

SCL è collegato al pin PB10 di STM32 e SDA è collegato al pin PB11. Poiché qui è implementato il ribaltamento a livello software, è possibile collegare due porte GPIO a piacimento.
I dati principali di OLED sono il numero ID del dispositivo. Il numero ID di questo MPU6050 è fissato su 0x68. Sotto, i tre a sinistra sono i dati di output del sensore di accelerazione, che sono rispettivamente l'accelerazione dell'asse X, dell'asse Y e dell'asse Z. I tre a destra sono i dati di output del sensore giroscopico. che sono la velocità angolare dell'asse X, dell'asse Y e dell'asse Z.

5.2.2 Risultati dell'operazione

IMG_20240406_172128.jpg

5.2.3 Processo di implementazione del codice

  1. Configura le periferiche I2C, inizializza le periferiche I2C, sostituisci MyI2C_Init
    (1) Accendere l'orologio della periferica I2C e la porta GPIO corrispondente,
    (2) Inizializzare la porta GPIO corrispondente alla periferica I2C in modalità multiplexed open-drain
    (3) Utilizzare la struttura per configurare l'intero I2C
    (4) I2C_Cmd, abilita I2C
  2. Controlla i circuiti periferici, realizza i tempi di scrittura sugli indirizzi specificati e sostituisce WriteReg
  3. Controllare il circuito periferico per realizzare i tempi di lettura dell'indirizzo specificato e sostituire ReadReg

5.2.4 Codice

  1. Codice 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. principale.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