Shop OBEX P1 Docs P2 Docs Learn Events
What would be the best way to use a P1 cog for real time keeping? — Parallax Forums

What would be the best way to use a P1 cog for real time keeping?

I've looked but it doesn't seem this topic has ever been covered here. I could be wrong about that though.

Working on a project that needs time keeping capability. It would seem the most obvious solution would be to use a separate RTC chip but have no free pins, even P30 and 31. However, it doesn't really matter that much in this case, the time could be easily be reset in event of a power failure or occasionally adjusted for drift, but this would be somewhat of an inconvenience. So its worth the effort to make the most accurate timekeeping method accomplish-able using only the prop itself. I'm hoping someone here has done this before or has some insight on the best approach to this.

Presently using an FC5BQCCMC18.432 crystal but if it would make a difference, could easily swap to something different as long as it is somewhat compact.

Here's what I'm using now:

CON

_CLKMODE = XTAL1 + PLL4X

_XINFREQ = 20_000_000

Then I have the following running in a separate cog. The seconds and hours are global variables which along with minutes are manipulated in the main program to adjust to a real time output.


PUB timeCount

repeat

   repeat 258_500
   seconds++

    if seconds > 3599  
       seconds := 0

hours++

I'm finding this rather terrible though. No matter how much I bugger the value in the repeat line, time is not consistent, sometimes too fast other times too slow probably depending on other processing going on. ERF!

Would anyone know of a better way of doing this?

Comments

  • The spin interpreter may not have consistent performance for delay loops. But even if the delay was consistent, every hour your code will take a little bit longer to run. This would need to be compensated somehow.

    You'll want to use WAITCNT to trigger the rest of the code exactly once per second.

    https://www.parallax.com/package/propeller-manual/ pg 220 for an example of waitcnt Synchronized Delay This also explains the trouble of using a fixed delay in time critical code.

  • jmgjmg Posts: 15,140
    edited 2023-03-03 04:10

    @cwyuzik said:
    Presently using an FC5BQCCMC18.432 crystal
    CON
    _CLKMODE = XTAL1 + PLL4X
    _XINFREQ = 20_000_000

    I'm not sure that's actually legal on a P1 ?
    The PLL design means the XIN is limited - Data says : XIN (4 MHz to 8 MHz with Clock PLL running)
    If you disable the PLL, and just use the 18.432 crystal with _XINFREQ = 18_432_000 directly, you could see if the wobble has gone away ?

    @cwyuzik said:
    However, it doesn't really matter that much in this case, the time could be easily be reset in event of a power failure or occasionally adjusted for drift, but this would be somewhat of an inconvenience.
    So its worth the effort to make the most accurate timekeeping method accomplish-able using only the prop itself.

    The 'most accurate timekeeping method accomplish-able using only the prop itself' will depend on the clock source. How accurate do you actually need ?

    You can measure the crystal, by setting a divided pin for testing, and store that in EEPROM as a correction/trim value. That gets fixed room temp/soldering offsets accounted for, leaving temperature and aging.

    Ordinary crystals can come as good as about ±10ppm initial, plus ±10ppm drift over -20°C ~ 70°C, and they age a few ppm per year.
    If you need better than that, a TCXO is the next step, with 19.2MHz and 26.00MHz being popular and thus low cost values. (also made are 32MHz and 38.4MHz and 52MHz)
    A TCXO might spec ± 0.5ppm max.@ -30 ~ +85℃, Aging ± 1.0ppm max 1Year, Reflow soldering ± 1.0ppm max 2times

    Addit: The TCXO's are commonly 'clipped sine' output, but another thread reports CAP coupling a clipped sine 26MHz osc into P1 XIN, in XTALx1 mode, worked fine. (Pick lowest Cin setting)

  • JonnyMacJonnyMac Posts: 8,912
    edited 2023-03-03 00:18

    You can share the EEPROM pins (28 and 29) with other I2C devices, including a compatible RTC. I've done this many times. You just have to ensure the RTC will tolerate 400kHz I2C because the boot process of the P1 runs I2C to the EE at about 200kHz, and slow devices (limited to 100kHz) may react poorly and corrupt the EE access. I have seen this, but usually not with stand-alone chips; it usually happens with an I2C display that is using a small micro and runs low-speed I2C only.

    If you want to use a background cog, that's fine so long as you don't need to maintain the clock when the P1 resets. You'll want to use a synchronized loop (using waitcnt) to keep your background clock as accurate as your P1 system clock. I've have deployed code like this many times.

    var
    
      byte  scs
      byte  mns
      byte  hrs
      byte  day
    
    
    pri bkg_clock | t
    
      t := cnt
      repeat
        waitcnt(t += clkfreq)
        if (++scs == 60)
          scs := 0
          if (++mns == 60)
            mns := 0
            if (++hrs == 24)
              hrs == 0
              if (++day == 7)
                day := 0
    

    I know Spin is "slow" but one can get a lot of work done is a short amount of time. I tend to make may background code run every millisecond and pick-off tasks with simple scheduling code. In one project I scanned pair of 74HC165 shift registers, and did color modulation on an R/G LED so the foreground could select off, red, green, or yellow (requires a blend that was not 50/50), and can have solid or blinking states with user-controlled timing.

  • Wuerfel_21Wuerfel_21 Posts: 4,374
    edited 2023-03-03 13:48

    Using individual second/minute/hour variables and updating them asynchronously from another cog is actually a really bad idea. If your code is trying to save/use a timestamp at the same time as, for example, the clock rolls over from 1:59:59 to 2:00:00 you might end up seeing 2:59:59 or 1:00:00, which is obviously very wrong. It is probably better to just have a seconds counter and derive the larger units from that. If you make it a long it won't overflow for 60+ years, which should be enough.

    Also, you do not need to waste a cog on this trivial task as long as you have, in any of the other cogs, some sort of loop that is guaranteed to run multiple times per second where you can call a clock update function.

    VAR
      long prev_tick
      long seconds
    PRI init_clock ' Call this once
      prev_tick := cnt
      seconds := 0
    PRI tick_clock ' Call this often
      if cnt-prevtick => clkfreq
        seconds++
        prev_tick += clkfreq
    

    If you don't quite care about the moment-to-moment accuracy and more about it staying accurate over long periods of time, you can of course also do

    PRI tick_clock : n ' Call this not as often
      if n:=((cnt-prevtick) / clkfreq)
        seconds += n
        prev_tick += n*clkfreq
    

    which doesn't need to be called very often at all...

  • It's a fair point, and why is used the phrase "code like this." In practice, I use a gated busy flag to prevent a read while updating, and I put the RTC registers in a long so the foreground can make a copy with one read. Here's a more practical example (which uses a very conservative busy period of ~5ms).

    pri bkg_clock | d0, d1, t
    
      d1 := clkfreq / 1000                                  
      d0 := d1 * 995
    
      t := cnt
      repeat
        busyflag := false
        waitcnt(t += d0)
        busyflag := true
        waitcnt(t += d1)
        if (++rtc.byte[0] == 60)
          rtc.byte[0] := 0
          if (++rtc.byte[1] == 60)
            rtc.byte[1] := 0
            if (++rtc.byte[2] == 24)
              rtc.byte[2] == 0
              if (++rtc.byte[3] == 7)
                rtc.byte[3] := 0
    

    Also, you do not need to waste a cog on this trivial task as long as you have,

    Agreed. In my lasertag code I use an instance of my jm_time_80.spin (attached) to maintain the game clock, but must be called regularly to prevent rollover. This object is used many times, and usually as a timer by setting a negative time and allowing it to move toward zero. When the time is at or above 0 I know the timer is expired, so it's really easy to use, especially for timing that can tolerate a little wobble (e.g., muzzle flash timing).

  • max72max72 Posts: 1,155

    I usually keep time in the main COG if the repeat loop is safely under a second.
    Otherwise I use another COG (where I can have other auxiliary routines).

    Another solution is to use a free pin and counters.
    There is an application note (ANN001)

    This is my object with counters (set clkfreq, and shift. in the commenti you have rollover time and error)

    '' *************************************************
    '' seconds counter with free pin and counters
    '' *************************************************
    
    
    CON
    
    ' must set appropriate settings
    ' clkfreq 80_000_000
    ' -7.61 ppm   rollover every 3107 days
    'freq1 = 859
    'shift1 = 4
    
    ' clkfreq 80_000_000
    ' 1.48 ppm    rollover every 24 days
    freq1 = 109951
    shift1 = 11
    
    ' clkfreq 100_000_000
    ' 283 ppm
    'freq1 = 387
    'shift1 = 4
    
    ' clkfreq 100_000_000
    ' 11 ppm
    'freq1 = 10995
    'shift1 = 8
    
    ' clkfreq 100_000_000
    ' -0.79 ppm
    'freq1 = 87961
    'shift1 = 11
    
    
    
    
    PUB start (pin)
    ' sets pin as output
      DIRA[pin]~~
      CTRa := %00100<<26 + pin           ' set oscillation mode on pin
      FRQa := freq1                      ' set FRequency of first counter                   
    
      CTRB := %01010<<26 + pin           ' at every zero crossing add 1 to phsb
      FRQB := 1                   
    
    
    
    pub seconds
       return (phsb>>shift1)
    
    
Sign In or Register to comment.