GPS模塊使用串口通信,那麼它的的數據處理本質上還是串口通信處理,只是GPS模塊的輸出的有其特定的格式,需要字元串處理邏輯來解析其含義。如何高效的處理從GPS模塊接收到的數據幀,是GPS驅動設計的重點,本文使用狀態機的思想來處理GPS輸出的串口數據流,相對於定時從串口環形bufer取數據包然後依次解... ...
1.摘要
GPS模塊使用串口通信,那麼它的的數據處理本質上還是串口通信處理,只是GPS模塊的輸出的有其特定的格式,需要字元串處理邏輯來解析其含義。如何高效的處理從GPS模塊接收到的數據幀,是GPS驅動設計的重點,本文使用狀態機的思想來處理GPS輸出的串口數據流,相對於定時從串口環形bufer取數據包然後依次解析有更高的實時性並且單片機負荷更低。
2. GPS數據協議簡介
常用的GPS模塊大多採用NMEA-0183 協議,目前業已成了GPS導航設備統一的RTCM(Radio Technical Commission for Maritime services)標準協議。NMEA-0183 是美國國家海洋電子協會(National Marine Electronics Association)所指定的標準規格,這一標準制訂所有航海電子儀器間的通訊標準,其中包含傳輸資料的格式以及傳輸資料的通訊協議。
GPS數據格式如下:
幀格式形如:$aaccc,ddd,ddd,„,ddd*hh(CR)(LF)
1、“$”:幀命令起始位
2、aaccc:地址域,前兩位為識別符(aa),後三位為語句名(ccc)
3、ddd„ddd:數據
4、“*”:校驗和首碼(也可以作為語句數據結束的標誌)
5、hh:校驗和,$與*之間所有字元ASCII碼的校驗和(各位元組做異或運算,得到
校驗和後,再轉換16進位格式的ASCII字元)
6、(CR)(LF):幀結束,回車和換行符
可以從串口抓數據幀:
$GPGSV,2,2,08,21,15,076,,23,52,270,,26,50,050,,27,52,179,*7D
$GPRMC,132043.00,V,,,,,,,120116,,,N*7F
$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
$GPRMC,133308.00,A,3949.63002,N,11616.48641,E,1.101,,120116,,,A*70
3. GPS狀態機接收
一般的應用中我們最關心的數據是GPRMC,即推薦定位信息。我們常見GPS數據接收方法主要是串口中斷法,串口中斷一直開著,然後定時從中斷中取一包數據,解析這包數據,找到定位信息,常見的主要是找到GPRMC幀。
這有幾個問題。
1. 一般而言,中斷數據很快,而數據處理過程會發生丟接收中斷。
2. 為了避免丟數據,可以使用雙buffer來處理,讀數據時候就把這個buffer鎖定,然後再來了中斷數據就往另外一個buffer放。
3. 如果取數據時刻剛好是一個有效定位信息,那麼切換到第二個buffer後,就導致一條幀分成2份,兩個buffer中該數據幀都不完整。
4.代碼結構不清晰,應用層收到的數據可能不是完整的一條幀。
基於以上幾點,改進方法使用狀態機來接收,可以每次完整的給應用層發送一幀數據。
並且不需要關閉串口中斷,接收過程一直進行。
接收到一個完整幀後就往上層送一次,上層負責解釋數據的含義。
下麵是在stm32平臺上的接收GPS數據的處理過程。
1 //gps receive gps_state machine. 2 #define Start 0// $ 3 #define G 1 4 #define P 2 5 #define R 3 6 #define M 4 7 #define C 5 8 #define Data 6 9 #define Check0 7 // * 10 #define Check1 8// * 11 void UART4_IRQHandler(void) 12 { 13 static uint8_t len = 0; 14 static uint8_t crc = 0; 15 static GPS_MSG_T GpsMsg; 16 uint8_t data = 0; 17 uint8_t tmp_flg = 0; 18 //$GPRMC,144601.00,A,3916.72973,N,11706.60267,E,0.719,,180117,,,A*76 19 if (USART_GetITStatus(GPS_UART, USART_IT_RXNE) != RESET) 20 { 21 data = (uint8_t)USART_ReceiveData(GPS_UART); 22 23 switch(gps_state) // find GPRMC 24 { 25 case Start: 26 if(data == '$') { 27 gps_state = G; 28 len = 0; 29 GpsMsg.maxLen = MaxGPSMsgLen; 30 memset(GpsMsg.buffer, 0, GpsMsg.maxLen); 31 GpsMsg.buffer[len++] = data; 32 crc = 0; 33 } 34 else gps_state = Start; 35 break; 36 case G: 37 if(data == 'G'){ 38 gps_state = P; 39 GpsMsg.buffer[len++] = data; 40 crc ^= data; 41 } 42 else gps_state = Start; 43 break; 44 case P: 45 if(data == 'P'){ 46 gps_state = R; 47 GpsMsg.buffer[len++] = data; 48 crc ^= data; 49 } 50 else gps_state = Start; 51 break; 52 case R: 53 if(data == 'R'){ 54 gps_state = M; 55 GpsMsg.buffer[len++] = data; 56 crc ^= data; 57 } 58 else gps_state = Start; 59 break; 60 case M: 61 if(data == 'M'){ 62 gps_state = C; 63 GpsMsg.buffer[len++] = data; 64 crc ^= data; 65 } 66 else gps_state = Start; 67 break; 68 case C: 69 if(data == 'C'){ 70 gps_state = Data; 71 GpsMsg.buffer[len++] = data; 72 crc ^= data; 73 } 74 else gps_state = Start; 75 break; 76 case Data: 77 if(data == '*'){ 78 gps_state = Check0; 79 GpsMsg.buffer[len++] = data; 80 } 81 else{ 82 gps_state = Data; 83 GpsMsg.buffer[len++] = data; 84 crc ^= data; 85 if(len>GpsMsg.maxLen) gps_state = Start; 86 } 87 break; 88 89 case Check0: 90 gps_state = Check1; 91 GpsMsg.buffer[len++] = data; 92 break; 93 case Check1: //*hh 94 gps_state = Start; 95 GpsMsg.buffer[len++] = data; 96 if(crc == ((GpsMsg.buffer[len-2]-'0')*16 + (GpsMsg.buffer[len-1]-'0'))) 97 { 98 GpsMsg.buffer[len++] = '\r'; 99 GpsMsg.buffer[len] = '\n'; 100 GpsMsg.length = len; 101 //send to gps task 102 xQueueSendFromISR(GpsQueue, (void *) &GpsMsg, 0 ); 103 } 104 } 105 106 USART_ClearITPendingBit(GPS_UART, USART_IT_RXNE); 107 }
113 }
這段代碼完成4個功能。1)串口接收數據;2)狀態機切換;3)數據校驗;4)把通過校驗的數據發給應用層。
4. GPS數據解析
應用層已經收到數據了,剩下的工作就是字元串解析了。如果只關註GPRMC信息的話,上面已經做了校驗,出錯的概率極小,那麼應用層就可以直接從收到的數據幀里提取經緯度了。
如果希望數據全部都處理,那麼在串口接收部分就不能只保留GPRMC信息,應該全部都保留然後發給應用層,應用層解析數據幀。這裡給出一個開源的例子,其中使用了多個c標準庫字元處理函數,優點是通用性強功能完備,當然在嵌入式中可能比較占記憶體,如果資源緊張可以自己寫該部分處理邏輯。
10 11 /*! \file tok.h */ 12 13 //#include "nmea/tok.h" 14 #include "tok.h" 15 #include <stdarg.h> 16 #include <stdlib.h> 17 #include <stdio.h> 18 #include <ctype.h> 19 #include <string.h> 20 #include <limits.h> 21 //#include "config.h" 22 23 #define NMEA_TOKS_COMPARE (1) 24 #define NMEA_TOKS_PERCENT (2) 25 #define NMEA_TOKS_WIDTH (3) 26 #define NMEA_TOKS_TYPE (4) 27 28 /** 29 * \brief Calculate control sum of binary buffer 30 */ 31 int nmea_calc_crc(const char *buff, int buff_sz) 32 { 33 int chsum = 0, 34 it; 35 36 for(it = 0; it < buff_sz; ++it) 37 chsum ^= (int)buff[it]; 38 39 return chsum; 40 } 41 42 /** 43 * \brief Convert string to number 44 */ 45 int nmea_atoi(const char *str, int str_sz, int radix) 46 { 47 char *tmp_ptr; 48 char buff[NMEA_CONVSTR_BUF]; 49 int res = 0; 50 51 if(str_sz < NMEA_CONVSTR_BUF) 52 { 53 memcpy(&buff[0], str, str_sz); 54 buff[str_sz] = '\0'; 55 res = strtol(&buff[0], &tmp_ptr, radix); 56 } 57 58 return res; 59 } 60 61 /** 62 * \brief Convert string to fraction number 63 */ 64 double nmea_atof(const char *str, int str_sz) 65 { 66 char *tmp_ptr; 67 char buff[NMEA_CONVSTR_BUF]; 68 double res = 0; 69 70 if(str_sz < NMEA_CONVSTR_BUF) 71 { 72 memcpy(&buff[0], str, str_sz); 73 buff[str_sz] = '\0'; 74 res = strtod(&buff[0], &tmp_ptr); 75 } 76 77 return res; 78 } 79 80 /** 81 * \brief Analyse string (specificate for NMEA sentences) 82 */ 83 int nmea_scanf(const char *buff, int buff_sz, const char *format, ...) 84 { 85 const char *beg_tok; 86 const char *end_buf = buff + buff_sz; 87 88 va_list arg_ptr; 89 int tok_type = NMEA_TOKS_COMPARE; 90 int width = 0; 91 const char *beg_fmt = 0; 92 int snum = 0, unum = 0; 93 94 int tok_count = 0; 95 void *parg_target; 96 97 va_start(arg_ptr, format); 98 99 for(; *format && buff < end_buf; ++format) 100 { 101 switch(tok_type) 102 { 103 case NMEA_TOKS_COMPARE: 104 if('%' == *format) 105 tok_type = NMEA_TOKS_PERCENT; 106 else if(*buff++ != *format) 107 goto fail; 108 break; 109 case NMEA_TOKS_PERCENT: 110 width = 0; 111 beg_fmt = format; 112 tok_type = NMEA_TOKS_WIDTH; 113 case NMEA_TOKS_WIDTH: 114 if(isdigit(*format)) 115 break; 116 { 117 tok_type = NMEA_TOKS_TYPE; 118 if(format > beg_fmt) 119 width = nmea_atoi(beg_fmt, (int)(format - beg_fmt), 10); 120 } 121 case NMEA_TOKS_TYPE: 122 beg_tok = buff; 123 124 if(!width && ('c' == *format || 'C' == *format) && *buff != format[1]) 125 width = 1; 126 127 if(width) 128 { 129 if(buff + width <= end_buf) 130 buff += width; 131 else 132 goto fail; 133 } 134 else 135 { 136 if(!format[1] || (0 == (buff = (char *)memchr(buff, format[1], end_buf - buff)))) 137 buff = end_buf; 138 } 139 140 if(buff > end_buf) 141 goto fail; 142 143 tok_type = NMEA_TOKS_COMPARE; 144 tok_count++; 145 146 parg_target = 0; width = (int)(buff - beg_tok); 147 148 switch(*format) 149 { 150 case 'c': 151 case 'C': 152 parg_target = (void *)va_arg(arg_ptr, char *); 153 if(width && 0 != (parg_target)) 154 *((char *)parg_target) = *beg_tok; 155 break; 156 case 's': 157 case 'S': 158 parg_target = (void *)va_arg(arg_ptr, char *); 159 if(width && 0 != (parg_target)) 160 { 161 memcpy(parg_target, beg_tok, width); 162 ((char *)parg_target)[width] = '\0'; 163 } 164 break; 165 case 'f': 166 case 'g': 167 case 'G': 168 case 'e': 169 case 'E': 170 parg_target = (void *)va_arg(arg_ptr, double *); 171 if(width && 0 != (parg_target)) 172 *((double *)parg_target) = nmea_atof(beg_tok, width); 173 break; 174 }; 175 176 if(parg_target) 177 break; 178 if(0 == (parg_target = (void *)va_arg(arg_ptr, int *))) 179 break; 180 if(!width) 181 break; 182 183 switch(*format) 184 { 185 case 'd': 186 case 'i': 187 snum = nmea_atoi(beg_tok, width, 10); 188 memcpy(parg_target, &snum, sizeof(int)); 189 break; 190 case 'u': 191 unum = nmea_atoi(beg_tok, width, 10); 192 memcpy(parg_target, &unum, sizeof(unsigned int)); 193 break; 194 case 'x': 195 case 'X': 196 unum = nmea_atoi(beg_tok, width, 16); 197 memcpy(parg_target, &unum, sizeof(unsigned int)); 198 break; 199 case 'o': 200 unum = nmea_atoi(beg_tok, width, 8); 201 memcpy(parg_target, &unum, sizeof(unsigned int)); 202 break; 203 default: 204 goto fail; 205 }; 206 207 break; 208 }; 209 } 210 211 fail: 212 213 va_end(arg_ptr); 214 215 return tok_count; 216 }
10 11 #ifndef __TOK_H__ 12 #define __TOK_H__ 13 14 //#include "config.h" 15 16 #ifdef __cplusplus 17 extern "C" { 18 #endif 19 20 #define NMEA_CONVSTR_BUF (256) 21 22 int nmea_calc_crc(const char *buff, int buff_sz); 23 int nmea_atoi(const char *str, int str_sz, int radix); 24 double nmea_atof(const char *str, int str_sz); 25 int nmea_printf(char *buff, int buff_sz, const char *format, ...); 26 int nmea_scanf(const char *buff, int buff_sz, const char *format, ...); 27 28 #ifdef __cplusplus 29 } 30 #endif 31 32 #endif /* __NMEA_TOK_H__ */