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

I'm trying to switch ON and OFF the 4 relays on a 4-relays-USB card. (One of the days I publisch how I did it).
I also have the intention to read out ds18b20 or PTC100 sensors via USB.
Reason:
You can write all software on Your PC and use Identical the same software on a RaspberryPI (via the USB ports).
Or better, use an old PC to do the job in place of the RaspberryPI

But for the relays card it seems more complicated:
I bought a PTA8D08 module (from Eletechsup) from Ali-express, which can read eight PTC temperature sensors.

https://nl.aliexpress.com/item/100500979...pt=glo2nld

This module has also screw-connectors B- A+ GND and Vin (RS485...)

But the problem for this case, is I need to program with the MODBUS RTU protocol.

Do any of you have experience or knowledge with this protocol and how to implement it in QB64? 
Any advice is welcome. 
Regards, Rudy M
Reply
#2
Can you share the manual for this here? I don't promise anything, but I think it will be treated like a regular COM port, even though it goes through a USB adapter, if I understand correctly. Then you need to set the COM port number the same as in the program. Give me the manual, I don't promise anything, but I'll try.


Reply
#3
(12-30-2025, 01:01 PM)Petr Wrote: Can you share the manual for this here? I don't promise anything, but I think it will be treated like a regular COM port, even though it goes through a USB adapter, if I understand correctly. Then you need to set the COM port number the same as in the program. Give me the manual, I don't promise anything, but I'll try.

Thanks Petr for Your intention.

This is from the Ali website:
Parameters:
Working voltage: DC 8~30V
Working current: 14-25MA
MODBUS RTU protocol, 03 read command, 06 or 16 write command.
Serial baud rate: 9600 (default), N, 8, 1
By modifying the 485 address, 247 modules can be cascaded at most (use R485 repeaters for more than 16)
Readable temperature and PT100 resistance value
Adaptive sensor: PT100 3-wire or 2-wire sensor
Temperature measurement range: Type A - 40 ℃ to+220 ℃; Type B - 40 ℃ to+500 ℃.
It is recommended to select a version with a smaller range within the range that meets the measured value.
Temperature measurement accuracy: 1%

For MODBUS RTU protocol, please refer to: PTA8D08 8CH PT100 RS485 sensor protocol
And so I found following link:
https://485io.com/eletechsup/PTA8D08-1.rar

Rudy M
Reply
#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
#5
No need to apologize, Petr, I'm very grateful you've taken on this difficult piece of programming. 
I'll try to test it out today and get back to you as soon as possible. 
Thanks in advance for your efforts!
Rudy M
Reply
#6
Hi Petr,

Hi Petr, 

Due to the holidays, there's been a delay in the delivery of an ordered USB-RS485 adapter. 
It's a bidirectional type with a CG343G chip. The baud rate table already shows the standard "9600" rate. 
I expect it to arrive early next week. Big Grin  
I'm trying to keep the connections between the screw adapter and the PT100 card as short as possible.

I've run your listing (without the adapter and sensor card) in QB64, and I'm not getting any errors. 

For  convenience, I've already translated the English comments into Dutch. 
(If any Dutch-speaking forum members would like a copy, please send me a private message with your email address, and I'll email you the listing with the Dutch commands.) 
Meanwhile, I'm mounting the sensor card on a board and connecting it to the four PT100 sensors and a 12-volt power supply. 
As soon as I've tested the USB adapter, I'll post my findings here again. 

Greetings, Rudy M


First part of my Configuration info:
PC: Linux Mint 22 (Wilma) with XFCE as desktop
qb64pe version: lnx-4.3.0
PTA8D08 card:
DIP switch 1 = ON 
DIP switch (2 to 6) = OFF

USB to RS485 connector:
Industrial USB TO RS485 Bidirectional Converter, Onboard original CH343G, Multi-Protection Circuits
USB TO RS485 (B) baudrate support: 50, 75, 100, 110, 134.5, 150, 300,
600, 900, 1200, 1800, 2400, 3600, 4800, 9600, 14400, 19200, 28800,
33600, 38400, 56000, 57600, 76800, 115200, 128000, 153600, 230400,
256000, 307200, 460800, 921600, 1M, 1.5M, 2M, and 3M.

Number of PT100 sensors I'm going to use: 4
Number1: controls de T in the (beer)boiling vessel
Number2: controls de T in the waterboiler
Number3: controls de T of the outgoing temp. of the plate cooler (to avoid spoiling of water).
Number4: Reserve-sensor (I put him in 'open air')
-------------------------------------------------------------------------------------------------------------------------------------
Reply
#7
Alright, I’ll wait for more information. Measuring the temperature in the beer brewing kettle? That’s a highly likeable affair!  Big Grin


Reply
#8
(01-04-2026, 11:29 AM)Petr Wrote: Alright, I’ll wait for more information. Measuring the temperature in the beer brewing kettle? That’s a highly likeable affair!  Big Grin
Hi Petr,
The USB-RS485 bidirectional adaptor is arrived, and I'll connected all the hardware together.
- Qb64 report no errors in the listing, so that stays good news.
- On the adaptor only the leds of PWR and TXD are ON, the led RXD stays OFF

Printscreen of the result:
[Image: Info-for-Petr.png]

Just arrived: USB-RS485 adaptor from another supplier, I'm going to test it also.

Greatings,
Rudy
Reply
#9
1) Tested the other USB-RS485 adaptor.
2) Seconde test: Change also DIP schitch on the sensorboard to OFF,
But result stays the same.
Greatings,
Rudy
Reply
#10
Oh. Linux Mint. Hmmmm. I took AI to help me with this.


1) First, check that Mint sees the adapter and what its name is
Find out the device after connecting

In the terminal:

dmesg | tail -n 50 (after connecting the adapter)

or: ls -l /dev/ttyUSB* /dev/ttyACM*

For a stable name, it is best to use a symlink:

ls -l /dev/serial/by-id/

The number ttyUSB0/1/... may change after a reboot or a connection, the by-id is usually stable.



2) Permissions: without the dialout group you can't get anywhere

Typical error on Mint/Ubuntu: the program doesn't have permission to open /dev/ttyUSB0.

Check:

ls -l /dev/ttyUSB0 (or specific port)

If the group is dialout, add the user:

sudo usermod -a -G dialout $USER

Then log out and log in (or reboot) for the group to really take effect.


3) Verify that the port is not occupied by anything else

If you are running screen, minicom, IDE, Modbus Poll in Wine, whatever... QB64PE is then "reading silent".



4) Quick non-QB64 line test (just a sanity check)

To verify that the port can at least be opened and is set up:

picocom -b 9600 /dev/ttyUSB0

or: screen /dev/ttyUSB0 9600

MODBUS is binary, so don't expect "readable characters". The point is just that you open the port without error and the adapter behaves sensibly.


5) QB64PE on Linux: edit port opening to /dev/ttyUSB0 + set up link via stty

Officially QB64 says it doesn't support OPEN "COMn:..." syntax for Linux, but on Linux with USB-serial converters, in practice it is often used to open /dev/ttyUSB0 directly as a file.

Here you have a ready-made "Linux Mint" skeleton. Two things are important:

I open dev$ (e.g. /dev/ttyUSB0 or /dev/serial/by-id/...)

Before opening, I call stty so that the port has 9600,N,8,1 and soft flow control crap is not enabled


Code: (Select All)


Option _Explicit


' Linux Mint 22 (Wilma) + USB-RS485: recommended approach
'
' Key differences vs Windows:
' - Use /dev/ttyUSB0 or /dev/serial/by-id/... instead of COM1
' - Ensure user has permission (dialout group)
' - Configure port via stty before opening it in QB64PE
'
' SEND FOR TUNING (paste into forum/chat):
' - output of: ls -l /dev/ttyUSB* /dev/serial/by-id/
' - output of: groups
' - output of: ls -l /dev/ttyUSB0  (or your actual device)
' - output of: lsof /dev/ttyUSB0
' - TX/RX hexdumps from this program


Dim Shared Byte1 As String * 1


' --- EDIT THIS: best is /dev/serial/by-id/xxxx (stable), otherwise /dev/ttyUSB0
Dim dev$: dev$ = "/dev/ttyUSB0"

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

Dim h%: h% = FreeFile
Dim req$, resp$
Dim slave%: slave% = 1

Dim i%, rawS%, rawU&, t!, r!
Dim cmd$

' 1) Configure port using stty
'    raw 9600 8N1, no flow control, no echo
cmd$ = "stty -F " + dev$ + " 9600 cs8 -cstopb -parenb -ixon -ixoff -crtscts raw -echo"
Shell cmd$

' 2) Open the device as a binary file
'    LEN=1 ensures byte-accurate GET/PUT behavior (no padded records).
Open dev$ For Binary As #h% Len = 1

Print "Opened "; dev$; " (should be USB-RS485). ESC exits."
Print

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

    ' Read 8 temperatures 0x0000..0x0007
    SerialFlush h%
    req$ = ModbusBuildRead$(slave%, &H0000, 8)
    If showFrames% Then Print "TX temps: "; HexDump$(req$)
    SerialWrite h%, req$

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

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

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

    If Asc(resp$, 2) <> &H03 Then
        Print "Unexpected function code:"; Asc(resp$, 2)
        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%
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

Sub SerialFlush (h%)
    While Loc(h%) > 0
        Get #h%, , Byte1
    Wend
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

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

Function SerialReadFrame$ (h%, expectedSlave%, timeoutSec!)
    ' Minimal frame reader for function 03 only:
    ' Accumulate bytes and try to find a valid CRC frame with:
    ' [addr][03][bytecount][...data...][crcLo][crcHi]
    Dim t0!, buf$, i%, n%, addr%, fn%, bc%, frameLen%
    Dim candidate$
    t0! = Timer
    buf$ = ""

    Do
        While Loc(h%) > 0
            Get #h%, , Byte1
            buf$ = buf$ + Byte1
            If Len(buf$) > 512 Then buf$ = Right$(buf$, 512)
        Wend

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

                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
                        SerialReadFrame$ = candidate$
                        Exit Function
                    End If
                End If

                NextI:
            Next i%
        End If

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

    SerialReadFrame$ = ""
End Function


What is the most likely problem now (based on what you described earlier)

When only TX is lit and RX never even blinks, it almost always falls into one of these categories on Linux:
the program actually opens a different port than the one the adapter is on (that's why I recommend /dev/serial/by-id/...)
you don't have rights to /dev/ttyUSB0 (dialout)
A/B are swapped or the module is not powered (RS485 doesn't power it - it must be VIN)
the port is being held by another process (lsof)
From Mint, send me the literal outputs of these commands:

ls -l /dev/serial/by-id/

ls -l /dev/ttyUSB*

groups

lsof /dev/ttyUSB0 (or the actual port)

And the log from QB64PE: TX temps: and RX temps: lines.

Try also this version:

Code: (Select All)

Option _Explicit


' Linux Mint 22 (Wilma) + USB-RS485: recommended approach
'
' Key differences vs Windows:
' - Use /dev/ttyUSB0 or /dev/serial/by-id/... instead of COM1
' - Ensure user has permission (dialout group)
' - Configure port via stty before opening it in QB64PE
'
' SEND FOR TUNING (paste into forum/chat):
' - output of: ls -l /dev/ttyUSB* /dev/serial/by-id/
' - output of: groups
' - output of: ls -l /dev/ttyUSB0  (or your actual device)
' - output of: lsof /dev/ttyUSB0
' - TX/RX hexdumps from this program


Dim Shared Byte1 As String * 1


' --- EDIT THIS: best is /dev/serial/by-id/xxxx (stable), otherwise /dev/ttyUSB0
Dim dev$: dev$ = "/dev/ttyUSB0"

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

Dim h%: h% = FreeFile
Dim req$, resp$
Dim slave%: slave% = 1

Dim i%, rawS%, rawU&, t!, r!
Dim cmd$

' 1) Configure port using stty
'    raw 9600 8N1, no flow control, no echo
cmd$ = "stty -F " + dev$ + " 9600 cs8 -cstopb -parenb -ixon -ixoff -crtscts raw -echo"
Shell cmd$

' 2) Open the device as a binary file
'    LEN=1 ensures byte-accurate GET/PUT behavior (no padded records).
Open dev$ For Binary As #h% Len = 1

Print "Opened "; dev$; " (should be USB-RS485). ESC exits."
Print

' ------------------------------------------------------------
' STEP 0: Read station address / DIP settings via broadcast read of 0x00FD
' Protocol gives example request: FF 03 00 FD 00 01 00 24
' We build it dynamically (CRC), send it, and accept ANY responding slave.
' ------------------------------------------------------------
SerialFlush h%
req$ = ModbusBuildRead$(255, &H00FD, 1) ' 255 = FF broadcast
If showFrames% Then Print "TX addr (FF 00FD): "; HexDump$(req$)
SerialWrite h%, req$

resp$ = SerialReadFrame$(h%, 0, timeout!) ' expectedSlave=0 => accept any slave in reply
If showFrames% Then Print "RX addr:          "; HexDump$(resp$)

If Len(resp$) > 0 And ModbusCheckCrc%(resp$) Then
    slave% = Asc(resp$, 1) And &HFF
    Print "Detected station address from reply ="; slave%
    Print
Else
    Print "Could not read station address (00FD)."
    Print "If RX LED never blinks: check power (VIN), A/B swap, correct /dev/ttyUSBx, permissions."
    Print
    ' Keep slave% as configured (default 1) and continue anyway
End If

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

    ' Read 8 temperatures 0x0000..0x0007
    SerialFlush h%
    req$ = ModbusBuildRead$(slave%, &H0000, 8)
    If showFrames% Then Print "TX temps: "; HexDump$(req$)
    SerialWrite h%, req$

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

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

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

    If Asc(resp$, 2) <> &H03 Then
        Print "Unexpected function code:"; Asc(resp$, 2)
        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%
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

Sub SerialFlush (h%)
    While Loc(h%) > 0
        Get #h%, , Byte1
    Wend
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

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

Function SerialReadFrame$ (h%, expectedSlave%, timeoutSec!)
    ' Minimal frame reader for function 03 only:
    ' Accumulate bytes and try to find a valid CRC frame with:
    ' [addr][03][bytecount][...data...][crcLo][crcHi]
    Dim t0!, buf$, i%, n%, addr%, fn%, bc%, frameLen%
    Dim candidate$
    t0! = Timer
    buf$ = ""

    Do
        While Loc(h%) > 0
            Get #h%, , Byte1
            buf$ = buf$ + Byte1
            If Len(buf$) > 512 Then buf$ = Right$(buf$, 512)
        Wend

        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
                        SerialReadFrame$ = candidate$
                        Exit Function
                    End If
                End If

                NextI:
            Next i%
        End If

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

    SerialReadFrame$ = ""
End Function


Reply


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

Forum Jump:


Users browsing this thread: 3 Guest(s)