MODBUS is an old protocol used on industrial automation. Although it is an “old” protocol, it still used on many industrial devices.
There are many articles about this protocol in the Internet, but most are very shallow. This guy has a fair introduction about MODBUS:
https://www.lammertbies.nl/comm/info/modbus.html
I spent some time (few days) to get it working on NuttX. First I implemented the DIR pin control for SAMD21 microcontroller and submitted to mainline: https://bitbucket.org/nuttx/nuttx/commits/a34c9733bcac707be803a27c91fec7406bda310b and also fixed a serial issue that was preventing the serial driver from working: https://bitbucket.org/nuttx/nuttx/commits/db0b9b7c34ddcf098059bb51d73d03d45b3f69b7
Then after spending many hours trying to get my computer communicating with SAMD21 board I discovered that the guilt was the USB/RS485 Dongle I was using.
Replacing it with a common USB/Serial (CP2102) and with a MAX485 module, like this: https://www.aliexpress.com/item/2PCS-MAX485-module-RS-485-module-TTL-to-RS-485-module/2055143247.html , solved the issue.
I connected this way:
+-----------------------------------------+
| USB/Serial (CP2102) | MAX485 Module |
+-----------------------+-----------------+
| RXD | RO |
| TXD | DI |
| RTS | DE + RE |
| GND | GND |
| +5V | VCC |
+-----------------------+-----------------+
This is the listing of features enabled on NuttX:
Serial SERCOM5 Config:
CONFIG_SAMD2L2_SERCOM5_ISUSART=y
CONFIG_USART5_RS485MODE=y
CONFIG_USART5_RS485_DIR_POLARITY=1
We need support to POLL (so don’t select the disable POLL)
# CONFIG_DISABLE_POLL is not set
FreeModbus config:
CONFIG_MODBUS=y
CONFIG_MB_FUNC_HANDLERS_MAX=16
CONFIG_MODBUS_SLAVE=y
CONFIG_MB_ASCII_ENABLED=y
CONFIG_MB_RTU_ENABLED=y
# CONFIG_MB_TCP_ENABLED is not set
CONFIG_MB_ASCII_TIMEOUT_SEC=1
CONFIG_MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS=0
CONFIG_MB_FUNC_OTHER_REP_SLAVEID_BUF=32
CONFIG_MB_FUNC_OTHER_REP_SLAVEID_ENABLED=y
CONFIG_MB_FUNC_READ_INPUT_ENABLED=y
CONFIG_MB_FUNC_READ_HOLDING_ENABLED=y
CONFIG_MB_FUNC_WRITE_HOLDING_ENABLED=y
CONFIG_MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED=y
CONFIG_MB_FUNC_READ_COILS_ENABLED=y
CONFIG_MB_FUNC_WRITE_COIL_ENABLED=y
CONFIG_MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED=y
CONFIG_MB_FUNC_READ_DISCRETE_INPUTS_ENABLED=y
CONFIG_MB_FUNC_READWRITE_HOLDING_ENABLED=y
# CONFIG_MODBUS_MASTER is not set
Modbus example:
CONFIG_EXAMPLES_MODBUS=y
CONFIG_EXAMPLES_MODBUS_PORT=0
CONFIG_EXAMPLES_MODBUS_BAUD=38400
CONFIG_EXAMPLES_MODBUS_PARITY=2
CONFIG_EXAMPLES_MODBUS_REG_INPUT_START=1000
CONFIG_EXAMPLES_MODBUS_REG_INPUT_NREGS=4
CONFIG_EXAMPLES_MODBUS_REG_HOLDING_START=2000
CONFIG_EXAMPLES_MODBUS_REG_HOLDING_NREGS=130
After compiling and flashing the nuttx.bin, run the modbus:
NuttShell (NSH)
nsh> modbus -e
From computer side I used the “mbpoll” program:
$ mbpoll -a 10 -b 38400 -t 3 -r 1000 -c 4 /dev/ttyUSB0 -R
mbpoll 1.4-11 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright © 2015-2019 Pascal JEAN, https://github.com/epsilonrt/mbpoll
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'mbpoll -w' for details.
Protocol configuration: Modbus RTU
Slave configuration...: address = [10]
start reference = 1000, count = 4
Communication.........: /dev/ttyUSB0, 38400-8E1
t/o 1.00 s, poll rate 1000 ms
Data type.............: 16-bit register, input register table
-- Polling slave 10... Ctrl-C to stop)
[1000]: 40122 (-25414)
[1001]: 0
[1002]: 0
[1003]: 0
-- Polling slave 10... Ctrl-C to stop)
[1000]: 832
[1001]: 0
[1002]: 0
[1003]: 0
-- Polling slave 10... Ctrl-C to stop)
[1000]: 24996
[1001]: 0
[1002]: 0
[1003]: 0
-- Polling slave 10... Ctrl-C to stop)
[1000]: 3747
[1001]: 0
[1002]: 0
[1003]: 0
This is an image of pulseview sniffing the bus:

That is it, very easy after you get it working.
Thanks Daniel Carvalho for the help and suggestions to fix the issue.
I used this online tool to check my modbus packets:
http://modbus.rapidscada.net