jueves, 1 de agosto de 2024

UART Recepcion GPS

 Recepción GPS con PIC16F

Hola, te doy la bienvenida por visitar este blog, creado para compartir el mutuo interés en la electrónica y en especial la programación de microcontroladores PIC, te recuerdo que estoy atento a cualquier sugerencia o critica constructiva relacionada con esta publicación, al final dejo mis datos de contacto.
Hoy vamos a ver como conectar un receptor GPS tipo OEM, al microcontrolador utilizando el puerto serial UART, una vez establecida la comunicación, revisaremos el tipo de mensajes que emite el receptor GPS y las técnicas para decodificar esta informacion en datos requeridos para una aplicación GPS.
La programación del microcontrolador se realizara utilizando el software  de diseño  MPLABX  y el compilador de lenguaje C para PIC XC8 ambos disponibles en la pagina de microchip de forma gratuita. Aquí dejo los enlaces de las versiones utilizadas para nuestro ejemplo:  <<MPLABX v6.20>>   <<XC8 v2.45>>
Mencionar también que es necesario contar con conocimientos mínimos sobre programación en lenguaje C y el uso de microcontroladores PIC16F.
 
Introducción al Tema

Dejando un poco de lado la teoría que hay detrás del sistema GPS, cuya informacion es bastante amplia en sitios de internet como Wikipedia, <Sistema GPS>, paginas del gobierno Americano <Sistema GPS> o fabricantes de receptores <Sistema GPS>, vamos a centrarnos únicamente en el segmento del usuario, donde revisaremos dos modelos de receptores GPS que tengo a mi alcance.

A manera de una introducción al tema, el Sistema de Posicionamiento Global, más conocido por sus siglas en inglés como GPS, es un sistema que permite determinar la posición de un objeto en la Tierra. De los tres segmentos que lo conforman, el segmento de usuario es aquel que corresponde a los diferentes receptores utilizados en la Tierra. Hoy en día, encontraras módulos receptores de bajo coste y muy accesibles para utilizar en una variedad de aplicaciones que no siempre se relacionan con la posición, en algunos caso su uso esta relacionado con la sincronización del tiempo. 
Los fabricantes de receptores diseñan sus chips GPS, con diversas prestaciones relacionadas con la precisión y el consumo de energía, pero en ocasiones muchas de estas características no siempre son lo mas importante, ya que por ejemplo una buena antena podría compensar ciertas carencias. Algunos de estos chips incluso tienen la capacidad de recibir señal de otros sistemas de posicionamiento Glonass o Galileo.
Los dos receptores que revisaremos, sera con el propósito de mostrar algunas diferencias entre ambos.

Receptor: NEO-6M

 Chipset U-Blox 50CH
  Frecuencia L1 C/A
 SBAS: WAAS, EGNOS, MSAS
 Sensibilidad -156dBm
 Inicio Frío 26s, Caliente 1s
 Precisión 2.5mts
 Altura Max. 50000mts
 Velocidad Max. 500m/s
 Operación 2.7 – 3.6V 50mA

Receptor RGM-3600
Chipset SIRF III 20CH
Frecuencia L1 C/A
SBAS: WAAS, EGNOS, MSAS
Sensibilidad -159dBm
Inicio Frío 35s, Caliente 1s
Precisión 10mts 2D
Altura Max.18000mts
Velocidad Max. 514m/s
Operación 5V 55mA

Dejo los siguientes enlaces con informacion adicional de estos dos receptores:

<Receptor GPS NEO>            <Receptor GPS RGM>

Cuando un receptor GPS se energiza por primera vez, el mismo no tiene la información del almanaque y las efemérides, por lo que debe proceder a la descarga de esta informacion, este proceso suele demorar varios minutos. 
  • Inicio Frio (Cold start): El receptor cuenta con un almanaque válido, pero necesita actualizar las efemérides y la referencia del tiempo,  el proceso puede demorar unos 45 segundos.
  • Inicio Caliente (Hot Start): El receptor cuenta con el almanaque y efemérides válidas, además ya posee una referencia del tiempo. Esta situación suele darse en ciudades cuando se pierde cobertura, y proceso suele demorar entre 3 y 5 segundos.

Ahora resumiremos algunas diferencias de los dos receptores, el primero que posee un Chipset U-blox cuenta con hasta 50 canales para capturar satélites a diferencial del Chipset SIRF-III que posee 20, con relación al tiempo de inicio en frío, la precisión y altura maxima de operación los números favorecen al Chipset U-blox. Pero una característica que me resulta muy practica para implementar en el ejemplo, es con el modelo RGM-3600, que opera con voltaje de 5V y además el receptor esta encapsulado con la antena para un uso externo, cuenta una base de imán para sujetarlo en cualquier superficie metálica. 

Entonces por el momento los aspectos técnico que debemos tomar en cuenta son el voltaje de alimentación que requiere el receptor, la velocidad de comunicación que utiliza por defecto y los niveles lógicos en las lineas TX - RX, en algunos casos se necesitara convertir estos niveles entre 3.3V y 5.0V.

Sin importar cual de los modelos se utilizará, el programa del PIC podrá recibir la informacion de ambos, ya que los mensajes que emiten estos receptores cumplen con un formato estándar denominado NMEA 0183. 

Información del GPS

Una vez establecida la comunicación con el receptor GPS, los mensajes se recibirán cada segundo, al menos es la configuracion por defecto que tienen la mayoría de receptores, estos mensajes cumplen con el estándar NMEA.  
<NMEA> se creó para el intercambio de información digital entre productos electrónicos marinos, el primer protocolo estándar se llamó NMEA 0183, y es el que todavía utilizan y aceptan la mayoría de los receptores del sistema GPS.
Un receptor GPS, transmite diferentes mensajes NMEA 0183, algunas de estos son:
  •     RMC - Recommend Minimum Specific GPS/TRAN Data
  •     GGA - Global Positioning System Fix Data
  •     GSA - GPS DOP and Active Satellites
  •     GSV - GPS Satellites in View
 De hecho si nos conectamos a la salida del receptor con una terminal serial, podremos ver muchos de estos mensajes de forma consecutiva, observe la figura 1. 

Fig1. Datos de salida del receptor GPS
 
El tipo y cantidad mensajes que emite un receptor GPS, depende mucho de cada fabricante, siendo en algunos casos configurable,  de toda la informacion recibida desde el receptor, solo nos centraremos en el mensaje tipo RMC que a mi parecer posee la informacion necesaria para el posicionamiento o sincronización de tiempo, la descripción de los campos para este mensaje se muestra en la figura 2.

Fig2. Mensaje RMC del protocolo NMEA

De este mensaje discutiremos los siguientes campos:
  • El tiempo utiliza el formato UTC, y por lo tanto debes tomarlo muy en cuenta en tu aplicación, para hacer la debida corrección con referencia a tu zona.
  • El campo de navegación es muy importante, ya que el valor los datos de posicionamiento son validos únicamente si este campo tiene la letra 'A'
  • Posterior al asterisco '*' el receptor envía un numero en formato hexadecimal que es la suma exclusiva correlativa de todos los bytes que tiene el mensaje, esto permite realizar una verificación básica del mensaje enviado y el recibido, una diferencia representaría un error de comunicación.
 
Esquema del Circuito GPS     
El esquema del circuito que implementaremos se muestra en la figura 3, como mencione antes, utilizare un GPS RGM-3600 fabricado por Royaltek. En el esquema notara que en el receptor GPS solo conectamos el pin de transmisión al pin de recepción RXD del PIC, el otro pin no sera necesario porque no se llevaran a cabo configuraciones al GPS. Por defecto la mayoría de estos receptores trabajan a [4800 8N1], esto es 4800 baudios, con longitud de 8 bits, sin paridad y un único bit de parada. 
Por otro lado en el PIC utilizaremos el pin de transmisión TXD para enviar mensajes decodificados a la computadora, utilizando un convertidor TTL-USB como el CP2102.

Fig3. Esquema de Circuito GPS RGM-3600

Cuando el receptor GPS y el PIC trabajan con diferentes voltajes de alimentación, por ejemplo 3.3V del GPS y 5.0V del PIC, podría necesitar adaptar los niveles de voltaje en las lineas TXD y RXD, es importante que revise las especificaciones eléctricas VIH/VOL y VOH/VIL de ambos dispositivos. En algún caso tuve que usar un transistor para incrementar el nivel de tensión en la salida del GPS.

El diodo LED conectado al pin RA4 se utilizara para indicar el funcionamiento normal del programa mediante un destello cada segundo.

 
Programación del PIC16F

Muy bien, en este punto toca describir el programa que recibirá la informacion del GPS para lo cual vamos abordar dos enfoques diferentes, considerando las limitaciones que existe en el PIC16F687(ROM 2K y 128 bytes de RAM), para ambos casos la frecuencia del oscilador es Fosc=8MHz. La selección de este PIC es precisamente con el fin de mostrar que un enfoque adecuado en la programación permite optimizar el uso de los recursos. 

Una vez energizado y con las conexiones debidamente establecidas, utilice un pequeño programa que replica cada byte recibido por el receptor RXD al transmisor TXD del PIC16F687, y con una terminal serial en mi CUTECOM, ver figura 4, se logra recibir los siguientes mensajes repetidos cada segundo.

Fig4. Recepción GPS en terminal CUTECOM

Como verán de acuerdo a la figura 4, se recibe bastante informacion del GPS cada segundo, pero no todo es necesariamente útil en una aplicación por esa razón es que trabajaremos con el mensaje RMC y es aquí donde quiero considerar las dos opciones para conseguir los datos que necesitamos:

Primera Opción: Se debe recuperar solo el mensaje RMC, esto es posible si el programa receptor que recibe cada palabra identifica el inicio de un mensaje especifico, por ejemplo si los tres últimos datos recibidos corresponden con las letras <M><C>< , >, podemos afirmar que se trata del mensaje RMC. Entonces una vez identificado el inicio del mensaje, el programa receptor deberá guardar en memoria cada dato recibido hasta que el ultimo valor recibido indique el final, por ejemplo la recepción del asterisco <*> que se envía al completar un mensaje RMC. A fin de mostrar el resultado de esta opción, he creado un programa receptor para guardar el mensaje, y luego enviarlo a la terminal serial, ver la figura 5.

Fig5. Recepción del mensaje RMC en CUTECOM

Algo importante con este método es que debemos considerar la cantidad memoria RAM necesaria para almacenar el mensaje, esto es aproximadamente 64 bytes, la mitad de la memoria disponible en un PIC16F687
 
GPRMC,215536.000,A,1744.7111,S,06308.9987,W,0.00,0.00,300724,,,A*5B
 
Una vez almacenado el mensaje, se procederá con la decodificación del mismo, hay varias maneras hacer esto, una es utilizar la función strtok del lenguaje C, para identificar cada coma < , > y extraer cada cadena de forma secuencial, los campos que son de interés se puede convertir en valores decimales utilizando la función atoi de lenguaje C. El programa de receptor indicara mediante banderas cuando un mensaje ha sido decodificado, esta opción se puede resumir como el método de recibir el mensaje y luego decodificar.
 
Segunda Opción: En este caso, consideramos que no se dispone de la cantidad de memoria para almacenar el mensaje, y en este entendido el programa receptor se encargara de recibir, y decodificar el mensaje RMC al mismo tiempo, lo que implica mayor uso de la memoria de programa (ROM).  
En un inicio el programa receptor identifica el inicio de mensaje RMC de la misma forma que en la primera opción, pero una vez identificado cada dato recibido se almacenara en memoria hasta recibir una coma ( , ), y dependiendo del campo que corresponde se procesara la informacion almacenada, de esta manera la informacion retenida temporalmente nunca supera los 10 bytes(RAM). Eso si, el programa receptor debe verificar en cada dato recibido todas las comas para identificar los campos en el mensaje, que son aproximadamente 11, de los cuales varios no poseen informacion. Esta opción se puede resumir como el método de recibir y decodificar al mismo tiempo. 
Luego de codificar el programa con esta ultima opción, que permite recibir y decodificar el mensaje al mismo tiempo, una vez conectado a la terminal serial, se reciben la informacion que muestra la figura 6. En este caso la informacion ya tiene un formato establecido a partir de los campos decodificados del mensaje RMC.
El programa que se describe en la siguiente sección utiliza este método debido a que se requiere menos uso de la memoria RAM.

Fig6. Mensaje final procesado CUTECOM

La programación del microcontrolador, no es algo simple de explicar con palabras y que a la vez sea entendible,  me disculpo si me he saltado con la descripción de algunas lineas  en el código, prometo lo mas antes posible elaborar un flujo-grama y actualizar esta publicación para comprender mejor como funciona este programa.

En el programa se hace uso de las interrupciones del temporizador TMR0 y el receptor USART del PIC, cabe resaltar que ninguna de las tareas en el programa representa un proceso bloqueante, la codificación en si utiliza una técnica de programación por estados, si quieres conocer mas detalles al respecto, te recomiendo revises antes de continuar, la siguiente publicación. 
 
 
#pragma config FOSC=INTRCIO,WDTE=OFF,PWRTE=ON,BOREN=OFF,IESO=OFF    
#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#define LED1pin PORTAbits.RA4  
//Led para destello
volatile __bit tick1ms, rmcok; 
//Banderas
typedef struct
//Estructura de datos GPS
{
    uint32_t lat, lon;
    uint8_t year, month, day;
    uint8_t hour, minute, second;
    uint8_t valid;
} rmcstruct_t;
rmcstruct_t rmc;
char buffer[12];  
//Memoria de datos RAM 12 bytes
void setup(void); 
//Procedimiento de inicializacion
void taskLED(void);
//Tarea para destello LED
void taskGPS(uint8_t data);
//Tarea del Programa Receptor
void __interrupt() isr(void)
{
    uint8_t res;
    if(INTCONbits.T0IF)
//Evento cada 1.0ms
    {
        INTCONbits.T0IF = 0; 
//Limpia bandera
        TMR0 += 131;
//Reinicia contador
        tick1ms = 1;
    }
    while(PIR1bits.RCIF)
//Evento con recepcion RXD
    {
        res = RCREG;
//Recibe el dato UART
        taskGPS(res);
//Llama al programa receptor
    }
}
void main()
{
  setup();
//Inicializa el PIC
  while(1)
  {
    if(tick1ms)
//Valida cada 1ms
    {
      tick1ms = 0;
//Limpia bandera
      taskLED();
//Destello LED
      if(rmcok)
//Si hay dato GPS decodificado
      {
        rmcok = 0;
        if(rmc.valid)
//Verifica si es valido
        {
          printf("Fecha:%u/%02u/%02u ",rmc.year, rmc.month, rmc.day);
          printf("Hora:%02u:%02u:%02u\n",rmc.hour, rmc.minute, rmc.second);
        }       
        RCSTAbits.CREN = 1;
//Activa interrupcion del receptor
      }
    }
  }
}
void setup(void)
{
    OSCCONbits.IRCF = 0b111;
//Ajusta Fosc=8MHz, Tcy=0.5u
    while(OSCCONbits.HTS == 0) {}
   
/* CONFIGURACION PUERTOS*/
    ANSEL = 0;
    ANSELH = 0;
    TRISAbits.TRISA4 = 0;
//Salida LED
    OPTION_REGbits.nRABPU = 0;
//Pull-up
   
/* CONFIGURACION TIMER0 0.1MS */
    OPTION_REGbits.T0CS = 0;
//Modo Termporizador
    OPTION_REGbits.PSA = 0;
//Con prescala
    OPTION_REGbits.PS = 0b011;
//Prescala 1:16
    TMR0 = 131;
//256-[(time*Fosc)/(pre*4)] time=0.001 seg
    INTCONbits.T0IF = 0;
//Limpia bandera
    INTCONbits.T0IE = 1;
//Activa interrupcion del TMR0
   
/* CONFIGURA USART A 4800N1 8MHz*/
    TXSTAbits.BRGH = 1;
//Alta del Generador
    TXSTAbits.TXEN = 1;
//Activa el transmisor
    RCSTAbits.CREN = 1;
//Activa el receptor
    RCSTAbits.SPEN = 1;
//Habilita el modulo USART
    SPBRG = 103;
//Formula [8MHz/(16 * 4800)] - 1
    PIE1bits.RCIE = 1;
//Activa interrupcion del receptor
    INTCONbits.PEIE = 1;
//Activa interrupcion de perifericos
    INTCONbits.GIE = 1;
//Habilita interrupciones
}
void taskGPS(uint8_t data)
//Tarea del Programa receptor GPS
{  
//<$GPRMC,215536.000,A,1744.7111,S,06308.9987,W,0.00,0.00,300724,,,A*5B>
    static uint8_t state = 0, pos;
    switch(state)
    {
        case 0:
//$GPRM
            if(data == 'M')
                state++;
            break;
        case 1:
//C
            if(data == 'C')
                state++;
            break;
        case 2:
//,
            if(data == ',')
//Inicio RMC
            {
                pos = 0;
                state++;
            }
            break;
        case 3:
//215536.
            if(data == '.')
//Campo TIME
            {
                buffer[pos] = 0;
//null end
                rmc.second = (uint8_t) atoi(&buffer[4]);
                buffer[4] = 0;
//null end
                rmc.minute = (uint8_t) atoi(&buffer[2]);
                buffer[2] = 0;
//null end
                rmc.hour = (uint8_t) atoi(buffer);
                state++;
            } else buffer[pos] = data;
            if(pos++ > 6) state = 0;
//Reinicia maquina por error
            break;
        case 4:
//000,
            if(data == ',')
//Campo, no utilizado
                state++;
            break;
        case 5:
//A
            if(data == 'A') rmc.valid = 1;
//Campo VALID
            else rmc.valid = 0;
            state++;
            break;
        case 6:
//,
            if(data == ',')
//Campo no utilizado
                state++;
            break;
        case 7:
//1744.7111,
            if(data == ',')
//Campo LAT, no se utiliza
                state++;
            break;
        case 8:
//S,
            if(data == ',')
//Campo no utilizado
                state++;
            break;
        case 9:
//06308.9987,
            if(data == ',')
//Campo LON, no se utiliza
                state++;
            break;
        case 10:
//W,
            if(data == ',')
//Campo no utilizado
                state++;
            break;
        case 11:
//0.00,
            if(data == ',')
//Campo no utilizado
                state++;
            break;
        case 12:
//0.00,
            if(data == ',')
//Campo no utilizado
            {
                state++;
                pos = 0;
            }
            break;
        case 13:
//300724,
             if(data == ',')
//Campo DATE
            {
                buffer[pos] = 0;
//null end
                rmc.year = (uint8_t) atoi(&buffer[4]);
                buffer[4] = 0;
//null end
                rmc.month = (uint8_t) atoi(&buffer[2]);
                buffer[2] = 0;
//null end
                rmc.day = (uint8_t) atoi(buffer);
                RCSTAbits.CREN = 0; //Desactiva el receptor
                rmcok = 1;
//Activa bandera
                state = 0;
//Reinicia maquina
            } else buffer[pos] = data;
            if(pos++ > 6) state = 0;
//Reiniciar maquina por error
    }
}
void taskLED(void)
//Tarea para destello led
{
    static uint16_t tcnt = 0;
    if(tcnt++ > 999)
    {
        tcnt = 0;
        LED1pin = 1;
    }
    if(tcnt == 200) LED1pin = 0;
}
void putch(char byte)
{
    while(PIR1bits.TXIF == 0) {};
    TXREG = byte;
}

Funcionamiento del Circuito

El programa descrito anteriormente se ensayo en el microcontrolador con una implementación simple del receptor RGM-3600 y una tarjeta de prueba basada en el PIC16F687, la imagen de la figura 7, muestra el montaje final.

Fig7. Implementacion del circuito PIC
La recepción de la informacion se muestra en la imagen en la figura 6, estos datos se envían cada segundo, como el propósito del programa es mostrar la recepción de datos,  no se decodifico la latitud y longitud del mensaje, pero como vera en el código del programa receptor, hacer esta tarea es bastante sencilla.
 
Conclusiones y Recomendaciones.
Se ha demostrado el funcionamiento adecuado del circuito para recibir y decodificar datos de un GPS, sin duda las posibles aplicaciones que hay para este ejemplo son diversas, por lo que únicamente dejo a consideración algunos aspectos si estas en planes de utilizarlo en tu proyecto electrónico.
  • Te en claro siempre, que la informacion de tiempo de un GPS estará en formato UTC,
  • Considera que para hacer pruebas con este circuito, deberás ubicarte en una zona despejado con vista al exterior, ya que dentro de un edificio el receptor no captara señal de los satélites.
  • Si bien en muchas aplicaciones el mensaje RMC tiene la informacion suficiente para la navegación y referencia temporal, hay otro tipo de mensaje que proveen informacion de la recepción y estado de los satélites, que podrían necesitar para ciertas aplicaciones. 
  • Aunque por defecto la comunicación de un receptor GPS se establece en 4800 8N1, debe revisar las especificaciones del modelo, ya que en algunos casos esto puede variar.
Aquí dejo los enlaces para que puedas descargar el proyecto, se encuentra en formato gzip, debes descomprimir y abrir con MPLABX  
Si quieres ver como compilar e implementar los proyectos de este blog, elaborados con MPLABX mira este vídeo.<Compilando proyectos MPLABX>
 
Sin mas que mencionar agradezco tu visita al blog y espero que el ejemplo visto pueda ser útil en tu formación y el proyecto que desarrollas.

Atentamente, Pablo Zárate Arancibia  email: pablinza@me.com / pablinzte@gmail.com, @pablinzar

Santa Cruz de la Sierra - Bolivia