Shop OBEX P1 Docs P2 Docs Learn Events
How to use waitcnt for long delays (10+ minutes)? — Parallax Forums

How to use waitcnt for long delays (10+ minutes)?

twm47099twm47099 Posts: 867
edited 2016-02-17 04:29 in Propeller 1
As part of my learning to use the prop I want to write a program to monitor some condition (i.e. barometric pressure) over a long period of time, taking readings every 10 (or 30 or 60) minutes and saving them to an SD card.

I did write such a program, and it worked, but I wonder if I wrote the delay part as well as it could have been.

I wrote the program in C using SimpleIDE and used the waitcnt() function.
The code below shows the declarations and how I used waitcnt.
I printed the delay calculated and found that if I used an interval greater than 30 the counter rolled over and I was getting saves every few seconds. I believe that the system counter rolls over at 50 or so seconds at 80MHz, so I limited each waitcnt to 30 seconds and used a "for" loop to do as many waitcnts as needed to get the delay I wanted for each sensor reading.

Is there a better way? I am using an Activity Board for this.

Thanks
Tom
int num_mins = 10; 		// number of minutes between each reading
int interval = 30;		// delay (seconds) per waitcnt
int k, time_steps;		// loop number of waitcnts to get num_mins
unsigned int delay, tcnt;
time_steps = num_mins * 2; 	  // based on 30 second waitcnt
delay = CLKFREQ * interval;      // 30 second intervals
print("clkfreq = %d delay = %d\n", CLKFREQ, delay);  // for debugging

// Do Some Stuff to initialize sensor
...

while (1) 
{
//do some stuff, collect & save data to SD card
...
// first time through initalize tcnt with the system counter value
if(!P0) { P0 = P; tcnt = CNT;}

for(k = 0; k < time_steps; k++) waitcnt(tcnt += delay); // delay nummins
} end while loop



Comments

  • That's the general gist of how I would do it. There are small, negligible changes that could be made to increase accuracy of you want. waitcnt2 gives you access to the native instruction.
    int delay = ONE_SECOND;
    int timer = CNT + delay;
    for (int i = 0; i < SECONDS; ++i)
      timer = waitcnt2(timer, delay);
    

    This gets rid of the error that accumulates from the overhead involved in calling waitcnt.
  • AribaAriba Posts: 2,690
    You can improve the precision of the time interval if you write the CNT to tcnt only in the initialization part. As long as the "stuff" you do before the waitcnt takes not longer then 30 seconds this should work.

    As you have it now, the time to execute the code to collect and save gets added to the interval time. If this is not a problem then I think you already have the best solution.

    Andy
  • twm47099twm47099 Posts: 867
    edited 2016-02-17 06:00
    Ariba wrote: »
    You can improve the precision of the time interval if you write the CNT to tcnt only in the initialization part. As long as the "stuff" you do before the waitcnt takes not longer then 30 seconds this should work.

    As you have it now, the time to execute the code to collect and save gets added to the interval time. If this is not a problem then I think you already have the best solution.

    Andy
    Thanks for the comments.
    In cutting and pasting I put the "if (! P0)" after the saving in my example above. It is actually after calculating P but before the save.

    That if is true only the first time average P is calculated. So it sets the zero point of the 10 minute delay just after the first average P is calculated. Each subsequent waitcnt has "delay" added to the previous value.

    I thought about setting tcnt = CNT in the initialization, but the conversion takes some time and is repeated a number of times to get an average of both P and temperature. So I decided to set the "zero" after the first average was calculated.

    Tom

  • AribaAriba Posts: 2,690
    edited 2016-02-17 06:16
    Ahh Okay, then it's already as good as it can be.

    @David
    It does not really matters when you calculate the next waitcnt value and how long it takes, as long as it happens in the 30 second interval.

    Andy
  • Here's a spin example which you might find useful:
    {{
      Long interval timer using Clock.spin by Jeff Martin (Parallax)
    }}
    CON
      _clkmode =  xtal1 + pll16x    ' 4..16MHz crystal using PLL 16x multiplier
      _xinfreq = 5_000_000          ' external crystal 5MHz : System clock → 80 MHz
    
      DELTASECONDS  = 60
    
    OBJ
      CLK   : "Clock"
      pst    : "Parallax Serial Terminal"                   ' Serial communication object
    
    PRI Timer(seconds,aExpired)
    { One shot timer for one or more seconds.
      Sets the first byte at aExpired true when the timer expires.
    
      If called directly it will return to the caller upon expiration.
      
      If invoked via cognew it will run until it expires then the cog
      will go dormant and therefore be returned to the available pool
      without the need to call cogstop.  
    }
      byte[aExpired]~
      CLK.MarkSync
      repeat while seconds > 15
        CLK.WaitSyncSec(15) 
        seconds -= 15
      CLK.WaitSyncSec(seconds)
      byte[aExpired]~~ ' indicate that the timer has expired
    
    PUB RunLongIntervalTimer | i, timerStack[25], timerExpired
      pst.Start(115200)                                     ' start parallax serial terminal for output
      waitcnt(clkfreq<<1+cnt)                               ' provide a couple seconds for getting pst started
      pst.str(string("long interval timer"))
      pst.newline
      
      i~                                                           
      repeat
        cognew(Timer(DELTASECONDS,@timerExpired), @timerStack)
        repeat 
          waitcnt(clkfreq>>4+cnt) ' not actually needed
          ' the code can be doing other stuff while the timer runs, for example printing an annoying dot
          pst.char(".")
        until timerExpired ' indicates the timer has run out
        
        i+=DELTASECONDS
        pst.dec(i) ' accumulated seconds                   
        pst.newline
    
        '...ad infinitum...
    
  • I'm guessing if you wanted go this route you would have, but:

    I have some similar projects and use a RTC instead. That way I get date as well as an easy to work with time base. If I don't need great accuracy or don't need to run it for a long time, I use the RTC object in the obex. If I do need the accuracy or long term running I use a real RTC. Makes it all real easy. Of course, it costs either a cog or some pins.

    Jonathan
  • C provides the "sleep" function that sleeps for N seconds:
      sleep(10*60);
    
    should wait for 10 minutes to elapse. The timing isn't as exact as you'd get with waitcnt, but if you don't need millisecond accurate timing that might do.
  • ersmith wrote: »
    C provides the "sleep" function that sleeps for N seconds:
      sleep(10*60);
    
    should wait for 10 minutes to elapse. The timing isn't as exact as you'd get with waitcnt, but if you don't need millisecond accurate timing that might do.

    Thanks, l didn't know about sleep.
    I believe that waitcnt puts the cog into a low power mode which is attractive for a battery operated long time data recorder. Does sleep do the same?

    Thanks
    Tom
  • twm47099 wrote: »
    ersmith wrote: »
    C provides the "sleep" function that sleeps for N seconds:
      sleep(10*60);
    
    should wait for 10 minutes to elapse. The timing isn't as exact as you'd get with waitcnt, but if you don't need millisecond accurate timing that might do.

    Thanks, l didn't know about sleep.
    I believe that waitcnt puts the cog into a low power mode which is attractive for a battery operated long time data recorder. Does sleep do the same?

    No, unfortunately sleep() does a busy wait.
  • ersmith wrote: »
    twm47099 wrote: »
    ersmith wrote: »
    C provides the "sleep" function that sleeps for N seconds:
      sleep(10*60);
    
    should wait for 10 minutes to elapse. The timing isn't as exact as you'd get with waitcnt, but if you don't need millisecond accurate timing that might do.

    Thanks, l didn't know about sleep.
    I believe that waitcnt puts the cog into a low power mode which is attractive for a battery operated long time data recorder. Does sleep do the same?

    No, unfortunately sleep() does a busy wait.

    I'm shocked... and disgusted... and... what??? Isn't the concept of sleep pretty popular among various architectures? Why doesn't GCC have some kind of support to allow different architectures to implement this however they deem necessary? Or, was this a decision made by the PropGCC team?
  • DavidZemon wrote: »
    ersmith wrote: »
    No, unfortunately sleep() does a busy wait.

    I'm shocked... and disgusted... and... what??? Isn't the concept of sleep pretty popular among various architectures? Why doesn't GCC have some kind of support to allow different architectures to implement this however they deem necessary? Or, was this a decision made by the PropGCC team?

    sleep() is part of the library, not the compiler. The PropGCC library has a lot of conflicting goals. At one point there was a demand for pthreads to work with multiple threads running on the same COG, and to do that sleep() has to call into pthreads (to allow scheduling of different threads). If pthreads isn't linked then sleep ends up busy waiting.

    The pthreads hook (napuntil_ptr) could be used to do a waitcnt() instead of busy wait. In fact that's probably a good idea. It'd be great if some volunteer could step up to do this :)
  • ersmith wrote: »
    DavidZemon wrote: »
    ersmith wrote: »
    No, unfortunately sleep() does a busy wait.

    I'm shocked... and disgusted... and... what??? Isn't the concept of sleep pretty popular among various architectures? Why doesn't GCC have some kind of support to allow different architectures to implement this however they deem necessary? Or, was this a decision made by the PropGCC team?

    sleep() is part of the library, not the compiler. The PropGCC library has a lot of conflicting goals. At one point there was a demand for pthreads to work with multiple threads running on the same COG, and to do that sleep() has to call into pthreads (to allow scheduling of different threads). If pthreads isn't linked then sleep ends up busy waiting.

    The pthreads hook (napuntil_ptr) could be used to do a waitcnt() instead of busy wait. In fact that's probably a good idea. It'd be great if some volunteer could step up to do this :)

    Sounds like about as small of a bite as could possibly be taken out of a GCC contribution. I'll try and look into this. This would be the right Git repo right?
  • DavidZemon wrote: »
    ersmith wrote: »
    sleep() is part of the library, not the compiler. The PropGCC library has a lot of conflicting goals. At one point there was a demand for pthreads to work with multiple threads running on the same COG, and to do that sleep() has to call into pthreads (to allow scheduling of different threads). If pthreads isn't linked then sleep ends up busy waiting.

    The pthreads hook (napuntil_ptr) could be used to do a waitcnt() instead of busy wait. In fact that's probably a good idea. It'd be great if some volunteer could step up to do this :)

    Sounds like about as small of a bite as could possibly be taken out of a GCC contribution. I'll try and look into this. This would be the right Git repo right?

    Yes, that's the right repo. I've added you as a collaborator. The guts of the code are in sys/propeller/nap.c (the __napuntil function). The whole "busy wait calling yield" code should probably be replaced with a waitcnt call. I think it was left there during a transition phase while we were updating pthreads and it didn't set the __napuntil_ptr, but pthreads does do that now (this is defined by the REAL_SLEEP define in pthreads/pthread_create.c). So the update itself is trivial. Testing it is the hard part, but it probably won't be too bad.

    Thanks,
    Eric
  • jmgjmg Posts: 15,173
    twm47099 wrote: »
    I believe that waitcnt puts the cog into a low power mode which is attractive for a battery operated long time data recorder. Does sleep do the same?

    If power matters, you could look at lowering the SysCLK during Sleep.
    5MHz can get directly to 10 minutes.

Sign In or Register to comment.