5

Looks like I'm overshooting the storage of my Adafruit Trinket:

Sketch uses 5,600 bytes (105%) of program storage space. Maximum is 5,310 bytes.
Global variables use 109 bytes of dynamic memory.

Any tips for reducing the footprint of my code?

#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <RTClib.h>

#define LEDPIN 1
#define TONE 4
#define BTNPIN 3

RTC_DS1307 rtc;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(4, LEDPIN, NEO_RGB);

long fadeTime = 1 * 10000L; // X minutes
int colorStops = 256;
int delaySpeed = fadeTime / colorStops;
int notes[] = {262,294,330,349};
int timeHour = 14;
int timeMinute = 43;
uint32_t alarmLength = 5 * 60000L; // 5 minutes

void setup() {
  rtc.begin();
  if(!rtc.isrunning()) {
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }

  pinMode(TONE,OUTPUT);
  pinMode(BTNPIN, INPUT);
  digitalWrite(BTNPIN, HIGH);

  strip.begin();
  strip.show();
}

void loop() {
  DateTime now = rtc.now(); 

  if (now.hour() == timeHour && now.minute() == timeMinute && now.second() == 0){
    // Fade in light
    for(int i = 1; i<colorStops; i++){
      if (kill() == true){ break; }

      for(int np = 0;np<strip.numPixels(); np++){
        strip.setPixelColor(np, strip.Color(i,i,0));
      }
      strip.show();
      delay(delaySpeed);
    }

    // Play tone after light fully bright
    for( uint32_t tStart = millis();  (millis()-tStart) < alarmLength; ){
      if (kill() == true){ break; }

      for(int np = 0;np<strip.numPixels(); np++){
        strip.setPixelColor(np, strip.Color(random(100,255),random(100,255),random(100,255)));
      }
      strip.show();

      beep(TONE, notes[random(0,3)], 50);
      delay(100);
    }

    for(int np = 0;np<strip.numPixels(); np++){
      strip.setPixelColor(np, strip.Color(0,0,0));
    }
    strip.show();
  }
}

void beep (unsigned char speakerPin, int frequencyInHertz, long timeInMilliseconds)
{   // http://web.media.mit.edu/~leah/LilyPad/07_sound_code.html
  int x;   
  long delayAmount = (long)(1000000/frequencyInHertz);
  long loopTime = (long)((timeInMilliseconds*1000)/(delayAmount*2));
  for (x=0;x<loopTime;x++)   
  {  
    digitalWrite(speakerPin,HIGH);
    delayMicroseconds(delayAmount);
    digitalWrite(speakerPin,LOW);
    delayMicroseconds(delayAmount);
  }  
}

bool kill() {
  if (! digitalRead(BTNPIN)) {
    for(int np = 0;np<strip.numPixels(); np++){
      strip.setPixelColor(np, strip.Color(0,0,0));
    }
    strip.show();
    return true;
  }
  else {
    return false;
  }
}
  • One suggestion would be to change any int variables that you know won't be outside the range of [-127, 127] to a byte variable. byte data type is of size 1 byte, int is 4 bytes. This similar concept can be used for the other variables also. – Jonathan Mar 31 '16 at 20:14
  • 1
    Have you tried different compiler optimization levels? – sekdiy Mar 31 '16 at 20:17
  • 2
    @JonathanSmit: The range of a byte is [0, 255], not [-127, 127], as it is typedefed to be an uint8_t. And int is 2 bytes, not 4. – Edgar Bonet Mar 31 '16 at 20:22
  • Ops, sorry about that, totally crossed my mind. I forgot byte is unsigned. Also, aren't ints 4 bytes on most 32 bit and 64 bit compilers? stackoverflow.com/questions/11438794/… – Jonathan Mar 31 '16 at 20:24
  • @JonathanSmit: “Also, aren't ints 4 bytes on most 32 bit and 64 bit compilers?” Yes, they are, but the ATtiny85 inside the Trinket is an 8 bit chip. – Edgar Bonet Mar 31 '16 at 20:27
4

The AdaFruit Trinket just doesn't have a lot of memory - 8kb, of which 3kb is used by the bootloader.

The order I look for things to reduce the footprint of an Arduino program are:

  • data (e.g. large strings)
  • libraries
  • your code (especially doing the same things several times, which might be combined into loops or functions).

In this case, you don't have much data. Most of your resulting program size is in the libraries you use.

It seems like you use the clock solely to detect the year - that seems like a lot of overhead just to figure out what year it is. It then does nothing for subsequent years (or prior years), if I read your code correctly. Maybe you would consider dropping this code? Then you can skip the entire RTC library. At least try it to see how much you save. I suspect that the RTC library will use another library to do the underlying work.

You use the library, but don't reference it in your code. It should be optimized out (unless used elsewhere), but there's no reason to have it there.

You may also save a little space by replacing initialized variables which don't change to constants. Storing 25 in a variable takes 1 byte, plus 2 bytes every time you reference it; 25 in a constant gets optimized away, and will take up 1 byte every time you reference it.

As mentioned in the comments, you can also save a few bytes by specifying the size of your int's, especially initialized ones.

Another option would be to use a larger chip - the AtMega328p used in (for example) the Arduino has 32kb of flash - 6 times the (available) size.

You can also save some bytes by removing the clause if(!rtc.isrunning()) { , once you have set it up once.

Additional: I have had some success reducing footprints by reimplementing a subset of a library. Usually, libraries are very well implemented, but if your needs are substantially different/simpler than what the library was designed for, or you are willing to forgo parts of it (e.g. error checking), then SOMETIMES this can help. WARNING! There is a HIGH price to pay (coding, debugging, loss of error checking, undetected bugs etc), and it may save you anything in the end.

You can also (sometimes) move data (e.g. large strings) to eeprom. The Trinket has 512 bytes of eeprom. You may need one sketch to load the data into eeprom, to run before loading your main sketch. eeprom has it's own overhead, so may not be worthwhile for 512 bytes, it is slower (which probably won't matter for this), and you have a limited number of writes.

Another few bytes may be saved by doing all the maths for the tone frequency/time beforehand, and sacrificing something. For example, you beep for 50ms. If, instead of passing the frequency (between 262 and 349 hertz) and instead pre-calculate the delayAmount (for 262 hz, this would be 1,000,000/262 = 3817ms); your looptime for this is ((timeInMilliseconds*1000)/(delayAmount*2)) = (50*1000)/(3817*2) = 6.55 cycles. 7 cycles is probably "close enough" for all the tones you want to produce. So you can hardcode 7 cycles (using a constant of 7 for looptime), and get rid of that formula to calculate looptime.

In a similar way, if you don't care too much about the exact frequency (which varies between 3817ms and 2865ms depending on your random number), you might specify the delayamount as 2865+random(0,3)*238 (make sure you store at least the random number, you don't want it to change during the loop!).

You now have:

#define random_beep_length 7
void random_beep (unsigned char speakerPin)
{   // http://web.media.mit.edu/~leah/LilyPad/07_sound_code.html
    // Heavily modified!
  int x;
  unsigned char pitch = 2865+random(0,3)*238;
  for (x=0;x<random_beep_length;x++)   
  {  
    digitalWrite(speakerPin,HIGH);
    delayMicroseconds(delayAmount);
    digitalWrite(speakerPin,LOW);
    delayMicroseconds(delayAmount);
  }  
}
  • Thanks for the tips! I forgot to update my code from when I was doing some debugging. I actually need the RTC to detect the current hour and minute (which is now reflected in my post). – Shpigford Mar 31 '16 at 21:04
  • I think it is a great idea to set the clock once - in a separate sketch - and then omit the part that checks if the clock is running. If that doesn't help, modify the clock library (or make a copy) to omit the parts that set the time. – Nick Gammon Mar 31 '16 at 23:08
  • you might specify the delayamount as 2865+random(0,3)*238”. Or, better yet, 2945+random(0,4)*256. This saves 8 bytes at the cost of a small inaccuracy in the frequencies. Or, an equivalent but less readable form: 2945+(random()&3)*256 (saves 18 bytes). – Edgar Bonet Apr 1 '16 at 10:45
  • @EdgarBonet, then why not (11+random()&3)*256, saving another byte? :) – AMADANON Inc. Apr 1 '16 at 16:27
  • The sum of these tips did the trick. I'm at 97% now. :) – Shpigford Apr 1 '16 at 18:02
4

As already mentioned by AMADANON Inc. in his answer, you should qualify as const all the constant variables at the beginning of the program. This is probably the biggest space saver.

You can gain something like 100 bytes by using direct port access instead of pinMode/digitalRead/digitalWrite. In the case of the ATtiny85, you only have port B, and the Arduino pin numbers are the same as the Atmel bit numbers. So you would for example use PINB & _BV(BTNPIN) instead of digitalRead(BTNPIN).

There are a few optimization opportunities in beep():

  • pass only one parameter, as speakerPin and timeInMilliseconds are constants in your program
  • have this parameter be delayAmount instead of frequencyInHertz, and store these delays, instead of the frequencies, in the notes[] constant array
  • do not compute the number of iterations (the misnamed loopTime) beforehand, instead compute the elapsed time and loop until you reach the required time

Here is my version of beep() with these optimizations:

void beep(int delayAmount)
{
  for (uint16_t t = 0; t < BEEP_TIME*1000/2; t += delayAmount)
  {  
    PORTB |= _BV(TONE);  // digitalWrite(TONE,HIGH);
    delayMicroseconds(delayAmount);
    PORTB &= ~_BV(TONE);  // digitalWrite(TONE,LOW);
    delayMicroseconds(delayAmount);
  }  
}

Notice that the loop variable is actually half the elapsed time. This saves a few bytes. Making the number of iterations constant, as proposed by AMADANON Inc., also saves a few bytes (6 bytes in my test).

You should also remove all the calls to strip.Color(): replace every instance of strip.setPixelColor(np, strip.Color(r, g, b)) by strip.setPixelColor(np, r, g, b).

With all these optimizations combined, I am quite confident you will gain more than the 290 bytes you need to fit your program in the Trinket.

  • "constant variables"? lol! I know what you mean :) – AMADANON Inc. Apr 1 '16 at 16:28
3

An option not already mentioned, but may be worth mentioning, would be to remove the bootloader and program the chip directly with a hardware programmer (or another Arduino). That will increase the available sketch memory from 5kiB to the full 8kiB on the chip.

  • 1
    This is a good idea, however bear in mind you'll need to put the bootloader back if you ever want to use it the way it was originally designed. – Nick Gammon Mar 31 '16 at 23:07
  • Is there some sort of how-to anywhere about doing this? I have an Uno I presume I could do that with. – Shpigford Apr 1 '16 at 16:49

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.