209 lines
9.1 KiB
C
209 lines
9.1 KiB
C
// MIN Protocol v2.0.
|
|
//
|
|
// MIN is a lightweight reliable protocol for exchanging information from a microcontroller (MCU) to a host.
|
|
// It is designed to run on an 8-bit MCU but also scale up to more powerful devices. A typical use case is to
|
|
// send data from a UART on a small MCU over a UART-USB converter plugged into a PC host. A Python implementation
|
|
// of host code is provided (or this code could be compiled for a PC).
|
|
//
|
|
// MIN supports frames of 0-255 bytes (with a lower limit selectable at compile time to reduce RAM). MIN frames
|
|
// have identifier values between 0 and 63.
|
|
//
|
|
// An optional transport layer T-MIN can be compiled in. This provides sliding window reliable transmission of frames.
|
|
//
|
|
// Compile options:
|
|
//
|
|
// - Define NO_TRANSPORT_PROTOCOL to remove the code and other overheads of dealing with transport frames. Any
|
|
// transport frames sent from the other side are dropped.
|
|
//
|
|
// - Define MAX_PAYLOAD if the size of the frames is to be limited. This is particularly useful with the transport
|
|
// protocol where a deep FIFO is wanted but not for large frames.
|
|
//
|
|
// The API is as follows:
|
|
//
|
|
// - min_init_context()
|
|
// A MIN context is a structure allocated by the programmer that stores details of the protocol. This permits
|
|
// the code to be reentrant and multiple serial ports to be used. The port parameter is used in a callback to
|
|
// allow the programmer's serial port drivers to place bytes in the right port. In a typical scenario there will
|
|
// be just one context.
|
|
//
|
|
// - min_send_frame()
|
|
// This sends a non-transport frame and will be dropped if the line is noisy.
|
|
//
|
|
// - min_queue_frame()
|
|
// This queues a transport frame which will will be retransmitted until the other side receives it correctly.
|
|
//
|
|
// - min_poll()
|
|
// This passes in received bytes to the context associated with the source. Note that if the transport protocol
|
|
// is included then this must be called regularly to operate the transport state machine even if there are no
|
|
// incoming bytes.
|
|
//
|
|
// There are several callbacks: these must be provided by the programmer and are called by the library:
|
|
//
|
|
// - min_tx_space()
|
|
// The programmer's serial drivers must return the number of bytes of space available in the sending buffer.
|
|
// This helps cut down on the number of lost frames (and hence improve throughput) if a doomed attempt to transmit a
|
|
// frame can be avoided.
|
|
//
|
|
// - min_tx_byte()
|
|
// The programmer's drivers must send a byte on the given port. The implementation of the serial port drivers
|
|
// is in the domain of the programmer: they might be interrupt-based, polled, etc.
|
|
//
|
|
// - min_application_handler()
|
|
// This is the callback that provides a MIN frame received on a given port to the application. The programmer
|
|
// should then deal with the frame as part of the application.
|
|
//
|
|
// - min_time_ms()
|
|
// This is called to obtain current time in milliseconds. This is used by the MIN transport protocol to drive
|
|
// timeouts and retransmits.
|
|
|
|
|
|
#ifndef MIN_H
|
|
#define MIN_H
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
|
|
#ifdef ASSERTION_CHECKING
|
|
#include <assert.h>
|
|
#endif
|
|
|
|
#ifndef NO_TRANSPORT_PROTOCOL
|
|
#define TRANSPORT_PROTOCOL
|
|
#endif
|
|
|
|
#ifndef MAX_PAYLOAD
|
|
#define MAX_PAYLOAD (255U)
|
|
#endif
|
|
|
|
// Powers of two for FIFO management. Default is 16 frames in the FIFO, total of 1024 bytes for frame data
|
|
#ifndef TRANSPORT_FIFO_SIZE_FRAMES_BITS
|
|
#define TRANSPORT_FIFO_SIZE_FRAMES_BITS (4U)
|
|
#endif
|
|
#ifndef TRANSPORT_FIFO_SIZE_FRAME_DATA_BITS
|
|
#define TRANSPORT_FIFO_SIZE_FRAME_DATA_BITS (10U)
|
|
#endif
|
|
|
|
#define TRANSPORT_FIFO_MAX_FRAMES (1U << TRANSPORT_FIFO_SIZE_FRAMES_BITS)
|
|
#define TRANSPORT_FIFO_MAX_FRAME_DATA (1U << TRANSPORT_FIFO_SIZE_FRAME_DATA_BITS)
|
|
|
|
#if (MAX_PAYLOAD > 255)
|
|
#error "MIN frame payloads can be no bigger than 255 bytes"
|
|
#endif
|
|
|
|
// Indices into the frames FIFO are uint8_t and so can't have more than 256 frames in a FIFO
|
|
#if (TRANSPORT_FIFO_MAX_FRAMES > 256)
|
|
#error "Transport FIFO frames cannot exceed 256"
|
|
#endif
|
|
|
|
// Using a 16-bit offset into the frame data FIFO so it has to be addressable within 64Kbytes
|
|
#if (TRANSPORT_FIFO_MAX_FRAME_DATA > 65536)
|
|
#error "Transport FIFO data allocated cannot exceed 64Kbytes"
|
|
#endif
|
|
|
|
struct crc32_context {
|
|
uint32_t crc;
|
|
};
|
|
|
|
#ifdef TRANSPORT_PROTOCOL
|
|
|
|
struct transport_frame {
|
|
uint32_t last_sent_time_ms; // When frame was last sent (used for re-send timeouts)
|
|
uint16_t payload_offset; // Where in the ring buffer the payload is
|
|
uint8_t payload_len; // How big the payload is
|
|
uint8_t min_id; // ID of frame
|
|
uint8_t seq; // Sequence number of frame
|
|
};
|
|
|
|
struct transport_fifo {
|
|
struct transport_frame frames[TRANSPORT_FIFO_MAX_FRAMES];
|
|
uint32_t last_sent_ack_time_ms;
|
|
uint32_t last_received_anything_ms;
|
|
uint32_t last_received_frame_ms;
|
|
uint32_t dropped_frames; // Diagnostic counters
|
|
uint32_t spurious_acks;
|
|
uint32_t sequence_mismatch_drop;
|
|
uint32_t resets_received;
|
|
uint16_t n_ring_buffer_bytes; // Number of bytes used in the payload ring buffer
|
|
uint16_t n_ring_buffer_bytes_max; // Largest number of bytes ever used
|
|
uint16_t ring_buffer_tail_offset; // Tail of the payload ring buffer
|
|
uint8_t n_frames; // Number of frames in the FIFO
|
|
uint8_t n_frames_max; // Larger number of frames in the FIFO
|
|
uint8_t head_idx; // Where frames are taken from in the FIFO
|
|
uint8_t tail_idx; // Where new frames are added
|
|
uint8_t sn_min; // Sequence numbers for transport protocol
|
|
uint8_t sn_max;
|
|
uint8_t rn;
|
|
};
|
|
#endif
|
|
|
|
struct min_context {
|
|
#ifdef TRANSPORT_PROTOCOL
|
|
struct transport_fifo transport_fifo; // T-MIN queue of outgoing frames
|
|
#endif
|
|
uint8_t rx_frame_payload_buf[MAX_PAYLOAD]; // Payload received so far
|
|
uint32_t rx_frame_checksum; // Checksum received over the wire
|
|
struct crc32_context rx_checksum; // Calculated checksum for receiving frame
|
|
struct crc32_context tx_checksum; // Calculated checksum for sending frame
|
|
uint8_t rx_header_bytes_seen; // Countdown of header bytes to reset state
|
|
uint8_t rx_frame_state; // State of receiver
|
|
uint8_t rx_frame_payload_bytes; // Length of payload received so far
|
|
uint8_t rx_frame_id_control; // ID and control bit of frame being received
|
|
uint8_t rx_frame_seq; // Sequence number of frame being received
|
|
uint8_t rx_frame_length; // Length of frame
|
|
uint8_t rx_control; // Control byte
|
|
uint8_t tx_header_byte_countdown; // Count out the header bytes
|
|
uint8_t port; // Number of the port associated with the context
|
|
};
|
|
|
|
#ifdef TRANSPORT_PROTOCOL
|
|
// Queue a MIN frame in the transport queue
|
|
bool min_queue_frame(struct min_context *self, uint8_t min_id, uint8_t *payload, uint8_t payload_len);
|
|
|
|
// Determine if MIN has space to queue a transport frame
|
|
bool min_queue_has_space_for_frame(struct min_context *self, uint8_t payload_len);
|
|
#endif
|
|
|
|
// Send a non-transport frame MIN frame
|
|
void min_send_frame(struct min_context *self, uint8_t min_id, uint8_t *payload, uint8_t payload_len);
|
|
|
|
// Must be regularly called, with the received bytes since the last call.
|
|
// NB: if the transport protocol is being used then even if there are no bytes
|
|
// this call must still be made in order to drive the state machine for retransmits.
|
|
void min_poll(struct min_context *self, uint8_t *buf, uint32_t buf_len);
|
|
|
|
// Reset the state machine and (optionally) tell the other side that we have done so
|
|
void min_transport_reset(struct min_context *self, bool inform_other_side);
|
|
|
|
// CALLBACK. Handle incoming MIN frame
|
|
void min_application_handler(uint8_t min_id, uint8_t *min_payload, uint8_t len_payload, uint8_t port);
|
|
|
|
#ifdef TRANSPORT_PROTOCOL
|
|
// CALLBACK. Must return current time in milliseconds.
|
|
// Typically a tick timer interrupt will increment a 32-bit variable every 1ms (e.g. SysTick on Cortex M ARM devices).
|
|
uint32_t min_time_ms(void);
|
|
#endif
|
|
|
|
// CALLBACK. Must return current buffer space in the given port. Used to check that a frame can be
|
|
// queued.
|
|
uint16_t min_tx_space(uint8_t port);
|
|
|
|
// CALLBACK. Send a byte on the given line.
|
|
void min_tx_byte(uint8_t port, uint8_t byte);
|
|
|
|
// CALLBACK. Indcates when frame transmission is finished; useful for buffering bytes into a single serial call.
|
|
void min_tx_start(uint8_t port);
|
|
void min_tx_finished(uint8_t port);
|
|
|
|
// Initialize a MIN context ready for receiving bytes from a serial link
|
|
// (Can have multiple MIN contexts)
|
|
void min_init_context(struct min_context *self, uint8_t port);
|
|
|
|
#ifdef MIN_DEBUG_PRINTING
|
|
// Debug print
|
|
void min_debug_print(const char *msg, ...);
|
|
#else
|
|
#define min_debug_print(...)
|
|
#endif
|
|
|
|
#endif //MIN_H
|