Análise de acelerômetro, giroscópio e magnetômetro com Raspberry Pi Parte I: leituras básicas
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 RPI | Pino 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:
- A rotação em torno do eixo y resulta na aceleração gravitacional na direção x
- A rotação mostra uma velocidade angular negativa na direção y
- 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.
Como faço para capturar os dados do acelerômetro e passar simultaneamente para uma planilha no Excel sendo que os dados tem que ser passado em milissegundos?
Dentro do Raspbery não sei como, pois só vi uma com o ESP32 a tempos.