O módulo que vamos ver aqui eu adquiri na FilipeFlop, mas módulos semelhantes são achados em todos os botequins que vendem Arduino. É um módulo de bússola com três eixos (já explico), com interface I2C. Na hora de colocar para funcionar surgiram algumas questões interessantes (ou frustrantes, dependendo do ponto de vista).
HMC5883L ou QMC5838L
Como mostrado na foto, a placa tem a indicação HMC5883L. Porém o site da FilipeFlop aponta para um datasheet do QMC5883L. Inicialmente ignorei isto e fui direto procurar informações sobre o HMC5883L; os links que eu encontrei estão listados no final, o google não sugeriu os meus posts :(. Apesar de encontrar várias informações úteis, as primeiras tentativas de uso do módulo foram um fracasso.
Indo direto para o importante: o módulo que testei usa o QMC5883L que tem diferenças cruciais em relação ao HMC5883L:
- Sem suporte a SPI
- Endereço I2C diferente (0x0D para o QMC e 0x1E para o HMC)
- Endereço (número) dos registradores diferentes (ver adiante)
- No QMC os registradores de saída estão na ordem X low, X high, Y low, Y high, Z low e Z high. No HMC a ordem é X high, X low, Z high, Z low, Y high, Y low
- O QMC tem um registrador Set/Reset Period no qual precisa ser escrito 1
- Outras diferenças nos registradores de configuração e status
O Chip Por Dentro
O diagrama abaixo, extraído do datasheet, dá uma ideia de como a coisa funciona. Temos três sensores magnéticos em posições ortogonais (ou seja, alinhados com três eixos). Um conversor analógico digital (ADC) ira converter a medida da intensidade magnética em um número, que é salvo nos registradores. Embora sejam três sensores, o chip tem só um ADC e um mux seleciona um sensor por vez (tudo isto é transparente para quem usa o chip).
Como dito, o mapeamento dos registradores é diferente entre o QMC e o HMC:
Obtendo a Direção do Norte
Os sensores fornecem um vetor que aponta para o Norte magnético da Terra. Para converter isto em uma direção (como a agulha de uma bússola tradicional) é preciso considerar como estão alinhados os três eixos do chip. Para facilitar, a placa tem impressa a direção dos eixos X e Y (o eixo Z aponta da placa para cima). Supondo que a placa está paralela ao chão, vamos desprezar o eixo Z e usar a função arco tangente para determinar a direção do Norte magnético em relação ao eixo X indicado na placa.
Fazendo uns testes, reparei que os ângulos obtidos estavam meio estranhos. Listando os valores obtidos para X e Y à medida que girava o sensor, descobri que as faixas de valores não estão centradas em zero e são diferentes entre os dois eixos. Um processo de calibração (inspirado em informações encontradas na internet) determina as faixas e calcula offsets e escalas para deixar os valores centrados em zero e com a mesma amplitude de variação. O resultado é razoável, mas existe ainda uma não linearidade que precisaria ser compensada.
Um refinamento visto em alguns dos exemplos nos links é considerar a diferença entre o polo Norte geográfico e o polo Norte magnético. Isto é dado pela declinação magnética e pode ser consultada para a sua localidade neste site.
Montagem Para Teste
A montagem é trivial: alimentação, terra, SCL e SDA. O módulo permite alimentação a 3.3V ou 5V, eu liguei no 3.3V do Arduino. Como já vimos em outros sensores, SCL e SDA são para operação a 3.3V, mas vamos abusar um pouco e ligar direto no Arduino.
Aplicação de Teste
Nos artigos listados no final tem link para várias bibliotecas para o HMC5883L. Como é meu costume, preferi não usar biblioteca e interagir direto com o chip. O exemplo abaixo identifica se é um HMC ou QMC e se adapta a isto. Inicialmente é necessário proceder a um calibração, que consiste em girar o sensor em círculo por um minuto, para determinarmos a faixa de valores retornados.
#include <Wire.h> // Classe simples para tratar a bússola class Bussola { public: typedef enum { INDEFINIDO, HMC, QMC } TIPO; Bussola(TIPO tipo); bool inicia(void); TIPO getTipo(void); void setDeclination (int graus , int mins, char dir); float leDirecao(void); void iniciaCal(); void encerraCal(); private: static const int ender_HMC = 0x1E; // endereço I2C do HMC5883 static const int regMODE_HMC = 2; // registrador de modo static const int regXH_HMC = 3; // primeiro registrador de dados static const int regST_HMC = 9; // registrador de status static const int ender_QMC = 0x0D; // endereço I2C do QMC5883 static const int regCR1_QMC = 9; // registrador de configuração static const int regSR_QMC = 11; // registador set/reset static const int regXL_QMC = 0; // primeiro registrador de dados static const int regST_QMC = 6; // registrador de status // fatores de correção determinados na calibração int16_t xMin, yMin, xMax, yMax; float escX = 1.0; float escY = 1.0; int16_t offX = 0; int16_t offY = 0; // Edereço e tipo do chip int ender; TIPO tipo; // Diferença entre o Polo Magnético e o Geográfico float declination = 0.0; // Rotina para disparar leitura dos dados void pedeDados(int regStatus, int regDados); }; Bussola bussola(Bussola::INDEFINIDO); // Iniciação do programa void setup(){ Wire.begin(); Serial.begin(115200); if (!bussola.inicia()) { Serial.println ("Nao encontrou a bussola!"); for (;;) { delay(100); } } Serial.print ("Achou bussola "); Serial.println (bussola.getTipo() == Bussola::QMC ? "QMC5883L" : "HMC5883L"); Serial.println ("Calibrando... rode o sensor em um círculo"); bussola.iniciaCal(); long tmpFim = millis()+60000L; while (millis() < tmpFim) { bussola.leDirecao(); delay(20); } bussola.encerraCal(); Serial.println ("Calibrado"); } // Laço principal void loop(){ float direcao = bussola.leDirecao(); Serial.println (direcao); delay(1000); } // Construtor Bussola::Bussola(TIPO tipo) { this->tipo = tipo; } // Inicia comunicação com a bússola bool Bussola::inicia() { if (tipo == INDEFINIDO) { // Tenta identificar o chip Wire.beginTransmission(ender_HMC); if (Wire.endTransmission() == 0) { tipo = HMC; ender = ender_HMC; } else { Wire.beginTransmission(ender_QMC); if (Wire.endTransmission() == 0) { tipo = QMC; ender = ender_QMC; } } } // Inicia o chip para modo contínuo if (tipo == HMC) { Wire.beginTransmission(ender); Wire.write(regMODE_HMC); Wire.write(0x00); Wire.endTransmission(); } else if (tipo == QMC) { Wire.beginTransmission(ender); Wire.write(regSR_QMC); Wire.write(0x01); Wire.endTransmission(); Wire.beginTransmission(ender); Wire.write(regCR1_QMC); Wire.write(0x0D); Wire.endTransmission(); } return tipo != INDEFINIDO; } // Informa o tipo de bússola Bussola::TIPO Bussola::getTipo(void) { return tipo; } // Define a declinação (correção entre o Norte magnético e o Norte geofráfico) // ver http://www.magnetic-declination.com/ void Bussola::setDeclination (int graus , int mins, char dir) { declination = (graus + mins/60.0) * PI / 180.0; if (dir == 'W') { declination = - declination; } Serial.println (declination); } // Le a direção da bússola em graus (0 a 360) em relação à marcação do eixo X // Assume que a bússola esta na horizontal float Bussola::leDirecao(void) { int16_t x, y, z; if (tipo == INDEFINIDO) { return 0.0; } // Le a intesidade do campo magnético if (tipo == HMC) { pedeDados (regST_HMC, regXH_HMC); x = Wire.read() << 8; //MSB x x |= Wire.read(); //LSB x z = Wire.read() << 8; //MSB z z |= Wire.read(); //LSB z y = Wire.read() << 8; //MSB y y |= Wire.read(); //LSB y } else if (tipo == QMC) { pedeDados(regST_QMC, regXL_QMC); x = Wire.read(); //LSB x x |= Wire.read() << 8; //MSB x y = Wire.read(); //LSB y y |= Wire.read() << 8; //MSB y z = Wire.read(); //LSB z z |= Wire.read() << 8; //MSB z } // Registra mínimo e máximo para a calibração if (x < xMin) { xMin = x; } if (xMax < x) { xMax = x; } if (y < yMin) { yMin = y; } if (yMax < y) { yMax = y; } // corrige e calcula o angulo em radianos float xC = (x - offX) * escX; float yC = (y - offY) * escY; float angulo = atan2 (yC, xC) + declination; // Garante que está entre 0 e 2*PI if (angulo < 0) { angulo += 2.0*PI; } else if (angulo > 2*PI) { angulo -= 2.0*PI; } // Converte para graus return (angulo*180.0)/PI; } void Bussola::pedeDados(int regStatus, int regDados) { // Espera ter um dado a ler do { Wire.beginTransmission(ender); Wire.write(regStatus); Wire.endTransmission(); Wire.requestFrom(ender, 1); } while ((Wire.read() & 1) == 0); Wire.beginTransmission(ender); Wire.write(regDados); Wire.endTransmission(); Wire.requestFrom(ender, 6); } // Inicia processo de calibração void Bussola::iniciaCal() { xMax = yMax = -32768; xMin = yMin = 32767; } // Encerra a calibração void Bussola::encerraCal() { Serial.print ("X: "); Serial.print (xMin); Serial.print (" - "); Serial.println (xMax); Serial.print ("Y: "); Serial.print (yMin); Serial.print (" - "); Serial.println (yMax); // Offset para centralizar leituras em zero offX = (xMax + xMin)/2; offY = (yMax + yMin)/2; // Escala para ter a mesma variação nos dois eixos int16_t varX = xMax - xMin; int16_t varY = yMax - yMin; if (varY > varX) { escY = 1.0; escX = (float) varY / varX; } else { escX = 1.0; escY = (float) varX / varY; } }
Links
Módulo na FilipeFlop:
https://www.filipeflop.com/produto/modulo-bussola-eletronica-hmc5883l/
Datasheet do HMC5883L:
https://cdn-shop.adafruit.com/datasheets/HMC5883L_3-Axis_Digital_Compass_IC.pdf
Datasheet do QMC5883L:
http://img.filipeflop.com/files/download/Datasheet-QMC5883L-1.0%20.pdf
Aviso de descontinuação do HMC5883L:
https://media.digikey.com/pdf/PCNs/Honeywell/HMC5883L,%20HMC5983%20EOL%20Update.pdf
Artigo 1:
https://www.dobitaobyte.com.br/magnetometro-hmc5883l-com-arduino-bussola/
Artigo 2:
https://www.usinainfo.com.br/blog/magnetometro-arduino-hmc5883l-projeto-pratico-com-display/
Artigo 3:
http://labdegaragem.com/profiles/blogs/tutorial-bussola-eletronica-com-hmc5883l
Biblioteca que fala em calibração:
https://github.com/jarzebski/Arduino-HMC5883L
Datasheet + Application Notes da Honeywell:
https://www.jameco.com/Jameco/Products/ProdDS/2150248.pdf
21/06/23: Por ato falho, em vários locais eu escrevi 5833 ao invés de 5883... corrigido!
2 comentários:
Parabéns pela sabedoria em divulgar de uma forma simples e completa o funcionamento desses 5883, revirei a internet sobre o assunto e você foi o único que consegui enterder. obrigado por disponibilizar um pouco do seu conhecimento.
Perfeito seu sketch!!! Fiz várias tentativas para realizar a calibração e fiz várias pesquisas, porém sem sucesso. Agradeço muito a sua contribuição.
Postar um comentário