0

I have some really simple code and tested on an 8 MHz ATtiny85 and ATtiny2313. It's basically a blocking blink:

int relayPin = 7;

long offPeriod = 1 * 60 * 1000;
long onPeriod = 1 * 60 * 1000;

void setup()
{
  pinMode(relayPin, OUTPUT);
}

void loop()
{
  digitalWrite(relayPin, LOW);
  delay(onPeriod);
  digitalWrite(relayPin, HIGH);
  delay(offPeriod);
}

When I assign onPeriod and offPeriod to be 1 * 1000 (1 second), the LED will blink as expected. When I add the 60 second multiplier in, it seems to fail.

For the ATtiny85 I use Arduino Pin 4 (physical pin 3) and for the ATtiny2313, I use Arduino pin 7 (physical pin 9).

I program through the Arduino IDE using boards manager to install the ATTinyCore library for the ATtiny2313 and this for the ATtiny85.

Am I doing something wrong here?

It looks like if I throw the delay in a function, and then invoke it with however many seconds I want, it will work. This is not good as it remains blocking. So perhaps the correct way to do this is to grab timestamps. Here's the somewhat working code:

int relayPin = 4;
unsigned long offPeriod = 60;
unsigned long onPeriod = 60;

void setup()
{
  pinMode(relayPin, OUTPUT);
}

void loop()
{
  digitalWrite(relayPin, LOW);
  delaySeconds(onPeriod);
  digitalWrite(relayPin, HIGH);
  delaySeconds(offPeriod);
}

void delaySeconds(int s) {
  for (int i = 0; i < s; i++) {
    delay(1000);
  }
}
  • The type for the delay value is an "unsigned long" according to this. But I don't think that's your problem. Could the Delay code not be compatible with your processor? Also, it is likely you can call delay(1000) in a loop driven by the number of seconds (for instance 6) you wish to delay. – st2000 Aug 17 '16 at 4:03
  • The delay() library works with times below a minute (I haven't tested exactly what but I've done <10 seconds, and then went to test 60 seconds, the latter of which does not work). I would think that if the library isn't compatible, any calls to it won't work, but maybe it's a small bug with it and my MCU? I just tried unsigned long and it doesn't work. Let me try throwing a delay(1000) in a function. – PGT Aug 17 '16 at 4:13
  • Looks like throwing it into a delay function works. I'm thinking maybe it's an overflow issue now. – PGT Aug 17 '16 at 4:20
  • Overflow issue seems weird, because unsigned long is 32-bit which is way more than the 15 * 60 * 1000 = 900000 I require. – PGT Aug 17 '16 at 4:44
8

This is absolutely nothing to do with "why can't the atmel attiny family delay over a minute?". It is all to do with the way that C++ works.

See my page about Integer arithmetic and overflow.

To excerpt from that:


On the Arduino (Uno) platform, what do you think will be printed here?

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();

  Serial.println (30000 + 30000); // twice 30000
  Serial.println (60 * 60 * 24);  // seconds in a day
  Serial.println (50 / 100 * 1000); // half of 1000
  }  // end of setup

void loop () { }

Did you guess:

60000
86400
500

Nope!

It prints:

-5536
20864
0

This is because of integer arithmetic. If the compiler can, it treats an numeric literal (like 60) as an int type, which means it has the range -32768 to +32767.

And, arithmetic is done using the type of the largest argument, which means the arithmetic in each case is done as 16-bit arithmetic, and thus it overflows once it reaches 32767.

For example, 30000 + 30000 = 60000 which is 0xEA60 in hex. Unfortunately, 0xEA60 is exactly how -5536 is stored in an int type, which is why it prints -5536.

Meanwhile, 60 * 60 * 24 = 86400 which is 0x15180 in hex. As that doesn't fit in 16 bits, it is truncated to 0x5180 which is 20864 in decimal (as printed).

Finally, in integer arithmetic 50/100 is zero, multiply zero by 1000 and you still get zero, which is why the final result is zero.


Solution

First, you can add a suffix to numeric literals (eg. L for long, or UL for unsigned long), and add a decimal place to floats, like this:

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();

  Serial.println (30000L + 30000); // twice 30000
  Serial.println (60L * 60 * 24);  // seconds in a day
  Serial.println (50.0 / 100 * 1000); // half of 1000
  }  // end of setup

void loop () { }

Now we get:

60000
86400
500.00

You only need to help out with the first literal, once the compiler knows we are using longs (or floats) it sticks with them for the expression.*


* It's actually somewhat more complex than that as this link explains: Understand integer conversion rules.

The compiler "promotes" a value in an expression to match another "higher-ranked" type, under certain circumstances. For example, adding an int and a long will result in the int being promoted to a long (regardless of whether it appears first in the expression or not). However if an int is being added to another int, it will not promote them to a long, even though the result may not fit into an int.

  • Thanks for all the information. I was suspecting overflow but have forgotten all the C++ I've learned in university since it's been so long. I had remembered arithmetic is done on largest type, but oversight made me blind to the fact that all the individual numbers are INT. You mentioned at the beginning of your answer "on the Arduino (Uno) platform", but from the diction used in your answer, this seems to be general C++ behaviour. Any reason to keep thinking it's for Arduino only? – PGT Aug 17 '16 at 20:31
  • The distinction is because on this platform an int is 16 bits. If you try the same code on your PC an int will probably be 32 bits or even 64 bits. If you have come from programming PCs the problem is still there, but masked by the larger integer size. 16-bit ints would be used on other platforms with small processors (8-bit processors) - probably such as PIC etc. – Nick Gammon Aug 17 '16 at 21:51
  • My copy of Kernighan and Ritchie (The C Programming Language) mentions on page 9: The range of both int and float depends on the machine you are using; 16-bit ints, which lie between -32768 and +32767, are common, as are 32-bit ints. – Nick Gammon Aug 17 '16 at 21:52
4

These are the problem lines:

long offPeriod = 1 * 60 * 1000;
long onPeriod = 1 * 60 * 1000;

Even though long should hold the result, integer operations in C++ are still done in int by default. And on AVR int is 16 bits. The fix is to tell the compiler what size the intermediate result should be:

long offPeriod = 1 * 60L * 1000;
long onPeriod = 1 * 60L * 1000;
-1

According to AVR libC the implementation of the delay function has a limited value range that varies with the F_CPU macro definition. So your delaySeconds(int) function is the only solution.

  • 2
    He is using delay, not _delay_ms – Gerben Aug 17 '16 at 9:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.