using an inerrupt with 120Hz source to aid in timekeeping

I have a 120 Hz source feeding data line D2 which is set up as an input and triggers a simple ISR on the RISING edge. The ISR simply adds 120 a global 32 bit uint. So now I can make my own version of a millis() routine (call it isrMillis() ) that can read the ISR driven number, do some simple math, and return the number of miliseconds passed. granted my resolution will only be a resolution of just over 8mS, but that's plenty accurate for my app.

My dilemma is that I'm not 100% sure of my plan for returning a copy of the current 32 bit counter, without risking the access being interrupted. That would be a no-no because it would result in situations where all 4 bytes of the 32 bit number are not atomically connected. Disabling the interrupt while fetching accessing the 32 bit number is no good either, because I could then miss a transition on the interrupt pin, and losing time in the long run.

So I thought of maybe adding some kind of atomic semaphore flag to the ISR, that is set and cleared as the ISR starts and ends. Then my new isrMillis() call could simply wait for it to be clear in a while() loop before reading. But I don't know what the NANO processor and language offer for such an atomic operation. When I had a problem like this in Visual C++, I had calls like "InterlockedExchange()" to work with, that would allow such atomic ops. Not sure what I have here.

While I'm spinning the wheels of my brain on this, I thought maybe I'd ask some advise.

Boring background: I've decided that my application will be more useful if I have more accurate time of day, and fall back to system time (millis() ) when its unavailable. I've built my own simple brute force power supply using a 6VAC transformer (60hz in the USA) , a rectifier bridge, and a filter cap, to power my Arduino NANO project, and to make a time base available, I've added another diode between the rectifier bridge and the filter capacitor. That offers me a filtered 9VDC for the board, as well as a convenient source of 120 Hz between the bridge and the extra diode. I then feed that point to two 10K resistors, so the midpoint will then offer pulses of about 4V to my data pin.

Just disable interrupts for the duration of the memory access. Generally, if an interrupt occurs when interrupts are disabled, the interrupt flag for the interrupt will be set and the pending interrupts will be serviced (in priority order) once interrupts are re-enabled. You would only run into problems if you disabled interrupts for so long that two of your timing interrupts would occur, but that would be pretty dopey anyhow.

arduarn:
Just disable interrupts for the duration of the memory access. Generally, if an interrupt occurs when interrupts are disabled, the interrupt flag for the interrupt will be set and the pending interrupts will be serviced (in priority order) once interrupts are re-enabled. You would only run into problems if you disabled interrupts for so long that two of your timing interrupts would occur, but that would be pretty dopey anyhow.

OK, thanks. So disabling doesn't destroy a pending interrupt. I'm not super concerned my short routine would take any time to finish. I guess I'm more worried about the idea of disabling ALL interrupts messing with something faster like incoming serial data events maybe.

Well, when your ISR is running other ISRs are blocked, so you could just as well lose something faster there too. So I don't think disabling interrupts for the duration of a 32bit copy is going to cause you any serious issues.

arduarn:
Well, when your ISR is running other ISRs are blocked, so you could just as well lose something faster there too. So I don't think disabling interrupts for the duration of a 32bit copy is going to cause you any serious issues.

I was a little worried because of the iterative loop I'd have needed to turn my counter (which would trigger 120 x per second) into milliseconds. In the end I decided to downscale my minimum resolution a little more to make the math simpler. 3/120ths of a second comes out to a nice round 25mS, which is more than adequate. It adds a step to my interrupt, but leaves the new myMillis() free to do nothing beyond the 32 bit copy during the time the interrupts are disabled. Do you see any "gotchas" here? I also added a flag so my new myMillis() routine could switch back to the regular millis() if the power failed, and the system went to battery backup.

volatile int32_t currExtTime =0;
volatile uint8_t extTimeWorking = FALSE;

void pin_ISR() 
 {
  static uint8_t t120Ctr = 0;
  extTimeWorking = TRUE;                // show interrupt is happening
  if (++t120Ctr != 3) return;              // only add time once every 3 calls
  t120Ctr = 0;
  currExtTime +=25;                     //  (1/ 120hZ) * 3 = 25mS
 }


uint32_t myMillis(uint32_t reset = FALSE) 
{
 static uint32_t ms;
 
 if (reset) currExtTime = millis(); // reset, only done called during setup()
 if (extTimeWorking)
  {
   noInterrupts();
   ms = currExtTime;
   interrupts();
  }
 else ms = millis();

#ifdef DEBUG
static uint8_t lastWorkingFlag = FALSE;

 if (extTimeWorking != lastWorkingFlag) 
   Serial.println(String("") + "External Time " + ((extTimeWorking) ? "Working" : "Lost"));
 lastWorkingFlag = extTimeWorking;
#endif

 extTimeWorking = FALSE;  // reset flag so we'll know quickly if it power fail
 return ms;
}

Probably a more efficient way of doing this would be to use an available hardware counter to count the input signal fluctuations, but I think that may be a little too involved for you at the moment. It should work OK using interrupts too.

volatile int32_t currExtTime = 0;

Make this unsigned, otherwise any roll-over will be a surprise.

  if (reset) currExtTime = millis(); // reset, only done called during setup()
  if (extTimeWorking)
  {
    noInterrupts();
    ms = currExtTime;
    interrupts();
  }
  else ms = millis();

So at the beginning myMillis() and millis() are synchronised. Over time, they will drift apart. If the external signal fails, then there is likely to be a small/medium/large step jump in the value returned by myMillis(). Likewise, if the external signal comes back online again, there will be another step jump of unknown amount. Any use of myMillis() would need to take that into account.

What's the reason for using this lower resolution method instead of the higher accuracy method using millis? Is there something else you need Timer0 for?

Delta_G:
What's the reason for using this lower resolution method instead of the higher accuracy method using millis? Is there something else you need Timer0 for?

Its just that the overall project needs to do certain things at user programmed times of the day. Granted, the 120hZ pulses from my power supply offer a much lower resolution (only 25 milliseconds in my example code ), it does offer better accuracy over time, as power company frequency is guaranteed to average out to a pretty amazing accuracy over time. And as it turns out, being able to go for days, weeks, or months and keep the time to within hour/minute/seconds accuracy is more important in this case.

And as it turns out, being able to go for days, weeks, or months and keep the time to within hour/minute/seconds accuracy is more important in this case.

So spend 2 bucks and get an RTC.

Delta_G:
So spend 2 bucks and get an RTC.

Delta_G:
So spend 2 bucks and get an RTC.

I'd be wasting my money. Unless you're talking about some new RTC that connects to the internet to obtain a time, an RTC will drift over time. On the other hand, the 120hZ from a rectifier bridge powered by the electric company is guaranteed to average and synchronize with NSB standard time, and the extra diode it costs me is all of about 2¢

PeterPan321:
On the other hand, the 120hZ from a rectifier bridge powered by the electric company is guaranteed to average and synchronize with NSB standard time, and the extra diode it costs me is all of about 2¢

If you really think that is more accurate than the crystal on an RTC then I guess I'm not going to convince you. Do what thou wilt.

Delta_G:
If you really think that is more accurate than the crystal on an RTC then I guess I’m not going to convince you. Do what thou wilt.

Well I’m always in listening mode. But my opinion is based on having worked with some RTC chips from Dallas Semi, AND also having worked in many technical and coding positions at major US electric companies for over 20 years. I’m always open to learning, but you can see why I might be biased.

I do understand that during the course of the day, the timing of the line frequency will change depending on load conditions. If that were an issue, than yes I’d be better of with an RTC. But over the course of a 24 hour period, electric companies keep a close watch on deviation and make sure it is compensated back to true time. So over the long haul (assuming no blackouts) it will be more accurate, because it is actively being corrected.

Plus, like I said… if the project is powered by AC anyway, how can you beat the cost of an extra diode vs the addition of an RTC. As far as replacing the resonator with a crystal, that in itself will not be enough to compensate for wide temperature changes, as my particular project will need to work outdoors, at least through the warmer part of the year. So surely you’d agree the application and environment should steer the design choices?

PeterPan321:
Do you see any "gotchas" here?

Yes...

PeterPan321:
I also added a flag so my new myMillis() routine could switch back to the regular millis() if the power failed, and the system went to battery backup.

Given the fact the two are guaranteed to drift apart abruptly switching between the two is very likely to cause problems.

Udo Klein has a reasonable solution: Use the accurate clock as a reference to adjust millis. Over time millis will come to be nearly as accurate as the reference. In the case of power failure, stop making adjustments. When power resumes, resume making adjustments (in small amounts). Unfortunately I cannot put my fingers on his code. I vaguely recall he published it in Software Development on this forum.

Yes... good idea. At least I could keep a static var of the difference between grid time and millis(), and make sure anytime the grid source fails, I stop updating that difference and just use its last value to correct the result of millis(). As far as going back, it might not be a problem, and it might be overkill for me to do anything about it. For my project, if power fails I could just let the program run on millis, set some kind of alarm indicating time should be reset by the user. I won't use the grid time again till that's been done. This probably makes sense because the battery backup will only last for a limited time, and I'll probably want to shut down all external operations safely before battery voltage drops too far.

Using my elite search skills :wink: , probably this :

Your application reminds me of the timekeeping code for my two-button clock.

The thread for my clock is here:

A relevant snippet:

uint32_t timeAdd (uint32_t x, uint32_t y) {
  // no sanity checking of input
  // format is hhmmssff with ff being decimal fractions of a second
  // "out of range" results are e.g. A0000000 for 100 hours
  uint32_t binsum = x + y;
  uint32_t carry = ((binsum + 0x06A6A666) ^ x ^ y) & 0x11111110;
  return (binsum + ((carry - (carry>>4)) & 0x06A6A666));  
}

uint32_t timeSub (uint32_t x, uint32_t y) {
  // no sanity checking of input
  // format is hhmmssff with ff being decimal fractions of a second
  // "negative" results are e.g. F9595999 for -0.01 second
  uint32_t bindiff = x - y;
  uint32_t borrow = (bindiff ^ x ^ y) & 0x11111110;
  return (bindiff - ((borrow - (borrow>>4)) & 0x06A6A666) );
}

Those functions are for arithmetic on times expressed in binary-coded decimal.

By changing a magic number (which appears in several places), we can modify the functions for your needs:

uint32_t timeAddCXX (uint32_t x, uint32_t y) {
  // no sanity checking of input
  // format is hhmmssff with ff being 10ths and 120ths of a second
  // (top digit of fractional seconds is decimal, bottom digit is duodecimal)
  // "out of range" results are e.g. A0000000 for 100 hours
  uint32_t binsum = x + y;
  uint32_t carry = ((binsum + 0x06A6A664) ^ x ^ y) & 0x11111110;
  return (binsum + ((carry - (carry>>4)) & 0x06A6A664));  
}

uint32_t timeSubCXX (uint32_t x, uint32_t y) {
  // no sanity checking of input
  // format is hhmmssff with ff being 10ths and 120ths of a second
  // (top digit of fractional seconds is decimal, bottom digit is duodecimal)
  // "negative" results are e.g. F959599B for -1/120 second
  uint32_t bindiff = x - y;
  uint32_t borrow = (bindiff ^ x ^ y) & 0x11111110;
  return (bindiff - ((borrow - (borrow>>4)) & 0x06A6A664) );
}