Example Path: examples/serial/SerialReceiveModbus
Serial Receive Modbus RTU Timing Example
Overview
This NetBurner embedded application demonstrates the timing requirements for receiving Modbus RTU serial data. It uses a serial receive callback to timestamp each incoming byte and software-based idle detection to identify frame boundaries. The console output shows per-byte timing analysis with inter-character gap measurements and violation detection.
Supported Platforms
- MODM7AE70 (SAME70)
- SBE70LC (SAME70)
- SOMRT1061 (i.MX RT1061)
- MODRT1171 (i.MX RT1171)
Application Description
The application monitors a serial port configured for Modbus RTU communication (9600 baud, 8E1) and provides detailed timing analysis of each received frame, including:
- Per-byte inter-character gap measurement with violation detection
- Inter-frame silence measurement (must be >= 3.5 character times)
- Frame hex dump with Modbus RTU field identification
- Frame validity assessment
Modbus RTU Timing Requirements
Modbus RTU uses timing-based frame delimiting rather than special start/end characters:
- Inter-Frame Silence: A minimum of 3.5 character times (~4.01 ms at 9600 baud) of silence on the bus indicates the start or end of a frame
- Inter-Character Limit: The gap between consecutive bytes within a frame must not exceed 1.5 character times (~1.72 ms at 9600 baud). If exceeded, the frame is considered invalid
- Character Time: At 9600 baud with 8E1 framing (11 bits per character: 1 start + 8 data + 1 parity + 1 stop), one character time is approximately 1.146 ms
Architecture
The example uses a serial receive callback registered via UartData[].m_pPutCharFunc. This ISR-level callback:
- Timestamps each byte using StopWatch::GetTime() (sub-microsecond resolution)
- Checks for 3.5 char-time gaps to detect frame boundaries in software
- Posts a semaphore when a complete frame is detected
The main loop pends on the semaphore, then prints the complete frame with per-byte timing analysis.
Platform-Specific Serial Port Selection
The default serial port is selected automatically based on the platform:
MODM7AE70 / SBE70LC (SAME70)
Default: port 2 (UART0)
These platforms have two types of serial peripheral: USART (ports 0-1) and UART (ports 2-6). This example uses a UART port because USART ports use DMA for receive, which batches incoming bytes and prevents accurate per-byte timing measurement. UART ports at baud rates <= 230400 use per-byte RXRDY interrupts, giving true wire-level timestamps.
| Port | Peripheral | Notes |
| 0 | USART0 | Typically stdio. Uses DMA – not suitable for per-byte timing |
| 1 | USART1 | Uses DMA – not suitable for per-byte timing |
| 2 | UART0 | Default for this example |
| 3 | UART1 | |
| 4 | UART2 | |
| 5 | UART3 | |
| 6 | UART4 | |
SOMRT1061 (i.MX RT1061)
Default: port 1 (LPUART2)
All LPUART ports use per-byte receive interrupts, so any port works for accurate timing.
| Port | Peripheral | Notes |
| 0 | LPUART3 | Typically stdio |
| 1 | LPUART2 | Default for this example |
| 2 | LPUART4 | |
| 3-6 | LPUART5-8 | |
MODRT1171 (i.MX RT1171)
Default: port 1 (LPUART2)
All LPUART ports use per-byte receive interrupts, so any port works for accurate timing.
| Port | Peripheral | Notes |
| 0 | LPUART1 | Typically stdio |
| 1 | LPUART2 | Default for this example |
| 2 | LPUART3 | |
| 3-10 | LPUART5-12 | |
To change the port, modify the MODBUS_SERIAL_PORT define in main.cpp.
Hardware Requirements
- A supported NetBurner platform (see above)
- Serial connection to the selected port at 9600 baud, 8E1
- A Modbus RTU master, serial terminal, or the included Python test script
Usage
- Compile and flash the application to a NetBurner device
- Connect a Modbus RTU master or serial terminal to the selected serial port at 9600 baud, 8 data bits, even parity, 1 stop bit
- Send Modbus RTU frames to the device
- Observe the timing analysis output on the debug console
Python Test Script
A test script (test_modbus_timing.py) is included that sends various valid and invalid Modbus RTU frames to exercise the timing analysis. It requires a USB-to-serial adapter connected to the selected port.
pip install pyserial
Definition test_modbus_timing.py:1
Console Output Example
--- Modbus RTU Frame Received (8 bytes) ---
Inter-frame silence: 5230 us (4.56 char times) [OK >= 3.5]
Byte Hex Delta(us) CharTimes Status
---- ---- --------- --------- ------
0 0x01 --- --- [START]
1 0x03 1148 1.00 ok
2 0x00 1147 1.00 ok
3 0x00 1148 1.00 ok
4 0x00 1147 1.00 ok
5 0x0A 1148 1.00 ok
6 0xC5 1147 1.00 ok
7 0xCD 1148 1.00 ok
Modbus RTU fields:
Slave address: 0x01 (1)
Function code: 0x03 (3)
Data: 4 bytes
CRC (received): 0xCDC5
Frame timing: VALID
Code Structure
main.cpp
Platform-specific port selection (#if defined)
Timing constants (character times, thresholds)
RxByte struct (byte + timestamp)
SerialRxCallback() - ISR-level byte capture with timestamp and frame detection
PrintFrameAnalysis() - Console output with per-byte timing analysis
UserMain() - Initialization and main processing loop
Key APIs Used
- OpenSerial() – Opens the serial port with 8E1 Modbus RTU framing
- UartData[].m_pPutCharFunc – ISR-level receive callback registration
- StopWatch – High-resolution timestamping (sub-microsecond resolution)