Compartir tecnología

STM32-I2C

2024-07-12

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

Este contenido se basa enTecnología Jiangxie STM32Compilado después del estudio del video.

1. Comunicación I2C

1.1 Introducción a la comunicación I2C

  • I2C (Inter IC Bus) es un bus de datos universal desarrollado por Philips
  • Dos líneas de comunicación: línea de reloj en serie SCL (Serial Clock), línea de datos en serie SDA (Serial Data)
  • Síncrono, semidúplex, de un solo extremo, multidispositivo
  • Responder con datos
  • Admite el montaje de múltiples dispositivos en el bus (un maestro y múltiples esclavos, múltiples maestros y múltiples esclavos)
    • Un maestro, varios esclavos: el microcontrolador actúa como host y domina el funcionamiento del bus I2C. Todos los módulos externos montados en el bus I2C son esclavos. Los esclavos solo pueden controlar el bus I2C después de haber sido nombrados por el host y no pueden tocarlo. sin permiso. bus I2C para evitar conflictos.
    • Multimaestro y multiesclavo: cualquier módulo del bus puede saltar activamente y actuar como maestro. Cuando ocurre un conflicto de bus, el protocolo I2C realizará un arbitraje. La parte que gana el arbitraje obtiene el control del bus y la parte perdedora se convierte automáticamente en esclava.

imagen.png

1.2 Circuito de hardware

  • El SCL de todos los dispositivos I2C está conectado entre sí y el SDA está conectado entre sí.
  • Tanto SCL como SDA del dispositivo deben configurarse en modo de salida de drenaje abierto.
  • Agregue una resistencia pull-up a cada uno de SCL y SDA; el valor de resistencia generalmente es de aproximadamente 4,7 KΩ

Figura 1Figura 2
imagen.png

  • Un maestro y varios esclavos: la CPU es una microcomputadora de un solo chip. Como maestro del bus, incluye control total de la línea SCL. El maestro tiene control total de la línea SCL en cualquier momento. Además, en el estado inactivo, el host puede iniciar activamente el control de SDA. Solo cuando el esclavo envía datos y el esclavo responde, el host transferirá el control de SDA al esclavo.
  • El IC controlado es un esclavo montado en el bus I2C, que puede ser un sensor de actitud, OLED, memoria, módulo de reloj, etc. La potencia del esclavo es relativamente pequeña. Para la línea de reloj SCL, solo puede leer pasivamente en cualquier momento. El esclavo no puede controlar la línea SCL. Para la línea de datos SDA, el esclavo no puede iniciar activamente el control de SDA. Sólo después de que el maestro envía un comando para leer el esclavo, o cuando el esclavo responde, el esclavo puede obtener brevemente el control del SDA.
  • Figura 2: SCL a la izquierda y SDA a la derecha. Todos los datos se pueden ingresar a través de un búfer de datos o un disparador Schmitt.
    • Debido a que la entrada no tiene ningún efecto en el circuito, cualquier dispositivo puede tener entrada en cualquier momento.
    • La salida adopta una configuración de salida de drenaje abierto. Cuando la salida es baja, el interruptor está encendido y el pin está conectado directamente a tierra, lo cual es una fuerte caída cuando la salida es alta, el interruptor está apagado y el pin; no está conectado a nada y está en un estado flotante, por lo que todos los dispositivos solo pueden generar un nivel bajo pero no un nivel alto. Para evitar la flotación causada por un nivel alto, SCL y SDA necesitan tener una resistencia pull-up externa. el bus, a través de una resistencia tirado a un nivel alto, por lo que es un pull-up débil. De esta manera, en primer lugar, elimina por completo el fenómeno del cortocircuito de alimentación y garantiza la seguridad del circuito; en segundo lugar, evita el cambio frecuente de modos de pin; En el modo de drenaje abierto, generar un nivel alto equivale a desconectar el pin, por lo que puede generar un nivel alto directamente antes de ingresar. En tercer lugar, este modo tiene un fenómeno de "Y cableado". Siempre que uno o más dispositivos emitan un nivel bajo, el bus estará en un nivel bajo. Solo cuando todos los dispositivos emitan un nivel alto, el bus estará en un nivel alto. . Por lo tanto, I2C puede aprovechar este fenómeno para realizar sincronización de reloj y arbitraje de bus en modo multimaestro. Entonces, aunque SCL aquí puede usar salida push-pull en un modo maestro y múltiples esclavos, todavía usa el modo de salida de drenaje abierto más extracción.

1.3 Unidad básica de temporización I2C

1.3.1 Condiciones iniciales y condiciones finales

  • condición inicial: Durante el nivel alto de SCL, SDA cambia del nivel alto al nivel bajo
  • Condición de terminación: Durante el nivel alto de SCL, SDA cambia del nivel bajo al nivel alto

imagen.png

  • bajo condiciones iniciales : Cuando el bus I2C está en estado inactivo, tanto SCL como SDA están en un estado de alto nivel, es decir, ningún dispositivo toca SCL y SDA y SDA son llevados a un nivel alto mediante resistencias pull-up externas, y el bus está. en un estado tranquilo. Estado de alto nivel. Cuando el host necesita enviar y recibir datos, primero debe romper el silencio del bus y generar una condición inicial, es decir, SCL está en un nivel alto sin tocarlo, y luego bajar SDA para generar un flanco descendente. Cuando el esclavo captura el nivel alto de SCL y la señal de flanco descendente de SDA, se reiniciará y esperará la llamada del maestro. Después del flanco descendente de SDA, el host necesita bajar SCL nuevamente. Por un lado, ocupa el bus y, por otro lado, también es para facilitar el empalme de la unidad básica. Posteriormente se garantizará que, excepto para las condiciones de inicio y parada, el SCL de cada unidad secuencial comience con un nivel bajo y termine con un nivel bajo.
  • En el estado de condición de terminación : SCL se suelta primero y rebota a un nivel alto, luego SDA se suelta y rebota a un nivel alto, generando un flanco ascendente, que desencadena la condición de terminación. Después de la condición de terminación simultánea, tanto SCL como SDA están altos y regresan al estado de calma inicial.
    El inicio y la parada son generados por el host y el esclavo no puede generar inicio y parada. Por lo tanto, cuando el autobús está inactivo, el esclavo siempre debe soltar las manos y no puede saltar y tocar el autobús.

1.3.2 Enviar un byte

  • enviar un byte: Durante el nivel bajo de SCL, el host coloca los bits de datos en la línea SDA en secuencia (el bit alto primero) y luego libera SCL. El esclavo leerá los bits de datos durante el nivel alto de SCL, por lo que no se permite SDA. para tener datos durante el nivel alto de SCL. Cuando los datos cambien, realice un ciclo del proceso anterior 8 veces para enviar un byte.

El host de bajo nivel coloca datos y el esclavo de alto nivel los lee.
imagen.png
Después de la condición de inicio, el host también debe enviar el primer byte. Cuando SCL está bajo, si el host quiere enviar 0, baja el SDA; si quiere enviar 1, lo suelta y el SDA rebota al nivel alto; Durante el nivel bajo de SCL, se permite que el nivel de SDA cambie. Después de colocar los datos, el host libera la línea de reloj y SCL rebota al nivel alto. Durante el nivel alto, es el momento en que el esclavo lee SDA, por lo que durante el nivel alto, no se permite que SDA cambie. Después de que SCL esté en un nivel alto, el esclavo necesita leer SDA lo más rápido posible. Generalmente, el esclavo ha completado la lectura en el flanco ascendente de SCL. Debido a que el reloj está controlado por el maestro, el esclavo no sabe cuándo ocurre el flanco descendente, por lo que el esclavo leerá los datos en el flanco ascendente de SCL. Después de que el host suelta SCL por un período de tiempo, puede continuar bajando SCL y transmitir el siguiente bit. El anfitrión también necesita colocar datos en SDA lo antes posible después de la caída de SCL. Pero el host tiene control sobre el reloj, por lo que solo necesita colocar datos en SDA en cualquier momento cuando el nivel bajo es bajo. Después de que se publican los datos, el host libera SCL nuevamente, SCL está alto y el esclavo lee este bit. Repita este proceso: el host reduce SCL, coloca los datos en SDA, el host libera SCL y el esclavo lee los datos de SDA. Bajo la sincronización de SCL, el maestro transmite y el esclavo recibe en secuencia. Después de 8 ciclos, se envían datos de 8 bits, que son un byte.
Dado que el bit de orden superior es el primero, el primer bit es el bit B7 más alto de un byte y el bit B0 más bajo se envía al final.

1.3.3 Recibir un byte

  • recibir un byte: Durante el nivel bajo de SCL, el esclavo coloca los bits de datos en la línea SDA en secuencia (el bit alto primero) y luego libera SCL. El host leerá los bits de datos durante el nivel alto de SCL, por lo que no se permite SDA. para tener datos durante el nivel alto de SCL Cuando los datos cambian, realice un ciclo del proceso anterior 8 veces para recibir un byte (el host debe liberar SDA antes de recibir).

El esclavo de bajo nivel coloca datos, el host de alto nivel lee datos
imagen.png
Línea SDA: el maestro necesita liberar SDA antes de recibir. En este momento, el esclavo obtiene el control de SDA. Si el esclavo necesita enviar 0, baja SDA. Si el esclavo necesita enviar 1, lo suelta y SDA. rebota a un nivel alto. El nivel bajo convierte datos, el nivel alto lee datos. La línea continua representa el nivel controlado por el maestro y la línea de puntos representa el nivel controlado por el esclavo. El host controla SCL durante todo el proceso, y el host SDA debe liberarse antes de recibirlo y entregárselo al esclavo para su control. Debido a que el reloj SCL está controlado por el host, la conversión de datos del esclavo se realiza básicamente en el flanco descendente de SCL, y el host puede leer en cualquier momento cuando SCL está alto.

1.3.4 Enviar respuesta y recibir respuesta

  • Enviar respuesta: Después de recibir un byte, el host envía un bit de datos en el siguiente reloj. El dato 0 indica respuesta y el dato 1 indica falta de respuesta.
  • recibir respuesta: Después de que el host envía un byte, recibe un bit de datos en el siguiente reloj para determinar si el esclavo responde. Los datos 0 indican respuesta y los datos 1 indican que no hay respuesta (el host debe liberar SDA antes de recibir).

imagen.png
Es decir, después de llamar al momento de enviar un byte, debe ir seguido del momento de llamar a la respuesta de recepción, que se utiliza para determinar si el esclavo ha recibido los datos que se le acaban de proporcionar. Si el esclavo lo recibe, entonces en el bit de respuesta, cuando el maestro libera SDA, el esclavo debe bajar inmediatamente el SDA y luego, durante el nivel alto de SCL, el host lee el bit de respuesta. Si el bit de respuesta es 0, significa que efectivamente el esclavo lo ha recibido.
Al recibir un byte, debe llamar al envío de respuesta. El propósito de enviar una respuesta es decirle al esclavo si desea continuar enviando. Si la máquina esclava recibe una respuesta de la máquina maestra después de enviar un dato, la máquina esclava continuará enviando. Si la máquina esclava no recibe una respuesta de la máquina maestra, la máquina esclava pensará que tiene un dato. Se ha enviado, pero la máquina maestra me ignora. Quizás el host no lo quiera. En este momento, el esclavo liberará obedientemente el SDA y entregará el control del SDA para evitar interferencias con las operaciones posteriores del host.

1.4 sincronización I2C

1.4.1 Especificar dirección para escribir

  • Especificar dirección para escribir
  • Para el dispositivo especificado (Dirección esclava), escriba los datos especificados (Datos) en la dirección especificada (Dirección de registro) (es decir, la dirección de registro del dispositivo especificado)

imagen.png
proceso:
(1) Condiciones iniciales
(2) Momento de envío de un byte: 0xD0 (dirección esclava (7 bits) + escritura (1 bit) -0) (1101 0000)
(3) Recibir respuesta: RA = 0 (recibir la respuesta del esclavo)
(4) Dirección especificada: 0x19 (0001 1001)
(5) Recibir respuesta: RA = 0 (recibir la respuesta del esclavo)
(6) Escriba los datos especificados: 0xAA (1010 1010)
(7) Recibir respuesta: RA = 0
(8) Bit de parada P (condición de terminación)

  • Después de la condición de inicio, debe ser el momento para enviar un byte. El contenido del byte debe ser la dirección del esclavo + bits de lectura y escritura. La dirección del esclavo es de 7 bits y los bits de lectura y escritura son de 1 bit, que es exactamente. 8 bits. Enviar la dirección del esclavo es para determinar el objeto de comunicación, y enviar el bit de lectura y escritura es para confirmar si se escribe o lee a continuación. Ahora el host envía un dato. El contenido del byte se convierte a hexadecimal. El bit de orden superior es 0xD0. La siguiente unidad es el bit de respuesta (RA) del esclavo receptor. El bit de escritura termina y SCL baja. Después de eso, el host necesita liberar SDA, seguido por el bit de reconocimiento RA.
  • El nivel alto después de que finaliza el bit de respuesta RA se genera cuando el esclavo libera SDA. El esclavo entrega el control de SDA. Debido a que el esclavo quiere intercambiar datos lo antes posible en el nivel bajo de SCL, el flanco ascendente de SDA y. El borde descendente de SCL ocurrió casi simultáneamente.
  • Una vez completada la respuesta, si continúa enviando un byte, el segundo byte se puede enviar al interior del dispositivo designado. El dispositivo esclavo puede definir el uso del segundo byte propio y posteriores. Generalmente, el segundo byte puede ser una dirección de registro o una palabra de control de instrucción, etc., y el tercer byte es el contenido que el host desea escribir en la dirección de registro (segundo byte).
  • P es el bit de parada.

El propósito de esta trama de datos es: para el dispositivo que especifica la dirección de esclavo 1101000, escribir el dato 0xAA en su registro interno en la dirección 0x19.
0 significa: el host realizará una operación de escritura en el tiempo posterior;
1 significa: el host realizará una operación de lectura en la secuencia de tiempo posterior;

1.4.2 Lectura de la dirección actual

  • dirección actual leída
  • Para el dispositivo especificado (Dirección esclava), lea los datos del esclavo (Datos) en la dirección indicada por el puntero de dirección actual.

imagen.png
proceso:
(1) Condiciones iniciales
(2) Momento de envío de un byte: 0xD1 (dirección esclava (7 bits) + lectura (1 bit) -1) (1101 0001)
(3) Recibir respuesta: RA = 0 (recibir la respuesta del esclavo)
(4) Leer datos del esclavo: 0x0F (0000 1111)
(7) Enviar respuesta: SA = 0
(8) Bit de parada P (condición de terminación)

  • El bit de lectura y escritura es 1, lo que indica que se realizará la siguiente operación de lectura. Después de que el esclavo responda (RA = 0), se invertirá la dirección de transmisión de datos. El maestro quiere entregar el control de SDA al esclavo y el maestro llama al momento de recibir un byte para realizar la operación de recepción.
  • En el segundo byte, el esclavo obtiene permiso del maestro y puede escribir en SCL durante el nivel bajo de SCL. El maestro lee SDA durante el nivel alto de SCL. Finalmente, el maestro lee en secuencia durante el nivel alto de SCL. 8 bits, se recibe un byte de datos enviado por el esclavo, que es 0x0F. ¿Pero qué registro del esclavo es 0x0F? En el tiempo de lectura, el protocolo I2C estipula que cuando el host se dirige, una vez que el indicador de lectura y escritura se establece en 1. El siguiente byte cambiará inmediatamente al tiempo de lectura. Por lo tanto, el host comenzará a recibir antes de que tenga tiempo de especificar qué registro desea leer, por lo que no hay ningún enlace para especificar la dirección aquí. En la máquina esclava, todos los registros se asignan a un área lineal y habrá una variable de puntero separada que indica uno de los registros. Este puntero se enciende de forma predeterminada, generalmente apunta a la dirección 0 y cada vez que se escribe un byte. Después de leer un byte, el puntero se incrementará automáticamente una vez y se moverá a la siguiente posición. Luego, al llamar al tiempo de lectura de la dirección actual, si el host no especifica qué dirección leer, el esclavo regresará al registro señalado por el. valor actual.

1.4.3 Leer en la dirección especificada

  • Especificar dirección para leer
  • Para el dispositivo especificado (Dirección esclava), en la dirección especificada (Dirección de registro), lea los datos del esclavo (Datos)

imagen.png
Comience primero, luego repita inicio, luego pare
proceso:
(1) Condiciones iniciales
(2) Momento de envío de un byte: 0xD0 (dirección esclava (7 bits) + escritura (1 bit) -0) (1101 0000)
(3) Recibir respuesta: RA = 0 (recibir la respuesta del esclavo)
(4) Dirección especificada: 0x19 (0001 1001)
(5) Recibir respuesta: RA = 0 (recibir la respuesta del esclavo)
(6) Repita la condición inicial.
(7) Momento de envío de un byte: 0xD1 (dirección esclava (7 bits) + lectura (1 bit) -1) (1101 0001)
(8) Recibir respuesta: RA = 0
(9) Leer datos del esclavo: 0xAA (1010 1010)
(10) Enviar respuesta: SA = 0
(11) Bit de parada P (condición de terminación)

  • La primera parte es escribir en la dirección especificada, pero solo se especifica la dirección y no hay tiempo para escribir. La segunda parte es leer la dirección actual, porque la dirección se acaba de especificar, por lo que se lee la dirección actual; llamado de nuevo.
  • La dirección del esclavo especificada es 1101000, el indicador de lectura y escritura es 0 y se realiza la operación de escritura. Después de que el esclavo responde, se escribe otro byte (el segundo byte) para especificar la dirección 0x19 en el esclavo. Puntero de dirección, es decir, después de que el esclavo recibe los datos, su puntero de registro apunta a la posición 0x19.
  • Sr es una condición de inicio repetida, que equivale a iniciar un nuevo tiempo. Debido a que el indicador de lectura y escritura especificado solo puede seguir el primer byte de la condición de inicio, si desea cambiar la dirección de lectura y escritura, solo puede tenerlo. otra condición de inicio.
  • Luego, después de la condición de inicio, vuelva a direccionar y especifique el bit de indicador de lectura y escritura. En este momento, el bit de indicador de lectura y escritura es 1, lo que indica que se debe leer. Luego, el host recibe un byte, que son los datos. 0xAA en la dirección 0x19.

2. MPU6050

2.1 Introducción a MPU6050

  • MPU6050 es un sensor de actitud de 6 ejes que puede medir los parámetros de aceleración y velocidad angular de los ejes X, Y y Z del propio chip. A través de la fusión de datos, se puede obtener más a menudo el ángulo de actitud (ángulo de Euler). Equilibrando vehículos, aviones, etc. que necesitan detectarse por sí mismos en la escena del gesto.
  • Acelerómetro de 3 ejes (Accelerometer): mide la aceleración de los ejes X, Y y Z
  • Sensor giroscópico de 3 ejes (Giroscopio): mide la velocidad angular de los ejes X, Y y Z

imagen.png

  • Tomando como ejemplo el fuselaje de un avión, el ángulo de Euler es el ángulo entre el fuselaje del avión y los tres ejes iniciales.
    • aviónLa nariz del avión se inclina hacia abajo o hacia arriba., el ángulo entre este eje se llamaPaso
    • aviónEl fuselaje gira hacia la izquierda o hacia la derecha., el ángulo entre este eje se llamaRollo
    • aviónMantenga el fuselaje niveladoGirar el morro del avión hacia la izquierda o hacia la derecha., el ángulo entre este eje se llamaGuiñada
    • El ángulo de Euler representa la actitud del avión en este momento, ya sea que esté inclinado hacia arriba o hacia abajo, hacia la izquierda o hacia la derecha.
  • Los algoritmos de fusión de datos comunes generalmente incluyen filtrado complementario, filtrado de Kalman, etc., y cálculo de actitud en navegación inercial.
  • Acelerómetro : La línea de puntos en el medio es el eje de inducción. En el medio hay un pequeño control deslizante con cierta masa que puede deslizarse hacia la izquierda y hacia la derecha. Hay un resorte a la izquierda y a la derecha contra él. Cuando el control deslizante se mueve, hará que el potenciómetro se mueva. Este potenciómetro es una resistencia divisoria de voltaje. Al medir la salida de voltaje del potenciómetro, puede obtener el valor de aceleración del control deslizante pequeño. Este acelerómetro es en realidad un dinamómetro de resorte. Según la segunda ley de Newton, F = ma. Si quieres medir la aceleración a, puedes encontrar un objeto con masa unitaria y medir la fuerza F. Eso es todo. Hay un acelerómetro en cada uno de los ejes X, Y y Z. Los acelerómetros tienen estabilidad estática pero no dinámica.
  • Sensor giroscópico : En el medio hay una rueda giratoria con una determinada masa. Cuando la rueda giratoria gira a alta velocidad, de acuerdo con el principio de conservación del momento angular, la rueda giratoria tiende a mantener su momento angular original. dirección del eje de rotación sin cambios. Cuando gira la dirección del objeto externo, la dirección del eje de rotación interno no girará, lo que producirá una desviación angular en la conexión del anillo de equilibrio. Si coloca un potenciómetro giratorio en la conexión y mide el voltaje del potenciómetro, puede obtener el ángulo de rotación. El giroscopio debería poder obtener directamente el ángulo, pero el giroscopio de este MPU6050 no puede medir directamente el ángulo. Mide la velocidad angular, es decir, la velocidad angular del chip que gira alrededor del eje X, el eje Y y el Z. -eje. La integral de la velocidad angular es el ángulo. Sin embargo, cuando el objeto está estacionario, el valor de la velocidad angular no puede volver completamente a cero debido al ruido. Luego, después de la acumulación continua de integrales, este pequeño ruido hará que el ángulo calculado se desvíe lentamente. que es el ángulo obtenido integrando la velocidad angular. No puede resistir la prueba del tiempo, pero este ángulo no es problema ya sea que esté estacionario o en movimiento, y no se verá afectado por el movimiento del objeto. Los giroscopios tienen estabilidad dinámica, no estática.
  • Según el acelerómetro, que tiene estabilidad estática pero no tiene estabilidad dinámica; el giroscopio tiene estabilidad dinámica pero no tiene estabilidad estática. Estas dos características, por lo que podemos aprender de las fortalezas de cada uno y complementar las debilidades de cada uno. , podemos integrar estabilidad estática y dinámica. La postura es incómoda.

2.2 parámetros MPU6050

  • ADC de 16 bits recopila la señal analógica del sensor, rango de cuantificación: -32768~32767
  • Selección de escala completa del acelerómetro: ±2, ±4, ±8, ±16 (g) (1g = 9,8 m/s2)
  • Selección de escala completa del giroscopio: ±250, ±500, ±1000, ±2000 (°/seg, grado/segundo, unidad de velocidad angular, cuántos grados de rotación por segundo) (cuanto mayor sea la selección de escala completa, más amplia será la rango de medición. Cuanto menor sea el rango de escala completa, mayor será la resolución de medición)
  • Filtro de paso bajo digital configurable: se puede configurar un registro para seleccionar el filtrado de paso bajo de los datos de salida.
  • Fuente de reloj configurable
  • División de frecuencia de muestreo configurable: la fuente del reloj se puede dividir por el divisor de frecuencia para proporcionar relojes para la conversión AD y otros circuitos internos. Al controlar el coeficiente de división de frecuencia, puede controlar la velocidad de conversión AD.
  • Dirección de esclavo I2C: 1101000 (AD0=0) o 1101001 (AD0=1)
    • 110 1000 se convierte a hexadecimal, que es 0x68, por lo que algunos dicen que la dirección esclava de MPU6050 es 0x68. Pero en la comunicación I2C, los 7 bits superiores del primer byte son la dirección del esclavo y el bit más bajo es el bit de lectura y escritura. Por lo tanto, si cree que 0x68 es la dirección del esclavo, primero debe cambiar al enviar el primer byte. 0x68 Desplazarse 1 bit hacia la izquierda (0x68 << 1), luego leer y escribir bits bit a bit o hacia arriba, leer 1 y escribir 0.
    • Otro método es desplazar los datos de 0x68 hacia la izquierda 1 bit (0x68 << 1) como dirección esclava, que es 0xD0. En este caso, la dirección esclava de MPU6050 es 0xD0. En este momento, cuando envíe el primer byte, si desea escribir, simplemente use 0xD0 como el primer byte; si desea leer, use 0xD0 o 0x01 (0xD0 | 0x01), es decir, 0xD1. . Esta representación no requiere una operación de desplazamiento a la izquierda o, en otras palabras, esta representación integra los bits de lectura y escritura en la dirección esclava. 0xD0 es la dirección de escritura y 0xD1 es la dirección de lectura.

2.3 Circuito de hardware

imagen.png

alfilerFunción
VCC, GNDfuente de alimentación
SCL, SDAPin de comunicación I2C
XCL, XDAPines de comunicación del host I2C
AD0El bit más bajo de la dirección esclava.
EN TSalida de señal de interrupción
  • LDO: regulador de voltaje lineal de baja caída, regulador de voltaje de 3,3 V.
  • SCL y SDA: son pines de comunicación I2C. El módulo tiene dos resistencias pull-up integradas de 4,7 K, por lo que al realizar el cableado, simplemente conecte SDA y SCL directamente al puerto GPIO. No es necesario conectar resistencias pull-up externas. .
  • XCL, XDA: pines de comunicación del host I2C. Estos dos pines están diseñados para ampliar las funciones del chip. Generalmente se usa para magnetómetros o barómetros externos. Cuando estos chips de expansión están conectados, la interfaz de host de MPU6050 puede acceder directamente a los datos de estos chips de expansión y leer los datos de estos chips de expansión en MPU6050 tiene una unidad DMP. cálculo de actitud.
    Pin AD0: es el bit más bajo de la dirección del esclavo. Si está conectado a un nivel bajo, la dirección del esclavo de 7 bits es 1101000; si está conectado a un nivel alto, la dirección del esclavo de 7 bits es 1101001. Hay una resistencia en el diagrama del circuito, que está débilmente bajada a un nivel bajo de forma predeterminada, por lo que si el pin se deja flotando, es de nivel bajo. Si desea conectarlo a un nivel alto, puede conectar directamente AD0 a VCC. y tire de él con fuerza hasta un nivel alto.
  • INT: pin de salida de interrupción Puede configurar algunos eventos dentro del chip para activar la salida del pin de interrupción, como datos listos, error del host I2C, etc.
  • El chip también tiene incorporado: detección de caída libre, detección de movimiento, detección de movimiento cero, etc. Estas señales pueden activar el pin INT para generar una transición de nivel y las señales de interrupción se pueden configurar si es necesario.
  • La fuente de alimentación del chip MPU6050 es de 2.375-3.46V, que es un dispositivo de fuente de alimentación de 3.3V y no se puede conectar directamente a 5V. Por lo tanto, se agrega un regulador de voltaje de 3,3 V y el voltaje del terminal de entrada VCC_5V puede estar entre 3,3 V y 5 V. Luego, el regulador de voltaje de 3,3 V genera un voltaje estable de 3,3 V para alimentar el chip siempre que el terminal de 3,3 V tenga energía. , La luz indicadora de encendido se encenderá.

2.4 Diagrama de bloques de MPU6050

imagen.png

  • CLKIN y CLKOUT son pines de entrada y salida de reloj, pero generalmente usamos el reloj interno.
  • La parte gris: es el sensor dentro del chip, el acelerómetro en el eje XYZ y el giroscopio en el eje XYZ.
  • También hay un sensor de temperatura incorporado que se puede utilizar para medir la temperatura.
  • Estos sensores son esencialmente equivalentes a resistencias variables. Después de dividir el voltaje, generan un voltaje analógico y luego realizan una conversión de analógico a digital a través del ADC. Una vez completada la conversión, los datos de estos sensores se colocan de manera uniforme en los datos. registro, que se puede obtener leyendo el registro de datos. El valor medido por el sensor. Todas las conversiones dentro de este chip están completamente automatizadas.
  • Cada sensor tiene una unidad de autoprueba, que se utiliza para verificar la calidad del chip. Cuando se inicia la autoprueba, el chip simulará una fuerza externa ejercida sobre el sensor. Esta fuerza externa hará que los datos del sensor sean. más grande de lo habitual. Proceso de autoprueba: primero puede habilitar la autoprueba, leer los datos, luego habilitar la autoprueba, leer los datos, restar los dos datos y los datos resultantes se denominan respuesta de autoprueba. Para esta respuesta de autoprueba, el manual proporciona un rango. Si está dentro de este rango, significa que no hay problema con el chip.
  • Bomba de carga: Es una bomba de carga o bomba de carga. La bomba de carga es un circuito de refuerzo.
  • El pin CPOUT requiere un condensador externo.
  • Registro de estado de interrupción: puede controlar qué eventos internos se envían al pin de interrupción,
  • FIFO: registro de primero en entrar, primero en salir, que puede almacenar en caché el flujo de datos.
  • Registro de configuración: Puede configurar varios circuitos internos.
  • Registro de sensor: Registro de datos, que almacena los datos de cada sensor.
  • Calibrado de fábrica: esto significa que los sensores internos están calibrados.
  • Procesador de movimiento digital: DMP para abreviar, es un algoritmo de hardware para el cálculo de actitud que viene dentro del chip. Puede usarse para el cálculo de actitud con la biblioteca oficial de DMP.
  • FSYNC: Sincronización de cuadros.

3. 10-1 Software I2C de lectura y escritura MPU6050

3.1 Conexión de hardware

A través de la comunicación I2C del software, lea y escriba los registros dentro del chip MPU6050. Al escribir en el registro de configuración, puede configurar el módulo enchufable. Al leer el registro de datos, puede obtener los datos del módulo enchufable. Se mostrarán los datos leídos en OLED, los datos superiores son el número de identificación del dispositivo. El número de identificación de este MPU6050 está fijo en 0x68. A continuación, los tres de la izquierda son los datos de salida del sensor de aceleración, que son la aceleración del eje X, el eje Y y el eje Z respectivamente. Los tres de la derecha son los datos de salida del sensor giroscópico. que son la velocidad angular del eje X, el eje Y y el eje Z.
SCL está conectado al pin PB10 de STM32 y SDA está conectado al pin PB11. Dado que aquí se implementa el cambio de nivel de software, se pueden conectar dos puertos GPIO a voluntad.

3.2 Resultados de la operación

Imagen_20240406_155156.jpg

3.3 Flujo de código

STM32 es el host y MPU6050 es el esclavo, que es un modo maestro-esclavo.

  1. Establecer los módulos .c y .h de la capa de comunicación I2C
    1. Escriba la inicialización GPIO subyacente de I2C
    2. 6 unidades de temporización básicas: inicio, fin, enviar un byte, recibir un byte, enviar respuesta, recibir respuesta
  2. Cree los módulos .c y .h de MPU6050
    1. Basado en el módulo de comunicación I2C, implementa lectura en la dirección especificada, escritura en la dirección especificada, escritura de registros para configurar el chip y lectura de registros para obtener datos del sensor.
  3. C Principal
    1. Llame al módulo MPU6050, inicialícelo, obtenga los datos y muestre los datos

3.4 Código

  1. Código 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. Código 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. Periféricos I2C

4.1 Introducción a los periféricos I2C

  • STM32 integra un circuito transceptor I2C de hardware, que puede realizar automáticamente funciones como generación de reloj, generación de condiciones de inicio y fin, transmisión y recepción de bits de respuesta y transmisión y recepción de datos por parte del hardware, lo que reduce la carga de la CPU.
  • Admite modelo de host múltiple
  • Admite modo de dirección de 7 bits/10 bits
  • Soporta diferentes velocidades de comunicación, velocidad estándar (hasta 100 kHz), rápida (hasta 400 kHz)
  • Soporte DMA
  • Compatible con protocolo SMBus
  • Recursos I2C de hardware STM32F103C8T6: I2C1, I2C2

4.2 Diagrama de bloques I2C

imagen.png

  • A la izquierda están los pines de comunicación: SDA y SCL son utilizados por SMBus;
    Los pines derivados de periféricos generales suelen estar conectados al mundo exterior a través del modo multiplexación del puerto GPIO (consulte la tabla)
  • Lo anterior es la parte de control de datos: SDA La parte central de la transmisión y recepción de datos es el registro de datos DR (REGISTRO DE DATOS) y el registro de desplazamiento de datos. Cuando es necesario enviar datos, se puede escribir un byte de datos en el registro de datos DR. Cuando el registro de desplazamiento no tiene datos para desplazar, el valor del registro de datos se transferirá aún más al registro de desplazamiento. Durante el proceso de cambio, los siguientes datos se pueden colocar directamente en el registro de datos y esperar. Una vez que se completa el cambio de datos anterior, los siguientes datos se pueden conectar sin problemas y continuar enviándose. Cuando se transfieren datos del registro de datos al registro de desplazamiento, el bit TXE del registro de estado se establece en 1, lo que indica que el registro de transmisión está vacío.
  • Recepción: los datos de entrada se mueven desde el pin al registro de desplazamiento bit a bit. Cuando se recopila un byte de datos, los datos se transfieren del registro de desplazamiento al registro de datos en su conjunto y el indicador RXNE se establece en el. Al mismo tiempo, indica recepción. El registro no está vacío, entonces los datos se pueden leer del registro de datos. En cuanto a cuándo recibir y cuándo enviar, es necesario escribir los bits correspondientes en el registro de control para la operación. Las condiciones de inicio, condiciones de terminación, bits de respuesta, etc., se completan mediante el control de datos.
  • El comparador y el registro de direcciones se utilizan en modo esclavo.
  • SCL: El control de reloj se utiliza para controlar la línea SCL. Escriba el bit correspondiente en el registro de control del reloj y el circuito realizará la función correspondiente. Circuito lógico de control, escribir en el registro de control puede controlar todo el circuito. El estado de funcionamiento del circuito se puede conocer leyendo el registro de estado.
  • Al enviar y recibir muchos bytes, se puede utilizar DMA para mejorar la eficiencia.

4.3 Estructura básica I2C

imagen.png

  • SDA: dado que I2C es de orden superior primero, este registro de desplazamiento se desplaza hacia la izquierda. Al enviar, el bit alto se mueve primero y luego el segundo bit alto. Un reloj SCL se desplaza una vez y se desplaza 8 veces, y se pueden colocar 8 bytes en la línea SDA desde el bit alto al bit bajo. Al recibir, los datos se mueven desde la derecha a través del puerto GPIO y finalmente se mueven 8 veces y se recibe un byte. Los datos de salida se envían al puerto a través del puerto GPIO. Los datos de entrada se ingresan al registro de desplazamiento a través del puerto GPIO.
  • El puerto GPIO debe configurarse para multiplexar el modo de salida de drenaje abierto; la multiplexación significa que el estado del puerto GPIO está controlado por periféricos en el chip, y la salida de drenaje abierto es la configuración del puerto requerida por el protocolo I2C. Incluso en el modo de salida de drenaje abierto, se puede ingresar el puerto GPIO.
  • SCL: el controlador de reloj controla la línea de reloj a través de GPIO.
    imagen.png

4.4 Envíos del host

imagen.png
Cuando STM32 quiere escribir en una dirección específica, debe seguir el diagrama de secuencia de transmisión del transmisor.

  • Dirección de 7 bits: se aborda el byte después de la condición de inicio.
  • Dirección de 10 bits: los dos bytes después de la condición de inicio son direccionamiento. El primer byte es el encabezado de la trama y el contenido es el bit de bandera de 5 bits 11110 + dirección de 2 bits + 1 bit de lectura y escritura; Dirección pura de 8 bits.
  • Proceso de 7 bits: inicio, dirección esclava, respuesta, datos, respuesta, datos, respuesta... Detener
  1. Después de la inicialización, el bus pasa al estado inactivo y STM pasa al modo esclavo. Para generar una condición de inicio, STM32 necesita escribir en el registro de control (CR1), escribir 1 y luego STM32 cambia del modo esclavo al modo maestro. .

imagen.png

  1. El evento EV5 puede considerarse como un bit de bandera. SB es un bit del registro de estado, lo que indica que el estado del hardware SB = 1 significa que se ha enviado la condición de inicio.

imagen.png

  1. Luego puede enviar un byte de dirección esclava. La dirección esclava debe escribirse en el registro de datos DR. Después de escribir en DR, el circuito de hardware transferirá automáticamente el byte de dirección al registro de desplazamiento y luego transferirá la palabra El nodo es. enviado al bus I2C, y luego el hardware recibirá automáticamente la respuesta y juzgará si no hay respuesta, el hardware establecerá el indicador de falla de respuesta y luego el indicador puede solicitar una interrupción para recordárnoslo.
  2. Cuando se complete el direccionamiento, se producirá el evento EV6 y el bit de indicador ADDR será 1. Este bit de indicador indica el final de la transmisión de dirección en modo maestro.

imagen.png

  1. El evento EV8_1 significa que el indicador TxE es 1, el registro de desplazamiento está vacío y el registro de datos está vacío. Necesitamos escribir en el registro de datos DR para enviar datos. Después de escribir en DR, debido a que el registro de desplazamiento está vacío, DR. cambiará inmediatamente al turno de registro para enviar. Se producirá el evento EV8. El registro de desplazamiento no está vacío y el registro de datos está vacío, lo que significa que el registro de desplazamiento está enviando datos. Por lo tanto, el tiempo del dato 1 se genera aquí en el proceso. En este momento, los datos 2 se escribirán en el registro de datos y estarán esperando. Después de recibir el bit de respuesta, los bits de datos se transfieren al registro de desplazamiento para su transmisión. El estado en este momento es que el registro de desplazamiento no está vacío. El registro de datos está vacío, por lo que en este momento, el incidente del EV8 volvió a ocurrir.
  2. Luego se envían los datos 2, pero esta vez los siguientes datos se han escrito en el registro de datos y están esperando. Una vez que se detecta el evento EV8, se pueden escribir los siguientes datos.
  3. Una vez escritos los datos que desea enviar, no se escriben datos nuevos en el registro de datos. Cuando se completa el desplazamiento de datos actual en el registro de desplazamiento, el registro de desplazamiento está vacío y el registro de datos también está vacío. Evento EV8_2, TxE=1 significa que el registro de desplazamiento está vacío, el registro de datos está vacío, BTF: indicador de fin de transmisión de bytes, cuando se transmite, cuando se enviarán nuevos datos y el registro de datos no se ha escrito con datos nuevos. Cuando se detecta EV8_2, se puede generar la condición de terminación Stop. Para generar una condición de terminación, obviamente, debe haber bits correspondientes en el registro de control que puedan controlarse. De esta forma finaliza la secuencia de envío.

4.5 Recepción del anfitrión

imagen.png
Recepción maestra de 7 bits: inicio, dirección esclava + lectura, recibir respuesta, recibir datos, enviar respuesta... recibir datos, no respuesta, terminar

  1. Primero, escriba el bit de inicio del registro de control para generar una condición de inicio y luego espere el evento EV5 (que indica que se ha enviado la condición de inicio).
  2. Después del direccionamiento, se recibe la respuesta y se genera un evento EV6 después del final (que indica que se completa el direccionamiento).
  3. Dato 1 significa que los datos se están ingresando a través del registro de desplazamiento.
  4. EV6_1 indica que los datos aún se están desplazando. Después de recibir la respuesta, significa que el registro de desplazamiento se ha movido con éxito en un byte de datos 1. En este momento, el byte desplazado se transfiere al registro de datos en su totalidad y el indicador RxNE se establece al mismo tiempo, lo que indica que el registro de datos no está vacío, es decir, se ha recibido un byte de datos. El estado es un evento EV7, RxNE = 1. que los datos han sido recibidos Después de leer los datos, el evento ya no existe.
  5. Por supuesto, cuando los datos 1 no se han leído, los datos 2 se pueden mover directamente al registro de desplazamiento. Después de eso, se completa el desplazamiento de los datos 2, se reciben los datos 2, se genera un evento EV7, se leen los datos 2 y. el evento EV7 ya no existe.
  6. Cuando no se necesita más recepción, el registro de control de bits de respuesta ACK debe establecerse en 0 por adelantado cuando ocurre la última unidad de temporización y se establece la solicitud de condición de terminación, es decir, el evento EV7_1. Luego, no hay respuesta NA. Se dará como el bit STOP está establecido, por lo que se genera una condición de terminación.

4.6 Comparación de formas de onda de software/hardware

imagen.png

imagen.png

5. 10-2 Hardware I2C lectura y escritura MPU6050

5.1 Funciones de la biblioteca 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 Implementación de hardware I2C de lectura y escritura de MPU6050

5.2.1 Conexión de hardware

SCL está conectado al pin PB10 de STM32 y SDA está conectado al pin PB11. Dado que aquí se implementa el cambio de nivel de software, se pueden conectar dos puertos GPIO a voluntad.
El dato principal de OLED es el número de identificación del dispositivo. El número de identificación de este MPU6050 está fijo en 0x68. A continuación, los tres de la izquierda son los datos de salida del sensor de aceleración, que son la aceleración del eje X, el eje Y y el eje Z respectivamente. Los tres de la derecha son los datos de salida del sensor giroscópico. que son la velocidad angular del eje X, el eje Y y el eje Z.

5.2.2 Resultados de la operación

Imagen_20240406_172128.jpg

5.2.3 Proceso de implementación del código

  1. Configure los periféricos I2C, inicialice los periféricos I2C, reemplace MyI2C_Init
    (1) Encienda el reloj del periférico I2C y el puerto GPIO correspondiente,
    (2) Inicialice el puerto GPIO correspondiente al periférico I2C al modo de drenaje abierto multiplexado
    (3) Utilice la estructura para configurar todo el I2C
    (4) I2C_Cmd, habilitar I2C
  2. Controle circuitos periféricos, realice el tiempo de escritura en direcciones específicas y reemplace WriteReg
  3. Controle el circuito periférico para realizar el tiempo de lectura de la dirección especificada y reemplazar ReadReg

5.2.4 Código

  1. Código 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. C Principal
#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