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.

Caminho para habilitar a formatação de números de ponto flutuante
Estes são conectados conforme o esquemático abaixo.

Diagrama de conexões
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.
Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!