关于GPS的1PPS时间同步功能探索与测试

转载自关于GPS的1PPS时间同步功能探索与测试_学一点IOT-程序员宅基地 - 程序员宅基地

最近在研究GPSD相关信息,查阅到GPSD可以与NTPD相配合实现高精度时间同步功能,因此才涉及到此主题。

目前手头用的是Ublox F9P模块,UART输出NEMA数据,另外一个GPIO输出1PPS脉冲

首先看一张时序图:

1. NEMA中包含有时间信息,一般是秒级别,也有部分带有毫秒

2. 1PPS即每秒输出一个脉冲,图中以高电平触发为例(没画下降沿),接收及处理1PPS脉冲的时间也在ns级别

3. 因为NEMA是通过串口发送和接收,而且一次NEMA数据量也有KB级别大小,处理时间远比1PPS时间长

4. 通过NEMA中的秒级时间和1PPS脉冲相配合,即可实现高精度时间同步(ns级:依据1PPS的响应时间)

具体时间同步实现,以Linux为例,常用组合方式为:kenel pps.ko,GPSD,chronyd或者NTPD

首先Kernel pps.ko:

当前kernel是支持pps处理的,因为我用的ublox的pps是接到gpio的,所以选择gpio方式

1. kernel timer client 是内核软件模拟的pps信号,用于测试

2. pps client using gpio 是以gpio作为pps信号源

pps-gpio.c源码实现也比较简单,主要通过注册gpio中断,当gpio电平变化时,记录当前系统运行时刻,然后post event到用户空间。

因为使用了外部GPIO,因此在使用该模块之前,需要在dts中指定相关的gpio引脚,compatible 为 "pps-gpio"

static const struct of_device_id pps_gpio_dt_ids[] = {

{ .compatible = "pps-gpio", },

{ /* sentinel */ }

};

配置后编译启动,查阅dmesg

root@imx8qxpmek:~# dmesg |grep pps
[    0.708441] pps_core: LinuxPPS API ver. 1 registered
[    0.713357] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti <giometti@linux.it>
[    1.737515] pps pps0: new PPS source ktimer
[    1.741727] pps pps0: ktimer PPS source registered
[    1.747556] pps pps1: new PPS source pps.-1
[    1.751804] pps pps1: Registered IRQ 115 as PPS source
[  236.866057] pps pps1: unsupported capabilities (2)

此处, PPS0为内核模拟的pps信号,pps1  ublox模块的pps 信号

在应用层,使用ppstest工具可查看pps信号时间值(pps信号发生时刻的系统时间点)

root@imx8qxpmek:~# ppstest /dev/pps0 
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1591828917.828448156, sequence: 85791 - clear  0.000000000, sequence: 0
source 0 - assert 1591828918.852533634, sequence: 85792 - clear  0.000000000, sequence: 0
source 0 - assert 1591828919.876534727, sequence: 85793 - clear  0.000000000, sequence: 0
^C
root@imx8qxpmek:~# 
root@imx8qxpmek:~# ppstest /dev/pps1 
trying PPS source "/dev/pps1"
found PPS source "/dev/pps1"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1591828923.065352191, sequence: 87742 - clear  0.000000000, sequence: 0
source 0 - assert 1591828924.065348846, sequence: 87743 - clear  0.000000000, sequence: 0
source 0 - assert 1591828925.065347127, sequence: 87744 - clear  0.000000000, sequence: 0
source 0 - assert 1591828926.065348783, sequence: 87745 - clear  0.000000000, sequence: 0
^C

查看pps中断:

root@imx8qxpmek:~# date
Thu Jun 11 06:43:49 CST 2020
root@imx8qxpmek:~# cat /proc/interrupts |grep pps
115:      87851          0          0          0  gpio-mxc  16 Edge      pps.-1
root@imx8qxpmek:~# date
Thu Jun 11 06:43:53 CST 2020
root@imx8qxpmek:~# cat /proc/interrupts |grep pps
115:      87855          0          0          0  gpio-mxc  16 Edge      pps.-1

通过date时间打印,可以看到1s产生一次中断。

至此,pps配置完毕,接下来处理NEMA数据,使用gpsd

gpsd是一个支持多设备,多协议以及提供丰富工具集的专用gps信号处理服务。此处主要介绍起使用,不做编译以及功能详细说明。

通过gpsd -l可以查阅支持的gps协议或者说模块,我裁剪过,只保留NEMA和ublox,所以只显示两个

root@imx8qxpmek:~# ./gpsd -l
                                NMEA0183
n       b       c       *       u-blox
# n: mode switch, b: speed switch, c: rate switch, *: non-NMEA packet type.
# Socket export enabled.
# Shared memory export enabled.
# Time service features enabled.

另外也可以看到,支持socket 和共享内存方式通讯,比如后续要用到的chronyd即是采用共享内存方式,同时也开启了时间服务,当然此处没有用,而是用chrony单独实现。

启动gpsd服务后,可以通过其提供的各种工具集获取gps状态,比如gpsmon:

接下来配置chrony,chrony为一个专用的时间服务程序,与NTPD类似

其配置为/etc/chrony.conf


leapsectz right/UTC
makestep 1.0 -1
rtcsync

refclock PPS /dev/pps1 lock GPSD prefer refid PPS
refclock SHM 0 offset 0.0 delay 0.2 refid GPSD

allow

driftfile - 根据实际时间计算出计算机增减时间的比率,将它记录到一个文件中是最合理的,它会在重启后为系统时钟作出补偿,甚至可能的话,会从时钟服务器获得较好的估值。

rtcsync -指令将启用一个内核模式,在该模式中,系统时间每11分钟会拷贝到实时时钟(RTC)。

allow / deny - 这里你可以指定一台主机、子网,或者网络以允许或拒绝NTP连接到扮演时钟服务器的机器。
makestep - 通常,chronyd将根据需求通过减慢或加速时钟,使得系统逐步纠正所有时间偏差。在某些特定情况下,系统时钟可能会漂移过快,导致该调整过程消耗很长的时间来纠正系统时钟。该指令强制chronyd在调整期大于某个阀值时步进调整系统时钟,但只有在因为chronyd启动时间超过指定限制(可使用负值来禁用限制),没有更多时钟更新时才生效

refclock driver parameter[:option]…​ [option]…​

    The refclock directive specifies a hardware reference clock to be used as a time source. It has two mandatory parameters, a driver name and a driver-specific parameter. The two parameters are followed by zero or more refclock options. Some drivers have special options, which can be appended to the driver-specific parameter using the : character.

    There are four drivers included in chronyd:

    PPS

        Driver for the kernel PPS (pulse per second) API. The parameter is the path to the PPS device (typically /dev/pps?). As PPS refclocks do not supply full time, another time source (e.g. NTP server or non-PPS refclock) is needed to complete samples from the PPS refclock. An alternative is to enable the local directive to allow synchronisation with some unknown but constant offset. The driver supports the following option:

        clear

            By default, the PPS refclock uses assert events (rising edge) for synchronisation. With this option, it will use clear events (falling edge) instead.

        Examples:

        refclock PPS /dev/pps0 lock NMEA refid GPS
        refclock SHM 0 offset 0.5 delay 0.2 refid NMEA noselect
        refclock PPS /dev/pps1:clear refid GPS2

详细的各项配置可以参阅官方文档:chrony – Documentation

运行chronyd之后,可以看到时间同步过程:

root@imx8qxpmek:~# chronyd  -d -f /etc/chrony.conf  
2020-06-10T17:00:41Z chronyd version 3.2 starting (+CMDMON +NTP +REFCLOCK +RTC -PRIVDROP -SCFILTER -SECHASH -SIGND +ASYNCDNS +IPV6 -DEBUG)
2020-06-10T17:00:41Z Initial frequency -116.709 ppm
2020-06-10T17:00:41Z Timezone right/UTC failed leap second check, ignoring

2020-06-10T17:01:28Z Selected source PPS
2020-06-10T17:01:28Z System clock wrong by 34565.191649 seconds, adjustment started
2020-06-11T02:37:33Z System clock was stepped by 34565.191649 seconds

在客户端方面,使用ntpd测试如下:

root@OpenWrt:~# ntpd -d -n -p 192.168.3.1
ntpd: sending query to 192.168.3.1
ntpd: reply from 192.168.3.1: offset:+6152.992411 delay:0.008204 status:0x24 strat:1 refid:0x00535050 rootdelay:0.000015 reach:0x01
ntpd: sending query to 192.168.3.1
ntpd: reply from 192.168.3.1: offset:+6152.990378 delay:0.004154 status:0x24 strat:1 refid:0x00535050 rootdelay:0.000015 reach:0x03
ntpd: setting time to 2020-06-11 10:42:38.090639 (offset +6152.990378s)

至此以NEMA,1PPS,GPSD,chronyd 等在linux平台搭建的时间服务器完毕。

以下是基于STM32和GPS模块实现时间同步和PPS检测的示例代码: 首先,需要配置STM32的UART和外部中断来接收GPS模块的NMEA协议数据和PPS信号。 ```c #include "stm32f4xx.h" #include "stdio.h" /* GPS串口定义 */ #define GPS_USART USART1 #define GPS_USART_CLK RCC_APB2Periph_USART1 #define GPS_USART_CLK_INIT RCC_APB2PeriphClockCmd #define GPS_USART_IRQn USART1_IRQn #define GPS_USART_IRQHandler USART1_IRQHandler #define GPS_USART_BAUDRATE 9600 /* GPS定位信息结构体定义 */ typedef struct { float latitude; // 纬度 float longitude; // 经度 float altitude; // 海拔高度 float speed; // 速度 float course; // 航向 int date; // 日期(YYYYMMDD) int time; // 时间(HHMMSS.SSS) } gps_info_t; /* GPS定位信息变量定义 */ gps_info_t gps_info = {0}; /* PPS信号检测变量定义 */ volatile uint32_t pps_count = 0; volatile uint32_t last_pps_time = 0; volatile uint8_t is_pps_detected = 0; /* GPS定位信息解析函数 */ void gps_parse(char *str) { float lat_degree, lat_minute, lon_degree, lon_minute; char lat_direction, lon_direction; int year, month, day, hour, minute, second; if (sscanf(str, "$GPGGA,%*f,%2d%2d.%*f,%c,%3d%2d.%*f,%c,%*d,%*d,%.1f,%*f,M,%.1f,M,,", &year, &month, &lat_degree, &lat_minute, &lat_direction, &gps_info.altitude, &gps_info.latitude) == 7) { // 解析纬度 gps_info.latitude = (float)lat_degree + lat_minute / 60.0; if (lat_direction == 'S') { gps_info.latitude = -gps_info.latitude; } if (sscanf(str, "$GPRMC,%2d%2d%2d.%*f,A,%3d%2d.%*f,%c,%4d%2d%2d,,,%*s*%*s", &hour, &minute, &second, &lon_degree, &lon_minute, &lon_direction, &year, &month, &day) == 9) { // 解析经度 gps_info.longitude = (float)lon_degree + lon_minute / 60.0; if (lon_direction == 'W') { gps_info.longitude = -gps_info.longitude; } // 解析时间 gps_info.time = hour * 10000 + minute * 100 + second; // 解析日期 gps_info.date = year * 10000 + month * 100 + day; } } } /* GPS串口接收中断处理函数 */ void GPS_USART_IRQHandler(void) { if (USART_GetITStatus(GPS_USART, USART_IT_RXNE) != RESET) { static char gps_buffer[128] = {0}; static uint8_t gps_index = 0; char ch = USART_ReceiveData(GPS_USART) & 0xff; if (ch == '$') { gps_index = 0; memset(gps_buffer, 0, sizeof(gps_buffer)); } gps_buffer[gps_index++] = ch; if (ch == '\n') { gps_parse(gps_buffer); } USART_ClearITPendingBit(GPS_USART, USART_IT_RXNE); } } /* PPS信号检测外部中断处理函数 */ void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) != RESET) { uint32_t current_time = TIM_GetCounter(TIM2); if (current_time - last_pps_time > 1000) { pps_count++; is_pps_detected = 1; } last_pps_time = current_time; EXTI_ClearITPendingBit(EXTI_Line0); } } /* 定时器2初始化函数 */ void TIM2_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseStructure.TIM_Period = 0xFFFFFFFF; TIM_TimeBaseStructure.TIM_Prescaler = 84 - 1; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE); } /* 外部中断初始化函数 */ void EXTI_Init(void) { EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0); EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } int main(void) { USART_InitTypeDef USART_InitStructure; /* GPS串口初始化 */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(GPS_USART_CLK, ENABLE); GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = GPS_USART_BAUDRATE; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(GPS_USART, &USART_InitStructure); USART_ITConfig(GPS_USART, USART_IT_RXNE, ENABLE); USART_Cmd(GPS_USART, ENABLE); /* PPS检测定时器和外部中断初始化 */ TIM2_Init(); EXTI_Init(); while (1) { if (is_pps_detected) { is_pps_detected = 0; // 在这里添加PPS信号检测后的处理代码 } // 在这里添加GPS定位信息处理代码 } } ``` 在主函数中,通过解析GPS模块发送的NMEA协议数据,可以获取到GPS定位信息。同时,使用定时器和外部中断来检测PPS信号,计算出两个PPS信号之间的时间差,从而实现时间同步。 需要注意的是,PPS信号的检测精度和稳定性对于时间同步的准确性有很大的影响,因此需要根据实际需求进行优化和调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值