Draw time and GPS information on a framebuffer display.
This has been built and tested on an Orange Pi Zero with an ST7789V-based SPI TFT screen, as part of a GPS-disciplined NTP server project. It should, however, be fairly portable.
- get time information from system
- get GPS information from gpsd shared memory
- draw time, date, timezone, satellite count and GPS status on the screen
- configure position, colour and format of display elements from an INI file
- use bitmap header fonts, allowing different heights and widths (including variable width fonts)
- control screen brightness via backlight PWM
- flash the status symbol in time with the PPS signal
gpsd needs to be installed and configured:
sudo apt install gpsd
# optional tools
sudo apt install gpsd-tools pps-tools
The configuration of gpsd will vary between setups, but using Orange Pi Zero GPS NTP as an example (with the GPS connected as a 115.2k serial device at /dev/ttyS2 and the PPS at /dev/pps0):
# Devices gpsd should connect to at boot time.
# They need to be read/writeable, either by user gpsd or the group dialout.
DEVICES="/dev/ttyS2 /dev/pps0"
# Other options you want to pass to gpsd
GPSD_OPTIONS="-n -s 115200"
# Automatically hot add/remove USB GPS devices via gpsdctl
USBAUTO="true"
/bin/stty -F /dev/ttyS2 115200
/bin/setserial /dev/ttyS2 low_latency
Once configured:
sudo systemctl daemon-reload
sudo systemctl enable gpsd
sudo systemctl start gpsd
Install build dependencies (this may not be a complete dependency list, more
requirements may pop up during make
):
sudo apt install gcc libgps-dev make
Get, build and install fbgpsclock:
git clone --depth=1 https://github.com/moonbuggy/fbgpsclock.git
cd fbgpsclock/
make
sudo make install
sudo systemctl enable fbgpsclock
sudo systemctl start fbgpsclock
make install
assumes systemd, but the installation step can be done
manually if necessary or desired. The default installation commands
look this this:
/usr/bin/install -c fbgpsclock '/usr/local/bin'
/usr/bin/install -c -m 644 fbgpsclock.ini '/usr/local/etc'
/usr/bin/install -c -m 644 fbgpsclock.service '/lib/systemd/system'
The position, colour and text of each display element can be controlled via
fbgpsclock.ini, which will be in /usr/local/etc/ after
sudo make install
. There's documentation in fbgpsclock.ini and it
should hopefully be fairly straightforward.
The default configuration is for a 240x320 pixel screen. Various positioning parameters, and maybe some font sizes, will need to be changed for a display with a different resolution.
To change the font used by any individual element you'll need to edit fbgpsclock.h and rebuild the executable.
An fbgpsclock.ini file in the same directory as the executable will take precedence over any in an */etc/ path, which is useful for testing any changes to the code..
The font colours in the default configuration are not in a standard RGB format. The ST7789V controller accepts an 8-bit RBG value, with the bits assigned RRR BB GGG.
There are some random colour codes in fbgpsclock.ini that should serve as examples.
fbgpsclock itself is relatively agnostic to the values of these settings,
feeding them unchanged to the display via memset()
. It should work with
displays that want colours in other formats, up to 16 bits long (or longer,
with some modifications to the relevant data types in the source).
Both background and foreground colours can be set. Commenting out the
bg_colour
setting for any particular screen element will cause it to
default to the global setting in [display]
.
The circle in the bottom right corner (in the default config) indicates the state of the GPS signal as such:
-
$\large \color{green} \text{green}$ : 3D GPS fix -
$\large \color{orange} \text{orange}$ : 2D GPS fix - black (absent): no fix
-
$\large \color{red} \text{red}$ : no data from GPS device - flashing: PPS signal acquired
The GPS device can take a while to get a fix from a cold start. In normal circumstances the indicator will progress from black/absent -> orange -> green over the course of some minutes after a boot, with the satellite count increasing accordingly. This doesn't apply to a reboot, if the GPS device remains powered and retains the existing fix.
Red indicates no data has appeared since a certain period of time has elapsed (configured in fbgpsclock.ini, default is 5 seconds). This is an indicator for communications between fbgpsclock and the GPS device (via gpsd), not between the GPS device and satellites.
The red status is useful because the display may retain the last good data it had, regardless of how old it is, if gpsd stops seeing fresh data from the GPS device. This lets us know that any on-screen GPS data isn't accurate (and we're probably not getting a PPS signal, so the time may not be reliable either).
The fonts are in the form of bitmap headers, included in the executable at build time, and are thus not changeable after building. The various fonts in this repository are already present in the code, with the unused ones commented out. Changing to one of these fonts should be fairly easy.
Font header files labelled with _time
only contain the characters
0123456789ampAMP:.
, and are thus only useful for displaying the time.
Variable width fonts will be labelled with _variable
, and fonts without this
label will be fixed width. This generally depends on whether the font itself
is monospaced, however in some cases a variable-width font will be made into a
fixed-width header (using arguments to ttf2bmh). In particular, this has been
done for the _time
headers.
The variable width fonts will usually have no blank space either side of the
characters, so padding
should be set in the configuration for these fonts.
Padding is also useful for fixed-width fonts with too much space between
characters, as negative values can be set to reduce the spacing. Padding is
applied left and right, so (for example) 1px padding will create 2px of
space between adjacent characters.
Some font headers contain full character sets but have the :.
characters
narrowed beyond that of the other characters, making them more suitable for
displaying the time. Tthese are labelled with _narrow_punctuation
. (The
_time
fonts will probably have the punctuation narrowed as well, but it
won't be explicitly labelled.)
The font headers are generated with hb020/TTF2BMH and then modified so each font header has distinct names for the variables it declares.
Using the somewhat arbitrary string <font_name>_<font_style>_<font_height>
for identification, the modifications look something like this:
font_height = "<font_height>"
id_string ="<font_name>_<font_style>_${font_height}"
sed -e "s|bitmap_|${id_string}_|g" \
-e "s|char_|${id_string}_char_|g" \
~/TTF2BMH/src/<font>.h > ${id_string}.h
echo "${id_string}_height_px = ${font_height}" >> ${id_string}.h
It's important that the four index arrays (<string>_char_*[]
) are properly
populated. The index of these arrays must align with the ASCII code for the
character.
The simplest way to handle this is to generate a 'blank' by first running TTF2BMH with width and height both set to 1 pixel, then generating the characters we want and manually pasting them in. See Lato_regular_60_time.h for an example.
It's possible to easily trim blank space from the bottom of fonts with the
${id_string}_height_px
setting, as rows beyond this value won't be drawn.
Space from the top of the fonts will need to be removed by ttf2bmh
, using
the --offset
argument and it's more sensible to use the -fh
and -O
arguments to take care of the bottom spacing at this stage too.
-
Maybe move the PPS code into the display thread and only update the screen when triggerd by the PPS signal, rather than having them run as independant threads.
But, aesthetically, it's kind of nice to run the screen update every 100ms or so because the satellite count will, 9 times of out 10, change in between the seconds changing, rather than everything locked together by the PPS signal.
It could make sense to move the satellite count (and maybe the status) out of the display thread, if we move PPS timing into it, keep the time-specific elements locked to the PPS and refresh everything else at whatever rate we fancy.
-
valgrid says there's some lost bytes, I don't know C well enough to see where they are. The leak seems to be the same size regardless of how long the run is for, so it's presumably not in one of the loops and thus not going to be continuously throwing fresh RAM away.
- moonbuggy/Orange-Pi-Zero-GPS-NTP - hardware for this software
- libgps - C service library for communicating with the GPS daemon
- pps-tools
- RFC 2783 - Pulse-Per-Second API for UNIX-like Operating Systems, Version 1.0
- PPS - Pulse Per Second - Linux kernel docs