Kernel PPS 신호 사용하기 (C source code)

반응형

보통 GNSS(GPS) 모듈은 1초에 한번씩 펄스 신호를 출력하는 핀을 가지고 있다. 해당 신호의 출력 시점은 UTC 절대시각과 매우 정밀하게 동기화되어 있기 때문에, 시스템에서는 해당 신호를 이용하여 시간 동기 등의 동작을 보다 정밀하게 수행할 수 있다.

 

일반적으로, GNSS 모듈을 사용하는 시스템은 GNSS 모듈의 PPS 신호 출력 핀을 프로세서(CPU 등)에 연결하여, PPS 신호 발생 시마다 해당 신호를 프로세서 상에서 동작하는 프로그램에서 사용할 수 있도록 구성한다.

 

시스템 운영체제가 리눅스일 경우, 리눅스 커널이 PPS 신호를 처리한 후 시스템 콜을 통해 어플리케이션 프로그램이 해당 신호의 발생시점을 받을 수 있도록 하는 Kernel PPS 기능을 제공된다. 따라서 어플리케이션 프로그램에서는 로우레벨의 신호 처리를 수행할 필요 없이, 커널이 제공하는 시스템 콜을 이용하여 쉽게 PPS 신호를 받아서 이용할 수 있다.

 

로우레벨에서 직접 처리하지 않기 때문에 실제 PPS 신호가 발생한 시점과 어플리케이션 프로그램이 해당 신호를 받는 시점에는 차이가 있을 수 있다. 따라서, 관련 시스템콜은 PPS 신호가 발생한 시점의 타임스탬프 값을 어플리케이션 프로그램에 전달해 줌으로써, 어플리케이션 프로그램의 신호 수신 시점과 상관없이 실제 PPS 신호가 발생한 시점이 언제인지 알 수 있도록 제공한다.

 

어플리케이션 프로그램이 리눅스 커널이 제공하는 Kernel PPS 기능을 사용하기 위해서는 다음과 같은 사항이 만족되어야 한다.

  • GNSS 모듈의 PPS 신호핀이 프로세서에 연결 (예: 입력으로 설정된 GPIO 핀에 연결)
  • 커널 PPS 기능을 제공하도록 커널 설정(컴파일)

커널 PPS 기능이 활성화되면, 시스템 상에 "/dev/pps1"과 같은 장치 파일이 생성되며 어플리케이션 프로그램은 해당 파일에 접근하여 PPS 신호를 받을 수 있다.

 

PPS 관련된 내용은 아래 글에서도 확인할 수 있다.

 

다음은 커널 PPS 기능을 사용하는 어플리케이션 프로그램 샘플 코드이다.

헤더 파일 중 timepps.h 파일은 pps-tools(https://github.com/redlab-i/pps-tools)라는 유틸리티 패키지 안에 포함된 파일을 활용하면 된다.

 

커널 PPS 사용 샘플 코드 - pps-test.c

#include <linux/pps.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>
#include <pthread.h>

#include "timepps.h"


int g_fd;
pps_handle_t g_handle;
bool g_canwait;
pthread_t g_thread;
const char *g_dev_name = "/dev/pps1";


/**
 * @brief PPS 기능을 초기화한다.
 * @retval 0: 성공
 * @retval -1: 실패 
 */ 
static int init_pps_function(void)
{
  /*
   * PPS 디바이스를 연다.
   */
  g_fd = open(g_dev_name, O_RDWR);
  if (g_fd < 0) {
    perror("Fail to open():");
    return -1;
  }

  /*
   * PPS 파일디스크립터에 대한 pps_handle을 생성한다.
   */
  int ret = time_pps_create(g_fd, &g_handle);
  if (ret < 0) {
    perror("Fail to time_pps_create();");
    close(g_fd);
    return -1;
  }

  /*
   * PPS 디바이스의 지원 모드를 확인한다.
   */
  int pps_mode;
  ret = time_pps_getcap(g_handle, &pps_mode);
  if (ret < 0) {
    perror("Fail to time_pps_getcap(): ");
    time_pps_destroy(g_handle);
    close(g_fd);
    return -1;
  }
  if (((pps_mode & PPS_CAPTUREASSERT) == 0) || ((pps_mode & PPS_OFFSETASSERT) == 0)) {
    printf("pps_mode : %X(PPS_CAPTUREASSERT or PPS_OFFSETASSERT is not supported)\n", pps_mode);
    time_pps_destroy(g_handle);
    close(g_fd);
    return -1;
  }
  if (pps_mode & PPS_CANWAIT) {
    g_canwait = true;
  }

  /*
   * assert 타임스탬프를 캡쳐하도록 설정하며, 전달지연 675 nsec 보상값을 설정한다. (per rfc2783)
   */
  pps_params_t params;
  ret = time_pps_getparams(g_handle, &params);
  if (ret < 0) {
    perror("Fail to time_pps_getparams():");
    time_pps_destroy(g_handle);
    close(g_fd);
    return -1;
  }
  params.assert_offset.tv_sec = 0;
  params.assert_offset.tv_nsec = 675;
  params.mode |= (PPS_CAPTUREASSERT | PPS_OFFSETASSERT);  // assert 타임스탬프 캡쳐
  params.mode &= ~(PPS_CAPTURECLEAR | PPS_OFFSETCLEAR);   // clear 타임스탬프 캡쳐 X
  ret = time_pps_setparams(g_handle, &params);
  if (ret < 0) {
    perror("Fail to time_pps_setparams():");
    time_pps_destroy(g_handle);
    close(g_fd);
    return -1;
  }

  return 0;
}


/**
 * @brief PPS 기능을 해제(종료)한다.
 */ 
static void release_pps_function(void)
{
  time_pps_destroy(g_handle);
  close(g_fd);
}


/**
 * @brief PPS 신호 폴링 쓰레드
 * @param priv 사용하지 않음
 * @return NULL (쓰레드 종료시에만 리턴됨)
 */ 
static void * pps_poll_thread(void *priv)
{
  (void)priv;
  int ret;
  pps_info_t info_buf;
  struct timespec timeout;

  while(1) {

    // PPS 신호를 폴링한다. PPS 신호가 발생될 때마다(1초 주기) 리턴된다.
    timeout.tv_sec = 3;
    timeout.tv_nsec = 0;
    if (g_canwait) {
      ret = time_pps_fetch(g_handle, PPS_TSFMT_TSPEC, &info_buf, &timeout);
    } else {
      sleep(1);
      ret = time_pps_fetch(g_handle, PPS_TSFMT_TSPEC, &info_buf, &timeout);
    }
    if (ret < 0) {
      perror("Fail to fetch PPS: ");
      continue;
    }

    // TODO ...
    printf("PPS assert timestamp: %ld.%09ld, sequence: %ld\n", 
            info_buf.assert_timestamp.tv_sec, info_buf.assert_timestamp.tv_nsec, info_buf.assert_sequence);
  }

  return NULL;
}


/**
 * @brief 어플리케이션 메인함수
 * @return 
 */ 
int main(void)
{
  /*
   * PPS 핸들을 초기화한다.
   */
  int ret = init_pps_function();
  if (ret < 0) {
    return -1;
  }

  /*
   * PPS 폴링 쓰레드를 생성한다.
   */
  ret = pthread_create(&g_thread, NULL, pps_poll_thread, NULL);
  if (ret < 0) {
    perror("Fail to pthread_create(): ");
    release_pps_function();
    return -1;
  }

  pthread_join(g_thread, NULL);

  release_pps_function();
  return 0;
}

 

다음 명령으로 빌드한다.

gcc -o pps-test pps-test.c -lpthread

 

 

댓글

Designed by JB FACTORY