Big LED Screen/ledtest.c

From Noisebridge
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>


/* serial output */
#define MOSI_PINB PB3

/* serial clock */
#define SCK_PINB PB5

/* slave select, needs to be output but is otherwise just ued for
 * debugging */
#define SS_PINB PB2

/* pin on PORTB that's hooked up to the latch output from the buffer board */
#define LATCH_IN_PINB PB1

/* our output latch port to the daughterboard */
#define LATCH_OUT_PINB PB0

/* pin on portd that's hooked up to one of the power pins so we can
 * synchronize which power pin we're using */
#define POWER_PIND PD7

/* output pin that's 1 when we are on our first power output */
#define DEBUG_FIRST_POWER_PIND PD6

void ioinit(void)
{
  cli();

  // set output pins for output
  DDRB = _BV(MOSI_PINB) | _BV(SCK_PINB) | _BV(SS_PINB) | _BV(LATCH_OUT_PINB);

  // enable internal pull-up on power pin so it doesn't cause issues if unconnected
  // PORTD = _BV(POWER_PIND);

  // debugging output
  DDRD = _BV(DEBUG_FIRST_POWER_PIND);

  // enable PCI0 interrupt interrupt when latch pin changes
  PCMSK0 = _BV(PCINT1);
  // enable PCI2 interrupt when power pin changes (PD7)
  PCMSK2 = _BV(PCINT23);
  // enable PCI0 and PCI2 interrupts
  PCICR = _BV(PCIE0) | _BV(PCIE2);

  // put MOSI up when we're not clocking out
  PORTB |= _BV(MOSI_PINB);

  sei ();
}

static void spistart(void)
{
  // start SPI in master mode, at SPI clock
  // running at CPU frequency / 8
  SPCR = _BV(SPE) | _BV(MSTR) | _BV(SPR0);
  SPSR = _BV(SPI2X);

  PORTB &= ~_BV(SS_PINB); // lower slave select (for debugging)
}

static void spistop(void)
{
  SPCR = 0;
  PORTB |= _BV(SS_PINB); // raise slave select (for debugging)
}

unsigned volatile char can_write_out;

unsigned char pow_select; // 0..15 depending on sequence of latching
unsigned char pow_inverse;
unsigned char display_count;
unsigned char inverse;
unsigned char hilite_byte;

// clock out serial data
static void writeout(void)
{
  unsigned char this_one_inverse;
  unsigned char this_pow_select;
  unsigned char byte_count;

  this_pow_select = pow_select;
  pow_select++;
  if (pow_select > 15)
    pow_select = 0;

  sei();

  if (this_pow_select == 0)
    PORTD |= _BV(DEBUG_FIRST_POWER_PIND);

  if (this_pow_select == pow_inverse) {
    display_count++;
    if (display_count > (pow_inverse ? 120 : 30)) {
      if (inverse) {
        inverse = 0;
      } else {
        pow_inverse++;
        if (pow_inverse > 15) {
          pow_inverse = 0;
          hilite_byte++;
          if (hilite_byte > (200 / 8)) {
            hilite_byte = 0;
          }
        }
        inverse = 1;
      }
      display_count = 0;
    }
    this_one_inverse = inverse;
  } else {
    this_one_inverse = 0;
  }

  // turn off the latch to the daughterboards
  PORTB &= ~_BV(LATCH_OUT_PINB);

  // and actually clock out

  spistart();

  SPDR = 0b01010101;
  for (byte_count = 0; byte_count < (200/8); byte_count++) {
    unsigned register char to_write;

    to_write = this_one_inverse ? ~byte_count : byte_count;
    to_write = (byte_count == hilite_byte) ? ~to_write : to_write;
    /* SPIF drops low when the SPI output is finished, so we want to start
     * the next byte as soon as possible.
     *
     * This requires any work that needs to be done to gather the value to
     * write (such as loading it in a register) to be done before we wait
     * for SPIF to drop */
    __asm__(
            ".spi_not_ready:"                      "\n\t"
            "in __tmp_reg__,%[spsr]"               "\n\t"
            "sbrs __tmp_reg__, %[spif]"            "\n\t"
            "rjmp .spi_not_ready"                  "\n\t"
            "out %[spdr], %[towrite]"              "\n\t"
            ::
            [spsr] "I" (_SFR_IO_ADDR(SPSR)),
            [spif] "I" (SPIF),
            [spdr] "I" (_SFR_IO_ADDR(SPDR)),
            [towrite] "r" (to_write)
            );
  }
  
  spistop();
  PORTD &= ~_BV(DEBUG_FIRST_POWER_PIND);
}

ISR(PCINT0_vect) /* "PCI0" interrupts */
{
  // latch pin changed.  write out serial if we're not in the middle of it already
  if (!can_write_out) return;

  // output latch pin to activate the new values we shifted in last time:
  PORTB |= _BV(LATCH_OUT_PINB);
  // (it's turned off within "writeout")

  // mark writing out as busy, and then enable interrupts so we can go on with life
  can_write_out = 0;  

  // now, emit our data
  writeout();

  // we're done! re-enable writing out
  can_write_out = 1;
}

// PCINT23 toggled
ISR(PCINT2_vect)
{
  if (PIND & _BV(POWER_PIND)) {
    // low->high transition at the beginning of the power signal.
    // we get a latch spike at approximately this time, so we have to figure out whether
    // we got that or not in order to avoid the race condition:
    if (can_write_out == 0) {
      // we're busy writing out, hopefully this is frame 0.  reset to frame 1.
      pow_select = 1;
    } else {
      // haven't started to write out frame 0 yet; next frame is 0
      pow_select = 0;
    }
  }
}

int main(void) 
{
  can_write_out = 1;
  ioinit();

  for (;;) {
    can_write_out = 2;
    _delay_ms(100);
    cli();
    if (can_write_out == 2) {
      // we didn't receive any input on the latch pin; write out every 100 ms
      // for debugging purposes
      writeout();
    }
    sei();
  }
}