02-23-2026, 04:31 PM
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:
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
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

