智工运维定位器之ublox

一,概述

 

GNSS芯片选用了ublox的 UBX-M8030 系列,有3个型号:

可以到官网去下载相关资料,文档还挺齐的:
https://www.u-blox.com/zh/product/ubx-m8030-series#tab-product-selection
比较重要的几个文档有:

ReceiverDescrProtSpec芯片通讯协议配置相关,软件开发较关心
DataSheet不用说了吧
HardwareIntegrationManual硬件设计详解
PowerManagement_AppNote电源及低功耗
GNSS-Antennas_AppNote天线相关

把选型相关和我比较关心的一些特性列一下:
最多可并发接收 3 个 GNSS(GPS、伽利略、GLONASS、北斗)
定位精度2米
-148 dBm 到 -167 dBm 灵敏度
冷启动26秒定位时间,热启动只要1.5s
1个串口,默认9600,8n1,本项目只用这个通讯口,其它的USB, I2C, SPI不关心
尺寸封装:QFN, 5x5
支持低电压,支持低功耗模式(这个对我们很重要)

简单来说,如果硬件没问题,天线调试可以,那样芯片会自动每秒发送一次信息,如果已定位成功,就有位置信息了,软件只要解析报文就可以。状态机如下图,acquisition我们叫搜星,tracking就是跟踪。

 Ublox还专门提供了一个调试用的软件,叫u-center,官网可下载。安装到PC上,通过串口与芯片通讯,分析芯片发过来的位置信息报文,图文展示卫星等信息,也支持下发配置,非常方便。

二,硬件

先说明,本人软件工程师,硬件水平渣。如果之前没有研发过GPS类芯片的硬件工程师可能要好好把文档看懂才动手,尤其是天线部分,后面会专门分析该部分。芯片主体框图如下:

 我们项目中原理图如下(天线部分后面提供):

 2.1 电源


上图把几个重要的外围接口都列了出来,芯片有几个供电pin 脚,硬件要怎么供电取决于你的需求,上图的Main Supply实际上有两路电源输入,V_CORE, 和 V_DCDC,几个电源接口有着不同的功能。
V_BCKP,给RTC,RAM供电,用来保存星历,下次定位更快,功耗只有15uA
V_CORE, 搜星时功耗,只有GPS时 33.6 mA, GNSS 43.2 mA
V_DCDC_IN, 只有GPS时18.3 mA, GNSS 24.2mA
VDD_IO,通讯pin 脚供电

详细功能看官方文档,这里只截个图:

 2.2 低功耗

低功耗与性能是个矛盾关系,各人根据需求去配置,例如硬件上有以下做法可以省电:
主电源1.4v供电
使用晶振(Crystal)而不是TCXO
用UART and DDC接口通讯,而不是USB, SPI
不使用SQI Flash(ROM only version)
无源天线(性能很受影响)

在软件上芯片有两种工作模式,连续模式(Continuous Mode)和省电模式(Power Save Mode,PSM)。连续模式就是使用全部性能,全部通道去搜星,星历都下载OK后进入跟踪引擎,这时功耗会降低。

 如上图最开始阶段,芯片(acquisition engine)尽最大能力搜星,星历下载完成,得到初始位置信息后,芯片关闭acquisition engine进入跟踪状态。检测到新的卫星信号又重新启动acquisition engine,也就是上图的fix阶段。很明显,要省电最好是加长”Update Period”的时间,也就是说省电模式只在跟踪阶段。
省电模式又分两种模式:”Cyclic tracking” 和 “ON/OFF operation”,从下图可以看出,只有设置”Update Period”超过10s “ON/OFF operation”才有省电优势,这也会影响到性能,看用户怎么平衡了。

 要想进入低功耗模式,要满足以下条件:
不要使用USB接口
有RTC,或者使用"single crystal"模式
使用GPS-only 模式


芯片配置步骤:
1. disable Glonass, 只要GPS。 UBX-CFG-GNSS
2. UBX-CFG-PM2 或者 UBX-CFG-PMS,一般推荐使用Cyclic tracking
3. UBX-CFG-RXM, set the power mode to "1 - Power Save Mode"

 2.3 时钟

从前面系统框图可知,这个芯片有两路时钟输入,分别是TCXO(或晶振)和RTC时钟,参考框图如下:

主时钟是一定要有的,而且对搜星性能有影响,温漂要控制在范围内。可以根据需求,成本,性能分析是使用TCXO还是晶振,频率是26 MHz,” HardwareIntegrationManual”文档会有专门的分析,这个文档很细,把外围电路各种器件的参考物料都列了出来,买文档推荐的物料肯定没错了。
主时钟是供信号采集用的,RTC是用来备份和热保持,当芯片主电源意外掉电,芯片热启动时会用到,频率是32.768 kHz。芯片也支持单晶振(Single Crystal)模式,就是主晶振也可以作RTC用,这需要配置芯片。这样V_BCKP电源也会给主晶振供电,主电源掉的时候主晶振依然能工作。这么做能省物料成本,但是增加了功耗。

2.4 IO配置


UBX-M8030芯片有17个IO口,从PIO0到PIO16,都是由VDD_IO供电,而且电平也跟它一致。PIO0到PIO5可以连SQI Flash,SQI Flash的作用是升级芯片固件,保存配置,保存log,保留辅助定位信息。这几个pin脚也可以通过配置作为LDO输出的功能。我们项目没用SQI Flash,也没用LDO输出,所以都悬空了。

 三,天线

 天线部分我们硬件参考了” HardwareIntegrationManual”文档里面的最佳性能电路,如下图:

 我们的原理图:

 如上图所示,最左边BWGNSCNX16-6W是天线座,U11的MAX2659是一个LNA(Low Noise Amplifier,低噪声放大器)芯片,用来放大信号。U9的SAFEB1G57KE0F00R15是一个声表面波滤波器(SAW),用来滤波,特别是我们的项目中用到了lte cat.1芯片,怕信号会干扰。经过这些简单处理后信号被送到芯片,整个过程如下图:

 要求硬件把外围电路处理好,然后重点就是天线的调试了。Ublox有专门文档介绍天线相关知识(GNSS-Antennas_AppNote),建议先看看。其实如果硬件工程师射频相关经验不多的话,最好是找天线厂帮忙,我们就因为这个浪费了一,两星期的时间。GPS的信号很弱,-100dBm以上的,天线比2.4G,433M之类的天线要难搞,还是要请专业人士保险。本人这一块也薄弱,只提供几个建议:
天线分陶瓷天线和FPC天线,增益强度,指向,体积都不一样,看产品需求选用。
天线对PCB有要求,例如净空,铺地,阻抗之类的,最好先让射频工程师评估。
串口输出的报文要能看懂,u-center最好会使用,能了解和推算出信号强弱。
产品外壳也很重要,天线的位置会有关系
最好还是有专业人员参与

 四,u-center

u-center 软件的功能非常强大,我只使用了其中小部分功能,主界面如下:

 


上图左边部分用来查看串口输出数据流,配置界面等,这些界面都可以通过菜单栏的view子菜单打开。右边会把数据解析后的实时结果展示出来,包括当前搜到的星,信号强度,如果定位到的话会有位置,海拔,速度等信息。几个view的作用:
Text console 打印串口上报的报文,如上图左边部分。
Messages View 上报的报文通过NMEA协议解析出来,是字符格式,可以直接查看。下发配置报文一般用UBX协议,是二进制格式,而且有窗口显示要发送的报文内容。因为我下发的配置都是固定的,我一般在这里把要配置的内容测试OK,然后直接把二进制内容复制到代码去,省去组织报文的代码,特别是检验码是要计算出来的。
 

 

另外还有configuration view 和 statistic view用得多一点。建议调试时先把硬件调OK了再写功能代码。
 

五,软件

直接贴别人开源项目的代码,侵删:

ublox.h

/*
 Andrea Toscano
 U-BLOX NEO M8M Parser
*/
 
#ifndef UBLOX_H_INCLUDED
#define UBLOX_H_INCLUDED
 
#include <Arduino.h>
 
 
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
 
 
 
 
class Ublox
{
public:
 
    class Tokeniser
    {
    public:
        Tokeniser(char* str, char token);
 
        bool next(char* out, int len);
 
    private:
        char* str;
        char token;
    };
 
 
    struct satellite
    {
        uint8_t prn;
        int16_t elevation;
        int16_t azimuth;
        uint8_t snr; //signal to noise ratio
    };
 
    struct _datetime
    {
        uint8_t day, month, year;
        uint8_t hours, minutes, seconds;
        uint16_t millis;
        bool valid; //1 = yes, 0 = no
    };
 
    enum _fixtype { FIX_TYPE_NONE, FIX_TYPE_GPS, FIX_TYPE_DIFF };
    enum _fix { FIX_NONE = 1, FIX_2D, FIX_3D };
    enum _op_mode { MODE_MANUAL, MODE_AUTOMATIC };
 
 
    bool encode(char c);
 
    float latitude, longitude, altitude, vert_speed;
    int latlng_age, alt_age;
 
    //these units are in hundredths
    //so a speed of 5260 means 52.60km/h
    uint16_t speed, course, knots;
    int speed_age, course_age, knots_age;
 
    _fixtype fixtype; //0 = no fix, 1 = satellite only, 2 = differential fix
    int fixtype_age;
    _fix fix;
    int fix_age;
 
    float pdop, hdop, vdop; //positional, horizontal and vertical dilution of precision
    int dop_age;
 
    int8_t sats_in_use;
    int8_t sats_in_view;
 
    satellite sats[12];
    int sats_age;
 
    _datetime datetime;
    int time_age, date_age;
 
    _op_mode op_mode;
 
private:
 
    bool check_checksum();
 
    uint8_t parse_hex(char h);
    bool process_buf();
 
    char buf[120];
    uint8_t pos;
 
    void read_gga();
    void read_gsa();
    void read_gsv();
    void read_rmc();
    void read_vtg();
 
};
 
//extern Ublox gps;
 
#endif // UBLOX_H_INCLUDED

ublox.c

#include "Ublox.h"
 
Ublox::Tokeniser::Tokeniser(char* _str, char _token)
{
    str = _str;
    token = _token;
}
 
 
bool Ublox::Tokeniser::next(char* out, int len)
{
    uint8_t count = 0;
 
    if(str[0] == 0)
        return false;
 
    while(true)
    {
        if(str[count] == '\0')
        {
            out[count] = '\0';
            str = &str[count];
            return true;
        }
 
        if(str[count] == token)
        {
            out[count] = '\0';
            count++;
            str = &str[count];
            return true;
        }
 
        if(count < len)
            out[count] = str[count];
 
        count++;
    }
    return false;
}
 
 
bool Ublox::encode(char c)
{
    buf[pos] = c;
    pos++;
 
    if(c == '\n') //linefeed
    {
        bool ret = process_buf();
        memset(buf, '\0', 120);
        pos = 0;
        return ret;
    }
 
    if(pos >= 120) //avoid a buffer overrun
    {
        memset(buf, '\0', 120);
        pos = 0;
    }
    return false;
}
 
 
bool Ublox::process_buf()
{
    if(!check_checksum()) //if checksum is bad
    {
        return false; //return
    }
 
    //otherwise, what sort of message is it
    if(strncmp(buf, "$GNGGA", 6) == 0)
    
    {
        read_gga();
    }
    if(strncmp(buf, "$GNGSA", 6) == 0)
    {
        read_gsa();
    }
 
    if(strncmp(buf, "$GPGSV", 6) == 0)
    {
        read_gsv();
    }
 
    if(strncmp(buf, "$GNRMC", 6) == 0)
    
    {
        read_rmc();
    }
    if(strncmp(buf, "$GNVTG", 6) == 0)
    {
        read_vtg();
    }
    return true;
}
 
// GNGGA 
void Ublox::read_gga()
{
    int counter = 0;
    char token[20];
    Tokeniser tok(buf, ',');
 
    while(tok.next(token, 20))
    {
        switch(counter)
        {
        case 1: //time
        {
            float time = atof(token);
            int hms = int(time);
 
            datetime.millis = time - hms;
            datetime.seconds = fmod(hms, 100);
            hms /= 100;
            datetime.minutes = fmod(hms, 100);
            hms /= 100;
            datetime.hours = hms;
 
            time_age = millis();
        }
        break;
        case 2: //latitude
        {
            float llat = atof(token);
            int ilat = llat/100;
            double mins = fmod(llat, 100);
            latitude = ilat + (mins/60);
        }
        break;
        case 3: //north/south
        {
            if(token[0] == 'S')
                latitude = -latitude;
        }
        break;
        case 4: //longitude
        {
            float llong = atof(token);
            int ilat = llong/100;
            double mins = fmod(llong, 100);
            longitude = ilat + (mins/60);
        }
        break;
        case 5: //east/west
        {
            if(token[0] == 'W')
                longitude = -longitude;
            latlng_age = millis();
        }
        break;
        case 6:
        {
            fixtype = _fixtype(atoi(token));
        }
        break;
        case 7:
        {
            sats_in_use = atoi(token);
        }
        break;
        case 8:
        {
            hdop = atoi(token);
        }
        break;
        case 9:
        {
            float new_alt = atof(token);
            vert_speed = (new_alt - altitude)/((millis()-alt_age)/1000.0);
            altitude = atof(token);
            alt_age = millis();
        }
        break;
        }
        counter++;
    }
}
 
 
void Ublox::read_gsa()
{
    int counter = 0;
    char token[20];
    Tokeniser tok(buf, ',');
 
    while(tok.next(token, 20))
    {
        switch(counter)
        {
        case 1: //operating mode
        {
            if(token[0] == 'A')
                op_mode = MODE_AUTOMATIC;
            if(token[0] == 'M')
                op_mode = MODE_MANUAL;
        }
        break;
        case 2:
        {
            fix = _fix(atoi(token));
            fix_age = millis();
        }
        break;
        case 14:
        {
            pdop = atof(token);
        }
        break;
        case 15:
        {
            hdop = atof(token);
        }
        break;
        case 16:
        {
            vdop = atof(token);
            dop_age = millis();
        }
        break;
        }
        counter++;
    }
}
 
 
void Ublox::read_gsv()
{
    char token[20];
    Tokeniser tok(buf, ',');
 
    tok.next(token, 20);
    tok.next(token, 20);
 
    tok.next(token, 20);
    int mn = atoi(token); //msg number
 
    tok.next(token, 20);
    sats_in_view = atoi(token); //number of sats
 
    int8_t j = (mn-1) * 4;
    int8_t i;
 
    for(i = 0; i <= 3; i++)
    {
        tok.next(token, 20);
        sats[j+i].prn = atoi(token);
 
        tok.next(token, 20);
        sats[j+i].elevation = atoi(token);
 
        tok.next(token, 20);
        sats[j+i].azimuth = atoi(token);
 
        tok.next(token, 20);
        sats[j+i].snr = atoi(token);
    }
    sats_age = millis();
}
 
 
void Ublox::read_rmc()
{
    int counter = 0;
    char token[20];
    Tokeniser tok(buf, ',');
 
    while(tok.next(token, 20))
    {
        switch(counter)
        {
        case 1: //time
        {
            float time = atof(token);
            int hms = int(time);
 
            datetime.millis = time - hms;
            datetime.seconds = fmod(hms, 100);
            hms /= 100;
            datetime.minutes = fmod(hms, 100);
            hms /= 100;
            datetime.hours = hms;
 
            time_age = millis();
        }
        break;
        case 2:
        {
            if(token[0] == 'A')
                datetime.valid = true;
            if(token[0] == 'V')
                datetime.valid = false;
        }
        break;
        /*
        case 3:
        {
            float llat = atof(token);
            int ilat = llat/100;
            double latmins = fmod(llat, 100);
            latitude = ilat + (latmins/60);
        }
        break;
        case 4:
        {
            if(token[0] == 'S')
                latitude = -latitude;
        }
        break;
        case 5:
        {
            float llong = atof(token);
            float ilat = llong/100;
            double lonmins = fmod(llong, 100);
            longitude = ilat + (lonmins/60);
        }
        break;
        case 6:
        {
             if(token[0] == 'W')
                longitude = -longitude;
            latlng_age = millis();
        }
        break;
        */
        case 8:
        {
            course = atof(token);
            course_age = millis();
        }
        break;
        case 9:
        {
            uint32_t date = atoi(token);
            datetime.year = fmod(date, 100);
            date /= 100;
            datetime.month = fmod(date, 100);
            datetime.day = date / 100;
            date_age = millis();
        }
        break;
        }
        counter++;
    }
}
 
void Ublox::read_vtg()
{
    int counter = 0;
    char token[20];
    Tokeniser tok(buf, ',');
 
    while(tok.next(token, 20))
    {
        switch(counter)
        {
        case 1:
        {
            course = (atof(token)*100);
            course_age = millis();
        }
        break;
        case 5:
        {
            knots = (atof(token)*100);
            knots_age = millis();
        }
        break;
        case 7:
        {
            speed = (atof(token)*100);
            speed_age = millis();
        }
        break;
        }
        counter++;
    }
}
 
 
bool Ublox::check_checksum()
{
    if (buf[strlen(buf)-5] == '*')
    {
        uint16_t sum = parse_hex(buf[strlen(buf)-4]) * 16;
        sum += parse_hex(buf[strlen(buf)-3]);
 
        for (uint8_t i=1; i < (strlen(buf)-5); i++)
            sum ^= buf[i];
        if (sum != 0)
            return false;
 
        return true;
    }
    return false;
}
 
 
uint8_t Ublox::parse_hex(char c)
{
    if (c < '0')
        return 0;
    if (c <= '9')
        return c - '0';
    if (c < 'A')
        return 0;
    if (c <= 'F')
        return (c - 'A')+10;
    return 0;
}

代码是c++写的,单片机不好支持,我修改为c了。代码的使用较简单,GPS串口发过来的每一个字节都丢到encode函数去,如果检测到换行符'\n',会调用process_buf函数解析,解析后得到时间,经纬度等值。

end


---------------------
作者:歌维
来源:CSDN
原文:https://blog.csdn.net/gavinpeng/article/details/120676552
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值