Bueno, siguiendo con los elementos que componen un sistema de control para UAV, no puede faltar el GPS para la navegación.
Es por eso que conseguí un módulo con el chip Ublox neo-6. Este módulo posee todo integrado, el ublox, la antena, la batería de respaldo para la configuracion, etc. Posee solo 4 terminales de conexion, +3.3V, GND, Tx y Rx. Posee dos LED's, uno verde que indica alimentacion y uno rojo que parpadea cuando tiene al menos 4 satélites linkeados y empieza a dar informacion útil.
Para ver la trama del GPS basta con conectar el módulo al puerto serie de la PC, teniendo presente que la parte de niveles TTL debe ser 3.3V y no 5V, configurar la velocidad e 9600 y listo!
Por ejemplo, mi GPS, de fábrica vino configurado así y al conectarlo me arroja cada 1 seg la siguiente trama:

Como pueden ver hay distintas tramas (renglones), cada una con informacion útil.
Pueden leer sobre las tramas en este enlace:
http://www.gpsinformation.org/dale/nmea.htmYo me voy a centrar en la trama GGA, la cual posee la informacion organizada de la siguiente manera:
$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
Where:
GGA Global Positioning System Fix Data
123519 Fix taken at 12:35:19 UTC
4807.038,N Latitude 48 deg 07.038' N
01131.000,E Longitude 11 deg 31.000' E
1 Fix quality: 0 = invalid
1 = GPS fix (SPS)
2 = DGPS fix
3 = PPS fix
4 = Real Time Kinematic
5 = Float RTK
6 = estimated (dead reckoning) (2.3 feature)
7 = Manual input mode
8 = Simulation mode
08 Number of satellites being tracked
0.9 Horizontal dilution of position
545.4,M Altitude, Meters, above mean sea level
46.9,M Height of geoid (mean sea level) above WGS84
ellipsoid
(empty field) time in seconds since last DGPS update
(empty field) DGPS station ID number
*47 the checksum data, always begins with *
De todos los campos (separados por comas) me interesan el de la hora, la latitud, la longitud, Calidad Fix, número de satélites linkeados y la altura en metros.
A la técnica utilizada para decodificar la trama en sus campos se denomina "parsear".
Si buscamos en google "nmea gps parser" van a aparecer cientos de librerías. A mi me gustó este:
http://code.google.com/p/shake-drivers/source/browse/trunk/%20shake-drivers%20--username%20stephenahughes/NMEA%20GPS%20parser%20-%20isr_uart.c?r=21no es el código más óptimo, pero me gusta el hecho de que es, para mi, intuitivo y va parseando caracter por caracter, por lo que la interrupcion de recepcion de caracter en el UART está activa muy poco tiempo.
La técnica se basa en ir detectando caracteres conocidos o esperados e ir avanzando en el estado del parseo incrementando una variable. Yo modifiqué un poco el código para mi uso y en definitiva el código de mi uart3.c quedó así:
#include "LPC17xx.h"
#include "uart3.h"
// PCUART3
#define PCUART3_POWERON (1 << 25)
//Peripheral Clock Selection. Para el uart3 usar PCLKSEL1
#define PCLK_UART3 18
#define PCLK_UART3_MASK (3 << 18) //Esta mascara se usara par aponer 0b00 -> PCLK = CCLK / 4 ->
#define IER_RBR 0x01 //
#define IER_THRE 0x02
#define IER_RLS 0x04
#define IIR_PEND 0x01
#define IIR_RLS 0x03
#define IIR_RDA 0x02
#define IIR_CTI 0x06
#define IIR_THRE 0x01
#define LSR_RDR 0x01
#define LSR_OE 0x02
#define LSR_PE 0x04
#define LSR_FE 0x08
#define LSR_BI 0x10
#define LSR_THRE 0x20
#define LSR_TEMT 0x40
#define LSR_RXFE 0x80
volatile uint32_t UART3Status;
volatile uint8_t UART3TxEmpty=1;
volatile uint8_t UART3Buffer[BUFSIZE];
volatile uint32_t UART3Count = 0;
volatile uint8_t nmea_state = 0;
volatile char char_rcv = 0;
extern char gps_height[5];
extern char gps_height_decimeters;
extern char calculateoutputgps;
extern char gps_latitud[10];
extern char gps_longitud[10];
extern char gps_UTC[9];
extern char gps_Sats[2];
void uart3_interruptsOn(void) {
LPC_UART3->IER = 0x07; // RBR, THRE, RLS
}
void uart3_interruptsOff(void) {
LPC_UART3->IER = 0x00; // !RBR, !THRE, !RLS
}
// ***********************
// Funcion para Inicializar el UART 2
void UART3_Init(int baudrate)
{
uint32_t fdiv, pclk;
// Encendemos el UART3
LPC_SC->PCONP |= PCUART3_POWERON;
// Encendemos el reloj del UART3
LPC_SC->PCLKSEL1 &= ~(PCLK_UART3_MASK); // Borro los bits corrspondientes al PCLK
LPC_SC->PCLKSEL1 |= (0 << PCLK_UART3); // Aplico el PCLK_periph = CCLK/4
// Configuro el IO P4.28 = RX, P4.29 = TX
// LPC_PINCON->PINSEL9 &= ~0xF000000; //Pongo en 0 los bits 24..27
LPC_PINCON->PINSEL9 |= (0xF << 24); //Pongo en 1 los bits 24..27
LPC_UART3->LCR = 0x83; // 0b10000011 -> 8 bits, no Parity, 1 Stop bit, Divisor Latch Access Bit=1
// PCLK_UART3 se va a setear a SystemCoreClock dividido 4 -> 25MHz
// pclk = SystemCoreClock / 4;
pclk = SystemCoreClock/4;
// do baudrate calculation
fdiv = (pclk / (16 * baudrate));
LPC_UART3->DLM = (fdiv >> 8) & 0xFF;
LPC_UART3->DLL = (fdiv) & 0xFF;
if(baudrate==115200){
LPC_UART3->DLM = 0;
LPC_UART3->DLL = 9;
LPC_UART3->FDR = 1 | 2<<4;
}
LPC_UART3->LCR = 0x03; // 8 bits, no Parity, 1 Stop bit DLAB = 0
LPC_UART3->FCR = 0x07; // Enable and reset TX and RX FIFO. 1 carcter en FIFO
NVIC_EnableIRQ(UART3_IRQn);
LPC_UART3->IER = IER_RBR | IER_THRE | IER_RLS; /* Enable UART3 interrupt */
}
// ***********************
// Funcion para enviar un caracter por el UART 2
void UART3_Sendchar(char c)
{
while( (LPC_UART3->LSR & LSR_THRE) == 0 ); // Esperamos hasta que Transmit Holding Register Empty esté en 1
LPC_UART3->THR = c; //Escrivo en el Transmit Holding Register para que se envíe el caracter
}
// ***********************
// Funcion para leer un caracter desde el UART 2
char UART3_Getchar()
{
char c;
while( (LPC_UART3->LSR & LSR_RDR) == 0 ); // Espero hasta que haya datos mirando el Receive Data Register
c = LPC_UART3->RBR; // Leo el Receive Buffer Register
return c;
}
// ***********************
// Funcion para enviar una cadena de caracteres por el UART 2
void UART3_PrintString(char *pcString)
{
int i = 0;
// loop through until reach string's zero terminator
while (pcString[i] != 0) {
UART3_Sendchar(pcString[i]); // print each character
i++;
}
}
/*****************************************************************************
** Function name: UART3_IRQHandler
**
** Descriptions: UART3 interrupt handler
**
Aquí intentamos parsear la trama $GPGGA del GPS para leer la informacion que provee.
La trama es así:
* $GPGGA
* $GPGGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh
* ex: $GPGGA,230600.501,4543.8895,N,02112.7238,E,1,03,3.3,96.7,M,39.0,M,,0000*6A,
*
* WORDS:
* 1 = UTC of Position
* 2 = Latitude
* 3 = N or S
* 4 = Longitude
* 5 = E or W
* 6 = GPS quality indicator (0=invalid; 1=GPS fix; 2=Diff. GPS fix)
* 7 = Number of satellites in use [not those in view]
* 8 = Horizontal dilution of position
* 9 = Antenna altitude above/below mean sea level (geoid)
* 10 = Meters (Antenna height unit)
* 11 = Geoidal separation (Diff. between WGS-84 earth ellipsoid and mean sea level.
* -geoid is below WGS-84 ellipsoid)
* 12 = Meters (Units of geoidal separation)
* 13 = Age in seconds since last update from diff. reference station
* 14 = Diff. reference station ID#
* 15 = Checksum
El tema con las tramas es que no los campos no tienen longitud fija. Por ejemplo el dato de la
Latitud puede variar de la forma xxx.y a xxx.yyyy ó xxxxx.yyyy dependiendo de la lectura, entonces
debemos ir tomando caracter por caracter y ver que formato tiene
La salida de datos esta de la siguiente forma:
gps_latitude_degrees[3],gps_longitude_degrees[3], where [0]*100 + [1]*10 + [2] = decimal degrees
gps_latitude_minutes[2],gps_longitude_minutes[2], where [0]*10 + [1] = decimal minutes
gps_latitude_decimal_seconds[4],gps_longitude_decimal_seconds[4], where ([0]*1000 + [1]*100 + [2]*10 + [3]) / 10000 * 60 = decimal seconds
gps_height[5], gps_height_decimeters, where ([0]*10000 + [1]*1000 + [2]*100 + [3]*10 + [4]) + gps_height_decimeters/1° = decimal height
tengo que agregar la lectura de los satélites linkeados...
**
*****************************************************************************/
void UART3_IRQHandler (void){
uint8_t IIRValue, LSRValue;
uint8_t Dummy = Dummy;
static uint8_t idx=0;
IIRValue = LPC_UART3->IIR;
IIRValue >>= 1; /* skip pending bit in IIR */
IIRValue &= 0x07; /* check bit 1~3, interrupt identification */
if ( IIRValue == IIR_RLS ){ /* Receive Line Status */
LSRValue = LPC_UART3->LSR;
/* Receive Line Status */
if ( LSRValue & (LSR_OE|LSR_PE|LSR_FE|LSR_RXFE|LSR_BI) ){
/* There are errors or break interrupt */
/* Read LSR will clear the interrupt */
UART3Status = LSRValue;
Dummy = LPC_UART3->RBR; /* Dummy read on RX to clear interrupt, then bail out */
return;
}
if ( LSRValue & LSR_RDR ){ /* Receive Data Ready */
/* If no error on RLS, normal ready, save into the data buffer. */
/* Note: read RBR will clear the interrupt */
UART3Buffer[UART3Count] = LPC_UART3->RBR;
UART3Count++;
if ( UART3Count == BUFSIZE ){
UART3Count = 0; /* buffer overflow */
}
}
}
else if ( IIRValue == IIR_RDA ){ /* Receive Data Available */
char_rcv = LPC_UART3->RBR;
UART3Buffer[UART3Count] = char_rcv;
UART3Count++;
if ( UART3Count == BUFSIZE ){
UART3Count = 0; /* buffer overflow */
}
// now try to see where this character fits in the NMEA GGA message we're trying to decipher
switch ( nmea_state ) // evaluate expression
{
case ( 1 ): // wait for 'G'
if ( char_rcv == 'G')
nmea_state++;
break;
case ( 2 ): // wait for 'G'
if ( char_rcv == 'G')
nmea_state++;
else
nmea_state=1; //Si no es una G volvemos al principio
break;
case ( 3 ): // wait for 'A'
if ( char_rcv == 'A')
nmea_state++;
else
nmea_state=1; //Si no es una G volvemos al principio
break;
case ( 4 ): // wait for ','
if ( char_rcv == ',')
nmea_state++;
break;
case ( 5 ): // Estoy en el Campo UTS
if ( char_rcv == ','){ //Si ya finalizó el campo
nmea_state++; //Paso al siguiente estado
idx=0;
}else{
gps_UTC[idx] = char_rcv; // Voy almacenando los caracteres recividos
idx++; // Incremento el índice dentro del campo actual
}
calculateoutputgps=0;
break;
case ( 6 ): // Estamos en el Campo LATITUD
if ( char_rcv == ','){ //Si ya finalizó el campo
nmea_state++; //Paso al siguiente estado
idx=0;
}else{
idx++; // Incremento el índice dentro del campo actual
gps_latitud[idx] = char_rcv; // Voy almacenando los caracteres recividos
}
break;
case ( 7 ):
if ( char_rcv == 'N') // Si es Norte
gps_latitud[0] = '+'; // Latitud positiva
else // Si es Sur
gps_latitud[0] = '-'; // Latitud negativa
nmea_state++;
break;
case ( 8 ): // this is a comma
nmea_state++;
break;
case ( 9 ): // Estamos en el Campo LONGITUD
if ( char_rcv == ','){ //Si ya finalizó el campo
nmea_state++; //Paso al siguiente estado
idx=0;
}else{
idx++; // Incremento el índice dentro del campo actual
gps_longitud[idx] = char_rcv; // Voy almacenando los caracteres recividos
}
break;
case ( 10 ):
if ( char_rcv == 'E') // Si es Este
gps_longitud[0] = '+'; // Latitud positiva
else // Si es Sur
gps_longitud[0] = '-'; // Latitud negativa
nmea_state++;
break;
case ( 11 ): // this is a comma
nmea_state++;
break;
case ( 12 ): // Este campo es Fix quality
nmea_state++;
break;
case (13): // Este es una ','
nmea_state++;
break;
case (14): // Este es el campo de número de satelites linkeados.
if ( char_rcv == ','){ //Si ya finalizó el campo
nmea_state++; //Paso al siguiente estado
idx=0;
}else{
gps_Sats[idx] = char_rcv; // Voy almacenando los caracteres recividos
idx++; // Incremento el índice dentro del campo actual
}
break;
case (15): // this is the 4th comma
if ( char_rcv == 0x2C) {
// move to the next state
nmea_state++;
// but first prepare the meters input variable
gps_height[0]= 0x30;
gps_height[1]= 0x30;
gps_height[2]= 0x30;
gps_height[3]= 0x30;
gps_height[4]= 0x30;
}
break;
case (16): // this is the heigth in meters
if ( char_rcv == 0x2e) { // waiting for a decimal point
// no more numbers? OK, done with the shifting, move to the next step
nmea_state++;
}
else {
// oh no, 1 more number! Just shift the digits around and we'll be fine
gps_height[0]=gps_height[1];
gps_height[1]=gps_height[2];
gps_height[2]=gps_height[3];
gps_height[3]=gps_height[4];
gps_height[4]=char_rcv;
}
break;
case (17): // this is the decimal part of the heigth
gps_height_decimeters = char_rcv;
// notify the main loop we have gps data available
calculateoutputgps = 1;
// and wait for the next NMEA message
nmea_state = 1;
break;
default:
nmea_state = 1; // we'll never and up here, but in case of a brownout make sure we start at the beginning
break;
}
}
else if ( IIRValue == IIR_CTI ){ /* Character timeout indicator */
/* Character Time-out indicator */
UART3Status |= 0x100; /* Bit 9 as the CTI error */
}
else if ( IIRValue == IIR_THRE ){ /* THRE, transmit holding register empty */
/* THRE interrupt */
LSRValue = LPC_UART3->LSR; /* Check status in the LSR to see if valid data in U0THR or not */
if ( LSRValue & LSR_THRE ){
UART3TxEmpty = 1;
}
else{
UART3TxEmpty = 0;
}
}
}
Bueno, el parseo sucede en la interrupcion del UART3. Despues de verificar ausencia de errores y ver que la interrupcion es por Receiv Data Available empiezo con el parseo.
Primero me aseguro de guardar la trama, caracter por caracter en un baffer (UART3Buffer) para hacer debug. Ese buffer lo voy a eliminar cuando tenga todo funcionando bien.
Luego comienza un SWITCH contra una variable, nmea_state, la cual iremos incrementando a medida que vallamos procesando los caracteres.
Las primeras opciones son para asegurarce de recivir la secuencia GGA. Luego biene el reconocimiento de la ',' y llegamos a la opcion 5. Aquí empiezan mis modificaciones. Lo que yo hago es mantenerme en el estado 5 y almacenando caracter por caracter en un solo array de char's, hasta que llegue la ','. Ese array de chars más tarde lo pasaré por una funcion que me la transformará en un flotante. Con ese método voy verificando caracteres, almacenando campos y avanzando en el estado, hasta llegar al final de la trama. Este código no esta 100% completo, pero es funcional y con el siguiente main:
/*
===============================================================================
Author : $(author)
Version :
Copyright : $(copyright)
Description : main definition
===============================================================================
*/
char gps_longitud[10];
char gps_latitud[10];
char gps_UTC[9];
char gps_Sats[2];
char gps_height[5];
char gps_height_decimeters;
char calculateoutputgps=0;
#ifdef __USE_CMSIS
#include "LPC17xx.h"
#endif
#include <cr_section_macros.h>
#include <NXP/crp.h>
#include "uart3.h"
#include "uart2.h"
volatile uint32_t msTicks;
__INLINE static void delay_ms (uint32_t delayTicks) {
uint32_t currentTicks;
currentTicks = msTicks; // read current tick counter
while ((msTicks - currentTicks) < delayTicks);
}
float string2float(char* s);
void SysTick_Handler(void) {
msTicks++; /* increment counter necessary in Delay() */
}
/*******************************************************************************
** Main Function main()
*******************************************************************************/
int main (void){
//Configuro el SysTick para que interrumpa cada 1 mseg
if (SysTick_Config(SystemCoreClock / 1000)) {
while (1);
}
UART2_Init(115200); // Inicializo el UART a 115200
UART3_Init(9600); // Inicializo el UART a 9600
UART2_PrintString ("\rGPS\r\n");
UART2_PrintString ("======== elgarbe ==========\r\n");
float flongitud=0.0;
float flatitud=0.0;
float fUTC=0.0;
float fSats=0.0;
while ( 1 ){
if(calculateoutputgps){
fUTC=string2float(gps_UTC);
flatitud=string2float(gps_latitud);
flongitud=string2float(gps_longitud);
fSats=string2float(gps_Sats);
UART2_PrintString("\n\rHora actual: ");
uart2_printUint32(((short)(fUTC/10000)+24-3), 10);
UART2_PrintString(":");
uart2_printUint32(((short)(fUTC/100) - (short)(fUTC/10000)*100), 10);
UART2_PrintString(":");
uart2_printUint32(( (short)fUTC - (short)(fUTC/100)*100 ), 10);
UART2_PrintString("\n\rLatitud: ");
uart2_printInt32((short)flatitud/100, 10);
UART2_PrintString("°");
uart2_printInt32( ((short)flatitud - ((short)flatitud/100)*100), 10);
UART2_PrintString("'");
UART2_PrintString("\n\rLongitud : ");
uart2_printInt32((short)flongitud/100, 10);
UART2_PrintString("°");
uart2_printInt32( ((short)flongitud - ((short)flongitud/100)*100), 10);
UART2_PrintString("'");
UART2_PrintString("\n\rSatélites linkeados: ");
uart2_printDouble(fSats, 0);
}
delay_ms(1000);
}
}
/* returns base-10 value of zero-terminated string
* that contains only chars '+','-','0'-'9','.';
* does not trap invalid strings!
*/
float string2float(char* s) {
long integer_part = 0;
float decimal_part = 0.0;
float decimal_pivot = 0.1;
char isdecimal = 0, isnegative = 0;
char c;
while ( ( c = *s++) ) {
// skip special/sign chars
if (c == '-') { isnegative = 1; continue; }
if (c == '+') continue;
if (c == '.') { isdecimal = 1; continue; }
if (!isdecimal) {
integer_part = (10 * integer_part) + (c - 48);
}
else {
decimal_part += decimal_pivot * (float)(c - 48);
decimal_pivot /= 10.0;
}
}
// add integer part
decimal_part += (float)integer_part;
// check negative
if (isnegative) decimal_part = - decimal_part;
return decimal_part;
}
Consigo la impresion de los datos reconocidos en el hyperterminal:

Lamentablemente no se como configurar el hyperterminal para que muestre acentos y simbolos especiales...
Aparte hay algunos errores en la conversion ya que tengo minutos de grados negativos....
Cuando corrija todo eso pondré el código definitivo.
Saludos!