Berbagi teknologi

Bahasa Indonesia: STM32-I2C

2024-07-12

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

Konten ini didasarkan padaTeknologi Jiangxie STM32Dikompilasi setelah studi video.

1. Komunikasi I2C

1.1 Pengantar komunikasi I2C

  • I2C (Inter IC Bus) adalah bus data universal yang dikembangkan oleh Philips
  • Dua jalur komunikasi: jalur jam serial SCL (Serial Clock), jalur data serial SDA (Serial Data).
  • Sinkron, setengah dupleks, ujung tunggal, multi-perangkat
  • Balas dengan data
  • Mendukung pemasangan beberapa perangkat di bus (satu master dan beberapa budak, banyak master dan banyak budak)
    • Satu master, banyak budak: Mikrokontroler berfungsi sebagai host dan mendominasi pengoperasian bus I2C. Semua modul eksternal yang dipasang pada bus I2C adalah budak hanya dapat mengontrol bus I2C setelah diberi nama oleh host dan tidak dapat menyentuhnya tanpa izin. bus I2C untuk mencegah konflik.
    • Multi-master dan multi-slave: Modul apa pun di bus dapat secara aktif melompat keluar dan bertindak sebagai master. Ketika terjadi konflik bus, protokol I2C akan melakukan arbitrase. Pihak yang memenangkan arbitrase memperoleh kendali atas bus, dan pihak yang kalah otomatis menjadi budak.

gambar.png

1.2 Rangkaian perangkat keras

  • SCL dari semua perangkat I2C terhubung bersama, dan SDA terhubung bersama.
  • SCL dan SDA perangkat harus dikonfigurasi dalam mode keluaran saluran terbuka.
  • Tambahkan resistor pull-up ke masing-masing SCL dan SDA, nilai resistansi umumnya sekitar 4,7KΩ

Gambar 1Gambar 2
gambar.png

  • Satu master dan beberapa budak: CPU adalah mikrokomputer chip tunggal. Sebagai master bus, ia mencakup kontrol penuh atas jalur SCL setiap saat. Selain itu, dalam keadaan idle, host dapat secara aktif memulai kendali SDA. Hanya ketika budak mengirimkan data dan budak merespons, tuan rumah akan mentransfer kendali SDA ke budak.
  • IC yang dikontrol adalah budak yang dipasang pada bus I2C, yang dapat berupa sensor sikap, OLED, memori, modul jam, dll. Kekuatan budak relatif kecil. Untuk jalur jam SCL, ia hanya dapat membaca secara pasif setiap saat. Untuk jalur data SDA, budak tidak diperbolehkan untuk secara aktif memulai kendali SDA. Hanya setelah master mengirimkan perintah untuk membaca budak, atau ketika budak merespons, budak dapat memperoleh kendali sebentar atas SDA.
  • Gambar 2: SCL di sebelah kiri dan SDA di sebelah kanan. Semua data dapat dimasukkan melalui buffer data atau pemicu Schmitt.
    • Karena masukan tidak berpengaruh pada rangkaian, perangkat apa pun dapat memperoleh masukan kapan saja.
    • Outputnya menggunakan konfigurasi output saluran terbuka. Ketika outputnya rendah, sakelar menyala dan pin terhubung langsung ke ground, yang merupakan pull-down yang kuat; ketika outputnya tinggi, sakelar mati dan pin tidak terhubung ke apa pun dan dalam keadaan mengambang, sehingga semua perangkat hanya dapat mengeluarkan level rendah tetapi tidak level tinggi. Untuk menghindari mengambang yang disebabkan oleh level tinggi, SCL dan SDA perlu memiliki resistor pull-up eksternal di luar bus, melalui resistor. Ditarik ke level tinggi, sehingga merupakan pull-up yang lemah. Dengan cara ini, pertama, ini sepenuhnya menghilangkan fenomena korsleting daya dan memastikan keamanan sirkuit; kedua, menghindari seringnya pergantian mode pin. Dalam mode saluran terbuka, mengeluarkan level tinggi sama dengan melepaskan pin, sehingga Anda dapat langsung mengeluarkan level tinggi sebelum memasukkan. Ketiga, mode ini memiliki fenomena "kabel DAN". Selama satu atau lebih perangkat mengeluarkan tingkat rendah, bus berada pada tingkat rendah. Hanya ketika semua perangkat mengeluarkan tingkat tinggi, bus berada pada tingkat tinggi. . Oleh karena itu, I2C dapat memanfaatkan fenomena ini untuk melakukan sinkronisasi jam dan arbitrase bus dalam mode multi-master. Jadi walaupun SCL disini bisa menggunakan output push-pull dalam satu mode master dan beberapa slave, namun tetap menggunakan mode output open-drain plus pull-out.

1.3 Unit dasar pengaturan waktu I2C

1.3.1 Kondisi awal dan kondisi akhir

  • kondisi awal: Selama SCL level tinggi, SDA beralih dari level tinggi ke level rendah
  • Kondisi penghentian: Selama SCL level tinggi, SDA beralih dari level rendah ke level tinggi

gambar.png

  • dalam kondisi awal : Ketika bus I2C dalam keadaan idle, SCL dan SDA berada dalam keadaan tingkat tinggi, yaitu, tidak ada perangkat yang menyentuh SCL dan SDA. SCL dan SDA ditarik ke tingkat tinggi oleh resistor pull-up eksternal, dan bus tersebut dalam keadaan tenang. Ketika host perlu mengirim dan menerima data, pertama-tama ia harus memecah keheningan bus dan menghasilkan kondisi awal, yaitu SCL berada pada level tinggi tanpa menyentuhnya, dan kemudian menarik SDA ke bawah untuk menghasilkan tepi jatuh. Ketika slave menangkap sinyal SCL tingkat tinggi dan sinyal tepi jatuh SDA, ia akan mengatur ulang sendiri dan menunggu panggilan master. Setelah tepi SDA jatuh, tuan rumah perlu menurunkan SCL lagi di satu sisi, menempati bus, dan di sisi lain, juga untuk memfasilitasi penyambungan unit dasar. Nanti akan dipastikan bahwa, kecuali kondisi start dan stop, SCL setiap unit sekuensial dimulai dengan level rendah dan diakhiri dengan level rendah.
  • Dalam keadaan kondisi terminasi : SCL melepaskan terlebih dahulu dan memantul ke level tinggi, lalu SDA melepaskan dan memantul ke level tinggi, menghasilkan tepian naik, yang memicu kondisi terminasi. Setelah kondisi penghentian simultan, SCL dan SDA menjadi tinggi dan kembali ke keadaan tenang awal.
    Mulai dan berhenti dihasilkan oleh host, dan budak tidak diperbolehkan untuk menghasilkan mulai dan berhenti. Oleh karena itu, ketika bus dalam keadaan idle, budak harus selalu melepaskan tangannya dan tidak diperbolehkan melompat keluar dan menyentuh bus.

1.3.2 Kirim satu byte

  • Kirim satu byte: Selama SCL level rendah, host menempatkan bit data pada jalur SDA secara berurutan (bit tinggi terlebih dahulu), dan kemudian melepaskan SCL. Budak akan membaca bit data selama SCL level tinggi, jadi SDA tidak diperbolehkan untuk memiliki data apa pun selama SCL tingkat tinggi. Ketika data berubah, putar proses di atas 8 kali untuk mengirim satu byte.

Host tingkat rendah memasukkan data, dan budak tingkat tinggi membaca data.
gambar.png
Setelah kondisi awal, byte pertama juga harus dikirim oleh host. Ketika SCL rendah, jika host ingin mengirim 0, ia menarik SDA rendah; jika ingin mengirim 1, ia melepaskannya, dan SDA memantul ke tingkat tinggi. Selama SCL level rendah, level SDA dibiarkan berubah. Setelah data ditempatkan, host melepaskan garis jam dan SCL memantul ke level tinggi. Pada saat level tinggi, itu adalah waktu ketika budak membaca SDA, jadi pada saat level tinggi, SDA tidak boleh diubah. Setelah SCL berada pada level tinggi, budak perlu membaca SDA secepat mungkin. Umumnya, budak telah menyelesaikan pembacaan pada tepi naik SCL. Karena clock dikendalikan oleh master, maka slave tidak mengetahui kapan tepi jatuh terjadi, sehingga budak akan membaca data pada tepi naik SCL. Setelah host melepaskan SCL untuk jangka waktu tertentu, ia dapat terus menarik SCL ke tingkat rendah dan mengirimkan bit berikutnya. Tuan rumah juga perlu memasukkan data ke SDA sesegera mungkin setelah SCL mengalami penurunan. Namun host memiliki kendali atas jam, sehingga hanya perlu memasukkan data ke SDA kapan saja ketika level rendahnya rendah. Setelah data dilepaskan, host melepaskan SCL lagi, SCL tinggi, dan budak membaca bit ini. Ulangi proses ini: host menarik SCL ke tingkat rendah, memasukkan data ke SDA, host melepaskan SCL, dan budak membaca data SDA. Di bawah sinkronisasi SCL, master mentransmisikan dan slave menerima secara berurutan. Setelah 8 siklus, data 8-bit, yaitu satu byte, dikirim.
Karena ini adalah bit tingkat tinggi pertama, bit pertama adalah bit B7 tertinggi dalam satu byte, dan bit terendah B0 dikirim terakhir.

1.3.3 Menerima satu byte

  • menerima satu byte: Selama SCL tingkat rendah, budak menempatkan bit data pada jalur SDA secara berurutan (bit tinggi terlebih dahulu), dan kemudian melepaskan SCL. Host akan membaca bit data selama SCL tingkat tinggi, jadi SDA tidak diperbolehkan untuk memiliki data apa pun selama SCL tingkat tinggi. Saat data berubah, putar proses di atas 8 kali untuk menerima satu byte (host harus melepaskan SDA sebelum menerima)

Budak tingkat rendah menempatkan data, host tingkat tinggi membaca data
gambar.png
Jalur SDA: Master harus melepaskan SDA sebelum menerima. Pada saat ini, budak mendapatkan kendali atas SDA. Jika budak perlu mengirim 0, ia menarik SDA rendah. Jika budak perlu mengirim 1, ia melepaskannya, dan SDA rebound ke level tinggi. Tingkat rendah mengonversi data, tingkat tinggi membaca data. Garis padat mewakili level yang dikontrol oleh master, dan garis putus-putus mewakili level yang dikontrol oleh slave. SCL dikendalikan oleh host selama seluruh proses, dan host SDA harus dilepaskan sebelum menerima dan diserahkan kepada budak untuk dikontrol. Karena jam SCL dikendalikan oleh host, konversi data budak pada dasarnya dilakukan pada tepi jatuh SCL, dan host dapat membaca kapan saja ketika SCL tinggi.

1.3.4 Mengirim tanggapan dan menerima tanggapan

  • Kirim balasan: Setelah menerima satu byte, host mengirimkan satu bit data pada jam berikutnya. Data 0 menunjukkan respons, dan data 1 menunjukkan non-respons.
  • menerima balasan: Setelah host mengirimkan satu byte, ia menerima sedikit data pada jam berikutnya untuk menentukan apakah budak merespons. Data 0 menunjukkan respons, data 1 menunjukkan non-respons (host perlu melepaskan SDA sebelum menerima)

gambar.png
Artinya, setelah memanggil waktu pengiriman sebuah byte, harus diikuti dengan waktu memanggil respon penerimaan, yang digunakan untuk menentukan apakah budak telah menerima data yang baru saja diberikan kepadanya. Jika budak menerimanya, maka di bit respons, ketika master melepaskan SDA, budak harus segera menarik SDA ke bawah, dan kemudian selama SCL tingkat tinggi, host membaca bit respons. Jika bit responnya 0, berarti budak memang menerimanya.
Saat menerima byte, Anda perlu memanggil respons pengiriman. Tujuan mengirimkan respons adalah untuk memberi tahu budak apakah Anda ingin melanjutkan pengiriman. Jika mesin budak menerima respons dari master setelah mengirimkan sebagian data, mesin budak akan terus mengirim. Jika mesin budak tidak menerima respons dari mesin master, mesin budak akan mengira ada sepotong data telah dikirim, tetapi mesin master mengabaikan saya. Mungkin Tuan rumah tidak menginginkannya. Pada saat ini, budak akan dengan patuh melepaskan SDA dan menyerahkan kendali SDA untuk mencegah gangguan pada operasi tuan rumah selanjutnya.

1.4 Waktu I2C

1.4.1 Tentukan alamat yang akan ditulis

  • Tentukan alamat untuk menulis
  • Untuk perangkat yang ditentukan (Alamat Budak), tulis data yang ditentukan (Data) pada alamat yang ditentukan (Alamat Reg) (yaitu, alamat register perangkat yang ditentukan)

gambar.png
proses:
(1) Kondisi awal
(2) Waktu pengiriman satu byte—0xD0 (alamat slave (7bit) + tulis (1bit)-0) (1101 0000)
(3) Menerima respon : RA = 0 (menerima respon dari slave)
(4) Alamat yang ditentukan: 0x19 (0001 1001)
(5) Menerima respon : RA = 0 (menerima respon dari slave)
(6) Tulis data yang ditentukan: 0xAA (1010 1010)
(7) Menerima respon: RA = 0
(8) Hentikan bit P (kondisi terminasi)

  • Setelah kondisi awal, itu harus menjadi waktu untuk mengirim satu byte. Isi byte harus berupa alamat budak + bit baca dan tulis. Alamat budak adalah 7 bit dan bit baca dan tulis adalah 1 bit, tepatnya 8 bit. Mengirim alamat budak untuk menentukan objek komunikasi, dan mengirimkan bit baca dan tulis untuk mengonfirmasi apakah akan menulis atau membaca selanjutnya. Sekarang host mengirimkan sepotong data. Isi byte diubah menjadi heksadesimal. Bit tingkat tinggi didahulukan, yaitu 0xD0. Unit berikutnya adalah bit respons (RA) dari budak penerima. bit tulis berakhir dan SCL ditarik rendah. Setelah itu, host perlu melepaskan SDA, diikuti oleh bit pengakuan RA.
  • Tingkat tinggi setelah bit respons RA berakhir dihasilkan oleh budak yang melepaskan SDA. Budak menyerahkan kendali SDA dan kejatuhan SCL terjadi hampir bersamaan.
  • Setelah respons selesai, jika Anda terus mengirim satu byte, byte kedua dapat dikirim ke dalam perangkat yang ditunjuk. Perangkat budak dapat menentukan penggunaan byte kedua dan berikutnya. Secara umum, byte kedua dapat berupa alamat register atau kata kontrol instruksi, dll., dan byte ketiga adalah konten yang ingin ditulis oleh host ke alamat register (byte kedua).
  • P adalah bit berhenti.

Tujuan dari bingkai data ini adalah: untuk perangkat yang menentukan alamat budak 1101000, tulis data 0xAA di register internalnya di alamat 0x19.
0 berarti: host akan melakukan operasi penulisan pada waktu berikutnya;
1 berarti: tuan rumah akan melakukan operasi pembacaan pada urutan waktu berikutnya;

1.4.2 Pembacaan alamat saat ini

  • Alamat saat ini terbaca
  • Untuk perangkat yang ditentukan (Alamat Budak), baca data budak (Data) pada alamat yang ditunjukkan oleh penunjuk alamat saat ini.

gambar.png
proses:
(1) Kondisi awal
(2) Waktu pengiriman satu byte—0xD1 (alamat slave (7bit) + baca (1bit)-1) (1101 0001)
(3) Menerima respon : RA = 0 (menerima respon dari slave)
(4) Baca data budak: 0x0F (0000 1111)
(7) Kirim tanggapan: SA = 0
(8) Hentikan bit P (kondisi terminasi)

  • Bit baca dan tulis adalah 1, yang menunjukkan bahwa operasi baca berikutnya harus dilakukan. Setelah budak merespons (RA=0), arah transmisi data akan dibalik. Master ingin menyerahkan kendali SDA kepada budak, dan master memanggil waktu penerimaan satu byte untuk melakukan operasi penerimaan.
  • Pada byte kedua, budak mendapat izin dari master dan dapat menulis ke SCL selama pengambilan SCL tingkat rendah. Master membaca SDA selama pengambilan SCL tingkat tinggi 8 bit, satu byte data yang dikirim oleh slave diterima, yaitu 0x0F. Tapi register budak mana yang 0x0F? Dalam waktu baca, protokol I2C menetapkan bahwa ketika host mengalamatkan, setelah tanda baca dan tulis disetel ke 1. Byte berikutnya akan segera beralih ke waktu baca. Oleh karena itu, host akan mulai menerima sebelum sempat menentukan register mana yang ingin dibaca, jadi tidak ada link untuk menentukan alamat di sini. Di mesin budak, semua register dialokasikan ke area linier, dan akan ada variabel penunjuk terpisah yang menunjukkan salah satu register. Penunjuk ini secara default menyala, umumnya menunjuk ke alamat 0, dan setiap kali byte ditulis dan Setelah membaca satu byte, penunjuk akan otomatis bertambah satu kali dan berpindah ke posisi berikutnya. Kemudian saat memanggil waktu pembacaan alamat saat ini, jika host tidak menentukan alamat mana yang akan dibaca, budak akan kembali ke register yang ditunjuk oleh byte. nilai penunjuk saat ini.

1.4.3 Baca di alamat yang ditentukan

  • Tentukan alamat untuk membaca
  • Untuk perangkat yang ditentukan (Alamat Budak), di bawah alamat yang ditentukan (Alamat Reg), baca data budak (Data)

gambar.png
Mulai dulu, lalu ulangi mulai, lalu berhenti
proses:
(1) Kondisi awal
(2) Waktu pengiriman satu byte—0xD0 (alamat slave (7bit) + tulis (1bit)-0) (1101 0000)
(3) Menerima respon : RA = 0 (menerima respon dari slave)
(4) Alamat yang ditentukan: 0x19 (0001 1001)
(5) Menerima respon : RA = 0 (menerima respon dari slave)
(6) Ulangi kondisi awal
(7) Waktu pengiriman satu byte—0xD1 (alamat slave (7bit) + baca (1bit)-1) (1101 0001)
(8) Menerima respon: RA = 0
(9) Baca data budak: 0xAA (1010 1010)
(10) Kirim tanggapan: SA = 0
(11) Hentikan bit P (kondisi terminasi)

  • Bagian pertama adalah menulis ke alamat yang ditentukan, tetapi hanya alamat yang ditentukan, dan tidak ada waktu untuk menulis; bagian kedua adalah membaca alamat saat ini, karena alamat tersebut baru saja ditentukan, sehingga alamat saat ini dibaca dipanggil lagi.
  • Alamat budak yang ditentukan adalah 1101000, tanda baca-tulis adalah 0, dan operasi tulis dilakukan. Setelah budak merespons, byte lain (byte kedua) ditulis untuk menentukan alamat penunjuk alamat, artinya, setelah budak menerima data, penunjuk registernya menunjuk ke posisi 0x19.
  • Sr adalah kondisi awal yang berulang, yang setara dengan memulai waktu baru. Karena flag baca dan tulis yang ditentukan hanya dapat mengikuti byte pertama dari kondisi awal, jadi jika Anda ingin mengganti arah baca dan tulis, Anda hanya dapat memilikinya. kondisi awal yang lain.
  • Kemudian setelah kondisi awal, alamat ulang dan tentukan bit flag baca-tulis. Pada saat ini, bit flag baca-tulis adalah 1, yang menunjukkan bahwa bit tersebut akan dibaca 0xAA di alamat 0x19.

2. MPU6050

2.1 Pengantar MPU6050

  • MPU6050 adalah sensor sikap 6 sumbu yang dapat mengukur parameter akselerasi dan kecepatan sudut dari sumbu X, Y, dan Z milik chip. Melalui fusi data, sudut sikap (sudut Euler) dapat diperoleh lebih lanjut menyeimbangkan kendaraan, pesawat terbang, dll. yang perlu mendeteksi adegan isyarat itu sendiri
  • Akselerometer 3 sumbu (Accelerometer): mengukur percepatan sumbu X, Y, dan Z
  • Sensor gyro 3 sumbu (Giroskop): mengukur kecepatan sudut sumbu X, Y, dan Z

gambar.png

  • Mengambil contoh badan pesawat, sudut Euler adalah sudut antara badan pesawat dengan tiga sumbu awal.
    • pesawat terbangHidung pesawat miring ke bawah atau ke atas, sudut antara sumbu ini disebutMelempar
    • pesawat terbangBadan pesawat berguling ke kiri atau ke kanan, sudut antara sumbu ini disebutGulungan
    • pesawat terbangJaga ketinggian badan pesawatPutar hidung pesawat ke kiri atau ke kanan, sudut antara sumbu ini disebutMengoleng
    • Sudut Euler mewakili sikap pesawat saat ini, apakah miring ke atas atau ke bawah, miring ke kiri atau ke kanan.
  • Algoritme fusi data umum umumnya mencakup pemfilteran komplementer, pemfilteran Kalman, dll., dan penghitungan sikap dalam navigasi inersia.
  • Akselerometer : Garis putus-putus di tengah adalah sumbu induksi. Di tengahnya terdapat penggeser kecil dengan massa tertentu yang dapat meluncur ke kiri dan ke kanan. Terdapat pegas di kiri dan kanan yang berlawanan. Ketika slider bergerak, maka akan menggerakkan potensiometer yang ada di atasnya untuk bergerak. Potensiometer ini merupakan resistor pembagi tegangan. Dengan mengukur tegangan output yang dihasilkan oleh potensiometer, Anda dapat memperoleh nilai percepatan dari slider yang kecil tersebut. Akselerometer ini sebenarnya adalah dinamometer pegas. Menurut hukum kedua Newton, F = ma. Jika Anda ingin mengukur percepatan a, Anda dapat mencari benda dengan satuan massa dan mengukur gaya F. Itu saja. Terdapat accelerometer pada masing-masing sumbu X, Y, dan Z. Akselerometer memiliki stabilitas statis tetapi tidak memiliki stabilitas dinamis.
  • Sensor giroskop : Di tengah-tengahnya terdapat sebuah roda yang berputar dengan massa tertentu. Bila roda yang berputar itu berputar dengan kecepatan tinggi, menurut prinsip kekekalan momentum sudut, roda yang berputar itu mempunyai kecenderungan untuk mempertahankan momentum sudut aslinya arah sumbu rotasi tidak berubah. Ketika arah benda luar berputar, maka arah sumbu putaran dalam tidak akan berputar, sehingga akan terjadi simpangan sudut pada sambungan cincin keseimbangan. Jika Anda meletakkan potensiometer berputar pada sambungan dan mengukur tegangan potensiometer, Anda bisa mendapatkan sudut putaran. Giroskop seharusnya dapat memperoleh sudut secara langsung, tetapi giroskop MPU6050 ini tidak dapat mengukur sudut secara langsung, melainkan mengukur kecepatan sudut, yaitu kecepatan sudut chip yang berputar pada sumbu X, sumbu Y, dan Z -sumbu. Integral kecepatan sudut adalah sudut. Namun, ketika benda diam, nilai kecepatan sudut tidak dapat sepenuhnya kembali ke nol karena adanya kebisingan. Kemudian setelah akumulasi integral secara terus menerus, kebisingan kecil ini akan menyebabkan sudut yang dihitung menyimpang secara perlahan, yang merupakan sudut yang diperoleh dengan mengintegrasikan kecepatan sudut. Sudut tersebut tidak tahan terhadap ujian waktu, tetapi sudut ini tidak menjadi masalah baik diam maupun bergerak, dan tidak akan terpengaruh oleh pergerakan benda. Giroskop memiliki stabilitas dinamis, bukan stabilitas statis.
  • Menurut akselerometer yang mempunyai kestabilan statis tetapi tidak mempunyai kestabilan dinamis; giroskop mempunyai kestabilan dinamis tetapi tidak mempunyai kestabilan statis, sehingga kita dapat saling belajar kelebihan dan melengkapi kelemahan masing-masing , kita dapat mengintegrasikan stabilitas statis dan dinamis.

2.2 parameter MPU6050

  • ADC 16-bit mengumpulkan sinyal analog sensor, rentang kuantisasi: -32768~32767
  • Pilihan skala penuh akselerometer: ±2, ±4, ±8, ±16 (g) (1g = 9,8m/s2)
  • Pemilihan skala penuh giroskop: ±250, ±500, ±1000, ±2000 (°/detik, derajat/detik, satuan kecepatan sudut, berapa derajat rotasi per detik) (semakin besar pemilihan skala penuh, semakin luas rentang pengukuran. Semakin kecil rentang skala penuh, semakin tinggi resolusi pengukurannya)
  • Filter low-pass digital yang dapat dikonfigurasi: Sebuah register dapat dikonfigurasi untuk memilih pemfilteran low-pass pada data keluaran.
  • Sumber jam yang dapat dikonfigurasi
  • Pembagian frekuensi pengambilan sampel yang dapat dikonfigurasi: Sumber jam dapat dibagi dengan pembagi frekuensi untuk menyediakan jam untuk konversi AD dan sirkuit internal lainnya. Dengan mengontrol koefisien pembagian frekuensi, Anda dapat mengontrol kecepatan konversi AD.
  • Alamat budak I2C: 1101000 (AD0=0) atau 1101001 (AD0=1)
    • 110 1000 diubah menjadi heksadesimal yaitu 0x68, sehingga ada yang mengatakan bahwa alamat budak MPU6050 adalah 0x68. Namun dalam komunikasi I2C, 7 bit tertinggi dari byte pertama adalah alamat budak, dan bit terendah adalah bit baca dan tulis. Oleh karena itu, jika menurut Anda 0x68 adalah alamat budak, saat mengirim byte pertama, Anda harus mengubahnya terlebih dahulu 0x68 Geser ke kiri sebanyak 1 bit (0x68 << 1), lalu baca dan tulis bit demi bit atau lebih tinggi, baca 1 dan tulis 0.
    • Cara lainnya adalah dengan menggeser data 0x68 ke kiri sebanyak 1 bit (0x68 << 1) sebagai alamat slave, yaitu 0xD0. Saat ini, ketika sebenarnya mengirim byte pertama, jika ingin menulis, gunakan saja 0xD0 sebagai byte pertama; jika ingin membaca, gunakan 0xD0 atau 0x01 (0xD0 | 0x01), yaitu 0xD1 . Representasi ini tidak memerlukan operasi shift kiri, atau dengan kata lain representasi ini mengintegrasikan bit baca dan tulis ke dalam alamat slave. 0xD0 adalah alamat tulis dan 0xD1 adalah alamat baca.

2.3 Rangkaian perangkat keras

gambar.png

pinFungsi
VCC dan GNDSumber Daya listrik
SCL dan SDAPin komunikasi I2C
XCL dan XDAPin komunikasi host I2C
iklan0Bit terendah dari alamat budak
KE DALAMKeluaran sinyal interupsi
  • LDO: pengatur tegangan linier putus sekolah rendah, pengatur tegangan 3.3V.
  • SCL dan SDA: Ini adalah pin komunikasi I2C. Modul ini memiliki dua resistor pull-up 4,7K bawaan, jadi saat memasang kabel, cukup sambungkan SDA dan SCL langsung ke port GPIO .
  • XCL, XDA: Pin komunikasi Host I2C. Kedua pin ini dirancang untuk memperluas fungsi chip. Biasanya digunakan untuk magnetometer atau barometer eksternal. Ketika chip ekspansi ini terhubung, antarmuka host MPU6050 dapat langsung mengakses data chip ekspansi ini dan membaca data chip ekspansi ini ke MPU6050 yang memiliki unit DMP perhitungan sikap.
    Pin AD0: Ini adalah bit terendah dari alamat budak. Jika terhubung ke tingkat rendah, alamat budak 7-bit adalah 1101000; jika terhubung ke tingkat tinggi, alamat budak 7-bit adalah 1101001. Ada resistor pada diagram rangkaian, yang secara default ditarik lemah ke level rendah, jadi jika pin dibiarkan mengambang, levelnya rendah. Jika ingin dihubungkan ke level tinggi, Anda dapat langsung mengarahkan AD0 ke VCC dan menariknya dengan kuat ke level tinggi.
  • INT: Pin keluaran interupsi. Anda dapat mengonfigurasi beberapa peristiwa di dalam chip untuk memicu keluaran pin interupsi, seperti data siap, kesalahan host I2C, dll.
  • Chip ini juga memiliki built-in: deteksi jatuh bebas, deteksi gerakan, deteksi gerakan nol, dll. Sinyal-sinyal ini dapat memicu pin INT untuk menghasilkan transisi level, dan sinyal interupsi dapat dikonfigurasi jika diperlukan.
  • Catu daya chip MPU6050 adalah 2.375-3.46V, yang merupakan perangkat catu daya 3.3V dan tidak dapat dihubungkan langsung ke 5V. Oleh karena itu, regulator tegangan 3.3V ditambahkan, dan tegangan terminal input VCC_5V dapat antara 3.3V dan 5V. Kemudian regulator tegangan 3.3V mengeluarkan tegangan 3.3V yang stabil untuk memberi daya pada chip selama terminal 3.3V memiliki daya , Lampu indikator daya akan menyala.

2.4 diagram blok MPU6050

gambar.png

  • CLKIN dan CLKOUT adalah pin input jam dan pin output jam, tetapi umumnya kami menggunakan jam internal.
  • Bagian abu-abu: adalah sensor di dalam chip, akselerometer pada sumbu XYZ, dan giroskop pada sumbu XYZ.
  • Ada juga sensor suhu internal yang dapat digunakan untuk mengukur suhu.
  • Sensor-sensor ini pada dasarnya setara dengan resistor variabel. Setelah membagi tegangan, mereka mengeluarkan tegangan analog, dan kemudian melakukan konversi analog-ke-digital melalui ADC. Setelah konversi selesai, data dari sensor-sensor ini ditempatkan secara seragam dalam data register, yang dapat diperoleh dengan membaca data register. Nilai yang diukur oleh sensor. Semua konversi dalam chip ini sepenuhnya otomatis.
  • Setiap sensor memiliki unit uji mandiri, yang digunakan untuk memverifikasi kualitas chip. Saat uji mandiri dimulai, chip akan mensimulasikan gaya eksternal yang diberikan pada sensor lebih besar dari biasanya. Proses self-test: Anda dapat mengaktifkan self-test terlebih dahulu, membaca data, kemudian mengaktifkan self-test, membaca data, mengurangi kedua data, dan data yang dihasilkan disebut respon self-test. Untuk respon self-test ini, manual memberikan range. Jika berada dalam range tersebut berarti tidak ada masalah pada chipnya.
  • Charge Pump: Ini adalah pompa pengisian atau pompa pengisian.
  • Pin CPOUT memerlukan kapasitor eksternal.
  • Register status interupsi: dapat mengontrol kejadian internal mana yang dikeluarkan ke pin interupsi,
  • FIFO: Register first-in-first-out, yang dapat menyimpan aliran data dalam cache.
  • Register konfigurasi: Anda dapat mengkonfigurasi berbagai sirkuit internal
  • Register sensor: Register data, yang menyimpan data setiap sensor.
  • Dikalibrasi Pabrik: Artinya sensor di dalamnya telah dikalibrasi.
  • Prosesor gerak digital: Singkatnya DMP, ini adalah algoritma perangkat keras untuk perhitungan sikap yang ada di dalam chip. Ini dapat digunakan untuk perhitungan sikap dengan perpustakaan DMP resmi.
  • FSYNC: Sinkronisasi bingkai.

3. 10-1 Perangkat Lunak I2C membaca dan menulis MPU6050

3.1 Koneksi perangkat keras

Melalui komunikasi perangkat lunak I2C, baca dan tulis register di dalam chip MPU6050. Dengan menulis ke register konfigurasi, Anda dapat mengkonfigurasi modul plug-in. Dengan membaca register data, Anda dapat memperoleh data modul plug-in data yang dibaca akan ditampilkan. Pada OLED, data teratas adalah nomor ID perangkat. Nomor ID MPU6050 ini ditetapkan pada 0x68. Di bawah, tiga di sebelah kiri adalah data keluaran sensor percepatan, yang masing-masing merupakan percepatan sumbu X, sumbu Y, dan sumbu Z. Tiga di sebelah kanan adalah data keluaran sensor giroskop. yang merupakan kecepatan sudut sumbu X, sumbu Y, dan sumbu Z.
SCL terhubung ke pin PB10 STM32, dan SDA terhubung ke pin PB11. Karena pembalikan level perangkat lunak diterapkan di sini, dua port GPIO dapat dihubungkan sesuka hati.

3.2 Hasil operasi

IMG_20240406_155156.jpg

3.3 Aliran kode

STM32 adalah host dan MPU6050 adalah slave, yang merupakan mode master-slave.

  1. Tetapkan modul .c dan .h pada lapisan komunikasi I2C
    1. Tulis inisialisasi GPIO yang mendasari I2C
    2. 6 unit waktu dasar: mulai, akhir, kirim byte, terima byte, kirim respons, terima respons
  2. Buat modul .c dan .h MPU6050
    1. Berdasarkan modul komunikasi I2C, mengimplementasikan pembacaan pada alamat yang ditentukan, penulisan pada alamat yang ditentukan, penulisan register untuk mengkonfigurasi chip, dan pembacaan register untuk memperoleh data sensor.
  3. utama.c
    1. Panggil modul MPU6050, inisialisasi, ambil data, dan tampilkan data

3.4 Kode

  1. Kode 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. Kode 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. Periferal I2C

4.1 Pengenalan periferal I2C

  • STM32 mengintegrasikan rangkaian transceiver I2C perangkat keras, yang secara otomatis dapat menjalankan fungsi seperti pembuatan jam, pembuatan kondisi awal dan akhir, transmisi dan penerimaan bit respons, serta transmisi dan penerimaan data oleh perangkat keras, sehingga mengurangi beban pada CPU.
  • Mendukung model multi-host
  • Mendukung mode alamat 7-bit/10-bit
  • Mendukung kecepatan komunikasi yang berbeda, kecepatan standar (hingga 100 kHz), cepat (hingga 400 kHz)
  • Mendukung DMA
  • Kompatibel dengan protokol SMBus
  • Sumber daya I2C perangkat keras STM32F103C8T6: I2C1, I2C2

4.2 Diagram blok I2C

gambar.png

  • Di sebelah kiri adalah pin komunikasi: SDA dan SCL; SMBALERT digunakan oleh SMBus;
    Pin yang berasal dari periferal umum biasanya terhubung ke dunia luar melalui mode multiplexing port GPIO (periksa tabel)
  • Di atas adalah bagian pengendalian data: SDA. Bagian inti dari transmisi dan penerimaan data adalah register data DR (DATA REGISTER) dan register perpindahan data. Ketika data perlu dikirim, satu byte data dapat ditulis ke register data DR. Ketika register geser tidak memiliki data untuk digeser, nilai register data selanjutnya akan ditransfer ke register geser. Selama proses perpindahan, data selanjutnya dapat langsung dimasukkan ke dalam register data dan ditunggu. Setelah perpindahan data sebelumnya selesai, data berikutnya dapat dihubungkan dengan lancar dan terus dikirim. Ketika data ditransfer dari register data ke register geser, bit TXE dari register status diatur ke 1, yang menunjukkan bahwa register transmisi kosong.
  • Penerimaan: Data masukan dipindahkan dari pin ke register geser sedikit demi sedikit. Ketika satu byte data dikumpulkan, data ditransfer dari register geser ke register data secara keseluruhan, dan flag RXNE disetel pada saat itu. pada saat yang sama, menandakan penerimaan. Register tidak kosong, maka data dapat dibaca dari register data. Mengenai kapan menerima dan mengirim, Anda perlu menulis bit yang sesuai dalam register kontrol untuk pengoperasian. Kondisi awal, kondisi terminasi, bit respons, dll. diselesaikan melalui kontrol data.
  • Komparator dan register alamat digunakan dalam mode budak.
  • SCL: Kontrol jam digunakan untuk mengontrol jalur SCL. Tulis bit yang sesuai dalam register kontrol jam, dan rangkaian akan menjalankan fungsi yang sesuai. Rangkaian logika kendali, penulisan ke register kendali dapat mengendalikan seluruh rangkaian. Status kerja rangkaian dapat diketahui dengan membaca status register.
  • Saat mengirim dan menerima banyak byte, DMA dapat digunakan untuk meningkatkan efisiensi.

4.3 Struktur dasar I2C

gambar.png

  • SDA: Karena I2C adalah high-order first, shift register ini bergeser ke kiri. Saat mengirim, bit tinggi dipindahkan terlebih dahulu, lalu bit tinggi kedua. Jam SCL digeser satu kali dan digeser 8 kali, dan 8 byte dapat ditempatkan pada jalur SDA dari bit tinggi ke bit rendah. Saat menerima, data dipindahkan dari kanan melalui port GPIO, dan akhirnya 8 kali, satu byte diterima. Data keluaran dikeluarkan ke port melalui port GPIO. Data masukan dimasukkan ke register geser melalui port GPIO.
  • Port GPIO perlu dikonfigurasi ke mode keluaran saluran terbuka multipleks; multipleks berarti status port GPIO dikontrol oleh periferal on-chip, dan keluaran saluran terbuka adalah konfigurasi port yang diperlukan oleh protokol I2C. Bahkan dalam mode keluaran saluran terbuka, port GPIO dapat dimasukkan.
  • SCL: Pengontrol jam mengontrol garis jam melalui GPIO.
    gambar.png

4.4 Tuan rumah mengirim

gambar.png
Ketika STM32 ingin menulis ke alamat tertentu, ia harus mengikuti diagram urutan transmisi pemancar.

  • Alamat 7-bit: Satu byte setelah kondisi awal dialamatkan
  • Alamat 10-bit: Dua byte setelah kondisi awal adalah pengalamatan. Byte pertama adalah header bingkai, dan isinya adalah bit bendera 5-bit 11110 + alamat 2-bit + 1 bit baca-tulis; Alamat 8-bit murni.
  • Proses 7-bit: mulai, alamat budak, respons, data, respons, data, respons... Berhenti
  1. Setelah inisialisasi, bus default ke status idle, dan STM default ke mode slave Untuk menghasilkan kondisi awal, STM32 perlu menulis ke register kontrol (CR1), menulis 1, dan kemudian STM32 berubah dari mode slave ke mode master. .

gambar.png

  1. Event EV5 dapat dianggap sebagai bit flag. SB adalah bit register status, yang menunjukkan status perangkat keras.

gambar.png

  1. Kemudian Anda dapat mengirim byte alamat budak. Alamat budak perlu ditulis ke register data DR. Setelah menulis ke DR, rangkaian perangkat keras akan secara otomatis mentransfer byte alamat ke register geser, dan kemudian mentransfer kata Node tersebut dikirim ke bus I2C, dan kemudian perangkat keras akan secara otomatis menerima respons dan menilai. Jika tidak ada respons, perangkat keras akan menyetel tanda kegagalan respons, dan kemudian tanda tersebut dapat mengajukan interupsi untuk mengingatkan kita.
  2. Ketika pengalamatan selesai, event EV6 akan terjadi dan bit flag ADDR akan menjadi 1. Bit flag ini menunjukkan akhir transmisi alamat dalam mode master.

gambar.png

  1. Event EV8_1 berarti flag TxE adalah 1, register geser kosong, dan register data kosong. Kita perlu menulis ke register data DR untuk mengirim data akan segera beralih ke shift mendaftar untuk mengirim. Akan terjadi event EV8. Shift register tidak kosong dan register data kosong, artinya shift register sedang mengirimkan data. Oleh karena itu, timing data 1 dihasilkan di sini dalam proses. Pada saat ini, data 2 akan ditulis ke dalam register data dan menunggu. Setelah menerima bit respon, bit data ditransfer ke register geser untuk transmisi register datanya kosong, sehingga kali ini kejadian EV8 terulang kembali.
  2. Kemudian data 2 dikirim, namun kali ini data berikutnya sudah ditulis ke register data dan menunggu. Setelah kejadian EV8 terdeteksi, data selanjutnya dapat ditulis.
  3. Setelah data yang ingin dikirim ditulis, tidak ada data baru yang ditulis ke register data. Ketika pergeseran data saat ini dalam register geser selesai, register geser kosong dan register data juga kosong. Peristiwa EV8_2, TxE=1 berarti register geser kosong, register data kosong, BTF: flag akhir transmisi byte, saat transmisi, saat data baru akan dikirim dan register data belum ditulis dengan data baru. Ketika EV8_2 terdeteksi, kondisi penghentian Stop dapat dihasilkan. Untuk menghasilkan kondisi terminasi, tentunya harus ada bit-bit yang sesuai dalam register kontrol yang dapat dikontrol. Dengan cara ini, urutan pengiriman selesai.

4.5 Penerimaan tuan rumah

gambar.png
Penerimaan master 7-bit: mulai, alamat budak + baca, terima respons, terima data, kirim respons... terima data, non-respons, akhiri

  1. Pertama, tulis bit Start dari register kontrol untuk menghasilkan kondisi start, dan kemudian tunggu event EV5 (menunjukkan bahwa kondisi start telah dikirimkan).
  2. Setelah pengalamatan, respons diterima, dan peristiwa EV6 dihasilkan setelahnya (menunjukkan bahwa pengalamatan selesai).
  3. Data 1 berarti data yang diinput melalui shift register.
  4. EV6_1 menandakan data masih dalam keadaan bergeser. Setelah mendapat respon berarti shift register berhasil memindahkan satu byte data 1. Pada saat ini byte yang digeser tersebut dipindahkan ke register data secara keseluruhan, dan bendera RxNE disetel pada saat yang sama, menunjukkan bahwa register data tidak kosong, yaitu satu byte data telah diterima. Statusnya adalah peristiwa EV7, RxNE=1. Membaca register DR akan menghapus peristiwa tersebut, yang berarti bahwa data sudah diterima. Setelah kita membaca data tersebut maka event tersebut sudah tidak ada lagi.
  5. Tentunya bila data 1 belum terbaca, maka data 2 bisa langsung dipindahkan ke dalam shift register. Setelah itu, pergeseran data 2 selesai, data 2 diterima, dihasilkan event EV7, data 2 dibaca, dan data 2 dibaca. acara EV7 hilang.
  6. Ketika tidak ada lagi penerimaan yang diperlukan, register kontrol bit respons ACK perlu disetel ke 0 terlebih dahulu saat unit pewaktuan terakhir terjadi, dan permintaan kondisi terminasi disetel, yaitu peristiwa EV7_1 Setelah itu, NA non-respons akan diberikan. Karena bit STOP diset, maka kondisi terminasi dihasilkan.

4.6 Perbandingan bentuk gelombang perangkat lunak/perangkat keras

gambar.png

gambar.png

5. 10-2 Perangkat Keras I2C membaca dan menulis MPU6050

5.1 Fungsi perpustakaan 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 Implementasi membaca dan menulis MPU6050 perangkat keras I2C

5.2.1 Koneksi perangkat keras

SCL terhubung ke pin PB10 STM32, dan SDA terhubung ke pin PB11. Karena pembalikan level perangkat lunak diterapkan di sini, dua port GPIO dapat dihubungkan sesuka hati.
Data teratas OLED adalah nomor ID perangkat. Nomor ID MPU6050 ini ditetapkan pada 0x68. Di bawah, tiga di sebelah kiri adalah data keluaran sensor percepatan, yang masing-masing merupakan percepatan sumbu X, sumbu Y, dan sumbu Z. Tiga di sebelah kanan adalah data keluaran sensor giroskop. yang merupakan kecepatan sudut sumbu X, sumbu Y, dan sumbu Z.

5.2.2 Hasil operasi

IMG_20240406_172128.jpg

5.2.3 Proses penerapan kode

  1. Konfigurasikan periferal I2C, inisialisasi periferal I2C, ganti MyI2C_Init
    (1) Nyalakan jam periferal I2C dan port GPIO yang sesuai,
    (2) Inisialisasi port GPIO yang sesuai dengan periferal I2C ke mode saluran terbuka multipleks
    (3) Gunakan struktur untuk mengkonfigurasi seluruh I2C
    (4) I2C_Cmd, aktifkan I2C
  2. Kontrol sirkuit periferal, sadari waktu penulisan ke alamat tertentu, dan ganti WriteReg
  3. Kontrol sirkuit periferal untuk mewujudkan waktu pembacaan alamat yang ditentukan dan ganti ReadReg

5.2.4 Kode

  1. Kode 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. utama.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