Close

current code, anyone any ideas to save space?

A project log for sloth badge

I guess I can't stop doing badges. Really love the @pimoroni #bearables - but they don't have sloths :(

davedarkodavedarko 02/19/2018 at 10:288 Comments

I won't lie, this feels messy. This currently features 5 animations, where 2 of them are just "played accelerating" . 

Some things I'm not happy with:

 - the button does not work within the animations, so you need to push the button until the animation cycle is over

#include <avr/sleep.h>
#define nop() asm volatile("nop")
boolean leds_on = true;

byte badly_programmed_counter = 0;
byte animation_counter_max = 5;
byte animation_counter = 0;

// we need two frames with 8 bits for 12 LEDs
byte frame1 = 0;
byte frame2 = 0;

// animation where every third LED is glowing
// I could shift that continuosly and drop 4 bytes
const byte three_apart_length = 6;
const byte three_apart[three_apart_length] PROGMEM = {
  0b10010010, 0b01000000,
  0b01001001, 0b00100000,
  0b00100100, 0b10010000,
};

// this one is huge
// is there a math function to do that instead?
const byte updown_length = 14;
const byte updown[updown_length] PROGMEM = {
  0b00100000, 0b00000000,
  0b01010000, 0b00000000,
  0b10001000, 0b00000000,
  0b00000100, 0b00010000,
  0b00000010, 0b00100000,
  0b00000001, 0b01000000,
  0b00000000, 0b10000000,
};

// that's just blinking each led 
// maybe inverting the data bitwise does the trick
const byte two_apart_length = 4;
const byte two_apart[two_apart_length] PROGMEM = {
  0b10101010, 0b10100000,
  0b01010101, 0b01010000,
};

// should have taken a switch, sleep stuff is huge
ISR(INT0_vect) {
  sleep_disable();
  leds_on = true;
}

void setup()
{
  // this is for the button setup
  PORTB |= (1 << PB1);
  DDRB &= ~(1 << PB1);

  shutdown_chip();
}

void loop()
{
  if (leds_on)
  {
    cli();
    leds_on = false;

    // I call it that, because the millis stuff
    // and the interrupts collided and broke
    badly_programmed_counter = 0;
  }

  // fancy way of checking the button state
  if (!(PINB & (1 << PB1)))
  {
    badly_programmed_counter++;
    my_100ms_delay();

    // check if button was released before time
    // so animation index can be incremented
    // maybe a modulo with animation_counter_max
    // safes space
    if ( (PINB & (1 << PB1)) )
    {
      badly_programmed_counter = 0;
      animation_counter++;
      if (animation_counter == animation_counter_max)
      {
        animation_counter = 0;
      }
      charlie(animation_counter);

      // calling a function instead of writing it down
      // 5 times helps already
      for (byte b = 0; b < 5; b++) my_100ms_delay();
    }

    if (badly_programmed_counter >= 7)
    {
      // blink when shutdown is immanent
      for (byte b = 0; b < 5; b++) blip();
      shutdown_chip();
    }
  }
  else
  {
    animate_leds();
    badly_programmed_counter = 0;
  }
}

// this is probably already at it's best
void charlie (byte var) {
  leds_off();

  switch (var) {
    case 0: // 1
      DDRB |= (1 << PB0) | (1 << PB2); // output
      PORTB |= (1 << PB0); // HIGH
      break;
    case 1: // 2
      DDRB |= (1 << PB0) | (1 << PB2); // output
      PORTB |= (1 << PB2); // HIGH
      break;
    case 2: // 3
      DDRB |= (1 << PB2) | (1 << PB3); // output
      PORTB |= (1 << PB2); // HIGH
      break;
    case 3: // 4
      DDRB |= (1 << PB2) | (1 << PB3); // output
      PORTB |= (1 << PB3); // HIGH
      break;
    case 4: // 5
      DDRB |= (1 << PB3) | (1 << PB4); // output
      PORTB |= (1 << PB3); // HIGH
      break;
    case 5: // 6
      DDRB |= (1 << PB3) | (1 << PB4); // output
      PORTB |= (1 << PB4); // HIGH
      break;
    case 6: // 7
      DDRB |= (1 << PB0) | (1 << PB3); // output
      PORTB |= (1 << PB0); // HIGH
      break;
    case 7: // 8
      DDRB |= (1 << PB0) | (1 << PB3); // output
      PORTB |= (1 << PB3); // HIGH
      break;
    case 8: // 9
      DDRB |= (1 << PB2) | (1 << PB4); // output
      PORTB |= (1 << PB2); // HIGH
      break;
    case 9: // 10
      DDRB |= (1 << PB2) | (1 << PB4); // output
      PORTB |= (1 << PB4); // HIGH
      break;
    case 10: // 11
      DDRB |= (1 << PB0) | (1 << PB4); // output
      PORTB |= (1 << PB0); // HIGH
      break;
    case 11: // 12
      DDRB |= (1 << PB0) | (1 << PB4); // output
      PORTB |= (1 << PB4); // HIGH
      break;
    default:
      ;

      nop();
  }
}

void shutdown_chip()
{
  animation_counter = 0;
  leds_off();

  /* Clear WDRF in MCUSR */
  MCUSR &= ~(1 << WDRF);
  /* Write logical one to WDCE and WDE */
  /* Keep old prescaler setting to prevent unintentional time-out */
  WDTCR |= (1 << WDCE) | (1 << WDE);
  /* Turn off WDT */
  WDTCR = 0x00;

  ADCSRA &= ~(1 << ADEN); // turn off adc

  GIMSK |= (1 << INT0);
  GIMSK |= (1 << PCIE);

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sei(); // Enable global interrupts
  sleep_cpu();
  sleep_disable();
}

void animate_leds()
{
  switch (animation_counter)
  {
    case 0:
      animate_frames(
        three_apart_length,
        three_apart,
        512
      );
      break;

    case 1:
      for (byte acc_counter = 1; acc_counter < 64; acc_counter += 2)
      {
        animate_frames(
          three_apart_length,
          three_apart,
          acc_counter * 8
        );
      }
      break;

    case 2:
      animate_frames(
        updown_length,
        updown,
        512
      );
      break;

    case 3:
      animate_frames(
        two_apart_length,
        two_apart,
        2048
      );
      break;

    case 4:
      for (byte acc_counter = 1; acc_counter < 64; acc_counter += 2)
      {
        animate_frames(
          updown_length,
          updown,
          (63-acc_counter) * 4
        );
      }
      break;

    default:
      leds_off();
      break;
  }
}

void animate_frames(
  byte animation_length,
  const byte* animation_array,
  uint16_t frame_duration
) {
  for (byte i = 0; i < animation_length; i += 2)
  {
    // save the last time you blinked the LED
    frame1 = pgm_read_byte_near(animation_array + i);
    frame2 = pgm_read_byte_near(animation_array + i + 1);
    show_frame(frame_duration);
  }
}

// this just looks ugly, but needs to be done
// for loop through frame bytes to set the LEDs with 
// the charlie plex function
void show_frame(uint16_t byteframe_length)
{
  for (uint16_t j = 0; j < byteframe_length; j++)
  {
    //iterate through bit mask
    for (byte i = 0; i < 12; i++)
    {
      // if bitwise AND resolves to true
      if (
        (
          i < 8 &&
          1 << i & frame1
        ) ||
        (
          i > 7 &&
          1 << (i - 4) & frame2
        )
      )
      {
        charlie(i);
      }
      else
      {
        charlie(255);
      }
    }
  }
}

void leds_off()
{
  DDRB &= ~(0b00011101);
  PORTB &= ~(0b00011101);
}

void blip()
{
  charlie(11);
  // delay(100);
  my_100ms_delay();
  charlie(255);
  // delay(100);
  my_100ms_delay();
}

void my_100ms_delay()
{
  for (uint32_t i=0;i<40000;i++) nop();
}

Discussions

K.C. Lee wrote 02/20/2018 at 00:15 point

// this is probably already at it's best
void charlie (byte var){ }

Not sure how smart the compiler is.  You could possibly to use var to index 2 constant uint8_t arrays (in program space) to set the values of DDRB and PORTB instead of a switch().  The arrays takes up 24 bytes, so there are some potential saving there

Should do a bound check for the index range.

  Are you sure? yes | no

Clara Hobbs wrote 02/20/2018 at 00:14 point

Maybe use a pair of lookup tables for the assignments to DDRB and PORTB in the charlie function?

  Are you sure? yes | no

deʃhipu wrote 02/19/2018 at 23:27 point

save space

  Are you sure? yes | no

davedarko wrote 02/19/2018 at 23:43 point

Yikes. I knew something looked off thanks.

  Are you sure? yes | no

Alex wrote 02/19/2018 at 22:32 point

I only looked over your code quite quickly but some thoughts:
- Are you using the Timer0 anywhere? I did not see it. If that timer is unused it could be handy for all that timing stuff. Also a timer needs mostly only code for setup, 
- Polling on the switch is in general a good idea, but using an interrupt (e.g. the - as far i can see -  unuesed pin-changed interrupt, could make it more independent from the rest)

Just some ideas... But it would be a nice challenge to improve that a little bit. I would also guess that the library sleep function could get minimized a little bit. Somehow I now would relay like to compile that now and have a look at the assembler listing, but too late... 

  Are you sure? yes | no

davedarko wrote 02/19/2018 at 22:35 point

I wasn't able to recover the Timer from the interrupt routine, but that might safe some loops and whiles on the way. 
I also was messing up the interrupts big time :D 

  Are you sure? yes | no

Ted Yapo wrote 02/19/2018 at 14:59 point

Would it help to make an animations struct to pass by ref to animate_frames()?

struct animation

{

  const byte length;

  const byte *data;

  const int16_t duration;

};

you could have an array of these structs indexed by animation_counter.  It saves the repetitive code in the switch statement inside animate_leds().  Not sure if it ends up being a net space win.

There is the problem of the accelerating ones, too.  You could put a function pointer in the struct to parametrize the acceleration function, but I'm not sure how much space this ends up taking.

Or, you could parameterize accelerations by with a formula:

(a*acc_counter + b) * c + duration

with (a = -1, b = 63, c = 4)  or (a = 1, b = 0, c = 8) or (a = 0, b = 0, c = 0) you can get the current functionality. Again, I don't know if this would end up being smaller than the inline code version.

  Are you sure? yes | no

davedarko wrote 02/19/2018 at 15:10 point

interesting idea :) I thought about a joined animations array before that. That might safe a byte or two but then I'd need to store a start point then. So win some lose some.

  Are you sure? yes | no