Big LED Screen/ledtest.c

From Noisebridge
Revision as of 19:40, 3 February 2009 by Shkoo (talk | contribs) (New page: <pre> #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 /* sl...)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
#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();
  }
}