ModBus

Share your advanced PureBasic knowledge/code with the community.
infratec
Always Here
Always Here
Posts: 7575
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

ModBus

Post by infratec »

Save it as ModBus_RTU.pbi

Code: Select all

;
; ModBus RTU
;

#ModBus_RTU = #True


Global ModBus_RTU_Echo.i


XIncludeFile "ModBus_Helpers.pbi"
XIncludeFile "ModBus_CRC.pbi"




Procedure.i ModBus_RTU_ReadCoils(Port.i, Address.i, StartCoil.i, Quantity.i, Array BitField.a(1))
  
  Protected Result.i, *Buffer, i.i, Timeout.i, Size.i, Bytes.i, Mask.i, Byte.i, Ptr.i
  
  
  Result = -1
  *Buffer = AllocateMemory(128)
  If *Buffer
    
    PokeA(*Buffer, Address)
    PokeA(*Buffer + 1, #ModBus_FunctionCode_ReadCoils)
    PokeU(*Buffer + 2, ModBus_BigEndian16(StartCoil))
    PokeU(*Buffer + 4, ModBus_BigEndian16(Quantity))
    PokeU(*Buffer + 6, ModBus_CalcCRC(*Buffer, 1 + 1 + 2 + 2))
    
    WriteSerialPortData(Port, *Buffer, 1 + 1 + 2 + 2 + 2)
    If ModBus_RTU_Echo
      ReadSerialPortData(Port, *Buffer, 1 + 1 + 2 + 2 + 2)
    EndIf
    
    i = 0
    Timeout = 1000
    Repeat
      If AvailableSerialPortInput(Port)
        ReadSerialPortData(Port, *Buffer + i, 1)
        i + 1
        If i = 5 And PeekA(*Buffer + 1) = $80 | #ModBus_FunctionCode_ReadCoils
          Result = PeekA(*Buffer + 2)
          Break
        EndIf
        
        Bytes = PeekA(*Buffer + 2)
        Size = 1 + 1 + 1 + (Bytes / 8) + 2
        If Bytes & $07 : Size + 1 : EndIf
        
        If i = Size
          If ModBus_CheckCRC(*Buffer, Size)
            Quantity - 1
            ReDim BitField(Quantity)
            For i = 0 To Quantity
              
              If i % 8 = 0
                Mask = $01
                Byte = PeekA(*Buffer + 3 + Ptr)
                Ptr + 1
              EndIf
              
              BitField(i) = Byte & Mask
              
              Mask << 1
            Next i
            Result = 0
          Else
            Result = -2
          EndIf
          Break
        EndIf
      Else
        Delay(1)
        Timeout - 1
      EndIf
    Until Timeout = 0
    
    If Timeout = 0
      Result = -10
    EndIf
    
    FreeMemory(*Buffer)
  EndIf
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_RTU_ReadDiscreteInputs(Port.i, Address.i, StartInput.i, Quantity.i, Array BitField.a(1))
  
  Protected Result.i
  
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_RTU_ReadHoldingRegisters(Port.i, Address.i, StartRegister.i, Quantity.i, Array Words.u(1))
  
  Protected Result.i, *Buffer, i.i, Timeout.i, Size.i
  
  
  Result = -1
  *Buffer = AllocateMemory(128)
  If *Buffer
    
    PokeA(*Buffer, Address)
    PokeA(*Buffer + 1, #ModBus_FunctionCode_ReadHoldingRegisters)
    PokeU(*Buffer + 2, ModBus_BigEndian16(StartRegister))
    PokeU(*Buffer + 4, ModBus_BigEndian16(Quantity))
    PokeU(*Buffer + 6, ModBus_CalcCRC(*Buffer, 1 + 1 + 2 + 2))
    
    WriteSerialPortData(Port, *Buffer, 1 + 1 + 2 + 2 + 2)
    If ModBus_RTU_Echo
      ReadSerialPortData(Port, *Buffer, 1 + 1 + 2 + 2 + 2)
    EndIf
    
    
    i = 0
    Timeout = 1000
    Repeat
      If AvailableSerialPortInput(Port)
        ReadSerialPortData(Port, *Buffer + i, 1)
        Debug Hex(PeekA(*Buffer + i))
        i + 1
        If i = 5 And PeekA(*Buffer + 1) = $80 | #ModBus_FunctionCode_ReadHoldingRegisters
          Result = PeekA(*Buffer + 2)
          Break
        EndIf
        Size = 1 + 1 + 1 + PeekA(*Buffer + 2) + 2
        If i = Size
          If ModBus_CheckCRC(*Buffer, Size)
            Quantity - 1
            ReDim Words(Quantity)
            For i = 0 To Quantity
              Words(i) = ModBus_BigEndian16(PeekU(*Buffer + 3 + 2 * i))
            Next i
            Result = 0
          Else
            Result = -2
          EndIf
          Break
        EndIf
      Else
        Delay(1)
        Timeout - 1
      EndIf
    Until Timeout = 0
    
    If Timeout = 0
      Result = -10
    EndIf
    
    FreeMemory(*Buffer)
  EndIf
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_RTU_ReadInputRegisters(Port.i, Address.i, StartRegister.i, Quantity.i, Array Words.u(1))
  
  Protected Result.i, *Buffer, i.i, Timeout.i, Size.i
  
  
  Result = -1
  *Buffer = AllocateMemory(128)
  If *Buffer
    
    PokeA(*Buffer, Address)
    PokeA(*Buffer + 1, #ModBus_FunctionCode_ReadInputRegisters)
    PokeU(*Buffer + 2, ModBus_BigEndian16(StartRegister))
    PokeU(*Buffer + 4, ModBus_BigEndian16(Quantity))
    PokeU(*Buffer + 6, ModBus_CalcCRC(*Buffer, 1 + 1 + 2 + 2))
    
    WriteSerialPortData(Port, *Buffer, 1 + 1 + 2 + 2 + 2)
    If ModBus_RTU_Echo
      ReadSerialPortData(Port, *Buffer, 1 + 1 + 2 + 2 + 2)
    EndIf
    
    
    i = 0
    Timeout = 1000
    Repeat
      If AvailableSerialPortInput(Port)
        ReadSerialPortData(Port, *Buffer + i, 1)
        Debug Hex(PeekA(*Buffer + i))
        i + 1
        If i = 5 And PeekA(*Buffer + 1) = $80 | #ModBus_FunctionCode_ReadHoldingRegisters
          Result = PeekA(*Buffer + 2)
          Break
        EndIf
        Size = 1 + 1 + 1 + PeekA(*Buffer + 2) + 2
        If i = Size
          If ModBus_CheckCRC(*Buffer, Size)
            Quantity - 1
            ReDim Words(Quantity)
            For i = 0 To Quantity
              Words(i) = ModBus_BigEndian16(PeekU(*Buffer + 3 + 2 * i))
            Next i
            Result = 0
          Else
            Result = -2
          EndIf
          Break
        EndIf
      Else
        Delay(1)
        Timeout - 1
      EndIf
    Until Timeout = 0
    
    If Timeout = 0
      Result = -10
    EndIf
    
    FreeMemory(*Buffer)
  EndIf
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_RTU_WriteSingleCoil(Port.i, Address.i, Coil.i, Value.i)
  
  Protected Result.i
  
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_RTU_WriteSingleRegister(Port.i, Address.i, Register.i, Value.i)
  
  Protected Result.i
  
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_RTU_ReadExceptionStatus(Port.i, Address.i)
  
  Protected Result.i
  
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_RTU_WriteMultipleCoils(Port.i, Address.i, StartCoil.i, Coils.i, Array Values.a(1))
  
  Protected Result.i
  
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_RTU_WriteMultipleRegisters(Port.i, Address.i, StartRegister.i, Quantity.i, Array Values.u(1))
  
  Protected Result.i, *Buffer, i.i, Timeout.i
  
  
  Result = -1
  *Buffer = AllocateMemory(1 + 1 + 2 + 2 + 1 + 2 * Quantity + 2)
  If *Buffer
    
    PokeA(*Buffer, Address)
    PokeA(*Buffer + 1, #ModBus_FunctionCode_WriteMultipleRegisters)
    PokeU(*Buffer + 2, ModBus_BigEndian16(StartRegister))
    PokeU(*Buffer + 4, ModBus_BigEndian16(Quantity))
    PokeA(*Buffer + 6, 2 * Quantity)
    For i = 0 To Quantity - 1
      PokeU(*Buffer + 7 + i * 2, ModBus_BigEndian16(Values(i)))
    Next i
    PokeU(*Buffer + 7 + i * 2, ModBus_CalcCRC(*Buffer, 1 + 1 + 2 + 2 + 1 + 2 * Quantity))
    
    WriteSerialPortData(Port, *Buffer, 1 + 1 + 2 + 2 + 1 + 2 * Quantity + 2)
    If ModBus_RTU_Echo
      ReadSerialPortData(Port, *Buffer, 1 + 1 + 2 + 2 + 1 + 2 * Quantity + 2)
    EndIf
    
    i = 0
    Timeout = 1000
    Repeat
      If AvailableSerialPortInput(Port)
        ReadSerialPortData(Port, *Buffer + i, 1)
        i + 1
        If i = 5 And PeekA(*Buffer + 1) = $80 | #ModBus_FunctionCode_WriteMultipleRegisters
          Result = PeekA(*Buffer + 2)
          Break
        EndIf
        If i = 8
          If ModBus_CheckCRC(*Buffer, 8)
            Result = 0
          Else
            Result = -2
          EndIf
          Break
        EndIf
      Else
        Delay(1)
        Timeout - 1
      EndIf
    Until Timeout = 0
    
    If Timeout = 0
      Result = -10
    EndIf
    
    FreeMemory(*Buffer)
  EndIf
  
  ProcedureReturn Result
  
EndProcedure

infratec
Always Here
Always Here
Posts: 7575
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: ModBus

Post by infratec »

Save it as ModBus_CRC.pbi

Code: Select all

;
; ModBus CRC
;

DataSection
  ModBus_CRCTable:
    Data.u $0000, $C0C1, $C181, $0140, $C301, $03C0, $0280, $C241
    Data.u $C601, $06C0, $0780, $C741, $0500, $C5C1, $C481, $0440     
    Data.u $CC01, $0CC0, $0D80, $CD41, $0F00, $CFC1, $CE81, $0E40     
    Data.u $0A00, $CAC1, $CB81, $0B40, $C901, $09C0, $0880, $C841     
    Data.u $D801, $18C0, $1980, $D941, $1B00, $DBC1, $DA81, $1A40     
    Data.u $1E00, $DEC1, $DF81, $1F40, $DD01, $1DC0, $1C80, $DC41     
    Data.u $1400, $D4C1, $D581, $1540, $D701, $17C0, $1680, $D641     
    Data.u $D201, $12C0, $1380, $D341, $1100, $D1C1, $D081, $1040     
    Data.u $F001, $30C0, $3180, $F141, $3300, $F3C1, $F281, $3240     
    Data.u $3600, $F6C1, $F781, $3740, $F501, $35C0, $3480, $F441     
    Data.u $3C00, $FCC1, $FD81, $3D40, $FF01, $3FC0, $3E80, $FE41    
    Data.u $FA01, $3AC0, $3B80, $FB41, $3900, $F9C1, $F881, $3840
    Data.u $2800, $E8C1, $E981, $2940, $EB01, $2BC0, $2A80, $EA41
    Data.u $EE01, $2EC0, $2F80, $EF41, $2D00, $EDC1, $EC81, $2C40     
    Data.u $E401, $24C0, $2580, $E541, $2700, $E7C1, $E681, $2640
    Data.u $2200, $E2C1, $E381, $2340, $E101, $21C0, $2080, $E041
    Data.u $A001, $60C0, $6180, $A141, $6300, $A3C1, $A281, $6240
    Data.u $6600, $A6C1, $A781, $6740, $A501, $65C0, $6480, $A441
    Data.u $6C00, $ACC1, $AD81, $6D40, $AF01, $6FC0, $6E80, $AE41
    Data.u $AA01, $6AC0, $6B80, $AB41, $6900, $A9C1, $A881, $6840
    Data.u $7800, $B8C1, $B981, $7940, $BB01, $7BC0, $7A80, $BA41
    Data.u $BE01, $7EC0, $7F80, $BF41, $7D00, $BDC1, $BC81, $7C40
    Data.u $B401, $74C0, $7580, $B541, $7700, $B7C1, $B681, $7640
    Data.u $7200, $B2C1, $B381, $7340, $B101, $71C0, $7080, $B041
    Data.u $5000, $90C1, $9181, $5140, $9301, $53C0, $5280, $9241
    Data.u $9601, $56C0, $5780, $9741, $5500, $95C1, $9481, $5440
    Data.u $9C01, $5CC0, $5D80, $9D41, $5F00, $9FC1, $9E81, $5E40
    Data.u $5A00, $9AC1, $9B81, $5B40, $9901, $59C0, $5880, $9841
    Data.u $8801, $48C0, $4980, $8941, $4B00, $8BC1, $8A81, $4A40
    Data.u $4E00, $8EC1, $8F81, $4F40, $8D01, $4DC0, $4C80, $8C41
    Data.u $4400, $84C1, $8581, $4540, $8701, $47C0, $4680, $8641
    Data.u $8201, $42C0, $4380, $8341, $4100, $81C1, $8081, $4040
EndDataSection


Procedure.u ModBus_CalcCRC(*Ptr, Len.i)
  
  Protected CRC_Value.u
  Protected i.i
  
  
  CRC_Value.u = $FFFF
  
  Len - 1
  For i = 0 To Len
    CRC_Value = (CRC_Value >> 8) ! PeekU(?ModBus_CRCTable + (PeekA(*Ptr + i) ! (CRC_Value & $FF)) << 1)
  Next i
  
  ProcedureReturn CRC_Value
  
EndProcedure




Procedure.i ModBus_CheckCRC(*Ptr, Len.i)
  
  Protected Result.i, CalcCRC.u
  
  
  Len - 2
  CalcCRC = ModBus_CalcCRC(*Ptr, Len)
  ;Debug Hex(CalcCRC)
  ;Debug Hex(PeekU(*Ptr + Len))
  If CalcCRC = PeekU(*Ptr + Len)
    Result = #True
  EndIf
  
  ProcedureReturn Result
  
EndProcedure
infratec
Always Here
Always Here
Posts: 7575
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: ModBus

Post by infratec »

Save it as ModBus_Helpers.pbi

Code: Select all

;
; ModBus helpers
;


#ModBus_Coil_On = $FF00
#ModBus_Coil_Off = $0000

Enumeration
  #ModBus_FunctionCode_ReadCoils = $01
  #ModBus_FunctionCode_ReadDiscreteInputs
  #ModBus_FunctionCode_ReadHoldingRegisters
  #ModBus_FunctionCode_ReadInputRegisters
  #ModBus_FunctionCode_WriteSingleCoil
  #ModBus_FunctionCode_WriteSingleRegister
  #ModBus_FunctionCode_WriteMultipleCoils = $0F
  #ModBus_FunctionCode_WriteMultipleRegisters
EndEnumeration


Enumeration
  #ModBus_Exception_IllegalFunctionCode = $01
  #ModBus_Exception_IllegalDataAddress
  #ModBus_Exception_IllegalDataValue
  #ModBus_Exception_DeviceFailure
  #ModBus_Exception_Acknowledge
  #ModBus_Exception_DeviceBusy
  #ModBus_Exception_ParityError
  #ModBus_Exception_GatewayNotReachable = $0A
  #ModBus_Exception_GatewayNotResponding
EndEnumeration




Procedure.u ModBus_BigEndian16(Value.u)
  
  ProcedureReturn PeekA(@Value) << 8 | PeekA(@Value + 1)
  
EndProcedure
infratec
Always Here
Always Here
Posts: 7575
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: ModBus

Post by infratec »

If you later add the other modbus types:

Code: Select all

;
; ModBus utilities
;


Enumeration
  #ModBus_Type_RTU
  #ModBus_Type_TCP
  #ModBus_Type_ASCII
  #ModBus_Type_RTU_Network
EndEnumeration


Prototype.i P_ModBus_ReadCoils(Connection.i, Address.i, StartCoil.i, Quantity.i, Array BitField.a(1))
Prototype.i P_ModBus_ReadHoldingRegisters(Connection.i, Address.i, StartRegister.i, Quantity.i, Array Words.u(1))
Prototype.i P_ModBus_ReadInputRegisters(Connection.i, Address.i, StartRegister.i, Quantity.i, Array Words.u(1))
Prototype.i P_ModBus_WriteMultipleRegisters(Connection.i, Address.i, StartRegister.i, Quantity.i, Array Values.u(1))
Prototype.i P_ModBus_WriteSingleCoil(Connection.i, Address.i, Coil.i, State.i)

Global ModBus_ReadCoils.P_ModBus_ReadCoils
Global ModBus_ReadHoldingRegisters.P_ModBus_ReadHoldingRegisters
Global ModBus_ReadInputRegisters.P_ModBus_ReadInputRegisters
Global ModBus_WriteMultipleRegisters.P_ModBus_WriteMultipleRegisters
Global ModBus_WriteSingleCoil.P_ModBus_WriteSingleCoil

Global ModBus_Type.i


Procedure ModBus_SetType(Type.i)
  
  Select Type
    Case #ModBus_Type_RTU
CompilerIf Defined(ModBus_RTU, #PB_Constant)
      ModBus_ReadCoils = @ModBus_RTU_ReadCoils()
      ModBus_ReadHoldingRegisters = @ModBus_RTU_ReadHoldingRegisters()
      ModBus_ReadInputRegisters = @ModBus_RTU_ReadInputRegisters()
      ModBus_WriteMultipleRegisters = @ModBus_RTU_WriteMultipleRegisters()
      ModBus_WriteSingleCoil = @ModBus_RTU_WriteSingleCoil()
      ModBus_Type = #ModBus_Type_RTU
CompilerEndIf
    Case #Modbus_Type_TCP
CompilerIf Defined(ModBus_TCP, #PB_Constant)
      ModBus_ReadCoils = @ModBus_TCP_ReadCoils()
      ModBus_ReadHoldingRegisters = @ModBus_TCP_ReadHoldingRegisters()
      ModBus_ReadInputRegisters = @ModBus_TCP_ReadInputRegisters()
      ModBus_WriteMultipleRegisters = @ModBus_TCP_WriteMultipleRegisters()
      ModBus_WriteSingleCoil = @ModBus_TCP_WriteSingleCoil()
      ModBus_Type = #ModBus_Type_TCP
    CompilerEndIf
  Case #ModBus_Type_RTU_Network
    CompilerIf Defined(ModBus_RTU_Network, #PB_Constant)
      ModBus_ReadCoils = @ModBus_RTU_Network_ReadCoils()
      ModBus_ReadHoldingRegisters = @ModBus_RTU_Network_ReadHoldingRegisters()
      ModBus_ReadInputRegisters = @ModBus_RTU_Network_ReadInputRegisters()
      ModBus_WriteMultipleRegisters = @ModBus_RTU_Network_WriteMultipleRegisters()
      ModBus_WriteSingleCoil = @ModBus_RTU_Network_WriteSingleCoil()
      ModBus_Type = #ModBus_Type_RTU_Network
      CompilerEndIf
  EndSelect
  
EndProcedure

User avatar
Erolcum
User
User
Posts: 51
Joined: Fri Jun 07, 2024 10:45 am
Location: Turkiye
Contact:

Re: ModBus

Post by Erolcum »

Using @infratec's modbus codes above, I communicated Arduino and PB with modbus RTU. My article on the subject was published on the web site of the store that sells electronic equipment.
my article, translated to english :
https://akademi-robolinkmarket-com.tran ... x_tr_hl=en

Also, you can watch the silent short film "PureBasic can talk modbus" here :
https://www.youtube.com/watch?v=GpbcHUkGTSk

Code: Select all

; modbus RTU code from infratec
; https://www.purebasic.fr/english/viewtopic.php?t=85050 
; console timer (thread) from mk-soft
; https://www.purebasic.fr/english/viewtopic.php?t=73927
; For arduino code and details, article is here :
; https://akademi.robolinkmarket.com/purebasic-nedir/


EnableExplicit

CompilerIf Not #PB_Compiler_Thread
  CompilerError "Use Compiler Option ThreadSafe!"
CompilerEndIf

Structure udtThread
  ThreadID.i
  Signal.i
  Ready.i
EndStructure

Enumeration
  #ModBus_FunctionCode_ReadCoils = $01 ; 1
  #ModBus_FunctionCode_ReadDiscreteInputs ; 2
  #ModBus_FunctionCode_ReadHoldingRegisters ; 3
  #ModBus_FunctionCode_ReadInputRegisters   ; 4
  #ModBus_FunctionCode_WriteSingleCoil      ; 5
  #ModBus_FunctionCode_WriteSingleRegister  ; 6
  #ModBus_FunctionCode_WriteMultipleCoils = $0F ; 15
  #ModBus_FunctionCode_WriteMultipleRegisters   ; 16
EndEnumeration

Global ModBus_RTU_Echo.i, Work.udtThread
Work\Signal = CreateSemaphore()

Procedure.u ModBus_CalcCRC(*Ptr, Len.i)
  Protected CRC_Value.u
  Protected i.i
  CRC_Value.u = $FFFF
  Len - 1 ; means Len = Len - 1
  For i = 0 To Len
    CRC_Value = (CRC_Value >> 8) ! PeekU(?ModBus_CRCTable + (PeekA(*Ptr + i) ! (CRC_Value & $FF)) << 1)
  Next i
  ProcedureReturn CRC_Value
EndProcedure


Procedure.i ModBus_CheckCRC(*Ptr, Len.i)
  Protected Result.i, CalcCRC.u
  Len - 2
  CalcCRC = ModBus_CalcCRC(*Ptr, Len)
  If CalcCRC = PeekU(*Ptr + Len)
    Result = #True
  EndIf
  ProcedureReturn Result
EndProcedure


Procedure.u ModBus_BigEndian16(Value.u)
  ProcedureReturn PeekA(@Value) << 8 | PeekA(@Value + 1)
EndProcedure


Procedure.i ModBus_RTU_ReadHoldingRegisters(Port.i, Address.i, StartRegister.i, RegCount.i, Array Registers.u(1))
  Protected Result.i, *Buffer, i.i, Timeout.i, Size.i
  Result = -1
  *Buffer = AllocateMemory(128)
  If *Buffer
    PokeA(*Buffer, Address)
    PokeA(*Buffer + 1, #ModBus_FunctionCode_ReadHoldingRegisters)
    PokeU(*Buffer + 2, ModBus_BigEndian16(StartRegister))
    PokeU(*Buffer + 4, ModBus_BigEndian16(RegCount))
    PokeU(*Buffer + 6, ModBus_CalcCRC(*Buffer, 1 + 1 + 2 + 2))
    PrintN("") : Print ("Request  : ")
    For i = 0 To 7
      Print ( Hex(PeekA(*Buffer + i)) + "-" )
    Next
    ;ShowMemoryViewer(*Buffer, 100) ; to see this memory in ram
    ;CallDebugger ; to stop program here
    WriteSerialPortData(Port, *Buffer, 1 + 1 + 2 + 2 + 2)
    If ModBus_RTU_Echo
      ReadSerialPortData(Port, *Buffer, 1 + 1 + 2 + 2 + 2)
    EndIf
    i = 0
    Timeout = 500
    PrintN("")
    Print ("Response : ")
    Repeat
      If AvailableSerialPortInput(Port)
        ReadSerialPortData(Port, *Buffer + i, 1)
        Print ( Hex(PeekA(*Buffer + i)) + "-" )
        
        i + 1 
        ; in case of an incorrect request from master, slave response has a different func.code than $03
        If i = 5 And PeekA(*Buffer + 1) <> #ModBus_FunctionCode_ReadHoldingRegisters 
          Result = PeekA(*Buffer + 2) ; Result may be 1, 2, 3
          Break
        EndIf
        Size = 1 + 1 + 1 + PeekA(*Buffer + 2) + 2
        If i = Size : PrintN ( " " )
          If ModBus_CheckCRC(*Buffer, Size)
            RegCount - 1
            ReDim Registers(RegCount)
            For i = 0 To RegCount
              Registers(i) = ModBus_BigEndian16(PeekU(*Buffer + 3 + 2 * i))    
            Next i
            Result = 0 ; in case of successful response from slave
          Else
            Result = -2 ; in case of incorrect CRC from slave
          EndIf
          Break
        EndIf
      Else
        Delay(1)
        Timeout - 1
      EndIf
    Until Timeout = 0
    If Timeout = 0
      Result = -10 ; in case of timeout or no response from slave
    EndIf
    FreeMemory(*Buffer)
  EndIf
  ProcedureReturn Result
EndProcedure

OpenConsole("Modbus RTU Test with PureBasic")
EnableGraphicalConsole(1)

Procedure thWork(*Data.udtThread) ; *Data is a pointer, it points an address
  Protected KeyPressed$
  
  Repeat
    KeyPressed$ = Inkey()
    Delay(2000)
    SignalSemaphore(*Data\Signal)
  Until UCase(KeyPressed$) = "X" ; Wait until X is pressed
  
  *Data\Ready = #True
  SignalSemaphore(*Data\Signal)
EndProcedure

; set your COM port name as a second parameter of OpenSerialPort
If OpenSerialPort(0, "COM3", 9600, #PB_SerialPort_NoParity, 8, 1, #PB_SerialPort_NoHandshake, 1, 1)
  PrintN ("Port is opened, wait a moment..") 
  Delay(3000) ; if I remove this, arduino send no response to request after restarting pc
Else
  PrintN ("Failed : can not open port!")
  Delay(3000)
  End
EndIf


Dim Words.u(0)
Define i, Result, Quantity
; change Quantity to 0 to see a result of an incorrect request
Quantity = 2; holding registers quantity

Work\ThreadID = CreateThread(@thWork(), Work) ; @ means the address of procedure thWork()

Repeat
  ; change slaveID address 1 to 5 below to see timeout and no response from slave
  Result = ModBus_RTU_ReadHoldingRegisters(0, 1, 0, Quantity, Words()) 
  ; we call the procedure and pass the values to the procedure 
  ; Words() array passes by reference (address) to the procedure
  ; that's why it's name is not important for the procedure
  
  PrintN ("")
  PrintN ("Registers read from arduino with modbus RTU : ")
  For i = 0 To ArraySize(Words())
    PrintN ("Words(" + i + ") : " + Words(i))
  Next
  PrintN ("")
  PrintN ("Result : " + Result ) : PrintN ("")
  PrintN ("Press X to eXit")
  WaitSemaphore(Work\Signal) ; program waits here until the signal comes back by SignalSemaphore
  ClearConsole()
Until Work\Ready

End

; ModBus CRC16 data
DataSection
  ModBus_CRCTable:
  Data.u $0000, $C0C1, $C181, $0140, $C301, $03C0, $0280, $C241
  Data.u $C601, $06C0, $0780, $C741, $0500, $C5C1, $C481, $0440     
  Data.u $CC01, $0CC0, $0D80, $CD41, $0F00, $CFC1, $CE81, $0E40     
  Data.u $0A00, $CAC1, $CB81, $0B40, $C901, $09C0, $0880, $C841     
  Data.u $D801, $18C0, $1980, $D941, $1B00, $DBC1, $DA81, $1A40     
  Data.u $1E00, $DEC1, $DF81, $1F40, $DD01, $1DC0, $1C80, $DC41     
  Data.u $1400, $D4C1, $D581, $1540, $D701, $17C0, $1680, $D641     
  Data.u $D201, $12C0, $1380, $D341, $1100, $D1C1, $D081, $1040     
  Data.u $F001, $30C0, $3180, $F141, $3300, $F3C1, $F281, $3240     
  Data.u $3600, $F6C1, $F781, $3740, $F501, $35C0, $3480, $F441     
  Data.u $3C00, $FCC1, $FD81, $3D40, $FF01, $3FC0, $3E80, $FE41    
  Data.u $FA01, $3AC0, $3B80, $FB41, $3900, $F9C1, $F881, $3840
  Data.u $2800, $E8C1, $E981, $2940, $EB01, $2BC0, $2A80, $EA41
  Data.u $EE01, $2EC0, $2F80, $EF41, $2D00, $EDC1, $EC81, $2C40     
  Data.u $E401, $24C0, $2580, $E541, $2700, $E7C1, $E681, $2640
  Data.u $2200, $E2C1, $E381, $2340, $E101, $21C0, $2080, $E041
  Data.u $A001, $60C0, $6180, $A141, $6300, $A3C1, $A281, $6240
  Data.u $6600, $A6C1, $A781, $6740, $A501, $65C0, $6480, $A441
  Data.u $6C00, $ACC1, $AD81, $6D40, $AF01, $6FC0, $6E80, $AE41
  Data.u $AA01, $6AC0, $6B80, $AB41, $6900, $A9C1, $A881, $6840
  Data.u $7800, $B8C1, $B981, $7940, $BB01, $7BC0, $7A80, $BA41
  Data.u $BE01, $7EC0, $7F80, $BF41, $7D00, $BDC1, $BC81, $7C40
  Data.u $B401, $74C0, $7580, $B541, $7700, $B7C1, $B681, $7640
  Data.u $7200, $B2C1, $B381, $7340, $B101, $71C0, $7080, $B041
  Data.u $5000, $90C1, $9181, $5140, $9301, $53C0, $5280, $9241
  Data.u $9601, $56C0, $5780, $9741, $5500, $95C1, $9481, $5440
  Data.u $9C01, $5CC0, $5D80, $9D41, $5F00, $9FC1, $9E81, $5E40
  Data.u $5A00, $9AC1, $9B81, $5B40, $9901, $59C0, $5880, $9841
  Data.u $8801, $48C0, $4980, $8941, $4B00, $8BC1, $8A81, $4A40
  Data.u $4E00, $8EC1, $8F81, $4F40, $8D01, $4DC0, $4C80, $8C41
  Data.u $4400, $84C1, $8581, $4540, $8701, $47C0, $4680, $8641
  Data.u $8201, $42C0, $4380, $8341, $4100, $81C1, $8081, $4040
EndDataSection
Last edited by Erolcum on Tue Sep 03, 2024 7:04 pm, edited 6 times in total.
You may visit my new Purebasic blog here..
:arrow: https://erolcum-github-io.translate.goo ... r_pto=wapp
User avatar
Erolcum
User
User
Posts: 51
Joined: Fri Jun 07, 2024 10:45 am
Location: Turkiye
Contact:

Re: ModBus

Post by Erolcum »

my article about PB is on the mainpage of the store for a few days, does it look nice ? https://akademi.robolinkmarket.com/
You may visit my new Purebasic blog here..
:arrow: https://erolcum-github-io.translate.goo ... r_pto=wapp
infratec
Always Here
Always Here
Posts: 7575
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: ModBus

Post by infratec »

Save it as ModBus_TCP.pbi

Code: Select all

;
; ModBus TCP
;

#ModBus_TCP = #True

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf


XIncludeFile "ModBus_Helpers.pbi"


Procedure.i ModBus_TCP_ReadCoils(Connection.i, Address.i, StartCoil.i, Quantity.i, Array BitField.a(1))
  
  Protected Result.i, *Buffer, i.i, Timeout.i, Size.i, Bytes.i, Mask.i, Byte.i, Ptr.i, TransactionID.u
  
  
  Result = -1
  *Buffer = AllocateMemory(320)
  If *Buffer
    
    TransactionID.u = Random($FFFF)
    
    PokeU(*Buffer + 0, TransactionID)         ; Transaction ID
    PokeU(*Buffer + 2, 0)                     ; Protocol ID
    PokeU(*Buffer + 4, ModBus_BigEndian16(6)) ; Length
    
    PokeA(*Buffer + 6, Address)
    PokeA(*Buffer + 7, #ModBus_FunctionCode_ReadCoils)
    PokeU(*Buffer + 8, ModBus_BigEndian16(StartCoil))
    PokeU(*Buffer + 10, ModBus_BigEndian16(Quantity))
    
    If SendNetworkData(Connection, *Buffer, 12) = 12
      
      Timeout = 1000
      Repeat
        If NetworkClientEvent(Connection) = #PB_NetworkEvent_Data
          Size = ReceiveNetworkData(Connection, *Buffer, 320)
          If Size > 6
            If PeekU(*Buffer + 0) = TransactionID
              If PeekA(*Buffer + 7) < $80
                Quantity - 1
                ReDim BitField(Quantity)
                For i = 0 To Quantity
                  
                  If i % 8 = 0
                    Mask = $01
                    Byte = PeekA(*Buffer + 9 + Ptr)
                    Ptr + 1
                  EndIf
                  
                  BitField(i) = Byte & Mask
                  
                  Mask << 1
                Next i
                Result = 0
              Else
                Result = PeekA(*Buffer + 8)
              EndIf
            EndIf
          EndIf
          Break
        Else
          Delay(1)
          Timeout - 1
        EndIf
      Until Timeout = 0
      
      If Timeout = 0
        Result = #ModBus_Exception_GatewayNotResponding
      EndIf
      
    Else
      Result = #ModBus_Exception_GatewayNotReachable
    EndIf
    
    FreeMemory(*Buffer)
  EndIf
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_TCP_ReadDiscreteInputs(Connection.i, Address.i, StartInput.i, Quantity.i, Array BitField.a(1))
  
  Protected Result.i
  
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_TCP_ReadHoldingRegisters(Connection.i, Address.i, StartRegister.i, Quantity.i, Array Words.u(1))
  
  Protected Result.i, *Buffer, i.i, Timeout.i, Size.i, TransactionID.u
  
  
  Result = -1
  *Buffer = AllocateMemory(320)
  If *Buffer
    
    TransactionID.u = Random($FFFF)
    
    PokeU(*Buffer + 0, TransactionID)         ; Transaction ID
    PokeU(*Buffer + 2, 0)                     ; Protocol ID
    PokeU(*Buffer + 4, ModBus_BigEndian16(6)) ; Length
    
    PokeA(*Buffer + 6, Address)
    PokeA(*Buffer + 7, #ModBus_FunctionCode_ReadHoldingRegisters)
    PokeU(*Buffer + 8, ModBus_BigEndian16(StartRegister))
    PokeU(*Buffer + 10, ModBus_BigEndian16(Quantity))
    
    If SendNetworkData(Connection, *Buffer, 12) = 12
      
      Timeout = 1000
      Repeat
        If NetworkClientEvent(Connection) = #PB_NetworkEvent_Data
          Size = ReceiveNetworkData(Connection, *Buffer, 320)
          If Size > 6
            If PeekU(*Buffer + 0) = TransactionID
              If PeekA(*Buffer + 7) < $80
                Quantity - 1
                ReDim Words(Quantity)
                For i = 0 To Quantity
                  Words(i) = ModBus_BigEndian16(PeekU(*Buffer + 9 + 2 * i))
                Next i
                Result = 0
              Else
                Result = PeekA(*Buffer + 8)
              EndIf
            EndIf
          EndIf
          Break
        Else
          Delay(1)
          Timeout - 1
        EndIf
      Until Timeout = 0
      
      If Timeout = 0
        Result = #ModBus_Exception_GatewayNotResponding
      EndIf
      
    Else
      Result = #ModBus_Exception_GatewayNotReachable
    EndIf
    
    FreeMemory(*Buffer)
  EndIf
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_TCP_ReadInputRegisters(Connection.i, Address.i, StartRegister.i, Quantity.i, Array Words.u(1))
  
  Protected Result.i
  
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_TCP_WriteSingleCoil(Connection.i, Address.i, Coil.i, Value.i)
  
  Protected Result.i
  
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_TCP_WriteSingleRegister(Connection.i, Address.i, Register.i, Value.i)
  
  Protected Result.i
  
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_TCP_ReadExceptionStatus(Connection.i, Address.i)
  
  Protected Result.i
  
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_TCP_WriteMultipleCoils(Connection.i, Address.i, StartCoil.i, Coils.i, Array Values.a(1))
  
  Protected Result.i
  
  
  ProcedureReturn Result
  
EndProcedure




Procedure.i ModBus_TCP_WriteMultipleRegisters(Connection.i, Address.i, StartRegister.i, Quantity.i, Array Values.u(1))
  
  Protected Result.i, *Buffer, i.i, Timeout.i, TransactionID.u, Size.i
  
  
  Result = -1
  *Buffer = AllocateMemory(320)
  If *Buffer
    
    TransactionID.u = Random($FFFF)
    
    PokeU(*Buffer + 0, TransactionID)         ; Transaction ID
    PokeU(*Buffer + 2, 0)                     ; Protocol ID
    PokeU(*Buffer + 4, ModBus_BigEndian16(1 + 1 + 2 + 2  + 1 + 2 * Quantity)) ; Length
    
    PokeA(*Buffer + 6, Address)
    PokeA(*Buffer + 7, #ModBus_FunctionCode_WriteMultipleRegisters)
    PokeU(*Buffer + 8, ModBus_BigEndian16(StartRegister))
    PokeU(*Buffer + 10, ModBus_BigEndian16(Quantity))
    PokeA(*Buffer + 12, 2 * Quantity)
    For i = 0 To Quantity - 1
      PokeU(*Buffer + 13 + i * 2, ModBus_BigEndian16(Values(i)))
    Next i
    
    If SendNetworkData(Connection, *Buffer, 13 + 2 * Quantity) = 13 + 2 * Quantity
      
      Timeout = 1000
      Repeat
        If NetworkClientEvent(Connection) = #PB_NetworkEvent_Data
          Size = ReceiveNetworkData(Connection, *Buffer, 320)
          If Size > 6
            If PeekU(*Buffer + 0) = TransactionID
              If PeekA(*Buffer + 7) < $80
                Result = 0
              Else
                Result = PeekA(*Buffer + 8)
              EndIf
            EndIf
          EndIf
          Break
        Else
          Delay(1)
          Timeout - 1
        EndIf
      Until Timeout = 0
      
      If Timeout = 0
        Result = #ModBus_Exception_GatewayNotResponding
      EndIf
      
    Else
      Result = #ModBus_Exception_GatewayNotReachable
    EndIf
    
    FreeMemory(*Buffer)
  EndIf
  
  ProcedureReturn Result
  
EndProcedure
User avatar
Erolcum
User
User
Posts: 51
Joined: Fri Jun 07, 2024 10:45 am
Location: Turkiye
Contact:

Re: ModBus

Post by Erolcum »

@infratec, always many thanks for your help and contribution to the community
You may visit my new Purebasic blog here..
:arrow: https://erolcum-github-io.translate.goo ... r_pto=wapp
SMaag
Enthusiast
Enthusiast
Posts: 302
Joined: Sat Jan 14, 2023 6:55 pm
Location: Bavaria/Germany

Re: ModBus

Post by SMaag »

some additional information for a better understanding of the Modbus Protocol!

The Modbus TCP Protocol in detail: ADU, PDU Data format!

Code: Select all

; -----------------------------------------------------------------------------------
; |             Modbus TCP ADU-HEADER                    |        Modbus PDU        |
; -----------------------------------------------------------------------------------
; |    [0..1]   |   [2..3]    |    [4..5]     |   [6]    | [7] | [8..9] | [10..259] |
; -----------------------------------------------------------------------------------
; | Transaction | Protocol ID | MessageLength | DeviceID | FC  |  ADR   |   DATA    |
; -----------------------------------------------------------------------------------
; |                                                      | [7] | [8] |  [9..259]    |
; |                    for the response                  ----------------------------
; |                                                      | FC  |  NB |    DATA      |   
; -----------------------------------------------------------------------------------
; MessageLength = Len(DeviceID) + Len(PDU); NB : NumberOfBytes follow

marc_256
Addict
Addict
Posts: 835
Joined: Thu May 06, 2010 10:16 am
Location: Belgium
Contact:

Re: ModBus

Post by marc_256 »

Hello Infratec, SMaag,

@Infratec
Wow, thanks for your splendid work and time.
I will test it if I have my hardware build.

@SMaag
Thanks for the extra info.

Marc,
- every professional was once an amateur - greetings from Pajottenland - Belgium -
PS: sorry for my english I speak flemish ...
Post Reply