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 | RI | | 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:
I just inspected the NuttX Modbus/RTU implementantion because I wanted to put my own Modbus code over the existing serial API.
Short version: It is unuseable as it is.
RTU defines t1.5 as packet end and t3.5 as minimum packet gap.
That means an end has to be detected somewhere between t1.5 and t3.5
t3.5 for 19200 is around 2ms.
NuttX waits 50ms in the slave code and 5ms in the master plus possible layer overhead.
There is code to calculate timer ticks, but the results isn’t used anywhere.
It is time wasting on the master to wait longer and too small for speeds below 9600, but you can get away with this as the bus stays idle during this time.
On a slave however this gets you into trouble because communication to other slaves can result in failing to detect the begin of a packet addressed to you.
That said, for an RTU slave the existing serial API wouldn’t work.
I usually put a hardware timer in addition to the uart code.
On the Atmel SAM7 and similar series they have specific idle support in the USART itself, which makes it super easy, unfortunately this is not on the Sercom in the SAMD21 and many other controllers.
I usually set packet end to t2.5 and gap to t4.5 to allow some margins.
Hi Bernd,
Thank you very much for this update.
I tested both master and slave, it worked for basic tests, maybe it could fail for some devices.
So, yes! It needs to be fixed!
I will try to fix it.
I really appreciated your comment.