ORANGE PI PLUS H3 with GPS/PPS

Installing Armbian

Follow the installation instruction on the ARMBIAN Website, it is pretty straight forward and works very well.

If you have eMMC on Board you can run the script “nand-sata-install”, it worked well on my ORANGE PI PLUS H3.

After installing the ARMBIAN Linux

# Login as root or prepend "sudo" to the following commands
# INSTALL CHRONY, GPSD and PPS-TOOLS
apt-get install chrony gpsd gpsd-clients pps-tools

Connect the GPS

The GPS (with PPS Output shall be connected to the GPIO connector, using the following pins:

+ DC Power to the GPS  3.3 V. or 5 V. Pin 1 (+3.3 Volts) or Pin 2 (+5 Volts)
 – DC (Ground) Pin 6 or Pin 9 or Pin 14 …
GPS TX Signal Pin 10 (PA14 – UART3_RX) [/dev/ttyS3]
GPS RX Signal Pin 8 (PA13 – UART3_TX)[/dev/ttyS3]
GPS PPS Signal Pin 7 (PA6 – PWM1)

Recompile the Kernel (First Run)

# Clone the Repository inside a directory of choice.

git clone --depth 1 https://github.com/igorpecovnik/lib

cp lib/compile.sh

# Modify the compile.sh script to allow kernel configuration as follows
KERNEL_ONLY=""                     # leave empty to select each time, set to "yes" or "no" to skip dialog prompt
KERNEL_CONFIGURE="yes"             # change provided kernel configuration
CLEAN_LEVEL="make,debs,oldcache"   # comma-separated list of clean targets: "make" = make clean for selected kernel and u-boot,
                                   # "debs" = delete packages in "./output/debs" for current branch and family,
                                   # "alldebs" = delete all packages in "./output/debs", "images" = delete "./output/images",
                                   # "cache" = delete "./output/cache", "sources" = delete "./sources"
                                   # "oldcache" = remove old cached rootfs except for the newest 6 files

DEST_LANG="en_US.UTF-8"            # sl_SI.UTF-8, en_US.UTF-8

# advanced
KERNEL_KEEP_CONFIG="yes"           # do not overwrite kernel config before compilation
EXTERNAL="yes"                     # build and install extra applications and drivers
EXTERNAL_NEW="prebuilt"            # compile and install or install prebuilt additional packages
CREATE_PATCHES="yes"               # wait that you make changes to uboot and kernel source and creates patches
FORCE_CHECKOUT="no"                # ignore manual changes to source
BUILD_ALL="no"                     # cycle through available boards and make images or kernel/u-boot packages.
                                   # set KERNEL_ONLY to "yes" or "no" to build all packages/all images

# Launch the compile.sh script

./compile.sh

# choose "Kernel and u-boot packages"
# then choose your target baoard (in my case "orangepiplus2e" worked fine)
# then choose the "default" kernel branch
# the u-boot compilation will start
# once the kernel configuration is launched, change the following:
#  - General Setup - Change the Local Version (i.e. "-PPS")
#  - Kernel Features - disable "Tickless System"
#  - Device Drivers
#    - PPS Support - Enable it
#      - PPS Client using GPIO - Enable it
#    - PTP Clock Support - Enable it (if you want to use Precision Time Protocol)
# exit saving the configuration to continue kernel compilation


Check if the GPS is working

# Edit the file /etc/default/gpsd and make it same as the following lines:
#-- EDIT -------------------------------------------------------------------------------
# Default settings for the gpsd init script and the hotplug wrapper.

# Start the gpsd daemon automatically at boot time
START_DAEMON="true"

# Use USB hotplugging to add new USB devices automatically to the daemon
USBAUTO="true"

# Devices gpsd should collect to at boot time.
# They need to be read/writeable, either by user gpsd or the group dialout.
# DEVICES=""
DEVICES="/dev/ttyS3"

# Other options you want to pass to gpsd
GPSD_OPTIONS="-n -b "
#-- END EDIT ----------------------------------------------------------------------------

service gpsd restart
stty -F /dev/ttyS3 115200
cgps -um
#-- OUTPUT -----------------------------------------------------------------------------
  Time:      2017-01-28T22:26:30.000Z   PRN:   Elev:  Azim:  SNR:  Used: 0m
  Latitude:   45.464184 N                 2    03    038    00      N   0m
  Longitude:   9.190196 E                 5    15    062    19      Y   0m
  Altitude:  145.3 m                     12    04    116    12      N   0m
  Speed:     0.1 kph                     16    14    300    00      N   0m
  Heading:   0.0 deg (true)              20    18    114    00      N   0m
  Climb:     0.0 m/min                   21    47    182    33      Y   0m
  Status:    3DO FIX 80 secs)            23    03    319    00      N   0m
  Longitude Err   +/- 2 m                25    43    115    27      Y   0m
  Latitude Err:   +/- 3 m                26    42    303    27      Y   0m
  Altitude Err:   +/- 8 m                29    64    047    29      Y   0m
  Course Err:     n/a                    31    54    239    26      Y   0m
  Speed Err:      n/a                   123    00    000    37      N   0m
  Time offset:    -21.191               
#-- END OUTPUT --------------------------------------------------------------------------

# configure chrony.conf with the following lines:
#-- EDIT -------------------------------------------------------------------------------
refclock PPS /dev/pps0 poll 0 refid GPPS lock GPSD offset 0.0000003 filter 0 maxdispersion 0.000450 
refclock SHM 0 refid GPSD precision 1e-1 offset 0.001 delay 0.0 poll 1 filter 32 maxdispersion 0.0605 noselect
#-- END EDIT ----------------------------------------------------------------------------

chronyc sources
#-- OUTPUT -----------------------------------------------------------------------------
210 Number of sources = 6
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
#? GPPS                          0   0     0     0  +7498ns[+7576ns] +/- 1000ns
#? GPSD                          0   1   377     1    +67ms[  +67ms] +/-  100ms
=- 172.16.5.31                   2   3   377     0   -408us[ -408us] +/-  815us
=- 172.16.5.32                   1   6   377    47    -73us[  -72us] +/-  181us
^- ntp1.inrim.it                 1  10   377   931   -920us[ -938us] +/- 3434us
^- ntp.freestone.net             1  10   377   394   -688us[ -692us] +/-   12ms
#-- END OUTPUT --------------------------------------------------------------------------
# As you can see the PPS is not working yet, to check it do the following:

modprobe gpio-sunxi
for A in 1 2 3 4 5 6 7 8 9 10 11 12; do echo "$A" > /sys/class/gpio/export ; done
ll /sys/class/gpio/
#-- OUTPUT -----------------------------------------------------------------------------
total 0
drwxr-xr-x  2 root root    0 Jan  1  1970 ./
drwxr-xr-x 53 root root    0 Jan  1  1970 ../
--w-------  1 root root 4096 Jan 28 21:49 export
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio1 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio1/
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio10 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio10/
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio11 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio11/
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio12 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio12/
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio2 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio2/
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio3 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio3/
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio4 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio4/
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio5 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio5/
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio6 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio6/
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio7 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio7/
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio8 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio8/
lrwxrwxrwx  1 root root    0 Jan 28 21:49 gpio9 -> ../../devices/platform/sunxi-pinctrl/gpio/gpio9/
lrwxrwxrwx  1 root root    0 Jan  1  1970 gpiochip0 -> ../../devices/platform/sunxi-pinctrl/gpio/gpiochip0/
--w-------  1 root root 4096 Jan  1  1970 unexport
#-- END OUTPUT --------------------------------------------------------------------------

echo in > /sys/class/gpio/gpio6/direction
 while true; do cat /sys/class/gpio/gpio6/value ; sleep 0.2; done
#-- OUTPUT -----------------------------------------------------------------------------
0
1   <---- PPS Asserted
0
0
0
0
0
0
0
0
1   <---- PPS Asserted
0
0
0
0
0
0
0
0
1   <---- PPS Asserted
0
#-- END OUTPUT --------------------------------------------------------------------------

At this point the GPS is working, from the hardware standpoint, but the PPS cannot be picked up by chrony, because the system ignores which is the gpio pin

Recompile the Kernel (Second Run)

The main issue with PPS on the Orange PI is the correct mapping of the GPIO Pin. Theoretically it should be don modifying the DSTI or the FEX. It is too complicate to use this method and for our purposes modifying the “pps-gpio” driver is faster and easier.

First let’s replace the original “pps-gpio.c” source with this one:

/*
 * pps-gpio.c -- PPS client driver using GPIO
 *
 *
 * Copyright (C) 2010 Ricardo Martins <rasm@fe.up.pt>
 * Copyright (C) 2011 James Nuss <jamesnuss@nanometrics.ca>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define PPS_GPIO_NAME "pps-gpio"
#define pr_fmt(fmt) PPS_GPIO_NAME ": " fmt

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/pps_kernel.h>
#include <linux/pps-gpio.h>
#include <linux/gpio.h>
#include <linux/list.h>
#include <linux/moduleparam.h>


/* Info for each registered platform device */
struct pps_gpio_device_data {
	int irq;			/* IRQ used as PPS source */
	struct pps_device *pps;		/* PPS source device */
	struct pps_source_info info;	/* PPS source information */
	const struct pps_gpio_platform_data *pdata;
};

/*
 * Report the PPS event
 */

static irqreturn_t pps_gpio_irq_handler(int irq, void *data)
{
	const struct pps_gpio_device_data *info;
	struct pps_event_time ts;
	int rising_edge;

	/* Get the time stamp first */
	pps_get_ts(&ts);

	info = data;

	rising_edge = gpio_get_value(info->pdata->gpio_pin);
	if ((rising_edge && !info->pdata->assert_falling_edge) ||
			(!rising_edge && info->pdata->assert_falling_edge))
		pps_event(info->pps, &ts, PPS_CAPTUREASSERT, NULL);
	else if (info->pdata->capture_clear &&
			((rising_edge && info->pdata->assert_falling_edge) ||
			 (!rising_edge && !info->pdata->assert_falling_edge)))
		pps_event(info->pps, &ts, PPS_CAPTURECLEAR, NULL);

	return IRQ_HANDLED;
}

static int pps_gpio_setup(struct platform_device *pdev)
{
	int ret;
	const struct pps_gpio_platform_data *pdata = pdev->dev.platform_data;

	ret = gpio_request(pdata->gpio_pin, pdata->gpio_label);
	if (ret) {
		pr_warning("failed to request GPIO %u\n", pdata->gpio_pin);
		return -EINVAL;
	}

	ret = gpio_direction_input(pdata->gpio_pin);
	if (ret) {
		pr_warning("failed to set pin direction\n");
		gpio_free(pdata->gpio_pin);
		return -EINVAL;
	}

	return 0;
}

static unsigned long
get_irqf_trigger_flags(const struct pps_gpio_platform_data *pdata)
{
	unsigned long flags = pdata->assert_falling_edge ?
		IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;

	if (pdata->capture_clear) {
		flags |= ((flags & IRQF_TRIGGER_RISING) ?
				IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING);
	}

	return flags;
}

static int pps_gpio_probe(struct platform_device *pdev)
{
	struct pps_gpio_device_data *data;
	int irq;
	int ret;
	int err;
	int pps_default_params;
	const struct pps_gpio_platform_data *pdata = pdev->dev.platform_data;


	/* GPIO setup */
	ret = pps_gpio_setup(pdev);
	if (ret)
		return -EINVAL;

	/* IRQ setup */
	irq = gpio_to_irq(pdata->gpio_pin);
	if (irq < 0) {
		pr_err("failed to map GPIO to IRQ: %d\n", irq);
		err = -EINVAL;
		goto return_error;
	}

	/* allocate space for device info */
	data = kzalloc(sizeof(struct pps_gpio_device_data), GFP_KERNEL);
	if (data == NULL) {
		err = -ENOMEM;
		goto return_error;
	}

	/* initialize PPS specific parts of the bookkeeping data structure. */
	data->info.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT |
		PPS_ECHOASSERT | PPS_CANWAIT | PPS_TSFMT_TSPEC;
	if (pdata->capture_clear)
		data->info.mode |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR |
			PPS_ECHOCLEAR;
	data->info.owner = THIS_MODULE;
	snprintf(data->info.name, PPS_MAX_NAME_LEN - 1, "%s.%d",
		 pdev->name, pdev->id);

	/* register PPS source */
	pps_default_params = PPS_CAPTUREASSERT | PPS_OFFSETASSERT;
	if (pdata->capture_clear)
		pps_default_params |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR;
	data->pps = pps_register_source(&data->info, pps_default_params);
	if (data->pps == NULL) {
		kfree(data);
		pr_err("failed to register IRQ %d as PPS source\n", irq);
		err = -EINVAL;
		goto return_error;
	}

	data->irq = irq;
	data->pdata = pdata;

	/* register IRQ interrupt handler */
	ret = request_irq(irq, pps_gpio_irq_handler,
			get_irqf_trigger_flags(pdata), data->info.name, data);
	if (ret) {
		pps_unregister_source(data->pps);
		kfree(data);
		pr_err("failed to acquire IRQ %d\n", irq);
		err = -EINVAL;
		goto return_error;
	}

	platform_set_drvdata(pdev, data);
	dev_info(data->pps->dev, "Registered IRQ %d as PPS source\n", irq);

	return 0;

return_error:
	gpio_free(pdata->gpio_pin);
	return err;
}

static int pps_gpio_remove(struct platform_device *pdev)
{
	struct pps_gpio_device_data *data = platform_get_drvdata(pdev);
	const struct pps_gpio_platform_data *pdata = data->pdata;

	platform_set_drvdata(pdev, NULL);
	free_irq(data->irq, data);
	gpio_free(pdata->gpio_pin);
	pps_unregister_source(data->pps);
	pr_info("removed IRQ %d as PPS source\n", data->irq);
	kfree(data);
	return 0;
}

static struct platform_driver pps_gpio_driver = {
	.probe		= pps_gpio_probe,
	.remove		=  __devexit_p(pps_gpio_remove),
	.driver		= {
		.name	= PPS_GPIO_NAME,
		.owner	= THIS_MODULE
	},
};

static struct pps_gpio_platform_data pps_gpio_info = {
	.assert_falling_edge = false,
	.capture_clear = false,
        .gpio_pin = 1,
	.gpio_label = "PPS",
};

static struct platform_device pps_gpio_device = {
	.name = "pps-gpio",
	.id = -1,
	.dev = { .platform_data = &pps_gpio_info },
};

static int __init pps_gpio_init(void)
{
	int ret = platform_driver_register(&pps_gpio_driver);
        if (ret < 0) {
		pr_err("failed to register platform driver\n");
                return ret;
        }

	ret = platform_device_register(&pps_gpio_device);
        if (ret < 0)
		pr_err("failed to register pps_gpio_device\n");
	return ret;
}

static void __exit pps_gpio_exit(void)
{
	platform_device_unregister(&pps_gpio_device);
	platform_driver_unregister(&pps_gpio_driver);
	pr_debug("unregistered platform driver\n");
}

module_init(pps_gpio_init);
module_exit(pps_gpio_exit);

module_param_named(gpio_pin, pps_gpio_info.gpio_pin, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_pin, "GPIO Pin number (default 1)");
module_param_named(falling_edge, pps_gpio_info.assert_falling_edge,
                   bool, S_IRUGO);
MODULE_PARM_DESC(falling_edge, "PPS occurs as falling edge (default 0)");
module_param_named(capture_clear, pps_gpio_info.capture_clear, bool, S_IRUGO);
MODULE_PARM_DESC(capture_clear, "PPS capture clear (default 0)");

MODULE_AUTHOR("Ricardo Martins <rasm@fe.up.pt>");
MODULE_AUTHOR("James Nuss <jamesnuss@nanometrics.ca>");
MODULE_AUTHOR("Chen Wei <weichen302@gmail.com>");
MODULE_DESCRIPTION("Use GPIO pin as PPS source");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0.1");

You can substitute the file after the u-boot compilation when the script compile.sh will wait for a patch (option CREATE_PATCHES=”yes”) and it will save the modification under the directory “/userpatches/patch/”.

The path to the “pps-gpio.c” source is “./sources/linux-sun8i/sun8i/drivers/pps/clients/”

Here is the generated patch file:

diff --git a/drivers/pps/clients/pps-gpio.c b/drivers/pps/clients/pps-gpio.c
index 6550555..5b69667 100644
--- a/drivers/pps/clients/pps-gpio.c
+++ b/drivers/pps/clients/pps-gpio.c
@@ -33,6 +33,8 @@
 #include <linux/pps-gpio.h>
 #include <linux/gpio.h>
 #include <linux/list.h>
+#include <linux/moduleparam.h>
+
 
 /* Info for each registered platform device */
 struct pps_gpio_device_data {
@@ -203,16 +205,36 @@ static struct platform_driver pps_gpio_driver = {
        },
 };
 
+static struct pps_gpio_platform_data pps_gpio_info = {
+       .assert_falling_edge = false,
+       .capture_clear = false,
+        .gpio_pin = 1,
+       .gpio_label = "PPS",
+};
+
+static struct platform_device pps_gpio_device = {
+       .name = "pps-gpio",
+       .id = -1,
+       .dev = { .platform_data = &pps_gpio_info },
+};
+
 static int __init pps_gpio_init(void)
 {
        int ret = platform_driver_register(&pps_gpio_driver);
-       if (ret < 0)
+        if (ret < 0) {
                pr_err("failed to register platform driver\n");
+                return ret;
+        }
+
+       ret = platform_device_register(&pps_gpio_device);
+        if (ret < 0)
+               pr_err("failed to register pps_gpio_device\n");
        return ret;
 }
 
 static void __exit pps_gpio_exit(void)
 {
+       platform_device_unregister(&pps_gpio_device);
        platform_driver_unregister(&pps_gpio_driver);
        pr_debug("unregistered platform driver\n");
 }
@@ -220,8 +242,17 @@ static void __exit pps_gpio_exit(void)
 module_init(pps_gpio_init);
 module_exit(pps_gpio_exit);
 
+module_param_named(gpio_pin, pps_gpio_info.gpio_pin, uint, S_IRUGO);
+MODULE_PARM_DESC(gpio_pin, "GPIO Pin number (default 1)");
+module_param_named(falling_edge, pps_gpio_info.assert_falling_edge,
+                   bool, S_IRUGO);
+MODULE_PARM_DESC(falling_edge, "PPS occurs as falling edge (default 0)");
+module_param_named(capture_clear, pps_gpio_info.capture_clear, bool, S_IRUGO);
+MODULE_PARM_DESC(capture_clear, "PPS capture clear (default 0)");
+
 MODULE_AUTHOR("Ricardo Martins <rasm@fe.up.pt>");
 MODULE_AUTHOR("James Nuss <jamesnuss@nanometrics.ca>");
+MODULE_AUTHOR("Chen Wei <weichen302@gmail.com>");
 MODULE_DESCRIPTION("Use GPIO pin as PPS source");
 MODULE_LICENSE("GPL");
-MODULE_VERSION("1.0.0");
+MODULE_VERSION("1.0.1");

Then proceed with the compilation of the kernel.

At the end of the compilation the debian packages with the new kernel will be under “./output/debs”

total 59432
drwxr-xr-x 1 root root      616 Jan 29 01:00 ./
drwxr-xr-x 1 root root       66 Jan 28 11:27 ../
-rw-r--r-- 1 root root  1618984 Jan 28 12:39 armbian-firmware_5.24_armhf.deb
-rw-r--r-- 1 root root 40528598 Jan 28 12:42 armbian-firmware-full_5.24_armhf.deb
-rw-r--r-- 1 root root    15654 Jan 28 12:42 armbian-tools-xenial_5.24_armhf.deb
drwxr-xr-x 1 root root        0 Jan 28 10:15 extra/
-rw-r--r-- 1 root root   112272 Jan 29 00:58 linux-firmware-image-sun8i_5.24_armhf.deb
-rw-r--r-- 1 root root  5770784 Jan 29 00:59 linux-headers-sun8i_5.24_armhf.deb
-rw-r--r-- 1 root root 12454972 Jan 29 01:00 linux-image-sun8i_5.24_armhf.deb
-rw-r--r-- 1 root root   178036 Jan 28 13:55 linux-u-boot-dev-orangepiplus2e_5.24_armhf.deb
-rw-r--r-- 1 root root   164778 Jan 29 00:51 linux-u-boot-orangepiplus2e_5.24_armhf.deb
drwxr-xr-x 1 root root      102 Jan 28 11:27 xenial/

The new kernel can be installed by simply transferring the deb package to the Orange PI and use dpkg.

If you have compiled the default kernel, the image package is the only one needed.

dpkg -i linux-image-sun8i_5.24_armhf.deb

Once the new kernel is installed, before rebooting, add a line to /etc/modules

pps-gpio

and specify the pin number by create a new file, /etc/modprobe.d/pps-gpio.conf, with following content:

# options pps-gpio gpio_pin=Your_PPS_GPIO_PIN (6 in my case)
options pps-gpio gpio_pin=6

If everything is correct, after the reboot the pps signal should be picked up by the driver. You can check it by doing the following:

lsmod
#-- OUTPUT -----------------------------------------------------------------------------
Module                  Size  Used by
mali_drm                2732  1
drm                   178255  2 mali_drm
bmp085                  3487  0
pcf8591                 3363  0
mali                  123146  0
ump                    29379  3 mali
pps_gpio                2665  2
pps_core                6923  3 pps_gpio
8189es               1076034  0
#-- END OUTPUT -------------------------------------------------------------------------

dmesg | grep pps
#-- OUTPUT -----------------------------------------------------------------------------
[    3.527868] pps_core: LinuxPPS API ver. 1 registered
[    3.527883] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti <giometti@linux.it>
[    3.529927] pps pps0: new PPS source pps-gpio.-1
[    3.530089] pps pps0: Registered IRQ 6 as PPS source
#-- END OUTPUT --------------------------------------------------------------------------
# A new device file /dev/pps0 will be created.
# You can also do a ppstest:
ppstest /dev/pps0   
#-- OUTPUT -----------------------------------------------------------------------------
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1485676729.999992822, sequence: 23648 - clear  0.000000000, sequence: 0
source 0 - assert 1485676731.000008165, sequence: 23649 - clear  0.000000000, sequence: 0
#-- END OUTPUT -------------------------------------------------------------------------
# and finally check chrony

chronyc sources
#-- OUTPUT -----------------------------------------------------------------------------
210 Number of sources = 6
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
#* GPPS                          0   0   377     0    -14us[  -15us] +/- 1000ns
#? GPSD                          0   1   377     1    +69ms[  +69ms] +/-  100ms
=- 172.16.5.31                   2   6   377   239    +38us[  +40us] +/-  742us
=- 172.16.5.32                   1   6   377    18    -74us[  -76us] +/-  176us
^- ntp1.inrim.it                 1  10   377    66   -818us[ -817us] +/- 3333us
^- ntp.freestone.net             1  10   377   549   -715us[ -698us] +/-   12ms
#-- END OUTPUT -------------------------------------------------------------------------

chronyc tracking
#-- OUTPUT -----------------------------------------------------------------------------
Reference ID    : 71.80.80.83 (GPPS)
Stratum         : 1
Ref time (UTC)  : Sun Jan 29 08:03:21 2017
System time     : 0.000000000 seconds fast of NTP time
Last offset     : -0.000000979 seconds
RMS offset      : 0.000000888 seconds
Frequency       : 40.407 ppm fast
Residual freq   : -0.006 ppm
Skew            : 0.231 ppm
Root delay      : 0.000000 seconds
Root dispersion : 0.000002 seconds
Update interval : 1.0 seconds
Leap status     : Normal
#-- END OUTPUT -------------------------------------------------------------------------

Congratulations, you have now a very precise (in the microseconds range) Stratum 1 Time server, with an Orange PI, running CHRONY with GPS and PPS.

19 thoughts on “ORANGE PI PLUS H3 with GPS/PPS”

  1. Thanks for your detailed instructions! In trying to decide whether to purchase one of the more-popular/supported ‘Raspberry Pi’ models for such a project, or a less-known/less-supported ‘Orange Pi’ or NanoPi’ device, it looks like it is more work to do this on a non-Raspberry Pi device, because kernel compiles are required. Am I correct? Is this because ‘PPS’ support is not present in any of the Armbain/DietPi/etc/ kernels, but it is in Raspbian? Also, do you have any other general advise about what devices to consider for projects like this i.e. I have read that wifi on the Pi Zero is poor. The Orange Pi PC Plus looks good since it has on-board eMMC.

    1. There is plenty of articles on how to implement PPS on Raspberry PI and also most of the info provided for Orange PI can apply to Armbian.
      Regarding the platform, the Orange PI is far less manageable than Raspberry PI, mostly for the lack of documentation.
      It is much easier to recompile the Raspbian kernel than doing the same on the different “Linuxes” of the orange PI.
      The only advantages of the Orange PI platform are: eMMC, SATA I/F, Non-USB Ethernet.
      If you don’t need any of these three, better choose the Raspberry platform.
      For what relates with PPS/GPS there are no performance drawbacks in using the Raspberry PI 3, with respect to the Orange PI.
      The most suitable usages for the Orange PI are for building a DIY NAS or for HOME PC.

      1. Thanks for the reply! More specifically, the R Pi articles that I have read for setup of GPS/PPS do not mention the need to recompile the kernel (?). Can you elaborate as to why this is necessary for the O Pi?

        1. It depends upon the level of precision you’ll need to achieve.
          For the highest precision you will need to disable the “Tickless System” kernel feature, you may need to increase the “Timer frequency” (hidden – requires a patch) above 100 HZ and you may need to modify the PPS-GPIO driver to better manage the gpio pin for the pps and the raising/falling edge of the pps assertion.
          With all these changes you will be able to achieve a precision below two microseconds and an offset in the range of twenty nanoseconds.
          These results may also require to change the PPS period below 1 second (1 HZ frequency) to 100 milliseconds (10 HZ frequency) by using a uBlox GPS and using CHRONY instead of NTP.
          Without recompiling the kernel the precision will be below ten microseconds and the offset will be in the range of one microsecond.
          See this output from a Raspberry PI 3 with CHRONY, a uBLOX-6M GPS and the RASPBIAN Kernel recompiled:

          Reference ID : 47505053 (GPPS)
          Stratum : 1
          Ref time (UTC) : Mon Jun 26 19:33:40 2017
          System time : 0.000000080 seconds slow of NTP time
          Last offset : -0.000000007 seconds
          RMS offset : 0.000000067 seconds
          Frequency : 5.587 ppm fast
          Residual freq : -0.000 ppm
          Skew : 0.020 ppm
          Root delay : 0.000000001 seconds
          Root dispersion : 0.000001151 seconds
          Update interval : 1.0 seconds
          Leap status : Normal
          210 Number of sources = 7
          MS Name/IP address Stratum Poll Reach LastRx Last sample
          ===============================================================================
          #* GPPS 0 0 377 2 -275ns[ -282ns] +/- 304ns
          #? GPSD 0 1 0 - +0ns[ +0ns] +/- 0ns
          ^? 172.16.5.31 2 6 377 49 -39us[ -39us] +/- 518us
          =- 172.16.5.32 2 2 377 6 +27us[ +27us] +/- 478us
          =? 172.16.5.37 0 2 0 - +0ns[ +0ns] +/- 0ns
          =- 172.16.5.138 1 2 377 5 +77us[ +77us] +/- 321us
          ^- ntp1.inrim.it 1 10 377 421 -178us[ -197us] +/- 3539us
          210 Number of sources = 7
          Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
          ==============================================================================
          GPPS 64 11 63 -0.000 0.019 -0ns 669ns
          GPSD 0 0 0 +0.000 2000.000 +0ns 4000ms
          172.16.5.31 25 15 28m +0.003 0.026 +75us 17us
          172.16.5.32 36 16 145 +0.068 0.116 +21us 8190ns
          172.16.5.37 0 0 0 +0.000 2000.000 +0ns 4000ms
          172.16.5.138 29 17 114 +0.028 0.374 +35us 18us
          ntp1.inrim.it 6 3 86m -0.019 0.697 -148us 283us

          I hope everything is clear.

          1. Yes, this does make it clearer to me! It also makes me wonder if the kernel maintainers could be convinced to make these optimizations ‘standard’ in their kernel creations (why not?).

  2. Hello Again! I now have my Orange Pi PC Plus and GPS module with PPS pin ready in front of me, but the very first obstacle I run into are the instructions to edit (a copy of?) the ‘compile.sh’ script. I was expected to override parameters already present in that script, but I guess I have misunderstood e.g. there is no reference to ‘KERNEL_ONLY’ in that script, etc.. Can you put me back onto track? Thanks!

      1. Hello! Just to be clear, are your instructions for compiling the kernel on the Orange Pi itself? I am not sure what your instructions meant by “cp lib/compile.sh” (the command is incomplete), but when I enter the ‘$HOME/lib’ directory on the Orange Pi and type “./compile.sh”, I eventually get errors stating:

        [ error ] ERROR in function prepare_host [ general.sh:440 ]
        [ error ] Running this tool on board itself is not supported

        Do I really need to set up some sort of cross-compiler environment to do this?

          1. I have been using the following as a rough guide as to how to do this:

            http://www.microdev.it/wp/en/2016/09/02/building-armbian-image-for-orange-pi-pc/

            Very confusing, but I think I am on the right track i.e. the compile process seems to pick up the ‘pps-over-gpio.patch’ that I put into ‘/data/orangepi/build/userpatches/kernel/sun8i-default’ (at least while using the compile option that makes ‘deb’ files). However, I see no opportunity to alter ‘General Setup’, ‘Kernel Features’, ‘Device Drivers’, etc. as you described in your instructions — so I am not sure if this is still required or how to do it (?).

            Thanks

          2. In “Device Drivers”, PPS Support and PPS Client using GPIO shall be enabled, otherwise PPS won’t work.
            In “Kernel Features”, Tickless System shall be disabled to obtain an higher precision, although it’s not mandatory.
            The “Tickless System” is usually on to reduce power consumption and allow low power modes on mobile devices, but with PPS is better not using it.

          3. During the compile process, I am not prompted to make any changes that would activate those other features. Is there a particular file that I need to manually edit to do so? Perhaps the behavior has changed since you originally wrote the webpage?

          4. Yes, config-default.conf, read my previous reply and links.

          5. Yes, the ‘config-default.conf’ file (as you mentioned) is the one to adjust the initial parameters that you mentioned. After the compile process is started, your instructions state “once the kernel configuration is launched, change the following:”. It is at that point that ‘Tickless System’, ‘PPS’, and ‘PTP’ options would be changed — but I am *not* prompted to do so, nor do I know where/when to do so. From my old experiences creating Linux kernels many years ago, I might expect a CLI-based prompt system to kick in for this — but it does not for me here (?). Thanks

          6. Set KERNEL_CONFIGURE="yes" in config-default.conf and other options you may need, then relaunch ./compile.sh .
            You should be prompted with a MENUCONFIG process dialog where you can change the kernel options.

          7. Yes, I would have thought that KERNEL_CONFIGURE=”yes” would activate that behavior, but it does not — at least for me. Oh well, thanks for your help nevertheless 🙂

  3. A followup question: without even getting past the first step of re-compiling the kernel, I don’t see any device on ‘/dev/ttyS3’ when I boot with my GPS module (with the connections you suggested). Is this normal?

      1. Thanks! This works well to get GPSD working on the Orange Pi 🙂

Comments are closed.