simple outlet timer bug

I made a simple timer circuit that mounts in the back of an outdoor box/receptacle. It's sole purpose is to turn the outlet off momentarily once every 3 weeks. This will cause a golf cart charger that is plugged into the outlet to trip/restart charging. (Surprisingly,most older cart chargers will not do this on their own) So if your cart is left unattended for a few months, it gets no charge but the first one. This timer turns the outlet off for a few seconds every 21 days, thus making the charger start again, and top off the batteries.
It's a simple PCB, and a simple sketch. It has a small LED that blinks out the number of days left until the next reset. It has a small 20 AMP relay that controls the outlet. It gets 5V power for a reputable Recom power supply
I have a few of these in different shops, at different locations. Recently, I noticed one of them wasn't working: The LED was ON, not blinking, and the outlet was NOT on. As you can see in this short sketch, that situation should never occur. Unless I'm missing something, which is quite possible. I unplugged it from it's power source briefly, and it started working again, and has been happily blinking since. It has 2 days left before it's first reset since I restarted it.
I've included the sketch, the schematic, and the board layout. Does anyone see any possible explanation for this malfunctioning? I can only think of 2 things: The decoupling cap trace is too long, or the ATtiny85 chip is loose in the socket. Neither seem too likely to me. I'm going to solder an axial 0.1uF cap across the back of the board on the GND/VCC pins (4,8) of the ATtiny. I can remove the socket and solder the Attiny85 direct, though I hate to do this as I'll never be able to change the sketch again if there's any other bugs in the code. But I purchased the socket from DigiKey, it was cheap, but it wouldn't likely be a china knockoff.

unsigned long oneDay = 86400000; //millis in a day
int days = 21; //this is how many days between resets
int dayCounter; //counter to track which day we're on
unsigned long cycleMillis; //timer
int LEDpin = 1; //LED on D1
int relayPin = 0; //Relay on D0
void setup() {
  pinMode(LEDpin, OUTPUT); //set pin 0 as output
  pinMode(relayPin, OUTPUT); //set pin 1 as output
  digitalWrite(relayPin, HIGH); //turn the relay on
}


void loop() {
  if (millis() - cycleMillis > oneDay) { //Has it been 1 day yet?
    cycleMillis = millis(); //update the timer
    dayCounter += 1; //increase the day counter by 1
    if (dayCounter == days) { //it's time to reset
      dayCounter = 0; //reset the day counter
      resetPower();
    }
  }
  blinkLED(); //LED shows the number of days remaining
}

void resetPower() {
  digitalWrite(relayPin, LOW); //turn off the relay
  delay(10000); //delay for 10 seconds
  digitalWrite(relayPin, HIGH); //turn on the relay
}

void   blinkLED() { //LED shows the number of days remaining
  for (int i = 0; i < days - dayCounter; i++) {
    digitalWrite(LEDpin, HIGH); //turn the LED on
    delay(100); //    delay(100);
    digitalWrite(LEDpin, LOW); //turn the LED on
    delay(600); //    delay(200);
    
  }
  delay(8000); //delay 8 seconds
}

Timer_brd.pdf (25.3 KB)

Timer_sch.pdf (17.4 KB)

Ah, a mystery!

Sockets have not been made in the US in more than 25 years, perhaps more. You did not allow for millis roll-over to zero, but that would not cause a problem with just one device. You do not have backup power for commercial power failure, but then who cares, the charger will start over, anyway. And the unit will start another cycle.

I would suspect a cold solder joint or failure to clean the board after soldering or using an organic flux without cleaning, especially the chip socket. But then other boards should have been effected as well.

How long was this unit in operation before the failure? About which number is it in your board build? Is the board in an environment hostile to electronics?

Paul

I thought my code was written properly for the zero rollover? That was the first thing I thought of, actually.
I do subtraction, using unsigned longs. That should cove the roll over issue, no?

Excellent. A puzzle.

Let's start by separating mutable data from immutable data (you should do this anyway)...

const unsigned long oneDay = 86400000; //millis in a day
const int days = 21; //this is how many days between resets
const int LEDpin = 1; //LED on D1
const int relayPin = 0; //Relay on D0

int dayCounter; //counter to track which day we're on
unsigned long cycleMillis; //timer

Anything const has no side effects so the corresponding code can be eliminated / reduced.

setup

is removed from consideration as it does not access data.

resetPower

is removed from consideration as it does not access data.

blinkLED

has to be considered. While it does not modify data it does use something (dayCounter) that can be modified. dayCounter has a lower bound of zero (initialization on startup and reset in loop). In the for-loop that becomes 21-0. The for-loop will terminate. dayCounter has an uppoer bound of days (if-condition in loop). In the for-loop that becomes 21-21. The for-loop
will terminate. There are no unbounded conditions in blinkLED. It cannot be the problem.

That leaves this code...

void loop() {
  if (millis() - cycleMillis > oneDay) { //Has it been 1 day yet?
    cycleMillis = millis(); //update the timer
    dayCounter += 1; //increase the day counter by 1
    if (dayCounter == days) { //it's time to reset
      dayCounter = 0; //reset the day counter
    }
  }
}

Nothing suspicious there.

There are only two possibilities remaining: the core has a bug; the toolset (compiler / linker) generated a buggy image. Given the fact that you have other devices working correctly neither of those is at all likely. The code is very unlikely to be the problem.

SouthernAtHeart:
I thought my code was written properly for the zero rollover?

It is.

(Though the condition should be greater-than-or-equal-to and cycleMillis should be incremented rather than set to millis. Those two differences will cause the clock to drift forward.)

Thanks, for the input.
"Note: The timing will drift." yes, I thought of that. No issue, though.

I always clean my boards with 91% alcohol. I'll double check this one, that it's clean, and all the solder connections.
I'll add a 0.1uF cap directly across pins 4 & 8 on the ATtiny85. Since I did not bother with a copper pour, the trace from the existing decoupling cap is about an inch from the ground pin on the chip. I wonder if when it trips the power, and it restarts, the golf cart charger's sudden start/use of current causes some kind of something? Seems the Recon supply would/should catch it/take care of it, but all I know is I've fixed several devious little bugs with the reminder of decoupling caps.

I'll hold off on removing the IC socket/ direct soldering the IC.

I'll report back with updates, but it may be another 21 day cycle

What is connected to the RESET pin?

The What??
Hmmm, nothing is! I bet that could be an issue?
I bet it should have a 10K pull up?
Is this the type of thing where people typical do nothing with it and "get by" but it can cause rare little quirks like I'm having?

PS. My ATtiny85 is programmed as 1Mhz, internal crystal.

That is very likely the culprit. A 12 V spike on RESET starts the processor into high-voltage programming mode. Before the auto-reset circuit change it caused more than one Uno to lock up.

There is an internal pullup but it is very weak (~50 kΩ).

When I'm in your situation (want to be able to reprogram; will not be programming in-system) I tie RESET to VCC.

Thanks. I have a good feeling this is the issue. I'll be adjusting all of the units I have in operation.
Thanks again!

You are welcome.

tie, as in you connect RESET directly to VCC?

Correct. Physical pin 1 (upper-left from above) to physical pin 8 (upper-right from above) on an ATtiny85 processor.

Update: I found another one that was 'froze'. In a different state, but froze non the less. On this one the LED was off, but the relay was still on. I'm fixing the floating reset on all of them.
cheers,

Man— I don’t see any great advice here yet. Millis is the WRONG method for long time timing. You need a RTC (real time clock). You can get one with a battery for less than $10. The rtc keeps time. By itself. Year, month, day of month, hour, minute, and seconds.

You can call on the rtc to pick out just one part of the clock. Like month. Or day of month. The do a real simple program around that.

Int dom= Rtc.dayofmonth

If (dom ==21)
{
-Cycle that power relay
-Set a flag not to check this function again for a full day—else it will trip all day on the 21
}

FullOfBadIdeas:
Man— I don’t see any great advice here yet. Millis is the WRONG method for long time timing. You need a RTC (real time clock). You can get one with a battery for less than $10. The rtc keeps time. By itself. Year, month, day of month, hour, minute, and seconds.

You can call on the rtc to pick out just one part of the clock. Like month. Or day of month. The do a real simple program around that.

Int dom= Rtc.dayofmonth

If (dom ==21)
{
-Cycle that power relay
-Set a flag not to check this function again for a full day—else it will trip all day on the 21
}

not a good idea for this application.
First post explains what's needed here. Doesn't matter if it 21 days, 22 days, 19 days, really. So I think in this application, Millis() is perfect.

Is the relay adequate for the voltage and current? Too much current will tend to weld the relay contacts in the "on" position.

MorganS:
Is the relay adequate for the voltage and current? Too much current will tend to weld the relay contacts in the "on" position.

Rated for 440VAC, 16A
switching 120VAC, likely 8-12A
but the beauty of my application is that a golf cart charger doesn't start charging until a few seconds after it powers up. So there'll not be much current flow on relay's contact close. Which I'm guessing is the only time current is a threat to a relay.

Not always. There's probably some large capacitors in the power supply which will draw peak current at switch-on.

The off cycle is actually more important for avoiding welding the relay. If there's a high current draw (and high inductance) when the relay disengages, that can make a spark and damage the contacts. That certainly won't be a problem for your use pattern.