
Bu yazımızda GPS nedir? GPS ile nasıl konum belirlenir? sorularına cevap vermeye çalışacak, Stellaris üzerinde GPS modülümüz üzerinden verileri okuyacak ve yorumlayacağız. Anlık konum bilgilerimizi hesaplayacağız. Konum bilgilerimizi UART haberleşmesi yardımıyla alıp(UART0), yine aynı haberleştirme protokolü ile ekrana verilerimizi basacağız.(UART1)
GPS İle Konum Belirleme
GPS modülü, uydular ile arasındaki mesafeyi ölçerek anlık konumumuzu belirlemeye çalışan bir mekanizmadır. Anlık konum bilgilerimizi alabilmek için bu modülü kullanacağız. Uygulamamızı SIRF III adlı modül ile yapacağız. Bu modülü seçmemizdeki faktörler yüksek duyarlılık(-159dBm) ve yer istasyonu olmadan 5 metreye kadar doğruluk verebilmesi olmuştur. Modül hakkında detaylı bilgi için GPS cihazı seçimi için yazmış olduğumuz yazı incelenebilir.
GPS İle Konum Nasıl Belirlenir?
GPS modülünün uydularla arasındaki mesafeyi ölçerek konumunu belirlemeye çalıştığından bahsetmiştik. Bu kısımda bu konuyu biraz daha detaylandırmaya çalışacağız. GPS temel olarak uydularla arasında üçgenleştirme metodunu kullanır. Fakat üçgenleştirmeden bahsederken yanılgıya düşülmemesi gereken kısım GPS ile uydular arasında her hangi bir açı yoktur. Böyle bir bilgi alamaz. Aslında 24 uydu verisi alınır fakat 3 uydu yardımıyla var olan konumumuz bulunur diyebiliriz.
Mesela diyelim ki ilk uydudan aldığımız verilere göre 30 000km uzaklık bilgisini aldık. Bu uzaklık bilgisi, dünya üzerinde o uyduyu merkez kabul edip etrafına yarı çapı 30 000km olan bir alan çizmemiz olarak yorumlanabilir. Daha sonra ikinci uydudan aynı şekilde 22 000 km uzaklık değeri aldığımızı düşünün ve bu uydunun etrafına da aynı şekilde bir alan oluşturulduğunu var sayalım. Bu uydu bilgilerinin 3 tanesinin oluşturduğu alanlar bir yerde kesişecek ve bize dünya üzerindeki yerimizi verecektir.
İlk resimde kesim noktasının bulunması, ikinci resimde geçerli alanların sınırlandırılması ve son resimde ise GPS alıcısının oluşan alanları değiştirerek kesişimi netleştirmesi resmedilmiştir. GPS alıcının duyarlılığı ve doğruluğu burada işimize yaramaktadır. Konumu belirlemek için uzaklıkları kullanıyoruz dedik. Peki bu uzaklıkları nasıl hesaplıyoruz gibi bir soru gelebilir.
Uzaklık hesabını aslında çok temel bir eşitliğe göre yapıyoruz. Yol = Hız x Zaman formülü yadımıyla uydu ile aramızdaki mesafenin hesabını yapacağız. Zaman olarak kullanacağımız kısım sinyalin seyahat zamanı, hız olarak kullanacağımız kısım ise sinyalin hızı olacaktır. Uydu bize o anki saat bilgisini göndermektedir. Bizde o anki alıcının zamanından uydunun zaman bilgisini çıkararak seyahat zamanını bulacağız. Fakat burada düşünülmesi gereken kısım her uydudan aynı zamanda veri alındığı baz alınmaktadır ve aslında aynı zamanda veri alınmamaktadır. Mesela 22000 km uzaklıktaki bir uydu ile 33000 km uzaklıktaki bir uydu aynı zamanda cevap vermemektedir. Fakat böyle bir var sayıma baş vurulmaktadır.
GPS hatalarının nedenlerinden bir tanesi yukarıda bahsettiğimiz zamanlamadır. Genellikle bu hata uydulardan kaynaklanmaz. Çünkü üzerilerinde atomik saatler vardır ve bunlar sürekli doğru veri gönderirler. Mükemmel zamanlama istiyorsak GPS modülümüz ile birlikte atomik saat kullanabiliriz.
Bahsettiğimiz uzaklık hesabı üzerinde tabiki hatalar mevcuttur. Zaten bu hataları telafi edebildiğimiz derecede hassas ölçümler yapabiliriz. Bu hatalar bulutlardan ya da havadaki partiküllerden kaynaklanabilir. Aynı zamanda atmosfer tabakasına girdikçe sapmalar yapacaktır. Işınımız atmosfer tabakalarından geçerken mesela iyonosfer üzerindeki partiküllerden etkilenebilir ya da trofosfer üzerindeki su buharı nedeniyle yavaşlayabilir. Ve bu bize zamanlama üzerinde hatalar verebilir. Bu sorunları çözmek için ise çift yönlü frekans diye adlandırılan bir hesaplamaya baş vurulmaktadır.
Çift yönlü frekans hesabı, GPS modüle gönderilen iki taşıyıcı sinyal arasındaki gecikme hesaplanarak yapılmaktadır. (L1 ve L2) L1 sinyali herkese açık olsa da L2 sinyali sadece askeri alan için kullanıma açıktır ve bu taşıyıcı sinyal üzerindeki veriyi biz alamayız. Bu yüzden bu yöntem bizim için geçerli bir yöntem olmayacaktır.
GPS Modül Çıktılarının Yorumlanması
GPS modül bize bir çok veri göndermektedir. Bu verilerin farklı farklı anlamları olmaktadır. Bu bölümde modülümüzün verdiği çıktıların görevlerini detaylandırmaya çalışacağız. İlk olarak bize enlem ve boylam bilgisi gerektiği için cihazımız üzerinde bu bilgiyi alabileceğimiz komut dizisini bulduk. GGA isimli komut dizisi yardımıyla bu bilgileri alabileceğiz.
Tablodan da görülebileceği üzere ilk virgülden sonraki veri bize UTC zamanını, ikinci virgülden sonra enlem bilgisi, üçüncü virgülden sonra kuzey-güney bilgisi, dördüncü virgülden sonra ise boylam bilgisi verilmektedir. Bu bilgileri tanımlamalarında da görüldüğü gibi tanımlayarak gerekli hesaplamalarımızı yapacağız. GGA bilgisini gelen verilerden yakalayarak bu seriye ulaşabileceğiz.
Kullanacağımız bir diğer kısım ise GPS üzerine göndereceğimiz giriş komutları olacaktır.
İlk olarak mesajın ne tür bir mesaj olduğunu söyleyecek tanımlama kodunu gönderiyoruz, ardından hangi protokolde çalışmak istediğimizi data gönderim hızını vs. ayrları yapıyoruz. Bu şekilde GPS içerisinde ayarlamaları yapabiliyoruz.
MCU Üzerindeki İşlemler
Stellaris üzerinde GPS modülünü interrupt üzerinden kontrol etmeye karar verdik. Uygulamamızda magnetometer için yazmış olduğumuz kaynak kodları güncelleyeceğiz ve iki uygulamayı birleştireceğiz. Öncelikle interrupt ayarlarını yapmamız gerekiyor.
[codesyntax lang=”c” title=”ConfigInt”]
void ConfigInt() { SysCtlPeripheralEnable(SYSCTL_PERIPH_UART1); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); IntMasterEnable(); GPIOPinTypeUART(GPIO_PORTD_BASE, GPIO_PIN_2 | GPIO_PIN_3); UARTConfigSetExpClk(UART1_BASE, SysCtlClockGet(), 4800, (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE)); IntEnable(INT_UART1); UARTIntEnable(UART1_BASE, UART_INT_RX | UART_INT_RT); }
[/codesyntax]
GPS ile UART1 üzerinden haberleşeceğimiz için UART1 ve onun ait olduğu port olan GPIOD portunu aktif hale getiriyoruz. Daha sonra genel interrupt bayrağını açıyoruz. Ardından UART haberleşme protokolü ayarlarını tanımlıyor ve ardından pinler üzerindeki interruptı aktifleştiriyoruz.
İnterrupt fonksiyonumuz içerisinde ise NMEA protokolünden gelen verileri ayrıştırıp istediğimiz değerleri alacak bir yapı kurmamız gerekiyordu. Bunun için öncelikle istediğimiz değişkeni yakalayana kadar bekleyip, yakalama işlemi gerçekleştikten sonra ise bu verileri bir yere kaydedip ayrıştırarak ekrana anlaşılır bir şekilde basmaya karar verdik.
[codesyntax lang=”c” title=”UARTIntHandler”]
void UARTIntHandler(void) { static int i=0; static int j=0; ulStatus = UARTIntStatus(UART1_BASE, true); while(UARTCharsAvail(UART1_BASE)) { temp[i] = UARTCharGetNonBlocking(UART1_BASE); if(i>=5 && temp[i]=='A' && temp[i-1]=='G' && temp[i-2]=='G' && temp[i-3]=='P'&& temp[i-4]=='G'&& temp[i-5]=='$') { for(j=0;j<6;j++) data[j] = temp[i+j-5]; valid = 1 ; GGA=1; } if(valid == 1 ) { data[j] = temp[i]; if(temp[i]=='*') { valid=0; UARTprintf("%s\n",data); getLocation(data); } } j=(j+1)%2000; i=(i+1)%2000; } UARTIntClear(UART1_BASE, ulStatus); // seri port interrupt bayrağını temizler }
[/codesyntax]
UARTCharsAvail(UART1_BASE) fonksiyonu yardımıyla buffer üzerindeki değerler bitene kadar UART üzerinden data alış verişini gerçekleştiriyoruz. Burada aldığımız değerleri temp değişkenine alarak istediğimiz protokolün gelip gelmediğini kontrol ediyoruz. Eğer istediğimiz komut gelmişse(GGA) bu komut bilgilerini data değişkenine kaydediyoruz. Ardından gelen komut üzerinden bilgilerimizi ayırmamız gerekiyor. Bunun için stringh.h kütüphanesinde yer alan strtok fonksiyonunu kullanacağız.
[codesyntax lang=”c” title=”getLocation”]
void getLocation(char * pch) { pch = strtok (data,","); while (pch != NULL) { if(GGA==6) { if(*pch=='E') UARTprintf(" Dogu\n"); else if(*pch=='W') UARTprintf(" Bati\n"); GGA=0; UARTprintf("\n\n"); } else if(GGA==5) { UARTprintf("Boylam= %s",pch); GGA=6; } else if(GGA==4) { GGA=5; if(*pch=='N') UARTprintf(" Kuzey\n"); else if(*pch=='S') UARTprintf(" Guney\n"); } else if(GGA==3) { UARTprintf("Enlem= %s",pch); GGA=4; } else if(GGA==2) { GGA=3; UARTprintf("UTC zamani= %s\n",pch); } else if(GGA==1) { GGA=2; } pch = strtok (NULL,","); } }
[/codesyntax]
Bu fonksiyonda diziyi virgülden ayırarak istediğimiz enlem ve boylam değerlerini alıp ekrana yazdıracağız. GGA değişkeni, seri sonu gelene kadar her seferinde bir virgül bulduğu için böyle bir numara yapmaya ihtiyaç duyduk.
Değişiklik yaptığımız bir diğer konu ise vektör tablosu oldu. Çünkü interrupt fonksiyonunu geçerli vektör tablosunda belirtmemiz gerekiyordu. Bunun için proje üzerindeki Startup.s dosyasına girerek gerekli değişiklikleri yapacağız.
[codesyntax lang=”c”]
;****************************************************************************** ; ; External declaration for the interrupt handler used by the application. ; ;****************************************************************************** EXTERN UARTIntHandler ;******************************************************************************
[/codesyntax]
[codesyntax lang=”c”]
;****************************************************************************** ; ; The vector table. ; ;****************************************************************************** EXPORT __Vectors __Vectors DCD StackMem + Stack ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NmiSR ; NMI Handler DCD FaultISR ; Hard Fault Handler DCD IntDefaultHandler ; The MPU fault handler DCD IntDefaultHandler ; The bus fault handler DCD IntDefaultHandler ; The usage fault handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD IntDefaultHandler ; SVCall handler DCD IntDefaultHandler ; Debug monitor handler DCD 0 ; Reserved DCD IntDefaultHandler ; The PendSV handler DCD IntDefaultHandler ; The SysTick handler DCD IntDefaultHandler ; GPIO Port A DCD IntDefaultHandler ; GPIO Port B DCD IntDefaultHandler ; GPIO Port C DCD IntDefaultHandler ; GPIO Port D DCD IntDefaultHandler ; GPIO Port E DCD IntDefaultHandler ; UART0 Rx and Tx DCD UARTIntHandler ; UART1 Rx and Tx DCD IntDefaultHandler ; SSI0 Rx and Tx DCD IntDefaultHandler ; I2C0 Master and Slave DCD IntDefaultHandler ; PWM Fault DCD IntDefaultHandler ; PWM Generator 0 DCD IntDefaultHandler ; PWM Generator 1 DCD IntDefaultHandler ; PWM Generator 2 DCD IntDefaultHandler ; Quadrature Encoder 0 DCD IntDefaultHandler ; ADC Sequence 0 DCD IntDefaultHandler ; ADC Sequence 1 DCD IntDefaultHandler ; ADC Sequence 2 DCD IntDefaultHandler ; ADC Sequence 3 DCD IntDefaultHandler ; Watchdog timer DCD IntDefaultHandler ; Timer 0 subtimer A DCD IntDefaultHandler ; Timer 0 subtimer B DCD IntDefaultHandler ; Timer 1 subtimer A DCD IntDefaultHandler ; Timer 1 subtimer B DCD IntDefaultHandler ; Timer 2 subtimer A DCD IntDefaultHandler ; Timer 2 subtimer B DCD IntDefaultHandler ; Analog Comparator 0 DCD IntDefaultHandler ; Analog Comparator 1 DCD IntDefaultHandler ; Analog Comparator 2 DCD IntDefaultHandler ; System Control (PLL, OSC, BO) DCD IntDefaultHandler ; FLASH Control
[/codesyntax]
Vektör tablosu üzerinde interrupt fonksiyonumuz için değişiklikleri yukarıdaki gibi yapıyoruz. Ardından programımızı derleyip çalıştırabiliriz.
Sadece GPS çıktıları;
Stellaris ile entegre edildikten sonra;
GPS üzerinden aldığımız verileri yorumlayarak konumumuzu hesaplamak mümkün oluyor. Öncelikle aldığımız enlem ve boylam verilerini derece;dakika;saniye olacak şekilde güncelliyoruz. Örnek olarak aldığımız verilerden bir tanesi ;
Enlem : 3947.0490 Kuzey Boylam: 03030.6671 doğu olduğu bilgisini aldık. Yukarıdaki tablolardan faydalanarak verileri güncellediğimizde;
Enlem: 39 derece : 47 dakika : 2.94saniye Boylam: 30 derece : 30dakika : 40.026 saniye olarak yorumlanabilir. Kordinatları yerine koyduğumuzda konumumuzu vermesini bekliyoruz.
10 metre kadar hata payı var. Böylelikle GPS ile konum belirlemeyi de tamamlamış bulunuyoruz.