Compartilhamento de tecnologia

STM32-I2C

2024-07-12

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

Este conteúdo é baseado emTecnologia Jiangxie STM32Compilado após estudo de vídeo.

1. Comunicação I2C

1.1 Introdução à comunicação I2C

  • I2C (Inter IC Bus) é um barramento de dados universal desenvolvido pela Philips
  • Duas linhas de comunicação: linha de relógio serial SCL (Serial Clock), linha de dados serial SDA (Serial Data)
  • Síncrono, half-duplex, single-ended, vários dispositivos
  • Responder com dados
  • Suporta montagem de vários dispositivos no barramento (um mestre e vários escravos, vários mestres e vários escravos)
    • Um mestre, vários escravos: O microcontrolador serve como host e domina a operação do barramento I2C. Todos os módulos externos montados no barramento I2C são escravos. Os escravos só podem controlar o barramento I2C após serem nomeados pelo host e não podem tocá-lo. sem permissão. Barramento I2C para evitar conflitos.
    • Multimestre e multiescravo: Qualquer módulo no barramento pode saltar ativamente e atuar como mestre. Quando ocorre um conflito de barramento, o protocolo I2C conduzirá a arbitragem. A parte vencedora da arbitragem obtém o controle do barramento e a parte perdedora torna-se automaticamente escrava.

imagem.png

1.2 Circuito de hardware

  • O SCL de todos os dispositivos I2C está conectado entre si e o SDA está conectado entre si.
  • Tanto o SCL quanto o SDA do dispositivo devem ser configurados no modo de saída de dreno aberto.
  • Adicione um resistor pull-up a cada SCL e SDA, o valor da resistência é geralmente cerca de 4,7KΩ

figura 1Figura 2
imagem.png

  • Um mestre e vários escravos: A CPU é um microcomputador de chip único. Como mestre do barramento, inclui controle total da linha SCL. O mestre tem controle total da linha SCL. Além disso, no estado inativo, o host pode iniciar ativamente o controle do SDA. Somente quando o escravo enviar dados e o escravo responder, o host transferirá o controle do SDA para o escravo.
  • O IC controlado é um escravo montado no barramento I2C, que pode ser um sensor de atitude, OLED, memória, módulo de relógio, etc. A potência do escravo é relativamente pequena. Para a linha de relógio SCL, ele só pode ler passivamente a qualquer momento. O escravo não tem permissão para controlar a linha SCL. Para a linha de dados SDA, o escravo não tem permissão para iniciar ativamente o controle do SDA. Somente depois que o mestre enviar um comando para ler o escravo, ou quando o escravo responder, o escravo poderá obter brevemente o controle do SDA.
  • Figura 2: SCL à esquerda e SDA à direita. Todos os dados podem ser inseridos através de um buffer de dados ou de um gatilho Schmitt.
    • Como a entrada não tem efeito no circuito, qualquer dispositivo pode receber entrada a qualquer momento.
    • A saída usa uma configuração de saída de dreno aberto. Quando a saída está baixa, a chave está ligada e o pino está diretamente conectado ao terra, o que é um forte pull-down quando a saída está alta, a chave está desligada e o pino; não está conectado a nada e está em um estado flutuante, de modo que todos os dispositivos só podem produzir nível baixo, mas não nível alto. Para evitar flutuação causada por nível alto, SCL e SDA precisam ter um resistor pull-up externo. o barramento, através de um resistor puxado para um nível alto, então é um pull-up fraco. Desta forma, em primeiro lugar, elimina completamente o fenômeno de curto-circuito de potência e garante a segurança do circuito, em segundo lugar, evita a troca frequente de modos de pino; No modo de drenagem aberta, a saída de um nível alto equivale a desconectar o pino, para que você possa emitir um nível alto diretamente antes de inserir. Terceiro, este modo tem um fenômeno "E com fio", desde que um ou mais dispositivos produzam um nível baixo, o barramento estará em um nível baixo. Somente quando todos os dispositivos emitirem um nível alto, o barramento estará em um nível alto. . Portanto, I2C pode aproveitar esse fenômeno para realizar sincronização de clock e arbitragem de barramento no modo multimestre. Portanto, embora o SCL aqui possa usar a saída push-pull em um modo mestre e vários escravos, ele ainda usa o modo de saída open-drain mais pull-out.

1.3 Unidade básica de temporização I2C

1.3.1 Condições iniciais e condições finais

  • condição inicial: Durante o nível alto do SCL, o SDA muda do nível alto para o nível baixo
  • Condição de rescisão: Durante o nível alto do SCL, o SDA muda do nível baixo para o nível alto

imagem.png

  • sob condições iniciais : Quando o barramento I2C está no estado inativo, tanto o SCL quanto o SDA estão em um estado de alto nível, ou seja, nenhum dispositivo toca o SCL e o SCL e o SDA são puxados para um nível alto por resistores pull-up externos e o barramento é. em um estado silencioso. Quando o host precisa enviar e receber dados, ele deve primeiro quebrar o silêncio do barramento e gerar uma condição de partida, ou seja, o SCL está em um nível alto sem tocá-lo, e então puxar o SDA para baixo para gerar uma borda descendente. Quando o escravo captura o sinal de alto nível do SCL e o sinal de borda descendente do SDA, ele se reinicializa e aguarda a chamada do mestre. Após a borda descendente do SDA, o host precisa baixar novamente o SCL. Por um lado, ele ocupa o barramento e, por outro lado, também serve para facilitar a emenda da unidade básica. Será garantido posteriormente que, exceto nas condições de partida e parada, o SCL de cada unidade sequencial começa com nível baixo e termina com nível baixo.
  • No estado de condição de rescisão : SCL solta primeiro e rebate para nível alto, então SDA solta e rebate para nível alto, gerando uma borda ascendente, que aciona a condição de encerramento. Após a condição de término simultâneo, tanto o SCL quanto o SDA ficam altos e retornam ao estado inicial de calma.
    O início e a parada são gerados pelo host e o escravo não tem permissão para gerar início e parada. Portanto, quando o barramento estiver ocioso, o escravo deve sempre soltar as mãos e não pode pular e tocar no barramento.

1.3.2 Enviar um byte

  • Envie um byte: Durante o nível baixo de SCL, o host coloca os bits de dados na linha SDA em sequência (bit alto primeiro) e então libera o SCL. O escravo lerá os bits de dados durante o nível alto de SCL, portanto, SDA não é permitido. para ter quaisquer dados durante o alto nível de SCL. Quando os dados forem alterados, repita o processo acima 8 vezes para enviar um byte.

O host de baixo nível coloca os dados e o escravo de alto nível lê os dados.
imagem.png
Após a condição inicial, o primeiro byte também deve ser enviado pelo host. Quando o SCL está baixo, se o host quiser enviar 0, ele puxa o SDA para baixo; se quiser enviar 1, ele o libera e o SDA retorna para o nível alto; Durante o nível baixo do SCL, o nível do SDA pode mudar. Depois que os dados são colocados, o host libera a linha do relógio e o SCL volta para o nível alto. Durante o nível alto, é o momento em que o escravo lê o SDA, portanto, durante o nível alto, o SDA não pode ser alterado. Depois que o SCL estiver em um nível alto, o escravo precisa ler o SDA o mais rápido possível. Geralmente, o escravo concluiu a leitura na borda ascendente do SCL. Como o relógio é controlado pelo mestre, o escravo não sabe quando ocorre a borda descendente, então o escravo lerá os dados na borda ascendente do SCL. Depois que o host abandonar o SCL por um período de tempo, ele poderá continuar a reduzir o SCL e transmitir o próximo bit. O host também precisa colocar os dados no SDA o mais rápido possível após a queda do SCL. Mas o host tem controle sobre o relógio, então ele só precisa colocar dados no SDA a qualquer momento quando o nível baixo estiver baixo. Após a liberação dos dados, o host libera o SCL novamente, o SCL está alto e o escravo lê esse bit. Faça um loop neste processo: o host puxa o SCL para baixo, coloca os dados no SDA, o host libera o SCL e o escravo lê os dados do SDA. Na sincronização do SCL, o mestre transmite e o escravo recebe em sequência. Após 8 ciclos, são enviados dados de 8 bits, que equivalem a um byte.
Como é o primeiro bit de ordem superior, o primeiro bit é o bit mais alto B7 de um byte e o bit mais baixo B0 é enviado por último.

1.3.3 Receber um byte

  • receber um byte: Durante o nível baixo de SCL, o escravo coloca os bits de dados na linha SDA em sequência (bit alto primeiro) e então libera o SCL. O host lerá os bits de dados durante o nível alto de SCL, portanto, SDA não é permitido. para ter quaisquer dados durante o alto nível de SCL. Quando os dados mudam, execute o processo acima 8 vezes para receber um byte (o host precisa liberar o SDA antes de receber).

Escravo de baixo nível coloca dados, host de alto nível lê dados
imagem.png
Linha SDA: O mestre precisa liberar o SDA antes de receber. Neste momento, o escravo obtém o controle do SDA. Se o escravo precisar enviar 0, ele puxa o SDA para baixo. rebotes para alto nível. Baixo nível converte dados, alto nível lê dados. A linha sólida representa o nível controlado pelo mestre e a linha pontilhada representa o nível controlado pelo escravo. O SCL é controlado pelo host durante todo o processo, e o host SDA deve ser liberado antes de receber e entregue ao escravo para controle. Como o relógio SCL é controlado pelo host, a conversão de dados do escravo é basicamente realizada na borda descendente do SCL, e o host pode ler a qualquer momento quando o SCL estiver alto.

1.3.4 Enviar resposta e receber resposta

  • Enviar resposta: Depois de receber um byte, o host envia um bit de dados no próximo clock. Os dados 0 indicam resposta e os dados 1 indicam não resposta.
  • receber resposta: Depois que o host envia um byte, ele recebe um bit de dados no próximo clock para determinar se o escravo responde. Os dados 0 indicam resposta, os dados 1 indicam não resposta (o host precisa liberar o SDA antes de receber).

imagem.png
Ou seja, após a chamada do tempo de envio de um byte, deve ser seguido pelo tempo de chamada da resposta de recebimento, que serve para determinar se o escravo recebeu os dados que acabou de lhe ser fornecidos. Se o escravo o receber, então no bit de resposta, quando o mestre liberar o SDA, o escravo deverá imediatamente puxar o SDA para baixo e, então, durante o alto nível de SCL, o host lerá o bit de resposta. Se o bit de resposta for 0, significa que o escravo realmente o recebeu.
Ao receber um byte, você precisa chamar a resposta de envio. O propósito de enviar uma resposta é informar ao escravo se você deseja continuar enviando. Se a máquina escrava receber uma resposta do mestre após enviar um dado, a máquina escrava continuará a enviar. Se a máquina escrava não receber uma resposta da máquina mestre, a máquina escrava pensará que um dado foi enviado. foi enviado, mas a máquina mestre me ignora. Talvez o host não queira. Neste momento, o escravo liberará obedientemente o SDA e entregará o controle do SDA para evitar interferência nas operações subsequentes do host.

1.4 Temporização I2C

1.4.1 Especifique o endereço para escrever

  • Especifique o endereço para escrever
  • Para o dispositivo especificado (Endereço Escravo), grave os dados especificados (Dados) no endereço especificado (Endereço Reg) (ou seja, o endereço de registro do dispositivo especificado)

imagem.png
processo:
(1) Condições iniciais
(2) Tempo de envio de um byte - 0xD0 (endereço escravo (7 bits) + gravação (1 bit) -0) (1101 0000)
(3) Receber resposta: RA = 0 (receber a resposta do escravo)
(4) Endereço especificado: 0x19 (0001 1001)
(5) Receber resposta: RA = 0 (receber a resposta do escravo)
(6) Gravar dados especificados: 0xAA (1010 1010)
(7) Receber resposta: RA = 0
(8) Bit de parada P (condição de terminação)

  • Após a condição de início, deve ser o tempo para enviar um byte. O conteúdo do byte deve ser o endereço do escravo + bits de leitura e gravação. O endereço do escravo é de 7 bits e os bits de leitura e gravação são de 1 bit, que é exatamente. 8 bits. O envio do endereço escravo serve para determinar o objeto de comunicação, e o envio do bit de leitura e gravação serve para confirmar se deve escrever ou ler a seguir. Agora o host envia um dado. O conteúdo do byte é convertido para hexadecimal. O bit de ordem superior é 0xD0. A unidade seguinte é o bit de resposta (RA) do escravo receptor. o bit de gravação termina e o SCL é reduzido. Depois disso, o host precisa liberar o SDA, seguido pelo bit de reconhecimento RA.
  • O nível alto após o término do bit de resposta RA é gerado pelo escravo liberando o SDA. O escravo transfere o controle do SDA porque o escravo deseja trocar dados o mais rápido possível no nível baixo do SCL, a borda ascendente do SDA e. a borda descendente do SCL aconteceu quase simultaneamente.
  • Depois que a resposta for concluída, se você continuar a enviar um byte, o segundo byte poderá ser enviado para dentro do dispositivo designado. O dispositivo escravo pode definir o uso do segundo self e dos bytes subsequentes. Geralmente, o segundo byte pode ser um endereço de registro ou uma palavra de controle de instrução, etc., e o terceiro byte é o conteúdo que o host deseja gravar no endereço de registro (segundo byte).
  • P é o bit de parada.

O objetivo deste data frame é: para o dispositivo que especifica o endereço escravo 1101000, escrever o dado 0xAA em seu registrador interno no endereço 0x19.
0 significa: o host executará uma operação de gravação no tempo subsequente;
1 significa: o host realizará uma operação de leitura na sequência de temporização subsequente;

1.4.2 Leitura do endereço atual

  • Endereço atual lido
  • Para o dispositivo especificado (Endereço Escravo), leia os dados do escravo (Dados) no endereço indicado pelo ponteiro de endereço atual.

imagem.png
processo:
(1) Condições iniciais
(2) Tempo de envio de um byte - 0xD1 (endereço escravo (7 bits) + leitura (1 bit) -1) (1101 0001)
(3) Receber resposta: RA = 0 (receber a resposta do escravo)
(4) Ler dados escravos: 0x0F (0000 1111)
(7) Enviar resposta: SA = 0
(8) Bit de parada P (condição de terminação)

  • O bit de leitura e gravação é 1, indicando que a próxima operação de leitura será executada. Após a resposta do escravo (RA=0), o sentido de transmissão dos dados será invertido. O mestre deseja entregar o controle do SDA ao escravo, e o mestre chama o tempo de recebimento de um byte para realizar a operação de recebimento.
  • No segundo byte, o escravo obtém permissão do mestre e pode escrever no SCL durante o nível baixo de SCL. O mestre lê SDA durante o nível alto de SCL. Finalmente, o mestre lê em sequência durante o nível alto de SCL. 8 bits, é recebido um byte de dados enviado pelo escravo, que é 0x0F. Mas qual registro do escravo é 0x0F? No tempo de leitura, o protocolo I2C estipula que quando o host está endereçando, uma vez que o sinalizador de leitura e gravação seja definido como 1. O próximo byte mudará imediatamente para tempo de leitura. Portanto, o host começará a receber antes que tenha tempo de especificar qual registro deseja ler, portanto não há link para especificar o endereço aqui. Na máquina escrava, todos os registros são alocados em uma área linear, e haverá uma variável de ponteiro separada indicando um dos registros. Este ponteiro é padronizado para ligar, geralmente aponta para o endereço 0, e toda vez que um byte é escrito e. Após a leitura de um byte, o ponteiro aumentará automaticamente uma vez e passará para a próxima posição. Então, ao chamar o tempo de leitura do endereço atual, se o host não especificar qual endereço ler, o escravo retornará ao registro apontado pelo. valor do ponteiro atual.

1.4.3 Ler no endereço especificado

  • Especifique o endereço para ler
  • Para o dispositivo especificado (Endereço Escravo), no endereço especificado (Endereço Reg), leia os dados do escravo (Dados)

imagem.png
Comece primeiro, depois repita o início e depois pare
processo:
(1) Condições iniciais
(2) Tempo de envio de um byte - 0xD0 (endereço escravo (7 bits) + gravação (1 bit) -0) (1101 0000)
(3) Receber resposta: RA = 0 (receber a resposta do escravo)
(4) Endereço especificado: 0x19 (0001 1001)
(5) Receber resposta: RA = 0 (receber a resposta do escravo)
(6) Repita a condição inicial
(7) Tempo de envio de um byte - 0xD1 (endereço escravo (7 bits) + leitura (1 bit) -1) (1101 0001)
(8) Receber resposta: RA = 0
(9) Ler dados escravos: 0xAA (1010 1010)
(10) Enviar resposta: SA = 0
(11) Bit de parada P (condição de terminação)

  • A primeira parte é escrever no endereço especificado, mas apenas o endereço é especificado, e não há tempo para escrever; a segunda parte é ler o endereço atual, porque o endereço acabou de ser especificado, então o endereço atual lido é; ligou novamente.
  • O endereço do escravo especificado é 1101000, o sinalizador de leitura e gravação é 0 e a operação de gravação é executada. Depois que o escravo responde, outro byte (o segundo byte) é gravado para especificar o endereço 0x19. ponteiro de endereço, ou seja, após o escravo receber os dados, seu ponteiro de registro aponta para a posição 0x19.
  • Sr é uma condição de início repetida, o que equivale a iniciar um novo tempo, porque o sinalizador de leitura e gravação especificado só pode seguir o primeiro byte da condição de início, portanto, se você quiser mudar a direção de leitura e gravação, só poderá ter. outra condição inicial.
  • Então, após a condição de início, reendereça e especifique o bit do sinalizador de leitura-gravação. Neste momento, o bit do sinalizador de leitura-gravação é 1, indicando que deve ser lido. 0xAA no endereço 0x19.

2. MPU6050

2.1 Introdução ao MPU6050

  • MPU6050 é um sensor de atitude de 6 eixos que pode medir os parâmetros de aceleração e velocidade angular dos próprios eixos X, Y e Z do chip. Por meio da fusão de dados, o ângulo de atitude (ângulo de Euler) pode ser obtido posteriormente. balanceamento de veículos, aeronaves, etc. que precisam se detectar cenas de gestos.
  • Acelerômetro de 3 eixos (acelerômetro): mede a aceleração dos eixos X, Y e Z
  • Sensor giroscópio de 3 eixos (giroscópio): mede a velocidade angular dos eixos X, Y e Z

imagem.png

  • Tomando como exemplo a fuselagem da aeronave, o ângulo de Euler é o ângulo entre a fuselagem da aeronave e os três eixos iniciais.
    • aviãoO nariz da aeronave inclina para baixo ou para cima, o ângulo entre este eixo é chamadoTom
    • aviãoA fuselagem rola para a esquerda ou para a direita, o ângulo entre este eixo é chamadoRolar
    • aviãoMantenha o nível da fuselagemVire o nariz da aeronave para a esquerda ou para a direita, o ângulo entre este eixo é chamadoGuinada
    • O ângulo de Euler representa a atitude da aeronave neste momento, esteja ela inclinada para cima ou para baixo, inclinada para a esquerda ou para a direita.
  • Algoritmos comuns de fusão de dados geralmente incluem filtragem complementar, filtragem de Kalman, etc., e cálculo de atitude na navegação inercial.
  • Acelerômetro : A linha pontilhada no meio é o eixo de indução. No meio há um pequeno controle deslizante com uma certa massa que pode deslizar para a esquerda e para a direita. Quando o controle deslizante se move, ele fará com que o potenciômetro se mova. Este potenciômetro é um resistor divisor de tensão. Medindo a saída de tensão pelo potenciômetro, você pode obter o valor de aceleração do pequeno controle deslizante. Este acelerômetro é na verdade um dinamômetro de mola. De acordo com a segunda lei de Newton, F = ma. Se você quiser medir a aceleração a, você pode encontrar um objeto com massa unitária e medir a força F. É isso. Existe um acelerômetro em cada um dos eixos X, Y e Z. Os acelerômetros têm estabilidade estática, mas não estabilidade dinâmica.
  • Sensor giroscópio : No meio está uma roda giratória com uma certa massa. Quando a roda giratória gira em alta velocidade, de acordo com o princípio da conservação do momento angular, a roda giratória tem tendência a manter seu momento angular original. direção do eixo de rotação inalterada. Quando a direção do objeto externo gira, a direção do eixo de rotação interno não girará, o que produzirá um desvio angular na conexão do anel de equilíbrio. Se você colocar um potenciômetro rotativo na conexão e medir a tensão do potenciômetro, poderá obter o ângulo de rotação. O giroscópio deve ser capaz de obter o ângulo diretamente, mas o giroscópio deste MPU6050 não pode medir diretamente o ângulo. Ele mede a velocidade angular, ou seja, a velocidade angular do chip girando em torno do eixo X, eixo Y e Z. -eixo. A integral da velocidade angular é o ângulo. No entanto, quando o objeto está estacionário, o valor da velocidade angular não pode ser completamente retornado a zero devido ao ruído. que é o ângulo obtido pela integração da velocidade angular. Ele não resiste ao teste do tempo, mas esse ângulo não é problema, esteja ele estacionário ou em movimento, e não será afetado pelo movimento do objeto. Os giroscópios têm estabilidade dinâmica, não estabilidade estática.
  • De acordo com o acelerômetro, que possui estabilidade estática, mas não possui estabilidade dinâmica, o giroscópio possui estabilidade dinâmica, mas não possui estabilidade estática, portanto podemos aprender com os pontos fortes um do outro e complementar os pontos fracos um do outro. , podemos integrar estabilidade estática e dinâmica. A postura é estranha.

2.2 Parâmetros MPU6050

  • ADC de 16 bits coleta o sinal analógico do sensor, faixa de quantização: -32768 ~ 32767
  • Seleção em escala completa do acelerômetro: ±2, ±4, ±8, ±16 (g) (1g = 9,8m/s2)
  • Seleção em escala completa do giroscópio: ±250, ±500, ±1000, ±2000 (°/seg, grau/segundo, unidade de velocidade angular, quantos graus de rotação por segundo) (quanto maior a seleção em escala completa, mais ampla será a faixa de medição. Quanto menor for a faixa da escala completa, maior será a resolução da medição).
  • Filtro passa-baixa digital configurável: Um registro pode ser configurado para selecionar a filtragem passa-baixa dos dados de saída.
  • Fonte de relógio configurável
  • Divisão de frequência de amostragem configurável: A fonte do relógio pode ser dividida pelo divisor de frequência para fornecer relógios para conversão AD e outros circuitos internos. Ao controlar o coeficiente de divisão de frequência, você pode controlar a velocidade de conversão AD.
  • Endereço escravo I2C: 1101000 (AD0=0) ou 1101001 (AD0=1)
    • 110 1000 é convertido para hexadecimal, que é 0x68, então alguns dizem que o endereço escravo do MPU6050 é 0x68. Mas na comunicação I2C, os 7 bits mais altos do primeiro byte são o endereço do escravo, e o bit mais baixo é o bit de leitura e gravação. Portanto, se você acha que 0x68 é o endereço do escravo, ao enviar o primeiro byte, você deve primeiro alterá-lo. 0x68 Desloca 1 bit para a esquerda (0x68 << 1), depois lê e escreve bits bit a bit ou para cima, lê 1 e escreve 0.
    • Outro método é deslocar os dados de 0x68 para a esquerda em 1 bit (0x68 << 1) como o endereço do escravo, que é 0xD0. Neste caso, o endereço do escravo do MPU6050 é 0xD0. Neste momento, ao enviar realmente o primeiro byte, se quiser escrever, basta usar 0xD0 como primeiro byte; se quiser ler, use 0xD0 ou 0x01 (0xD0 | 0x01), ou seja, 0xD1. . Esta representação não requer uma operação de deslocamento para a esquerda, ou seja, esta representação integra os bits de leitura e escrita no endereço escravo. 0xD0 é o endereço de gravação e 0xD1 é o endereço de leitura.

2.3 Circuito de hardware

imagem.png

alfineteFunção
VCC,terrafonte de energia
SCL, SDAPino de comunicação I2C
XCL, XDAPinos de comunicação I2C do host
AD0O bit mais baixo do endereço escravo
INTSaída de sinal de interrupção
  • LDO: regulador de tensão linear de baixa queda, regulador de tensão de 3,3V.
  • SCL e SDA: São pinos de comunicação I2C. O módulo possui dois resistores pull-up de 4,7K integrados, portanto, ao fazer a fiação, basta conectar SDA e SCL diretamente à porta GPIO. .
  • XCL, XDA: Pinos de comunicação Host I2C Esses dois pinos são projetados para expandir as funções do chip. Geralmente usado para magnetômetros ou barômetros externos Quando esses chips de expansão são conectados, a interface host do MPU6050 pode acessar diretamente os dados desses chips de expansão e ler os dados desses chips de expansão no MPU6050. cálculo de atitude.
    Pino AD0: É o bit mais baixo do endereço escravo. Se estiver conectado a um nível baixo, o endereço escravo de 7 bits é 1101000; Há um resistor no diagrama de circuito, que é fracamente puxado para baixo para nível baixo por padrão, portanto, se o pino permanecer flutuando, ele estará em nível baixo. Se você quiser conectá-lo a um nível alto, poderá levar AD0 diretamente ao VCC. e puxe-o fortemente para um nível alto.
  • INT: Pino de saída de interrupção Você pode configurar alguns eventos dentro do chip para acionar a saída do pino de interrupção, como dados prontos, erro de host I2C, etc.
  • O chip também possui: detecção de queda livre, detecção de movimento, detecção de movimento zero, etc. Esses sinais podem acionar o pino INT para gerar uma transição de nível e sinais de interrupção podem ser configurados, se necessário.
  • A fonte de alimentação do chip MPU6050 é de 2,375-3,46 V, que é um dispositivo de fonte de alimentação de 3,3 V e não pode ser conectado diretamente a 5 V. Portanto, um regulador de tensão de 3,3 V é adicionado, e a tensão do terminal de entrada VCC_5V pode estar entre 3,3 V e 5 V. Em seguida, o regulador de tensão de 3,3 V emite uma tensão estável de 3,3 V para alimentar o chip, desde que o terminal de 3,3 V tenha energia. , A luz indicadora de energia acenderá.

2.4 Diagrama de blocos do MPU6050

imagem.png

  • CLKIN e CLKOUT são pinos de entrada e saída de clock, mas geralmente usamos o clock interno.
  • A parte cinza: é o sensor dentro do chip, o acelerômetro no eixo XYZ e o giroscópio no eixo XYZ.
  • Há também um sensor de temperatura integrado que pode ser usado para medir a temperatura.
  • Esses sensores são essencialmente equivalentes a resistores variáveis. Depois de dividir a tensão, eles emitem uma tensão analógica e, em seguida, realizam a conversão analógico-digital por meio do ADC. Após a conclusão da conversão, os dados desses sensores são colocados uniformemente nos dados. registro, que pode ser obtido lendo o registro de dados O valor medido pelo sensor. Todas as conversões dentro deste chip são totalmente automatizadas.
  • Cada sensor possui uma unidade de autoteste, que é usada para verificar a qualidade do chip. Quando o autoteste é iniciado, o chip simulará uma força externa exercida sobre o sensor. maior que o normal. Processo de autoteste: você pode ativar o autoteste primeiro, ler os dados, depois ativar o autoteste, ler os dados, subtrair os dois dados e os dados resultantes são chamados de resposta do autoteste. Para esta resposta de autoteste, o manual fornece uma faixa. Se estiver dentro dessa faixa, significa que não há problema com o chip.
  • Bomba de carga: É uma bomba de carga ou bomba de carga. A bomba de carga é um circuito de reforço.
  • O pino CPOUT requer um capacitor externo.
  • Registro de status de interrupção: pode controlar quais eventos internos são enviados para o pino de interrupção,
  • FIFO: Registro primeiro a entrar, primeiro a sair, que pode armazenar em cache o fluxo de dados.
  • Registro de configuração: Você pode configurar vários circuitos internos
  • Registro de sensor: Registro de dados, que armazena os dados de cada sensor.
  • Calibrado de fábrica: Isso significa que os sensores internos estão calibrados.
  • Processador de movimento digital: abreviadamente DMP, é um algoritmo de hardware para cálculo de atitude que vem dentro do chip. Ele pode ser usado para cálculo de atitude com a biblioteca DMP oficial.
  • FSYNC: Sincronização de quadros.

3. 10-1 Software I2C lendo e escrevendo MPU6050

3.1 Conexão de hardware

Através da comunicação I2C do software, leia e grave os registros dentro do chip MPU6050. Ao escrever no registro de configuração, você pode configurar o módulo plug-in. Ao ler o registro de dados, você pode obter os dados do módulo plug-in. os dados lidos serão exibidos no OLED, os dados principais são o número de ID do dispositivo. O número de ID deste MPU6050 é fixado em 0x68. Abaixo, os três à esquerda são os dados de saída do sensor de aceleração, que são a aceleração do eixo X, eixo Y e eixo Z, respectivamente. Os três à direita são os dados de saída do sensor giroscópio, que são a velocidade angular do eixo X, eixo Y e eixo Z.
SCL está conectado ao pino PB10 do STM32 e SDA está conectado ao pino PB11. Como a inversão de nível de software é implementada aqui, duas portas GPIO podem ser conectadas à vontade.

3.2 Resultados da operação

IMG_20240406_155156.jpg

3.3 Fluxo de código

STM32 é o host e MPU6050 é o escravo, que é um modo mestre-escravo.

  1. Estabeleça os módulos .c e .h da camada de comunicação I2C
    1. Escreva a inicialização GPIO subjacente do I2C
    2. 6 unidades básicas de temporização: início, fim, envio de byte, recebimento de byte, envio de resposta, recebimento de resposta
  2. Crie os módulos .c e .h do MPU6050
    1. Baseado no módulo de comunicação I2C, ele implementa a leitura no endereço especificado, a escrita no endereço especificado, a gravação de registros para configurar o chip e a leitura dos registros para obter dados do sensor.
  3. principal.c
    1. Chame o módulo MPU6050, inicialize, obtenha os dados e exiba os dados

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 Introdução aos periféricos I2C

  • STM32 integra um circuito transceptor de hardware I2C, que pode executar automaticamente funções como geração de relógio, geração de condições de início e fim, transmissão e recepção de bits de resposta e transmissão e recepção de dados pelo hardware, reduzindo a carga na CPU.
  • Suporta modelo multi-host
  • Suporta modo de endereço de 7/10 bits
  • Suporta diferentes velocidades de comunicação, velocidade padrão (até 100 kHz), rápida (até 400 kHz)
  • Suporte DMA
  • Compatível com protocolo SMBus
  • Recursos I2C de hardware STM32F103C8T6: I2C1, I2C2

4.2 Diagrama de blocos I2C

imagem.png

  • À esquerda estão os pinos de comunicação: SDA e SCL são usados ​​pelo SMBus;
    Os pinos derivados de periféricos gerais geralmente são conectados ao mundo externo através do modo multiplexação da porta GPIO (verifique a tabela)
  • A parte acima é a parte de controle de dados: SDA A parte central da transmissão e recepção de dados é o registrador de dados DR (DATA REGISTER) e o registrador de deslocamento de dados. Quando os dados precisam ser enviados, um byte de dados pode ser escrito no registrador de dados DR. Quando o registrador de deslocamento não tiver dados para deslocar, o valor do registrador de dados será posteriormente transferido para o registrador de deslocamento. Durante o processo de mudança, os próximos dados podem ser colocados diretamente no registro de dados e aguardados. Assim que a mudança de dados anterior for concluída, os próximos dados poderão ser conectados perfeitamente e continuar a ser enviados. Quando os dados são transferidos do registrador de dados para o registrador de deslocamento, o bit TXE do registrador de status é definido como 1, indicando que o registrador de transmissão está vazio.
  • Recebendo: Os dados de entrada são movidos do pino para o registrador de deslocamento bit a bit. Quando um byte de dados é coletado, os dados são transferidos do registrador de deslocamento para o registrador de dados como um todo, e o sinalizador RXNE é definido no. ao mesmo tempo, indicando a recepção O registro não está vazio, então os dados podem ser lidos do registro de dados. Quanto a quando receber e quando enviar, é necessário escrever os bits correspondentes no registro de controle para que as condições de início, condições de término, bits de resposta, etc. sejam concluídas por meio do controle de dados.
  • O comparador e o registrador de endereço são usados ​​no modo escravo.
  • SCL: O controle de clock é usado para controlar a linha SCL. Escreva o bit correspondente no registro de controle de clock e o circuito executará a função correspondente. Circuito lógico de controle, escrever no registro de controle pode controlar todo o circuito. O status de funcionamento do circuito pode ser conhecido lendo o registro de status.
  • Ao enviar e receber muitos bytes, o DMA pode ser usado para melhorar a eficiência.

4.3 Estrutura básica I2C

imagem.png

  • SDA: Como o I2C é primeiro de ordem superior, esse registrador de deslocamento se desloca para a esquerda. Ao enviar, o bit alto é movido primeiro e depois o segundo bit alto. Um clock SCL é deslocado uma vez e 8 vezes, e 8 bytes podem ser colocados na linha SDA do bit alto para o bit baixo. Ao receber, os dados são movidos pela direita através da porta GPIO e, finalmente, 8 vezes, um byte é recebido. Os dados de saída são enviados para a porta por meio da porta GPIO. Os dados de entrada são inseridos no registrador de deslocamento por meio da porta GPIO.
  • A porta GPIO precisa ser configurada para o modo de saída de drenagem aberta multiplexação significa que o status da porta GPIO é controlado por periféricos no chip, e a saída de drenagem aberta é a configuração da porta exigida pelo protocolo I2C. Mesmo no modo de saída de drenagem aberta, a porta GPIO pode ser inserida.
  • SCL: O controlador de clock controla a linha de clock por meio do GPIO.
    imagem.png

4.4 Host envia

imagem.png
Quando o STM32 deseja escrever em um endereço especificado, ele precisa seguir o diagrama de sequência de transmissão do transmissor.

  • Endereço de 7 bits: O byte após a condição inicial ser endereçada
  • Endereço de 10 bits: Os dois bytes após a condição inicial são endereçados. O primeiro byte é o cabeçalho do quadro e o conteúdo é o bit de sinalização de 5 bits 11110 + endereço de 2 bits + 1 bit de leitura-gravação; Endereço puro de 8 bits.
  • Processo de 7 bits: início, endereço escravo, resposta, dados, resposta, dados, resposta... Parar
  1. Após a inicialização, o barramento é padronizado para o estado inativo e o STM é padronizado para o modo escravo. Para gerar uma condição de partida, o STM32 precisa escrever no registro de controle (CR1), escrever 1 e, em seguida, o STM32 muda do modo escravo para o modo mestre. .

imagem.png

  1. O evento EV5 pode ser considerado como um bit de sinalização SB é um bit do registro de status, indicando que o status do hardware SB=1 significa que a condição de inicialização foi enviada.

imagem.png

  1. Então você pode enviar um byte de endereço escravo. O endereço escravo precisa ser escrito no registrador de dados DR. Depois de escrever no DR, o circuito de hardware transferirá automaticamente o byte de endereço para o registrador de deslocamento e, em seguida, transferirá a palavra O nó é. enviado para o barramento I2C e, em seguida, o hardware receberá automaticamente a resposta e julgará. Se não houver resposta, o hardware definirá o sinalizador de falha de resposta e, em seguida, o sinalizador poderá solicitar uma interrupção para nos lembrar.
  2. Quando o endereçamento for concluído, ocorrerá o evento EV6 e o ​​bit de flag ADDR será 1. Este bit de flag indica o fim da transmissão do endereço no modo mestre.

imagem.png

  1. O evento EV8_1 significa que o sinalizador TxE é 1, o registrador de deslocamento está vazio e o registrador de dados está vazio. Precisamos escrever no registrador de dados DR para enviar os dados. mudará imediatamente para o registro de deslocamento para enviar. O evento EV8 ocorrerá. O registrador de deslocamento não está vazio e o registrador de dados está vazio, o que significa que o registrador de deslocamento está enviando dados. Portanto, o tempo do dado 1 é gerado aqui no processo. Neste momento, os dados 2 serão gravados no registrador de dados e aguardarão. Após receber o bit de resposta, os bits de dados serão transferidos para o registrador de deslocamento. O estado neste momento é que o registrador de deslocamento não está vazio. o registro de dados está vazio, então, neste momento, o incidente do EV8 aconteceu novamente.
  2. Então o dado 2 está sendo enviado, mas desta vez o próximo dado foi escrito no registrador de dados e está aguardando. Assim que o evento EV8 for detectado, os próximos dados poderão ser gravados.
  3. Depois que os dados que você deseja enviar são gravados, nenhum dado novo é gravado no registrador de dados. Quando a mudança de dados atual no registrador de deslocamento é concluída, o registrador de deslocamento fica vazio e o status do registrador de dados também. Evento EV8_2, TxE = 1 significa que o registrador de deslocamento está vazio, o registrador de dados está vazio, BTF: sinalizador de fim de transmissão de byte, durante a transmissão, quando novos dados serão enviados e o registrador de dados não foi gravado com novos dados. Quando EV8_2 é detectado, a condição de término Stop pode ser gerada. Para gerar uma condição de terminação, obviamente, deve haver bits correspondentes no registrador de controle que possam ser controlados. Desta forma, a sequência de envio termina.

4.5 Recepção do anfitrião

imagem.png
Recepção mestre de 7 bits: iniciar, endereço escravo + ler, receber resposta, receber dados, enviar resposta... receber dados, não responder, encerrar

  1. Primeiro, escreva o bit inicial do registro de controle para gerar uma condição inicial e, em seguida, aguarde o evento EV5 (indicando que a condição inicial foi enviada).
  2. Após o endereçamento, a resposta é recebida e um evento EV6 é gerado após o término (indicando que o endereçamento foi concluído).
  3. Dados 1 significa que os dados estão sendo inseridos através do registrador de deslocamento.
  4. EV6_1 indica que os dados ainda estão sendo deslocados. Depois de receber a resposta, significa que o registrador de deslocamento moveu com sucesso um byte de dados 1. Neste momento, o byte deslocado é transferido para o registrador de dados como um todo, e o sinalizador RxNE é definido ao mesmo tempo, indicando O registro de dados não está vazio, ou seja, um byte de dados foi recebido. O status é um evento EV7, a leitura do registro DR limpa o evento. que os dados foram recebidos Depois de lermos os dados, o evento Não existe mais.
  5. Claro, quando os dados 1 não foram lidos, os dados 2 podem ser movidos diretamente para o registrador de deslocamento. Depois disso, a mudança dos dados 2 é concluída, os dados 2 são recebidos, um evento EV7 é gerado, os dados 2 são lidos e. o evento EV7 acabou.
  6. Quando não for necessária mais recepção, o registro de controle do bit de resposta ACK precisa ser definido como 0 antecipadamente quando ocorrer a última unidade de temporização, e a solicitação de condição de término é definida, ou seja, o evento EV7_1 Posteriormente, um NA sem resposta. será fornecido. Como o bit STOP está definido, uma condição de terminação é gerada.

4.6 Comparação de formas de onda de software/hardware

imagem.png

imagem.png

5. 10-2 Hardware I2C lê e grava MPU6050

5.1 Funções da 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 Implementação de leitura e gravação de hardware I2C MPU6050

5.2.1 Conexão de hardware

SCL está conectado ao pino PB10 do STM32 e SDA está conectado ao pino PB11. Como a inversão de nível de software é implementada aqui, duas portas GPIO podem ser conectadas à vontade.
Os dados principais do OLED são o número de ID do dispositivo. O número de ID deste MPU6050 é fixado em 0x68. Abaixo, os três à esquerda são os dados de saída do sensor de aceleração, que são a aceleração do eixo X, eixo Y e eixo Z, respectivamente. Os três à direita são os dados de saída do sensor giroscópio, que são a velocidade angular do eixo X, eixo Y e eixo Z.

5.2.2 Resultados da operação

IMG_20240406_172128.jpg

5.2.3 Processo de implementação do código

  1. Configure periféricos I2C, inicialize periféricos I2C, substitua MyI2C_Init
    (1) Ligue o relógio do periférico I2C e da porta GPIO correspondente,
    (2) Inicialize a porta GPIO correspondente ao periférico I2C para o modo de drenagem aberta multiplexado
    (3) Use a estrutura para configurar todo o I2C
    (4) I2C_Cmd, habilite I2C
  2. Controle circuitos periféricos, realize o tempo de gravação em endereços especificados e substitua WriteReg
  3. Controle o circuito periférico para realizar o tempo de leitura do endereço especificado e substitua 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. principal.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;// 接收XYZ轴的加速度值和陀螺仪值



int main(void)
{
	OLED_Init();
	MPU6050_Init();
	
	OLED_ShowString(1,1,"ID:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1, 4, ID, 2);
	
//	// 指定地址写
//	MyI2C_Start(); // 产生起始条件,开始一次传输
//	// 主机首先发送一个字节,内容时从机地址+读写位,进行寻址
//	MyI2C_SendByte(0xD0);  // 1101 000 0,0代表即将进行写入操作
//	// 发送一个字节后,要接收一下应答位,看看从机有没有收到刚才的数据
//	uint8_t Ack = MyI2C_ReceiveAck();
//	// 接收应答之后,要继续发送一个字节,写入寄存器地址
//	MyI2C_Stop();
//	
//	OLED_ShowNum(1, 1, Ack, 3);
	
//	// 指定地址读
//	uint8_t ID = MPU6050_ReadReg(0X75);// 返回值是0x68
//	OLED_ShowHexNum(1, 1, ID, 2);
	
//	// 指定地址写,需要先解除睡眠模式,否则写入无效
//	// 睡眠模式是电源管理寄存器1的这一位SLEEP控制的,把该寄存器写入0x00,解除睡眠模式
//	// 该寄存器地址是0x6B
//	MPU6050_WriteReg(0x6B, 0x00);
//	// 采样率分频寄存器,地址是0x19,值的内容是采样分频
//	MPU6050_WriteReg(0x19, 0xAA);
//	
//	uint8_t ID = MPU6050_ReadReg(0X19);
//	OLED_ShowHexNum(1, 1, ID, 2);//显示0x19地址下的内容,应该是0xAA
	
	
	
	while(1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57