MODBUS master and slave?

Is there any MODBUS implementation for the Propeller so far? I've used the search engine but haven't found much. MODBUS is used for industrial IO modules and VFDs, usually with rather low baud rates like 9.6kBd. But as the P2 has lots of power so I think it could even be used for transmitting servo commands with higher baud rates like 1MBd.
Unlike other standards like EtherCAT I think it's relatively easy to implement, at least if not all commands need to be supported. Simple register read and write commands are all you need for simple IO modules and even VFDs or servo drives with hundreds of parameters can be controlled that way.
BTW. I find the documentation of how MODBUS communication works in the technical manual of the Yaskawa GA500 drives much more comprehensible than the original MODBUS specification. It is relatively straigh-forward and explains the basics on only 6 pages whereas the official docs have 50 pages and are written in a quite "academical" style.
However, getting the CRC calculation, error and timeout handling right is somehow non-trivial, I think. So if there is existing code it would help me a lot.
Comments
Not Prop but it works:
fruitoftheshed.com/MMBasic.Modbus-CRC-uses-the-table-lookup-method-for-speed.ashx?HL=modbus
http://www.fruitoftheshed.com/MMBasic.Modbus-CRC-uses-the-bit-by-bit-method.ashx?HL=modbus
Wouldn't let me paste in the link thingy
http://www.fruitoftheshed.com/MMBasic.Xmodem-CRC.ashx?HL=modbus
I see that Mikroe have a Click module for EtherCAT (although the chip is unobtainium, right now).
There is both master and slave versions for Prop1 - https://github.com/parallaxinc/propeller/tree/master/libraries/community/p1
https://github.com/parallaxinc/propeller/tree/master/libraries/community/p1/All/Modbus RTU Master
https://github.com/parallaxinc/propeller/tree/master/libraries/community/p1/All/Modbus RTU
No need for a CRC table, there are hardware accelerated CRC instructions (CRCBIT and CRCNIB)
Thanks for the links.
Yes, it's would be a crime to use a table on the P2 instead of the built in CRCBIT/NIB instructions. But the table method might still be useful for the P1 part. I plan to use both. The servo controller (slave) has a P2 but my older design of the CNC controller has a P1 and would be the master.
My first try was successful.
According to the manual $D140 is the correct result for this example.
'' CRC16 algorithm for MODBUS CON _xtlfreq = 25_000_000 _clkfreq = 200_000_000 polynomial = $A001 VAR long crc PUB demo () crc:= $FFFF ' seed / start value debug (uhex (crc16($02))) ' slave address $02 debug (uhex (crc16($03))) ' function code $03 PUB crc16 (b): c | p ' data byte in, crc word out c:= crc p:= polynomial ORG rev b setq b crcnib c,p crcnib c,p END crc:= c
I used this function in a P1 MODBUS app that I wrote a long time ago.
pub calc_crc16(p_buf, len) : crc '' Calculate 16-bit CRC for MODBUS RTU crc := $FFFF ' initialize crc repeat len ' loop through frame bytes crc ^= byte[p_buf++] ' apply frame byte repeat 8 ' loop through low bits crc ->= 1 ' rotate right if (crc & $8000_0000) ' carry? crc ^= $A001 ' yes, apply polynomial crc &= $FFFF ' clear carry pub mg_crc16(p_buf, len) : crc crc := $FFFF repeat len crc ^= byte[p_buf++] repeat 8 crc := crc >> 1 ^ ($A001 & (crc & 1 <> 0))
Mike Green's version is a tad faster -- which is helpful in pure Spin because the routine is a bit slow. I ran a test on an 11-byte packet. The first version takes 2.96ms, the second 2.77ms. Tested on P1 @ 80MHz.
I found a P2 MODBUS WIP -- this is my conversion of the CRC routine. It's identical to yours except that it expects a pointer to a buffer and a byte count.
pub calc_crc16(p_buf, len) : crc | b '' Calculate CRC16 for MODBUS RTU org mov crc, ##$FFFF .loop rdbyte b, p_buf add p_buf, #1 rev b setq b crcnib crc, ##$A001 crcnib crc, ##$A001 djnz len, #.loop end
On a P2 @ 200MHz it takes about 6us to calculate the CRC for the same 11-byte buffer.
Ok, I think I have to write my own code in assembler, at least for the servo where I have to update position and poll status about once per millisecond. But the above examples are extremely useful as starting point for my own experiments and can probably be used for low-speed applications (VFD speed control and sensor/actor IO) with very little modifications.
Thanks a lot!
I had a bit of time to kill before getting on a train, so I gave it a try. This is a conversion of my verbose CRC16 code -- the result matches with my Spin and Mike's Spin functions. Again, I tend to write very verbose code, so you may be able to trim this.
var long bufpointer long crc16result pub setup_fast_crc cognew(@crc16, @bufpointer) pub fast_crc16(p_buf, len) bufpointer := (p_buf << 16) | len repeat while (bufpointer <> 0) return crc16result dat org 0 crc16 rdlong t0, par wz ' something to work on? if_z jmp #crc16 mov p_hub, t0 ' copy shr p_hub, 16 ' pointer in upper word mov buflen, t0 and buflen, #$1FF ' length in lower 9 bits mov final, H_FFFF crc_main rdbyte t0, p_hub add p_hub, #1 xor final, t0 mov t1, #8 :loop ror final, #1 wc if_c xor final, POLY djnz t1, #:loop and final, H_FFFF djnz buflen, #crc_main mov t0, par add t0, #4 wrlong final, t0 wrlong ZERO, par jmp #crc16 ' ------------------------------------------------------------------------------------------------- H_FFFF long $FFFF POLY long $A001 ZERO long $0 p_hub res 1 buflen res 1 final res 1 t0 res 1 t1 res 1 fit 496
Edit: Moved and line after bits loop.
The conference starts later today, so I thought I'd review yesterday's code. A simple move of the AND instruction will speed up the pure Spin version:
pub calc_crc16(p_buf, len) : crc '' Calculate 16-bit CRC for MODBUS RTU crc := $FFFF ' initialize crc repeat len ' loop through frame bytes crc ^= byte[p_buf++] ' apply frame byte repeat 8 ' loop through low bits crc ->= 1 ' rotate right if (crc & $8000_0000) ' carry? crc ^= $A001 ' yes, apply polynomial crc &= $FFFF ' clean-up
We can wait until the end of the inner loop to clear the stray carry bits.