Page 1 of 1
Strings from queues
Posted: 15 February 2008, 3:57 AM
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
Re: Strings from queues
Posted: 15 February 2008, 7:39 AM
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?
Posted: 17 February 2008, 5:12 AM
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(rq3, first, 1) 'look for start flag
Loop
ct = 1
timeout = Timer() + 0.2
Do While StatusQueue(rq3) And Timer() < timeout
Call GetQueue(rq3, data(ct), 1)
If data(ct) = endflag Then 'end of data is endflag
Exit Do
End If
ct = ct + 1 'get next byte
Loop
If data(ct) = endflag Then
Com3GetStr = MakeString(MemAddress(data), ct)'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
Posted: 19 February 2008, 13:27 PM
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(rq3, first, 1)
If (first = startflag) Then
Exit Do
End If
' not the start flag, discard
Call GetQueue(rq3, first, 1)
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.
Posted: 19 February 2008, 16:59 PM
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
Posted: 19 February 2008, 17:15 PM
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.
Posted: 19 February 2008, 19:55 PM
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.
Posted: 20 February 2008, 2:20 AM
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
Posted: 20 February 2008, 3:55 AM
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
Posted: 20 February 2008, 7:38 AM
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.
Posted: 20 February 2008, 20:30 PM
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
Posted: 20 February 2008, 22:31 PM
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 (1.95 ms)
messageFrameInterval = CByte(3.5 * 11.0 * CSng(Register.RTCTickFrequency) / CSng(baudrate)) + 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()
' loop forever receiving messages
Do
' check the queue to see if any data has been received
Dim count as UnsignedInteger
count = CUInt(GetQueueCount(CByteArray(inQueueAddr)))
' 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(CByteArray(inQueueAddr), messageAddress, 1)
' set to receiving state to process rest of the message
status = Receiving
Else
' nothing to do so wait a while
Call Sleep(messageFrameInterval)
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(CByteArray(inQueueAddr), CByteArray(bufAddress+dataSize), receiveCount)
dataSize = dataSize + receiveCount
count = count - receiveCount
End If
' wait to see if more data has been received during frame timeout period
Call Sleep(messageFrameInterval)
count = CUInt(GetQueueCount(CByteArray(inQueueAddr)))
' 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(messageFrameInterval)
End If
Loop
End Sub
The full code is a little more complex and can be found in the application note.