Forum Index
HomeZBasic Home   Forum RulesForum Rules   Forum FAQForum FAQ   MemberlistMemberlist   UsergroupsUsergroups   RSS FeedRSS Feed
Site SearchSite Search   LinksLinks   DownloadDownload   Digests and SubscriptionsDigests and Subscriptions
ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in   RegisterRegister
recommendation on task prioritization
Goto page 1, 2  Next
 
Post new topic   Reply to topic    Forum Index -> ZBasic Language
Author Message
stevech



Joined: 23 Feb 2006
Posts: 657

Posted: 13 October 2006, 2:11 AM    Post subject: recommendation on task prioritization Reply with quote

The ZBasic program I've developed works perfectly except for one thing: one of the tasks that must run every 250mSec sometimes does not. This happens when other tasks are sending a lot of data (about 5 lines of text, about 30 chars per line) out the hardware uart at 19.2K. Creating this uart output does a lot of formatting of numbers, etc., so there's quite a bit of math. I did code up a special console.output which does a sleep if it's hogging RAM, as shown below, but it didn't cure the problem.

I'm looking for suggestions on how to prioritize this one special task. Each time it runs, it does very little code, no calculations, then again calls waitForInterrupt().

I have been assuming that preemption in task scheduling would save my bacon, as they say.

EDIT: I also have two tasks that interrupt 300 times per second. At each interrupt, they increment a counter and that's all. My issue is that every 1/4 second a task is checking to see how many interrupts happened - and the number is too small. It makes more sense, since the numbers are small, that in that 1/4 or more second that too many of the 300x2 interrupts per second were lost.

I don't have any lockTask() sections that do I/O that might block. I do use lockTask() for one line of code in the task that wakes up upon interrupt so that it's manipulation of the integer is assuredly atomic.


Code:
sub consoleWriteLineWait(byRef s as string)
   Console.WriteLine(s)
   do
      sleep(10)
   loop while (GetQueueCount(CByteArray(Register.TxQueue)) <> 0)
end sub
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Posts: 2499
Location: Portland, OR

Posted: 13 October 2006, 16:05 PM    Post subject: Reply with quote

The Console.WriteLine() may be causing the problem. This routine, along with Debug.Print and Console.Write(), essentially locks the calling task until the string is completely transferred to the queue. The default output queue is fairly small - it only has space for a few characters. That means that, worst case, the call will require (N * 10) / baud seconds to complete. At 19.2K baud, each character requires 0.5mS to transmit.

In comparison, an interrupt at 300Hz represents a period of 3.3mS. Waiting for the outputting more than 6 characters or so will cause you to miss servicing at least one of the two interrupts.

You may be able to solve this problem by defining a generous output queue and using OpenCom() to assign the queue to Com1. If the queue has plenty of space each time Console.WriteLine() is invoked it will copy the data to the queue and return immediately rather than having to wait for most of the characters to be transmitted.
Back to top
stevech



Joined: 23 Feb 2006
Posts: 657

Posted: 13 October 2006, 17:11 PM    Post subject: Reply with quote

Thanks for the comments, Don...
I'm doing the serial output from main(); the two tasks that loop on WaitForInterrupt() 300 times/sec are of course not main, and they do no serial I/O. So I hopefully assummed correctly that if main() blocks on console.write, this would not add latency to the two high rate tasks.

As I read the the documentation, the tasks sleeping on waitForInterrupt() will be put in the head of the ready-to-run queue in the scheduler. Literally? Always ahead of all others? And if I do a locktask() right after waitForInterrupt(), does that task risk being rescheduled due to that system call?
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Posts: 2499
Location: Portland, OR

Posted: 13 October 2006, 17:39 PM    Post subject: Reply with quote

stevech wrote:
So I hopefully assumed correctly that if main() blocks on console.write, this would not add latency to the two high rate tasks.

But it does. Debug.Print, Console.Write() and Console.WriteLine() are handled differently from PutQueueStr(). The reason for the difference is the small default queue size for the "system" routines. PutQueueStr() will block until the entire string can be placed in the queue. If this were used with the system output routines you could never output a string longer than the queue capacity.

If you invoke Debug.Print et al, no other task can possibly get control until all of the characters are copied to the output queue (0.5mS per character, worst case). To reduce this latency, use a larger queue for Com1 as described earlier.

You could also switch to using PutQueueStr(). This routine doesn't block task switching like the system output routines do. On the other hand, the total latency for either method will be nearly the same if there is sufficient space in the queue for the string being output. The difference between the methods will arise when there is not sufficient space in the queue at the time of the call. In this situation, the task calling PutQueueStr() will immediately relinquish control but the system output routines will block all task switching until the string's characters are copied to the queue.

This should be described more clearly in the documentation.

[Edit: the currently published documentation is actually incorrect. It says that another task can run while awaiting transfer of the string to the output queue.]

stevech wrote:
As I read the the documentation, the tasks sleeping on waitForInterrupt() will be put in the head of the ready-to-run queue in the scheduler. Literally? Always ahead of all others? And if I do a locktask() right after waitForInterrupt(), does that task risk being rescheduled due to that system call?

A task awaiting an interrupt will be the next task to run when the interrupt occurs (subject to priority of multiple such interrupts) as soon as the next task can run. A locked task prevents switching to the next ready-to-run task.
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Posts: 2499
Location: Portland, OR

Posted: 13 October 2006, 20:20 PM    Post subject: Reply with quote

dkinzer wrote:
A locked task prevents switching to the next ready-to-run task.

I had second thoughts about the accuracy of this statement after I posted it so I checked the code. A locked task does not prevent a task switch to an interrupt task upon receipt of the interrupt. The lock only prevents the normal time-based task switching.
Back to top
stevech



Joined: 23 Feb 2006
Posts: 657

Posted: 14 October 2006, 2:18 AM    Post subject: Reply with quote

an "interrupt task" is one suspended in a WaitForInterrupt(), right? Once that task resumes, it is an ordinary task, right?

So it will preempt even a locked task. Hmmm. This makes exclusion a bit more tricky than I had done. I hate to see the interrupt task loop and delay on a semaphore to get mutual exclusion with another task that is accessing the same multi-byte datum.

secondly, please clarify for this code

call waitForInterrupt()
call lockTask()

if the second line of code could cause the (interrupt task?) to lose control and be preempted by the scheduler.

( I'm feeling like we're talking about a heavy duty RTOS on a minicomputer here, rather than an itty bitty 8 bit micro)
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Posts: 2499
Location: Portland, OR

Posted: 14 October 2006, 3:11 AM    Post subject: Reply with quote

stevech wrote:
an "interrupt task" is one suspended in a WaitForInterrupt(), right? Once that task resumes, it is an ordinary task, right?

I've used "interrupt task" to connote a task that is awaiting an interrupt.

stevech wrote:
So it will preempt even a locked task. Hmmm.

It was decided that a task awaiting an interrupt is likely to need to execute fairly quickly. Hence, LockTask() does not prevent an interrupt task from getting control when the interrupt condition is satisfied.
stevech wrote:
This makes exclusion a bit more tricky than I had done. I hate to see the interrupt task loop and delay on a semaphore to get mutual exclusion with another task that is accessing the same multi-byte datum.

You could alternately use a queue to transmit data from an interrupt task to another task that utilizes the semaphore for exclusive access.

stevech wrote:
secondly, please clarify for this code

call waitForInterrupt()
call lockTask()

if the second line of code could cause the (interrupt task?) to lose control and be preempted by the scheduler.

After the task executes LockTask(), it can only be preempted by another task whose WaitForInterrupt() has been satisfied.

We may need to extend LockTask() to specify a higher level of locking that is not preemptable.
Back to top
stevech



Joined: 23 Feb 2006
Posts: 657

Posted: 14 October 2006, 4:28 AM    Post subject: Reply with quote

thanks - now off to er, improve my design!
Back to top
spamiam



Joined: 13 Nov 2005
Posts: 665

Posted: 15 October 2006, 12:05 PM    Post subject: Reply with quote

I have been following this thread with some interest. I have not been brave enough to attempt multiple high-rate interrupt tasks, along with complicated foreground math and comm.

I am not sure I understand the essence of your problem.

It sounds as if you are losing interrupts in the 300Hz inputs

This seems to be due to latency plus the console functions locking (all?) interrupts. Is this correct?

I do not understand how a higher-level task locking will help in this instance. Is one 300Hz task interrupting the other? Or is the console comm function locking out everything, including the high rate interrupts.

Have you considered the possibility of skinning the cat in another way? Maybe use the counter functions of a timer? I think you may have said that all the timers are in use, so it may not be possible. Plus, you might have only 1 timer available anyway!

-Tony
Back to top
stevech



Joined: 23 Feb 2006
Posts: 657

Posted: 15 October 2006, 17:50 PM    Post subject: Reply with quote

The challenges I'm facing are two:

1. 300 interrupts per second times two sources = 600 task schedules per second. I've tried to code each task to be as brief as possible.

2. Each task that awakes from waitForInterrupt() has to alter global variables. I'm trying to figure a way to provide atomic access and mutual exclusion between the interrupt task and any other task. After the interrupt task is scheduled to awake and run, it is like any other task. So it can be preempted without a lockTask(), adding more overhead.

The 300 x 2 interrupts/sec are produced by external hardware.

So what I did is move code into the "interrupt response" task that was previously in a different task, making the interrupt longer to run and then sleep again, but eliminating the need for exclusion. I think.

I am still seeing lots of lost interrupts when some task does console.write. One or two lines of output doesn't hurt; but 5 or 10 causes the interrupt counters to show that 1/4 of the interrupts were missed. A sleep() for each line of text didn't help. It's acting like the waitForInterrupt() task either isn't preempting the other task, or there is too much scheduler latency for what I'm trying to do at the VM code level.

In my application, this doesn't hurt. But for academic curiousisty, I'm trying to understand.

I may be asking too much of the VM - 600 task schedules per second is a lot. And meanwhile, the AVR is also getting 1024 interrupts/sec (I think) for the real time clock. Now those interrupts are no doubt handled in tight assembly language inside the VM, but it is a lot of context switching for the humble little microprocessor.

as to skinning the cat another way... I should revisit, but as I recall, there was no way to count pulses from two different streams
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Posts: 2499
Location: Portland, OR

Posted: 15 October 2006, 18:33 PM    Post subject: Reply with quote

stevech wrote:
I am still seeing lots of lost interrupts when some task does console.write. One or two lines of output doesn't hurt; but 5 or 10 causes the interrupt counters to show that 1/4 of the interrupts were missed.

It may help to switch to using PutQueueStr() instead of Console.Write(). If there is sufficient space in the queue when these are invoked, there will be no difference. If there is insufficient space, PutQueueStr() will allow the next task to run and try again later (somewhat like an implied Sleep(0)). In contrast, Console.Write() just sits in a tight loop way down in the VM code until it can transfer to the queue all of the characters in the passed string, during which time no other tasks will be allowed to run.

If you've already enlarged the system output queue (see previous message in this thread) you may still be running into the Console.Write() blocking if the queue's data space is not large enough to all of the strings that are written within a short period of time.
Back to top
spamiam



Joined: 13 Nov 2005
Posts: 665

Posted: 15 October 2006, 20:33 PM    Post subject: Reply with quote

Quote:
as to skinning the cat another way... I should revisit, but as I recall, there was no way to count pulses from two different streams


Well, the ATMega32, 644, and 644p are all similar in this respect:

Timer 0 and Timer 1 have simple external clock sources (Timer 0 uses PB0, Timer 1 uses PB1).

In principle these 2 could be used to count 2 streams. On the ZX hardware Timer 0 is in use for the Operating system and is not available for any other purpose.

Timer 1 could still be used.

How about Timer 2? It is "optimized for a 32.767Khz watch crystal." It can use an external clock. It uses TOSC1 and TOSC2 on PC6 and PC7 respectively. I believe that with the right signals on PC6 and PC7 one can get Timer 2 to function like Timer 1. Use CTC mode and set it to trigger on a count of 0. It should trigger an interrupt on every clock cycle.

But how to use TOSC1 and TOSC2? Can you just ground TOSC2 and clock TOSC1? I have never tried this, but I think it can be done this way.

-Tony
Back to top
stevech



Joined: 23 Feb 2006
Posts: 657

Posted: 15 October 2006, 20:56 PM    Post subject: Reply with quote

how about this idea...

each of my 300Hz signals comes from an opto-isolator's NPN transistor output. Currently, the emitters are grounded and the collectors connect to the INT0 and INT1 pins.

If I connect each emitter to a different output bit of the AVR, I can tri-state the output to disable the transistor. Then, I connect the collectors together and tie these to the timer's external clock. Now I should be able to choose which pulse train goes into the counter (without adding chips).

The flaw in this might be that the Vce of the transitor adds to the Vout(Low) of the AVR and this might be too high of a voltage to qualify as a logic zero. Have to read the data sheets.
Back to top
spamiam



Joined: 13 Nov 2005
Posts: 665

Posted: 16 October 2006, 1:24 AM    Post subject: Reply with quote

It sounds as if it wil work. I would assume that "low" would be as high as 0.7V x 2 = 1.4V. I think that this still qualifies as "low" for the AVR hardware.

Does this mean that you are interested in only one interrupt source at a time?

-Tony
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Posts: 2499
Location: Portland, OR

Posted: 16 October 2006, 2:02 AM    Post subject: Reply with quote

stevech wrote:
If I connect each emitter to a different output bit of the AVR, I can tri-state the output to disable the transistor.

What you're implementing is essentially a multiplexor but one that requires two outputs to control it. A simpler solution would be to use a multiplexor like the 74HC157 - it only requires a single line to select which input is routed to the output. If you wanted the option to have no inputs routed to the output you can utilize the '157's enable line.

Another solution is to use two AND (74HC08), OR (74HC32), NAND (74HC00) or NOR gates (74HC02). If you choose NAND or NOR, you can use one of the remaining gates in the package as an inverter and build a 2-1 multiplexor using three gates from the package.
Back to top
Display posts from previous:   
Post new topic   Reply to topic    Forum Index -> ZBasic Language Time synchro. with the server - Timezone/DST with your computer
Goto page 1, 2  Next
Page 1 of 2

 


All content Copyright © 2005-2012 Elba Corp. All Rights Reserved.
Opinions expressed in posts are those of the author and not necessarily those of Elba Corp.
Powered by phpBB © 2001, 2005 phpBB Group