Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Has anybody experience with MODBUS to read temp sensors?
#4
@Rudy M 


Code: (Select All)



' MODBUS RTU (RS-485) - PTA8D08 (8x PT100) - QB64/QB64PE
'
'
' What this program does:
' - Opens COM port (USB-RS485 adapter usually appears as COMx)
' - Periodically reads 8 temperature registers (0x0000..0x0007) via MODBUS function 03
' - Validates CRC16 (MODBUS)
' - Decodes signed 16-bit temperature values in 0.1°C units
'
' IMPORTANT NOTES (read before debugging):
' - QB64 commonly supports COM1..COM9 easily. If Windows assigns COM10+, renumber it in Device Manager.
' - RS-485 wiring: A/B must match. If you swap A/B you usually get no reply.
' - Many USB-RS485 adapters auto-handle TX enable. Some need RTS control -> harder in QB64.
' - Device might be in "auto-report" mode and spam the line. If so, your receive buffer fills with junk.
'  You can force "query mode" by writing register 0x00FA = 0 (code included as optional).



Dim Shared Byte1 As String * 1 ' used by GET/PUT for serial I/O


Dim port$: port$ = "1" ' COM1..COM9 are the safest range for QB64
Dim slave%: slave% = 1 ' MODBUS slave address of your module (often 1 by default)

' ---- Tuning knobs:
Dim pollHz!: pollHz! = 2 ' how often to poll (2 = twice per second)
Dim showRawFrames%: showRawFrames% = -1 ' -1 = yes, 0 = no (raw hex dumps help debugging)

' ---- Expected response length for reading 8 registers:
' addr(1) + func(1) + bytecount(1) + data(16) + crc(2) = 21 bytes
Dim respLen%: respLen% = 21

' ---- Open COM port
Dim h%: h% = FreeFile
' NOTE: If your QB64 build rejects this OPEN string, share the error text and your QB64/QB64PE version.
Open "COM" + port$ + ":9600,N,8,1,BIN,CS0,DS0" For Random As #h%

Print "Opened COM"; port$; " at 9600,N,8,1"
Print "Polling slave address:"; slave%
Print "Press ESC to exit."
Print

' Optional: Force query mode (stop auto-report spamming).
' If the device was previously configured to auto-report, your RX buffer may contain unsolicited frames.
' Register 0x00FA:
'  0 = query mode (default)
'  1..255 = auto-report interval in seconds
'
' Uncomment to enforce query mode:
'
'  DIM wr$ : wr$ = ModbusWriteSingle$(slave%, &H00FA, 0)
'  SerialFlush h%
'  SerialWrite h%, wr$
'  DIM wrResp$ : wrResp$ = SerialReadFixed$(h%, 8, 1.0) ' write-single response is 8 bytes
'  IF showRawFrames% THEN DumpHex "WRITE 00FA response", wrResp$
'  IF LEN(wrResp$) <> 8 THEN PRINT "No/short reply to write 00FA. Continuing anyway."
'

Do
    Dim k$: k$ = InKey$
    If k$ = Chr$(27) Then Exit Do ' ESC

    ' Always flush any leftover bytes before issuing a request.
    ' This is crucial if:
    ' - The module is/was in auto-report mode
    ' - The line has noise, partial frames, or previous retries
    SerialFlush h%

    ' Build and send MODBUS request: Read 8 temperature registers starting at 0x0000
    Dim req$: req$ = ModbusReadRegs$(slave%, &H0000, 8)

    If showRawFrames% Then DumpHex "TX (Read 8 temps)", req$
    SerialWrite h%, req$

    ' Read fixed-length response
    Dim resp$: resp$ = SerialReadFixed$(h%, respLen%, 1.0)

    If Len(resp$) <> respLen% Then
        Print "Timeout / short response. Received bytes:"; Len(resp$)
        If Len(resp$) > 0 And showRawFrames% Then DumpHex "RX (partial)", resp$
        Print "NOTES:"
        Print "- Check A/B wiring and module power (8-30V)."
        Print "- Confirm correct COM port number and slave address."
        Print "- If COM is > 9, renumber it to COM1..COM9."
        Print "- If you suspect auto-report mode, enforce register 00FA=0 (see optional block)."
        Print
        _Limit pollHz!
        GoTo ContinueLoop
    End If

    If showRawFrames% Then DumpHex "RX (Read 8 temps)", resp$

    ' Basic header checks
    If Asc(resp$, 1) <> (slave% And &HFF) Then
        Print "Wrong slave address in response:"; Asc(resp$, 1)
        Print "NOTE: Some adapters echo bytes; some devices respond on a different address."
        Print
        _Limit pollHz!
        GoTo ContinueLoop
    End If

    If Asc(resp$, 2) <> &H03 Then
        Print "Wrong function code in response:"; Asc(resp$, 2)
        Print "NOTE: If MSB is set (e.g. 0x83) it's an exception response."
        Print
        _Limit pollHz!
        GoTo ContinueLoop
    End If

    If ModbusCheckCrc%(resp$) = 0 Then
        Print "CRC FAIL (response corrupted / wrong framing)."
        Print "NOTES:"
        Print "- Try lower baudrate if configurable (but default is 9600)."
        Print "- Improve shielding/grounding, keep RS485 lines short, add termination if needed."
        Print "- Ensure adapter is genuine RS485 (not TTL serial)."
        Print
        _Limit pollHz!
        GoTo ContinueLoop
    End If

    ' Byte count should be 16 for 8 registers (8*2)
    If Asc(resp$, 3) <> 16 Then
        Print "Unexpected byte count:"; Asc(resp$, 3); " (expected 16)"
        Print
        _Limit pollHz!
        GoTo ContinueLoop
    End If

    ' Decode temperatures
    ' Data starts at byte 4 (1-based string indexing):
    ' [4..5]=CH0, [6..7]=CH1, ... each is signed 16-bit big-endian, unit 0.1°C
    Dim i As Integer, raw%, t!
    For i = 0 To 7
        raw% = S16FromBE%(Asc(resp$, 4 + i * 2), Asc(resp$, 5 + i * 2))
        t! = raw% / 10!
        Print Using "CH## = ####.# C"; i; t!
    Next
    Print "----------------------------"
    Print

    ' TODO / OPTIONAL EXTENSIONS:
    ' 1) Read PT100 resistance registers 0x0020..0x0027 (unit 0.1 ohm, unsigned).
    ' 2) Implement write (06) for settings like:
    '    - 00FA (auto-report interval / query mode)
    '    - 00FE (baudrate) -> requires power cycle
    '    - 00FF (parity)  -> requires power cycle
    ' 3) Add robust framing:
    '    - Read header first (3 bytes), then read bytecount+2 CRC dynamically
    '    - Handle exception frames (func|0x80 + exception code)
    '
    ' WHAT TO SEND FOR TUNING (copy/paste into forum/chat):
    ' - Your exact USB-RS485 adapter model/link
    ' - Windows COM port number
    ' - Module slave address (DIP/setting)
    ' - RAW HEX dumps shown by DumpHex() for TX and RX
    ' - If you never get RX: confirm A/B wiring and whether GND is shared
    '
    ' With those, we can fix 95% of issues quickly.

    ContinueLoop:
    _Limit pollHz!
Loop

Close #h%
Print "Done."
End

'===========================================================
' SUBs / FUNCTIONs (MUST BE AFTER MAIN)
'===========================================================

Function Crc16Modbus& (s$)
    ' MODBUS CRC16:
    ' - Init 0xFFFF
    ' - Poly 0xA001 (reflected)
    Dim crc As Long, i As Integer, j As Integer
    crc = &HFFFF&
    For i = 1 To Len(s$)
        crc = crc Xor Asc(s$, i)
        For j = 1 To 8
            If (crc And 1) Then
                crc = (crc \ 2) Xor &HA001&
            Else
                crc = crc \ 2
            End If
        Next j%
    Next i%
    Crc16Modbus& = (crc And &HFFFF&)
End Function

Function U16BE$ (v&)
    ' Convert 0..65535 to 2 bytes, Big Endian
    U16BE$ = Chr$((v& \ 256) And &HFF) + Chr$(v& And &HFF)
End Function

Sub SerialFlush (h%)
    ' Drain all currently buffered RX bytes.
    ' Important before issuing a request, especially if auto-report mode is enabled.
    While Loc(h%) > 0
        Get #h%, , Byte1
    Wend
End Sub

Sub SerialWrite (h%, s$)
    ' Write raw bytes to COM via PUT.
    Dim i%
    For i% = 1 To Len(s$)
        Byte1 = Mid$(s$, i%, 1)
        Put #h%, , Byte1
    Next i%
End Sub

Function SerialReadFixed$ (h%, wantLen%, timeoutSec!)
    ' Read exactly wantLen bytes or stop on timeout.
    ' This is the simplest approach when response length is known.
    Dim t0!, r$
    r$ = ""
    t0! = Timer

    Do While Len(r$) < wantLen%
        If Loc(h%) > 0 Then
            Get #h%, , Byte1
            r$ = r$ + Byte1
        Else
            If (Timer - t0!) >= timeoutSec! Then Exit Do
        End If
    Loop

    SerialReadFixed$ = r$
End Function

Function ModbusReadRegs$ (slave%, reg&, count%)
    ' Build MODBUS RTU frame: [slave][03][regHi][regLo][cntHi][cntLo][crcLo][crcHi]
    Dim req$, crc&
    req$ = Chr$(slave% And &HFF) + Chr$(&H03) + U16BE$(reg&) + U16BE$(count%)
    crc& = Crc16Modbus&(req$)
    req$ = req$ + Chr$(crc& And &HFF) + Chr$((crc& \ 256) And &HFF) ' CRC = low, high
    ModbusReadRegs$ = req$
End Function

Function ModbusWriteSingle$ (slave%, reg&, value&)
    ' Build MODBUS RTU frame: [slave][06][regHi][regLo][valHi][valLo][crcLo][crcHi]
    Dim req$, crc&
    req$ = Chr$(slave% And &HFF) + Chr$(&H06) + U16BE$(reg&) + U16BE$(value&)
    crc& = Crc16Modbus&(req$)
    req$ = req$ + Chr$(crc& And &HFF) + Chr$((crc& \ 256) And &HFF)
    ModbusWriteSingle$ = req$
End Function

Function ModbusCheckCrc% (frame$)
    ' Verify CRC for a complete MODBUS RTU frame.
    ' CRC is last 2 bytes: [crcLo][crcHi]
    Dim n%, crcRx&, crcCalc&
    n% = Len(frame$)
    If n% < 4 Then
        ModbusCheckCrc% = 0
        Exit Function
    End If

    crcRx& = Asc(frame$, n% - 1) + 256& * Asc(frame$, n%)
    crcCalc& = Crc16Modbus&(Left$(frame$, n% - 2))
    ModbusCheckCrc% = (crcRx& = crcCalc&)
End Function

Function S16FromBE% (hi%, lo%)
    ' Convert 2 bytes (big-endian) to signed 16-bit integer (two's complement).
    Dim v%
    v% = (hi% And &HFF) * 256 + (lo% And &HFF)
    If v% >= 32768 Then v% = v% - 65536
    S16FromBE% = v%
End Function

Sub DumpHex (caption$, s$)
    ' Print a byte string as hex pairs for debugging and forum paste.
    Dim i%, out$
    out$ = ""
    For i% = 1 To Len(s$)
        out$ = out$ + Right$("0" + Hex$(Asc(s$, i%)), 2) + " "
    Next i%
    Print caption$; ": "; out$
End Sub

Sorry, had to finish some other stuff first. I'm on it now. Let me know if it's working or reporting any issues. We'll need to debug remotely as I have no way of testing it locally.


Reply


Messages In This Thread
RE: Has anybody experience with MODBUS to read temp sensors? - by Petr - 01-02-2026, 09:21 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
Question How to read Google Calendar events? Ikerkaz 10 1,882 07-10-2023, 11:06 AM
Last Post: Ultraman
  SHELL creates unicode file, can't read correctly with LINE INPUT thesolarcode 3 1,248 05-06-2023, 09:41 PM
Last Post: thesolarcode
  Read / Allocate Cores dISP 10 2,227 04-25-2023, 12:23 PM
Last Post: dISP
  Can images be read from a file into an array? PhilOfPerth 11 2,556 02-17-2023, 03:31 AM
Last Post: TerryRitchie

Forum Jump:


Users browsing this thread: 1 Guest(s)