无线定位之 三 SX1302 网关源码 thread_gps 线程详解

news2025/5/13 12:02:44

前言

笔者计划通过无线定位系列文章、系统的描述 TDOA 无线定位和混合定位相关技术知识点,
并以实践来验证此定位系统精度。

笔者从实践出发、本篇直接走读无线定位系统关键节点、网关 SX1302 源码框架,并在源码走读过程
中、着重分析与无线定位相关的PPS时间的来龙去脉、并在后期文章中以实际代码讲解 TDOA 无线定位
实现过程及多网关综合定位内容,敬请期待。

semtech 公司在 2020年06月份推出 LR1110\LR1120 两款GNSS、WIFI和Lora(LR-HFSS)混合
定位芯片、并提供’定位云服务’的接入、国内与腾讯云合作,腾讯云也提供定位云服务接入,这是
笔者对混合无线定位技术背景简单描述、此用意看官自行审度。

第1节 主程序代码走读

主线程基本功能:
<1>. 读取 *.conf.json 文件内容、并解析内容把变量赋值到相关全局变量中;
<2>. 启动各子线程、子线程清单如下所述;
<3>. 固定周期定时检测gps的时间戳、并上报网关的状态信息;
<4>. 等待退出信号量、网络断开信号量和各子线程退出.

子线程清单.

/* threads */
void thread_up(void);               //> 上行线程:负责接收lora模块的数据、并把数据通过网络上传至网络服务器;
void thread_down(void);             //> 下行线程:负责接收服务器的数据,并把数据通过lora无线下方给终端模块;
void thread_jit(void);              //> jit 下行数据处理线程
void thread_gps(void);              //> gps 线程时间同步线程
void thread_valid(void);            //> 时钟校正线程
void thread_spectral_scan(void);    //> SX1261 SCAN扫描线程

主程序源码基本功能就这么多,笔者就不贴出源码对照了,下面进入我们本章主题 thread_gps 线程的代码走读。

第2节 thread_gps 程序框架描述

2.1 thread_gps 线程通讯参数

此gps线程是针对 ubx-7 模块设计、串口参数配置内容如下:

 "gateway_conf": {
        "gateway_ID": "AA555A0000000000",
        /* change with default server address/ports */
        "server_address": "localhost",
        "serv_port_up": 1730,
        "serv_port_down": 1730,
        /* adjust the following parameters for your network */
        "keepalive_interval": 10,
        "stat_interval": 30,
        "push_timeout_ms": 100,
        /* forward only valid packets */
        "forward_crc_valid": true,
        "forward_crc_error": false,
        "forward_crc_disabled": false,
        /* GPS configuration */
        "gps_tty_path": "/dev/ttyS0",
        /* GPS reference coordinates */
        "ref_latitude": 0.0,
        "ref_longitude": 0.0,
        "ref_altitude": 0,
        /* Beaconing parameters */
        "beacon_period": 0,
        "beacon_freq_hz": 869525000,
        "beacon_datarate": 9,
        "beacon_bw_hz": 125000,
        "beacon_power": 14,
        "beacon_infodesc": 0
    },

配置文件中 “gps_tty_path”: “/dev/ttyS0” 指定 UBX-7 模块硬件连接到串口1上,通讯参数配置及串口打开如下:

/* Start GPS a.s.a.p., to allow it to lock */
    if (gps_tty_path[0] != '\0') { /* do not try to open GPS device if no path set */
        i = lgw_gps_enable(gps_tty_path, "ubx7", 0, &gps_tty_fd); /* HAL only supports u-blox 7 for now */
        if (i != LGW_GPS_SUCCESS) {
            printf("WARNING: [main] impossible to open %s for GPS sync (check permissions)\n", gps_tty_path);
            gps_enabled = false;
            gps_ref_valid = false;
        } else {
            printf("INFO: [main] TTY port %s open for GPS synchronization\n", gps_tty_path);
            gps_enabled = true;
            gps_ref_valid = false;
        }
    }

//> 此段源码路径 @libloragw/src/loragw_gps.c

int lgw_gps_enable(char *tty_path, char *gps_family, speed_t target_brate, int *fd_ptr) {
    int i;
    struct termios ttyopt; /* serial port options */
    int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */
    uint8_t ubx_cmd_timegps[UBX_MSG_NAVTIMEGPS_LEN] = {
                    0xB5, 0x62, /* UBX Sync Chars */
                    0x06, 0x01, /* CFG-MSG Class/ID */
                    0x08, 0x00, /* Payload length */
                    0x01, 0x20, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, /* Enable NAV-TIMEGPS output on serial */
                    0x32, 0x94 }; /* Checksum */
    ssize_t num_written;

    /* check input parameters */
    CHECK_NULL(tty_path);
    CHECK_NULL(fd_ptr);

    /* open TTY device */
    gps_tty_dev = open(tty_path, O_RDWR | O_NOCTTY);
    if (gps_tty_dev <= 0) {
        DEBUG_MSG("ERROR: TTY PORT FAIL TO OPEN, CHECK PATH AND ACCESS RIGHTS\n");
        return LGW_GPS_ERROR;
    }
    *fd_ptr = gps_tty_dev;

    /* manage the different GPS modules families */
    if (gps_family == NULL) {
        DEBUG_MSG("WARNING: this version of GPS module may not be supported\n");
    } else if (strncmp(gps_family, "ubx7", 4) != 0) {
        /* The current implementation relies on proprietary messages from U-Blox */
        /* GPS modules (UBX, NAV-TIMEGPS...) and has only be tested with a u-blox 7. */
        /* Those messages allow to get NATIVE GPS time (no leap seconds) required */
        /* for class-B handling and GPS synchronization */
        /* see lgw_parse_ubx() function for details */
        DEBUG_MSG("WARNING: this version of GPS module may not be supported\n");
    }

    /* manage the target bitrate */
    if (target_brate != 0) {
        DEBUG_MSG("WARNING: target_brate parameter ignored for now\n"); // TODO
    }

    /* get actual serial port configuration */
    i = tcgetattr(gps_tty_dev, &ttyopt);
    if (i != 0) {
        DEBUG_MSG("ERROR: IMPOSSIBLE TO GET TTY PORT CONFIGURATION\n");
        return LGW_GPS_ERROR;
    }

    /* Save current serial port configuration for restoring later */
    memcpy(&ttyopt_restore, &ttyopt, sizeof ttyopt);

    /* update baudrates */
    cfsetispeed(&ttyopt, DEFAULT_BAUDRATE);   //>DEFAULT_BAUDRATE=B9600
    cfsetospeed(&ttyopt, DEFAULT_BAUDRATE);

    /* update terminal parameters */
    /* The following configuration should allow to:
            - Get ASCII NMEA messages
            - Get UBX binary messages
            - Send UBX binary commands
        Note: as binary data have to be read/written, we need to disable
              various character processing to avoid loosing data */
    /* Control Modes */
    ttyopt.c_cflag |= CLOCAL;  /* local connection, no modem control */
    ttyopt.c_cflag |= CREAD;   /* enable receiving characters */
    ttyopt.c_cflag |= CS8;     /* 8 bit frames */
    ttyopt.c_cflag &= ~PARENB; /* no parity */
    ttyopt.c_cflag &= ~CSTOPB; /* one stop bit */
    /* Input Modes */
    ttyopt.c_iflag |= IGNPAR;  /* ignore bytes with parity errors */
    ttyopt.c_iflag &= ~ICRNL;  /* do not map CR to NL on input*/
    ttyopt.c_iflag &= ~IGNCR;  /* do not ignore carriage return on input */
    ttyopt.c_iflag &= ~IXON;   /* disable Start/Stop output control */
    ttyopt.c_iflag &= ~IXOFF;  /* do not send Start/Stop characters */
    /* Output Modes */
    ttyopt.c_oflag = 0;        /* disable everything on output as we only write binary */
    /* Local Modes */
    ttyopt.c_lflag &= ~ICANON; /* disable canonical input - cannot use with binary input */
    ttyopt.c_lflag &= ~ISIG;   /* disable check for INTR, QUIT, SUSP special characters */
    ttyopt.c_lflag &= ~IEXTEN; /* disable any special control character */
    ttyopt.c_lflag &= ~ECHO;   /* do not echo back every character typed */
    ttyopt.c_lflag &= ~ECHOE;  /* does not erase the last character in current line */
    ttyopt.c_lflag &= ~ECHOK;  /* do not echo NL after KILL character */

    /* settings for non-canonical mode
       read will block for until the lesser of VMIN or requested chars have been received */
    ttyopt.c_cc[VMIN]  = LGW_GPS_MIN_MSG_SIZE;
    ttyopt.c_cc[VTIME] = 0;

    /* set new serial ports parameters */
    i = tcsetattr(gps_tty_dev, TCSANOW, &ttyopt);
    if (i != 0){
        DEBUG_MSG("ERROR: IMPOSSIBLE TO UPDATE TTY PORT CONFIGURATION\n");
        return LGW_GPS_ERROR;
    }
    tcflush(gps_tty_dev, TCIOFLUSH);

    /* Send UBX CFG NAV-TIMEGPS message to tell GPS module to output native GPS time */
    /* This is a binary message, serial port has to be properly configured to handle this */
    num_written = write (gps_tty_dev, ubx_cmd_timegps, UBX_MSG_NAVTIMEGPS_LEN);
    if (num_written != UBX_MSG_NAVTIMEGPS_LEN) {
        DEBUG_MSG("ERROR: Failed to write on serial port (written=%d)\n", (int) num_written);
    }

    /* get timezone info */
    tzset();

    /* initialize global variables */
    gps_time_ok = false;
    gps_pos_ok = false;
    gps_mod = 'N';

    return LGW_GPS_SUCCESS;
}

主程序启动时打开串口、并配置 UBX-7 模块的工作参数,启动 thread_gps 线程时就直接读取gps串口数据就可以。

2.2 thread_gps 程序框架


void thread_gps(void) {
    /* serial variables */
    char serial_buff[128]; /* buffer to receive GPS data */
    size_t wr_idx = 0;     /* pointer to end of chars in buffer */

    /* variables for PPM pulse GPS synchronization */
    enum gps_msg latest_msg; /* keep track of latest NMEA message parsed */

    /* initialize some variables before loop */
    memset(serial_buff, 0, sizeof serial_buff);

    while (!exit_sig && !quit_sig) {
        size_t rd_idx = 0;
        size_t frame_end_idx = 0;

        /* blocking non-canonical read on serial port,阻塞式读取 gps 串口数据内容 */
        ssize_t nb_char = read(gps_tty_fd, serial_buff + wr_idx, LGW_GPS_MIN_MSG_SIZE);
        if (nb_char <= 0) {
            MSG("WARNING: [gps] read() returned value %zd\n", nb_char);
            continue;
        }
        wr_idx += (size_t)nb_char;

        /*******************************************
         * Scan buffer for UBX/NMEA sync chars and *
         * attempt to decode frame if one is found *
         *******************************************/
        while (rd_idx < wr_idx) {
            size_t frame_size = 0;

            /* Scan buffer for UBX sync char */
            if (serial_buff[rd_idx] == (char)LGW_GPS_UBX_SYNC_CHAR) {

                /***********************
                 * Found UBX sync char *
                 ***********************/
                latest_msg = lgw_parse_ubx(&serial_buff[rd_idx], (wr_idx - rd_idx), &frame_size);

                if (frame_size > 0) {
                    if (latest_msg == INCOMPLETE) {
                        /* UBX header found but frame appears to be missing bytes */
                        frame_size = 0;
                    } else if (latest_msg == INVALID) {
                        /* message header received but message appears to be corrupted */
                        MSG("WARNING: [gps] could not get a valid message from GPS (no time)\n");
                        frame_size = 0;
                    } else if (latest_msg == UBX_NAV_TIMEGPS) {
                        gps_process_sync();
                    }
                }
            } else if (serial_buff[rd_idx] == (char)LGW_GPS_NMEA_SYNC_CHAR) {
                /************************
                 * Found NMEA sync char *
                 ************************/
                /* scan for NMEA end marker (LF = 0x0a) */
                char* nmea_end_ptr = memchr(&serial_buff[rd_idx],(int)0x0a, (wr_idx - rd_idx));

                if(nmea_end_ptr) {
                    /* found end marker */
                    frame_size = nmea_end_ptr - &serial_buff[rd_idx] + 1;
                    latest_msg = lgw_parse_nmea(&serial_buff[rd_idx], frame_size);

                    if(latest_msg == INVALID || latest_msg == UNKNOWN) {
                        /* checksum failed */
                        frame_size = 0;
                    } else if (latest_msg == NMEA_RMC) { /* Get location from RMC frames */
                        gps_process_coords();
                    }
                }
            }

            if (frame_size > 0) {
                /* At this point message is a checksum verified frame
                   we're processed or ignored. Remove frame from buffer */
                rd_idx += frame_size;
                frame_end_idx = rd_idx;
            } else {
                rd_idx++;
            }
        } 

        if (frame_end_idx) {
          /* Frames have been processed. Remove bytes to end of last processed frame */
          memcpy(serial_buff, &serial_buff[frame_end_idx], wr_idx - frame_end_idx);
          wr_idx -= frame_end_idx;
        } 

        /* Prevent buffer overflow */
        if ((sizeof(serial_buff) - wr_idx) < LGW_GPS_MIN_MSG_SIZE) {
            memcpy(serial_buff, &serial_buff[LGW_GPS_MIN_MSG_SIZE], wr_idx - LGW_GPS_MIN_MSG_SIZE);
            wr_idx -= LGW_GPS_MIN_MSG_SIZE;
        }
    }
    MSG("\nINFO: End of GPS thread\n");
}

线程 thread_gps 通过阻塞式读取 ubx-7 模块的时间同步信息和 NMEA 信息内容。

2.3 lgw_parse_ubx 函数

源码路径:@libloragw/src/loragw-gps.c

enum gps_msg lgw_parse_ubx(const char *serial_buff, size_t buff_size, size_t *msg_size) {
    bool valid = 0;    /* iTOW, fTOW and week validity */
    unsigned int payload_length;
    uint8_t ck_a, ck_b;
    uint8_t ck_a_rcv, ck_b_rcv;
    unsigned int i;

    *msg_size = 0; /* ensure msg_size alway receives a value */

    /* check input parameters */
    if (serial_buff == NULL) {
        return IGNORED;
    }
    if (buff_size < 8) {
        DEBUG_MSG("ERROR: TOO SHORT TO BE A VALID UBX MESSAGE\n");
        return IGNORED;
    }

    /* display received serial data and checksum */
    DEBUG_MSG("Note: parsing UBX frame> ");
    for (i=0; i<buff_size; i++) {
        DEBUG_MSG("%02x ", serial_buff[i]);
    }
    DEBUG_MSG("\n");

    /* Check for UBX sync chars 0xB5 0x62 */
    if ((serial_buff[0] == (char)0xB5) && (serial_buff[1] == (char)0x62)) {

        /* Get payload length to compute message size */
        payload_length  = (uint8_t)serial_buff[4];
        payload_length |= (uint8_t)serial_buff[5] << 8;
        *msg_size = 6 + payload_length + 2; /* header + payload + checksum */

        /* check for complete message in buffer */
        if(*msg_size <= buff_size) {
            /* Validate checksum of message */
            ck_a_rcv = serial_buff[*msg_size-2]; /* received checksum */
            ck_b_rcv = serial_buff[*msg_size-1]; /* received checksum */
            /* Use 8-bit Fletcher Algorithm to compute checksum of actual payload */
            ck_a = 0; ck_b = 0;
            for (i=0; i<(4 + payload_length); i++) {
                ck_a = ck_a + serial_buff[i+2];
                ck_b = ck_b + ck_a;
            }

            /* Compare checksums and parse if OK */
            if ((ck_a == ck_a_rcv) && (ck_b == ck_b_rcv)) {
                /* Check for Class 0x01 (NAV) and ID 0x20 (NAV-TIMEGPS) */
                if ((serial_buff[2] == 0x01) && (serial_buff[3] == 0x20)) {
                    /* Check validity of information */
                    valid = serial_buff[17] & 0x3; /* towValid, weekValid */
                    if (valid) {
                        /* Parse buffer to extract GPS time */
                        /* Warning: payload byte ordering is Little Endian */
                        gps_iTOW =  (uint8_t)serial_buff[6];
                        gps_iTOW |= (uint8_t)serial_buff[7] << 8;
                        gps_iTOW |= (uint8_t)serial_buff[8] << 16;
                        gps_iTOW |= (uint8_t)serial_buff[9] << 24; /* GPS time of week, in ms */

                        gps_fTOW =  (uint8_t)serial_buff[10];
                        gps_fTOW |= (uint8_t)serial_buff[11] << 8;
                        gps_fTOW |= (uint8_t)serial_buff[12] << 16;
                        gps_fTOW |= (uint8_t)serial_buff[13] << 24; /* Fractional part of iTOW, in ns */

                        gps_week =  (uint8_t)serial_buff[14];
                        gps_week |= (uint8_t)serial_buff[15] << 8; /* GPS week number */

                        gps_time_ok = true;
#if 0
                        /* For debug */
                        {
                            short ubx_gps_hou = 0; /* hours (0-23) */
                            short ubx_gps_min = 0; /* minutes (0-59) */
                            short ubx_gps_sec = 0; /* seconds (0-59) */

                            /* Format GPS time in hh:mm:ss based on iTOW */
                            ubx_gps_sec = (gps_iTOW / 1000) % 60;
                            ubx_gps_min = (gps_iTOW / 1000 / 60) % 60;
                            ubx_gps_hou = (gps_iTOW / 1000 / 60 / 60) % 24;
                            printf("  GPS time = %02d:%02d:%02d\n", ubx_gps_hou, ubx_gps_min, ubx_gps_sec);
                        }
#endif
                    } else { /* valid */
                        gps_time_ok = false;
                    }

                    return UBX_NAV_TIMEGPS;
                } else if ((serial_buff[2] == 0x05) && (serial_buff[3] == 0x00)) {
                    DEBUG_MSG("NOTE: UBX ACK-NAK received\n");
                    return IGNORED;
                } else if ((serial_buff[2] == 0x05) && (serial_buff[3] == 0x01)) {
                    DEBUG_MSG("NOTE: UBX ACK-ACK received\n");
                    return IGNORED;
                } else { /* not a supported message */
                    DEBUG_MSG("ERROR: UBX message is not supported (%02x %02x)\n", serial_buff[2], serial_buff[3]);
                    return IGNORED;
                }
            } else { /* checksum failed */
                DEBUG_MSG("ERROR: UBX message is corrupted, checksum failed\n");
                return INVALID;
            }
        } else { /* message contains less bytes than indicated by header */
            DEBUG_MSG("ERROR: UBX message incomplete\n");
            return INCOMPLETE;
        }
    } else { /* Not a UBX message */
        /* Ignore messages which are not UBX ones for now */
        return IGNORED;
    }
}

2.5 gps_process_sync 函数功能

代码路径在 lora_pkt_fwd.c 文件中

static void gps_process_sync(void) {
    struct timespec gps_time;
    struct timespec utc;
    uint32_t trig_tstamp; /* concentrator timestamp associated with PPM pulse */
    int i = lgw_gps_get(&utc, &gps_time, NULL, NULL);                      //>第一处 转换 gps 时间

    /* get GPS time for synchronization */
    if (i != LGW_GPS_SUCCESS) {
        MSG("WARNING: [gps] could not get GPS time from GPS\n");
        return;
    }

    /* get timestamp captured on PPM pulse  */
    pthread_mutex_lock(&mx_concent);
    i = lgw_get_trigcnt(&trig_tstamp);                                    //> 第二处 获取网关内部计时器时间
    pthread_mutex_unlock(&mx_concent);
    if (i != LGW_HAL_SUCCESS) {
        MSG("WARNING: [gps] failed to read concentrator timestamp\n");
        return;
    }

    /* try to update time reference with the new GPS time & timestamp */
    pthread_mutex_lock(&mx_timeref);
    i = lgw_gps_sync(&time_reference_gps, trig_tstamp, utc, gps_time);   //> 第三处 修正网关内部计时器
    pthread_mutex_unlock(&mx_timeref);
    if (i != LGW_GPS_SUCCESS) {
        MSG("WARNING: [gps] GPS out of sync, keeping previous time reference\n");
    }
}

第3节 gps本地校时

在GPS线程中调用 gps_process_sync() 函数进行校时,函数中分别调用 lgw_gps_get()、lgw_get_trigcnt()和 lgw_gps_sync() 函数,
完成本地时间同步,函数中 trig_tstamp 和 time_reference_gps 是全局变量,通过获取gps时间进行校正这两个变量内容。

3.1 把 gps 时间转换为UTC时间

在函数 lgw_gps_get() 中把gps时间转换成 UTC 时间, 着重关注 utc->tv_sec 与 utc->tv_nsec 数据.

int lgw_gps_get(struct timespec *utc, struct timespec *gps_time, struct coord_s *loc, struct coord_s *err) {
    struct tm x;
    time_t y;
    double intpart, fractpart;

    if (utc != NULL) {
        if (!gps_time_ok) {
            DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n");
            return LGW_GPS_ERROR;
        }
        memset(&x, 0, sizeof(x));
        if (gps_yea < 100) { /* 2-digits year, 20xx */
            x.tm_year = gps_yea + 100; /* 100 years offset to 1900 */
        } else { /* 4-digits year, Gregorian calendar */
            x.tm_year = gps_yea - 1900;
        }
        x.tm_mon = gps_mon - 1; /* tm_mon is [0,11], gps_mon is [1,12] */
        x.tm_mday = gps_day;
        x.tm_hour = gps_hou;
        x.tm_min = gps_min;
        x.tm_sec = gps_sec;

        //> 转换出 utc->tv_sec utc->tv_nsec
        y = mktime(&x) - timezone; /* need to substract timezone bc mktime assumes time vector is local time */
        if (y == (time_t)(-1)) {
            DEBUG_MSG("ERROR: FAILED TO CONVERT BROKEN-DOWN TIME\n");
            return LGW_GPS_ERROR;
        }
        utc->tv_sec = y;
        utc->tv_nsec = (int32_t)(gps_fra * 1e9);
    }
    
    if (gps_time != NULL) {
        if (!gps_time_ok) {
            DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n");
            return LGW_GPS_ERROR;
        }
        //> 周内秒转换
        fractpart = modf(((double)gps_iTOW / 1E3) + ((double)gps_fTOW / 1E9), &intpart);
        /* Number of seconds since beginning on current GPS week */
        gps_time->tv_sec = (time_t)intpart;
        /* Number of seconds since GPS epoch 06.Jan.1980 */
        gps_time->tv_sec += (time_t)gps_week * 604800; /* day*hours*minutes*secondes: 7*24*60*60; */
        /* Fractional part in nanoseconds */
        gps_time->tv_nsec = (long)(fractpart * 1E9);
    }
    
    if (loc != NULL) {
        if (!gps_pos_ok) {
            DEBUG_MSG("ERROR: NO VALID POSITION TO RETURN\n");
            return LGW_GPS_ERROR;
        }
        loc->lat = ((double)gps_dla + (gps_mla/60.0)) * ((gps_ola == 'N')?1.0:-1.0);
        loc->lon = ((double)gps_dlo + (gps_mlo/60.0)) * ((gps_olo == 'E')?1.0:-1.0);
        loc->alt = gps_alt;
    }
    if (err != NULL) {
        DEBUG_MSG("Warning: localization error processing not implemented yet\n");
        err->lat = 0.0;
        err->lon = 0.0;
        err->alt = 0;
    }

    return LGW_GPS_SUCCESS;
}

同时把gps周时间 gps_iTOW 和 gps_fTOW 值转换成 gps_time->tv_sec 和 gps_time->tv_nsec 绝对时间。

3.2 网关参考时钟

int lgw_get_trigcnt(uint32_t* trig_cnt_us) {
    DEBUG_PRINTF(" --- %s\n", "IN");

    CHECK_NULL(trig_cnt_us);

    *trig_cnt_us = sx1302_timestamp_counter(true);

    DEBUG_PRINTF(" --- %s\n", "OUT");

    return LGW_HAL_SUCCESS;
}

函数调用关系如下:
lgw_get_trigcnt()
==>sx1302_timestamp_counter(true);
==>timestamp_counter_get(&counter_us, &inst_cnt, &pps_cnt);
函数入口参数 counter_us 是全局变量、从 SX1302 内部寄存器中读取的 LoRa 信号 TOA 时间, inst_cnt 和 pps_cnt 临时变量;
最终调用函数内容如下:

int timestamp_counter_get(timestamp_counter_t * self, uint32_t * inst, uint32_t * pps) {
    int x;
    uint8_t buff[8];
    uint8_t buff_wa[8];
    uint32_t counter_inst_us_raw_27bits_now;
    uint32_t counter_pps_us_raw_27bits_now;

    /* Get the freerun and pps 32MHz timestamp counters - 8 bytes
            0 -> 3 : PPS counter
            4 -> 7 : Freerun counter (inst)
    */
    x = lgw_reg_rb(SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS, &buff[0], 8);
    if (x != LGW_REG_SUCCESS) {
        printf("ERROR: Failed to get timestamp counter value\n");
        return -1;
    }

    /* Workaround concentrator chip issue:
        - read MSB again
        - if MSB changed, read the full counter again
     */
    x = lgw_reg_rb(SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS, &buff_wa[0], 8);
    if (x != LGW_REG_SUCCESS) {
        printf("ERROR: Failed to get timestamp counter MSB value\n");
        return -1;
    }
    
    if ((buff[0] != buff_wa[0]) || (buff[4] != buff_wa[4])) {
        x = lgw_reg_rb(SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS, &buff_wa[0], 8);
        if (x != LGW_REG_SUCCESS) {
            printf("ERROR: Failed to get timestamp counter MSB value\n");
            return -1;
        }
        memcpy(buff, buff_wa, 8); /* use the new read value */
    }
    //> 从 sx1302 中读取的 32MHz 时钟数量
    counter_pps_us_raw_27bits_now  = (buff[0]<<24) | (buff[1]<<16) | (buff[2]<<8) | buff[3];
    counter_inst_us_raw_27bits_now = (buff[4]<<24) | (buff[5]<<16) | (buff[6]<<8) | buff[7];

    /* Store PPS counter to history, for fine timestamp calculation,时间戳存储至全局 timestap_pps_history 中 */
    timestamp_pps_history_save(counter_pps_us_raw_27bits_now);

    /* Scale to 1MHz, 转换成 1MHz 时钟 */
    counter_pps_us_raw_27bits_now /= 32;
    counter_inst_us_raw_27bits_now /= 32;

    /* Update counter wrapping status */
    timestamp_counter_update(self, counter_pps_us_raw_27bits_now, counter_inst_us_raw_27bits_now);

    /* Convert 27-bits counter to 32-bits counter */
    *inst = timestamp_counter_expand(self, false, counter_inst_us_raw_27bits_now);
    *pps  = timestamp_counter_expand(self, true, counter_pps_us_raw_27bits_now);
    return 0;
}

把27bit us 时间转换为 32bit us 时间, 统一时间位数。

void timestamp_counter_update(timestamp_counter_t * self, uint32_t pps, uint32_t inst) {
    //struct timestamp_info_s* tinfo = (pps == true) ? &self->pps : &self->inst;

    /* Check if counter has wrapped, and update wrap status if necessary */
    if (pps < self->pps.counter_us_27bits_ref) {
        self->pps.counter_us_27bits_wrap += 1;
        self->pps.counter_us_27bits_wrap %= 32;
    }
    if (inst < self->inst.counter_us_27bits_ref) {
        self->inst.counter_us_27bits_wrap += 1;
        self->inst.counter_us_27bits_wrap %= 32;
    }

    /* Update counter reference */
    self->pps.counter_us_27bits_ref = pps;
    self->inst.counter_us_27bits_ref = inst;
}

此部分程序实现了两件事情、保存32MHz时间到 timestap_pps_history 全局变量中、统一时间位数并更新 counter_us 时间。

3.3 本地时间同步

lgw_gps_sync(&time_reference_gps, trig_tstamp, utc, gps_time); 参数 time_reference_gps 和 trig_tstamp 全局变量,


#define TS_CPS              1E6 /* count-per-second of the timestamp counter */
#define PLUS_10PPM          1.00001
#define MINUS_10PPM         0.99999

int lgw_gps_sync(struct tref *ref, uint32_t count_us, struct timespec utc, struct timespec gps_time) {
    double cnt_diff;   /* internal concentrator time difference (in seconds) */
    double utc_diff;   /* UTC time difference (in seconds) */
    double slope;      /* time slope between new reference and old reference (for sanity check) */

    bool aber_n0;     /* is the update value for synchronization aberrant or not ? */
    static bool aber_min1 = false; /* keep track of whether value at sync N-1 was aberrant or not  */
    static bool aber_min2 = false; /* keep track of whether value at sync N-2 was aberrant or not  */

    CHECK_NULL(ref);

    /* calculate the slope, 参考时钟有 本地时钟、utc 和 gps 时钟数 */
    //> 计算出本地时钟与参考时钟间差,
    cnt_diff = (double)(count_us - ref->count_us) / (double)(TS_CPS); /* uncorrected by xtal_err */
    utc_diff = (double)(utc.tv_sec - (ref->utc).tv_sec) + (1E-9 * (double)(utc.tv_nsec - (ref->utc).tv_nsec));

    /* detect aberrant points by measuring if slope limits are exceeded */
    if (utc_diff != 0) { // prevent divide by zero
        slope = cnt_diff/utc_diff;
        if ((slope > PLUS_10PPM) || (slope < MINUS_10PPM)) {
            DEBUG_MSG("Warning: correction range exceeded\n");
            aber_n0 = true;
        } else {
            aber_n0 = false;
        }
    } else {
        DEBUG_MSG("Warning: aberrant UTC value for synchronization\n");
        aber_n0 = true;
    }

    /* watch if the 3 latest sync point were aberrant or not */
    if (aber_n0 == false) {
        /* value no aberrant -> sync with smoothed slope */
        //> 时钟未同步、同步参考时钟
        ref->systime = time(NULL);
        ref->count_us = count_us;
        ref->utc.tv_sec = utc.tv_sec;
        ref->utc.tv_nsec = utc.tv_nsec;
        ref->gps.tv_sec = gps_time.tv_sec;
        ref->gps.tv_nsec = gps_time.tv_nsec;
        ref->xtal_err = slope;

        aber_min2 = aber_min1;
        aber_min1 = aber_n0;
        return LGW_GPS_SUCCESS;
    } else if (aber_n0 && aber_min1 && aber_min2) {
        /* 3 successive aberrant values -> sync reset (keep xtal_err) 
         * 同步本地时钟
        */
        ref->systime = time(NULL);
        ref->count_us = count_us;
        ref->utc.tv_sec = utc.tv_sec;
        ref->utc.tv_nsec = utc.tv_nsec;
        ref->gps.tv_sec = gps_time.tv_sec;
        ref->gps.tv_nsec = gps_time.tv_nsec;

        /* reset xtal_err only if the present value is out of range */
        if ((ref->xtal_err > PLUS_10PPM) || (ref->xtal_err < MINUS_10PPM)) {
            ref->xtal_err = 1.0;
        }

        DEBUG_MSG("Warning: 3 successive aberrant sync attempts, sync reset\n");
        aber_min2 = aber_min1;
        aber_min1 = aber_n0;
        return LGW_GPS_SUCCESS;
    } else {
        /* only 1 or 2 successive aberrant values -> ignore and return an error */
        aber_min2 = aber_min1;
        aber_min1 = aber_n0;
        return LGW_GPS_ERROR;
    }

    return LGW_GPS_SUCCESS;
}

总结

此篇 thread_gps 线程读取gps同步时间 和 NMEA 信息内容,并同步网关内部计时器。

如果本篇文章对您有所启发或帮助、请给笔者点赞助力、鼓励笔者坚持把此系列内容尽快梳理、分享出来。
谢谢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2374638.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Aware和InitializingBean接口以及@Autowired注解失效分析

Aware 接口用于注入一些与容器相关信息&#xff0c;例如&#xff1a; ​ a. BeanNameAware 注入 Bean 的名字 ​ b. BeanFactoryAware 注入 BeanFactory 容器 ​ c. ApplicationContextAware 注入 ApplicationContext 容器 ​ d. EmbeddedValueResolverAware 注入 解析器&a…

Unity3D仿星露谷物语开发41之创建池管理器

1、目标 在PersistentScene中创建池管理器&#xff08;Pool Manager&#xff09;。这将允许一个预制对象池被创建和重用。 在游戏中当鼠标点击地面时&#xff0c;便会启用某一个对象。比如点击地面&#xff0c;就创建了一棵树&#xff0c;而这棵树是从预制体对象池中获取的&a…

Modbus协议介绍

Modbus是一种串行通信协议&#xff0c;由Modicon公司&#xff08;现为施耐德电气&#xff09;在1979年为可编程逻辑控制器&#xff08;PLC&#xff09;通信而开发。它是工业自动化领域最常用的通信协议之一&#xff0c;具有开放性、简单性和跨平台兼容性&#xff0c;广泛应用于…

I/O多路复用(select/poll/epoll)

通过一个进程来维护多个Socket&#xff0c;也就是I/O多路复用&#xff0c;是一种常见的并发编程技术&#xff0c;它允许单个线程或进程同时监视多个输入/输出&#xff08;I/O&#xff09;流&#xff08;例如网络连接、文件描述符&#xff09;。当任何一个I/O流准备好进行读写操…

Westlake-Omni 情感端音频生成式输出模型

简述 github地址在 GitHub - xinchen-ai/Westlake-OmniContribute to xinchen-ai/Westlake-Omni development by creating an account on GitHub.https://github.com/xinchen-ai/Westlake-Omni Westlake-Omni 是由西湖心辰&#xff08;xinchen-ai&#xff09;开发的一个开源…

随手记录5

一些顶级思维&#xff1a; ​ 顶级思维 1、永远不要自卑。 也永远不要感觉自己比别人差&#xff0c;这个人有没有钱&#xff0c;有多少钱&#xff0c;其实跟你都没有关系。有很多人就是那个奴性太强&#xff0c;看到比自己优秀的人&#xff0c;甚至一些装逼的人&#xff0c;这…

Linux驱动:驱动编译流程了解

要求 1、开发板中的linux的zImage必须是自己编译的 2、内核源码树,其实就是一个经过了配置编译之后的内核源码。 3、nfs挂载的rootfs,主机ubuntu中必须搭建一个nfs服务器。 内核源码树 解压 tar -jxvf x210kernel.tar.bz2 编译 make x210ii_qt_defconfigmakeCan’t use ‘…

使用 Flowise 构建基于私有知识库的智能客服 Agent(图文教程)

使用 Flowise 构建基于私有知识库的智能客服 Agent(图文教程) 在构建 AI 客服时,常见的需求是让机器人基于企业自身的知识文档,提供准确可靠的答案。本文将手把手教你如何使用 Flowise + 向量数据库(如 Pinecone),构建一个结合 RAG(Retrieval-Augmented Generation)检…

RabbitMQ ③-Spring使用RabbitMQ

Spring使用RabbitMQ 创建 Spring 项目后&#xff0c;引入依赖&#xff1a; <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp --> <dependency><groupId>org.springframework.boot</groupId><artifac…

linux中常用的命令(四)

目录 1-cat查看文件内容 2-more命令 3-less命令 4-head命令 5-tail命令 1-cat查看文件内容 cat中的一些操作 -b : 列出行号&#xff08;不含空白行&#xff09;-E : 将结尾的断行以 $ 的形式展示出来-n : 列出行号&#xff08;含空白行&#xff09;-T : 将 tab 键 以 ^I 显示…

利用SSRF击穿内网!kali靶机实验

目录 1. 靶场拓扑图 2. 判断SSRF的存在 3. SSRF获取本地信息 3.1. SSRF常用协议 3.2. 使用file协议 4. 172.150.23.1/24探测端口 5. 172.150.23.22 - 代码注入 6. 172.150.23.23 SQL注入 7. 172.150.23.24 命令执行 7.1. 实验步骤 8. 172.150.23.27:6379 Redis未授权…

DVWA在线靶场-xss部分

目录 1. xxs&#xff08;dom&#xff09; 1.1 low 1.2 medium 1.3 high 1.4 impossible 2. xss&#xff08;reflected&#xff09; 反射型 2.1 low 2.2 medium 2.3 high 2.4 impossible 3. xss&#xff08;stored&#xff09;存储型 --留言板 3.1 low 3.2 medium 3.3 high 3.…

Go 语言 slice(切片) 的使用

序言 在许多开发语言中&#xff0c;动态数组是必不可少的一个组成部分。在实际的开发中很少会使用到数组&#xff0c;因为对于数组的大小大多数情况下我们是不能事先就确定好的&#xff0c;所以他不够灵活。动态数组通过提供自动扩容的机制&#xff0c;极大地提升了开发效率。这…

js常用的数组遍历方式

以下是一个完整的示例&#xff0c;将包含图片、文字和数字的数组渲染到 HTML 页面&#xff0c;使用 ​多种遍历方式​ 实现不同的渲染效果&#xff1a; 1. 准备数据&#xff08;数组&#xff09; const items [{ id: 1, name: "苹果", price: 5.99, image: "h…

【网络编程】五、三次握手 四次挥手

文章目录 Ⅰ. 三次握手Ⅱ. 建立连接后的通信Ⅲ. 四次挥手 Ⅰ. 三次握手 ​ 1、首先双方都是处于未通信的状态&#xff0c;也就是关闭状态 CLOSE。 ​ 2、因为服务端是为了服务客户端的&#xff0c;所以它会提前调用 listen() 函数进行对客户端请求的监听。 ​ 3、接着客户端就…

从 AGI 到具身智能体:解构 AI 核心概念与演化路径全景20250509

&#x1f916; 从 AGI 到具身智能体&#xff1a;解构 AI 核心概念与演化路径全景 作者&#xff1a;AI 应用实践者 在过去的几年中&#xff0c;AI 领域飞速发展&#xff0c;从简单的文本生成模型演进为今天具备复杂推理、感知能力的“智能体”系统。本文将从核心概念出发&#x…

Docker Compose 的历史和发展

这张图表展示了Docker Compose从V1到V2的演变过程&#xff0c;并解释了不同版本的Compose文件格式及其支持情况。以下是对图表的详细讲解&#xff1a; Compose V1 No longer supported: Compose V1已经不再支持。Compose file format 3.x: 使用了版本3.x的Compose文件格式。 …

从 JIT 即时编译一直讲到CGI|FastGGI|WSGI|ASGI四种协议的实现细节

背景 我一度理解错了这个东西&#xff0c;之前没有AI的时候&#xff0c;也没深究过&#xff0c;还觉得PHP8支持了常驻内存的运行的错误理解&#xff0c;时至今日再来看这个就很清晰了。 另外&#xff0c;早几年对以上4个协议&#xff0c;我也没搞懂&#xff0c;时至今日&…

CSS3 遮罩

在网页设计中&#xff0c;我们经常需要实现一些特殊的视觉效果来增强用户体验。CSS3 遮罩&#xff08;mask&#xff09;允许我们通过控制元素的可见区域来创建各种精美的视觉效果。本文将带你全面了解 CSS3 遮罩的功能和应用。 什么是 CSS3 遮罩&#xff1f; CSS3 遮罩是一种…

ResNet残差神经网络的模型结构定义(pytorch实现)

ResNet残差神经网络的模型结构定义&#xff08;pytorch实现&#xff09; ResNet‑34 ResNet‑34的实现思路。核心在于&#xff1a; 定义残差块&#xff08;BasicBlock&#xff09;用 _make_layer 方法堆叠多个残差块按照 ResNet‑34 的通道和层数配置来搭建网络 import torch…