Skip to content
N4LLZ

APRS Packet Anatomy: AX.25, TNC2, and Data Types

What is actually on the air. The AX.25 UI frame field by field, address encoding and the has-been-repeated bit, the TNC2 monitor format, and the full data type identifier table.

APRSAdvanced14 minUpdated

Most APRS documentation describes which menu to press. This one describes what is on the air.

APRS is not its own link layer. It is a set of conventions layered on AX.25 Unnumbered Information frames — a connectionless broadcast of an amateur packet frame that anyone in earshot may decode. Everything distinctive about APRS lives in two places: how the address fields are abused to carry routing semantics, and what the first byte of the payload means.

Reference

The authoritative document is the APRS Protocol Reference version 1.0.1 (APRS101), published at aprs.org/doc/APRS101.PDF, together with the later addenda (1.1 and 1.2) that added items, and refinements to compressed and Mic-E formats. Where this guide gives byte offsets, they follow that specification.

The physical layer, briefly

Classic terrestrial APRS is 1200 baud AFSK, Bell 202 tones — 1200 Hz for a mark, 2200 Hz for a space — transmitted as audio through an ordinary FM voice radio. The bit stream is NRZI encoded and HDLC framed, with bit stuffing: after five consecutive 1 bits a 0 is inserted so that the flag byte 0x7E can never appear inside the data.

That flag byte delimits every frame. A frame is:

7E  | Address field | Control | PID |   Information   |  FCS  | 7E
    | 14–70 bytes   |  0x03   |0xF0 |  1–256 bytes    | 2 byte|
  • Control = 0x03 marks this as a UI (Unnumbered Information) frame. Unnumbered means no sequence numbers, no acknowledgement, no connection. A position beacon is simply shouted.
  • PID = 0xF0 means “no layer 3 protocol” — the information field is not IP, not NET/ROM, just bytes for the application to interpret. That application is APRS.
  • FCS is a 16-bit CRC. A frame that fails it is discarded silently, which is why a marginal signal produces no packet rather than a corrupted one.

The 9600 baud variant (G3RUH) uses direct FSK into the radio’s discriminator rather than audio tones, but the framing above is identical.

The address field

This is where APRS does something unusual. AX.25 addresses are call signs, and APRS uses the destination address — which in a connected protocol would identify the recipient — as a data field.

Each address is exactly 7 bytes: six bytes of call sign, space-padded, then one SSID byte. The call sign characters are shifted left by one bit, so A (0x41) is transmitted as 0x82. The low bit is thereby freed as the address extension bit, which is 0 on every address except the last, where it is 1. That is how a receiver knows where the address field ends without a length prefix.

The SSID byte unpacks as:

 bit:  7   6   5   4   3   2   1   0
       C   R   R   S   S   S   S   E
       │   └─┬─┘   └────┬────┘    └── extension bit (1 = last address)
       │     │          └── SSID, 0–15
       │     └── reserved, normally 11
       └── "C" bit: command/response in AX.25;
           in a digipeater address this is the HAS-BEEN-REPEATED bit (H)

The address field contains, in order:

  1. Destination — for APRS this is not a recipient. It carries either a destination identifier (APRS, or a tocall like APDR16 identifying the sending software and version) or, for Mic-E, the sender’s encoded latitude.
  2. Source — the transmitting station’s call sign and SSID.
  3. Zero to eight digipeater addresses — the path.

The H bit is the whole routing mechanism

Each digipeater address carries that top bit, conventionally shown as * in monitor output. When a digipeater repeats a frame, it sets the H bit on the address it consumed. Every other digipeater then sees that entry as already used and will not act on it again.

This is the entire loop-prevention scheme. There is no hop count in a header, no TTL field — just a row of call signs, each of which can be marked spent exactly once.

TNC2 monitor format

Nobody reads raw HDLC. Every TNC, software modem and APRS-IS server presents frames in the TNC2 format, which is what you will actually see:

N0CALL-9>APDR16,WIDE1-1,WIDE2-1:=3554.12N/08646.50W>Mobile
└──┬───┘ └──┬──┘└───────┬──────┘ └───────────┬─────────────┘
source   dest      digi path            information field

The rules are mechanical:

  • Source, then >, then destination.
  • Then ,-separated digipeaters, if any.
  • Then :the first colon separates header from information field. Every parser splits here, and this is why the information field may contain colons freely but the header may not.
  • A * after a digipeater means its H bit is set: WIDE1* has been used.

Parsers treat everything after that first colon as opaque payload, and everything before it as routing.

The data type identifier

The first byte of the information field determines everything about how the rest is interpreted. This single-character dispatch is the core of APRS.

Byte Meaning
! Position, no timestamp, no APRS messaging capability
= Position, no timestamp, station is messaging-capable
/ Position with timestamp, no messaging
@ Position with timestamp, messaging-capable
; Object — a position for something that is not the sender
) Item — like an object but without a timestamp
: Message, bulletin, or announcement
> Status report
_ Positionless weather report
` Mic-E data, current GPS fix
' Mic-E data, old GPS fix
T Telemetry report
$ Raw NMEA sentence from a GPS
< Station capabilities
? Query
} Third-party traffic — a complete frame wrapped inside another
{ User-defined experimental format
# Raw weather data (Peet Bros)
% Agrelo DF report
* Complete weather report (Peet Bros)

Two of these deserve immediate comment.

! versus = is not cosmetic. It advertises whether the station can receive APRS messages. A tracker that cannot accept messages should send !; sending = invites messages that will never be acknowledged.

} third-party traffic is how an IGate injects a frame it received from the internet back onto RF. The original frame, header and all, is embedded verbatim in the information field of a new frame. The gateway’s own call sign appears on the outer frame, so the frame’s RF provenance is honest while the original sender is preserved inside.

Worked example

Take this frame:

N0CALL-9>APRS,WIDE1-1,WIDE2-1:!3554.12N/08646.50W>088/043/A=000712Test

Read it in order:

  • N0CALL-9 — source. SSID 9, conventionally a vehicle.
  • APRS — generic destination identifier. Carries no position here (that would be Mic-E).
  • WIDE1-1,WIDE2-1 — requested path; neither has a *, so this frame has not yet been digipeated.
  • ! — position report, no timestamp, not messaging-capable.
  • 3554.12N — latitude, fixed width, DDMM.hhN: 35° 54.12′ North. Note it is degrees and decimal minutes, not decimal degrees.
  • / — symbol table identifier. / is the primary table, \ the alternate, and a digit or letter indicates an overlay.
  • 08646.50W — longitude, DDDMM.hhW: 86° 46.50′ West. Longitude always uses three degree digits.
  • > — symbol code. In the primary table > is a car.
  • 088/043 — course 88°, speed 43 knots.
  • /A=000712 — altitude, 712 feet, always six digits.
  • Test — free-text comment.

The fixed-width fields are not a stylistic choice. Because latitude is always exactly 8 characters and longitude exactly 9, a parser can index directly rather than tokenise, which mattered a great deal on the hardware this was designed for.

Position ambiguity

The minutes digits may be replaced with spaces to deliberately coarsen a position:

3554.1 N   →  one digit of ambiguity
3554.  N   →  two digits
355 .  N   →  three digits
35  .  N   →  four digits

A receiver fills the blanked digits to centre the position within the resulting box. This is a genuine privacy and honesty mechanism: a station that only knows its position to the nearest town should say so rather than transmit false precision.

Symbols

The symbol is two characters: a table identifier and a symbol code. Together they select an icon from a fixed, documented set — car, house, boat, weather station, digipeater, ambulance, and so on.

  • / selects the primary table.
  • \ selects the alternate table.
  • Any other character is an overlay: it selects the alternate table and prints that character on top of the icon. This is how you see numbered digipeaters — 1 overlaid on the digi symbol — without needing a new symbol for each.

A handful worth recognising on sight:

Table / code Symbol
/> Car
/- House (QTH)
/_ Weather station
/# Digipeater
/& Gateway (IGate)
/[ Human / walker
/k Truck
/O Balloon
/' Aircraft, small

Why any of this matters

Two practical consequences fall directly out of the structure above.

The information field is small. An AX.25 information field is bounded, and at 1200 baud every byte is airtime on a shared channel. A frame with a long comment, sent every minute, is measurably antisocial. The compressed and Mic-E formats exist precisely because the uncompressed position format is verbose.

There is no acknowledgement for position. UI frames are fire-and-forget. If your beacon collides with another station’s, both are lost and neither transmitter knows. This is why beacon intervals matter, why SmartBeaconing exists, and why a long digipeater path multiplies harm — it is not just your airtime, it is the collision probability for everyone in range of every digipeater you triggered.


Next: Decoding APRS Position Reports works through the compressed and Mic-E formats byte by byte, and APRS-IS covers the internet side.