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

To be able to use Petr's program in my own programs, I performed several tests with the QB64 "on timer" statement. 
However, it made the program quite unreadable. 
Therefore, I created a SHARED ARRAY Temp!() to store the temperatures of multiple sensors, and two subs.
Because the T-info are "shared," it can be used in all sub-functions of the QB64. 

The first sub: "SUB PT100Read start%" The second sub: "SUB PT100Stop" 

To start the module only once without having to create a separate SUB, I added a simple IF THEN ELSE END IF sequence. This if then is controlled by the variable "start%". 

With what I created, you can still clearly see the structure of the program Petr created for this purpose.
Once again, my thanks to Petr for the cleverly programmed program for reading PT100 sensors via MODBUS. 

Na de start van de module voegde ik ook een kleine controle (T=0 °C... or T>320°C indicates problems with one or more sensors (The test from this You can found it at the end of this reply.

And... I'm certainly open to your comments and improvements. Best, Rudy

Code: (Select All)
 OPTION _EXPLICIT

CONST TRUE = -1, FALSE = 0

DIM SHARED Byte1 AS STRING * 1
DIM SHARED h%: h% = FREEFILE
DIM SHARED MaxSensors%: MaxSensors% = 4 'Number of sensors (may be < then real present nr of sensors)
DIM SHARED Temp!(3) 'To read T in all subs en functions

'User-configurable settings (edit these)
DIM SHARED cmd$
DIM SHARED dev$: dev$ = "/dev/ttyACM0"
DIM SHARED dipRaw&
DIM SHARED pollHz!: pollHz! = 2
DIM SHARED rawS%
DIM SHARED req$, resp$
DIM SHARED showFrames%: showFrames% = -1
DIM SHARED slave%: slave% = 1
DIM SHARED timeout!: timeout! = 1.0
DIM start%: start% = TRUE '              (First loop start% =-1 'TRUE'  thereafter= 0 'FALSE')
DIM test%: test% = TRUE

'for programflow
DIM i%
DIM key$

PT100Read start% 'start% = true so START UP SENSORMODUEL

'Test all PT100 sensortest:
key$ = INKEY$
IF key$ = CHR$(27) THEN END

PT100Read start% 'start = false so read T in shared variable Temp!()

'Safety first: Before cooking test all sensors:
'==============================================
FOR i% = 0 TO MaxSensors% - 1
  PRINT USING "PT100 sensor CH# = ####.# C"; i%; Temp!(i%),
  IF Temp!(i%) = 0 THEN '        Connected but T = 0 degrees Celcius is NOT normal.
    PRINT " indicates this sensor is possible defect."
    PLAY "a8e8g8a8g8e8c4f8f8f4d8d8d4a8e8g8a8g8e8c4f8f8f4d8d8d4" 'Make much noice
    test% = FALSE
  ELSEIF Temp!(i%) > 300 THEN '  Not connected temperature = +-320 degrees Celcius...
    PRINT " is not connected."
    PLAY "a8e8g8a8g8e8c4f8f8f4d8d8d4a8e8g8a8g8e8c4f8f8f4d8d8d4" 'Make much noice
    test% = FALSE
  ELSE
    PRINT
  END IF
NEXT i%

IF test% = FALSE THEN
  PRINT
  PRINT "*********************************************"
  PRINT "*  Replace a zero indicting sensor,        *"
  PRINT "*  or reconnect some sensors if necessary. *"
  PRINT "*-------------------------------------------*"
  PRINT "*  If all sensors indicte ZERO degrees:    *"
  PRINT "*  Possible the PT100 module did not start.*"
  PRINT "*  Switch PC and senormodule OFF          *"
  PRINT "*  First Power UP sensormodule.            *"
  PRINT "*  Thereafter Power UP PC                  *"
  PRINT "*  Only RESTART PC this does not work!    *"
  PRINT "*********************************************"
  SLEEP

  PT100Stop
  END

END IF

'Place your cooking or temperature controlled program here.
'And call sub 'PT100Read start%' to store actuel T in shared variable Temp!()

PT100Stop 'Close sensor-module

END





'==============================================================================
SUB PT100Read (start%)
  'Start and read T storage in shared variable Temp!()

  DIM i%

  IF start% = -1 THEN 'only need once at program startup, control with variable "start%".

    ' 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

    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

    start% = 0

  ELSE 'Read (again) actuel T of all sensors in the shared variable Temp()

    SerialFlush h%

    req$ = ModbusBuildRead$(slave%, &H0000, 8)
    'IF showFrames% THEN PRINT "TX temps:          "; HexDump$(req$)
    SerialWrite h%, req$

    resp$ = SerialReadFrame03$(h%, slave%, timeout!)

    ' Decode and print temperatures
    ' Each channel is a signed 16-bit big-endian integer in 0.1°C.

    FOR i% = 0 TO MaxSensors% - 1 'pe: Option base 0 count from 0 to 3 for 4 sensors
      rawS% = GetS16BE%(resp$, 4 + i% * 2)
      Temp!(i%) = rawS% / 10! 'Temp!() is shared to read T everywhere
      'PRINT USING "CH# = ####.# C"; i%; Temp!(i%)
    NEXT i%

    _LIMIT pollHz!

  END IF

END SUB 'PT100Read (start%) -------------------------------

'==============================================================================
SUB PT100Stop
  CLOSE #h%
  PRINT "Reading PT100-sensors finished."
END SUB

'==============================================================================                                                                              '==============================================================================
FUNCTION Crc16Modbus& (s$) 'moet blijven
  ' 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 'Crc16Modbus& (s$) ------------------------

'==============================================================================
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$) 'moet blijven
  ' 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 'SerialWrite (h%, s$) -------------------------

'==============================================================================
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 'SerialFlus (h%) --------------------------

'==============================================================================
FUNCTION SerialReadFrame03$ (h%, expectedSlave%, timeoutSec!)
  ' Read and extract a valid MODBUS function 03 response frame.
  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 'SerialReadFrame03$ -------------------


[Image: Test-OK.png]

(The difference between T can be eliminated by placing a resistance in one of the leads of the PT100 sensors)

[Image: Testfalse.png]
Reply


Messages In This Thread
RE: Has anybody experience with MODBUS to read temp sensors? - by Rudy M - 01-18-2026, 03:23 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)