Muitas das bibliotecas voltadas ao Arduino IDE são portadas para a BluePill, o que é um grande pró à escolha deste como ambiente de trabalho com a placa. O STM32CubeIDE, por outro lado, apesar de não apresentar tantas bibliotecas voltadas a esta placa -nenhuma oficial, inclusive, visto que a placa foi desenvolvida pela comunidade e não integra o portfólio da empresa-, oferece recursos personalizados para seus microcontroladores, o que faz com que o desenvolvimento de projetos neste seja mais dinâmico e aprofundado do que no primeiro. No post BluePill com STM32CubeIDE, aprendemos a desenvolver e carregar programas utilizando este IDE. Neste, exploraremos, um pouco, suas funções voltadas às interfaces USART e IIC.
Na falta de uma biblioteca dedicada a este módulo, precisaremos compreender, em um nível mais baixo, seu funcionamento. A operação do MPU6050, cujo uso junto ao Arduino IDE é abordado no post Acelerômetros MPU-6050, MMA8452 e MPU-9250, é regida por seus registradores, explicados em https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Register-Map1.pdf. Para nossa aplicação, precisamos nos atentar a poucos. Sobre estes, tratamos abaixo:
Registrador para leitura, somente. Armazena o valor padrão 0x68. Pode ser utilizado para verificar se a comunicação foi estabelecida com sucesso.
Cada par sucessivo de registradores compõe o resultado da leitura em um eixo. Assim, a concatenação dos registradores ACCEL_XOUT_H e ACCEL_XOUT_L corresponde à leitura da aceleração no eixo X, ACCEL_YOUT_H e ACCEL_YOUT_L no Y e ACCEL_ZOUT_H e ACCEL_ZOUT_L, no Z.
Cada par sucessivo de registradores compõe o resultado da leitura em um eixo. Assim, a concatenação dos registradores GYRO_XOUT_H e GYRO_XOUT_L corresponde à leitura da velocidade angular no eixo X, GYRO_YOUT_H e GYRO_YOUT_L no Y e GYRO_ZOUT_H e GYRO_ZOUT_L, no Z.
O MPU6050 se comunica por uma interface IIC. Por isso, utilizaremos este periférico da BluePill. O STM32CubeIDE, por meio de sua HAL, sigla para “Hardware Abstraction Layer”, “Camada de Abstração de Hardware”, disponibiliza duas funções voltadas à comunicação, por meio da IIC, com dispositivos regidos por memória:
HAL_I2C_Mem_Write(I2C_HandleTypeDef * hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t * pData, uint16_t Size, uint32_t Timeout);
I2C_HandleTypeDef * hi2c
: ponteiro para a estrutura que armazena as informações sobre a interface;uint16_t DevAddress
: endereço do dispositivo com o qual a comunicação será realizada;uint16_t MemAddress
: endereço, na memória do dispositivo, em que o primeiro dado será escrito;uint16_t MemAddSize
: tamanho, em bytes, de cada célula da memória do dispositivo;uint8_t * pData
: ponteiro para, na memória do microcontrolador, os dados a serem enviados para o dispositivo;uint16_t Size
: quantidade de dados a serem enviados;uint32_t Timeout
: máximo de tempo a ser aguardado pelos sinais de confirmação do dispositivo;Esta função permite a escrita de dados na memória do dispositivo.
HAL_I2C_Mem_Read(I2C_HandleTypeDef * hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t * pData, uint16_t Size, uint32_t Timeout);
I2C_HandleTypeDef * hi2c
: ponteiro para a estrutura que armazena as informações sobre a interface;uint16_t DevAddress
: endereço do dispositivo com o qual a comunicação será realizada;uint16_t MemAddress
: endereço, na memória do dispositivo, de onde o primeiro dado será lido;uint16_t MemAddSize
: tamanho, em bytes, de cada célula da memória do dispositivo;uint8_t * pData
: ponteiro para, na memória do microcontrolador, o endereço em que os dados lidos serão armazenados;uint16_t Size
: quantidade de dados a ser lida;uint32_t Timeout
: máximo de tempo a ser aguardado pelos sinais de confirmação do dispositivo;Esta função permite a leitura de dados da memória do dispositivo.
Com estas, será possível realizar toda a comunicação necessária para a configuração e leitura das medições do sensor.
No código, encontrado em BluePillMPU6050, primeiro, declaramos uma constante com o endereço do sensor, neste módulo, 0xD0: const uint8_t mpu6050Addr = 0xD0;
. Abaixo, são definidas as estruturas a armazenar os dados lidos do sensor:
typedef struct leituraAcel { int16_t accelX; int16_t accelY; int16_t accelZ; } leituraAcel; typedef struct leituraGyro { int16_t gyroX; int16_t gyroY; int16_t gyroZ; } leituraGyro;
Cada estrutura armazena, separadamente, o valor correspondente a cada eixo. Então, definimos a primeira função personalizada, void mpu6050Init(void)
. Esta verifica se a comunicação com o sensor está adequada e, em caso positivo, prossegue com sua configuração. Para configurar o módulo, serão escritos, nos registradores previamente abordados, os valores correspondentes às configurações escolhidas. Como o acesso à memória do dispositivo não pode ser feito bit-a-bit, mas byte-a-byte, devem ser enviados valores que correspondam ao acionamento, somente, dos bits corretos. A tabela abaixo relaciona cada registrador ao estado desejado para seus bits e ao correspondente valor hexadecimal.
Registrador | Estado dos Bits | Valor Hexadecimal |
---|---|---|
WHO_AM_I | 01101000 | 68 |
PWR_MGMT_1 | 00001000 | 08 |
CONFIG | 00000110 | 06 |
ACCEL_CONFIG | 00000000 | 0 |
GYRO_CONFIG | 00000000 | 0 |
O trecho abaixo contém a definição da função.
void mpu6050Init(void) { static const uint8_t whoAmIReg = 0x75; static const uint8_t pwrMgmt1Reg = 0x6B; static const uint8_t configReg = 0x1A; static const uint8_t accelConfigReg = 0x1C; static const uint8_t gyroConfigReg = 0x1B; static unsigned char msgErro[] = "Erro na inicializacao do MPU6050"; uint8_t check; uint8_t data; HAL_I2C_Mem_Read(&hi2c1, mpu6050Addr, whoAmIReg, 1, &check, 1, 1000); if(check == 0x68) { data = 0x08; HAL_I2C_Mem_Write(&hi2c1, mpu6050Addr, pwrMgmt1Reg, 1, &data, 1, 1000); data = 0x06; HAL_I2C_Mem_Write(&hi2c1, mpu6050Addr, configReg, 1, &data, 1, 1000); data = 0x00; HAL_I2C_Mem_Write(&hi2c1, mpu6050Addr, accelConfigReg, 1, &data, 1, 1000); data = 0x00; HAL_I2C_Mem_Write(&hi2c1, mpu6050Addr, gyroConfigReg, 1, &data, 1, 1000); } else { HAL_UART_Transmit(&huart1, msgErro, sizeof(msgErro), 100); } }
A próxima função, void mpu6050ReadAccel(leituraAcel *leitura)
, lê e armazena as medições do acelerômetro. Para isso, são lidos 6 bytes a partir de accelXoutHReg
. Cada par é, então, concatenado e armazenado na componente da estrutura correspondente ao respectivo eixo.
void mpu6050ReadAccel(leituraAcel *leitura) { static const uint8_t accelXoutHReg = 0x3B; uint8_t recData[6]; HAL_I2C_Mem_Read(&hi2c1, mpu6050Addr, accelXoutHReg, 1, recData, 6, 1000); leitura->accelX = (int16_t) (recData[0] << 8 | recData [1]); leitura->accelY = (int16_t) (recData[2] << 8 | recData [3]); leitura->accelZ = (int16_t) (recData[4] << 8 | recData [5]); }
Muito similar, void mpu6050ReadGyro(leituraGyro *leitura)
lê e armazena as medições do giroscópio.
void mpu6050ReadGyro(leituraGyro *leitura) { static const uint8_t gyroXoutHReg = 0x43; uint8_t recData[6]; HAL_I2C_Mem_Read(&hi2c1, mpu6050Addr, gyroXoutHReg, 1, recData, 6, 1000); leitura->gyroX = (int16_t) (recData[0] << 8 | recData [1]); leitura->gyroY = (int16_t) (recData[2] << 8 | recData [3]); leitura->gyroZ = (int16_t) (recData[4] << 8 | recData [5]); }
Já na função principal, primeiro, são declarados o buffer para os caracteres da mensagem de saída, que é, também, inicializado como vazio, e as estruturas que receberão as medições.
uint8_t mensagem[128] = {0}; leituraAcel leituraA; leituraGyro leituraG;
Após, o sensor é inicializado e configurado pela chamada mpu6050Init();
. No loop infinito, os dados são lidos e armazenados nas estruturas, convertidos para as unidades de medidas correspondentes e formatados para, por fim, serem enviados pela interface USART.
while(1) { mpu6050ReadAccel(&leituraA); mpu6050ReadGyro(&leituraG); float accelG[3] = {leituraA.accelX / 16384.0, leituraA.accelY / 16384.0, leituraA.accelZ / 16384.0}; float gyroGpS[3] = {leituraG.gyroX / 131.0, leituraG.gyroY / 131.0, leituraG.gyroZ / 131.0}; sprintf((char *) mensagem, "a (x, y, z) [g]: %.2f %.2f %.2f | v (x, y, z) [°/s]: %.2f %.2f %.2f\r\n", accelG[0], accelG[1], accelG[2], gyroGpS[0], gyroGpS[1], gyroGpS[2]); HAL_UART_Transmit(&huart1, mensagem, sizeof(mensagem), 100); HAL_Delay(100); }
Como o microcontrolador utilizado não possui hardware para aritmética de ponto flutuante, alguns recursos que a usam são, por padrão, desabilitados. Por isso, para formatar a mensagem como no trecho acima, é necessário habilitar a passagem de números em ponto flutuante como parâmetro para a função sprintf
. Para isso, acesse Project → Properties → C/C++ Build → Settings → MCU Settings e habilite a caixa de seleção “Use float with printf from newlib-nano (-u _printf_float)”, como mostra a imagem abaixo.
Estes são conectados conforme o esquemático abaixo.
A demonstração utiliza o terminal serial do próprio IDE e pode ser vista abaixo.
Em linhas de raciocínio similares, outros sensores podem ser utilizados juntos a esta placa e ao IDE. Além disso, para o MPU6050, as funções deste post podem ser reutilizadas.
|
A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.
Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!