GPS模块开发详解(转)

一、了解硬件我使用的GPS模块型号为UBX-M8030参看:UBX-M8030系列参看:UBX-M8030datasheet1、查看一下它的特性:多用途GNSS芯片,提供三种产品等级最多可并发接收3个GNSS(GPS、伽利略、GLONASS、北斗)行业领先的-167dBm导航灵敏度业界最低电流消耗在城市峡谷中具有绝佳的定位精度安全性和完整性保护支持所有…

大家好,又见面了,我是你们的朋友全栈君。

一、了解硬件
我使用的GPS模块型号为UBX-M8030

参看:UBX-M8030 系列
参看:UBX-M8030 datasheet

1、查看一下它的特性:
多用途 GNSS 芯片,提供三种产品等级
最多可并发接收 3 个 GNSS(GPS、伽利略、GLONASS、北斗)
行业领先的 -167 dBm 导航灵敏度
业界最低电流消耗
在城市峡谷中具有绝佳的定位精度
安全性和完整性保护
支持所有的卫星增强系统
车载级芯片的工作温度为 -40°至 +105°C
##2、原理图

可以看出与MCU相连的只有RXD、TXD、GPS_POW三个引脚

其中GPS_POW 模块主电源使能引脚:
用来使能BL9198稳压芯片输入5v输出3.3v

同4G模块一样,GPS_POW 引脚,高电平GPS工作,低电平GPS不工作。

#define    GPS_POWER_ON()            (GPIO_SetBits    (BSP_GPIOA_PORTS, BSP_GPIOA_GPS_POWEN_PINS))
#define    GPS_POWER_OFF()            (GPIO_ResetBits    (BSP_GPIOA_PORTS, BSP_GPIOA_GPS_POWEN_PINS))
1
2
这里不做过多讲解了。

二、软件部分
1、初始化
首先明确一下,我们一共使用了4个串口:

4G模块 – USART1
GPS – USART2
BLE – USART3
DEBUG – UART4

###串口初始化
其中串口初始化部分我就不讲了,参看:STM32开发 – 串口详解

需要注意的两点:
1、串口时钟使能
USART2是挂载在 APB1 下面的外设,所以使能函数为:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);  
1
2、GPIO端口模式设置
TX的GPIO工作模式为:GPIO_Mode_AF_PP;//复用推挽输出
RX的GPIO工作模式为:GPIO_Mode_IN_FLOATING;//浮空输入或者上拉输入
###DMA配置
通过DMA方式接收GPS过来的数据。

void GpsRxDMACfg( uint8_t buf[],uint16_t BufferSize)
{

    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //开启DMA1时钟
      USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);   //打开串口2 DMA接收使能  开启串口DMA接收
      
    DMA_DeInit(GPS_RxDMA_Ch);  //恢复缺省值
      DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->DR); //设置USART2发送数据寄存器
      DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)buf; //设置发送缓冲区首地址
      DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //设置外设位目标,内存缓冲区->外设寄存器
      DMA_InitStructure.DMA_BufferSize = BufferSize; //需要发送的字节数
      DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不做增加调整,调整不调整都是DMA自动实现的
      DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存缓冲区地址增加调整
      DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据宽度8位,1个字节
      DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //内存数据宽度8位,1个字节
      DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //单次传输模式
      DMA_InitStructure.DMA_Priority = DMA_Priority_High; //优先级设置
      DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //关闭内存到内存的DMA模式
      DMA_Init(GPS_RxDMA_Ch, &DMA_InitStructure); //写入配置
      DMA_Cmd(GPS_RxDMA_Ch, ENABLE); //开启DMA通信,等待接收数据
}

这里留下一个小疑问,DMA是什么作用?
参看:STM32开发 – DMA详解

三、GNSS卫星协议
参看:GNSS卫星协议

NMEA(National Marine Electronics Association) 0183协议简介
NMEA 0183 是美国国家海洋电子协会为海用电子设备制定的标准格式,是一种航海、海运方面有关于数字信号传递的标准,此标准定义了电子信号所需要的传输协议,传输数据时间。这个协议是文本格式的。大致格式如下:

NMEA0183消息输出格式 : $–sss,df1,df2,…[CR][LF]
数据标识是表示某种卫星发射。 –标识如下:

各主要 GNNS 消息内容识别码的含义如下:
GGA:时间、位置、定位数据
GLL:经纬度,UTC时间和定位状态
GSA:接收机模式和卫星工作数据,包括位置和水平/竖直稀释精度等。稀释精度(Dilution of Precision)是个地理定位
术语.一个接收器可以在同一时间得到许多颗卫星定位信息,但在精密定位上,只要四颗卫星讯号即已足够了
GSV:接收机能接收到的卫星信息,包括卫星 ID,海拔,仰角,方位角,信噪比(SNR)等
RMC:日期,时间,位置,方向,速度数据。是最常用的一个消息
VTG:方位角与对地速度
MSS:信噪比(SNR),信号强度,频率,比特率
ZDA:时间和日期数据
注: GNSS系统还含有一些未在此列出的其它信号,特定软硬件平台只能处理的特定的信号

与地理信息密切相关的消息及其所含主要内容如下,各消息之间的信息字段有出入也有重复,在一轮消息循环里,各消息相同的字段中包含相同的地理数据,可综合多个消息来获取完整的数据。

各信息内容识别码下的信号分析如下:
1. GGA(时间、位置、定位数据)
例样数据:
$–GGA,1661229.478,3723.2475,N,12158.3416,W,1,07,1.0,9.0,M,7.3,M, ,0000*18

2. GLL(经纬度,UTC时间和定位状态)
例样数据:
$–GLL,3723.2475,N,12158.3416,W,161229.487,A,0*2C

3. GSA(接收机模式和卫星工作数据,包括位置和水平/竖直稀释精度等)
例样数据:
$–GSA,A,3,07,02,26,27,09,04,15, , , , , ,1.8,1.0,1.5*33

4. GSV(接收机能接收到的卫星信息,包括卫星ID,仰角,方位角,信噪比(SNR)等)
例样数据:
$–GSV,2,1,07,07,79,048,42,02,51,062,43,26,36,256,42,27,27,138,42*71

$–GSV,2,2,07,09,23,313,42,04,19,159,41,15,12,041,42*41
这两条语句描述一个完整的卫星信息(这里共描述7颗卫星,每颗卫星的描述部分已用不同颜色标出),每颗卫星用4个段来描述:卫星ID(又称随机伪代码, PRC)、卫星高程(仰角,卫星和接收点连线与水平面的夹角)、方位角(连线在水平面上的投影与正北方向的顺时针旋转夹角)、信噪比。

5. MSS(信噪比(SNR),信号强度,频率,比特率)
例样数据:
$–MSS,55,27,318.0,100,*66

6. RMC(日期,时间,位置,方向,速度数据。是最常用的一个消息)
例样数据:
$–RMC,161229.487,A,3723.2475,N,12158.3416,W,0.13,309.62,120598, , ,A*10
这条语句基本上包含了GPS应用程序所需的全部数据:纬度、经度、速度、方向、卫星时间、状态以及磁场变量

7. VTG(方位角与对地速度)
例样数据:
$–VTG,309.62,T, ,M,0.13,N,0.2,K,A*6E

8. TXT(短文本信息传送)
例样数据:
$–TXT,01,01,01,ANTENNA OK*2B

四、数据接收
我们知道了GPS使用的是USART2,DMA接收使能
参看:STM32开发 – DMA详解

一个比较重要的函数,获取当前剩余数据量大小:

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)
1
则:
先配置DMA

GpsRxDMACfg( GpsTransferBuffer,DEF_GPS_RBUFSIZE );   
//GpsTransferBuffer为接收buffer,DEF_GPS_RBUFSIZE 为设置的接收buffer大小(512)
1
2
根据设置的接收buff大小减去当前剩余数据量,得到当前接收数据大小。

curcount = DEF_GPS_RBUFSIZE – DMA_GetCurrDataCounter( GPS_RxDMA_Ch );    
1
五、GNNS 消息解析
最重要的是将接收到的GNNS 消息解析,提取出自己想要的数据。

这部分根据GNNS 消息格式来看。

举个栗子:
$GPGSV,2,1,07,07,79,048,42,02,51,062,43,26,36,256,42,27,27,138,42*71

$GPGSV,2,2,07,09,23,313,42,04,19,159,41,15,12,041,42*41

循环到接收buffer里 ‘$’位置,然后为信息内容识别码 GPGSV,然后就是数逗号。
最后提取想要的数据。

gps.c 代码如下:

#include “gps.h” 
#include “led.h” 
#include “delay.h”                                    
#include “usart3.h”                                    
#include “stdio.h”     
#include “stdarg.h”     
#include “string.h”     
#include “math.h”
//     
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F103开发板
//ATK-S1216F8 GPS模块驱动代码       
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2015/04/11
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved                              
//        

const u32 BAUD_id[9]={4800,9600,19200,38400,57600,115200,230400,460800,921600};//模块支持波特率数组
//从buf里面得到第cx个逗号所在的位置
//返回值:0~0XFE,代表逗号所在位置的偏移.
//       0XFF,代表不存在第cx个逗号                              
u8 NMEA_Comma_Pos(u8 *buf,u8 cx)
{                 
    u8 *p=buf;
    while(cx)
    {         
        if(*buf==’*’||*buf<‘ ‘||*buf>’z’)return 0XFF;//遇到’*’或者非法字符,则不存在第cx个逗号
        if(*buf==’,’)cx–;
        buf++;
    }
    return buf-p;     
}
//m^n函数
//返回值:m^n次方.
u32 NMEA_Pow(u8 m,u8 n)
{

    u32 result=1;     
    while(n–)result*=m;    
    return result;
}
//str转换为数字,以’,’或者’*’结束
//buf:数字存储区
//dx:小数点位数,返回给调用函数
//返回值:转换后的数值
int NMEA_Str2num(u8 *buf,u8*dx)
{

    u8 *p=buf;
    u32 ires=0,fres=0;
    u8 ilen=0,flen=0,i;
    u8 mask=0;
    int res;
    while(1) //得到整数和小数的长度
    {

        if(*p==’-‘){mask|=0X02;p++;}//是负数
        if(*p==’,’||(*p==’*’))break;//遇到结束了
        if(*p==’.’){mask|=0X01;p++;}//遇到小数点了
        else if(*p>’9’||(*p<‘0’))    //有非法字符
        {    
            ilen=0;
            flen=0;
            break;
        }    
        if(mask&0X01)flen++;
        else ilen++;
        p++;
    }
    if(mask&0X02)buf++;    //去掉负号
    for(i=0;i<ilen;i++)    //得到整数部分数据
    {  
        ires+=NMEA_Pow(10,ilen-1-i)*(buf[i]-‘0’);
    }
    if(flen>5)flen=5;    //最多取5位小数
    *dx=flen;             //小数点位数
    for(i=0;i<flen;i++)    //得到小数部分数据
    {  
        fres+=NMEA_Pow(10,flen-1-i)*(buf[ilen+1+i]-‘0’);
    } 
    res=ires*NMEA_Pow(10,flen)+fres;
    if(mask&0X02)res=-res;           
    return res;
}                                   
//分析GPGSV信息
//gpsx:nmea信息结构体
//buf:接收到的GPS数据缓冲区首地址
void NMEA_GPGSV_Analysis(nmea_msg *gpsx,u8 *buf)
{

    u8 *p,*p1,dx;
    u8 len,i,j,slx=0;
    u8 posx;        
    p=buf;
    p1=(u8*)strstr((const char *)p,”$GPGSV”);
    len=p1[7]-‘0’;                                //得到GPGSV的条数
    posx=NMEA_Comma_Pos(p1,3);                     //得到可见卫星总数
    if(posx!=0XFF)gpsx->svnum=NMEA_Str2num(p1+posx,&dx);
    for(i=0;i<len;i++)
    {     
        p1=(u8*)strstr((const char *)p,”$GPGSV”);  
        for(j=0;j<4;j++)
        {      
            posx=NMEA_Comma_Pos(p1,4+j*4);
            if(posx!=0XFF)gpsx->slmsg[slx].num=NMEA_Str2num(p1+posx,&dx);    //得到卫星编号
            else break; 
            posx=NMEA_Comma_Pos(p1,5+j*4);
            if(posx!=0XFF)gpsx->slmsg[slx].eledeg=NMEA_Str2num(p1+posx,&dx);//得到卫星仰角 
            else break;
            posx=NMEA_Comma_Pos(p1,6+j*4);
            if(posx!=0XFF)gpsx->slmsg[slx].azideg=NMEA_Str2num(p1+posx,&dx);//得到卫星方位角
            else break; 
            posx=NMEA_Comma_Pos(p1,7+j*4);
            if(posx!=0XFF)gpsx->slmsg[slx].sn=NMEA_Str2num(p1+posx,&dx);    //得到卫星信噪比
            else break;
            slx++;       
        }   
         p=p1+1;//切换到下一个GPGSV信息
    }   
}
//分析BDGSV信息
//gpsx:nmea信息结构体
//buf:接收到的GPS数据缓冲区首地址
void NMEA_BDGSV_Analysis(nmea_msg *gpsx,u8 *buf)
{

    u8 *p,*p1,dx;
    u8 len,i,j,slx=0;
    u8 posx;        
    p=buf;
    p1=(u8*)strstr((const char *)p,”$BDGSV”);
    len=p1[7]-‘0’;                                //得到BDGSV的条数
    posx=NMEA_Comma_Pos(p1,3);                     //得到可见北斗卫星总数
    if(posx!=0XFF)gpsx->beidou_svnum=NMEA_Str2num(p1+posx,&dx);
    for(i=0;i<len;i++)
    {     
        p1=(u8*)strstr((const char *)p,”$BDGSV”);  
        for(j=0;j<4;j++)
        {      
            posx=NMEA_Comma_Pos(p1,4+j*4);
            if(posx!=0XFF)gpsx->beidou_slmsg[slx].beidou_num=NMEA_Str2num(p1+posx,&dx);    //得到卫星编号
            else break; 
            posx=NMEA_Comma_Pos(p1,5+j*4);
            if(posx!=0XFF)gpsx->beidou_slmsg[slx].beidou_eledeg=NMEA_Str2num(p1+posx,&dx);//得到卫星仰角 
            else break;
            posx=NMEA_Comma_Pos(p1,6+j*4);
            if(posx!=0XFF)gpsx->beidou_slmsg[slx].beidou_azideg=NMEA_Str2num(p1+posx,&dx);//得到卫星方位角
            else break; 
            posx=NMEA_Comma_Pos(p1,7+j*4);
            if(posx!=0XFF)gpsx->beidou_slmsg[slx].beidou_sn=NMEA_Str2num(p1+posx,&dx);    //得到卫星信噪比
            else break;
            slx++;       
        }   
         p=p1+1;//切换到下一个BDGSV信息
    }   
}
//分析GNGGA信息
//gpsx:nmea信息结构体
//buf:接收到的GPS数据缓冲区首地址
void NMEA_GNGGA_Analysis(nmea_msg *gpsx,u8 *buf)
{

    u8 *p1,dx;             
    u8 posx;    
    p1=(u8*)strstr((const char *)buf,”$GNGGA”);
    posx=NMEA_Comma_Pos(p1,6);                                //得到GPS状态
    if(posx!=0XFF)gpsx->gpssta=NMEA_Str2num(p1+posx,&dx);    
    posx=NMEA_Comma_Pos(p1,7);                                //得到用于定位的卫星数
    if(posx!=0XFF)gpsx->posslnum=NMEA_Str2num(p1+posx,&dx); 
    posx=NMEA_Comma_Pos(p1,9);                                //得到海拔高度
    if(posx!=0XFF)gpsx->altitude=NMEA_Str2num(p1+posx,&dx);  
}
//分析GNGSA信息
//gpsx:nmea信息结构体
//buf:接收到的GPS数据缓冲区首地址
void NMEA_GNGSA_Analysis(nmea_msg *gpsx,u8 *buf)
{

    u8 *p1,dx;             
    u8 posx; 
    u8 i;   
    p1=(u8*)strstr((const char *)buf,”$GNGSA”);
    posx=NMEA_Comma_Pos(p1,2);                                //得到定位类型
    if(posx!=0XFF)gpsx->fixmode=NMEA_Str2num(p1+posx,&dx);    
    for(i=0;i<12;i++)                                        //得到定位卫星编号
    {

        posx=NMEA_Comma_Pos(p1,3+i);                     
        if(posx!=0XFF)gpsx->possl[i]=NMEA_Str2num(p1+posx,&dx);
        else break; 
    }                  
    posx=NMEA_Comma_Pos(p1,15);                                //得到PDOP位置精度因子
    if(posx!=0XFF)gpsx->pdop=NMEA_Str2num(p1+posx,&dx);  
    posx=NMEA_Comma_Pos(p1,16);                                //得到HDOP位置精度因子
    if(posx!=0XFF)gpsx->hdop=NMEA_Str2num(p1+posx,&dx);  
    posx=NMEA_Comma_Pos(p1,17);                                //得到VDOP位置精度因子
    if(posx!=0XFF)gpsx->vdop=NMEA_Str2num(p1+posx,&dx);  
}
//分析GNRMC信息
//gpsx:nmea信息结构体
//buf:接收到的GPS数据缓冲区首地址
void NMEA_GNRMC_Analysis(nmea_msg *gpsx,u8 *buf)
{

    u8 *p1,dx;             
    u8 posx;     
    u32 temp;       
    float rs;  
    p1=(u8*)strstr((const char *)buf,”$GNRMC”);//”$GNRMC”,经常有&和GNRMC分开的情况,故只判断GPRMC.
    posx=NMEA_Comma_Pos(p1,1);                                //得到UTC时间
    if(posx!=0XFF)
    {

        temp=NMEA_Str2num(p1+posx,&dx)/NMEA_Pow(10,dx);         //得到UTC时间,去掉ms
        gpsx->utc.hour=temp/10000;
        gpsx->utc.min=(temp/100)%100;
        gpsx->utc.sec=temp%100;          
    }    
    posx=NMEA_Comma_Pos(p1,3);                                //得到纬度
    if(posx!=0XFF)
    {

        temp=NMEA_Str2num(p1+posx,&dx);              
        gpsx->latitude=temp/NMEA_Pow(10,dx+2);    //得到°
        rs=temp%NMEA_Pow(10,dx+2);                //得到’         
        gpsx->latitude=gpsx->latitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;//转换为° 
    }
    posx=NMEA_Comma_Pos(p1,4);                                //南纬还是北纬 
    if(posx!=0XFF)gpsx->nshemi=*(p1+posx);                     
     posx=NMEA_Comma_Pos(p1,5);                                //得到经度
    if(posx!=0XFF)
    {                                                  
        temp=NMEA_Str2num(p1+posx,&dx);              
        gpsx->longitude=temp/NMEA_Pow(10,dx+2);    //得到°
        rs=temp%NMEA_Pow(10,dx+2);                //得到’         
        gpsx->longitude=gpsx->longitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;//转换为° 
    }
    posx=NMEA_Comma_Pos(p1,6);                                //东经还是西经
    if(posx!=0XFF)gpsx->ewhemi=*(p1+posx);         
    posx=NMEA_Comma_Pos(p1,9);                                //得到UTC日期
    if(posx!=0XFF)
    {

        temp=NMEA_Str2num(p1+posx,&dx);                         //得到UTC日期
        gpsx->utc.date=temp/10000;
        gpsx->utc.month=(temp/100)%100;
        gpsx->utc.year=2000+temp%100;          
    } 
}
//分析GNVTG信息
//gpsx:nmea信息结构体
//buf:接收到的GPS数据缓冲区首地址
void NMEA_GNVTG_Analysis(nmea_msg *gpsx,u8 *buf)
{

    u8 *p1,dx;             
    u8 posx;    
    p1=(u8*)strstr((const char *)buf,”$GNVTG”);                             
    posx=NMEA_Comma_Pos(p1,7);                                //得到地面速率
    if(posx!=0XFF)
    {

        gpsx->speed=NMEA_Str2num(p1+posx,&dx);
        if(dx<3)gpsx->speed*=NMEA_Pow(10,3-dx);                  //确保扩大1000倍
    }
}  
//提取NMEA-0183信息
//gpsx:nmea信息结构体
//buf:接收到的GPS数据缓冲区首地址
void GPS_Analysis(nmea_msg *gpsx,u8 *buf)
{

    NMEA_GPGSV_Analysis(gpsx,buf);    //GPGSV解析
    NMEA_BDGSV_Analysis(gpsx,buf);    //BDGSV解析
    NMEA_GNGGA_Analysis(gpsx,buf);    //GNGGA解析     
    NMEA_GNGSA_Analysis(gpsx,buf);    //GPNSA解析
    NMEA_GNRMC_Analysis(gpsx,buf);    //GPNMC解析
    NMEA_GNVTG_Analysis(gpsx,buf);    //GPNTG解析
}
///UBLOX 配置代码/
检查CFG配置执行情况
返回值:0,ACK成功
      1,接收超时错误
      2,没有找到同步字符
      3,接收到NACK应答
u8 SkyTra_Cfg_Ack_Check(void)
{             
    u16 len=0,i;
    u8 rval=0;
    while((USART3_RX_STA&0X8000)==0 && len<100)//等待接收到应答   
    {

        len++;
        delay_ms(5);
    }         
    if(len<100)       //超时错误.
    {

        len=USART3_RX_STA&0X7FFF;    //此次接收到的数据长度 
        for(i=0;i<len;i++)
        {

            if(USART3_RX_BUF[i]==0X83)break;
            else if(USART3_RX_BUF[i]==0X84)
            {

                rval=3;
                break;
            }
        }
        if(i==len)rval=2;                        //没有找到同步字符
    }else rval=1;                                //接收超时错误
    USART3_RX_STA=0;                            //清除接收
    return rval;  
}
//配置SkyTra_GPS/北斗模块波特率
//baud_id:0~8,对应波特率,4800/9600/19200/38400/57600/115200/230400/460800/921600      
//返回值:0,执行成功;其他,执行失败(这里不会返回0了)
u8 SkyTra_Cfg_Prt(u8 baud_id)
{

    SkyTra_baudrate *cfg_prt=(SkyTra_baudrate *)USART3_TX_BUF;
    cfg_prt->sos=0XA1A0;        //引导序列(小端模式)
    cfg_prt->PL=0X0400;            //有效数据长度(小端模式)
    cfg_prt->id=0X05;            //配置波特率的ID 
    cfg_prt->com_port=0X00;            //操作串口1
    cfg_prt->Baud_id=baud_id;         波特率对应编号
    cfg_prt->Attributes=1;           //保存到SRAM&FLASH
    cfg_prt->CS=cfg_prt->id^cfg_prt->com_port^cfg_prt->Baud_id^cfg_prt->Attributes;
    cfg_prt->end=0X0A0D;        //发送结束符(小端模式)
    SkyTra_Send_Date((u8*)cfg_prt,sizeof(SkyTra_baudrate));//发送数据给SkyTra   
    delay_ms(200);                //等待发送完成 
    usart3_init(BAUD_id[baud_id]);    //重新初始化串口3  
    return SkyTra_Cfg_Ack_Check();//这里不会反回0,因为UBLOX发回来的应答在串口重新初始化的时候已经被丢弃了.

//配置SkyTra_GPS模块的时钟脉冲宽度
//width:脉冲宽度1~100000(us)
//返回值:0,发送成功;其他,发送失败.
u8 SkyTra_Cfg_Tp(u32 width)
{

    u32 temp=width;
    SkyTra_pps_width *cfg_tp=(SkyTra_pps_width *)USART3_TX_BUF;
    temp=(width>>24)|((width>>8)&0X0000FF00)|((width<<8)&0X00FF0000)|((width<<24)&0XFF000000);//小端模式
    cfg_tp->sos=0XA1A0;            //cfg header(小端模式)
    cfg_tp->PL=0X0700;        //有效数据长度(小端模式)
    cfg_tp->id=0X65    ;                //cfg tp id
    cfg_tp->Sub_ID=0X01;            //数据区长度为20个字节.
    cfg_tp->width=temp;          //脉冲宽度,us
    cfg_tp->Attributes=0X01;  //保存到SRAM&FLASH    
    cfg_tp->CS=cfg_tp->id^cfg_tp->Sub_ID^(cfg_tp->width>>24)^(cfg_tp->width>>16)&0XFF^(cfg_tp->width>>8)&0XFF^cfg_tp->width&0XFF^cfg_tp->Attributes;        //用户延时为0ns
    cfg_tp->end=0X0A0D;       //发送结束符(小端模式)
    SkyTra_Send_Date((u8*)cfg_tp,sizeof(SkyTra_pps_width));//发送数据给NEO-6M  
    return SkyTra_Cfg_Ack_Check();
}
//配置SkyTraF8-BD的更新速率       
//Frep:(取值范围:1,2,4,5,8,10,20,25,40,50)测量时间间隔,单位为Hz,最大不能大于50Hz
//返回值:0,发送成功;其他,发送失败.
u8 SkyTra_Cfg_Rate(u8 Frep)
{

    SkyTra_PosRate *cfg_rate=(SkyTra_PosRate *)USART3_TX_BUF;
     cfg_rate->sos=0XA1A0;        //cfg header(小端模式)
    cfg_rate->PL=0X0300;            //有效数据长度(小端模式)
    cfg_rate->id=0X0E;          //cfg rate id
    cfg_rate->rate=Frep;           //更新速率
    cfg_rate->Attributes=0X01;           //保存到SRAM&FLASH    .
    cfg_rate->CS=cfg_rate->id^cfg_rate->rate^cfg_rate->Attributes;//脉冲间隔,us
    cfg_rate->end=0X0A0D;       //发送结束符(小端模式)
    SkyTra_Send_Date((u8*)cfg_rate,sizeof(SkyTra_PosRate));//发送数据给NEO-6M 
    return SkyTra_Cfg_Ack_Check();
}
//发送一批数据给SkyTraF8-BD,这里通过串口3发送
//dbuf:数据缓存首地址
//len:要发送的字节数
void SkyTra_Send_Date(u8* dbuf,u16 len)
{

    u16 j;
    for(j=0;j<len;j++)//循环发送数据
    {

        while((USART3->SR&0X40)==0);//循环发送,直到发送完毕   
        USART3->DR=dbuf[j];  
    }    
}

其中字符串函数,和进制间转换、进制和字符串间转换有点难度。
参看:STM32开发 – 进制与字符串间的转换

六、GPS位置拾取
举个栗子:
gpsifo=092128.00,2948.63490,12127.56768,191218,12,0.57,0.015
对应:
时间分秒时,纬度,经度, RMC 时间日期,GGA卫星数,精度因子,VTG 方向,速度

其中,GPS精度因子大于7的GPS信息是不上报的

通过 GGA(时间、位置、定位数据)我们可以知道经纬度这些信息。
如得到的纬度和经度为:
2312.49700,11314.65422

具体参看方法:GPS经纬度的表示方法及换算

2312.49700,11314.65422
ddmm.mmmmm,dddmm.mmmmm

23°12′
0.49700′ * 60 = 29.82″

113°14′
0.65422′ * 60 = 39.25″
即:北纬N23°12′29.82″ 东经E113°14′39.25″

也可以这么算:
2312.49700,11314.65422
ddmm.mmmmm,dddmm.mmmmm

23°
12.49700′ / 60 = 0.2082833333°

113°
14.65422′ / 60 = 0.2442370000°
即:23.2082833333,113.2442370000

因此转换为应该为:
23.2082833333,113.2442370000
打开:GPS经纬度坐标拾取

就可以定位当前的位置。
当然你也可以反向查询当前位置的坐标地址。
参看:日常生活小技巧 – 百度地图坐标拾取

七、UBX-CFG
需要ublox-m8030设置成GPS+北斗模式,使用SBAS卫星增强系统。

查看相关手册
查看相关手册:u-blox 8 / u-blox M8 Receiver Description Including Protocol Specification

查看UBX-CFG:

我们重点关注的就是两个部分,UBX-CFG-GNSS(0x06 0x3E)和UBX-CFG-SBAS(0x06 0x16)

UBX-CFG-GNSS(0x06 0x3E)

UBX-CFG-SBAS(0x06 0x16)

UBX-CFG-NMEA(0x06 0x17)

u-center软件使用
上面的手册看完了,你以为这样我就会配置了吗?哈哈,太天真了。
我真的不会配置。。。
但是呢,u-center软件可以配置,并得到我要的报文。

1、在菜单栏选择Receiver->Port,选择对应的COM口和Baudrate

2、在菜单栏选择View->Text Console,查看相关输出信息

输出信息:

2、在菜单栏选择View->Messages View,进行配置

在Messages View中找到 UBX->CFG->GNSS(GNSS Config),点击后会出现卫星模式设置对话框,我们可以设置成GPS+BeiDou

里面的16进制码就是我们要的报文咯,通过串口发送即可配置为GPS+北斗模式。

八、ublox-m8030设置成GPS+北斗模式和使用SBAS卫星增强系统
配置 UBX->CFG->CFG

配置 UBX->CFG->PRT
设置波特率

配置UBX->CFG->NMEA
在NMEA中选择NMEA的版本号为V4.1,查看GBGSV信息,同时还可以显示出北斗的SNR值

配置UBX->CFG->GNSS
设置成GPS+北斗模式和使用SBAS卫星增强系统的报文

通过查看u-center软件可以查看定位卫星:包含GPS/BD/SBAS

串口发送代码实现:

// UBX Protocol sync chars
#define UBX_SYNC_CHAR_1             (0xB5)     // First synchronization character of UBX Protocol
#define UBX_SYNC_CHAR_2             (0x62)     // Second synchronization character of UBX Protocol
#define UBX_MIN_SIZE                (8)    // sync-2, class-1, ID-1, size-2, crc-2
#define UBX_MAX_INCOMING_SIZE        (500)
#define UBX_INVALID_INCOMING_SIZE    (0xFFFF)

typedef enum /*__attribute__((__packed__)) UBX_CLASS_ID_t*/
{

    UBX_CLASS_NAV    = 0x01,
    UBX_CLASS_RXM    = 0x02,
    UBX_CLASS_INF    = 0x04,
    UBX_CLASS_ACK    = 0x05,
    UBX_CLASS_CFG    = 0x06,
    UBX_CLASS_MON    = 0x0A,
    UBX_CLASS_AID    = 0x0B,
    UBX_CLASS_TIM    = 0x0D,
} UBX_CLASS_ID;

typedef enum /*__attribute__((__packed__)) UBX_MSG_ID_t*/
{

    // NAV Class
    UBX_MSG_NAV_POSECEF    = 0x01,
    UBX_MSG_NAV_POSLLH    = 0x02,
    UBX_MSG_NAV_STATUS    = 0x03,
    UBX_MSG_NAV_DOP        = 0x04,
    UBX_MSG_NAV_SOL        = 0x06,
    UBX_MSG_NAV_VELECEF    = 0x11,
    UBX_MSG_NAV_VELNED    = 0x12,
    UBX_MSG_NAV_TIMEGPS    = 0x20,
    UBX_MSG_NAV_TIMEUTC    = 0x21,
    UBX_MSG_NAV_CLOCK    = 0x22,
    UBX_MSG_NAV_SVINFO    = 0x30,
    UBX_MSG_NAV_SBAS    = 0x32,
    
    // RXM Class
    UBX_MSG_RXM_SVSI    = 0x20,        // SV Status Info
    
    // ACK Class
    UBX_MSG_ACK_NAK        = 0x00,        // Not Acknowledged
    UBX_MSG_ACK_ACK        = 0x01,        // Acknowledged
    
    // class CFG
    UBX_CFG_PRT        = 0x00,        // Configure port
    UBX_CFG_MSG     = 0x01,        // Configure Message Rate
    UBX_CFG_INF     = 0x02,        // Configure Protocol
    UBX_CFG_RST     = 0x04,        // Perform Reset
    UBX_CFG_DAT     = 0x06,        // Set Standard Datum
    UBX_CFG_TP         = 0x07,        // Configure Time Pulse
    UBX_CFG_RATE     = 0x08,        // Configure Nav Rates
    UBX_CFG_CFG     = 0x09,        // Save/load configuration
    UBX_CFG_RXM     = 0x11,        // Configure Low Power Mode
    UBX_CFG_ANT     = 0x13,        // Configure Antenna
    UBX_CFG_SBAS     = 0x16,        // Configure SBAS
    UBX_CFG_NMEA    = 0x17,        // Configure NMEA Protocol
    UBX_CFG_USB        = 0x1B,        // Configure USB
    UBX_CFG_TMODE    = 0x1D,        // Save/load configuration
    UBX_CFG_NAVX5    = 0x23,        // Save/load configuration
    UBX_CFG_NAV5     = 0x24,        // Save/load configuration
    UBX_CFG_GNSS     = 0x3E,        // Save/load configuration

    // class MON
    UBX_CFG_MON_IO        = 0x02,        // I/O Status
    UBX_CFG_MON_VER        = 0x04,        // HW/SW Version
    UBX_CFG_MON_MSGPP    = 0x06,        // Message Parse/Process Status
    UBX_CFG_MON_RXBUF    = 0x07,        // RX Buffer Status
    UBX_CFG_MON_TXBUF    = 0x08,        // TX Buffer Status
    UBX_CFG_MON_HW        = 0x09,        // HW Status
} UBX_MSG_ID;

/*F************************************************************************
*
*   UINT16 gps_CalcChecksum()
*
*  Calculate the 16 Bit checksum as defined in the UBX protocol spec.
*/
static uint16_t gps_CalcChecksum(const uint8_t *Payload, uint16_t Length)
{

    uint16_t i;
    uint8_t  Ck_A        = 0;        // First Byte of Checksum
    uint8_t  Ck_B        = 0;        // Second Byte of Checksum
    uint16_t Checksum    = 0;        // Final result
    if (NULL != Payload){

        for (i = 0; i < Length; i++) {

            Ck_A = Ck_A + Payload[i];
            Ck_B = Ck_B + Ck_A;
        }
        Checksum = (Ck_A<<8) | Ck_B;
    }
    return (Checksum);
}

static uint8_t sendUbxMessage(uint8_t ClassId, uint8_t MsgId, uint8_t *Payload, uint16_t PayloadLength)
{

    static uint8_t ubxMessage[100];
    uint16_t Checksum = 0xFFFF, i = 0;

    if((Payload != NULL) && /*(PayloadLength > 0) && */(PayloadLength < sizeof(ubxMessage))){

        // Fill in the PacketArray to be sent to I2C
        //  UBX_SYNC_CHAR_1 will be passed to the i2c_Write in the RegAddr field.
        //  The Data structure starts with CHAR2
        ubxMessage[i++] = UBX_SYNC_CHAR_1;
        ubxMessage[i++] = UBX_SYNC_CHAR_2;
        ubxMessage[i++] = ClassId;
        ubxMessage[i++] = MsgId;
        ubxMessage[i++] = (uint8_t)(PayloadLength & 0xff);
        ubxMessage[i++] = (uint8_t)((PayloadLength >> 8) & 0xff);
        // Now the Payload
        memcpy(&ubxMessage[i], Payload, PayloadLength);
        i += PayloadLength;
        // Finally the Checksum
        {

            Checksum = gps_CalcChecksum(&ubxMessage[2], PayloadLength+4);   // Do not include Sync Char2.  Do include Class,ID,Length,Payload
            ubxMessage[i++] = (Checksum >> 8) & 0xff;
            ubxMessage[i++] = Checksum & 0xff;
        }
        ComSendByteFra(USART2, ubxMessage, i);
        return 0;
    }else
        return 2;
}

static uint8_t configPortGPS(uint8_t EnableNMEA)
{

    Uart portID 0x1, default disable out Protocl NMEA
    uint8_t PortCfgPayload[]={0x01, 0x00, 0x00, 0x00, 0xC0, 0x08,   \
    0x00, 0x00, 0x80, 0x25, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00, \
    0x00, 0x00, 0x00, 0x00};

    if ( EnableNMEA )
    {

        PortCfgPayload[14] = 0x03;
    }

    return sendUbxMessage(UBX_CLASS_CFG,UBX_CFG_PRT, PortCfgPayload,sizeof(PortCfgPayload));
}

九、NMEA版本4.1介绍

文档里有这样一段话,建议希望使用NMEA协议的Beidou和/或Galieo客户,建议使用NMEA版本4.1,因为早期版本不支持这两个GNSS。

接下来我们需要了解一下NMEA版本4.1,如下图所示NMEA协议消息的结构:

标识符:
NMEA标准区分GNSS的方法之一是使用双字母消息标识符,‘Talker ID’。 u-blox接收器使用的特定Talker ID取决于设备型号和系统组态。
下表显示了将用于各种GNSS配置的Talker ID。

NMEA 4.1及以上版本的额外字段

消息概述
使用UBX协议消息UBX-CFG-MSG配置NMEA消息时,显示的类/ ID
应使用该表

对应的 UBX-CFG-MSG

十、卫星定位几个重要概念
参看:卫星定位几个重要概念

概念
1、GNSS的全称是全球导航卫星系统(Global Navigation Satellite System),它是泛指所有的卫星导航系统,包括全球的、区域的和增强的,如美国的GPS、俄罗斯的Glonass、欧洲的Galileo、中国的北斗卫星导航系统,以及相关的增强系统,如美国的WAAS(广域增强系统)、欧洲的EGNOS(欧洲静地导航重叠系统)和日本的MSAS(多功能运输卫星增强系统)等,还涵盖在建和以后要建设的其他卫星导航系统。
2、NMEA是美国国家海洋电子协会的简称,是GPS导航设备统一的RTCM标准协议。北斗协议和NMEA协议的数据格式一模一样,这两种协议的唯一区别是协议头不一样,如:NMEA协议:”GPGGA“,对应的北斗协议为”BDGGA“
另外,GNGGA表示GPS与北斗混合定位,比如定位的卫星共7颗,其中5颗是GPS定位,2颗是北斗定位
3、AGPS,辅助全球卫星定位系统,是一种GPS的运行方式。它可以利用手机基地站的资讯,配合传统GPS卫星,让定位的速度更快。
4、GPGSA(BDGSA)、GPGSV(BDGSV);GNGGA、GNGLL、GNRMC、GNVTG

经验分享
其中上面这段GNGGA表示GPS与北斗混合定位
困扰了我好久,我要做GPS+北斗模式定位,一开始以为可以同时获得GPS(GPGGA)和北斗(BDGGA)的定位信息。其实是不对的,GPS与北斗混合定位其定位信息为GNGGA。

比如之前获取的GPS信息:
(可用卫星数范围0~12)

这里的可用卫星数范围0~12由来:

十一、GPS飘移
什么是GPS的漂移?
用过GPS的人大概都有这种体会:当GPS终端静止的时候,其定位坐标(经纬度)经常在变,偶尔变化还比较大,甚至还会显示有速度。业内人士把这种现象称为“漂移”。

其实,GPS漂移不仅在静止的时候会产生,动态的时候也会产生,只不过漂移的程度没那么明显,产生的几率小些罢了,这是GPS的一个基本特性。(至于GPS为什么会产生漂移,了解GPS的定位原理就不难解释,在此不再详述。)

GPS的神奇就在于不论你走到哪里,它都知道你的位置坐标。然而在实际应用中,它也会让你感到难堪或委屈。

假如你是某单位司机,单位领导为了加强对车辆的管理,都装了GPS监控设备,限定车辆在某段时间内只能在某个指定的区域行驶,对违反该规定的司机作出处罚。也许某天你就会收到处罚通知:某时某刻,你违反了此项规定。而你却莫名其妙,深感委屈。如果你真的委屈了,请别怪管理人员,因为是“GPS出错了”,GPS当时发生了漂移,漂到其他一个地方而导致了“越区行驶”!

GPS漂移现象还会导致其他更多问题,如里程统计偏差较大。车辆停在单位门口一天,却显示其行驶里程为十几公里,甚至上百公里。由此可见,很多GPS应用中的问题都和“漂移现象”有关。

如果GPS漂移问题不能较好的解决,将会使越来越多的用户对GPS产生误解甚至怀疑,在一定程度上制约着GPS应用的推广。

导致GPS飘移的原因
参看:GPS定位不准及产生漂移的原因详解
参看:【GPS】导致GPS定位飘移的几种原因

1、天气情况
下雨天,空气中水分多,影响了信号的传输。这也是为什么夏季手机信号稍弱的原因,夏季雨多潮湿,再加之高温蒸发,使得空气中的水分变多,从而影响GPS信号的传输。

2.高楼因素影响GPS接受信号
在一些高层建筑物的低层或者地下建筑,如地下停车场、地下商场、地铁、隧道等,由于受到墙体的遮挡,室内信号衰减非常大,就形成了信号覆盖弱点,所以造成定位不精准,误差大等情况。

3.卫星数量
农村及偏僻地区上空安置卫星数量少,造成位置偏差大等情况。
当定位器搜索不到GPS信号而自动启用基站定位的话,则误差更大,范围几十到几百米都有可能。

4.信号传播过程
GPS信号从卫星向地面传输的过程中,会受到大气电离层、地面建筑物、森林、水面等因素 的影响,导致GPS接受器计算出现偏差,所以会出现漂移。尤其是车辆静止时,更容易出现飘移。博实结车载GPS的漂移一般是几十米范围内。

解决GPS漂移
参看:关于解决GPS定位设备:GPS静态漂移的方法
GPS漂移是GPS应用时需要处理的问题之一,漂移主要有两个方面,
第一, 速度过快,以至于GPS的响应时间短于当前运行速度,出现漂移;
第二, 在高大建筑密集或天气情况不好的地方,因为GPS信号经过多次的折、反射,造成信号误差,出现漂移。

GPS漂移的两种表现:静态位置漂移、速度(位置)漂移
静态速度漂移可以解决,不动时候为零。位置漂移是属于正常的,这是精度的问题,现在民用一般在10米以下,好的时候5米。另外测量型的精度很高,如果再用差分技术可以达到更高,不过这种产品很贵,用的少。

解决GPS漂移主要从两方面入手:

一、 主系统的设计主要减少在近距离内对GPS信号的干扰。
二、 软件处理。软件处理主要集中在导航软件处完成,导航软件会将坐标定位在道路之内,如果GPS接收到的信号超出道路的半径范围将自动过滤这个数据,并根据上次的速度及方向推算出当前点的位置。

对于GPS静态飘移,也有建议做软件判断:
1、检测到的状态为静止时,强制速度为0
2、速度为0时,强制方向为0
3、数据中的速度值为0时,就不去更新地图上的经纬度
4、通过比较上次定位数据的经纬度差的绝对值(同时包括时间)再来判断是否有慢速移动
5、对于车载终端,只能通过辅助手段来解决GPS静态飘移的问题,如通过检查ACC钥匙电的方法来检测是否为静态飘移,因为钥匙电是关闭时,车一定是不动的,另外有些GPS模块(UBLOX)可设置静止模式、行走模式、汽车模式、海上模式、飞行模式,通过设置这些参数来解决漂移问题。
6、通过判断PDOP(定位精度因子)来判断,是否要传当前这个定位经纬度数据,当PDOP值≥3.0时,定位精度会比较差,建议不要传这个经纬度数据,可以再GPGSA语句获得。
7、2D数据定位精度比较差,并且容易出现叠加飘移,所以也不建议上传数据,也可以再GPGSA里面获得。
8、在定位设备采用电子围栏方式处理,连续判断5-10次。如果该点连续判断5-10次在设置的围栏以外,可以确定该点确实处于移动状态,并出电子围栏以外,如果该点只有1次或者2次在电子围栏以外,可以判断该点是飘移出去的点,进行数据过滤,丢掉此点数据。

扩展:
参看:GPS漂移过滤算法
参看:计算两个GPS坐标的距离
参看:【GPS】GPS定位原理
参看:GPS坐标转化工具类
参看:GPS定位基本原理浅析
参看:10分钟开发 GPS 应用,了解一下
参看:GPS精度因子
参看:NMEA data

GPS定位要求要接收至少4颗卫星的位置才能完成定位,这是因为接收机的时钟和卫星的时钟存在差异,所以三颗卫星计算位置,另一颗卫星用来修正误差。

WGS84坐标系    地球坐标系,国际通用坐标系
GCJ02坐标系    火星坐标系,WGS84坐标系加密后的坐标系;Google国内地图、高德、QQ地图 使用
BD09坐标系    百度坐标系,GCJ02坐标系加密后的坐标系

HDOP(horizontal dilution of precision )水平分量精度因子:为纬度和经度等误差平方和的开根号值。
DOP值的大小与GPS定位的误差成正比,DOP值越大,定位误差越大,定位的精度就低

获取信号强度??
查看相关手册,里面没有信号强度(GPMSS),只有信噪比(GPGSV)
NMEA标准中称为SNR(信噪比)的字段通常被称为信号强度。 SNR是原始信号强度的间接但更有用的值。 它的范围从0到99,并且根据NMEA标准具有dB单位,但是各个制造商发送具有不同起始编号的不同数字范围,因此这些值本身不一定用于评估不同的单位。 给定gps中的工作值范围通常在最低值和最高值之间显示大约25到35的差异,但是0是特殊情况,并且可以显示在视图中但未被跟踪的卫星上。
信噪比越大越好!!

原文:https://juyou.blog.csdn.net/article/details/82110535 
 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/125073.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • java栈的实现_java技术栈

    java栈的实现_java技术栈一.数组实现的栈,能存储任意类型的数据。/***java使用数组来实现栈,能存储任意数据**@authorLinging*@date2019/2/10**/importjava.lang.reflect.Array;publicclassArrayStack&amp;lt;T&amp;gt;{ privatefinalstaticintDEFA…

  • Java分页原理_分页系统原理

    Java分页原理_分页系统原理Java分页常见的分页实现方式使用List接口中的subList(intstartIndex,intendIndex)方法实现分页直接使用数据库SQL语句实现分页使用hibernate等框架实现跨数据库的分页使用subList()实现分页使用数据库SQL语句实现分页mysql数据库使用limit关键字,oracle中使用rownum关键字。例如,从学生表(

  • BeanCopier_contabo测评

    BeanCopier_contabo测评概述常见或常用的几种Bean属性复制工具Apache.BeanUtilsApache.PropertyUtilSpring.BeanUtilsCglib.BeanCopierMapStructEZMorph使用场景:Dto与Entity转换普通属性复制个别属性过滤属性类型转换数组或集合拷贝性能对比测试在两个简单的Bean之间转换的耗时,执行次数分别为10、10…

  • 终端terminal个性化配置[通俗易懂]

    终端terminal个性化配置[通俗易懂]http://blog.csdn.net/pipisorry/article/details/39584489{本文介绍linux终端字体颜色设置、终端提示符显示内容设置、自定义alias命令}linux打开终端快捷键:ctrl+alt+t新窗口中打开ctrl+shift+t新标签页中打开个人配置文件介绍系统默认状态下的终端显示为紫底白字(皮皮的…

  • 怎么判断草图完全约束_算法基础课acwing下载

    怎么判断草图完全约束_算法基础课acwing下载爱丽丝和鲍勃正在玩以下游戏。首先,爱丽丝绘制一个 N 个点 M 条边的有向图。然后,鲍勃试图毁掉它。在每一步操作中,鲍勃都可以选取一个点,并将所有射入该点的边移除或者将所有从该点射出的边移除。已知,对于第 i 个点,将所有射入该点的边移除所需的花费为 W+i,将所有从该点射出的边移除所需的花费为 W−i。鲍勃需要将图中的所有边移除,并且还要使花费尽可能少。请帮助鲍勃计算最少花费。输入格式第一行包含 N 和 M。第二行包含 N 个正整数,第 i 个为 W+i。第三行包含 N 个正整数,第.

  • window openJdk 下载「建议收藏」

    window openJdk 下载「建议收藏」windowopenJDK下载

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号