5. Driver Robusto em C para o BNO055 com Tratamento de Timeout e Recuperação de Barramento
Agora vamos estruturar uma implementação em C pensada para uso profissional em microcontroladores como STM32, RP2040, nRF52 ou similares, considerando os problemas reais de clock stretching e timeout discutidos anteriormente.
O objetivo aqui não é apenas “ler o sensor”, mas construir uma camada confiável, com retry automático, verificação de CHIP_ID e recuperação do barramento quando necessário.
Assumiremos que exista uma camada HAL mínima com as funções:
int I2C_Write(uint8_t addr, uint8_t *data, uint16_t len);
int I2C_Read(uint8_t addr, uint8_t *data, uint16_t len);
void delay_ms(uint32_t ms);
void I2C_RecoverBus(void);
Onde:
- Retorno 0 = sucesso
- Retorno diferente de 0 = erro
5.1 Endereço I2C
O BNO055 possui dois possíveis endereços:
- 0x28 (ADR = GND)
- 0x29 (ADR = VDDIO)
Definimos:
#define BNO055_ADDR 0x28
5.2 Escrita com Retry
int BNO055_WriteReg(uint8_t reg, uint8_t value)
{
uint8_t buffer[2];
buffer[0] = reg;
buffer[1] = value;
for(int retry = 0; retry < 3; retry++)
{
if(I2C_Write(BNO055_ADDR, buffer, 2) == 0)
return 0;
I2C_RecoverBus();
delay_ms(2);
}
return -1;
}
Aqui usamos três tentativas antes de declarar falha. Isso aumenta drasticamente robustez em ambientes ruidosos.
5.3 Leitura com Retry
int BNO055_ReadReg(uint8_t reg, uint8_t *data, uint16_t len)
{
for(int retry = 0; retry < 3; retry++)
{
if(I2C_Write(BNO055_ADDR, ®, 1) == 0)
{
if(I2C_Read(BNO055_ADDR, data, len) == 0)
return 0;
}
I2C_RecoverBus();
delay_ms(2);
}
return -1;
}
Esse padrão é essencial porque muitos travamentos ocorrem entre o envio do registrador e a leitura subsequente.
5.4 Inicialização Segura Completa
int BNO055_Init(void)
{
uint8_t id;
if(BNO055_ReadReg(0x00, &id, 1) != 0)
return -1;
if(id != 0xA0)
return -2;
// CONFIG MODE
if(BNO055_WriteReg(0x3D, 0x00) != 0)
return -3;
delay_ms(25);
// RESET
if(BNO055_WriteReg(0x3F, 0x20) != 0)
return -4;
delay_ms(650);
// NDOF MODE
if(BNO055_WriteReg(0x3D, 0x0C) != 0)
return -5;
delay_ms(25);
return 0;
}
Note que usamos delays conservadores. Em sistemas críticos, prefira robustez à velocidade.
5.5 Leitura de Quaternion
int BNO055_ReadQuaternion(float *qw, float *qx,
float *qy, float *qz)
{
uint8_t buffer[8];
if(BNO055_ReadReg(0x20, buffer, 8) != 0)
return -1;
int16_t raw_w = (buffer[1] << 8) | buffer[0];
int16_t raw_x = (buffer[3] << 8) | buffer[2];
int16_t raw_y = (buffer[5] << 8) | buffer[4];
int16_t raw_z = (buffer[7] << 8) | buffer[6];
*qw = raw_w / 16384.0f;
*qx = raw_x / 16384.0f;
*qy = raw_y / 16384.0f;
*qz = raw_z / 16384.0f;
return 0;
}
Observe a ordem LSB primeiro, conforme especificado no datasheet.
5.6 Conversão para Ângulos
void Quaternion_ToEuler(float qw, float qx,
float qy, float qz,
float *roll,
float *pitch,
float *yaw)
{
*roll = atan2f(2.0f*(qw*qx + qy*qz),
1.0f - 2.0f*(qx*qx + qy*qy));
*pitch = asinf(2.0f*(qw*qy - qz*qx));
*yaw = atan2f(2.0f*(qw*qz + qx*qy),
1.0f - 2.0f*(qy*qy + qz*qz));
}
5.7 Estrutura de Aplicação Principal
int main(void)
{
if(BNO055_Init() != 0)
{
// tratar erro
while(1);
}
while(1)
{
float qw, qx, qy, qz;
float roll, pitch, yaw;
if(BNO055_ReadQuaternion(&qw, &qx, &qy, &qz) == 0)
{
Quaternion_ToEuler(qw, qx, qy, qz,
&roll, &pitch, &yaw);
}
delay_ms(10);
}
}
Isso produz orientação estável a 100 Hz aproximadamente.
5.8 Considerações Profissionais Importantes
- Nunca mudar modo operacional durante leitura contínua.
- Sempre verificar calibração antes de usar dados críticos.
- Em aplicações industriais, implementar watchdog para I2C.
- Considerar uso de RTOS separando:
- Task I2C
- Task processamento
- Task aplicação
Na próxima seção vamos abordar:
- Salvamento e restauração de calibração
- Leitura de vetores de aceleração linear
- Uso para estimativa de movimento
- Limitações do BNO055 comparado a fusão externa (Madgwick/Mahony)
- Considerações para projetos críticos como robótica e navegação