Shop OBEX P1 Docs P2 Docs Learn Events
Modbus RTU slave - Page 2 — Parallax Forums

Modbus RTU slave

2»

Comments

  • evanhevanh Posts: 16,545

    @Rayman said:
    Guess had a 50/50 shot at getting byte order correct and of course was wrong...

    Yeah, Modbus is something of a dog's breakfast when it comes to consistency. When reading the docs it's apparent it had distinctly different authors between the original and later extensions. It shows the likely ages it was written. Before the PC became dominant Big Endian was seen as the right way but after, then Little Endian gets preferential treatment.

  • evanhevanh Posts: 16,545
    edited 2025-06-24 08:58

    Eg: There is a 32-bit extension for register sizes.

  • JonnyMacJonnyMac Posts: 9,363
    edited 2025-06-24 19:36

    Ray: I noticed that you're using jm_fullduplexserial in your code. Are you going through an RS-485 converter? If yes, are you using a 4-wire connection? I don't see any mechanism in your code to enable the transmit of a '485 chip.

  • RaymanRayman Posts: 15,342
    edited 2025-06-24 20:08

    Just using regular serial via FTDI USB chip, nothing special.

    Although in future will probably be Modbus TCP. Although maybe a serial connection to something nearby might be useful, who knows...

  • JonnyMacJonnyMac Posts: 9,363
    edited 2025-06-25 05:18

    Your MODBUS work inspired me to get an USB-to-RS485 adapter and give it a go. I already had a RS-485 driver for the P2 which is [mostly] compatible with my other serial drivers (though it lacks [unneeded] formatting methods). My friend John at JonyJib uses the RS-485 driver is his projects, though in that case, we have a custom protocol (that looks a lot like XBee messages). That is to say I think the RS-485 driver is solid.

    Over the past several nights I started chipping away. I liberated some bits and bobs from your project; maybe you'll find something useful here. ATM it is a framework for a MODBUS slave; command 2 (read inputs) works. I have a 4-button board that attaches to an Eval (my version of the Parallax Control accessory). I can press any of the buttons and the scanner shows the message working. On that board I have an APA102c. If you find a piece of shareware that lets you write registers I will use that command to set the R, G, and B levels of the pixel.

    (Program updated. See below)

    1920 x 1080 - 411K
  • JonnyMacJonnyMac Posts: 9,363
    edited 2025-06-25 05:21

    Was using ChatGPT as an assistant while programming and asked about a piece of freeware for MODBUS testing. It recommended QModMaster. It's easy, if a bit annoying in that it sends a message any time you change a control, but it does allow you to use the write commands. I updated my program to use the Write Register ($06) and Write Registers ($10) commands.

  • RaymanRayman Posts: 15,342

    Qmodmaster looks good. Have to try that. Most seem to only handle commands 1..4. This looks to do more.

  • MicksterMickster Posts: 2,787

    @Rayman

    Don't know if there's anything of any use to you in the attached but you never know.

  • MicksterMickster Posts: 2,787

    Also "Modbus Spy"

    'modbus spy for Pico Zero

    'version control
    'v01 serial snoop works, ws2812 indicates errors
    'v02 added timestamping
    'v03e preparations for decoding


    option default integer

    'ansi colors
    bl$=Chr$(27)+"[36m"
    wh$=Chr$(27)+"[37m"
    gr$=Chr$(27)+"[32m"
    rd$=Chr$(27)+"[31m"

    'ws2812 colors
    cya%=&h2030
    pur%=&h300030
    grn%=&h4000
    red%=&h400000

    'defaults for Copeland VFD's
    Const bitrate=19200 'communication speed
    ID=45 'default modbus id

    'init system for ZERO using gp0/gp1/gp2

    'open COM1 port with modbus defaults 19200/8bit/even parity/1 stop
    setpin gp1, gp0, COM1 'rx,tx,port
    setpin gp2,dout : pin(gp2)=0 'DE pin is GP2
    Open "COM1:"+Str$(bitrate)+",EVEN" As #1

    device ws2812 o,gp16,1,pur% 'gp16 is connected to the RGB color LED

    dim oe$ = "" , e$ = ""
    timout!= 2 * 11 * 1000 / bitrate 'message end = 2 characters, 11 bits each, in ms
    timer=0

    'snoop bus
    do
    timestamp!=timout!+timer
    do
    if loc(1) then e$=e$+Input$(Loc(1),#1): timestamp!=timout!+timer
    loop until timer>timestamp! 'end of message when no new character for 22 bittime
    if len(e$)>0 then prtx e$ : oe$ = e$ : e$ = ""
    loop



    'prints the string from UART in readable form on console
    Sub prtx a$
    Local i

    print gr$;str$(timer/1000,4,3,"0");" : ";wh$;

    For i=1 To Len(a$)

    If i=2 Then Print choice(Asc(Mid$(a$,i,1))>127,rd$,bl$);
    If i=Len(a$)-1 Then Print choice(check(a$),wh$,rd$);
    Print Right$("0"+Hex$(Asc(Mid$(a$,i,1))),2);" ";

    Next

    'try to analyze source of message

    'Print space$(60-3len(e$));bl$;
    'print "h";
    print wh$

    End Sub


    'checks if e received string has matching CRC in it
    Function check(a$)

    Local pd$,cr$
    Local crc_pd,crc_mes

    If Len(a$)>3 Then

    'separate ID/PDU from message
    pd$=Left$(a$,Len(a$)-2)

    crc_pd=math(crc16 pd$,Len(pd$),&h8005,&hffff,0,1 ,1)

    'separate CRC from message
    cr$=Right$(a$,2)
    crc_mes=256
    Asc(Right$(cr$,1))+Asc(Left$(cr$,1))

    'compare CRC's -> when equal, message is okay.
    If crc_mes=crc_pd Then
    check=1 : device ws2812 o,gp16,1,grn%
    else
    check=0 : device ws2812 o,gp16,1,red%
    end if
    else
    device ws2812 o,gp16,1,pur%
    End If

    End Function

  • RaymanRayman Posts: 15,342

    The write multiple coils and write multiple registers seems a little dangerous to me...
    System working on now also has local control buttons, 7 on and 7 off buttons, that basically control 7 "coils", in PLC jargon.

    Write Multiple Coils would be like if somebody just started jamming several buttons at the same time.
    Doesn't seem like something you'd want to encourage...

  • JonnyMacJonnyMac Posts: 9,363
    edited 2025-06-27 13:17

    Of those two tasks, Write Multiple Coils seems the most straightforward. Even then it seems like the app has to do a lot of bounds checking to ensure there are no problems. For the P2 I figured the maximum write count would be 32 (for pinwrite) and implemented as below. Now... I've only tested with the P56 and P57 LEDs on the P2, but it does seem to work.

    I added the ofs, first, and last parameters so that the routine could be called from a higher-level dispatch routine in the event an application has multiple groups of coils outputs.

    pub write_coils() | ofs                                         ' MODBUS command 15 (W_COILS)
    
      ofs := get_reg(@rxbuf, 2)                                     ' get first coil offset
    
    ' Dispatch wr_coils() based on offset in the command message
    ' -- first parameter in wr_coils is offset within the specific group
    '    * group offset is message offset minus first offset of group
    
      case ofs
        00..01 : wr_coils(ofs-00, LED1, LED2)                       ' 00001..00002 --> P56..P57
        10..17 : wr_coils(ofs-10, 0, 7)                             ' 00011..00018 --> P00..P07     
        other  : exception_response(EC_ADDR)
    
    
    pri wr_coils(ofs, first, last) | cmax, count, lsb, msb, b, outbits, crc
    
    '' Write up to 32 coils
    '' -- ofs is offset within defined pin group
    '' -- first and last designate IO pin bounds of group
    
      cmax := last - first + 1                                      ' pins in the group
    
      count := (rxbuf[4] << 8) | rxbuf[5]                           ' # of coils to write
    
      if (count < 1) || (count > cmax)                              ' validate
        exception_response(EC_VALUE)
        return
    
      lsb := first + ofs                                            ' lsb output pin
      msb := lsb + count - 1                                        ' msb output pin
    
      if (lsb > last) || (msb > last)   
        exception_response(EC_VALUE)
        return
    
      b := rxbuf[6]                                                 ' # of bytes in outbits
      if (b < 1) || (b > 4)
        exception_response(EC_VALUE)
        return
    
      outbits := 0                                                  ' get coils status
      bytemove(@outbits, @rxbuf[7], b)                              ' Little Endian!
    
      pinwrite(lsb addpins (count-1), outbits)                      ' refresh the coils
    
      if (rxbuf[0] == BROADCAST)
        return
    
      bytemove(@txbuf, @rxbuf, 6)                                   ' copy front of command
    
      crc := calc_crc(@txbuf, 6)                                    ' calc crc of reply
    
      bytemove(@txbuf[6], @crc, 2)                                  ' add crc (Little Endian)
    
      send_msg(8)                                                   ' reply to master
      show_reply(8)
    
    

    I was up way too late playing with this, but it is a bit of fun.

  • JonnyMacJonnyMac Posts: 9,363

    I finally took a friend's advice and am using ChatGPT when working on this. Given MODBUS has been around for such a long time and is well-documented, it's nice to ask it for examples and clarification. Give it a try if you haven't already.

    ChatGPT chat on Write Multiple Coils
    -- https://chatgpt.com/share/685dfa0d-03ac-8006-933c-5cd6ee74cff3

  • GenetixGenetix Posts: 1,770

    Sorry to be a distraction from the main topic but where I am working has several devices that support Modbus over Serial so I am interested in any P1 code.

  • RaymanRayman Posts: 15,342

    @Genetix Think there might be a modbus thing for P1 in OBEX.

  • evanhevanh Posts: 16,545

    I linked it at post #3.

Sign In or Register to comment.