Strings from queues

This forum is for posts that might be considered off-topic but that may be useful or interesting to members. Examples include posts about electronics or programming in general, other microcontrollers or interesting devices, useful websites, etc.
Post Reply
victorf
Posts: 342
Joined: 01 January 2006, 4:08 AM
Location: Schenectady, New York

Strings from queues

Post by victorf »

I need to extract a string from a receive queue and have written the following in an attempt to do so:

Code: Select all

Public Function Com3GetString(ByRef inbuf as String, _
                              ByVal endflag as Byte) as byte
                                                      
'this function gets a string from Com3. If the subroutine times out before 
'receiving the endflag character then the function returns 0 else it
'returns the number of bytes read from the receive queue. The string inbuf
'will contain all data in queue up to and including the endflag. 

'NOTE: If function times out then inbuf is returned empty.
  
  Dim timeout as Single
  Dim ct as Byte
  Dim i as Byte
  Dim data as Byte
  ct = 0                          'assume failure
  
  data = 0
  inbuf = ""                      'start with empty string
  If StatusQueue(rq3) Then        'something to get?
     timeout = Timer + 0.2        'adjust as needed for length of messages
     ct = 1
      Do While StatusQueue(rq3)   'as long as there is something to get
        Call GetQueue(rq3, data, 1)
        If Timer > timeout Then
          Exit Do
        End If
        inbuf = inbuf & Chr(data) 'plunk into string
        If data = endflag Then    'end of data in queue
          Exit Do
        End If
        ct = ct + 1              'get next byte
      Loop
  End If
  If data = endflag Then
     Com3GetString = ct          'return actual byte count
  Else
     Com3GetString = 0           'return error flag
  End If

End Function  'Com3GetString
Does this code get the job done or is there a right/better way to do this?

Any enlightenment will be appreciated.

Vic
Vic Fraenckel
KC2GUI
windswaytoo ATSIGN gmail DOT com
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Re: Strings from queues

Post by mikep »

I can see several problems with the code:
1. Concatenating the string each time is a very expensive operation. It is probably better to put each character into a byte array and convert it to a string at the end.
2. There really isn't any need to return the length of the string. You can return the string as a return value and use an empty string to signify an error.
3. If the routine starts to receive characters and then times out, it returns a partial string which doesn't match the description of the function.
4. What happens if there is something to get but there is a leftover from a timeout on a previous call. How do you reset?
Mike Perks
victorf
Posts: 342
Joined: 01 January 2006, 4:08 AM
Location: Schenectady, New York

Post by victorf »

mikep

Thanks for the suggestions. I still cannot make any progress. Here is my next attempt that does not seem to work either.

BTW: rq3 is declared big enough to do what I want.

Code: Select all

'===============================================================================

Public Function Com3GetStr( ByRef Data() as Byte, _
                                    ByVal startflag as Byte, _
                                    ByVal endflag as Byte) as String
                                                      
'this function extracts data from the receive queue. If the routine times out
'without receiving the endflag byte then the function returns null string else
'it returns string up to and including the endflag.


  Dim timeout as Single
  Dim ct as Byte
  dim first as Byte
  
   first = 0
  
  Call Clearqueue(rq3)                          'throw away all received data
  Do While CBool(StatusCom(1) And &H04) 'wait til xmission complete
  Loop
  Do While first <> startflag
    Call PeekQueue&#40;rq3, first, 1&#41;   'look for start flag
  Loop
  
  ct = 1
  timeout = Timer&#40;&#41; + 0.2
  Do While StatusQueue&#40;rq3&#41; And Timer&#40;&#41; < timeout
    Call GetQueue&#40;rq3, data&#40;ct&#41;, 1&#41;
    If data&#40;ct&#41; = endflag Then      'end of data is endflag
      Exit Do
    End If
    ct = ct + 1                     'get next byte
  Loop

  If data&#40;ct&#41; = endflag Then
     Com3GetStr = MakeString&#40;MemAddress&#40;data&#41;, ct&#41;'return actual byte count
  Else
     Com3GetStr = ""                'return error flag
  End If

End Function  'Com3GetStr

'===============================================================================
IS there any different approach I should consider? I notice that the standard SerialPort_24 derivatives do not seem to address this sort of thing beyond GetByte(). I suspect there is a reason for this.


Any further enlightenment will be appreciated.

Vic
Vic Fraenckel
KC2GUI
windswaytoo ATSIGN gmail DOT com
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

victorf wrote:Here is my next attempt that does not seem to work either.
Have you made any progress on this?

It's not clear to me exactly how you want the routine to behave. For example, do you want to discard all characters received before the startflag character? If so, the code that awaits startflag should probably be something like this:

Code: Select all

  'look for start flag
  Do
    Call PeekQueue&#40;rq3, first, 1&#41;
    If &#40;first = startflag&#41; Then
      Exit Do
    End If

    ' not the start flag, discard
    Call GetQueue&#40;rq3, first, 1&#41;
  Loop 
Note that PeekQueue() doesn't remove the character from the queue. As the code was originally written, the first loop just kept checking the same character over and over. If the first character in the queue was not the start character, it would appear to hang.

You may also want to add a timeout in the loop that awaits the start character. Also, you probably should add a test in the second loop to prevent writing past the end of the data buffer.
- Don Kinzer
victorf
Posts: 342
Joined: 01 January 2006, 4:08 AM
Location: Schenectady, New York

Post by victorf »

Don,

I finally realized what I was doing. You are absolutely correct! In fact, I came up with the same solution. My only concern now is how long to wait for the start character. I am interfacing to a device that outputs a quasi-NMEA string and talks at 9600b. I know the start and end characters. Any ideas as how long to make the wait? Also, I have a timer on the section of code that grabs characters from the queue after receiving the start character. I have guessed 0.2 seconds would be enough once the start character arrives.

Any further enlightenment will be appreciated.

Vic
Vic Fraenckel
KC2GUI
windswaytoo ATSIGN gmail DOT com
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

victorf wrote:Any ideas as how long to make the wait?
Perhaps an alternate strategy is in order. You could have a separate task that checks the input queue for characters being available and then checks for the presence of the start character. All characters other than the start character would be discarded and the task would sleep for a while. When the start character is seen, then the task could sit in a loop waiting for the end flag.
- Don Kinzer
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

dkinzer wrote:Perhaps an alternate strategy is in order. You could have a separate task that checks the input queue for characters being available and then checks for the presence of the start character. All characters other than the start character would be discarded and the task would sleep for a while. When the start character is seen, then the task could sit in a loop waiting for the end flag.
I think Vic's original idea was that after the start character was received, the code would timeout if the rest of the message was not received within a certain time interval.

I think probably Vic needs to go back to the original device specification. For example the length of the timeout depends on the baud rate and the maximum length of the message. For example it takes just over 200 ms to receive 100 bytes at 4800 baud so a overall 0.2 second timeout may be insufficient.

Alternatively the timeout could be reset every time a character is received which mean the timeout is only contingent on the baud rate and not the length of the message.
Mike Perks
victorf
Posts: 342
Joined: 01 January 2006, 4:08 AM
Location: Schenectady, New York

Post by victorf »

Don and Mike,

Thanks for taking time to comment on my issue.

How does one compute the time a string of length L takes to be transmitted by a link of baud B?

I suspect that even though the link is called asynchronous, the data is transmitted in a continuous stream indicating that a timeout strategy might be the way to handle this.

I sort of like the idea of resetting the timer each time a character is received, though.

I am beginning to see why the 'standard' software such as SerialPort_24 (I think it's called) declines to address this GetString issue. :)

Any further enlightenment will be appreciated.

Vic
Vic Fraenckel
KC2GUI
windswaytoo ATSIGN gmail DOT com
Don_Kirby
Posts: 341
Joined: 15 October 2006, 3:48 AM
Location: Long Island, New York

Post by Don_Kirby »

victorf wrote: How does one compute the time a string of length L takes to be transmitted by a link of baud B?
If my math isn't faulty, I believe it would be 1/B*L*10, figuring on 8 data bits, 1 start bit, 1 stop bit and no parity (hence the *10, as baud is roughly synonymous with bits per second).

Using Mike's example, a 100 character transmission at 4800 baud equates to 1/4800*100*10 = 0.208 seconds, or 208 ms. This only works if there is no flow control.

-Don
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

The proposed calculations provide the minimum transmission time. The actual transmission time could be much longer because the standard allows an arbitrary amount of time to elapse between characters.
- Don Kinzer
Don_Kirby
Posts: 341
Joined: 15 October 2006, 3:48 AM
Location: Long Island, New York

Post by Don_Kirby »

dkinzer wrote:The proposed calculations provide the minimum transmission time.
Good point. Probably a non-issue when working with a PC side client, but it certainly could be an issue when dealing with something with a less capable processor (and perhaps other duties to perform).

Thinking (typing) out loud here..

An arbitrary value could be chosen as a multiplier to the calculated value to compensate for possible delays in transmission. For the sake of an example, let's say a multiplier of 3.

Using the previous example of a 100 byte string, that would mean a timeout setting of about .6 seconds if the timeout resets after each completed string. If the timeout is reset on every character, the same multiplier only leaves a 0.006 second window for the sender to catch up before the timeout occurs. While the latter might be easier to implement, I don't think it's as forgiving as the former.

If the string length is known (i.e. constant), my opinion would be to timeout on the entire string. If the string length is not constant, the character based approach would be the only option, unless the timeout value were large enough to encompass the longest possible string.



-Don
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

Don_Kirby wrote:Using the previous example of a 100 byte string, that would mean a timeout setting of about .6 seconds if the timeout resets after each completed string. If the timeout is reset on every character, the same multiplier only leaves a 0.006 second window for the sender to catch up before the timeout occurs. While the latter might be easier to implement, I don't think it's as forgiving as the former.
I think it all depends on the sending device. In most cases it should be able to keep sending once it has started. Reading the datasheet or the understanding the message protocol might help. For example here is my code for RS485 which specifies a 3.5 character timeout

Code: Select all

' The timeout is at least 3.5 characters and is dependent 
' on baud rate. Minimum value is at least 1 tick &#40;1.95 ms&#41;
messageFrameInterval = CByte&#40;3.5 * 11.0 * CSng&#40;Register.RTCTickFrequency&#41; / CSng&#40;baudrate&#41;&#41; + 1
I would not use your multiplier factor for per character timeout but set it to some suitable value such as 10, 25 or 50ms and leave it at that. As you say 6ms is probably too short.

If you write the code appropriately then the ZX device can be doing other work while waiting for characters on the RX queue i.e. no tight loops in the receiving task. Here is an example also extracted (and cutdown to essentials) from the RS485 application note (AN-218). One of the key sleeps is the last one (near the bottom of the code) that allows other tasks to do work even in the middle of receiving a RS485 packet.

Code: Select all

Private Sub receiveDataPacket&#40;&#41;
	' loop forever receiving messages
	Do
		' check the queue to see if any data has been received
		Dim count as UnsignedInteger
		count = CUInt&#40;GetQueueCount&#40;CByteArray&#40;inQueueAddr&#41;&#41;&#41;

		' either we are in the process of receiving or we are idling
		If status <> Receiving Then
			' check if there is something to do		
			If count <> 0 Then	
				' get the first byte of the message which should be an ID
				Dim messageAddress as Byte
				Call GetQueue&#40;CByteArray&#40;inQueueAddr&#41;, messageAddress, 1&#41;

				' set to receiving state to process rest of the message
				status = Receiving
			Else
				' nothing to do so wait a while
				Call Sleep&#40;messageFrameInterval&#41;
			End If

		' if we are not idling then we are in the middle of receiving data
		' and need to either copy the data into the receive buffer or ignore
		' it until this packet is finished
		Else

			' read in the received data, if any and add to message buffer
			If receiveCount <> 0 Then
				Call GetQueue&#40;CByteArray&#40;inQueueAddr&#41;, CByteArray&#40;bufAddress+dataSize&#41;, receiveCount&#41;
				dataSize = dataSize + receiveCount
				count = count - receiveCount
			End If	
			
			' wait to see if more data has been received during frame timeout period
			Call Sleep&#40;messageFrameInterval&#41;
			count = CUInt&#40;GetQueueCount&#40;CByteArray&#40;inQueueAddr&#41;&#41;&#41;

			' if some data already received and now no data has been received
			' after the frame timeout then this is the end of the frame
			If count = 0 Then
			        ' complete packet and signal that it is ready to be processed

			        ' set status back to waiting for the next packet

			        status = Waiting
			End If	
			
			' Wait a while before checking if more data has been received
			' this allows the user process to do some work as well.
			Call Sleep&#40;messageFrameInterval&#41;
		End If
	Loop
End Sub
The full code is a little more complex and can be found in the application note.
Mike Perks
Post Reply