6 KiB
Packet -- Reliable Serial Transport
Packetized serial transport with HDLC-style framing, CRC-16 error detection, and a Go-Back-N sliding window protocol for reliable, ordered delivery over an unreliable serial link.
Architecture
Application
|
[packet] framing, CRC, retransmit, ordering
|
[rs232] raw byte I/O
The packet layer sits on top of an already-open rs232 COM port. It does not open or close the serial port itself.
Frame Format
Before byte stuffing:
[0x7E] [SEQ] [TYPE] [LEN] [PAYLOAD...] [CRC_LO] [CRC_HI]
| Field | Size | Description |
|---|---|---|
0x7E |
1 byte | Frame delimiter (flag byte) |
SEQ |
1 byte | Sequence number (wrapping uint8) |
TYPE |
1 byte | Frame type (see below) |
LEN |
1 byte | Payload length (0-255) |
| Payload | 0-255 | Application data |
CRC |
2 bytes | CRC-16-CCITT over SEQ+TYPE+LEN+payload |
Frame Types
| Type | Value | Description |
|---|---|---|
DATA |
0x00 | Data frame carrying application payload |
ACK |
0x01 | Cumulative acknowledgment (next expected seq) |
NAK |
0x02 | Negative ack (request retransmit from seq) |
RST |
0x03 | Connection reset |
Byte Stuffing
The flag byte (0x7E) and escape byte (0x7D) are escaped within
frame data:
0x7Ebecomes0x7D 0x5E0x7Dbecomes0x7D 0x5D
Reliability
The protocol uses Go-Back-N with a configurable sliding window (1-8 slots, default 4):
- Sender assigns sequential numbers to each DATA frame and retains a copy in the retransmit buffer until acknowledged.
- Receiver delivers frames in order. Out-of-order frames trigger a NAK for the expected sequence number.
- ACK carries the next expected sequence number (cumulative).
- NAK triggers retransmission of the requested frame and all subsequent unacknowledged frames.
- Timer-based retransmit fires after 500 poll cycles if no ACK or NAK has been received.
API Reference
Types
// Receive callback -- called for each verified, in-order packet
typedef void (*PktRecvCallbackT)(void *ctx, const uint8_t *data, int len);
// Opaque connection handle
typedef struct PktConnS PktConnT;
Constants
| Name | Value | Description |
|---|---|---|
PKT_MAX_PAYLOAD |
255 | Max payload bytes per packet |
PKT_DEFAULT_WINDOW |
4 | Default sliding window size |
PKT_MAX_WINDOW |
8 | Maximum sliding window size |
PKT_SUCCESS |
0 | Success |
PKT_ERR_INVALID_PORT |
-1 | Invalid COM port |
PKT_ERR_NOT_OPEN |
-2 | Connection not open |
PKT_ERR_ALREADY_OPEN |
-3 | Connection already open |
PKT_ERR_WOULD_BLOCK |
-4 | Operation would block |
PKT_ERR_OVERFLOW |
-5 | Buffer overflow |
PKT_ERR_INVALID_PARAM |
-6 | Invalid parameter |
PKT_ERR_TX_FULL |
-7 | Transmit window full |
PKT_ERR_NO_DATA |
-8 | No data available |
Functions
pktOpen
PktConnT *pktOpen(int com, int windowSize,
PktRecvCallbackT callback, void *callbackCtx);
Creates a packetized connection over an already-open COM port.
com-- RS232 port index (RS232_COM1..RS232_COM4)windowSize-- sliding window size (1-8), 0 for default (4)callback-- called frompktPoll()for each received packetcallbackCtx-- user pointer passed to callback
Returns a connection handle, or NULL on failure.
pktClose
void pktClose(PktConnT *conn);
Frees the connection state. Does not close the underlying COM port.
pktSend
int pktSend(PktConnT *conn, const uint8_t *data, int len, bool block);
Sends a packet. len must be 1..PKT_MAX_PAYLOAD.
block = true-- waits for window space, polling for ACKs internallyblock = false-- returnsPKT_ERR_TX_FULLif the window is full
The packet is stored in the retransmit buffer until acknowledged.
pktPoll
int pktPoll(PktConnT *conn);
Reads available serial data, processes received frames, sends ACKs and NAKs, and checks retransmit timers. Returns the number of DATA packets delivered to the callback.
Must be called frequently (e.g. in your main loop).
pktReset
int pktReset(PktConnT *conn);
Resets all sequence numbers and buffers to zero. Sends a RST frame to the remote side so it resets as well.
pktGetPending
int pktGetPending(PktConnT *conn);
Returns the number of unacknowledged packets currently in the transmit window. Useful for throttling sends in non-blocking mode.
Example
#include "packet.h"
#include "../rs232/rs232.h"
void onPacket(void *ctx, const uint8_t *data, int len) {
// process received packet
}
int main(void) {
// Open serial port first
rs232Open(RS232_COM1, 115200, 8, 'N', 1, RS232_HANDSHAKE_NONE);
// Create packet connection with default window size
PktConnT *conn = pktOpen(RS232_COM1, 0, onPacket, NULL);
// Send a packet (blocking)
uint8_t msg[] = "Hello, packets!";
pktSend(conn, msg, sizeof(msg), true);
// Main loop
while (1) {
int delivered = pktPoll(conn);
// delivered = number of packets received this iteration
}
pktClose(conn);
rs232Close(RS232_COM1);
return 0;
}
CRC
CRC-16-CCITT (polynomial 0x1021, init 0xFFFF) computed via a 256-entry lookup table (512 bytes). The CRC covers the SEQ, TYPE, LEN, and payload fields.
Building
make # builds ../lib/libpacket.a
make clean # removes objects and library
Requires librs232.a at link time.
Target: DJGPP cross-compiler, 486+ CPU.