基于上一章的介绍,本章将介绍如何基于gd32开发板使用gps定位模块。
一、官方代码分析
正点原子的官方测试例程,测试代码的逻辑还是比较简单的,主要就是先调用函数atk_mo1218_init()进行初始化,接着就调用 SkyTraq binary 协议的 API 函数来配置ATK-MO1218 模块,配置无误后,ATK-MO1218 模块便会根据配置的测量频率不断的输出数据,接下来就调用函数 atk_mo1218_update()来获取 ATK-MO1218 模块输出的各个数据信息,然后打印至串口调试助手。
void demo_run(void)
{
uint8_t ret;
//初始化
ret = atk_mo1218_init(38400);
if (ret != 0)
{
printf("ATK-MO1218 init failed!\r\n");
while (1)
{
LED0_TOGGLE();
delay_ms(200);
}
}
//配置
ret = atk_mo1218_factory_reset(ATK_MO1218_FACTORY_RESET_REBOOT);
ret += atk_mo1218_config_output_type(ATK_MO1218_OUTPUT_NMEA, ATK_MO1218_SAVE_SRAM_FLASH);
ret += atk_mo1218_config_nmea_msg(1, 1, 1, 1, 1, 1, 0, ATK_MO1218_SAVE_SRAM_FLASH);
ret += atk_mo1218_config_position_rate(ATK_MO1218_POSITION_RATE_5HZ, ATK_MO1218_SAVE_SRAM_FLASH);
ret += atk_mo1218_config_gnss_for_navigation(ATK_MO1218_GNSS_GPS_BEIDOU, ATK_MO1218_SAVE_SRAM_FLASH);
if (ret != 0)
{
printf("ATK-MO1218 configure failed!\r\n");
while (1)
{
LED0_TOGGLE();
delay_ms(200);
}
}
while (1)
{
uint8_t ret;
atk_mo1218_time_t utc;
atk_mo1218_position_t position;
int16_t altitude;
uint16_t speed;
atk_mo1218_fix_info_t fix_info;
atk_mo1218_visible_satellite_info_t gps_satellite_info = {0};
atk_mo1218_visible_satellite_info_t beidou_satellite_info = {0};
uint8_t satellite_index;
while (1)
{
//获取 ATK-MO1218 模块输出的各个数据信息
ret = atk_mo1218_update(&utc, &position, &altitude, &speed, &fix_info, NULL, NULL, 5000);
if (ret == ATK_MO1218_EOK)
{
/* UTC */
printf("UTC Time: %04d-%02d-%02d %02d:%02d:%02d.%03d\r\n", utc.year, utc.month, utc.day, utc.hour, utc.minute, utc.second, utc.millisecond);
//经纬度 (放大了100000)
printf("Position: %d.%d'%s %d.%d'%s\r\n", position.longitude.degree / 100000, position.longitude.degree % 100000, (position.longitude.indicator == ATK_MO1218_LONGITUDE_EAST) ? "E" : "W", position.latitude.degree / 100000, position.latitude.degree % 100000, (position.latitude.indicator == ATK_MO1218_LATITUDE_NORTH) ? "N" : "S");
//海拔高度 (放大了10)
printf("Altitude: %d.%dm\r\n", altitude / 10, altitude % 10);
// 速度(放大了10)
printf("Speed: %d.%dKm/H\r\n", speed / 10, speed % 10);
//定位质量
printf("Fix quality: %s\r\n", (fix_info.quality == ATK_MO1218_GPS_UNAVAILABLE) ? "Unavailable" : ((fix_info.quality == ATK_MO1218_GPS_VALID_SPS) ? "SPS mode" : "differential GPS mode"));
//用于定位的卫星数量
printf("Satellites Used: %d\r\n", fix_info.satellite_num);
//定位方式
printf("Fix type: %s\r\n", (fix_info.type == ATK_MO1218_FIX_NOT_AVAILABLE) ? "Unavailable" : ((fix_info.type == ATK_MO1218_FIX_2D) ? "2D" : "3D"));
//用于定位的卫星编号
for (satellite_index=0; satellite_index<fix_info.satellite_num; satellite_index++)
{
if (satellite_index == 0)
{
printf("Satellite ID:");
}
printf(" %d", fix_info.satellite_id[satellite_index]);
if (satellite_index == fix_info.satellite_num - 1)
{
printf("\r\n");
}
}
//位置、水平、垂直精度因子(放大了10)
printf("PDOP: %d.%d\r\n", fix_info.pdop / 10, fix_info.pdop % 10);
printf("HDOP: %d.%d\r\n", fix_info.hdop / 10, fix_info.hdop % 10);
printf("VDOP: %d.%d\r\n", fix_info.vdop / 10, fix_info.vdop % 10);
//可见的gps,北斗卫星数量
printf("Number of GPS visible satellite: %d\r\n", gps_satellite_info.satellite_num);
printf("Number of Beidou visible satellite: %d\r\n", beidou_satellite_info.satellite_num);
printf("\r\n");
}
else
{
//ATK-MO1218模块未定位时,不输出NMEA协议的GSV语句,
//导致因获取不到可见GPS、北斗卫星的信息而超时失败,
//此时可将函数atk_mo1218_update()的入参gps_satellite_info和beidou_satellite_info传入NULL,
//从而获取未定位时的其它数据
printf("Error!\r\n");
}
delay_ms(1000);
}
}
}
值得一提的是,由于 ATK-MO1218 模块通过 UART 发送给主控芯片的数据的长度是不固定的,因此主控芯片就无法直接通过接收到数据的长度来判断 ATK-MO1218 模块传来的一帧数据是否完成。对于这种通过 UART 接收不定长数据的情况,可以通过 UART 总线是否空闲来判断一帧的传输是否完成,恰巧 STM32 的 UART 提供了总线空闲中断功能,因此可以开启 UART 的总线空闲中断,并在中断中做相应的处理。
void ATK_MO1218_UART_IRQHandler(void)
{
uint8_t tmp;
if (__HAL_UART_GET_FLAG(&g_uart_handle, UART_FLAG_ORE) != RESET)
{
__HAL_UART_CLEAR_OREFLAG(&g_uart_handle);
(void)g_uart_handle.Instance->SR;
(void)g_uart_handle.Instance->DR;
}
if (__HAL_UART_GET_FLAG(&g_uart_handle, UART_FLAG_RXNE) != RESET)
{
HAL_UART_Receive(&g_uart_handle, &tmp, 1, HAL_MAX_DELAY);
if (g_uart_rx_frame.sta.len < (ATK_MO1218_UART_RX_BUF_SIZE - 1))
{
g_uart_rx_frame.buf[g_uart_rx_frame.sta.len] = tmp;
g_uart_rx_frame.sta.len++;
}
else
{
g_uart_rx_frame.sta.len = 0;
g_uart_rx_frame.buf[g_uart_rx_frame.sta.len] = tmp;
g_uart_rx_frame.sta.len++;
}
}
if (__HAL_UART_GET_FLAG(&g_uart_handle, UART_FLAG_IDLE) != RESET)
{
g_uart_rx_frame.sta.finsh = 1;
__HAL_UART_CLEAR_IDLEFLAG(&g_uart_handle);
}
}
二、硬件
开发板使用GD32F450,串口使用USART5,引脚为PC6和PC7。杜邦线按设备要求进行连接即可。
使用了四根杜邦线连接到开发板的电源3.3V,GND和PC6、PC7引脚。
三、软件
NEMA协议是一种用于GPS设备的通信协议,它定义了一系列标准消息格式,用于传输GPS设备的位置、速度和时间等信息。在使用gd32进行NEMA协议解析时,可以采用以下步骤:
1、配置串口通信参数,如波特率、数据位、停止位和校验位等。
2、通过串口接收数据,每次接收到一条完整的NEMA协议消息后,进行解析。
3、解析消息头,判断消息类型和数据长度。
4、解析消息体,提取需要的位置、速度和时间等信息。
5、对解析出来的数据进行处理或者存储,以便后续使用。
需要注意的是,NEMA协议中的消息格式比较复杂,解析过程需要仔细处理各个字段的含义和数据类型,避免出现解析错误的情况。同时,由于GPS设备的数据传输速率较快,需要采用合适的缓存机制,确保数据不被丢失或者重复解析。
参考代码:
#include "gd32f30x.h"
#include <stdio.h>
#include <string.h>
/* 定义串口接收缓冲区大小 */
#define RX_BUF_SIZE 128
/* 定义NEMA协议消息类型枚举 */
typedef enum {
NEMA_MSG_GPGGA, // GGA消息
NEMA_MSG_GPVTG, // VTG消息
NEMA_MSG_GPGSA, // GSA消息
NEMA_MSG_GPGSV, // GSV消息
NEMA_MSG_UNKNOWN, // 未知消息
} nema_msg_type_t;
/* 定义NEMA协议消息结构体 */
typedef struct {
nema_msg_type_t type; // 消息类型
uint32_t time; // 时间
float latitude; // 纬度
float longitude; // 经度
float altitude; // 海拔高度
float speed; // 速度
float course; // 航向
uint8_t num_satellites; // 卫星数
} nema_msg_t;
/* 定义全局变量 */
static uint8_t rx_buf[RX_BUF_SIZE];
static uint8_t rx_index = 0;
static nema_msg_t nema_msg;
/* 串口接收中断处理函数 */
void USART5_IRQHandler(void)
{
if (RESET != usart_interrupt_flag_get(USART5, USART_INT_FLAG_RBNE)) {
/* 读取接收数据寄存器 */
uint8_t data = usart_data_receive(USART5);
/* 判断是否接收到换行符 */
if (data == '\n') {
/* 解析NEMA协议消息 */
if (0 == strncmp((const char *)rx_buf, "$GPGGA,", 7)) {
/* GGA消息 */
nema_msg.type = NEMA_MSG_GPGGA;
/* 解析消息体 */
sscanf((const char *)rx_buf, "$GPGGA,%lu,%f,%c,%f,%c,%d,%d,%f,%f,M,%f,M,,",
&nema_msg.time, &nema_msg.latitude, &latitude_dir, &nema_msg.longitude,
&longitude_dir, &nema_msg.fix_quality, &nema_msg.num_satellites,
&nema_msg.hdop, &nema_msg.altitude, &nema_msg.geoid_sep);
} else if (0 == strncmp((const char *)rx_buf, "$GPVTG,", 7)) {
/* VTG消息 */
nema_msg.type = NEMA_MSG_GPVTG;
/* 解析消息体 */
sscanf((const char *)rx_buf, "$GPVTG,%f,T,%f,M,%f,N,%f,K",
&nema_msg.course, &nema_msg.course_true, &nema_msg.speed, &nema_msg.speed_knots);
} else if (0 == strncmp((const char *)rx_buf, "$GPGSA,", 7)) {
/* GSA消息 */
nema_msg.type = NEMA_MSG_GPGSA;
/* 解析消息体 */
sscanf((const char *)rx_buf, "$GPGSA,%c,%d,%d,%d,%d,%d,%d,%d,%d,%f,%f,%f",
&nema_msg.fix_mode, &nema_msg.fix_type, &nema_msg.sat1, &nema_msg.sat2,
&nema_msg.sat3, &nema_msg.sat4, &nema_msg.sat5, &nema_msg.sat6,
&nema_msg.sat7, &nema_msg.pdop, &nema_msg.hdop, &nema_msg.vdop);
} else if (0 == strncmp((const char *)rx_buf, "$GPGSV,", 7)) {
/* GSV消息 */
nema_msg.type = NEMA_MSG_GPGSV;
/* 解析消息体 */
sscanf((const char *)rx_buf, "$GPGSV,%d,%d,%d",
&nema_msg.num_msgs, &nema_msg.msg_num, &nema_msg.num_sats);
} else {
/* 未知消息 */
nema_msg.type = NEMA_MSG_UNKNOWN;
}
/* 清空接收缓冲区 */
memset(rx_buf, 0, sizeof(rx_buf));
rx_index = 0;
} else {
/* 累加接收缓冲区 */
rx_buf[rx_index++] = data;
}
}
}
int main(void)
{
/* 配置串口通信参数 */
usart_deinit(USART5);
usart_baudrate_set(USART5, 38400);
usart_word_length_set(USART5, USART_WL_8BIT);
usart_stop_bit_set(USART5, USART_STB_1BIT);
usart_parity_config(USART5, USART_PM_NONE);
usart_receive_config(USART5, USART_RECEIVE_ENABLE);
usart_interrupt_enable(USART5, USART_INT_RBNE);
nvic_enable_irq(NVIC_USART5_IRQ);
usart_enable(USART5);
/* 解析NEMA协议消息 */
while (1) {
if (nema_msg.type != NEMA_MSG_UNKNOWN) {
/* 处理解析出来的数据 */
// ...
/* 清空消息结构体 */
memset(&nema_msg, 0, sizeof(nema_msg));
}
}
}
实际移植了正点原子的例程到gd32的开发板,测试如下:
获取打印的是utc时间,utc时间是8点56, 加上8则为本地时间,本地时间时16点56,和实际一致符合预期。
个人公众号:嵌入式学习与实践