STM32的GPS数据提取程序设计说明——基于NMEA0183协议
一、硬軟件平臺
本次程序實現效果為對GPS信號穿送來的數據進行篩選,并將篩選后的信息通過上位機顯示出來,所以此次設計所需硬件包括STM32F407、RS232轉TTL、CH340USB轉串口模塊,注意該模塊在使用前,對應的系統需要安裝驅動,否則串口調試助手無法識別,另外還包括JLink下載器。
本次代碼設計軟件為KEIL5并結合F4固件包,上位機系統為WIN7,主機系統為WIN10。
二、算法總體思路設計
由于GPS通過RS232將數據傳送給板子,因此使用兩個串口資源(串口1和串口2),其中一個用來接收數據,另外一個用來將篩選后的數據發送給上位機。GPS在發送信號時,相鄰兩次數據的發送之間有明顯的時間間隔,所以該間隔可作為判斷當前接收的數據是否是一個完整數據的依據。為了保證接收數據的及時性,接收數據時采用串口接收中斷,發送數據則作為主程序。因此可得到以下主程序流程圖。
三、具體實現步驟
3.1 串口1初始化
本次程序設計通過串口1將篩選后的數據發送給上位機,對應的硬件資源為PA9(USART1-TX)、PA10(USART1-RX)。通過將這兩個IO口連接CH340,實現數據傳送。串口1波特率設置為38400。
3.2 串口2初始化
串口2用來接收GPS信號,對應硬件資源為PA2(UASRT2-TX)、PA3(USART2-RX),GPS設備接口為RS232,接入單片機時,需要轉為TTL電平,注意,在連接板子和RS232轉TTL模塊的TTL輸出端時,RX和TX用反接,否則會接收不到數據。由于GPS信號發送波特率為固定的38400,所以初始化時串口2的波特率也要設置為38400。
串口2使用中斷來接收數據,因為初始化時也要配置中斷。具體代碼如下:
?
void uart2_init(u32 bound){//GPIO端口設置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA時鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART1時鐘//串口1對應引腳復用映射GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); //GPIOA2復用為USART2GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); //GPIOA3復用為USART2//USART1端口配置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; //GPIOA9與GPIOA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//復用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽復用輸出GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10//USART1 初始化設置USART_InitStructure.USART_BaudRate = bound;//波特率設置USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位數據格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬件數據流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式USART_Init(USART2, &USART_InitStructure); //初始化串口2USART_Cmd(USART2, ENABLE); //使能串口2USART_ClearFlag(USART2, USART_FLAG_TC);USART_ITConfig(USART2, USART_IT_RXNE,ENABLE);//開啟相關中斷//Usart2 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;//串口2中斷通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//搶占優先級1NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子優先級3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器TIM7_Int_Init(99,7199);USART2_RX_STA=0;TIM_Cmd(TIM7,DISABLE); //關閉定時器7}?
3.3 串口2接收中斷函數
該中斷函數是整個程序的重點所在,主要目的是將接收的字符存入數組內,這里數組名及大小定義為USART2_RX_BUF[NMEA_COUNT_MAX];其中將NMEA_COUNT_MAX設置為600,代表數組最大容量。當有數據發過來時,存入數組,并對該數組進行處理,若沒有處理完畢,則不再接收其他數據,這里定義數據接收狀態變量vu16 USART2_RX_STA。另外借助10ms定時器(TIM7)中斷判斷是不是一次連續的數據,如果接收連續2個字符之間的時間差不大于10ms則認為是,如果大于10ms則中斷觸發,強制標記數據接收完成。
void USART2_IRQHandler(void) //串口2中斷服務程序 {GPIO_ResetBits(GPIOA,GPIO_Pin_6); //LED0燈亮 char Buffer;if(SET==USART_GetITStatus(USART2,USART_IT_RXNE)){USART_ClearFlag(USART2, USART_FLAG_RXNE);Buffer = USART_ReceiveData(USART2);//接收數據if((USART2_RX_STA&(1<<15))==0)//接收完的一批數據,還沒有被處理,則不再接收其他數據{if(USART2_RX_STA<NMEA_COUNT_MAX)//還可以接收數據{TIM_SetCounter(TIM7,0);//計數器清空if(USART2_RX_STA==0){TIM_Cmd(TIM7,ENABLE);//使能定時器7}USART2_RX_BUF[USART2_RX_STA++]=Buffer;//記錄接收到的值 }else{USART2_RX_STA|=1<<15;//強制標記接收完成}}}GPIO_SetBits(GPIOA,GPIO_Pin_6);//LED0燈滅 }?
/*定時器7中斷服務函數*/ void TIM7_IRQHandler(void) { if (TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET)//是更新中斷{ USART2_RX_STA|=1<<15; //標記接收完成TIM_ClearITPendingBit(TIM7, TIM_IT_Update ); //清除TIM7更新中斷標志 TIM_Cmd(TIM7, DISABLE); //關閉TIM7 } }?
?
3.4數據解析
每次接收的數據都存放在數組里面,每個信息都有屬于自己的標識符,例如$GNRMC、!AIVDM等,所以要找到目標信息的位置,直接對數組進行字符串搜索即可。搜索算法如下圖所示,返回字符串首字符在數組中的位置。
?
/*查找字符串*/ u16 FindStr(char *str,char *ptr) {u16 index=0;char *STemp=NULL;char *PTemp=NULL;char *MTemp=NULL;if(0==str||0==ptr)return 0;for(STemp=str;*STemp!='\0';STemp++) //依次查找字符串{index++; //當前偏移量加1MTemp=STemp; //指向當前字符串//比較for(PTemp=ptr;*PTemp!='\0';PTemp++){ if(*PTemp!=*MTemp)break;MTemp++;}if(*PTemp=='\0') //出現了所要查找的字符,退出break;}return index; }得到目標信息的位置后,就可以提取信息中的數據了,NMEA數據的特點是信息中的數據之間都是用逗號隔開,所以逗號的數量就代表了該條信息含有多少個數據,通過數逗號的方法就可以得到每個數據。
以提取!AIVDM信息為例,其他信息提取方法相同。代碼中通過宏定義的方式來選擇解析和發送哪條信息。
?
/*定義AIVDM數據結構體*/ typedef struct AIVDM_data {char first[2];char second[2];char third[2];char four[5];char five[50];char six[12];}AIVDM_data;?
/*數據解析*/ #if PRINT_AIVDM //發送數據開關u8 CommaNum_AIVDM=0;//逗號數量u8 BufIndex_AIVDM=0;char Sbuf_AIVDM;char *Pstr_AIVDM;u16 index_AIVDM=0;memset(&AIVDM_data_ais,0x00,sizeof(AIVDM_data_ais));index_AIVDM=FindStr(GPS_REC_data,"!AIVDM");if(index_AIVDM){CommaNum_AIVDM=0;Pstr_AIVDM=GPS_REC_data+index_AIVDM+6;do{Sbuf_AIVDM=*Pstr_AIVDM++ ;switch(Sbuf_AIVDM){case ',':CommaNum_AIVDM++;BufIndex_AIVDM=0;break;default:switch(CommaNum_AIVDM){case 0:AIVDM_data_ais.first[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗號數量為0表示第一個數據case 1:AIVDM_data_ais.second[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗號數量為1表示第二個數據case 2:AIVDM_data_ais.third[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗號數量為2表示第三個數據case 3:AIVDM_data_ais.four[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗號數量為3表示第四個數據case 4:AIVDM_data_ais.five[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗號數量為4表示第五個數據case 5:AIVDM_data_ais.six[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗號數量為5表示第六個數據default:break;}BufIndex_AIVDM++;break;}}while(Sbuf_AIVDM!='\r');//每條信息以'\r'字符結尾}//memset(GPS_Buffer,0,sizeof(GPS_Buffer));#endif3.5 信息發送
信息發送就用普通的printf函數,不過要使用串口1函數,所以要重寫一個fputs()函數,并在頭文件中stdlib.h頭文件。在發送之前,需要對數據進行一些簡單的過濾,比較最后一個數據是信息的校驗碼,校驗碼的第二位一定是符號*等等,如果不符合過濾的條件就不發送,保證數據的正確性。
?
/*數據過濾及打印*/ #if PRINT_AIVDMif(AIVDM_data_ais.six[1]=='*'&&AIVDM_data_ais.five[0]!='0'&&AIVDM_data_ais.five[1]!='0'&&AIVDM_data_ais.five[2]!='0'&&AIVDM_data_ais.five[0]!='8'&&AIVDM_data_ais.five[1]!='8'&&AIVDM_data_ais.five[2]!='8'){printf("!AIVDM");printf(",");printf("%s",AIVDM_data_ais.first);printf(",");printf("%s",AIVDM_data_ais.second);printf(",");printf("%s",AIVDM_data_ais.third);printf(",");printf("%s",AIVDM_data_ais.four);printf(",");printf("%s",AIVDM_data_ais.five);printf(",");printf("%s\n",AIVDM_data_ais.six);}#endif3.6 主函數
各個模塊的功能已經實現了,接下來就可以根據圖2.1編寫主函數的程序,如下圖所示。
?
/*主函數大循環*/ while(1){delay_ms(1);if(USART2_RX_STA&0X8000)//接收到一次數據了{rxlen=USART2_RX_STA&0X7FFF;//得到數據長度for(i=0;i<rxlen;i++)GPS_REC_data[i]=USART2_RX_BUF[i];//將緩沖數據寫入數組中USART2_RX_STA=0;//啟動下一次接收GPSParse();//解析字符串send_data();//發送字符串}}?
四、遇到的問題及解決方案
4.1 數據打印前一半打印正常,后一半沒有數據或者是不正確的數據
原因:在前一次數據沒有解析發送完,就來了第二次數據,由于接收是采用中斷方式,所以會暫停解析發送轉而去接收數據,這就導致了上一次的數據被新的數據沖刷掉了,沖刷后的結果無法預測,可能沒有了,可能是別的。
解決方法:參考正點原子代碼。定義接收狀態標志變量USART3_RX_STA,將該變量看作一個16位的寄存器,其中0-14位代表串口接收數據的長度,第15位為1時代表不接收當前數據,為0代表接受當前數據,只有當定時器中斷觸發時即GPS一次連續的數據已經發送完畢時,手動給該位置1,此時不再接收下一次數據,當當前數據取出后即寫給另外一個數組時該位再重新置1。具體代碼見圖3.2和3.7。
4.2 ?GPS接收沒數據
原因:RS232轉TTL的TTL輸出端與PA2,PA3反接了。
解決方法:不用反接,RX接RX,TX接TX。
五、結果
六、工程下載連接
https://download.csdn.net/download/weixin_39954922/13218297
- 七、參考文獻
總結
以上是生活随笔為你收集整理的STM32的GPS数据提取程序设计说明——基于NMEA0183协议的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [vue] 写出你知道的表单修饰符和事件
- 下一篇: awvs 与 xray联动