智工运维定位器之ublox

2023-05-16

一,概述

 

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博客文章一键转载插件

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

智工运维定位器之ublox 的相关文章

随机推荐

  • 李群,李代数的几何学心得总结

    大家好 xff0c 我是飞鸽 上了研究生后听到师兄讲到李群 李代数 xff0c 一脸的懵逼 xff0c 他说 我面试搞无人机的如果不知道这个肯定不要 于是乎 xff0c 我在图书馆查询了相关书籍 xff0c 可只有一些英文的书籍 xff0c
  • Hex HERE+ RTK GPS用于自创地面站的过程记录

    教研室本着便宜的原则买了一套hex HERE 43 RTK GPS 5000人民币 xff0c 水平定位精度5cm 垂直定位精度也还可以 xff0c 忘了当时测的是多少了 xff08 以前万级的RTK xff0c 定位精度厘米级 xff09
  • u-center配置GPS的使用说明

    承接上篇 HERE 43 RTK GPS用于自创地面站的过程记录 xff0c 本文主要讲解u center如何配置HERE 43 RTK GPS xff08 一般的gps同此方法一样 xff09 基本配置可先查看网址 xff1a http
  • 对云台、IMU、鲁棒性和硬件时间同步的理解

    作者 xff1a 朱金灿 来源 xff1a clever101的专栏 为什么大多数人学不会人工智能编程 xff1f gt gt gt slam是一门集硬件和软件的多科学技术 xff0c 涉及到很多技术术语 概念以及数学公式等等 下面我将结合
  • QT无人机地面站设计与制作随笔总结

    自己不是专职地面站设计 xff0c 这里简单将经验叙述下 xff08 另外把自己一直空缺着的QT分类补一篇文章 xff09 无论是处于地面站学习的过程还是做项目进行重新整体设计 xff0c 对于这种写代码的过程最好先在MindMaster中
  • 矩阵该左乘还是右乘

    首先说明一点 xff0c 矩阵具有几何意义 xff0c 对于这几何意义的具体理解可看B站的 线性代数的本质 的视频讲解 开始展开议题 为理解记忆这个问题 xff0c 可以从方向余弦矩阵开始着手 xff0c 方向余弦矩阵是由三次旋转矩阵顺序乘
  • 《绿皮书》影评

    以一种看经典的心态看这部电影 xff0c 确实这部电影没有让我失望 很经典 xff0c 很深刻 xff0c 可能每个人都有各自的认知 xff0c 各自的感受 而我感受到了主人公如同城市孤独人的孤独感 或许 xff0c 遭受肉体上的折磨和伤害
  • Cannot find module 'body-parser'

    bug Cannot find module 39 body parser 39 原因很明显 xff0c 这个模块是没有的 xff0c 查看node modules目录下 xff0c 确实没有 解决办法 xff1a 重新安装这个模块 xff
  • gitlab安装和使用--让我们的版本管理可视化

    一 安装环境 xff1a centos6 7实验2G xff0c 生产4G安装包下载 xff08 需要翻墙 xff0c 不然下载很慢 xff09 xff1a https packages gitlab com gitlab gitlab c
  • STM32CubeMX基本使用

    视频 https www bilibili com video BV11t41147wc from 61 search amp seid 61 9347368692610984203 前言 在配置好CubeMX之后 xff0c 就是新建工程
  • make 命令

    https www ibm com support knowledgecenter zh ssw aix 71 com ibm aix cmds3 make htm 用途 维护 更新和重新生成程序组 语法 make DVariable d
  • Opencv求轮廓的中心点坐标

    Opencv求轮廓的中心点坐标 思路 xff1a 1 通过findContours找出图片中的轮廓 xff1b 2 minAreaRect找到最小外接矩形 xff1b 3 得到最小外接矩形的中心点坐标作为轮廓的中心坐标 xff1b cv s
  • 路由器二次开发一步一步把工业路由器变成一个高端的可指定出网、节点和链路的路由器,包含详细过程及快捷脚本(四)

    路由器二次开发一步一步把工业路由器变成一个高端的可指定出网 节点和链路的路由器 包含详细过程及快捷脚本 四 如果没有 路由器 可以采用 废旧的电脑 详细环境部署参考第 一 篇文章 这里采用800米的工业路由器j1900进行二次定制开发 可以
  • 路由器二次开发一步一步把工业路由器变成一个高端的可指定出网、节点和链路的路由器,包含详细过程及快捷脚本(五)

    路由器二次开发一步一步把工业路由器变成一个高端的可指定出网 节点和链路的路由器 包含详细过程及快捷脚本 五 如果没有 路由器 可以采用 废旧的电脑 详细环境部署参考第 一 篇文章 这里采用800米的工业路由器j1900进行二次定制开发 可以
  • 应用windows批处理嵌套复制文件夹

    作者 xff1a 朱金灿 来源 xff1a clever101的专栏 为什么大多数人学不会人工智能编程 xff1f gt gt gt 应用windows批处理文件将一个文件夹下的多个子文件夹复制到另一个文件夹下 xff0c 代码如下 xff
  • FreeRTOS代码剖析之4:内存管理Heap_4.c

    FreeRTOS8 0 1内存管理的最后一个堆模型Heap 4 xff0c 貌似是在这一个版本才有的 所以找到的说明几乎没有 代码的开头注释也只是简单地说了一下实现了pvPortMalloc 和vPortFree 两个函数 xff0c 并且
  • 如何在 Ubuntu 上安装 Python 3.8

    Python是一种解释型 面向对象 动态数据类型的高级程序设计语言 Python是世界上使用最广泛的编程语言之一 xff0c 由于其简单易学的语法 xff0c Python是初学者和有经验的开发者的热门选择 xff0c Python是一种相
  • (三)OpenCV中的图像处理之轮廓

    注释 xff1a 本文翻译自OpenCV3 0 0 document gt OpenCV Python Tutorials xff0c 包括对原文档种错误代码的纠正 该章节分为以下四个小节 xff1a 一 Contours xff1a Ge
  • Arduino UNO+ESP8266 WIFI+USB转TTL连接线使用EDP协议控制LED灯

    Arduino是许多智能硬件爱好者的首选 xff0c 使用简单快捷 xff0c 而ESP8266模块也是当前最为热门的WIFI模块 本项目完成了Arduino使用EDP协议通过ESP8266 WIFI模块接入OneNET服务器 xff0c
  • 智工运维定位器之ublox

    一 xff0c 概述 GNSS芯片选用了ublox的 UBX M8030 系列 xff0c 有3个型号 xff1a 可以到官网去下载相关资料 xff0c 文档还挺齐的 xff1a https www u blox com zh produc