?? 文章作者:二土電子
?? 關(guān)注公眾號(hào)獲取更多資料!
?? 期待大家一起學(xué)習(xí)交流!
一、GPS模塊簡介
??我們在做一些項(xiàng)目時(shí)有時(shí)會(huì)需要進(jìn)行GPS定位,獲取自身的經(jīng)緯度信息,這里使用的是中科微電子的GPS模塊ATGM336H,帶陶瓷天線。
??該模塊可以實(shí)現(xiàn)GPS定位,返回定位點(diǎn)的UTC時(shí)間和經(jīng)緯度信息。
??UTC時(shí)間是全世界公用的時(shí)間,我們用的北京時(shí)間比UTC時(shí)間快8個(gè)小時(shí)。
??在使用時(shí)必須注意以下幾點(diǎn)
- 必須在室外空曠地帶才能進(jìn)行定位;
- 定位是陶瓷天線上的小圓點(diǎn)必須朝上,上方不要有遮擋物;
- 樓間距也可能會(huì)影響定位,樓間距較小的地方可能定位失?。?/li>
二、使用方法
2.1 引腳介紹
??ATGM336H使用3.3V或者5V供電,利用串口將GPS信息發(fā)送出來,我們簡單地介紹一下它的幾個(gè)引腳
引腳 | 功能 |
---|---|
VCC | 電源正極(3.3V或5V) |
GND | 電源負(fù)極 |
TXD | 串口發(fā)送 |
RXD | 串口接收 |
??PPS引腳這里沒有用到,就不再做介紹了,有興趣的小伙伴可以自行搜索一下它的用途。
2.2 數(shù)據(jù)幀介紹
??ATGM336H利用串口發(fā)送定位信息給主控芯片,串口波特率為9600,我們最開始可以先用USB-TTL連接ATGM336H,到空曠地帶觀察一下它的輸出信息,定位成功時(shí)接收到的信息如下
??我們可以看到,ATGM336H一次會(huì)返回許多信息,其實(shí)我們只需要關(guān)注其中的“GNRMC”這條信息即可,我們簡單看一下這條信息。
$GNRMC,015135.000,A,4159.65553,N,12136.79345,E,0.52,0.00,191123,,,A*7F
- 消息ID —— $GNRMC
- 定位點(diǎn)的UTC時(shí)間 —— 015135.000
- 定位狀態(tài) —— A:定位;V:導(dǎo)航(我們進(jìn)行定位時(shí),如果該位為A表示數(shù)據(jù)有效,該位為V表示數(shù)據(jù)無效)
- 緯度 —— 4159.65553
- 緯度方向 —— N
- 經(jīng)度 —— 12136.79345
- 經(jīng)度方向 —— E
??需要注意的是,這里的經(jīng)緯度并不能直接拿來在地圖上搜索定位,而是需要進(jìn)行數(shù)據(jù)轉(zhuǎn)換之后才可以,關(guān)于數(shù)據(jù)抓換,后續(xù)的程序設(shè)計(jì)會(huì)詳細(xì)介紹。
2.3 關(guān)于不同的啟動(dòng)方式
??ATGM336H有三種不同的啟動(dòng)模式,不同模式定位成功所需的時(shí)間是不同的,我們這里來簡單描述一下這幾種啟動(dòng)模式
-
冷啟動(dòng)
冷啟動(dòng)是指在一個(gè)陌生的環(huán)境下啟動(dòng) GPS 直到 GPS 和周圍衛(wèi)星聯(lián)系并且計(jì)算出坐標(biāo)的啟動(dòng)過程。比如我們初次使用,電池耗盡導(dǎo)致星歷信息丟失,或者關(guān)機(jī)狀態(tài)下將接收機(jī)移動(dòng) 1000 公里以上距離再啟動(dòng)都屬于冷啟動(dòng),冷啟動(dòng)大概需要等待1~2分鐘才能定位成功。 -
溫啟動(dòng)
溫啟動(dòng)是指距離上次定位時(shí)間超過 2 個(gè)小時(shí)的啟動(dòng),搜星定位時(shí)間介于冷啟動(dòng)和熱啟動(dòng)之間。 -
熱啟動(dòng)
熱啟動(dòng)是指在上次關(guān)機(jī)的地方?jīng)]有過多移動(dòng)啟動(dòng) GPS,但距離上次定位時(shí)間必須小于 2 個(gè)小時(shí)。
三、前置知識(shí)
??我們在介紹程序設(shè)計(jì)之前先介紹一下一些必備的前置知識(shí),關(guān)于STM32串口的配置這里就不再詳細(xì)介紹了,具體可以到博主的STM32俗稱筆記專欄串口篇查看。這里著重介紹幾個(gè)C語言中的函數(shù)。
3.1 strstr函數(shù)
??strstr函數(shù)原型為
char *strstr( const char *str1, const char *str2 );
??該函數(shù)是在字符串str1中查找是否含有字符串str2,如果存在,返回str2在str1中第一次出現(xiàn)的地址(指針);否則返回NULL。使用strstr函數(shù)時(shí)需要包含頭文件<string.h>。
??值得注意的是,實(shí)際輸入變量都是指針,如果我們稍加設(shè)計(jì),能得到循環(huán)查找分隔符的效果,具體可以看后面在解析接收幀信息時(shí)的程序設(shè)計(jì),這里只介紹一下它的基本用法。
3.2 memset函數(shù)
??memset函數(shù)的函數(shù)原型為
void *memset(void *s, int c, size_t n);
??memset是一個(gè)初始化函數(shù),作用是將某一塊內(nèi)存中的全部設(shè)置為指定的值。
- s指向要填充的內(nèi)存塊。
- c是要被設(shè)置的值。
- n是要被設(shè)置該值的字符數(shù)。
- 返回類型是一個(gè)指向存儲(chǔ)區(qū)s的指針。
3.3 memcpy函數(shù)
??memcpy函數(shù)的函數(shù)原型為
void *memcpy(void*dest, const void *src, size_t n);
??該函數(shù)的功能是將由src指向地址為起始地址的連續(xù)n個(gè)字節(jié)的數(shù)據(jù)復(fù)制到以destin指向地址為起始地址的空間內(nèi)。
3.4 strtod函數(shù)
??strtod函數(shù)的函數(shù)原型為
double strtod(const char *nptr, char **endptr);
??strtod函數(shù)會(huì)檢查輸入的nptr字符串,跳過前面的空格字符,直到遇上數(shù)字或正負(fù)符號(hào)才開始做轉(zhuǎn)換,到出現(xiàn)非數(shù)字或字符串結(jié)束時(shí)(‘\0’)才結(jié)束轉(zhuǎn)換,并將結(jié)果返回,返回值是一個(gè)double型數(shù)值。
??若endptr不為NULL,則會(huì)將遇到不合條件而終止的nptr中的字符指針由endptr傳回。參數(shù)nptr字符串可包含正負(fù)號(hào)、小數(shù)點(diǎn)或E(e)來表示指數(shù)部分。
??使用該函數(shù)時(shí)需要包含頭文件。
四、程序設(shè)計(jì)
??下面我們來進(jìn)行程序設(shè)計(jì),我們用串口1來接收ATGM336H發(fā)送來的信息,用串口2將我們解析處理后的GPS信息發(fā)送給上位機(jī)。
4.1 串口初始化程序
??這里用到了兩個(gè)串口,我們封裝一個(gè)串口初始化函數(shù)用來初始化串口
/*
*==============================================================================
*函數(shù)名稱:uart_init
*函數(shù)功能:初始化USART
*輸入?yún)?shù):UARTx:串口幾;bound:波特率
*返回值:無
*備 注:可以修改成輸入初始化哪個(gè)USART
*==============================================================================
*/
void uart_init(UART_TypeDef UARTx,u32 bound)
{
// 相關(guān)結(jié)構(gòu)體定義
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
switch (UARTx)
{
case 0:
// 使能USART1,GPIOA時(shí)鐘
RCC_APB2PeriphClockCmd (RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 復(fù)用推挽輸出
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA.9
// USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空輸入
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA.10
// Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; // 搶占優(yōu)先級(jí)3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 子優(yōu)先級(jí)3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根據(jù)指定的參數(shù)初始化VIC寄存器
// USART 初始化設(shè)置
USART_InitStructure.USART_BaudRate = bound; // 串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 字長為8位數(shù)據(jù)格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 一個(gè)停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 無奇偶校驗(yàn)位
// 無硬件數(shù)據(jù)流控制
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收發(fā)模式
USART_Init(USART1, &USART_InitStructure); // 初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 開啟串口接收中斷
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); // 使能空閑中斷
USART_Cmd(USART1, ENABLE); // 使能串口1
break;
case 1:
// 使能USART2,GPIOA時(shí)鐘
RCC_APB1PeriphClockCmd (RCC_APB1Periph_USART2 | RCC_APB2Periph_GPIOA, ENABLE);
// USART2_TX GPIOA.2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // PA.2
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 復(fù)用推挽輸出
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA.2
// USART2_RX GPIOA.3初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // PA3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空輸入
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA.3
// Usart2 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; // 搶占優(yōu)先級(jí)3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 子優(yōu)先級(jí)3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根據(jù)指定的參數(shù)初始化VIC寄存器
// USART2 初始化設(shè)置
USART_InitStructure.USART_BaudRate = bound; // 串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 字長為8位數(shù)據(jù)格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 一個(gè)停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 無奇偶校驗(yàn)位
// 無硬件數(shù)據(jù)流控制
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收發(fā)模式
USART_Init(USART2, &USART_InitStructure); // 初始化串口2
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); // 開啟串口接收中斷
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); // 使能空閑中斷
USART_Cmd(USART2, ENABLE); // 使能串口2
break;
default:
break;
}
}
4.2 串口1接收中斷服務(wù)函數(shù)
??在進(jìn)行數(shù)據(jù)接收時(shí),我們一幀一幀地接收,直到接收到我們需要的幀之后將接接收緩沖區(qū)的數(shù)據(jù)復(fù)制到我們定義好的接收數(shù)據(jù)結(jié)構(gòu)體中。
/*
*==============================================================================
*函數(shù)名稱:USART1_IRQHandler
*函數(shù)功能:串口1中斷服務(wù)函數(shù)
*輸入?yún)?shù):無
*返回值:無
*備 注:無
*==============================================================================
*/
void USART1_IRQHandler (void)
{
u8 recContent; // 存儲(chǔ)接收內(nèi)容
// 如果串口接收到內(nèi)容
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
recContent = USART_ReceiveData(USART1); // 存儲(chǔ)接收內(nèi)容
// 如果接收到的是$($是一幀信息的開始)
// 保證每接收到新的一幀信息就從緩沖區(qū)起始位置開始存儲(chǔ)
if(recContent == '$')
{
gReceCunt = 0; // 清零幀信息計(jì)數(shù)變量
}
// 存儲(chǔ)接收到的幀信息
gUart1RcecBuf[gReceCunt ++] = recContent;
// 確定是否收到"GPRMC/GNRMC"這一幀數(shù)據(jù)
if(gUart1RcecBuf[0] == '$' && gUart1RcecBuf[4] == 'M' && gUart1RcecBuf[5] == 'C')
{
// 接收到換行(接收完了一幀信息)
if(recContent == '\n')
{
memset(receDataFrame.Frame_Buffer, 0, Frame_Buffer_Length); // 初始化接收幀信息數(shù)組
memcpy(receDataFrame.Frame_Buffer, gUart1RcecBuf, recContent); // 保存GPRMC/GNRMC這幀的數(shù)據(jù)
receDataFrame.isGetData = TRUE; // 接收成功
recContent = 0; // 清零接收幀信息接收計(jì)數(shù)變量
memset(gUart1RcecBuf, 0, UART1RX_MAX_LENGTH); // 清空串口1接收Buf
}
}
// 如果接收內(nèi)容超出最大長度,不再繼續(xù)接收
if(recContent >= UART1RX_MAX_LENGTH)
{
recContent = UART1RX_MAX_LENGTH;
}
}
}
4.3 幀信息解析
??存儲(chǔ)到需要的幀信息后,下一步就是對(duì)它進(jìn)行解析,解析時(shí)我們利用每一個(gè)數(shù)據(jù)間的逗號(hào)作為分隔符,利用strstr函數(shù)尋找逗號(hào)對(duì)應(yīng)的地址,將兩個(gè)逗號(hào)之間的信息存儲(chǔ)到我們提前定義好的解析信息存儲(chǔ)數(shù)組中。仔細(xì)分析一下“$GNRMC”幀會(huì)發(fā)現(xiàn),我們只需要找到七個(gè)逗號(hào)的地址,提取他們兩個(gè)相鄰逗號(hào)中間的字符串就可以得到GPS信息,思路介紹完了,我們來看一下程序設(shè)計(jì)。
/*
*==============================================================================
*函數(shù)名稱:Uart_Rece_Pares
*函數(shù)功能:解析串口接收內(nèi)容
*輸入?yún)?shù):無
*返回值:無
*備 注:無
*==============================================================================
*/
void Uart_Rece_Pares(void) // 串口接收內(nèi)容解析函數(shù)
{
// 注意變量類型
char *point = 0; // 逗號(hào)的地址指針
char *nextPoint = 0; // 下一個(gè)逗號(hào)的地址指針
u8 tempVar = 0; // 臨時(shí)循環(huán)變量
// 如果數(shù)據(jù)接收成功
if (receDataFrame.isGetData)
{
receDataFrame.isGetData = 0; // 清除接收成功標(biāo)志位
// for循環(huán)解析接收幀
// 總共需要找到7個(gè)逗號(hào)
for (tempVar = 0;tempVar < 7;tempVar ++)
{
// 第一次循環(huán)
if (tempVar == 0)
{
// 尋找第一個(gè)逗號(hào)
if ((point = strstr(receDataFrame.Frame_Buffer,",")) == NULL)
{
printf ("Prase Errpr!\r\n"); // 解析錯(cuò)誤
}
}
else
{
point ++; // 防止重復(fù)找到同一個(gè)逗號(hào)
// 尋找下一個(gè)逗號(hào)
// 注意strstr函數(shù)的輸入變量,是從上一個(gè)逗號(hào)之后開始找下一個(gè)逗號(hào)
if ((nextPoint = strstr(point,",")) != NULL)
{
// 存儲(chǔ)信息
switch (tempVar)
{
case 1: // UTC時(shí)間
memcpy(receDataFrame.UTCTime,point,nextPoint - point);
break;
case 2: // 數(shù)據(jù)有效標(biāo)識(shí)
memcpy(receDataFrame.UsefullFlag,point,nextPoint - point);
break;
case 3: // 緯度
memcpy(receDataFrame.latitude,point,nextPoint - point);
break;
case 4: // 緯度方向
memcpy(receDataFrame.N_S,point,nextPoint - point);
break;
case 5: // 經(jīng)度
memcpy(receDataFrame.longitude,point,nextPoint - point);
break;
case 6: // 經(jīng)度方向
memcpy(receDataFrame.E_W,point,nextPoint - point);
break;
}
point = nextPoint; // 更新上一個(gè)逗號(hào)地址指針
receDataFrame.isParseData = TRUE; // 數(shù)據(jù)解析完成
// 數(shù)據(jù)有效
if (receDataFrame.UsefullFlag[0] == 'A')
{
printf ("Data is usefull!\r\n");
}
else if (receDataFrame.UsefullFlag[0] == 'V')
{
printf ("Data is invalid!\r\n");
}
}
else
{
printf ("Prase Errpr!\r\n"); // 解析錯(cuò)誤
}
}
}
}
}
4.4 經(jīng)緯度數(shù)據(jù)解析轉(zhuǎn)換
??解析出經(jīng)緯度信息后,我們需要將它轉(zhuǎn)換成我們需要的格式,可以直接在地圖中輸入定位,拿上面的例子來介紹一下轉(zhuǎn)換方法
??上面接收到地經(jīng)緯度信息為
4159.65553,N,12136.79345,E
??換算方法和結(jié)果為文章來源:http://www.zghlxwxcb.cn/news/detail-761204.html
41 + 59.65553 / 60 = 41.994
121 + 36.79345 / 60 = 121.613
??下面我們來展示一下程序?qū)崿F(xiàn)文章來源地址http://www.zghlxwxcb.cn/news/detail-761204.html
/*
*==============================================================================
*函數(shù)名稱:Data_Transfor
*函數(shù)功能:數(shù)據(jù)轉(zhuǎn)換
*輸入?yún)?shù):無
*返回值:無
*備 注:無
*==============================================================================
*/
void Data_Transfor (void)
{
float latitude = 0; // 存儲(chǔ)緯度信息
u16 temp1 = 0; // 臨時(shí)變量1,存儲(chǔ)整數(shù)
float longitude = 0; // 存儲(chǔ)經(jīng)度信息
u16 temp2 = 0; // 臨時(shí)變量2,存儲(chǔ)整數(shù)
latitude = strtod(receDataFrame.latitude,NULL); // 字符串轉(zhuǎn)換成浮點(diǎn)數(shù)
longitude = strtod(receDataFrame.longitude,NULL); // 字符串轉(zhuǎn)換成浮點(diǎn)數(shù)
// 緯度信息處理
// 五位緯度信息
if ((latitude - 10000.0) >= 0)
{
// 前三位需要單獨(dú)拿出來組成一個(gè)數(shù)
temp1 = (((u16)latitude / 10000) % 10) * 100 + (((u16)latitude / 1000) % 10) * 10 + ((u16)latitude / 100) % 10;
latitude = latitude - (float)temp1 * 100;
latitude = (float)temp1 + latitude / 60;
printf ("latitude:%.3f\r\n",latitude);
}
else // 四位緯度信息
{
// 前兩位需要單獨(dú)拿出來組成一個(gè)數(shù)
temp1 = (((u16)latitude / 1000) % 10) * 10 + ((u16)latitude / 100) % 10;
latitude = latitude - (float)temp1 * 100;
latitude = (float)temp1 + latitude / 60;
printf ("latitude:%.3f\r\n",latitude);
}
// 經(jīng)度信息處理
// 五位經(jīng)度信息
if ((longitude - 10000.0) >= 0)
{
// 前三位需要單獨(dú)拿出來組成一個(gè)數(shù)
temp2 = (((u16)longitude / 10000) % 10) * 100 + (((u16)longitude / 1000) % 10) * 10 + ((u16)longitude / 100) % 10;
longitude = longitude - (float)temp2 * 100;
longitude = (float)temp2 + longitude / 60;
printf ("longitude:%.3f\r\n",longitude);
}
else // 四位經(jīng)度信息
{
// 前兩位需要單獨(dú)拿出來組成一個(gè)數(shù)
temp2 = (((u16)longitude / 1000) % 10) * 10 + ((u16)longitude / 100) % 10;
longitude = longitude - (float)temp2 * 100;
longitude = (float)temp2 + longitude / 60;
printf ("longitude:%.3f\r\n",longitude);
}
}
到了這里,關(guān)于【STM32外設(shè)系列】GPS定位模塊(ATGM336H)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!