01-10-2026, 06:24 PM
Hi, Rudy M, try this:
please write me back what program wrote about RX raw addr: and RX raw temp.
Then try this version - its newerest.
Attention: "RX and TX LEDs are on" does not mean that the module is responding with the correct MODBUS data. With many RS-485 dongles, the RX LED flashes even when the transmitter is on (there is activity on the bus and the LED interprets it as RX). The only real proof is that the program prints a non-zero RX frame and passes the CRC.
Leave dev$ at /dev/ttyUSB0 only if it really exists. Better is /dev/serial/by-id/....
Run the program and send back:
full text of the dump (mainly RX addr: and RX temps
, output from the commands that the program prints in the top debug block, confirmation of VIN power.
Once we see the first valid RX addr or RX temps (and it passes the CRC), it's solved and we can continue to temperatures reading.
Code: (Select All)
OPTION _EXPLICIT
' PTA8D08 MODBUS RTU - Linux Mint - QB64PE
'
' IMPORTANT:
' - When opening /dev/ttyACM0 (or ttyUSB0) as a file, DO NOT use LOC()/GET
' to decide if data is available. LOC() is not a RX-buffer indicator for files. :contentReference[oaicite:5]{index=5}
' - Use stty to set non-blocking-ish reads (min 0 time 1) and read via INPUT$.
'
' What this program does:
' 1) stty config: 9600 8N1 raw, no flow control, clocal, cread, min 0 time 1
' 2) TX: broadcast read 0x00FD (address/DIP) - protocol gives example FF 03 00 FD 00 01 00 24 :contentReference[oaicite:6]{index=6}
' 3) RX: dump any received bytes + try to extract a valid MODBUS frame by CRC
' 4) TX: read CH0 temperature (0x0000, count=1) - protocol example exists :contentReference[oaicite:7]{index=7}
'
' SEND FOR TUNING:
' - Which device path you used (prefer /dev/serial/by-id/...)
' - Confirm module is powered DC 8..30V on VIN/GND :contentReference[oaicite:8]{index=8}
' - Whether swapping A/B changes anything
' - The printed TX/RX hex dumps below
'===========================================================
DECLARE FUNCTION Crc16Modbus& (s$)
DECLARE FUNCTION U16BE$ (v&)
DECLARE FUNCTION HexDump$ (s$)
DECLARE FUNCTION ModbusBuildRead$ (slave%, reg&, count%)
DECLARE FUNCTION ModbusCheckCrc% (frame$)
DECLARE FUNCTION TryExtractFrame03$ (buf$, expectedSlave%)
DECLARE FUNCTION GetU16BE& (s$, position%)
DECLARE FUNCTION GetS16BE% (s$, position%)
DECLARE SUB SerialWrite (h%, s$)
DECLARE FUNCTION SerialReadSome$ (h%, timeoutSec!)
DECLARE SUB SleepMs (ms!)
DIM SHARED Byte1 AS STRING * 1
'========================
' MAIN (must be first)
'========================
DIM dev$ : dev$ = "/dev/ttyACM0" ' your current device; /dev/ttyUSB0 is also common
DIM showFrames% : showFrames% = -1
DIM timeout! : timeout! = 1.0
DIM h% : h% = FREEFILE
DIM cmd$
DIM req$, rxRaw$, frame$
DIM slave% : slave% = 1
DIM dipOrAddr&
DIM rawTemp%
DIM tempC!
' 1) Configure port using stty
' - clocal: ignore modem control lines (important on many USB serial devices)
' - cread : enable receiver
' - min 0 time 1: read returns quickly even if no bytes are available (tenths of seconds)
cmd$ = "stty -F " + dev$ + " 9600 cs8 -cstopb -parenb -ixon -ixoff -crtscts raw -echo clocal cread min 0 time 1"
SHELL cmd$
' 2) Open the device as a binary file (byte-accurate)
OPEN dev$ FOR BINARY AS #h% LEN = 1
PRINT "Opened "; dev$; " (USB-Serial/RS485). ESC exits."
PRINT
'------------------------------------------------------------
' STEP A: Broadcast read 0x00FD (address / DIP settings)
' Protocol example: FF 03 00 FD 00 01 00 24 :contentReference[oaicite:9]{index=9}
'------------------------------------------------------------
req$ = ModbusBuildRead$(255, &H00FD, 1)
IF showFrames% THEN PRINT "TX addr (FF 00FD): "; HexDump$(req$)
SerialWrite h%, req$
rxRaw$ = SerialReadSome$(h%, timeout!)
IF showFrames% THEN PRINT "RX raw addr: "; HexDump$(rxRaw$)
frame$ = TryExtractFrame03$(rxRaw$, 0) ' 0 = accept any slave in reply
IF LEN(frame$) = 0 THEN
PRINT
PRINT "No valid MODBUS frame received for 00FD."
PRINT "This is NOT a software victory yet."
PRINT "Focus on: module power (VIN DC 8..30V), A/B swap, GND reference, correct device path." ' :contentReference[oaicite:10]{index=10}
PRINT
ELSE
PRINT "RX frame addr: "; HexDump$(frame$)
dipOrAddr& = GetU16BE&(frame$, 4)
slave% = ASC(frame$, 1) AND &HFF
PRINT "Station address from reply ="; slave%; " (00FD raw data = &H"; HEX$(dipOrAddr&); ")"
PRINT
END IF
'------------------------------------------------------------
' STEP B: Read CH0 temperature only (simpler than 8 regs while debugging)
' Protocol example for CH0 exists :contentReference[oaicite:11]{index=11}
'------------------------------------------------------------
req$ = ModbusBuildRead$(slave%, &H0000, 1)
IF showFrames% THEN PRINT "TX temp CH0: "; HexDump$(req$)
SerialWrite h%, req$
rxRaw$ = SerialReadSome$(h%, timeout!)
IF showFrames% THEN PRINT "RX raw temp: "; HexDump$(rxRaw$)
frame$ = TryExtractFrame03$(rxRaw$, slave%)
IF LEN(frame$) = 0 THEN
PRINT
PRINT "No valid MODBUS temp frame received."
PRINT "If RX LED never blinks, it is wiring/power/address, not register mapping."
PRINT
ELSE
PRINT "RX frame temp: "; HexDump$(frame$)
' frame: [addr][03][02][dataHi][dataLo][crcLo][crcHi]
rawTemp% = GetS16BE%(frame$, 4)
tempC! = rawTemp% / 10!
PRINT "CH0 temperature ="; tempC!; "C"
PRINT
PRINT "If this works, switch to reading 8 regs: reg=0000 count=8 and parse 16 bytes."
END IF
PRINT
PRINT "Press ESC to exit."
DO
IF INKEY$ = CHR$(27) THEN EXIT DO
SleepMs 50
LOOP
CLOSE #h%
END
'========================
' SUBs / FUNCTIONs (must be last)
'========================
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 SerialWrite (h%, s$)
DIM i%
FOR i% = 1 TO LEN(s$)
Byte1 = MID$(s$, i%, 1)
PUT #h%, , Byte1
NEXT i%
END SUB
FUNCTION SerialReadSome$ (h%, timeoutSec!)
' Read whatever arrives within timeoutSec!
' Uses INPUT$(1,#) repeatedly; with stty "min 0 time 1" it should not block forever.
DIM t0!, b$, r$
t0! = TIMER
r$ = ""
DO
b$ = INPUT$(1, #h%)
IF LEN(b$) > 0 THEN
r$ = r$ + b$
ELSE
SleepMs 10
END IF
IF (TIMER - t0!) >= timeoutSec! THEN EXIT DO
IF LEN(r$) > 512 THEN EXIT DO
LOOP
SerialReadSome$ = r$
END FUNCTION
FUNCTION TryExtractFrame03$ (buf$, expectedSlave%)
' Scan buffer for a valid MODBUS function 03 frame by CRC.
' expectedSlave% = 0 => accept any slave
DIM i%, n%, addr%, fn%, bc%, frameLen%
DIM candidate$
n% = LEN(buf$)
TryExtractFrame03$ = ""
IF n% < 5 THEN EXIT FUNCTION
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
TryExtractFrame03$ = candidate$
EXIT FUNCTION
END IF
END IF
NextI:
NEXT i%
END FUNCTION
SUB SleepMs (ms!)
' crude, portable sleep (milliseconds)
_DELAY ms! / 1000!
END SUB
please write me back what program wrote about RX raw addr: and RX raw temp.
Then try this version - its newerest.
Attention: "RX and TX LEDs are on" does not mean that the module is responding with the correct MODBUS data. With many RS-485 dongles, the RX LED flashes even when the transmitter is on (there is activity on the bus and the LED interprets it as RX). The only real proof is that the program prints a non-zero RX frame and passes the CRC.
Code: (Select All)
Option _Explicit
' PTA8D08 (8x PT100) - MODBUS RTU over RS-485 - Linux Mint
' QB64PE Linux-friendly implementation
'
' WHY THIS VERSION EXISTS:
' On Linux when opening /dev/ttyUSBx or /dev/ttyACMx as a file,
' using LOC(#)/GET to "poll RX bytes" is often unreliable.
' This version uses:
' - stty to configure serial port (9600 8N1, raw, no flow control)
' - non-blocking-ish behavior (min 0 time 1) so reads return quickly
' - INPUT$(1,#) to read bytes into a buffer
' - CRC-based extraction of MODBUS function 03 frames from that buffer
'
' DEVICE DEFAULTS (per vendor docs):
' - 9600, N, 8, 1
' - Function 03 = read holding registers
' - Temperatures: regs 0x0000..0x0007, signed, unit 0.1°C
' - Address/DIP info: read reg 0x00FD (broadcast request allowed)
'
' IMPORTANT HARDWARE NOTES:
' - The PTA8D08 board must be powered via VIN+GND (8..30V DC). RS-485 does NOT power it.
' - A/B labeling is inconsistent across vendors; if no reply, swap A and B.
' - Connecting adapter GND to module GND often improves reliability (reference).
Dim Shared Byte1 As String * 1
'User-configurable settings (edit these)
Dim dev$: dev$ = "/dev/ttyUSB0"
' TIP: Prefer a stable path like /dev/serial/by-id/.... if available.
Dim pollHz!: pollHz! = 2
Dim showFrames%: showFrames% = -1
Dim timeout!: timeout! = 1.0
Dim h%: h% = FreeFile
Dim cmd$
Dim req$, resp$
Dim slave%: slave% = 1
Dim i%
Dim rawS%
Dim t!
Dim dipRaw&
Dim key$
' What to send back for debugging
Print "============================================================"
Print "PTA8D08 MODBUS RTU tester (Linux, QB64PE)"
Print
Print "DEBUG INFO TO SEND BACK (copy/paste output):"
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) Confirm module power: VIN = 8..30V DC (and wiring photo if possible)."
Print "6) Confirm whether swapping A/B changes behavior."
Print "7) Paste TX/RX hex dumps printed by this program."
Print "============================================================"
Print
' Configure serial port via stty
'----------------------------------------------------------------
' Explanation:
' - raw: disable canonical processing, special chars, etc.
' - -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: read returns quickly even if no bytes are available (0.1s units)
cmd$ = "stty -F " + dev$ + " 9600 cs8 -cstopb -parenb -ixon -ixoff -crtscts raw -echo clocal cread min 0 time 1"
Shell cmd$
' Open the device as a binary file
' LEN=1 ensures PUT/GET operate on single bytes correctly.
Open dev$ For Binary As #h% Len = 1
Print "Opened "; dev$; " (USB-RS485). Press ESC to exit."
Print
' STEP 0: Broadcast read 0x00FD to discover station address/DIP.
' Request (example from docs): FF 03 00 FD 00 01 00 24
' We build it dynamically using CRC.
'
' expectedSlave = 0 means: accept any slave address in the reply.
SerialFlush 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 "This usually means: no real MODBUS reply is arriving."
Print "Focus on: VIN power, A/B wiring swap, GND reference, correct device path, permissions."
Print "Continuing with slave address ="; slave%; "(default)."
Print
End If
' MAIN POLL LOOP: Read 8 temperatures (0x0000..0x0007) repeatedly
' Response for 8 regs should contain:
' [addr][03][16][16 data bytes][crcLo][crcHi]
Do
key$ = InKey$
If key$ = Chr$(27) Then Exit Do
SerialFlush 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: hardware/power/wiring/address is wrong."
Print "If RX LED blinks but no frame: still likely wiring/power, or your adapter echoes/garbles data."
Print
_Limit pollHz!
GoTo ContinueLoop
End If
If ModbusCheckCrc%(resp$) = 0 Then
Print "CRC FAIL (temps frame). Data is corrupted or not a valid MODBUS RTU frame."
Print
_Limit pollHz!
GoTo ContinueLoop
End If
' Basic header checks
If Asc(resp$, 2) <> &H03 Then
Print "Unexpected function code:"; Asc(resp$, 2); " (expected 03)."
Print "NOTE: If you see 0x83, it is an exception response."
Print
_Limit pollHz!
GoTo ContinueLoop
End If
If Asc(resp$, 3) <> 16 Then
Print "Unexpected bytecount:"; Asc(resp$, 3); " (expected 16 for 8 registers)."
Print
_Limit pollHz!
GoTo ContinueLoop
End If
' Decode and print temperatures
' Each channel is a signed 16-bit big-endian integer in 0.1°C.
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$)
' MODBUS RTU CRC16:
' - init 0xFFFF
' - polynomial 0xA001 (reflected)
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&)
' Convert 0..65535 into 2 bytes big-endian.
U16BE$ = Chr$((v& \ 256) And &HFF) + Chr$(v& And &HFF)
End Function
Function HexDump$ (s$)
' Render bytes as hex pairs "01 03 00 ..."
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%)
' Build MODBUS RTU request 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)
ModbusBuildRead$ = req$
End Function
Function ModbusCheckCrc% (frame$)
' Verify CRC for a complete MODBUS RTU frame.
' CRC is stored as [crcLo][crcHi] in the last two bytes.
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%)
' Read unsigned 16-bit big-endian from s$ at 1-based 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%)
' Read signed 16-bit big-endian (two's complement).
Dim v&
v& = GetU16BE&(s$, position%)
If v& >= 32768& Then v& = v& - 65536&
GetS16BE% = v&
End Function
Sub SleepMs (ms!)
' Simple portable sleep using QB64 _DELAY (seconds).
_Delay ms! / 1000!
End Sub
Sub SerialWrite (h%, s$)
' Write raw bytes one-by-one using PUT (LEN=1 record).
Dim i%
For i% = 1 To Len(s$)
Byte1 = Mid$(s$, i%, 1)
Put #h%, , Byte1
Next i%
End Sub
Sub SerialFlush (h%)
' Linux-friendly RX flush:
' Read and discard bytes for a short time window, without LOC().
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 the window slightly.
t0! = Timer
End If
Loop
End Sub
Function SerialReadFrame03$ (h%, expectedSlave%, timeoutSec!)
' Read and extract a valid MODBUS function 03 response frame.
'
' Method:
' - Accumulate bytes with INPUT$(1,#) until timeout
' - Scan buffer for: [addr][03][bytecount][...data...][crcLo][crcHi]
' - Verify CRC; return the first valid frame found
'
' expectedSlave%:
' - 0 => accept any slave address (useful for broadcast queries)
' - else => only accept frames from that slave
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% ' addr+fn+bc + data(bc) + crc(2)
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
Leave dev$ at /dev/ttyUSB0 only if it really exists. Better is /dev/serial/by-id/....
Run the program and send back:
full text of the dump (mainly RX addr: and RX temps
, output from the commands that the program prints in the top debug block, confirmation of VIN power.Once we see the first valid RX addr or RX temps (and it passes the CRC), it's solved and we can continue to temperatures reading.

