GPS 모듈을 이용하여 시스템시각을 UTC 시간에 동기화하는 임베디드 장치 구성하기

반응형

본 글에서는 GPS 모듈을 포함한 임베디드 장치의 시스템 시각을 UTC에 동기화하는 방법을 설명한다.

 

개요

개발환경

개발 환경은 다음과 같다.

  • 개발 머신
    • 우분투 리눅스 14.04
  • 타겟 하드웨어 플랫폼
    • MCU: i.MX6Q (ARM)
    • GPS모듈: ublox NEO M8U
    • MCU-GPS모듈 인터페이스: UART(NMEA) and GPIO(PPS)
  • 타겟 소프트웨어 플랫폼
    • OS: 임베디드 리눅스 (커널버전 4.9.11)
    • 타겟: arm-linux-gnueabihf
    • 크로스컴파일러: arm-linux-gnueabihf-gcc-4.8.4
    • GPS 모듈 인터페이스
      • NMEA: /dev/ttymxc3
      • PPS: /dev/pps2

필요 패키지

필요한 패키지는 다음과 같다.

  • pps-tools : User-space tools for LinuxPPS 
  • GPSd : GPS service daemon
    • GPS/PPS 정보를 획득하여 chrony를 포함한 어플리케이션(GPSd clients)들에게 제공하는 데몬.
    • http://www.catb.org/gpsd/
  • chrony : Implementation of NTP(Network Time Protocol)
    • GPSd가 제공하는 GPS/PPS 정보를 이용하여 시스템 시간을 UTC에 동기화하는 데몬 (유사 프로그램 - ntpd, NTPsec)
    • 주로 NTP를 통해 시간서버와 시간을 동기화하는 프로그램이지만 GPS/PPS를 이용한 동기도 지원함.
    • https://chrony.tuxfamily.org/

 

 

사전준비사항

리눅스 개발환경 셋업

필요한 라이브러리와 패키지들을 리눅스 개발머신에 설치한다. 이미 설치되어 있는 패키지들은 무시되며, 이 중 실제로 사용되지 않는 것도 있다.

HOST$ sudo dpkg --add-architecture i386
HOST$ sudo apt-get update
HOST$ sudo apt-get install build-essential gcc-arm-linux-gnueabihf gcc-arm-linux-gnueabi libstdc++6-armhf-cross libc6-armhf-cross libc6-dev-armhf-cross binutils-arm-linux-gnueabihf linux-libc-dev-armhf-cross gcc-powerpc-linux-gnu ssh vim dos2unix libc6:i386 libncurses5:i386 libstdc++6:i386 zlib1g:i386 bc scons libncurses5-dev g++-arm-linux-gnueabihf git m4

커널 및 DTB 설정

타겟보드의 리눅스 커널이 Kernel PPS(KPPS) 기능을 지원하도록 설정하여 빌드한다.

pps-tools 빌드

패키지 다운로드

https://github.com/redlab-i/pps-tools/releases에서 1.0.2 버전(pps-tools-1.0.2.tar.gz, 2018.08.08 기준 최신 버전)을 개발머신에 다운로드한다.

HOST$ cd $WORK_DIR
HOST$ tar zxf pps-tools-1.0.2.tar.gz
HOST$ cd pps-tools-1.0.2

빌드

패키지를 빌드한다. 

빌드가 완료되면 ppsctl, ppsldisc, ppstest, ppswatch 유틸리티가 생성된다. 해당 유틸리티들은 테스트용으로만 사용되며 실제 UTC 동기를 수행하는 데에는 사용되지 않는다.

각 유틸리티의 기능은 다음과 같다.

참고로 GPSd를 빌드하기 위해서는 pps-tools 패키지 내에 포함된 timepps.h 파일이 필요하다.

HOST$ make CC=arm-linux-gnueabihf-gcc
arm-linux-gnueabihf-gcc -Wall -O2 -D_GNU_SOURCE -ggdb -fPIC -M ppstest.c ppsctl.c ppswatch.c ppsldisc.c > .depend
arm-linux-gnueabihf-gcc -Wall -O2 -D_GNU_SOURCE -ggdb -fPIC    ppstest.c  -lm -o ppstest
arm-linux-gnueabihf-gcc -Wall -O2 -D_GNU_SOURCE -ggdb -fPIC    ppsctl.c  -lm -o ppsctl
arm-linux-gnueabihf-gcc -Wall -O2 -D_GNU_SOURCE -ggdb -fPIC    ppswatch.c  -lm -o ppswatch
arm-linux-gnueabihf-gcc -Wall -O2 -D_GNU_SOURCE -ggdb -fPIC    ppsldisc.c  -lm -o ppsldisc
HOST$ ls
COPYING  Makefile  ppsctl  ppsctl.c  ppsfind  ppsldisc  ppsldisc.c  ppstest  ppstest.c  ppswatch  ppswatch.c  timepps.h

GPSd 빌드

패키지 다운로드

http://download-mirror.savannah.gnu.org/releases/gpsd/에서 gpsd-3.17 버전을 다운로드한다. 

(2018.08.08 현시점에서 가장 최신 버전이며, GPSD Time Service HOWTO 문서에서는 3.17 버전 이상을 권장한다)

HOST$ cd $WORK_DIR
HOST$ tar zxf gpsd-3.17.tar.gz
HOST$ cd gpsd-3.17

빌드 준비

GPSd를 빌드하기 위해서는 pps-tools에서 제공하는 timepps.h 파일과 타겟보드용 libncurses.so, libtinfo.so 라이브러리 파일이 필요하다.

 

pps-tools/timepps.h

timepps.h 파일을 필요한 위치(/usr/arm-linux-gnueabihf/include/sys/)로 복사한다.

  • /usr/arm-linux-gnueabihf/include/는 GPSd가 arm-linux-gnueabihf 타겟으로 빌드될 때 참조하는 기본 INCLUDE 디렉터리이다. 
  • GPSd가 빌드될 때, 해당 디렉터리에 timepps.h 파일의 존재 여부를 검사하여 존재하는 경우에만 리눅스 PPS 기능을 사용하도록 빌드된다. 
HOST$ cd $WORK_DIR
HOST$ sudo cp pps-tools-1.0.2/timepps.h /usr/arm-linux-gnueabihf/include/sys/

주) 해당 디렉터리에 timepps.h 파일이 존재해야 GPSd에서 PPS 디바이스 유형의 PPS 신호를 사용할 수 있다. GPSd가 사용할 수 있는 PPS는 두가지 종류가 있는데, 하나는 시리얼 디바이스(/dev/tty...)를 통해 받는 것과 또 하나는 PPS 디바이스(/dev/ppsX, GPIO)를 통해 받는 것이다. PPS 디바이스로 구현되어 있는 경우, GPSd가 빌드될 때 해당 디바이스를 사용할 수 있도록 "HAVE_SYS_TIMEPPS_H" 매크로가 1로 정의되어 빌드되어야 한다. 해당 매크로가 1로 정의되려면, /usr/arm-linux-gnueabihf/include/sys/ 디렉터리에 types.h, time.h, timepps.h 파일이 존재해야 한다 (types.h, time.h는 이미 존재한다) -  gpsd-3.17/SConstruct 파일 참조 (HAVE_SYS_TIMEPPS_H로 검색)

 

libncurses, libtinfo

libncurses.so.X, libtinfo.so.X 파일을 gps-3.17/ 디렉터리로 복사한다. 해당 파일들은 타겟플랫폼용을 사용해야 하므로, 타겟 플랫폼의 sysroot에서 복사하거나 혹은 실제 타겟보드의 라이브러리 디렉터리(/lib, /usr/lib)에 위치한 libncurses.so.x, libtinfo.so.x 파일을 개발머신으로 업로드하여 사용한다.

HOST$ cd $WORK_DIR
HOST$ cp $SYSROOT/lib/libncurses.so.5 gps-3.17/libncurses.so
HOST$ cp $SYSROOT/lib/libtinfo.so.5 gps-3.17/libtinfo.so

주의) 원본 파일명에 포함된 버전과 상관없이 gpsd-3.17/ 디렉터리에는 파일명의 버전값을 제거한 libncurses.so, libtinfo.so 라는 파일명으로 복사해야 한다.

빌드

다음 명령을 통해 빌드한다. 

각 명령의 의미는 다음과 같다.

  • timeservice=yes : GPSd가 Time Service 기능을 제공하도록 빌드한다. 반드시 yes로 설정되어야 한다.
  • nmea0183=yes : GPS모듈이 NMEA0183 버전을 지원하는 경우 yes로 설정한다. default 값은 no이다.
  • fixed_port_speed=115200 / fixed_stop_bits=1 : GPS모듈이 UART로 전송하는 Baudrate/Stop Bit를 설정한다. (GPS 모듈이 실제 동작하는 값으로 세팅한다. 사실, 값을 세팅하지 않아도 GPSd가 자동으로 감지하는 기능이 포함되어 있긴 하다)
  • target=arm-linux-gnueabihf : arm-linux-gnueabihf 플랫폼용으로 빌드하기 위해 설정한다.

결과물 중 gpsd가 주요 서비스 데몬이며, 그 외 유틸리티는 ppscheck, gpsmon, cgps가 주로 사용된다.

HOST$ scons -c      ;# 기존 결과 파일 삭제
HOST$ scons timeservice=yes nmea0183=yes fixed_port_speed=115200 fixed_stop_bits=1 target=arm-linux-gnueabihf

 

scons가 설치되어 있지 않을 경우 다음 명령으로 설치한 후 진행한다.

HOST$ sudo apt install scons

 

 

Chrony 빌드

패키지 다운로드

https://chrony.tuxfamily.org/download.html에서 chrony-3.3.tar.gz를 다운로드한다.

(2018.08.08 현 시점에서 가장 최신 버전이다)

HOST$ cd $WORK_DIR
HOST$ tar zxf chrony-3.3.tar.gz
HOST$ cd chrony-3.3

빌드

다음 명령을 통해 빌드한다. 결과물 중 chronyd가 주요 서비스 데몬이며, 확인용 유틸리티로 chronyc가 사용된다.

HOST$ make clean        ;# 기존 결과 파일 삭제
HOST$ CC=arm-linux-gnueabihf-gcc ./configure
HOST$ make

 

 

타겟보드 다운로드

위 절차에서 생성된 파일들을 타겟보드에 다운로드한다.

  • gpsd-3.17/gpsd
  • gpsd-3.17/cgps
  • gpsd-3.17/ppscheck
  • gpsd-3.17/ntpshmmon
  • gpsd-3.17/gpsmon
  • chrony-3.3/chronyc
  • chrony-3.3/chronyd
  • chrony-3.3/examples/chrony.conf.example1
  • pps-tools-1.0.2/ppstest

 

타겟보드 동작 확인

타겟보드를 UTC 동기 디바이스로 만들기 전에 이 챕터에서 설명된 내용을 차례로 확인하여, 각 구성이 제대로 동작하고 있는지 확인한다.

Kernel PPS 동작 확인

ppstest 유틸리티를 이용하여 PPS의 동작을 확인할 수 있다. 해당 유틸리티를 실행하여 PPS 이벤트에 대한 로그가 1초에 한번씩 출력되는 것을 확인한다.

TARGET$ ./ppstest /dev/pps2
trying PPS source "/dev/pps2"
found PPS source "/dev/pps2"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1533780973.004587988, sequence: 6873 - clear  0.000000000, sequence: 0
source 0 - assert 1533780974.004590689, sequence: 6874 - clear  0.000000000, sequence: 0
source 0 - assert 1533780975.004592389, sequence: 6875 - clear  0.000000000, sequence: 0
source 0 - assert 1533780976.004585090, sequence: 6876 - clear  0.000000000, sequence: 0
source 0 - assert 1533780977.004595457, sequence: 6877 - clear  0.000000000, sequence: 0
source 0 - assert 1533780978.004587491, sequence: 6878 - clear  0.000000000, sequence: 0

GPS 정보 입력 확인

gpsmon

gpsmon 유틸리티를 통해 입력되는 GPS 정보를 확인할 수 있다.  참고로 이 유틸리티는 gpsd 데몬과 동시에 실행할 수 없다.

GPS를 통해 들어오는 NMEA 메시지들(마지막줄)과 파싱된 정보들을 확인할 수 있다.

참고로 PPS 값은 공란으로 비워져 있는데, 이는 본 플랫폼에서는 시리얼 기반 PPS가 아닌 GPIO 기반 PPS를 사용하기 때문이다.

TARGET$ ./gpsmon /dev/ttymxc3

gpsmon 실행 화면

cgps

GPSd 데몬이 동작하는 상태에서 cgps(GPSd API client)를 통해 GPSd가 클라이언트들에게 제공하는 정보를 확인할 수 있다.

gpsmon 유틸리티를 사용하는 것과 다른 점은 gpsmon은 시리얼로 입력되는 NMEA 메시지를 직접 파싱하여 보여주는 것이고, cgps는 GPSd가 제공하는 정보를 GPSd API를 통해 확인하는 것이다. 따라서 cgps로 확인을 해야 GPSd가 제대로 동작하는 것을 확신할 수 있다.

TARGET$ ./gpsd -n /dev/ttymxc3 /dev/pps2
TARGET$ ./cgps

cgps 실행 화면

Time Service 동작 확인

ntpshmmon 유틸리티를 통해 GPSd가 Time Service를 제대로 제공하는지 확인할 수 있다. (GPSd가 Chrony 등 클라이언트들에게 제공하는 시간정보가 공유메모리에 저장되고 있는 것을 확인할 수 있다.)

아래 출력 결과에서 NTP0는 /dev/ttymxc3로부터 입력되는 시간이고, NTP2는 /dev/pps2로부터 입력되는 시간이다. Prec 값을 보면 확실히 PPS 디바이스로부터 입력된 시간값이 보다 정확한 것을 알 수 있다.

  • Prec = log2(source jitter)
  • NTP0's source jitter = 2^(-20)
  • NTP2's source jitter = 2^(-30)

여기서 만약 NTP0 하나만 출력된다면 PPS 디바이스는 제대로 동작하지 않는 것이다. 이 부분이 잘 동작한다면 NTPsec/Chrony 등을 이용해 정밀한 UTC 동기 디바이스를 만들 수 있다.

TARGET$ ./ntpshmmon
ntpshmmon version 1
# Name Seen@ Clock Real L Prec
sample NTP0 1504654548.268040766 1504654547.810452099 1533732791.000000000 0 -20
sample NTP2 1504654548.268352766 1504654547.374487766 1533732791.000000000 0 -30
sample NTP2 1504654548.375504433 1504654548.374492099 1533732792.000000000 0 -30
sample NTP0 1504654548.802300433 1504654548.801384766 1533732792.000000000 0 -20
sample NTP2 1504654549.374916766 1504654549.374499766 1533732793.000000000 0 -30

GPSd 로그 확인

GPSd 실행 시 -N 옵션을 사용하면 데몬이 아닌 foreground로 실행하여 로그를 확인할 수 있다. -D 옵션을 이용하여 로그 출력 레벨을 조정할 수 있다.

해당 로그에서는 GPS정보 수신, PPS 수신, 이벤트, 처리루틴 등에 관련된 로그를 확인할 수 있다.

PPS 디바이스가 잘 동작하지 않는 경우, KPPS/PPS 로그 라인이 주기적으로 출력되지 않는 것을 확인할 수 있으며, 초기화 루틴의 로그 내용을 확인하여 각종 에러 원인을 확인할 수 있다.

TARGET$ killall -9 gpsd
TARGET$ ./gpsd -D 8 -n -N /dev/ttymxc3 /dev/pps2

주) 처음 실행 시 PPS가 제대로 되지 않는 현상이 있었는데 해당 로그를 확인하여 해결하였다 - timepps.h 파일의 부재로 HAVE_SYS_TIMEPPS_H 매크로가 정의되지 않아, 초기화 루틴에서 pps 이벤트 처리 쓰레드가 종료되어 버리는 것을 로그를 통해 확인하여 수정하였다.

 

 

타겟보드 UTC 동기 실행

chrony 설정 파일 편집

다운로드한 chrony.conf.example1 파일을 /etc/chrony/chrony.conf로 복사한 후 다음과 같이 수정한다. 아래 파일 내용 중 마지막 두 줄이 수정된 내용이다.

마지막 줄의 PPS 소켓 이름이 /var/run/chrony.pps2.sock 인 것은 실제로 /dev/pps2라는 이름의 PPS 디바이스를 사용하기 때문이다.

마지막에서 두번째 줄의 GPS 설정의 precision은 위에서 실행한 ntpshmmon의 Prec 결과값을 적용한다. 위 결과에 따라 2^(-20) = 1/1048576 = 약 10^(-6)이 된다.

# Use public NTP servers from the pool.ntp.org project.
pool pool.ntp.org iburst
 
# Record the rate at which the system clock gains/losses time.
driftfile /var/lib/chrony/drift
 
# Allow the system clock to be stepped in the first three updates
# if its offset is larger than 1 second.
makestep 1.0 3
 
# Enable kernel synchronization of the real-time clock (RTC).
#rtcsync
refclock SHM 0 refid GPS precision 1e-6 offset 0.9999 delay 0.2
refclock SOCK /var/run/chrony.pps2.sock refid PPS

데몬 실행

다음 절차에 따라 chrony와 gpsd를 실행한다 

주의해야 할 점은, 위 데몬들은 root 권한으로 실행되어야 한다. root 계정이 아닐 경우 KPPS를 사용할 수 없다.

 

주) 위 데몬들을 root 계정으로 실행하면, KPPS 등을 초기화하는 초기화 루틴을 수행한 후 nobody 계정 권한으로 변경되어 동작한다 (이는 데몬이 root 권한으로 동작할 때의 보안 위험성 때문에 최근 리눅스에서 동작하는 방식이다). 

 

아래 명령들을 디바이스 초기화 스크립트에 추가하여 부팅 시 자동 실행되도록 한다.

gpsd의 -n옵션은 활성화된 클라이언트가 없을 때에도 클럭을 업데이트하도록 강제하는 옵션이다. GPSd 빌드 시 timeservice=yes로 설정할 경우에는 이 옵션을 반드시 사용해야 한다.

TARGET$ su -
TARGET$ killall -9 gpsd chronyd     ;# 기존에 실행 중인 데몬 종료
TARGET$ ./chronyd -f /etc/chrony/chrony.conf
TARGET$ sleep 2
TARGET$ ./gpsd -n /dev/ttymxc3 /dev/pps2

동작 확인

chronyc

chronyc 유틸리티를 이용하여 동기화 상태를 확인한다.

"chronyc sources" 명령을 통해 확인한다. 첫번째 행은 GPS디바이스(ttymxc3)를 이용한 동기화 상태, 두번째 행은 PPS디바이스(pps2)를 이용한 동기화 상태를 나타내며, 각 열의 의미는 다음과 같다. 

(http://manpages.ubuntu.com/manpages/bionic/man1/chronyc.1.html)

  • M : 참조클럭 소스의 유형 - #: 로컬시스템에 연결된 참조클럭
  • S : 참조클럭 소스의 상태 - x: indicates a clock which chronyd thinks is a falseticker (i.e. its time is inconsistent with a majority of other sources
  • Name/IP address : 참조클럭 소스의 이름
  • Stratum : 소스의 등급 
  • Poll : 참조클럭 소스가 샘플링되는 주기 - 4일 경우 2^4 = 16. 즉 16초마다 샘플링 수행.
  • Reach : This shows the source’s reachability register printed as an octal number. The register has 8 bits and is updated on every received or missed packet from the source. A value of 377 indicates that a valid reply was received for all from the last eight transmissions.
  • LastRx : 참조클럭 소스가 마지막으로 샘플링된 시간 (현재로부터 X초) -> 16일 경우, 17초 전에 샘플링 했다는 의미.
  • Last sample : 로컬클럭과 참조소스클럭 사이의 오프셋 - +/-는 지터
TARGET$ ./chronyc sources
210 Number of sources = 2
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
#x GPS                           0   4    37    16   -581ms[ -581ms] +/-  101ms
#x PPS                           0   4    37    13  +1496us[+1496us] +/- 1666ns

 

"chronyc sourcestats" 명령을 통해 확인한다. 각 열의 의미는 다음과 같다. (http://manpages.ubuntu.com/manpages/bionic/man1/chronyc.1.html)

  • Name/IP Address : 참조 클럭의 이름
  • NP : This is the number of sample points currently being retained for the server. The drift rate and current offset are estimated by performing a linear regression through these points.
  • NR : This is the number of runs of residuals having the same sign following the last regression. If this number starts to become too small relative to the number of samples, it indicates that a straight line is no longer a good fit to the data. If the number of runs is too low, chronyd discards older samples and re-runs the regression until the number of runs becomes acceptable.
  • Span: 가장 오래된 샘플과 가장 최근의 샘플 사이의 간격(초 단위)이다. 
  • Frequency : This is the estimated residual frequency for the server, in parts per million. In this case, the computer’s clock is estimated to be running 1 part in 10^9 slow relative to the server.
  • Freq Skew : This is the estimated error bounds on Freq (again in parts per million).
  • Offset : This is the estimated offset of the source - 참조 클럭과 시스템 시각 사이의 오프셋???
  • Std Dev : This is the estimated sample standard deviation.
TARGET$ ./chronyc sourcestats
210 Number of sources = 2
Name/IP Address            NP  NR  Span  Frequency  Freq Skew  Offset  Std Dev
==============================================================================
GPS                         4   3    51   -444.762   5492.834   -593ms  6760us
PPS                         4   3    47    +39.273    295.814  +2119us   983ns

date

"date" 명령을 통해 UTC에 동기화된 시스템 시각을 확인한다.

단, chronyc와 gpsd를 실행한 후 시스템시각이 동기화되기 까지는 꽤 오랜 시간이 소요될 수 있다.

TARGET$ date
Thu Aug  9 04:40:44 UTC 2018

 

기타

ppscheck

ppscheck를 이용하여 PPS 이벤트 상태를 확인할 수 있다고 되어 있다. 그런데 타겟플랫폼에서 실행해보면 에러가 발생한다. 추측컨대 본 유틸리티는 시리얼 기반의 PPS를 체크하는 프로그램 같다. 즉, 시리얼 디바이스 드라이버에서는 PPS ioctl을 지원하지만, /dev/pps2는 시리얼 디바이스가 아니므로 해당 ioctl을 지원하지 않는 것으로 보인다.

TARGET$ ./ppscheck /dev/pps2
# Seconds nanoSecs Signals
PPS ioctl(TIOCMIWAIT) failed: 25 Inappropriate ioctl for device
 
TARGET$ ./ppscheck /dev/ttymxc3
# Seconds nanoSecs Signals
--> 이후로 반응 없음.

 

참고

댓글

Designed by JB FACTORY