A small no_std Modbus RTU helper library designed to run on embedded Rust targets (e.g. RP2040/RP2350) and work with rp-usb-serial USB CDC link & rp-pio-serial PIO-based software serial for RP2040 & RP2350 using arbitrary GPIO pins. It currently focuses on Modbus function codes 01, 02, 03, and 04 (read operations) and builds valid Modbus RTU frames including CRC16.
At runtime the library processes fixed-length Modbus requests carried over a byte-stream transport (USB CDC). For each incoming request frame it:
- Validates CRC16 (Modbus polynomial 0xA001, init 0xFFFF)
- Parses the request fields:
- Unit ID
- Function code (0x01/0x02/0x03/0x04/0x05/0x06)
- Start address
- Quantity
- Checks address range using
is_valid(addr) - Builds one of:
- A normal response frame for the requested function, or
- An exception response frame:
- Function | 0x80
- Exception code (ILLEGAL_FUNCTION / ILLEGAL_DATA_ADDRESS / ILLEGAL_DATA_VALUE)
- CRC16
- 0x01 Read Coils
- 0x02 Read Discrete Inputs
- 0x03 Read Holding Registers (16-bit registers, big-endian in the payload)
- 0x04 Read Input Registers (16-bit registers, big-endian in the payload)
- 0x05 Write Single Coil
- 0x06 Write Single Register
- 0x0F Write Multiple Coils
- 0x10 Write Multiple Registers
Coils/inputs are packed into bytes using Modbus rules (LSB-first bit packing).
The library defines interfaces so you can plug in your own memory map:
Used for 16-bit register based functions (FC03/FC04):
get(addr: u16) -> u16is_valid(addr: u16) -> bool
Used for 16-bit register based functions (FC06/FC16)
set_reg(addr: u16, val: u16)set_qty(qty: u16)get_qty() -> usize
Used for bit based functions (FC01/FC02):
get(addr: u16) -> boolis_valid(addr: u16) -> bool
Used for 16 bit based functions (FC05/FC15)
set_bit(addr: u16, val: bool)set_qty(qty: u16)get_qty() -> usize
It also provides basic storage types that implement these traits:
Hreg<N>for Holding Registers (FC03/FC06/FC16)Ireg<N>for Input Registers (FC04)Coil<N>for Coils (FC01/FC05/FC15)Ists<N>for Discrete Inputs (FC02)
Key components:
-
crc16_modbus(data: &[u8]) -> u16
Implements Modbus RTU CRC16. -
Frame parsing
- Requests are assumed to be 8 bytes long (standard RTU frame for function 01/02/03/04 read requests, 05/06/15/16 write requests).
parse_pdu()|parse_framedispatch supports multiple function codes.
-
Response builders
build_resp_bit_reads()builds FC01/FC02 responses.build_resp_regs()builds FC03/FC04 responses.build_exception_resp()builds exception responses.
-
ModbusCtx::pharse_frame()The main entry that takes a request frame and outputs either a response or an exception.
In your main loop you typically:
- Receive bytes from
rp-usb-serialinto an 8-byte or more byte depend on funcFrameLen4Func(func, &rx_accum, rx_len);buffer. - Call:
ctx.pharse_frame::<MAX_QTY>(frame, &mut resp_buf, &mut exc_buf);
- Send the resulting frame back with:
RpUsbConsole::write(&resp_or_exc[..len])
- This library is RTU-focused but transport-agnostic: it assumes requests arrive as a byte stream and are accumulated into exact 8-byte frames.
- Read functions are implemented (01/02/03/04).
- Write functions are implemented (05/06/15/16).
- Bit packing follows Modbus LSB-first conventions.