Manual
do
Maker
.
com
Esse fantástico e diminuto GPS GP 635-T pode ter muitas funções e é facilmente integrado a qualquer tipo de sistema, seja micro-controladoras ou diretamente sistemas embarcados, graças a sua interface USB ou TTL. A terminação do modelo é que diz o tipo de comunicação. Nesse post discorro sobre o modelo T (de TTL); claramente o outro modelo é U (de USB), mas não trataremos muitos detalhes desse modelo, exceto a pinagem.
Uma de suas interessantes características está na possibilidade de desligamento do módulo quando não for necessário ao sistema que o integra. Porém em modo tracking consome apenas 56mA, quase nada a mais que 2 LEDs comuns e bem menos que um LED RGB.
Obviamente ninguem compra um modulo GPS para projetos de cafeteiras, já que essas não necessitam rastreamento e devido a isso um módulo GPS deve realmente ter baixo consumo, pois sua alimentação dar-se-á por bateria. Seu peso é de apenas 4g, podendo inclusive ser utilizado para rastreamento humano, prendendo-o ao vestuário tendo apenas a bateria de alimentação como maior peso, considerando a utilização de um PIC com tecnologia nanowatts.
Para conversar com esse dispositivo utiliza-se o protocolo NMEA, que constitui uma mensagem num formato específico, porém relativamente simples. Então, antes de seguirmos falando desse GPS, vejamos o protocolo que obrigatoriamente utilizaremos.
As regras de protocolo são bastante simples e as mensagens bastante curtas. O protocolo define que:
Quando solicitando que a validação seja feita, os 2 bytes após o asterisco representam a disjunção exclusiva (OR lógico) de todos os bytes entre início e fim da mensagem ($ e *). O checksum não é obrigatório exceto na utilização de outros protocolos.
O fim de linha da mensagem é um tradicional 'n', e o formato final da mensagem é algo como o exemplo a seguir:
*$GPAAM,A,A,0.10,N,WPTNME,*32*
Onde:
GP ID do Remetente (GP para uma unidade de GPS, GL para um GLONASS)
AAM Aviso de chegada
A Entrada no ciclo de chegada
A Perpendicular passed
0.10 Raio do circulo
N Milha náutica
WPTNME Nome do ponto do caminho
*32 Dados de checksum
Essa mensagem está horrível, eu sei. Mas é o suficiente para ver o formato, o entendimento e aplicação veremos mais adiante na comunicação com esse módulo.
Ele possui 6 pinos em seu conector. A posição correta para observá-lo é com os pinos no topo da caixa que o envolve e sua contagem se dá da direta para a esquerda, de forma que a numeração dos pinos vão de 6 a 1:
Todos os detalhes podem ser vistos no datasheet nesse linkGP-635T-121130.pdf, mas vou descrever a pinagem para comunicação USB e para a comunicação UART TTL.
Tirado do próprio datasheet, temos a primeira imagem mostrando a pinagem TTL, seguida pela pinagem USB:
Esse é o tipo de cabo utilizado nesse módulo GPS. No primeiro momento foi um pouco difícil para mim identificar o tipo, porque só encontrei o módulo com o cabo fora do Brasil e no Brasil só encontrei o módulo no Lab de Garagem e sem o cabo. Então pesquisei um bocado e após descobrir o modelo, comprei na MultiLógica.
Inicialmente, vamos fazer funcionar em Arduino para uma breve apreciação de seu funcionamento. No post do projeto final (ainda não vou dizer de que se trata), veremos seu funcionamento em um conjunto mais otimizado, com PIC, de forma a reduzir o consumo de energia e minimar o espaço utilizado. Dessa forma será fácil escondê-lo pelo corpo ou em qualquer itém de transporte como por exemplo a mochila do notebook. Claro que para fazer a localização será necessário uma comunicação através de algum tipo de rede, recurso que utilizaremos em outro post onde escreverei sobre um módulo GSM que também inclui um GPS integrado. Já o tenho em mãos então é um post garantido para breve. Sigamos.
A mensagem que utilizaremos é a GPRMC e seus campos são especificados na tabela acima.
Repare a saída de exemplo. Todas as tabelas utilizadas nesse post são do datasheet, muito bem exclarecido. A única coisa que você não encontrará no datasheet é a conversão do valor exibido para um formato que possa ser utilizado por exemplo no google maps. Então vamos agora entender como fazer essa conversão.
O datasheet diz que latitude tem o format ddmm.mmmmm e longitude tem o formato dddmm.mmmmm. Sul e Oeste devem ser multiplicados por -1 e após os calculos explico o porquê.
A precisão de minutos é de 7 bytes ao final, precedido por 2 bytes de graus em latitude e 3 bytes de graus em longitude, portanto especificamente para esse modelo de GPS a fórmula é separar os ultimos 7 bytes e dividir o valor por 60 (inclúi-se o ponto). O resultado é somado aos bytes dos graus, então:
2447.65027,N : (24+(47.65027/60)) = 24.7941
12100.78318,E: 121+(00.78318/60) = 121.0130
Aqui temos duas importantes questões, sendo por quê os graus de Latitude tem 2 bytes e Longitude tem 3 bytes? E, porquê Oeste e Sul devem ser multiplicados por -1?
Longitude é a medida utilizada para posicionamento baseada no meridiano de Greenwich, indo de 0 a 180 graus partindo desse ponto. Então, se temos um posicionamento à esquerda (ou Oeste) teremos um grau negativo, por isso a multiplicação por -1. Para compreender perfeitamente, imagine o globo terrestre com uma linha divisória vertical ao centro. Deslocando-o para a esquerda, graus negativos; deslocando-o para a direita, graus positivos.
Latitude mede-se a partir da linha do Equador, para Norte ou para Sul, com máxima entre -90 (ao Sul) e 90 graus (ao Norte). Para perfeito compreendimento, imagine uma linha horizontal cortando o globo terrestre ao meio, mas dessa vez não há deslocamento da linha; o movimento agora é ANGULAR.
Já temos disposta a fórmula e os conceitos. Agora vamos conectar o GPS primeiramente ao Arduino e depois ao PIC, apresentando também o código para cada uma das micro-controladoras. Invés de jogar todo o código para PIC ou Arduino, vamos fazer primeiramente a parte de código portável, que é o tratamento da mensagem capturada via TTL. Supondo que uma mensagem tenha sido mandada no formato abaixo:$GPRMC,065500.00,A,2447.65027,N,12100.78318,E,15.869,189.32,051109,,,D*57
Vamos então fazer o parsing dessa string.
Como se pode facilmente notar, o separador é vírgula, o início de mensagem é o cifrão e o verificador é o último campo, não utilizado nos nossos exemplos, mas se quiser implementar, a lógica é bastante simples. O 'D57' é o checksum, mais precisamente a contagem do número de bytes na mensagem, excluindo-se as vírgulas e 'D57'.
Vamos então criar uma função que receba a mensagem pronta e faça o parsing. Desse modo essa parte de código poderá ser utilizada em qualquer lugar porque não há nada especifico da plataforma. Então, o recebimento da mensagem que precederá essa função será feita conforme a plataforma. Por fim, essa mensagem deve ser passada adiante para um pŕoximo tratamento, como monitoramento ou post para um site que possa repassar as coordenadas para o Google Maps.
void gpsPrint(string msg){
char casulo[14][12];
int i = 0;
int position = 0;
int character = 0;
for (i=0;i<msg.size();i++){
if (msg[i] == ','){
casulo[position][character] = '';
position += 1;
character = 0;
}
else if (msg[i] != ','){
casulo[position][character] = msg[i];
character += 1;
}
}
casulo[position][character] = '';
}
Pronto! Por ser posicional, sabemos que os campos estarão sempre no mesmo lugar (ou pelo menos deveriam estar na mesma posição sempre, conforme cita o padrão). Com essa função, temos um array bi-dimensional que guarda cada valor em sua respectiva posição, ou seja, casulo[2] deve conter o status do GPS, que deve ser 'A' para uma leitura válida. Nesse caso, faremos a verificação mais adiante com um if, comparando 'A' com casulo[2] antes de seguir com os cálculos, que serão tratados em outra função.
Agora vamos tratar apenas 2 variáveis do nosso parsing; longitude e latitude. Como citado anteriormente, para saber se o posicionamento é positivo ou negativo é necessário verificar a posição geográfica indicada na string. No meu exemplo eu fiz uma classe C++ chamada nmea para testar antes de subir para Arduino e PIC, rodando diretamente no desktop utilizando a string dada como exemplo nesse post. No código final NÃO utilizaremos uma classe específica porque o objetivo é criar um código portável e para PIC utilizo o MikroC (C, não C++) para programar. Eis o código para longitude e latitude:
void nmea::LongLatCalc(int coordPos, string longOrLat)
{
char coord;
coord = *casulo[coordPos];
if ( coord == 'S' || coord == 'W'){
this->multiplier = -1;
}
else{
this->multiplier = 1;
}
string degreeStr;
string minuteStr;
int rule;
rule = longOrLat.size();
rule = rule-8;
degreeStr = longOrLat.substr(0,rule);
minuteStr = longOrLat.substr(rule);
char longLatDegreeChar[4] = {''};
char longLatMinChar[9] = {''};
short int i;
for (i=0;i<degreeStr.size();i++){
longLatDegreeChar[i] = degreeStr[i];
}
for (i=0;i<minuteStr.size();i++){
longLatMinChar[i] = minuteStr[i];
}
float posLongitudeLatitude;
posLongitudeLatitude = this->multiplier*(atof(longLatDegreeChar)+(atof(longLatMinChar)/60));
char outCharLongitudeLatitude[12] = {''};
sprintf(outCharLongitudeLatitude, "%f", posLongitudeLatitude);
strcpy(this->result, outCharLongitudeLatitude);
}
Desse código, apenas algumas variáveis serão mudadas, como por exemplo result, que é ponteiro para uma variável da classe. Também chamadas diretamente ao std:: (que está no header da classe, mas nem vou exibí-lo aqui), mas não se preocupe. No final exibirei os 2 códigos completos e limpos, por enquanto vá aprecisando cada etapa do desenvolvimento.
Então, após finalizado esse método (que será uma função no código final) bastou incluir 2 chamadas ao final do método GPSPrint, dessa forma:
this->LongLatCalc(4,casulo[5]);
cout << this->result << endl;
this->LongLatCalc(6,casulo[3]);
cout << this->result << endl;
As posições citadas no método se referem às coordenadas, como pode ser visto logo abaixo:
//0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12
//msgID,utc,status,latitude,northSouth,longitude,eastWest,speedOverGround,courseOverGround,date,magneticVar,modeIndicator,checksum
Para desenvolver em C/C++ no desktop, utilizo a IDE Code::Blocks, uma excelente IDE de fácil utilização. Se o objetivo for fazer realmente uma aplicação desktop em C++ ou um aplicativo para Arduino, então prefiro fazer em Qt utilizando QtCreator. Estas são minhas recomendações de IDE.
A ordem lógica não seria exatamente essa não fosse a necessidade de ter o dispositivo conectado à micro-controladora para fazer o tratamento do dado serial. Mas precedendo a conexão ao Arduino, vamos fazer uma análise da saída do GPS com o sniffer de barramentos da Dangerous Prototype, o Bus Pirate. Já o utilizei em outro post, sniffando UART do Raspberry.
Comunicação RS-232, UART e TTL tratam da mesma coisa; comunicação serial. O RS485 também é comunicação serial, porém possui protocolo de comunicação devido a sua utilização em rede serial. Também deve-se utilizar um CI RS485 na 'borda da rede', mas o 'lado de dentro', saindo da micro-controladora para o CI é pura comunicação serial. Existem outras características muito importantes em RS485 e sugiro ler meu post sobre redes RS485 e esse outro post onde forneço um protocolo alternativo que escrevi em C na IDE MikroC, cuja IDE tenho uma licença e recomendo a todos que pretendem desenvolver projetos em PIC com similar facilidade do Arduino.
Pendurei o GPS na tela da janela do meu apartamento pois no canto da minha sala não obtive sinal de satélite.
Através do GtkTerm fiz o monitoramento serial com o Bus Pirate e utilizei a protoboard exclusivamente para fazer a alimentação do módulo GPS. O Bus Pirate tem uma saída 3.3 e uma saída 5v, mas preferi alimentação e aterramento externo para não sobrecarregar o dispositivo. Por fim, pode parecer um passo desnecessário, afinal é uma comunicação serial! Mas garanto, se nao fosse isso, talvez eu tivesse desistido do projeto porque inicialmente fiz a leitura no Bus Pirate e quando fui fazer no Arduino, só saía sujeira no terminal. Como eu tinha visto o módulo funcionando, parei para filosofar a respeito e percebi meu erro primário; e não estava utilizando um aterramento comum para a micro-controladora e para o módulo GPS. Resolvendo essa flutuação, passei a ler normalmente a saída.
Conforme o datasheet, temos 2 fios que não são obrigatórios, sendo o quinto e o sexto. O quinto é a alimentação de backup do dispositivo. Fundamental se você pretende mantê-lo funcionando ao fim da carga da alimentação principal ou seja lá qual for o motivo do corte da alimentação principal.
O sexto fio é um controle de estado e controlado através de uma saída digital da micro-controladora. Quando em HIGH ele mantém o dispositivo ligado, quando em LOW ele põe o dispositivo em stand by. Estando desconectado ele se mantém em HIGH, que é o estado necessário para esse teste, portanto mantive o fio desconectado.
Tendo coletado uma quantidade de dados, pude ver todos os formatos das mensagens NMEA, como pode ser visto no screenshot do GtkTerm. A utilizada pelo nosso programa é a $GPRMC, então copiei a mensagem original e testei no desktop mesmo.
A parte fantástica vem agora: A precisão! É inacreditável a precisão desse módulo! No primeiro momento tomei um susto, pois inverti Longitude com Latitude e fui parar no meio do oceano Atlantico, mas após o susto, inverti as coordenadas convertidas pelo programa e voilá! Só faltou exibir a janela do meu apartamento!
Passei por todas essas etapas de pesquisa, fui escrevendo esse post conforme tempo e evolução e finalmente cheguei ao esperado momento - vê-lo funcionar com Arduino.
O código abaixo está otimizado para Arduino Leonardo. Em seguida ao código farei alguns comentários breves.
String serialData = "";
char msg[80];
int i;
char letra[1];
char casulo[14][12];
int multiplier;
char outCharLongitudeLatitude[12] = {''};
void setup(){
Serial.begin(9600);
Serial1.begin(9600);
pinMode(7,OUTPUT);
digitalWrite(7,HIGH);
}
void loop(){
for (i=0;i<80;i++){
msg[i] = '';
}
i = 0;
//necessario com leonardo
while (!Serial) {
;
}
while (!Serial1.find("GPRMC,")){
;
}
Serial1.readBytesUntil('n',msg,78);
String tamanho = msg;
delay(900);
gpsPrint();
}
void gpsPrint()
{
unsigned int i = 0;
int position = 0;
int character = 0;
String count = msg;
for (i=0;i<count.length();i++){
if (msg[i] == ','){
casulo[position][character] = '';
position += 1;
character = 0;
}
else if (msg[i] != ','){
casulo[position][character] = msg[i];
character += 1;
}
}
casulo[position][character] = '';
char is_ready = *casulo[1];
if (is_ready == 'A'){
LongLatCalc(5,casulo[2]);
String link = "https://maps.google.com/maps?q=";
link += outCharLongitudeLatitude;
LongLatCalc(3,casulo[4]);
link += outCharLongitudeLatitude;
Serial.println(link);
//https://maps.google.com/maps?q=xx.xxxxxx,+x.xxxxxx
}
}
void LongLatCalc(int coordPos, String longOrLat)
{
char coord;
coord = *casulo[coordPos];
if ( coord == 'S' || coord == 'W'){
multiplier = -1;
}
else{
multiplier = 1;
}
String degreeStr;
String minuteStr;
int rule;
rule = longOrLat.length();
rule = rule-8;
degreeStr = longOrLat.substring(0,rule);
minuteStr = longOrLat.substring(rule);
char longLatDegreeChar[4] = {''};
char longLatMinChar[9] = {''};
short int i;
for (i=0;i<degreeStr.length();i++){
longLatDegreeChar[i] = degreeStr[i];
}
for (i=0;i<minuteStr.length();i++){
longLatMinChar[i] = minuteStr[i];
}
float posLongitudeLatitude;
posLongitudeLatitude = multiplier*(atof(longLatDegreeChar)+(atof(longLatMinChar)/60));
dtostrf(posLongitudeLatitude,3,6,outCharLongitudeLatitude);
}
Agora vamos falar do código.
Primeiramente, em setup() temos duas seriais inicializadas. O Arduino Leonardo possui uma serial para a USB e uma serial nos pinos de I/O, sendo o pino 0 e 1, tal como no Arduino UNO.
Não vou ensinar C para que fique claro esse código, mas algumas coisas posso citar superficialmente. Por exemplo a leitura serial (que obviamente será diferente em PIC). Há diversas maneiras de fazer a leitura serial, sendo que fiz 5 códigos diferentes e achei esse o mais adequado. Se você pretende executar esse processo, recomendo a utilização da leitura serial tal como fiz, mas tenha em mente que nesse caso as demais mensagens estão perdidas.
Na função gpsPrint() tem um if onde faço comparação de um byte diretamente com o comparador '=='. Isso é totalmente plausível considerando que char nada mais é que um tipo de int. Em seguida, uso um tipo string que como vantagem tem a possibilidade de concatenar dados sem interações complexas. Nesse caso nem experimentei utilizar strcat() (e nem sei se está disponível para Arduino).
As duas partes mais lascadas de ruim foram as conversões de array de char para float e float para string. O Arduino tem a função float(), mas essa droga não funcionou a contento e perdi bastante tempo até perceber que poderia ter utilizado atof() tal como fiz na versão desktop.
O último agoro foi a conversão de float para string, sendo que esse post tem essas duas jóias intrinsicas; coloque em favoritos esse link, porque o dia que você precisar fazer isso te garanto que será um parto sem essas referências.
Primeiramente tentei o que qualquer programador C tentaria; strcpy, mas essa função não funcionou e passei algum tempo a debugar. Depois de um tempo de sofrimento, voltei a exercitar meditação e lembrei que em uma época remota já havia me deparado com esse problema. Então usei o próprio google para buscar o post onde apresentei a solução, e achei fácil. Sugiro que quando tiver uma dúvida de Arduino ou PIC, adicione "dobitaobyte" à sua pesquisa no Google.
A solução foi utilizar ** dtostrf()**, onde você pode controlar dígitos à esquerda e direita, passando a origem e o buffer - ou seja, essa função recebe quatro parâmetros e funciona lindamente.
Após todos os testes deixei o código imprimindo na tela somente o link com as coordenadas de forma que basta copiar e colar no browser para ver a localização. Mas não é esse o objetivo que me levou a escrever esse post. Na verdade tive uma necessidade pessoal que envolve um desenvolvimento complexo e vou descrever cada etapa do meu desenvolvimento aqui, sendo que o produto final será desenvolvido totalmente em PIC.
Se gostou, não deixe de compartilhar; dê seu like no video e inscreva-se no nosso canal Manual do Maker Brasil no YouTube.
Próximo post a caminho!
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.