Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Has anybody experience with MODBUS to read temp sensors?
#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


Messages In This Thread
RE: Has anybody experience with MODBUS to read temp sensors? - by Petr - 01-06-2026, 07:44 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)