Shop OBEX P1 Docs P2 Docs Learn Events
MODBUS master and slave? — Parallax Forums

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

  • 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 :neutral:

    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).

  • No need for a CRC table, there are hardware accelerated CRC instructions (CRCBIT and CRCNIB)

  • Thanks for the links.

    @Wuerfel_21 said:
    No need for a CRC table, there are hardware accelerated CRC instructions (CRCBIT and CRCNIB)

    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. B) 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
    
    
  • JonnyMacJonnyMac Posts: 9,102
    edited 2022-06-04 14:49

    But the table method might still be useful for the P1 part.

    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.

  • @JonnyMac said:

      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.77. Test on P1 @ 80MHz.

    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!

  • JonnyMacJonnyMac Posts: 9,102
    edited 2022-06-05 20:35

    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.

  • JonnyMacJonnyMac Posts: 9,102
    edited 2022-06-05 18:09

    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.

Sign In or Register to comment.