Análise de acelerômetro, giroscópio e magnetômetro com Raspberry Pi Parte I: leituras básicas

Tempo de leitura: 10 minutes

O MPU9250 é uma poderosa unidade de medição inercial (IMU) que consiste em três sensores primários: um acelerômetro, um giroscópio e um magnetômetro. Cada sensor mede um sinal de 3 eixos na referência cartesiana x, y, z. Cada um dos 9 graus de liberdade é convertido em um sinal digital de 16 bits, que pode ser lido em diferentes velocidades dependendo do sensor. O acelerômetro é capaz de medir ± 2, ± 4, ± 8 e ± 16g a uma taxa de amostra de 1 kHz, o giroscópio pode medir ± 250°/s, ± 500°/s, ± 1000°/s e ± 2000°/s (dps ou graus por segundo) a uma taxa de amostragem de 8 kHz, e o magnetômetro pode medir ± 4800μT a 100 Hz.

Um Raspberry Pi será usado para ler a aceleração de 3 eixos MPU9250, velocidade de rotação angular de 3 eixos e fluxo magnético de 3 eixos (a página do produto MPU9250 pode ser encontrada aqui). A saída e as limitações do MPU9250 serão exploradas, o que ajudará a definir as limitações de aplicações para cada sensor. Esta é apenas a primeira entrada na série MPU9250 IMU, onde, na extensão dos artigos, aplicaremos técnicas avançadas em Python para analisar cada um dos 9 eixos da IMU e desenvolver aplicativos do mundo real para o sensor, que podem ser útil para engenheiros interessados ​​em análise de vibração, navegação, controle de veículos e muitas outras áreas.

 

Lista de peças e Ligação

Este tutorial usa dois componentes principais: Um MPU9250 9-DoF IMU e um computador Raspberry Pi. Qualquer Rapsberry Pi servirá, desde que tenha comunicação I2C e seja capaz de executar Python 3.x. Listei as peças e onde as comprei abaixo, junto com alguns outros componentes que podem tornar o acompanhamento do tutorial mais fácil:

  • Raspberry Pi 4 Computador
  • MPU9250 IMU
  • Mini ProtoBoard
  • Jumper Wires
  • Kit Raspberry Pi 4 (cabos HDMI, cabos USB, etc.)

O diagrama de ligação para MPU9250 e Raspberry Pi é fornecido abaixo (e na tabela ao lado). Na verdade, estarei usando o Rapsberry Pi 4 (apesar do diagrama indicar que é um RPi 3), no entanto, a pinagem e os protocolos são todos iguais. Observe a segunda ligação I2C (EDA/ECL) – isso é importante porque conecta o MPU6050 (acelerômetro/giroscópio) e o AK8963 (magnetômetro) à porta I2C do RPi. Isso também significa que estaremos nos comunicando com dois dispositivos I2C (mais sobre isso mais tarde).

Pino RPIPino MPU9250
3.3V (1)VCC
GND (6)GND
SDA (3)SDA
SCL (5)SCL
ECL → SCL
EDA → SDA

Raspberry Pi Porta I2C

O MPU9250 se comunicará com o Raspberry Pi usando o protocolo I2C. Para ler e gravar dados via I2C, devemos primeiro habilitar as portas I2C no RPi. A maneira como fazemos isso é usando a linha de comando ou navegando até Preferências → Configuração do Raspberry Pi. Adafruit tem um ótimo tutorial descrevendo este processo, mas uma versão resumida será fornecida abaixo usando capturas de tela da janela de configuração do RPi.

1. Preferências → Configuração do Raspberry Pi
2. Interfaces → Habilitar I2C
3. Abra a janela de comando e digite “sudo i2cdetect -y 1”

Uma vez que ambos os dispositivos MPU9250 aparecem no detector I2C na janela de comando RPi, estamos prontos para ler o MPU6050 (endereço do dispositivo 0x68) e AK8963 (endereço do dispositivo 0x0C). O motivo pelo qual precisamos de ambos os endereços é que os conectamos à mesma porta I2C, portanto, agora usamos seus endereços para controlá-los em um programa. Neste tutorial, Python será usado. Os endereços dos dispositivos podem ser encontrados em suas respectivas planilhas de dados ou testando-os individualmente, conectando-os um a um. Sabemos que o MPU6050 (acelerador/giroscópio) é 0x68 em seu datasheet, e sabemos que o AK8963 (magnetômetro) é 0x0C em seu datasheet, então o teste de fiação não é necessário.

Se você vir apenas um endereço de dispositivo, verifique novamente a fiação; e se nenhum dispositivo estiver aparecendo também verifique a fiação e certifique-se de que há alimentação para o MPU9250 e também que o I2C foi habilitado no RPi! Daqui para frente, presume-se que o MPU9250 foi conectado ao RPi e que os endereços do dispositivo são idênticos aos fornecidos acima. Claro, podemos facilmente alterar os endereços do dispositivo no código Python, portanto, se o seu dispositivo, por algum motivo, tiver endereços diferentes, o usuário precisará alterá-los nos códigos a seguir.

Por último, precisaremos aumentar a velocidade da taxa de transmissão I2C para obter a resposta mais rápida do MPU9250, o que podemos fazer inserindo o seguinte na linha de comando:

1. sudo nano /boot/config.txt
2. Adicionar linha ao lado da configuração I2C

Tudo o que estamos fazendo aqui é definir a taxa de transmissão para 1 Mbps. Isso deve nos dar uma taxa de amostragem de cerca de 400 Hz – 500 Hz (após a conversão para unidades do mundo real). Podemos alcançar uma taxa de amostragem muito maior para o giroscópio e uma taxa de amostragem ligeiramente maior para o acelerômetro, mas isso não será explorado nesta série.

 

Interface do MPU9250 com Python

Em Python, a porta I2C pode ser acessada usando uma biblioteca particular chamada ‘smbus’. O smbus é inicializado usando a seguinte rotina simples:

import smbus
bus = smbus.SMBus(1)

Usando o ‘barramento’ especificado no código acima, usaremos o documento Mapa de Registro MPU9250 para nos fornecer informações sobre como nos comunicar com o acelerômetro, giroscópio e magnetômetro (MPU6050 e AK8963). Os métodos de barramento I2C usados em Python estão fora do escopo deste tutorial, portanto, não serão descritos em grandes detalhes; portanto, o código usado para se comunicar para frente e para trás com o MPU6050 e AK8963 é fornecido abaixo sem muita descrição. Os detalhes completos e as capacidades de cada sensor são fornecidos na ficha técnica do MPU9250, onde muitas perguntas sobre os registros e valores correspondentes podem ser exploradas, se desejado.

# deve ser salvo na pasta local com o nome "mpu9250_i2c.py"
# será usado como controlador I2C e porto de função para o projeto
# consulte o datasheet e registre o mapa para obter uma explicação completa

import smbus,time

def MPU6050_start():
    # alterar a taxa de amostragem (estabilidade)
    samp_rate_div = 0 # sample rate = 8 kHz/(1+samp_rate_div)
    bus.write_byte_data(MPU6050_ADDR, SMPLRT_DIV, samp_rate_div)
    time.sleep(0.1)
    # redefinir todos os sensores
    bus.write_byte_data(MPU6050_ADDR,PWR_MGMT_1,0x00)
    time.sleep(0.1)
    # gerenciamento de energia e configurações de cristal
    bus.write_byte_data(MPU6050_ADDR, PWR_MGMT_1, 0x01)
    time.sleep(0.1)
    #Write to Configuration register
    bus.write_byte_data(MPU6050_ADDR, CONFIG, 0)
    time.sleep(0.1)
    #Escrever no registro de configuração do Gyro
    gyro_config_sel = [0b00000,0b010000,0b10000,0b11000] # byte registers
    gyro_config_vals = [250.0,500.0,1000.0,2000.0] # degrees/sec
    gyro_indx = 0
    bus.write_byte_data(MPU6050_ADDR, GYRO_CONFIG, int(gyro_config_sel[gyro_indx]))
    time.sleep(0.1)
    #Gravar no registro de configuração do Accel 
    accel_config_sel = [0b00000,0b01000,0b10000,0b11000] # byte registers
    accel_config_vals = [2.0,4.0,8.0,16.0] # g (g = 9.81 m/s^2)
    accel_indx = 0                            
    bus.write_byte_data(MPU6050_ADDR, ACCEL_CONFIG, int(accel_config_sel[accel_indx]))
    time.sleep(0.1)
    # registro de interrupção (relacionado ao estouro de dados [FIFO])
    bus.write_byte_data(MPU6050_ADDR, INT_ENABLE, 1)
    time.sleep(0.1)
    return gyro_config_vals[gyro_indx],accel_config_vals[accel_indx]
    
def read_raw_bits(register):
    #ler valores de aceleração e giroscópio
    high = bus.read_byte_data(MPU6050_ADDR, register)
    low = bus.read_byte_data(MPU6050_ADDR, register+1)

    # combinado alto e baixo para valor de bit sem sinal
    value = ((high << 8) | low)
    
    # converter para +- valor
    if(value > 32768):
        value -= 65536
    return value

def mpu6050_conv():
    #bits de aceleração brutos
    acc_x = read_raw_bits(ACCEL_XOUT_H)
    acc_y = read_raw_bits(ACCEL_YOUT_H)
    acc_z = read_raw_bits(ACCEL_ZOUT_H)

    # bits temporários brutos
##    t_val = read_raw_bits(TEMP_OUT_H) # uncomment to read temp
    
    #pedaços brutos de giroscópio
    gyro_x = read_raw_bits(GYRO_XOUT_H)
    gyro_y = read_raw_bits(GYRO_YOUT_H)
    gyro_z = read_raw_bits(GYRO_ZOUT_H)

    #converter para aceleração em ge giroscópio dps
    a_x = (acc_x/(2.0**15.0))*accel_sens
    a_y = (acc_y/(2.0**15.0))*accel_sens
    a_z = (acc_z/(2.0**15.0))*accel_sens

    w_x = (gyro_x/(2.0**15.0))*gyro_sens
    w_y = (gyro_y/(2.0**15.0))*gyro_sens
    w_z = (gyro_z/(2.0**15.0))*gyro_sens

##    temp = ((t_val)/333.87)+21.0 # descomente e adicione abaixo em troca
    return a_x,a_y,a_z,w_x,w_y,w_z

def AK8963_start():
    bus.write_byte_data(AK8963_ADDR,AK8963_CNTL,0x00)
    time.sleep(0.1)
    AK8963_bit_res = 0b0001 # 0b0001 = 16-bit
    AK8963_samp_rate = 0b0110 # 0b0010 = 8 Hz, 0b0110 = 100 Hz
    AK8963_mode = (AK8963_bit_res <<4)+AK8963_samp_rate # bit conversion
    bus.write_byte_data(AK8963_ADDR,AK8963_CNTL,AK8963_mode)
    time.sleep(0.1)
    
def AK8963_reader(register):
    # ler os valores do magnetômetro
    low = bus.read_byte_data(AK8963_ADDR, register-1)
    high = bus.read_byte_data(AK8963_ADDR, register)
    # combinado alto e baixo para valor de bit sem sinal
    value = ((high << 8) | low)
    # converter para +- valor
    if(value > 32768):
        value -= 65536
    return value

def AK8963_conv():
    # bits de magnetômetro brutos

    loop_count = 0
    while 1:
        mag_x = AK8963_reader(HXH)
        mag_y = AK8963_reader(HYH)
        mag_z = AK8963_reader(HZH)

        # a próxima linha é necessária para AK8963
        if bin(bus.read_byte_data(AK8963_ADDR,AK8963_ST2))=='0b10000':
            break
        loop_count+=1
        
    #converter para aceleração em ge giroscópio dps
    m_x = (mag_x/(2.0**15.0))*mag_sens
    m_y = (mag_y/(2.0**15.0))*mag_sens
    m_z = (mag_z/(2.0**15.0))*mag_sens

    return m_x,m_y,m_z
    
# Registros MPU6050
MPU6050_ADDR = 0x68
PWR_MGMT_1   = 0x6B
SMPLRT_DIV   = 0x19
CONFIG       = 0x1A
GYRO_CONFIG  = 0x1B
ACCEL_CONFIG = 0x1C
INT_ENABLE   = 0x38
ACCEL_XOUT_H = 0x3B
ACCEL_YOUT_H = 0x3D
ACCEL_ZOUT_H = 0x3F
TEMP_OUT_H   = 0x41
GYRO_XOUT_H  = 0x43
GYRO_YOUT_H  = 0x45
GYRO_ZOUT_H  = 0x47
#Registros AK8963
AK8963_ADDR   = 0x0C
AK8963_ST1    = 0x02
HXH          = 0x04
HYH          = 0x06
HZH          = 0x08
AK8963_ST2   = 0x09
AK8963_CNTL  = 0x0A
mag_sens = 4900.0 # sensibilidade do magnetômetro: 4800 uT

# iniciar o driver I2C
bus = smbus.SMBus(1) # iniciar comunicação com ônibus i2c
gyro_sens,accel_sens = MPU6050_start() # instantiate gyro/accel
AK8963_start() # magnetômetro instanciar

O bloco de código fornecido acima lida com a inicialização de cada sensor I2C (MPU6050 e AK8963) e também a conversão de bits para valores do mundo real (gravitação, graus por segundo e Teslas). O bloco de código deve ser salvo na pasta local com o nome ‘mpu6050_i2c.py’ – esta biblioteca será importada no exemplo abaixo. Tudo o que fazemos é chamar o script de conversão para cada sensor e temos as saídas de cada uma das nove variáveis. Simples!

O exemplo de código de uso é fornecido abaixo, junto com as leituras de amostra impressas no console Python:

# MPU6050 9-DoF Example Printout

from mpu9250_i2c import *

time.sleep(1) # delay necessary to allow mpu9250 to settle

print('recording data')
while 1:
    try:
        ax,ay,az,wx,wy,wz = mpu6050_conv() # read and convert mpu6050 data
        mx,my,mz = AK8963_conv() # read and convert AK8963 magnetometer data
    except:
        continue
    
    print('{}'.format('-'*30))
    print('accel [g]: x = {0:2.2f}, y = {1:2.2f}, z {2:2.2f}= '.format(ax,ay,az))
    print('gyro [dps]:  x = {0:2.2f}, y = {1:2.2f}, z = {2:2.2f}'.format(wx,wy,wz))
    print('mag [uT]:   x = {0:2.2f}, y = {1:2.2f}, z = {2:2.2f}'.format(mx,my,mz))
    print('{}'.format('-'*30))
    time.sleep(1)
Impressão de exemplo de MPU9250 9-DoF 

A impressão acima pode ser usada para verificar se o sensor e o código estão funcionando corretamente. O seguinte deve ser observado:

  • Na direção z, temos um valor próximo a 1, o que significa que a gravidade está agindo na direção vertical e o positivo é para baixo
  • O giroscópio está lendo valores próximos de 0 e, neste caso, não movemos o dispositivo, então eles devem estar próximos de 0
  • O magnetômetro está mostrando valores entre -10μT-40μT nas direções x, y, que é aproximadamente a aproximação da força do campo magnético da Terra na cidade de Nova York (onde as medições foram feitas).

Com esses valores verificados, podemos afirmar que os sensores MPU9250 estão funcionando e podemos iniciar nossas investigações e alguns cálculos simples!

 

Visualizando aceleração, velocidade angular e intensidade do campo magnético

Agora que podemos verificar se cada sensor está retornando valores significativos, podemos prosseguir para investigar o sensor na prática. As impressões brutas mostradas na seção anterior podem ser plotadas em função do tempo:

O código para replicar o gráfico acima é fornecido abaixo:

# MPU9250 Código de visualização simples
# Para que isso seja executado, o arquivo mpu9250_i2c precisa
# esteja na pasta local

from mpu9250_i2c import *
import smbus,time,datetime
import numpy as np
import matplotlib.pyplot as plt

plt.style.use('ggplot') # matplotlib visual style setting

time.sleep(1) # wait for mpu9250 sensor to settle

ii = 1000 # number of points
t1 = time.time() # for calculating sample rate

# prepping for visualization
mpu6050_str = ['accel-x','accel-y','accel-z','gyro-x','gyro-y','gyro-z']
AK8963_str = ['mag-x','mag-y','mag-z']
mpu6050_vec,AK8963_vec,t_vec = [],[],[]

print('recording data')
for ii in range(0,ii):
    
    try:
        ax,ay,az,wx,wy,wz = mpu6050_conv() # read and convert mpu6050 data
        mx,my,mz = AK8963_conv() # read and convert AK8963 magnetometer data
    except:
        continue
    t_vec.append(time.time()) # capture timestamp
    AK8963_vec.append([mx,my,mz])
    mpu6050_vec.append([ax,ay,az,wx,wy,wz])

print('sample rate accel: {} Hz'.format(ii/(time.time()-t1))) # print the sample rate
t_vec = np.subtract(t_vec,t_vec[0])

# plot the resulting data in 3-subplots, with each data axis
fig,axs = plt.subplots(3,1,figsize=(12,7),sharex=True)
cmap = plt.cm.Set1

ax = axs[0] # plot accelerometer data
for zz in range(0,np.shape(mpu6050_vec)[1]-3):
    data_vec = [ii[zz] for ii in mpu6050_vec]
    ax.plot(t_vec,data_vec,label=mpu6050_str[zz],color=cmap(zz))
ax.legend(bbox_to_anchor=(1.12,0.9))
ax.set_ylabel('Acceleration [g]',fontsize=12)

ax2 = axs[1] # plot gyroscope data
for zz in range(3,np.shape(mpu6050_vec)[1]):
    data_vec = [ii[zz] for ii in mpu6050_vec]
    ax2.plot(t_vec,data_vec,label=mpu6050_str[zz],color=cmap(zz))
ax2.legend(bbox_to_anchor=(1.12,0.9))
ax2.set_ylabel('Angular Vel. [dps]',fontsize=12)

ax3 = axs[2] # plot magnetometer data
for zz in range(0,np.shape(AK8963_vec)[1]):
    data_vec = [ii[zz] for ii in AK8963_vec]
    ax3.plot(t_vec,data_vec,label=AK8963_str[zz],color=cmap(zz+6))
ax3.legend(bbox_to_anchor=(1.12,0.9))
ax3.set_ylabel('Magn. Field [μT]',fontsize=12)
ax3.set_xlabel('Time [s]',fontsize=14)

fig.align_ylabels(axs)
plt.show()

Também podemos começar a explorar os sensores girando o dispositivo. Por exemplo, se virarmos o sensor de lado de modo que a direção x esteja apontada para cima, podemos visualizar como cada um dos 9 graus de liberdade responde:

Uma visualização GIF 3D em tempo real é mostrada abaixo, onde cada eixo pode ser rastreado. É uma ótima ferramenta de visualização para entender como cada eixo se comporta sob rotações específicas:

Algumas observações podem ser feitas sobre o comportamento do gráfico acima:

  1. A rotação em torno do eixo y resulta na aceleração gravitacional na direção x
  2. A rotação mostra uma velocidade angular negativa na direção y
  3. O campo magnético muda das direções x e y para as direções y e z

Nos próximos tutoriais, explorarei o acelerômetro, o giroscópio e o magnetômetro individualmente, bem como a fusão de todos os três para criar relacionamentos significativos com a engenharia do mundo real.

Conclusão e Continuação

Este tutorial apresentou o acelerômetro, giroscópio e magnetômetro MPU9250 e como usar o computador Raspberry Pi para se comunicar com o dispositivo. Usando o protocolo I2C, pudemos ler 9 variáveis diferentes, uma para cada um dos três eixos cartesianos de cada um dos três sensores. Em seguida, cada uma das 9 variáveis foi visualizada e plotada para um determinado movimento. No exemplo específico usado acima, demonstrei o comportamento de cada sensor sob uma determinada rotação do eixo, onde aprendemos como cada sensor respondeu. As magnitudes de cada sensor são importantes e fornecem informações sobre aplicações do mundo real e, nos próximos tutoriais, o acelerômetro, giroscópio e magnetômetro serão explorados individualmente em grandes extensões, a fim de fornecer um sistema de fusão de sensores totalmente funcional capaz de reproduzir movimentos físicos e traduções no espaço tridimensional.