I (new to programming) am attempting to use VB.net (scripts) on VB2019 to query a vintage PID controller. This is connected to my desktop via PIC card RS485 Modbus RTU. While it is a Modbus connection, the controller is not using standard function code like F03
, to read its registers, rather an array 81 81 52 00 00 53
(check below image)..
I modified a code i found on YouTube for me to be able to somewhat send and receive the data; I received 98 42 01 80 00 10 A0
; as per manual(wE49B) the result is supposed to be 42 98 08 01 00 A0 10
; of which I'm most interested in (98 42
) which contains the PV value (DEC: 17048);
Imports System.IO.Ports
Imports System.Threading
Public Class ReadHoldingRegistersForm
'Declare variables & constants
Private serialPort As SerialPort = Nothing
Private Sub ReadHoldingRegistersForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
serialPort = New SerialPort("COM4", 9600, Parity.None, 8, StopBits.Two)
serialPort.Open() 'Open COM4
Catch ex As Exception
MessageBox.Show(Me, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
Private Sub ReadHoldingRegistersForm_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
Try
serialPort.Close() ' Close COM4.
Catch ex As Exception
MessageBox.Show(Me, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
Private Sub btnReadHoldingRegisters_Click(sender As Object, e As EventArgs) Handles btnReadHoldingRegisters.Click
Try
Dim MeterAddress As Byte = 129
Dim MeterAddress1 As Byte = 129
Dim startAddress As Byte = 82
Dim ReadOpMaker As Byte = 0
Dim ReadData As Byte = 0
Dim ReadData1 As Byte = 0
Dim ReadPMaeker As Byte = 83
Dim ReadPcode As Byte = ReadOpMaker
Dim frame As Byte() = Me.ReadHolingRegisters(MeterAddress, MeterAddress1, startAddress, ReadOpMaker, ReadData, ReadData1, ReadPMaeker, ReadPcode)
txtSendMsg.Text = Me.DisplayValue(frame) ' Diplays frame: send.
serialPort.Write(frame, 0, frame.Length) ' Send frame to modbus slave.
Thread.Sleep(100) ' Delay 100ms.
If serialPort.BytesToRead > 5 Then
Dim buffRecei As Byte() = New Byte(serialPort.BytesToRead) {}
serialPort.Read(buffRecei, 0, buffRecei.Length) ' Read data from modbus slave.
txtReceiMsg.Text = Me.DisplayValue(buffRecei) ' Display frame: received.
Dim data As Byte() = New Byte(buffRecei.Length - 5) {}
Array.Copy(buffRecei, 3, data, 0, data.Length)
' Convert byte array to word array
Dim result As UInt16() = DataType.Word.ToArray(data) '
'Display Result
For Each item As UInt16 In result
txtResult.Text += String.Format("{0:0} ", item)
Next
End If
Catch ex As Exception
MessageBox.Show(Me, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
''' <summary>
''' Read holding registers
''' </summary>
''' <param name="MeterAddress">Slave Address</param>
''' <param name="MeterAddress1">Function</param>
''' <param name="startAddress">Starting Address</param>
''' <returns>Byte()</returns>
Private Function ReadHolingRegisters(MeterAddress As Byte, MeterAddress1 As Byte, startAddress As Byte, ReadOpMaker As Byte, ReadData As Byte, ReadData1 As Byte, ReadPMaeker As Byte, ReadPcode As Byte) As Byte()
Dim frame As Byte() = New Byte(7) {} ' Total 8 Bytes
frame(0) = MeterAddress ' Slave Address
frame(1) = MeterAddress1 'Function
frame(2) = startAddress 'Starting Address Hi.
frame(3) = ReadOpMaker 'Starting Address Lo.
frame(4) = ReadData ' Quantity of Registers Hi.
frame(5) = ReadData1 ' Quantity of Registers Lo.
frame(6) = ReadPMaeker ' Error Check Lo
frame(7) = ReadPcode ' Error Check Hi
Return frame '
End Function
The manual states:
and:
Currently the value am getting 32768??? Any way i wish to be guided on how I can via code collect the PV(98 42)(DEC:17048) and display it on text bar. Below is part of code that i believe i need guide to achieve my goal.
If serialPort.BytesToRead > 5 Then
Dim buffRecei As Byte() = New Byte(serialPort.BytesToRead) {}
serialPort.Read(buffRecei, 0, buffRecei.Length) ' Read data from modbus slave.
txtReceiMsg.Text = Me.DisplayValue(buffRecei) ' Display frame: received.
Dim data As Byte() = New Byte(buffRecei.Length - 5) {}
Array.Copy(buffRecei, 3, data, 0, data.Length)
' Convert byte array to word array
Dim result As UInt16() = DataType.Word.ToArray(data) '
'Display Result
For Each item As UInt16 In result
txtResult.Text += String.Format("{0:0} ", item)
Next
End If
the 'DataType' comes from this:
Imports System.Collections.Generic
Imports System.Text
Namespace DataType
Public Class Word
#Region "Chuyển đổi mảng bytes thành kiểu Word."
''' <summary>
''' Phương thức chuyển đổi mảng bytes thành kiểu Word.
''' </summary>
''' <param name="bytes">Mảng byte cần chuyển đổi</param>
''' <returns>Trả về giá trị kiểu Word</returns>
Public Shared Function FromByteArray(bytes As Byte()) As UInt16
' bytes[0] -> HighByte
' bytes[1] -> LowByte
Return FromBytes(bytes(1), bytes(0))
End Function
''' <summary>
''' Phương thức chuyển đổi mảng bytes thành kiểu Word.
''' </summary>
''' <param name="LoVal">Giá trị byte thấp</param>
''' <param name="HiVal">Giá trị byte cao</param>
''' <returns>Trả về giá trị kiểu Word</returns>
Public Shared Function FromBytes(LoVal As Byte, HiVal As Byte) As UInt16
Return CType(HiVal * 256 + LoVal, UInt16)
End Function
#End Region
#Region "Chuyển đổi kiểu Word thành mảng bytes."
''' <summary>
''' Phương thức chuyển đổi kiểu Word thành mảng bytes.
''' </summary>
''' <param name="value">Giá trị kiểu Word</param>
''' <returns>Trả về giá trị mảng kiểu byte</returns>
Public Shared Function ToByteArray(value As UInt16) As Byte()
Dim array1 As Byte() = BitConverter.GetBytes(value)
Array.Reverse(array1)
Return array1
End Function
''' <summary>
''' Phương thức chuyển đổi mảng kiểu Word thành mảng bytes.
''' </summary>
''' <param name="value">Mảng kiểu Word</param>
''' <returns>Trả về mảng kiểu byte</returns>
Public Shared Function ToByteArray(value As UInt16()) As Byte()
Dim arr As New ByteArray()
For Each val As UInt16 In value
arr.Add(ToByteArray(val))
Next
Return arr.array
End Function
''' <summary>
''' Phương thức chuyển đổi kiểu mảng bytes thành mảng word.
''' </summary>
''' <param name="bytes">Giá trị mảng bytes</param>
''' <returns>Trả về giá trị mảng kiểu Word</returns>
Public Shared Function ToArray(bytes As Byte()) As UInt16()
Dim values As UInt16() = New UInt16(bytes.Length \ 2 - 1) {}
Dim counter As Integer = 0
For cnt As Integer = 0 To values.Length - 1 Step 2
values(cnt) = FromByteArray(New Byte() {bytes(cnt), bytes(cnt + 1)})
Next
Return values
End Function
#End Region
End Class
End Namespace
Lets simplify your code considerably:
Dim buffRecei() as Byte= { &H98, &H42, &H01, &H80, &H00, &H10, &Ha0, &H00, &H00 }
Dim data As Byte() = New Byte(buffRecei.Length - 5) {}
Console.WriteLine(BitConverter.ToString(buffRecei).Replace("-"," "))
Array.Copy(buffRecei, 3, data, 0, data.Length)
Console.WriteLine(BitConverter.ToString(data).Replace("-"," "))
Dim result As UInt16() = ToArray(data)
Dim txtResult as string
For Each item As UInt16 In result
txtResult += String.Format("{0} ", item.ToString())
Next
Console.WriteLine(txtResult)
This outputs:
98 42 01 80 00 10 A0 00 00
80 00 10 A0 00
32768 0
Hopefully this shows what is happening. You are throwing away the first 3 bytes with the Array.Copy
and then converting 80 00
to an int16 (0x8000
= 32768 decimal).
To get the result you want you can just do something like:
Dim data() as Byte= { &H98, &H42, &H01, &H80, &H00, &H10, &Ha0, &H00, &H00 }
Console.WriteLine(BitConverter.ToString(data).Replace("-"," "))
Dim result As UInt16() = ToArray(data)
Dim txtResult as string
For Each item As UInt16 In result
txtResult += String.Format("{0} ", item.ToString())
Next
Console.WriteLine(txtResult)
' Alternative
dim val as UInt16 = BitConverter.ToUInt16(data, 0)
Console.WriteLine(val)
Which will output:
98 42 01 80 00 10 A0 00 00
38978 0 384 0
17048
So why does your code output 38978
, whereas the alternative I provided in the comments outputs 17048
? The answer is endianness:
You might assume that the first approach (Big Endian) is logical but different chips organise memory in different ways (and often send data over the serial link in the same order as held in memory). The code you are using makes an assumption that does not hold up (whereas BitConverter
uses your systems endianess so the above code might have a different result on a different system).
The manual you showed (note that this is NOT Modbus; the device may support Modbus but it's also supporting something else!) clearly states that the first byte is Low
; so you would need to edit the function as follows:
' Old: values(cnt) = FromByteArray(New Byte() {bytes(cnt), bytes(cnt + 1)})
values(cnt) = FromByteArray(New Byte() {bytes(cnt+1), bytes(cnt)})
Here is my code in an online playground (so you can play around with it).