PiTG is a Raspberry Pi 1 timecode and protocol bridge. It provides three runtime outputs:
pitg— SMPTE LTC over analog audio (3.5mm jack) via ALSA + libltcpitg-gpio— Limitimer protocol stream over hardware UART or GPIO bit-bangpitg-cue-buttons— PerfectCue cue button bridge via USB RS-485 adapter with hardware button inputs
The Limitimer and PerfectCue paths operate on separate serial devices and run simultaneously.
- Overview
- Requirements
- Build
- Binaries
- Deployed Configuration
- Raspberry Pi 1 Wiring
- Wiring Verification
- Protocol Reference
- Service Installation
- Service Configuration
- Harness Helper
- Notes For Raspberry Pi 1
PiTG runs on a Raspberry Pi 1 Model B (Rev 2, BCM2835, 26-pin header). It bridges between a timecode source and two DSAN protocol bus devices:
- Limitimer receives a continuous status stream over RS-485 at 19200 baud, driven from the Pi's hardware UART (
/dev/ttyAMA0→ HW-0519 auto-direction board). - PerfectCue receives one-shot cue commands over RS-485 at 19200 baud, driven by
pitg-cue-buttonsvia a Waveshare USB-TO-RS485(B) adapter (CH343G + SP485EEN, with hardware TNOW-based direction control). - Buttons (NEXT/PREV) are wired to GPIO inputs and drive PerfectCue events when pressed.
On Raspberry Pi OS:
gccmakelibltc-devlibasound2-devlibgpiod-dev
Install build dependencies:
make depsmakeBuild outputs:
pitgpitg-gpiopitg-harnessctlpitg-cue-buttons
Generates SMPTE LTC timecode signal over the Pi's 3.5mm analog audio jack via ALSA.
Run with defaults:
./pitgCommon options:
-r <fps>:24 | 25 | 29.97 | 29.97df | 30-a <hz>: sample rate (8000-192000)-d <device>: ALSA device-s <HH:MM:SS:FF>: fixed start timecode
Example:
./pitg -r 25 -a 48000 -d plughw:CARD=Headphones,DEV=0Outputs a Limitimer protocol stream and/or single PerfectCue cue commands.
Supported protocol modes:
limitimerperfectcueboth
Run defaults (Limitimer stream):
./pitg-gpioCLI options:
-p <protocol>:limitimer | perfectcue | both(default:both)-u <tty>: UART device for Limitimer output (example:/dev/ttyAMA0)-g <gpio>: Limitimer GPIO pin (default:17)-q <gpio>: PerfectCue GPIO pin (default:18)-x <cmd>: PerfectCue command:next | prev | blank-off | blank-on(default:next)-b <baud>: UART/serial baud rate (default:19200)-i <ms>: transmission interval in ms (default:1000)-c <index>: gpiochip index (default:0)-t <MM:SS|seconds>: countdown total time (default:10:00)-T: test mode — auto-fires every 10 seconds alternating next/prev-1: one-shot mode (send one event/frame and exit)
Examples:
./pitg-gpio -p limitimer -u /dev/ttyAMA0 -b 19200 -i 250
./pitg-gpio -p perfectcue -q 18 -x next -b 19200 -1
./pitg-gpio -p both -u /dev/ttyAMA0 -q 18 -x blank-on -b 19200 -i 250
./pitg-gpio -p perfectcue -q 18 -x blank-on -b 19200 -1Monitors hardware button inputs (GPIO) and fires PerfectCue commands over a USB RS-485 adapter when pressed.
CLI options:
-N <gpio>: NEXT button GPIO (default:23)-P <gpio>: PREV button GPIO (default:24)-S <tty>: USB RS-485 serial device-B <baud>: baud rate (default:19200)-D <ms>: debounce delay in ms (default:120)-c <index>: gpiochip index (default:0)-H: active-high button logic (default: active-low, contacts to GND)-T: test mode — auto-fires every 10 seconds alternating next/prev
The deployed system on PiTG uses stable udev symlinks for the USB serial adapters, defined in
99-waveshare-limitimer.rules and 99-waveshare-rs485.rules (installed to /etc/udev/rules.d/
by make install-service):
/dev/ttyLimitimer→ Limitimer output forpitg-gpio/dev/ttyRS485→ PerfectCue RS-485 output forpitg-cue-buttons
Both service units bind to their udev device unit (BindsTo=/After= on dev-ttyLimitimer.device
and dev-ttyRS485.device) so they start as soon as the adapter enumerates rather than racing boot
order, and stop cleanly if the adapter is unplugged. StartLimitIntervalSec=0 disables systemd's
restart-attempt limit so the service retries indefinitely instead of landing in failed if the
adapter is slow to appear or briefly drops out.
The receiver (piclocktg, a Pi 5) receives the Limitimer stream on /dev/ttyAMA1 (GPIO0/GPIO1). Clock configuration is at:
/boot/firmware/piclock/clock.ini
Recommended service options:
PITG_GPIO_OPTS="-p limitimer -u /dev/ttyLimitimer -b 19200 -i 250 -c 0 -t 10:00"
PITG_CUE_BUTTONS_OPTS="-N 23 -P 24 -S /dev/ttyRS485 -B 19200 -D 120 -c 0 -T"Notes:
pitg-gpio.serviceruns in Limitimer-only mode continuously.pitg-cue-buttons.servicehandles PerfectCue events via hardware buttons;-Tenables automatic test firing on boot.- Remove
-Tfrompitg-cue-buttonsfor live show operation.
Recommended configuration for Pi 1 Model B Rev 2 (26-pin header):
- Limitimer stream: hardware UART (
/dev/ttyAMA0) → RS-485 transceiver #1 - PerfectCue events: USB RS-485 adapter (
pitg-cue-buttons) → RS-485 transceiver #2
Physical pin numbers (26-pin header):
| Signal | BCM GPIO | Physical Pin |
|---|---|---|
| UART TXD0 (Limitimer) | GPIO14 | 8 |
| UART RXD0 (optional) | GPIO15 | 10 |
| PerfectCue TX (GPIO) | GPIO18 | 12 |
| NEXT button input | GPIO23 | 16 |
| PREV button input | GPIO24 | 18 |
| Ground | — | 6 |
Button wiring (default active-low):
- One side of each momentary button → GPIO input pin (
GPIO23orGPIO24) - Other side of each button → GND
- No external pull-up resistors needed —
pitg-cue-buttonsenables internal pull-ups via libgpiod - If buttons are wired active-high, use
-H
RS-485 line side:
- Transceiver #1 A/B → Limitimer A/B
- Transceiver #2 A/B → PerfectCue A/B
These boards auto-switch direction (no DE/RE wiring needed). Use 5V for VCC.
Limitimer — RS-485 board → UART /dev/ttyAMA0:
| Board pin | Connect to |
|---|---|
| TXD | Pi pin 8 (GPIO14 / TXD0) |
| RXD | leave unconnected |
| VCC | Pi pin 2 (5V) |
| GND | Pi pin 6 (GND) |
| A+ | RJ45 Limitimer A pins (3 + 6) |
| B- | RJ45 Limitimer B pins (4 + 5) |
| 接大地 | leave unconnected |
PerfectCue — RS-485 board → GPIO18:
| Board pin | Connect to |
|---|---|
| TXD | Pi pin 12 (GPIO18) |
| RXD | leave unconnected |
| VCC | Pi pin 2 (5V) |
| GND | Pi pin 6 (GND) |
| A+ | RJ45 PerfectCue A pins (2 + 7) |
| B- | RJ45 PerfectCue B pins (4 + 5) |
| 接大地 | leave unconnected |
Use when building RJ45 breakout/cable adapters for DSAN bus devices. Each device gets its own RS-485 transceiver board and its own RJ45 cable.
Limitimer RJ45 TTL→RS485 PerfectCue RJ45 TTL→RS485
┌──────────┐ ┌───────┐ ┌──────────┐ ┌───────┐
│ Pin 1 │ │ │ │ Pin 1 │ │ │
│ Pin 2 │ │ │ │ Pin 2 ├───────────────┤ A (+) │
│ Pin 3 ├───────────────┤ A (+) │ │ Pin 3 │ │ │
│ Pin 4 ├───────────────┤ B (-) │ │ Pin 4 ├───────────────┤ B (-) │
│ Pin 5 ├───────────────┤ B (-) │ │ Pin 5 ├───────────────┤ B (-) │
│ Pin 6 ├───────────────┤ A (+) │ │ Pin 6 │ │ │
│ Pin 7 │ │ │ │ Pin 7 ├───────────────┤ A (+) │
│ Pin 8 │ │ │ │ Pin 8 │ │ │
└──────────┘ └───────┘ └──────────┘ └───────┘
ttyAMA0 GPIO18
Pin groups:
- Limitimer socket:
3 + 6toA (+), and4 + 5toB (-). - PerfectCue socket:
2 + 7toA (+), and4 + 5toB (-).
flowchart TB
subgraph PI[Raspberry Pi 1]
P8[Pin 8<br/>GPIO14 / TXD0]
P10[Pin 10<br/>GPIO15 / RXD0 optional]
P12[Pin 12<br/>GPIO18 PerfectCue TX]
P16[Pin 16<br/>GPIO23 NEXT button]
P18[Pin 18<br/>GPIO24 PREV button]
PG[Pin 6<br/>GND]
end
subgraph BTN[Momentary Buttons]
BN[NEXT button]
BP[PREV button]
end
subgraph T1[RS-485 Transceiver #1<br/>Limitimer]
T1DI[DI]
T1RO[RO optional]
T1A[A / D+]
T1B[B / D-]
T1G[GND]
end
subgraph T2[RS-485 Transceiver #2<br/>PerfectCue]
T2DI[DI]
T2A[A / D+]
T2B[B / D-]
T2G[GND]
end
subgraph L[Limitimer Device/Bus]
LA[A / D+]
LB[B / D-]
end
subgraph C[PerfectCue Device/Bus]
CA[A / D+]
CB[B / D-]
end
P8 --> T1DI
P10 -. optional RX .- T1RO
P12 --> T2DI
PG --> T1G
PG --> T2G
T1A --> LA
T1B --> LB
T2A --> CA
T2B --> CB
P16 --- BN
P18 --- BP
BN --- PG
BP --- PG
Early Raspberry Pi board revisions mapped physical pin 13 differently (GPIO21 on rev1 boards, GPIO27 on later revisions). Use physical pin 12 (GPIO18) for PerfectCue output to avoid this ambiguity.
References:
- Raspberry Pi pinout with rev1/rev2 notes: https://pinout.xyz/pinout/pin13_gpio27
- Raspberry Pi GPIO basics and board docs: https://www.raspberrypi.com/documentation/computers/raspberry-pi.html
Check board revision:
cat /proc/cpuinfo | grep RevisionStart with one link at a time (Limitimer first, then PerfectCue), and verify on hardware:
- Confirm UART TX is active on Pi pin 8 while the Limitimer stream is running.
- Confirm the PerfectCue GPIO pin toggles only when a one-shot command is sent.
- If there is no communication, swap A/B on that specific RS-485 link.
- For manual DE/RE boards, force TX-only (
DE=HIGH,RE=HIGH). - Verify
19200 8N1on both sides. - Add/confirm 120 ohm termination only at bus ends.
Quick debug commands:
# Limitimer continuous stream over UART
./pitg-gpio -p limitimer -u /dev/ttyAMA0 -b 19200 -i 250
# PerfectCue one-shot tests over GPIO18
./pitg-gpio -p perfectcue -q 18 -x next -b 19200 -1
./pitg-gpio -p perfectcue -q 18 -x prev -b 19200 -1
./pitg-gpio -p perfectcue -q 18 -x blank-on -b 19200 -1
./pitg-gpio -p perfectcue -q 18 -x blank-off -b 19200 -1- Limitimer
- Transport: RS-485,
19200 8N1 - Packet framing: starts
0x81, payload bytes use 7-bit values, checksum marker0x83, CRC16/Modbus (2 bytes), end0xFF - Status packet type
0x00, 55-byte packet generated bybuild_limitimer_status_packet()inpitg_gpio.c
- Transport: RS-485,
- PerfectCue
- Transport:
19200 8N1 - Single-byte commands:
0x0Fnext/right0x1Fprevious/left0x2Fblank off0x3Fblank on
- Transport:
To modify packet behavior, edit build_limitimer_status_packet() or perfectcue_command_byte() in pitg_gpio.c.
Install binaries and service files:
sudo make install-serviceEnable services:
sudo make enable-serviceThis installs:
/usr/local/bin/pitg/usr/local/bin/pitgctl/usr/local/bin/pitg-harnessctl/usr/local/bin/pitg-gpio/usr/local/bin/pitg-cue-buttons/etc/systemd/system/pitg.service/etc/systemd/system/pitg-gpio.service/etc/systemd/system/pitg-cue-buttons.service/etc/default/pitg(if missing)/etc/default/pitg-gpio(if missing)/etc/default/pitg-cue-buttons(if missing)
| Service | Config file |
|---|---|
LTC audio (pitg) |
/etc/default/pitg |
Limitimer transmitter (pitg-gpio) |
/etc/default/pitg-gpio |
PerfectCue button bridge (pitg-cue-buttons) |
/etc/default/pitg-cue-buttons |
Receiver clock (piclocktg) |
/boot/firmware/piclock/clock.ini |
Verified button/contact closure inputs for pitg-cue-buttons:
- NEXT: BCM
GPIO23, physical pin16 - PREV: BCM
GPIO24, physical pin18 - Both contacts close to
GND - Default logic is active-low
- Use
-Honly if contacts are wired active-high
-T on pitg-cue-buttons enables automatic PerfectCue firing once every 10 seconds, alternating NEXT and PREV. This is the default boot test mode. Remove -T for live show operation.
Restart after config changes:
sudo systemctl restart pitg-gpio.service
sudo systemctl restart pitg-cue-buttons.servicepitg-harnessctl controls both services and can send one-shot PerfectCue events:
sudo pitg-harnessctl start
sudo pitg-harnessctl status
sudo pitg-harnessctl logs
sudo pitg-harnessctl cue next
sudo pitg-harnessctl cue blank-on
sudo pitg-harnessctl restart
sudo pitg-harnessctl stop- Pi 1 UART0 pins: GPIO14 (TXD0, pin 8) and GPIO15 (RXD0, pin 10).
- GPIO numbering follows BCM convention throughout.
- The HW-0519 auto-direction RS-485 board is suitable for the Limitimer continuous stream. For isolated single-byte commands (PerfectCue), use a board with proper hardware direction control (e.g. Waveshare USB-TO-RS485(B) with TNOW).
- If getting inverted data (e.g.
0x0Freceived as0x78), swap the A/B wires. - For protocol verification, use a logic analyzer or oscilloscope on the RS-485 bus.
- Use physical GPIO level shifting/isolation if the destination system requires 5V signaling.
