Native mode problem with StatusQueue

Discussion about the ZBasic language including the System Library. If you're not sure where to post your message, do it here. However, do not make test posts here; that's the purpose of the Sandbox.
Post Reply
spamiam
Posts: 739
Joined: 13 November 2005, 6:39 AM

Native mode problem with StatusQueue

Post by spamiam »

I am writing a program in Native mode and I went back to some of my old stuff to get some LCD display modules. These had been working when I had used them on VM devices.

Now I am writing code for native mode and I am getting an infinite loop.

I have the following subroutine:

Code: Select all

Public Sub LCDWaitComplete()

Debug.Print "Entering WaitComplete"

	do while StatusQueue(OutputQueue)		'loop while there are characters to send
	'Debug.Print "StatusQueue loop "	'loop never exits if this line is commented!
	loop
	
Debug.Print "StatusQueue Complete "
	
	do while StatusCom(3)>3		'loop while there is data in the comport
	loop
Debug.Print "Exiting WaitComplete"

End Sub
When I run the code as listed it never exits the statusqueue check loop. If I do a Debug.Print, then it works fine. Is this a known issue? Am I doing something wrong?

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

Post by mikep »

Does it still fail when you turn off optimization using the --optimize=no-optimize compiler flag? Put this flag in your PJT file or on the command line for running the ZBasic compiler.

BTW You are better off putting a sleep in your busy loop so other tasks can do work. This looks like a relatively slow operation. Here is code I have used to do a send with wait for completion.

Code: Select all

Private Sub waitForSend()		
	' get size of the transmit buffer
	Dim count as Integer
	count = GetQueueCount(outQueue)
	If count > 0 Then	
		' the wait time to send the remaining bytes in the
		' transmit buffer is based on the baud rate
		Call Sleep(CUInt(CLng(count)*5000\BAUDRATE + 1))
		
		' wait any remaining time just in case there are
		' byte left to transmit - this should happen rarely
		Do While GetQueueCount(outQueue) > 0
			Call Sleep(1)
		Loop
	End If
End Sub
Last edited by mikep on 09 June 2009, 19:40 PM, edited 1 time in total.
Mike Perks
spamiam
Posts: 739
Joined: 13 November 2005, 6:39 AM

Post by spamiam »

Mike,

Yes, It still does fail with the --optimize=no-optimize line in the project file.

I get the same failure if I check for GetQueueCount(OutputQUeue) > 0. If any Debug.Print appears inside the loop, then it works properly! Odd.

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

Re: Native mode problem with StatusQueue

Post by dkinzer »

spamiam wrote:When I run the code as listed it never exits the statusqueue check loop.
I suspect that if you put a Sleep() call in the StatusQueue() check loop it will work. As Mike suggested, this is generally advisable anyway.

As a side note, if the queue you're checking is the output queue for channel 3, it is sufficient to call only StatusCom(). The &H04 bit in the return value will be asserted if there is data in the output queue or if a byte is in the process of being transmitted.

Generally speaking, you should check for the presence of bits of interest rather than comparing the return value with an integral value. The reason is that if we add some higher order bit in the future your test may fail but the bit test will still work correctly.

Code: Select all

Const TxBusy as Byte = &H04
Do While CBool(StatusCom(3) And TxBusy)
  Call Sleep(0.05)
Loop
- Don Kinzer
spamiam
Posts: 739
Joined: 13 November 2005, 6:39 AM

Post by spamiam »

Don,

Good point re: the TXBusy bit. I wrote this code a LONG time ago, and it was just a quick and dirty approach that ended up working so well that I never went back to clean it up! Now, it appears that ZBasic has evolved enough that the old stuff is too dirty!

To add insult to injury, I just blew out my serial backpack (an old Sparkfun unit which I just plugged in 1 pin off, so it Vcc went GND, and Gnd went to the serial RX pin) and I had to start using one of my own serial backpacks that is compatible with the NetMedia serial LCD interface, not the Sparkfun. So, I just had to adapt the API to fit my serial backpack too!

Now the wiat routine is working with Mike's sleep. Now I have to remove the no optimize flag in the PJT file.

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

Post by mikep »

I put the following stand-alone test code around your function and it does hang without the sleep as you have observed.

Code: Select all

Private OutputQueue(1 to 32) as Byte
Private TaskStack(1 to 100) as Byte

Public Sub Main()
		Dim b as Byte
		Call OpenQueue(OutputQueue, 32) 
		Call PutQueueStr(OutputQueue, "Bug")
		CallTask LCDWaitComplete, TaskStack
		Call GetQueue(OutputQueue, b, 1)
		Debug.Print Chr(b)
		Call Sleep(0.1)
		Call GetQueue(OutputQueue, b, 1)
		Debug.Print Chr(b)
		Call Sleep(0.1)
		Call GetQueue(OutputQueue, b, 1)
		Debug.Print Chr(b)
		Call Sleep(2)
		Debug.Print "Consumed Queue"
End Sub
Busy loops with no idle time are generally a bad thing. The testcase above using a separate consumer task illustrates why you need a sleep in your busy wait loop. It is just not so obvious with the COM3 serial port.

I suspect that in the ZVM version, the task dispatcher has a chance to run and allows the software UART to transmit the characters. Perhaps a similar facility is needed is some of the zxLib functions to allow other tasks to run when the time slice has expired.

P.S. I had a small browser problem so this append arrived later than I wanted. Sorry to hear about your back pack.
Mike Perks
spamiam
Posts: 739
Joined: 13 November 2005, 6:39 AM

Post by spamiam »

mikep wrote:I put the following stand-alone test code around your function and it does hang without the sleep as you have observed.

SNIP

Busy loops with no idle time are generally a bad thing. The testcase above using a separate consumer task illustrates why you need a sleep in your busy wait loop. It is just not so obvious with the COM3 serial port.

I suspect that in the ZVM version, the task dispatcher has a chance to run and allows the software UART to transmit the characters. Perhaps a similar facility is needed is some of the zxLib functions to allow other tasks to run when the time slice has expired.

P.S. I had a small browser problem so this append arrived later than I wanted. Sorry to hear about your back pack.
All in all, while the Native mode implementation of the loop is so tight as to not allow task switching (or so it appears), I think that it is a GOOD THING!

I'd rather have tight loops be very efficient rather than ALWAYS be saddled with inefficient loops. I have learned my lesson in tight loops inside a nultitasking architecture. While I love ZBasic's ability to multitask, my instincts are most definitely of a single tasking (with hardware interrupts) nature! It does not automatically seem to me to be a bad idea to burn time in a tight loop waiting for something else to happen. The lesson is learned, I hope. I just wonder where else in some of my old code I have these little gremlins....

I will try out the code you suggested.

As for the dead backpack, well, I just have to assemble another backpack of my own for the dead unit. I have a bunch of PCBs and parts readfy to go. I was using the Sparkfun display because it was the only 4-line LCD display that I have on hand. All the rest are 2 line displays, though with nice BIG characters. This way I get to test out my backpack code's ability to be set to more than a 2 line display. I wrote that code for the backpack, but never actually used it!

Also, the backpack alone from Sparkfun is not all that costly.

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

Post by dkinzer »

mikep wrote:I put the following stand-alone test code around your function and it does hang without the sleep as you have observed.
This is caused by a problem with the way that StatusQueue() is implemented in native mode, now corrected.

You can correct the problem by loading the file zxlib.h and locating the definition of the macro getQueueMember (around line 420 depending on the version of ZX Library you have). That definition will look like this:

Code: Select all

#define getQueueMember(qp,mbr,mbrType)	 \
({ \
  uint8_t flag = dsblInt(); \
  mbrType val = ((Queue *)(qp))->mbr; \
  restInt(flag); \
  val;	 \
})
In the fourth line, add the keyword volatile before the word Queue so that it looks like this:

Code: Select all

#define getQueueMember(qp,mbr,mbrType) \
({ \
  uint8_t flag = dsblInt(); \
  mbrType val = ((volatile Queue *)(qp))->mbr; \
  restInt(flag); \
  val;	 \
})
Note that the number of spaces/tabs preceding the backslashes on the lines above is insignificant. However, when present, the backslash must be the last character on the line.
- Don Kinzer
spamiam
Posts: 739
Joined: 13 November 2005, 6:39 AM

Post by spamiam »

Don, I am constantly impressed. I can't tell you how many times I have searched and searched for the cause of a problem only to eventually find that it was the absence of the volatile keyword, and you found it in a trice! That macro you list may look simple to some people, but I can tell even without looking at the rest of the file (yet) that this is just a small piece. And this small piece is very impressive. The design and implementation of two whole compilers for Zbasic is actually a work of art and a thing of beauty!

And the problem wasn't something with an inhibited task switch, but a problem with a volatile pointer. I must admit that I had not expected that the queue pointer would change from time to time.

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

Post by dkinzer »

spamiam wrote:I must admit that I had not expected that the queue pointer would change from time to time.
As used, the volatile keyword expresses that the item pointed to by the pointer, rather than the pointer itself, is volatile. In the absence of the volatile keyword, the backend compiler was free to move the reference to the queue member out of the loop corresponding to the "Do While ... Loop". With volatile present, the compiler must generate code to read the queue member on each pass through the loop.

As an aside, to express that a pointer is volatile, one would write something like:

Code: Select all

char * volatile foo;
In C, the definitions are read from the "inside out". The definition above is read as: "foo is a volatile pointer to a char". In contrast, the definition

Code: Select all

volatile char *foo;
is read as "foo is a pointer to a char that is volatile".
- Don Kinzer
Post Reply