How can I reduce the size of my sketch?

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);
  }  
}

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.


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.