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

SerialFlush reads via INPUT$(1, #h%) in a situation where there is often nothing flowing on the line yet.
If for some reason stty ... min 0 time 1 is not applied on that particular mini PC (or the port is reset back after OPEN), then INPUT$ can be blocking and waits for the first byte "forever".
As soon as you skip the first SerialFlush, the program immediately sends a request (TX), the sensor responds (RX), so INPUT$ already has something to read and the program runs.

So the answer to your question:
Yes, you can leave the first SerialFlush h% call disabled, if it is already reading stably for you now.

Try this version:

Code: (Select All)

Option _Explicit


' PTA8D08 (8x PT100) - MODBUS RTU over RS-485 - Linux Mint
' QB64PE Linux-friendly implementation
'
' Key design goals:
' - Reliable on Linux when opening /dev/ttyUSBx or /dev/ttyACMx
' - Avoid LOC()/GET() polling (often unreliable for /dev/tty* as files)
' - Use stty to force "raw" serial mode + non-blocking-ish reads
' - Read bytes via INPUT$(1,#) into a buffer
' - Extract MODBUS function 03 frames by CRC check
'
' IMPORTANT FIX (for "hangs in SerialFlush" on some systems):
' - Apply stty BEFORE open AND AGAIN AFTER open.
'  Some drivers reset or override tty settings at open-time.
'
' Hardware notes:
' - Board must be powered via VIN+GND (8..30V DC). RS-485 does not power it.
' - A/B labeling is not consistent; if no reply, swap A and B.
' - Connecting adapter GND to module GND can improve stability (reference).
'

Dim Shared Byte1 As String * 1

'===========================================================
' MAIN PROGRAM (must be first)
'===========================================================

'User-configurable settings
Dim dev$: dev$ = "/dev/ttyUSB0"
' TIP: Prefer /dev/serial/by-id/... if available (stable naming across reboots).
' NOTE: On some adapters it can be /dev/ttyACM0 instead.

Dim pollHz!: pollHz! = 2
Dim showFrames%: showFrames% = -1
Dim timeout!: timeout! = 1.0

'--- Variables (no DIM inside loops)
Dim h%: h% = FreeFile
Dim sttyCmd$
Dim req$, resp$
Dim slave%: slave% = 1
Dim i%
Dim rawS%
Dim t!
Dim dipRaw&
Dim key$


' Print what to send back for debugging
Print "============================================================"
Print "PTA8D08 MODBUS RTU tester (Linux, QB64PE)"
Print
Print "If you need help debugging, send back:"
Print "1) ls -l /dev/serial/by-id/"
Print "2) ls -l /dev/ttyUSB* /dev/ttyACM*"
Print "3) groups"
Print "4) lsof "; dev$
Print "5) stty -F "; dev$; " -a"
Print "6) Confirm VIN power 8..30V DC and whether A/B swap was needed."
Print "7) Paste TX/RX hex dumps printed by this program."
Print "============================================================"
Print


' Configure serial port via stty
'----------------------------------------------------------------------------------------
' Explanation:
' - raw:            disable canonical processing
' - -echo:          no echo
' - -ixon -ixoff:  disable software flow control
' - -crtscts:      disable hardware flow control
' - clocal:        ignore modem control lines (important on USB serial)
' - cread:          enable receiver
' - min 0 time 1:  reads return quickly even if no bytes are available (time in 0.1s units)
sttyCmd$ = "stty -F " + dev$ + " 9600 cs8 -cstopb -parenb -ixon -ixoff -crtscts raw -echo clocal cread min 0 time 1"
Shell sttyCmd$


' Open serial device
' LEN=1 ensures byte-accurate PUT/INPUT$ behavior.
'--------------------------------------------------------------------------------------------
Open dev$ For Binary As #h% Len = 1

' CRITICAL FIX: apply stty again AFTER OPEN (prevents INPUT$ blocking on some systems)
Shell sttyCmd$

Print "Opened "; dev$; " (USB-RS485). Press ESC to exit."
Print


' STEP 0: Broadcast read 0x00FD to discover station address/DIP.
' Vendor example request: FF 03 00 FD 00 01 00 24
' We build it dynamically by CRC.
'
' expectedSlave = 0 => accept any slave address in the reply.

SerialFlushSoft h%

req$ = ModbusBuildRead$(255, &H00FD, 1)
If showFrames% Then Print "TX addr (FF 00FD): "; HexDump$(req$)
SerialWrite h%, req$

resp$ = SerialReadFrame03$(h%, 0, timeout!)
If showFrames% Then Print "RX addr:          "; HexDump$(resp$)

If Len(resp$) > 0 And ModbusCheckCrc%(resp$) Then
    slave% = Asc(resp$, 1) And &HFF
    dipRaw& = GetU16BE&(resp$, 4)
    Print "Detected station address from reply ="; slave%
    Print "00FD raw register value (hex) = &H"; Hex$(dipRaw&)
    Print
Else
    Print "Could not read station address (00FD)."
    Print "If temperatures still read fine later, ignore this. Otherwise check power/wiring/device path."
    Print "Continuing with slave address ="; slave%; "(default)."
    Print
End If


' MAIN LOOP: Read 8 temperatures (0x0000..0x0007) repeatedly
' Response should look like:
' [addr][03][16][16 data bytes][crcLo][crcHi]

Do
    key$ = InKey$
    If key$ = Chr$(27) Then Exit Do

    SerialFlushSoft h%

    req$ = ModbusBuildRead$(slave%, &H0000, 8)
    If showFrames% Then Print "TX temps:          "; HexDump$(req$)
    SerialWrite h%, req$

    resp$ = SerialReadFrame03$(h%, slave%, timeout!)
    If showFrames% Then Print "RX temps:          "; HexDump$(resp$)

    If Len(resp$) = 0 Then
        Print "No MODBUS reply (temps)."
        Print "If RX LED never blinks: power/wiring/A-B swap/device path."
        Print
        _Limit pollHz!
        GoTo ContinueLoop
    End If

    If ModbusCheckCrc%(resp$) = 0 Then
        Print "CRC FAIL (temps frame)."
        Print
        _Limit pollHz!
        GoTo ContinueLoop
    End If

    If Asc(resp$, 2) <> &H03 Then
        Print "Unexpected function code:"; Asc(resp$, 2); " (expected 03)."
        Print "If you see 0x83, it is a MODBUS exception response."
        Print
        _Limit pollHz!
        GoTo ContinueLoop
    End If

    If Asc(resp$, 3) <> 16 Then
        Print "Unexpected bytecount:"; Asc(resp$, 3); " (expected 16)."
        Print
        _Limit pollHz!
        GoTo ContinueLoop
    End If

    For i% = 0 To 7
        rawS% = GetS16BE%(resp$, 4 + i% * 2)
        t! = rawS% / 10!
        Print Using "CH# = ####.# C"; i%; t!
    Next i%
    Print "----------------------------"
    Print

    ContinueLoop:
    _Limit pollHz!
Loop

Close #h%
Print "Done."
End


Function Crc16Modbus& (s$)
    Dim crc&, i%, j%
    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&)
    U16BE$ = Chr$((v& \ 256) And &HFF) + Chr$(v& And &HFF)
End Function

Function HexDump$ (s$)
    Dim i%, out$
    out$ = ""
    For i% = 1 To Len(s$)
        out$ = out$ + Right$("0" + Hex$(Asc(s$, i%)), 2) + " "
    Next i%
    HexDump$ = out$
End Function

Function ModbusBuildRead$ (slave%, reg&, count%)
    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)
    ModbusBuildRead$ = req$
End Function

Function ModbusCheckCrc% (frame$)
    Dim n%, crcRx&, crcCalc&
    n% = Len(frame$)
    If n% < 5 Then ModbusCheckCrc% = 0: Exit Function
    crcRx& = Asc(frame$, n% - 1) + 256& * Asc(frame$, n%)
    crcCalc& = Crc16Modbus&(Left$(frame$, n% - 2))
    ModbusCheckCrc% = (crcRx& = crcCalc&)
End Function

Function GetU16BE& (s$, position%)
    Dim hi&, lo&
    hi& = Asc(s$, position%) And &HFF
    lo& = Asc(s$, position% + 1) And &HFF
    GetU16BE& = hi& * 256& + lo&
End Function

Function GetS16BE% (s$, position%)
    Dim v&
    v& = GetU16BE&(s$, position%)
    If v& >= 32768& Then v& = v& - 65536&
    GetS16BE% = v&
End Function

Sub SleepMs (ms!)
    _Delay ms! / 1000!
End Sub

Sub SerialWrite (h%, s$)
    Dim i%
    For i% = 1 To Len(s$)
        Byte1 = Mid$(s$, i%, 1)
        Put #h%, , Byte1
    Next i%
End Sub

Sub SerialFlushSoft (h%)
    ' Soft flush with a bounded time window.
    ' We do NOT use LOC() here. We just read/discard for a short time.
    '
    ' If INPUT$ is configured correctly (stty min 0 time 1), this should never block forever.
    ' If it STILL blocks on a specific machine, it almost always means tty settings did not apply.
    Dim t0!, b$
    t0! = Timer

    Do
        b$ = Input$(1, # h%)
        If Len(b$) = 0 Then
            If (Timer - t0!) > 0.2 Then Exit Do
            SleepMs 5
        Else
            ' Keep flushing while bytes arrive; extend window slightly.
            t0! = Timer
        End If
    Loop
End Sub

Function SerialReadFrame03$ (h%, expectedSlave%, timeoutSec!)
    ' Accumulate bytes until timeout and extract a valid MODBUS 03 response frame by CRC.
    ' Format searched:
    ' [addr][03][bytecount][...data...][crcLo][crcHi]
    '
    ' expectedSlave%:
    ' - 0 => accept any address (useful for broadcast queries)
    ' - else => accept only that slave address
    Dim t0!, buf$, b$
    Dim i%, n%, addr%, fn%, bc%, frameLen%
    Dim candidate$

    t0! = Timer
    buf$ = ""
    SerialReadFrame03$ = ""

    Do
        b$ = Input$(1, # h%)
        If Len(b$) > 0 Then
            buf$ = buf$ + b$
            If Len(buf$) > 512 Then buf$ = Right$(buf$, 512)
        Else
            SleepMs 2
        End If

        n% = Len(buf$)
        If n% >= 5 Then
            For i% = 1 To n% - 4
                addr% = Asc(buf$, i%) And &HFF

                If expectedSlave% <> 0 Then
                    If addr% <> (expectedSlave% And &HFF) Then GoTo NextI
                End If

                fn% = Asc(buf$, i% + 1) And &HFF
                If fn% <> &H03 Then GoTo NextI

                bc% = Asc(buf$, i% + 2) And &HFF
                frameLen% = 5 + bc%

                If (i% + frameLen% - 1) <= n% Then
                    candidate$ = Mid$(buf$, i%, frameLen%)
                    If ModbusCheckCrc%(candidate$) Then
                        SerialReadFrame03$ = candidate$
                        Exit Function
                    End If
                End If

                NextI:
            Next i%
        End If

        If (Timer - t0!) >= timeoutSec! Then Exit Do
    Loop
End Function

If that fails, then the quickest way is to send me the output:

stty -F /dev/ttyUSB0 -a (or your actual dev$)

and those debug dumps + first TX/RX dumps


Reply


Messages In This Thread
RE: Has anybody experience with MODBUS to read temp sensors? - by Petr - 02-23-2026, 04:31 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,231 04-25-2023, 12:23 PM
Last Post: dISP
  Can images be read from a file into an array? PhilOfPerth 11 2,560 02-17-2023, 03:31 AM
Last Post: TerryRitchie

Forum Jump:


Users browsing this thread: 1 Guest(s)