tags:

views:

571

answers:

10

I am using a microcontroller with a C51 core. I have a fairly timeconsuming and large subroutine that needs to be called every 500ms. An RTOS is not being used.

The way I am doing it right now is that I have an existing Timer interrupt of 10 ms. I set a flag after every 50 interrupts that is checked for being true in the main program loop. If the Flag is true the subroutine is called. The issue is that by the time the program loop comes round to servicing the flag, it is already more than 500ms,sometimes even >515 ms in case of certain code paths. The time taken is not accurately predictable.

Obviously, the subroutine cannot be called from inside the timer interrupt due to that large time it takes to execute.The subroutine takes 50ms to 89ms depending upon various conditions.

Is there a way to ensure that the subroutine is called in exactly 500ms each time?

A: 

You probably need finer resolution in your timekeeping. I would increment a global counter on every timer interrupt, then in your main loop you can decide whether or not to call the regular processing functions or not (I assume you have some normal processing going on in addition to this big function).

Suppose your normal functions take no more than 20 ms to execute. In your main loop, if you see that there are less than two ticks until the next time you want to call the big function, then just wait around until it is time, then call the big function. Otherwise, if there is sufficient time left, call your normal processing functions.

The requirement of this method is that you need to be able to state an upper bound on the amount of time your normal processing functions will take. You said that the time is "not accurately predictable", which is not really compatible with real-time requirements.

Greg Hewgill
+1  A: 

I think you have some conflicting/not-thought-through requirements here. You say that you can't call this code from the timer ISR because it takes too long to run (implying that it is a lower-priority than something else which would be delayed), but then you are being hit by the fact that something else which should have been lower-priority is delaying it when you run it from the foreground path ('program loop').

If this work must happen at exactly 500ms, then run it from the timer routine, and deal with the fall-out from that. This is effectively what a pre-emptive RTOS would be doing anyway.

If you want it to run from the 'program loop', then you will have to make sure than nothing else which runs from that loop ever takes more than the maximum delay you can tolerate - often that means breaking your other long-running work into state-machines which can do a little bit of work per pass through the loop.

Will Dean
I cannot call it from the ISR because the timer is non-reentrant, and i need to return from the timer ISR before the next timer interrupt occurs.
Vaibhav Garg
You didn't deserve that down-vote that I saw...
Toybuilder
A: 

Would this do what you need?

#define FUDGE_MARGIN 2    //In 10ms increments

volatile unsigned int ticks = 0;

void timer_10ms_interrupt( void )  { ticks++; }

void mainloop( void )
{
    unsigned int next_time = ticks+50;

    while( 1 )
    {
        do_mainloopy_stuff();

        if( ticks >= next_time-FUDGE_MARGIN )
        {
            while( ticks < next_time );
            do_500ms_thingy();
            next_time += 50;
        }
    }
}

NB: If you got behind with servicing your every-500ms task then this would queue them up, which may not be what you want.

This approach has a problem with wrapping: if you exit the if-statement with ticks == 65500 and next_time == 14 (65550-65536), you'll execute your 500ms code every time through the loop until ticks wraps around as well. Also you should increase next_time BEFORE calling do_500ms().
paxdiablo
And since you're adding 50 to next_time each time you do_500ms, it may well be into the hundreds or thousands by the time ticks wraps around which will be a huge delay before calling do_500ms() the next time.
paxdiablo
I have used this already with the following modification, may be Mike F. can incorporate this: I have reset the ticks to 0 just before servicing the 500 ms subroutine. and fixed next_time to 50.
Vaibhav Garg
There's another problem with this -- ticks is not atomically accessed; and since it's a two byte value, it's possible for the interrupt to fire in the middle of the comparison operation, and cause a misreading of ticks.
Toybuilder
Right, exactly why we have set ticks as an unsigned character. Its value should never exceed 50 following what I suggested.
Vaibhav Garg
+1  A: 

I don't think there's a way to guarantee it but this solution may provide an acceptable alternative.

Might I suggest not setting a flag but instead modifying a value?

Here's how it could work.

1/ Start a value at zero.

2/ Every 10ms interrupt, increase this value by 10 in the ISR (interrupt service routine).

3/ In the main loop, if the value is >= 500, subtract 500 from the value and do your 500ms activities.

You will have to be careful to watch for race conditions between the timer and main program in modifying the value.

This has the advantage that the function runs as close as possible to the 500ms boundaries regardless of latency or duration.

If, for some reason, your function starts 20ms late in one iteration, the value will already be 520 so your function will then set it to 20, meaning it will only wait 480ms before the next iteration.

That seems to me to be the best way to achieve what you want.

I haven't touched the 8051 for many years (assuming that's what C51 is targeting which seems a safe bet given your description) but it may have an instruction which will subtract 50 without an interrupt being possible. However, I seem to remember the architecture was pretty simple so you may have to disable or delay interrupts while it does the load/modify/store operation.

volatile int xtime = 0;
void isr_10ms(void)  {
    xtime += 10;
}
void loop(void) {
    while (1) {
        /* Do all your regular main stuff here. */
        if (xtime >= 500) {
            xtime -= 500;
            /* Do your 500ms activity here */
        }
    }
}
paxdiablo
The bucket fill/deplete approach is good. However, you are accessing a multi-byte variable without protection. During the comparison or subtration, one half of the int could be processed, and then the ISR fires and increments the variable, and then the second half of the int it processed => error.
Toybuilder
I suppose you could scale the values down by 10 and use a volatile byte; that would hopefully reduce the modification to an non-interruptable one.
paxdiablo
+1  A: 

A good option is to use an RTOS or write your own simple RTOS.

An RTOS to meet your needs will only need to do the following:

  • schedule periodic tasks
  • schedule round robin tasks
  • preform context switching

Your requirements are the following:

  • execute a periodic task every 500ms
  • in the extra time between execute round robin tasks ( doing non-time critical operations )

An RTOS like this will guarantee a 99.9% chance that your code will execute on time. I can't say 100% because whatever operations your do in your ISR's may interfere with the RTOS. This is a problem with 8-bit micro-controllers that can only execute one instruction at a time.

Writing an RTOS is tricky, but do-able. Here is an example of small ( 900 lines ) RTOS targeted at ATMEL's 8-bit AVR platform.

The following is the Report and Code created for the class CSC 460: Real Time Operating Systems ( at the University of Victoria ).

Justin Tanner
A: 

One straightforward solution is to have a timer interrupt that fires off at 500ms...
If you have some flexibility in your hardware design, you can cascade the output of one timer to a second stage counter to get you a long time base. I forget, but I vaguely recall being able to cascade timers on the x51.

Toybuilder
A: 

Why do you have a time-critical routine that takes so long to run?

I agree with some of the others that there may be an architectural issue here.

If the purpose of having precise 500ms (or whatever) intervals is to have signal changes occuring at specific time intervals, you may be better off with a fast ISR that ouputs the new signals based on a previous calculation, and then set a flag that would cause the new calculation to run outside of the ISR.

Can you better describe what this long-running routine is doing, and what the need for the specific interval is for?


Addition based on the comments:

If you can insure that the time in the service routine is of a predictable duration, you might get away with missing the timer interrupt postings...

To take your example, if your timer interrupt is set for 10 ms periods, and you know your service routine will take 89ms, just go ahead and count up 41 timer interrupts, then do your 89 ms activity and miss eight timer interrupts (42nd to 49th).

Then, when your ISR exits (and clears the pending interrupt), the "first" interrupt of the next round of 500ms will occur about a ms later.

Given that you're "resource maxed" suggests that you have your other timer and interrupt sources also in use -- which means that relying on the main loop to be timed accurately isn't going to work, because those other interrupt sources could fire at the wrong moment.

Toybuilder
The architecture is a dual processor 1. The secondary processor ( whose source code can not be changed) integrates wrt time the outputs of several ADCs. As the integration is discrete, it is just a summation at the sampling rate.
Vaibhav Garg
Now if the value of the integrated op is not read and reset in equal intervals, the values will vary despite having constant IPs. Hope that makes sense. As there are several integrated outputs being processed parellely, all of them need to be read and reset in the same routine, hence the length.
Vaibhav Garg
See my other comment about using lower-level priority interrrupts to make an ISR that handles the work... (I edited to add a little more.)There's something still strange here, though -- why does the time to read/reset the integrator processor so variable??
Toybuilder
The time "to" read/reset the integrator variable is not variable in the strict sense( varies from 50 to 89 ms). It is just that some inputs are not relevent in certain cases and the subroutine leaves them alone. There are more than 24 inputs being dealt with.Thanks for the link. I'll have a look.
Vaibhav Garg
The ISRs are all optimised for speed and run under 1 ms each. So even in the worst case there is an error of only a insignificant duration.
Vaibhav Garg
A: 

Ah, one more alternative for consideration -- the x51 architecture allow two levels of interrupt priorities. If you have some hardware flexibility, you can cause one of the external interrupt pins to be raised by the timer ISR at 500ms intervals, and then let the lower-level interrupt processing of your every-500ms code to occur.

Depending on your particular x51, you might be able to also generate a lower priority interrupt completely internal to your device.

See part 11.2 in this document I found on the web: http://www.esacademy.com/automation/docs/c51primer/c11.htm

Toybuilder
The hardware is being used to the max, with no scope of any interrupts being available. Thanks for the suggestion.
Vaibhav Garg
A: 

If I'm interpretting your question correctly, you have:

  • a main loop
  • some high priority operation that needs to be run every 500ms, for a duration of up to 89ms
  • a 10ms timer that also performs a small number of operations.

There are three options as I see it. The first is to use a second timer of a lower priority for your 500ms operations. You can still process your 10ms interrupt, and once complete continue servicing your 500ms timer interrupt.

Second option - doe you actually need to service your 10ms interrupt every 10ms? Is it doing anything other than time keeping? If not, and if your hardware will allow you to determine the number of 10ms ticks that have passed while processing your 500ms op's (ie. by not using the interrupts themselves), then can you start your 500ms op's within the 10ms interrupt and process the 10ms ticks that you missed when you're done.

Third option: To follow on from Justin Tanner's answer, it sounds like you could produce your own preemptive multitasking kernel to fill your requirements without too much trouble. It sounds like all you need is two tasks - one for the main super loop and one for your 500ms task.

The code to swap between two contexts (ie. two copies of all of your registers, using different stack pointers) is very simple, and usually consists of a series of register pushes (to save the current context), a series of register pops (to restore your new context) and a return from interrupt instruction. Once your 500ms op's are complete, you restore the original context.

(I guess that strictly this is a hybrid of preemptive and cooperative multitasking, but that's not important right now)


edit: There is a simple fourth option. Liberally pepper your main super loop with checks for whether the 500ms has elapsed, both before and after any lengthy operations. Not exactly 500ms, but you may be able to reduce the latency to a tolerable level.

Andrew Edgecombe
+1  A: 

You can also use two flags - a "pre-action" flag, and a "trigger" flag (using Mike F's as a starting point):

#define PREACTION_HOLD_TICKS (2)
#define TOTAL_WAIT_TICKS (10)

volatile unsigned char pre_action_flag;
volatile unsigned char trigger_flag;

static isr_ticks;
interrupt void timer0_isr (void) {
   isr_ticks--;
   if (!isr_ticks) {
      isr_ticks=TOTAL_WAIT_TICKS;
      trigger_flag=1;
   } else {
      if (isr_ticks==PREACTION_HOLD_TICKS)
          preaction_flag=1;
   }
}

// ...

int main(...) {


isr_ticks = TOTAL_WAIT_TICKS;
preaction_flag = 0;
tigger_flag = 0;
// ...

   while (1) {
      if (preaction_flag) {
          preaction_flag=0;
          while(!trigger_flag)
             ;
          trigger_flag=0;
          service_routine();
      } else {
          main_processing_routines();
      }
   }
 }
Toybuilder