dcsimg
Results 1 to 27 of 27

Thread: Serial Communication over RS485

  1. #1

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Serial Communication over RS485

    Hello,

    I am new to this forum, but I have learned a lot from reading on here. I am developing a software which communicates with approximately 12 different devices over RS485. I specifically have learned a lot from a post on here a few years back that has now become the basis for my serial communication code. Of these 12 devices I am communicating with, there are 3 different manufacturers so each manufacturers equipment has its own protocol and timing as far as receiving and responding to commands is concerned. My ultimate goal for this project is to send one command out on the line, wait for a specific instrument to respond to that command and after the response is dealt with (generally just updating the UI with this information), I want to start the process over with the next command in line. This communication loop would go on indefinitely until the user ends this loop (Typically for about 2 weeks at a time). It is very important that if communication ceases up or stops for any reason that it automatically resume.

    Right now I am facing two issues. The first is related, I believe, to the timing of the instruments. If I just send one single command, regardless of which instrument this is sent to, I have no problem looping this communication and getting a response back. If I add a second or third command to the communication sequence, depending on which manufacturers device I am communicating with, it will sometimes cease up and not respond to one of my commands. I can deal with this by placing a delay before the cmdDone.Set() routine as you can see below, however, I am not understanding exactly why this delay is necessary, because the way I understand this code is the response is dealt with before the next command is sent that way the devices should always be available to respond to a command. Like I said depending on the System.Threading.Thread.Sleep() time determines how often the communication stops, if at all. One of these devices I can send as many commands as I want with no system.threading.sleep and have no issues. Another one, gives me no issues with just one command, but always give me issues with more than one command without the delay. If I have to live with this delay that is fine but I would then like to be able to resume communications automatically if it ever seized up.

    The second issue I am facing is what is the best way to deal with the data when I receive it. Right now I update the UI based on a variable that increases everytime I get a full response from a command. This seems to work ok now because I know what order the responses are coming based on the order that the commands are sent. This does not seem like a very efficient way, but I am struggling to find another route here.

    Thanks.

    Code:
    Public Class frmRunData
    
    
        Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
            '
            'port setup and open - modify to fit your needs
            '
            Dim p() As String = IO.Ports.SerialPort.GetPortNames 'array p contains ports visible to system
    
            'your settings here
            With SerialPortRunData
                .PortName = "COM3" '<<<<<<<<<<<<<<<<<YOUR PORT<<<<<<<<<<<<<<<<<<<<<<<<
                .BaudRate = 38400
                .Parity = IO.Ports.Parity.Even
                .DataBits = 8
                .StopBits = IO.Ports.StopBits.One
                .Encoding = System.Text.Encoding.GetEncoding(28591)
                .DtrEnable = True
                .RtsEnable = True
                'etc
            End With
    
            Try
                SerialPortRunData.Open()
                isopen.Set()
                BackgroundWorker1.WorkerReportsProgress = True
                BackgroundWorker1.WorkerSupportsCancellation = True
                BackgroundWorker1.RunWorkerAsync()
            Catch ex As Exception
                MessageBox.Show(ex.Message)
            End Try
    
        End Sub
    
        Dim sc As Threading.Thread
    
        Private Sub btnStartRunAB_Click(sender As Object, e As EventArgs) Handles btnStartRunAB.Click
    
            Try
                If isopen.WaitOne(0) AndAlso SerialPortRunData.IsOpen Then
                    btnStartRunAB.Enabled = False
                    BeginRead()
                End If
            Catch ex As TimeoutException
                MessageBox.Show(ex.Message)
    
            Catch ex As InvalidOperationException
                MessageBox.Show(ex.Message)
    
            Catch ex As UnauthorizedAccessException
                MessageBox.Show(ex.Message)
    
            End Try
            isopen.Set()
    
        End Sub
    
        Private Sub BeginRead()
    
            sc = New Threading.Thread(AddressOf SendCmds)
            sc.IsBackground = True
            sc.Start()
    
        End Sub
    
        Dim cmdDone As New Threading.AutoResetEvent(True)
    
        Private Sub SendCmds()
    
            Dim cmds As New List(Of Byte())
    
            Dim runData As New classRunData
    
            cmds.Add(runData.ReadVacuum("&H41"))
            cmds.Add(runData.ReadVacuum("&H42"))
            cmds.Add(runData.ReadVacuum("&H43"))
            cmds.Add(runData.ReadVacuum("&H44"))
            cmds.Add(runData.ReadPropFlow("&H41"))
            cmds.Add(runData.ReadPropFlow("&H42"))
            cmds.Add(runData.BoilerStatus("&H41"))
            cmds.Add(runData.BoilerStatus("&H42"))
            cmds.Add(runData.ReadMFCValues("&H37"))
    
            For Each c As Byte() In cmds
                cmdDone.WaitOne()
                SerialPortRunData.Write(c, 0, c.Count)
            Next
            'Me.Invoke(Sub() btnStartRunAB.Enabled = True)
        End Sub
    
        Dim isopen As New Threading.AutoResetEvent(False)
    
        Dim dataByts As New List(Of Byte)
        Dim dataLock As New Object
        Dim dataAvailable As New Threading.AutoResetEvent(False)
    
        Private Sub SerialPort1_DataReceived(sender As Object,
                                             e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPortRunData.DataReceived
            Dim br As Integer = SerialPortRunData.BytesToRead '# of bytes to read
            If br > 0 Then
                Dim b(br - 1) As Byte 'create buffer to read the data
                Try
                    br = SerialPortRunData.Read(b, 0, b.Length) 'read the bytes, note non-blocking
                    If br < b.Length Then 'adjust length if required
                        Array.Resize(b, br)
                    End If
                    'add bytes just read to list
                    Threading.Monitor.Enter(dataLock)
                    dataByts.AddRange(b)
                    Threading.Monitor.Exit(dataLock)
                    dataAvailable.Set()
                Catch ex As Exception
                    'exception handling
                End Try
            End If
    
        End Sub
    
        Const LF As Byte = 10 'responses end with vbCrLf
        Const CR As Byte = 13
    
        Private Sub BackgroundWorker1_DoWork(sender As Object,
                                             e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
            Do
                If BackgroundWorker1.CancellationPending Then Exit Do
                Dim idxLF As Integer = dataByts.IndexOf(LF)  'get the index of the LF
                If idxLF >= 0 Then 'do we have a LF
                    'yes, get the message from controller
                    Dim s As String
                    Threading.Monitor.Enter(dataLock)
                    s = System.Text.Encoding.GetEncoding(28591).GetChars(dataByts.ToArray, 0, idxLF - 1)
                    dataByts.RemoveRange(0, idxLF + 1) 'remove the message from the buffer
                    Threading.Monitor.Exit(dataLock)
                    BackgroundWorker1.ReportProgress(1, s)
                    'cmdDone.Set() '<<<<<<<<<<<<<<<
                Else
                    'wait for more data
                    dataAvailable.WaitOne()
                End If
            Loop
        End Sub
    
        Public j As Integer = 0
    
        Private Sub BackgroundWorker1_ProgressChanged(sender As Object,
                                                      e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
            'this code run on UI
            j = j + 1
    
            Dim s As String = DirectCast(e.UserState, String) 'get message
    
            If j = 1 Then
                Dim vacuumA As Double = Format((s * 0.0366) - 7.04863, "0.00")
                VacuumAGaugeControl.SetPointerValue("Pointer1", vacuumA)
            ElseIf j = 2 Then
                Dim vacuumB As Double = Format((s * 0.0366) - 7.04863, "0.00")
                VacuumBGaugeControl.SetPointerValue("Pointer1", vacuumB)
            ElseIf j = 3 Then
                Dim vacuumC As Double = Format((s * 0.0366) - 7.04863, "0.00")
                VacuumCGaugeControl.SetPointerValue("Pointer1", vacuumC)
            ElseIf j = 4 Then
                Dim vacuumD As Double = Format((s * 0.0366) - 7.04863, "0.00")
                VacuumDGaugeControl.SetPointerValue("Pointer1", vacuumD)
            ElseIf j = 5 Then
                lblStackFlowAB.Text = s
            ElseIf j = 6 Then
                lblStackFlowCD.Text = s
            ElseIf j = 7 Then
                If s = 0 Then
                    lblBoilerStatusAB.Text = "ON"
                ElseIf s = 1 Then
                    lblBoilerStatusAB.Text = "OFF"
                End If
            ElseIf j = 8 Then
                If s = 0 Then
                    lblBoilerStatusCD.Text = "ON"
                ElseIf s = 1 Then
                    lblBoilerStatusCD.Text = "OFF"
                End If
            ElseIf j = 9 Then
                Dim processValue() As String
                processValue = Split(s, ",")
                Dim processA As NumericIndicator = FlowAGaugeControl.GaugeItems(0)
                processA.Text = Format(((Convert.ToInt32(processValue(2))) / 100), "0.00")
                j = 0
                BeginRead()
            End If
    
            lblSerialTimer.Text = lblSerialTimer.Text + 1
            System.Threading.Thread.Sleep(30)
            cmdDone.Set()
    
        End Sub
    
        Private Sub SerialPort1_ErrorReceived(sender As Object,
                                              e As IO.Ports.SerialErrorReceivedEventArgs) Handles SerialPortRunData.ErrorReceived
    
            Debug.WriteLine("ER " & e.EventType.ToString)
        End Sub
    
        Private Sub SerialPort1_PinChanged(sender As Object,
                                           e As IO.Ports.SerialPinChangedEventArgs) Handles SerialPortRunData.PinChanged
    
            Debug.WriteLine("PC " & e.EventType.ToString)
        End Sub
    
    End Class

  2. #2
    Frenzied Member
    Join Date
    May 2014
    Location
    Central Europe
    Posts
    1,285

    Re: Serial Communication over RS485

    Hi and welcome to the Forum. I get the Impression that your current code is unneccesarily complicated in having a thread sending the data, the data received Event, a Background worker processing the received data and all the Status vars.
    i'd suggest a simpler design such as:

    Code:
    Sub SendingThread()
      'runs in ist own thread, code is mainly coming from your SendCmds proc
      set up all the commands in a list
      do
      	replyReceived=false
        send command
        do while not replyReceived
            thread.sleep 100ms
            cSleeps += 1
            if sleeps >10 then Exit do  'device did not respond in time, move on to next command
        Loop
        Move on to next command, if at end start with first again
      while
    end Sub
    
    sub DataReceived()
        'this combines the code you have in DataReceived,BackgroundWorker1_DoWork and BackgroundWorker1_ProgressChanged
    	read from port and append to bytearray
    	if LF is present then
    	   extract the data from the message and store it in a corresponding form wide var
    	   replyreceived=true
    	end if
    end sub
    
    sub FormTimer()
    	'some code from BackgroundWorker1_ProgressChanged
    	update GUI with the content of the vars
    end sub
    so the code should send one command, wait for reply and parse the reply or timeout and move to the next command. the GUI update is fully isolated from this running in a timer event.
    If parsing of result is depending on the command sent, i.e. the parsing code needs to know to what command the answer belongs, you would also need to set up a form wide var to store information about what command was sent.

  3. #3

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    Thank you for the response digitalShaman. I tried a version of what you requested and it seems to have cured my issue related to a device not communicating and re-establishing the command loop, however, it still seems to timeout from time to time and when it doesn't, the communication seems choppy. I prefer the performance of the original at this time, but I will continue to play with this idea or possible incorporate this idea into the original code.

    I dont, however, understand what the major difference between the two is, other than being simplified. What you recommend to do with the replyReceived variable is the same as the cmdDone.Set() and cmdDone.Wait() correct? Also, doesn't the background worker which is running on a different thread also isolate the GUI from the serial comms?

    Thanks

  4. #4
    Frenzied Member
    Join Date
    May 2014
    Location
    Central Europe
    Posts
    1,285

    Re: Serial Communication over RS485

    the simpler the code the less chance of wackyness. the less lines the less Chance of bugs. if a device does not respond to a command and the Loop that waits for the respond after the command was sent times out, a short thread sleep after each successfully received reply may cure this. it could be that the devices Need a Little break on the Serial port to reinitialize before beeing able to receive the next command.

    Note that DataReceived is fired on its own thread so you already have it separated from the GUI.

  5. #5
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    5,919

    Re: Serial Communication over RS485

    Quote Originally Posted by amertz0625 View Post
    ... If I add a second or third command to the communication sequence, depending on which manufacturers device I am communicating with, it will sometimes cease up and not respond to one of my commands. I can deal with this by placing a delay before the cmdDone.Set() routine as you can see below, however, I am not understanding exactly why this delay is necessary, because the way I understand this code is the response is dealt with before the next command is sent that way the devices should always be available to respond to a command.
    It sounds like you're using half-duplex RS-485, aka two-wire RS-485.
    Only one device can be transmission connected to the signal lines at a time, and have to be high-impedance connected for receiving at all other times. After transmission, it can take a bit of time for the transmitting device to return to the high-impedance state, so a short delay needs to be done before the next device starts transmitting. Ideally, this would be a one character transmission time delay, which at the baud rate you're using would only be about 300 microseconds. But, perhaps some equipment is slower than ideal at tri-stateing their connection. It seems like 30ms would be much more than needed, that 1ms sleep should be sufficient.
    Quote Originally Posted by amertz0625 View Post
    ...If I have to live with this delay that is fine but I would then like to be able to resume communications automatically if it ever seized up.
    Is this still an issue. I don't see any provisions in your code to timeout a non-response, or incomplete response condition. Usually, when I send a command, I would set a counter variable, and have a "watchdog" timer, decrement the counter and if it ever reached 0, then cleanup and retransmit, or move on to the next device, whatever is appropriate. If transmissions are working normally, each command would reset the timeout counter to the countdown count, and the watchdog timer would never get it down to 0.
    Quote Originally Posted by amertz0625 View Post
    ... The second issue I am facing is what is the best way to deal with the data when I receive it. Right now I update the UI based on a variable that increases everytime I get a full response from a command. This seems to work ok now because I know what order the responses are coming based on the order that the commands are sent. This does not seem like a very efficient way, but I am struggling to find another route here.
    Yeah, I wouldn't like that method much. Ideally, I would like the protocol to include a tag that is sent with the command and returned in the response so the response could be correlated with the request.
    But as an alternative, since you're only sending one command at a time, is to set a variable based on the command sent, that is used to identify the response. Since you're queuing the commands as array of bytes, perhaps you could add an extra byte on the front to identify the command, then when you dequeue the command, set the variable to the identifier, and send the rest of the byte array as the command.
    Or, you could always queue the identifier first as a small byte array (could be a single byte array) and the command second, so you dequeue the identifier, set the variable, and dequeue the command and send it.

    On one system where I was allowed to send multiple commands out at a time, but the protocol didn't have an id built in, then I used a queue to note the commands sent, and then dequeued the details when I received the responses. This improved throughput because I could send some number of commands at once, but I would still have to wait for all the responses to comeback before sending the next batch. With each response and dequeue, if the the queue wasn't empty, I knew I hadn't received all the responses yet, so would reset the watchdog counter and continue to wait. If the watchdog fired, meaning I wasn't receiving anymore responses and my queue wasn't empty, I knew that some messages were dropped, but had no way of knowing which ones, so would have to empty the queue, and resend the whole batch of requests again. The remedial task, was to reduce the number of simultaneous requests to a level that had 100% completion rate, or close to it.

    When I was allowed to change the interface to have tagged requests and responses, then I was able to switch from queue to a dictionary and use the tag id as a key, so removed the requests from the dictionary as they came in. If the watchdog fired, I had a list of which commands didn't get a response, so could resend just those requests again.
    In this scenario, I didn't have to wait for a batch of commands to complete before sending the next, I could send at a rate that was near 100% completion all the requests (could be hundreds of thousands), and then if the watchdog fired, I might have several hundred left in the dictionary that didn't get responses, so I could just resend those, and again if they all didn't receive, responses, repeat.

    I did have a check in there as well, that if the dictionary count didn't go down in two passes of the watchdog phase, then there was some issue where I had to assume there would never be a response to those particular requests, so call it done, with errors.
    Last edited by passel; Mar 17th, 2016 at 04:34 AM.

  6. #6

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    It sounds like you're using half-duplex RS-485, aka two-wire RS-485.
    Only one device can be transmission connected to the signal lines at a time, and have to be high-impedance connected for receiving at all other times. After transmission, it can take a bit of time for the transmitting device to return to the high-impedance state, so a short delay needs to be done before the next device starts transmitting. Ideally, this would be a one character transmission time delay, which at the baud rate you're using would only be about 300 microseconds. But, perhaps some equipment is slower than ideal at tri-stateing their connection. It seems like 30ms would be much more than needed, that 1ms sleep should be sufficient.
    You are correct here passel, I am using two wire RS485, and I am under the same impression timing wise.

    Is this still an issue. I don't see any provisions in your code to timeout a non-response, or incomplete response condition. Usually, when I send a command, I would set a counter variable, and have a "watchdog" timer, decrement the counter and if it ever reached 0, then cleanup and retransmit, or move on to the next device, whatever is appropriate. If transmissions are working normally, each command would reset the timeout counter to the countdown count, and the watchdog timer would never get it down to 0.
    Yes, unfortunately this is still an issue and an inconsistent issue. The system doesn't timeout often, but every once and a while it does. I originally did implement a watchdog timer almost exactly as the example you provided. I incremented a variable overtime I received a response and then in a timer I check to see if that variable was equal to the same value it was on the previous check and if so i would restart the command sequence. Right now this seems to work pretty well, however, I was having difficulty starting the communications over as opposed to resuming the communications which was throwing my response variable off, basically when i expected to get data from Station A, I was now getting it from Station C. I believe with a little debugging this can be worked out, and I am going to work on this today.

    Unfortunately I request specific parameters from the same device and the protocol returned is the exact same regardless of the parameter I am after, with the exception of the value of that parameter. Because these are off the shelf products I do not have access to modifying the protocol. I am not too concerned about this though anymore as I haven't really seen any issues with the approach I am using now I was just thinking theres got to be a better way to do it. My main goal right now is understanding exactly why it freezes up sometimes and not others.

    Thanks

  7. #7

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    I also just realized that I get an error sometimes "ER RXParity" and "ERFrame". I am assuming the ER RXParity issue is related to the parity of the serial port setting on my computer which I have confirmed is the same the parity on all devices. I use Even parity for these communications. Any idea what could be causing this error or what the ER Frame error is related to?

  8. #8
    Frenzied Member
    Join Date
    May 2014
    Location
    Central Europe
    Posts
    1,285

    Re: Serial Communication over RS485

    Quote Originally Posted by amertz0625 View Post
    I also just realized that I get an error sometimes "ER RXParity" and "ERFrame". I am assuming the ER RXParity issue is related to the parity of the serial port setting on my computer which I have confirmed is the same the parity on all devices. I use Even parity for these communications. Any idea what could be causing this error or what the ER Frame error is related to?
    my best guess would be that more than one device is feeling addressed by a command and they send their Response at the same time.

  9. #9

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    I changed the parity on all devices from "EVEN" to none, and so far I don't seem to have any issues, even without the delay so I am going to test this overnight and see if that was the issue.

    Thanks

  10. #10

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    Well I got rid of the RXParity issue I was seeing, but I still get the Frame Error every so often so I am going to try to look into this. The watchdog timer seems to do the trick and if I have just 16 commands as a part of my command stream, then the watchdog timer rarely fires, but if I add more than 16 commands it seems to fire every few minutes, sometimes more often. I will look more into this tonight as well.

  11. #11
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    5,919

    Re: Serial Communication over RS485

    I wouldn't think that setting the parity to none fixed anything, it just won't detect a parity error any longer.
    But it you were getting parity errors occasionally, then I would expect to also get framing errors (less occasionally) as well.

    As digitalShaman surmises, it is an indication that you may have two devices driving the bus at the same time so you have a short period of garbled bits. With parity checking on, you probably would detect errors at least 3 or more times more often (any odd number of 8 bits detected as in the wrong state), than framing errors which is only detecting the start and stop bits as not being in the state they are expected to be. Without CRC type checksums, a fair number of bit errors could make it past the parity and framing checks. Who knows, perhaps you have ground loops in the bus putting the voltage differentials in a range more susceptible to occasional errors.

  12. #12

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    Passel I believe you may be on to something here. Recently I discovered something interesting. I have 6 different devices on this rs485 loop right now, and I can continuously communicate with any one of them with no problems. I can even send multiple different commands to one device with no issues or no interruptions, even though all 6 devices are wired into the communication line. As soon as I try to communicate with 2 devices by sending one command to one device, wait for the response, and then send a command to the other device, I begin to have issues. This communication may work fine for some time, maybe 50-200 cycles of one command followed by another, but eventually it just stops. The commands are being sent properly but eventually I just won't get a response. I am not very experienced in wiring rs485 com lines, but I do not have termination resistors at the end of the line of communication. Do you think this could explain why sometimes, if I am communicating with more than one device, I don't get a response?

  13. #13
    Fanatic Member namrekka's Avatar
    Join Date
    Feb 2005
    Location
    Netherlands
    Posts
    639

    Re: Serial Communication over RS485

    Quote Originally Posted by amertz0625 View Post
    ...but I do not have termination resistors at the end of the line of communication...
    Yes that is important. The line is "floating" in the time that the system is switching from send to receive. It can give spikes on the line that can be interpreted as some bits. Also the line is more sensible for interference, specially when the line gets longer. This can give the framing errors. So the line needs to be terminated at both ends. And more...you always have a change that the communication is messed up by some interference. Many devices have some sort of checksum. You have to check that and you must recover.

    ...I have 6 different devices on this rs485 loop right now, and I can continuously communicate with any one of them with no problems...
    ...As soon as I try to communicate with 2 devices by sending one command to one device, wait for the response, and then send a command to the other device, I begin to have issues....
    This above is not clear to me what you mean.

    Normally with those devices you have a polling protocol. So you poll a device and wait for his response. If a timeout, poll that device again ( a few times and after that put it on a black list). Check the checksum. If correct, accept the data and go to the next device. If not then poll that device again...etc.

    Also your PC is fast...very fast. Those devices are slow and could be very slow comparing to your PC. All devices have to listen on the line if the message is his. Not all devices do have something to see if the line is busy to send a response. So be patient on the line. Perhaps a hardware guy with a scope can have a look on the line.

  14. #14

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    I have some attached some code to explain what I am seeing. In this example I have 4 different commands that are sent to a total of 3 different devices. These devices are addressed by their station ID, for example "&H37" is one station, "&H38" is another station, and "readArduino" is another station. If I set the communications up like they are below, the communications will eventually cease up and stop. To be more specific, there will come a time when I send out one of these commands and do not get a response. Now, if I attempt to only communicate with one station at a time, I can communicate indefinitely with no issues whatsoever. I can even duplicate these commands and add additional commands, but they can only be sent to one station address. Once I add a second or third station address to the loop, communications will be fine for a while, but will eventually stop. This leads me to believe the issue isn't in the code at all, and may be in the wiring of the devices, I just don't understand how it can work with one device all the time, but with multiple devices only some of the time.

    Code:
     Private Sub SendCmds()
    
            Dim cmds As New Dictionary(Of String, Byte())
    
            Do
                cmds.Add("MFCA", ReadMFCValues("&H37"))
                cmds.Add("WriteFlowA", WriteMFCValues("&H37", IntegerInput1.Value))
    
                cmds.Add("Temp", ReadTemps("&H38"))
     
                cmds.Add("Arduino", readArduino)
    
                For Each c As KeyValuePair(Of String, Byte()) In cmds
                    typeOfData = c.Key
                    cmdDone.WaitOne()
                    SerialPort1.Write(c.Value, 0, c.Value.Count)
                Next
                cmds.Clear()
            Loop
    
        End Sub
    
        Dim isopen As New Threading.AutoResetEvent(False)
    
        Dim dataByts As New List(Of Byte)
        Dim dataLock As New Object
        Dim dataAvailable As New Threading.AutoResetEvent(False)
    
        Private Sub SerialPort1_DataReceived(sender As Object,
                                             e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
            Dim br As Integer = SerialPort1.BytesToRead '# of bytes to read
            If br > 0 Then
                Dim b(br - 1) As Byte 'create buffer to read the data
                Try
                    br = SerialPort1.Read(b, 0, b.Length) 'read the bytes, note non-blocking
                    If br < b.Length Then 'adjust length if required
                        Array.Resize(b, br)
                    End If
                    'add bytes just read to list
                    Threading.Monitor.Enter(dataLock)
                    dataByts.AddRange(b)
                    Threading.Monitor.Exit(dataLock)
                    dataAvailable.Set()
                Catch ex As Exception
                    Debug.WriteLine(ex.Message)
                End Try
            End If
    
        End Sub
    
        Const LF As Byte = 10 'responses end with vbCrLf
        Const CR As Byte = 13
    
        Private Sub BackgroundWorker1_DoWork(sender As Object,
                                             e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
            Do
                If BackgroundWorker1.CancellationPending Then Exit Do
                Dim idxLF As Integer = dataByts.IndexOf(LF)  'get the index of the LF
                If idxLF >= 0 Then 'do we have a LF
                    'yes, get the message from controller
                    Dim s As String
                    Threading.Monitor.Enter(dataLock)
                    s = System.Text.Encoding.GetEncoding(28591).GetChars(dataByts.ToArray, 0, idxLF - 1)
                    dataByts.RemoveRange(0, idxLF + 1) 'remove the message from the buffer
                    Threading.Monitor.Exit(dataLock)
                    BackgroundWorker1.ReportProgress(1, s)
                    cmdDone.Set() '<<<<<<<<<<<<<<<
                Else
                    'wait for more data
                    dataAvailable.WaitOne()
                    'Debug.WriteLine("WaitOne")
                End If
            Loop
        End Sub
    
    Private Sub BackgroundWorker1_ProgressChanged(sender As Object,
                                                      e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
    
            Dim s As String = DirectCast(e.UserState, String) 'get message
    
        End Sub

  15. #15
    Fanatic Member namrekka's Avatar
    Join Date
    Feb 2005
    Location
    Netherlands
    Posts
    639

    Re: Serial Communication over RS485

    But a polling protocol means 1 device at a time and when handled move to the next device. Not all devices do have a buffer to wait and are capability to sense if the line is free to send (collision detection). As I do not have the doc's of the devices I can't judge if this is true.

    Is there some sort of checksum?

    What I forgot to mention on the hardware side is that you need a twisted pair (2 wires twisted in each other). Specially when the line gets longer, or in a industrial environment, you NEED a twisted pair AND line termination. On a long line you will have reflections when the line is not terminated. A twisted pair is less sensible to pickup interference and, deeper in to electronics, will have a characteristic impedance. The termination resistors will make the line infinite and will not give reflections. You can use a piece network cable. You will find some twisted pairs in it.

  16. #16

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    I believe I am polling one device at a time. I am communicating with multiple panel mount azbil mass flow controllers. Because I only need a small amount of information from these devices, I just send out a single command requesting a specific piece of information, I wait for the response using cmdDone.Wait(), and once I receive and handle this response, I set cmdDone.Set() which allows the next command to be sent. There is a checksum with these devices.

    I do have a twisted pair running from device to device, but there are 3 legs, Line A, Line B, and a ground line. They are all twisted together and go from one device to the next. The manufacturer wiring diagram does say that a terminating resistor is required, so I will try to add that, but I just cant understand why I can send command after command to any one station address with no issues, but if I try to send command to one device and then to another, I have issues.

    Thanks

  17. #17
    Fanatic Member namrekka's Avatar
    Join Date
    Feb 2005
    Location
    Netherlands
    Posts
    639

    Re: Serial Communication over RS485

    It looks like your cable is correct except for the terminating resistors at BOTH ends. I agree it seems not the issue in this test case. But in another environment it sure will.

    Without going trough your code you mean that your polling sequence is like this:
    Ask1 - Response1 - Wait - Ask2 - Response2 - Wait - etc?

  18. #18

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    I would describe it more as
    Ask1, Wait for response1, Once response1 is received, Ask2

    This gives me issues, but if I do
    Ask1, Wait for response1, Once response1 is received, Ask1 again

    This gives me no issues

  19. #19
    Fanatic Member namrekka's Avatar
    Join Date
    Feb 2005
    Location
    Netherlands
    Posts
    639

    Re: Serial Communication over RS485

    Quote Originally Posted by amertz0625 View Post
    I would describe it more as
    Ask1, Wait for response1, Once response1 is received, Ask2
    Are you sure?

    If you set a breakpoint on:
    "SerialPort1.Write(c.Value, 0, c.Value.Count)"

    and on:
    "Private Sub SerialPort1_DataReceived..."

  20. #20

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    I will doublecheck this tonight but yes, I believe when the comms fail that the command gets sent, but I get no response whatsoever, not even a partial or broken response just no response at all.

  21. #21

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    I confirmed. The serial command is being sent, but there is no response. Again this only happens when I try to send commands in the following way. If

    Do
    Station1.command1
    Station2.command1
    Station1.command2
    Station2.command2
    Loop

    If I send...
    Do
    Station1.command1
    Station1.command2
    Station1.command3
    Station1.command4
    etc.
    Loop

    No issues here. I cant wrap my head around this unless its possible that this could be related to a wiring issue. I wont be able to try that until tomorrow but I am out of ideas outside of that.

    Thanks

  22. #22

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    After some further research it seems that the terminating resistor is most likely not my problem. I am using azbil mass flow controllers which say to install a terminating resistor, and I am also using azbil temperature controllers which say not to install a terminating resistor and that if you do it will cause communication issues. I am going to try it anyways, but it seems like my issue lies elsewhere.

  23. #23
    Fanatic Member namrekka's Avatar
    Join Date
    Feb 2005
    Location
    Netherlands
    Posts
    639

    Re: Serial Communication over RS485

    Quote Originally Posted by namrekka View Post
    It looks like your cable is correct except for the terminating resistors at BOTH ends. I agree it seems not the issue in this test case. But in another environment it sure will......
    .Are you sure?

    If you set a breakpoint on:
    "SerialPort1.Write(c.Value, 0, c.Value.Count)"

    and on:
    "Private Sub SerialPort1_DataReceived..." ..
    So these breakpoints alternate?

  24. #24

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    namrekka,

    I tried debugging this the way you recommended, using breakpoints at both of those locations and yes, they alternate. Normally It alternates in this fashion...

    SerialPort1.Write....SerialPort1_DataReceived....SerialPort1_DataReceived....SerialPort1.Write....et c...

    I am now really familiar with using breakpoints, I always just use debug.writeline when I am trying to troubleshoot something, but using the breakpoints is requiring me to press continue every time I hit a breakpoint in order to proceed on, which makes sense, however, I can't seem to replicate a failed communication this way. I can continue to press continue after continue and every thing works. Maybe the speed is my issue, because as soon as I disable the breakpoints, the communications cease back up. I originally added a debug.writeline call immediately after the SerialPort1.writline and in the SerialPort1_DataReceived routine. This is how I came to the conclusion that when the comms did fail, it failed because a command was sent but not responded to. Every time the communications fail, I showed that the command was sent, but I get no response form the data received event.

    This is interesting though that it won't fail if I have the breakpoints in and I just click really fast to get by them. This seems like a speed issue to me. What do you think?

    Thanks

  25. #25

    Thread Starter
    Junior Member
    Join Date
    Mar 2016
    Posts
    16

    Re: Serial Communication over RS485

    I did some more troubleshooting and I think I my have identified the problem, but not sure I understand it. One of the devices I am using is an arduino board. I added a second arduino board and tried to send commands to one and then the other. This works fine with no problems. I even added a third with no issues, so I believe my issue is specifically with the azbil controllers. I added a delay between commands coming from one controller to the next, just a 50 ms delay, and it seems to be working at this time. If I lower that delay to less than 40 ms, then I get a break in communications, but nearly as often as I did before. This leads me to believe that when the break in communication occurs, its because the controllers are not yet ready to receive a signal when I send one out. Why a single controller is always ready to receive commands if they are constantly coming to it, I am not sure at this time, but I have made some ground. Does this make sense as being the potential issue here? Could one controller need the command to be delayed slightly if the previous command was sent to another controller?, possibly to allow it to now become ready to receive commands?

  26. #26
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    5,919

    Re: Serial Communication over RS485

    Well, with the two wire setup the currently receiving controller has to return to high impedance before someone else can can transmit on the line. Perhaps with two lower impedance drivers connected, the master and one receiver, there is enough drive to get the signal through even if the receiver is a bit slow to return to high impedance. But when addressing a second controller, perhaps it switches to a low impedance to respond and now you have two controllers at low impedance dragging the line down so the master gets errors in the response from the second controller. Having a required delay (but a variable delay depending on the speed that a given controller can return to a high impedance state after transmitting) is normal when the master switches addresses.
    If you want full speed on RS-485 you have to have full duplex (4-wire) communication.

    The amount of delay needed for a 2-wire setup might have to be empirically found when you have a mix of controllers. It might be possible to have a table to vary the amount of delay depending on which controller was the last to transmit (so you know by experience how long a delay it needs to revert its driver to high impedance listening mode).

  27. #27
    Fanatic Member namrekka's Avatar
    Join Date
    Feb 2005
    Location
    Netherlands
    Posts
    639

    Re: Serial Communication over RS485

    Quote Originally Posted by namrekka View Post
    Also your PC is fast...very fast. Those devices are slow and could be very slow comparing to your PC. All devices have to listen on the line if the message is his. Not all devices do have something to see if the line is busy to send a response. So be patient on the line. Perhaps a hardware guy with a scope can have a look on the line.
    I guess that is the problem indeed. Isn't something in the docs about response times?
    Also I advice to handle the checksum thing, and then only accept the data that is valid. If not do a retry. Also use a timeout. And try to recover when things goes wrong. You will find out that in a real environment (industrial) the data can be messed up sometimes.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width