# Modbus-Impl (RTU) for rp-usb-serial & rp-pio-serial
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**.
---
## What it does
At runtime the library processes fixed-length Modbus requests carried over a byte-stream transport (USB CDC). For each incoming request frame it:
1. Validates **CRC16 (Modbus polynomial 0xA001, init 0xFFFF)**
2. Parses the request fields:
- Unit ID
- Function code (0x01/0x02/0x03/0x04/0x05/0x06)
- Start address
- Quantity
3. Checks **address range** using `is_valid(addr)`
4. 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
---
## Supported Modbus Functions
- **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**).
---
## Data Model
The library defines interfaces so you can plug in your own memory map:
### `RegisterRead`
Used for 16-bit register based functions (FC03/FC04):
- `get(addr: u16) -> u16`
- `is_valid(addr: u16) -> bool`
### `RegisterWrite`
Used for 16-bit register based functions (FC06/FC16)
- `set_reg(addr: u16, val: u16)`
- `set_qty(qty: u16)`
- `get_qty() -> usize`
### `BitRead`
Used for bit based functions (FC01/FC02):
- `get(addr: u16) -> bool`
- `is_valid(addr: u16) -> bool`
### `BitWrite`
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)
---
## Implementation Overview
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_frame` dispatch 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.
---
## Typical Usage Flow
In your main loop you typically:
1. Receive bytes from `rp-usb-serial` into an 8-byte or more byte depend on func `FrameLen4Func(func, &rx_accum, rx_len);` buffer.
2. Call:
- `ctx.pharse_frame::<MAX_QTY>(frame, &mut resp_buf, &mut exc_buf);`
3. Send the resulting frame back with:
- `RpUsbConsole::write(&resp_or_exc[..len])`
## Example for use
[rp2040-usb-modbus-example](https://github.com/sndnvaps/modbus-impl/tree/main/rp2040-usb-modbus-example)
[rp2040-usb-modbus-example-2](https://github.com/sndnvaps/modbus-impl/tree/main/rp2040-usb-modbus-example-2)
[rp2350-pio-soft-serial-modbus-example](https://github.com/sndnvaps/modbus-impl/tree/main/rp2350-pio-modbus-example)
---
## Notes / Limitations
- 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.
---