Go Down

Topic: FINISHED PROJECT: ATTiny13A DMX Receiver, to WS2813 LED controller in 962 bytes (Read 1 time) previous topic - next topic

mcnobby

Hi All,

I thought I would publish this for all those who are interested in DMX and those clever little WS2812 RGB leds

Its written to fit in an ATTiny13A which has 1k of flash space and 64 bytes of Ram

Code: [Select]
/*
ATTINY13 - DMX512-Receiver to 4 x WS2812 driver - 9.6mhz - 962 bytes
LED to show that DMX data is being received
Up/Down buttons to select DMX RX channel - both held will reset channel to 1
Channel saved to EEprom
Requires 75176 differential transceiver chip to interface DMX to ATTiny13

Get tiny13 cores -> http://forum.arduino.cc/index.php?topic=89781.0

boards.txt entries :
attiny13at9.name=ATtiny13 @ 9.6MHz (internal 9.6 MHz clock)
attiny13at9.bootloader.low_fuses=0x3a
attiny13at9.bootloader.high_fuses=0xff
attiny13at9.upload.maximum_size=1024
attiny13at9.build.mcu=attiny13
attiny13at9.build.f_cpu=1200000
attiny13at9.build.core=core13

                 RESET 1 -     - 8 VCC
     DEBUG/LED/A3/Pin3 2 -     - 7 Pin2/A1/SCK/WS2812 data
  DMXin+/DMXin/A2/Pin4 3 -     - 6 Pin1/MISO/PWM/CHANUP button to 0v
                   GND 4 -     - 5 Pin0/MOSI/PWM/CHANDN button to 0v
*/

#include <avr/eeprom.h>
#define WAIT_FOR_BREAK_START 0
#define WAIT_FOR_START_BIT 1

// Pin definitions
#define DMX_IN 4 // PortB4 = actual pin 3 on chip
#define INPUT_PULLUPS 0b00000011 // pin 0 & 1 for pull ups only
#define UP_KEY 0b00000010
#define DN_KEY 0b00000001
#define KEYS   0b00000011
#define DEBUG_PIN 0b10000000
#define LED_MASK 0b00001000
// precise timings etc
#define MAXCHANNELS 512
#define PINMASK 0b00010000
#define DEBOUNCE 100 // debounce time in micros
// WS2812 specific
#define DIGITAL_PIN   (2)         // Digital port number
#define PORT          (PORTB)     // Digital pin's port
#define PORT_PIN      (PORTB2)    // Digital pin's bit position
#define DMX_PIN       (PORTB4)
#define NUM_CHANS     (13)

uint8_t lastRaw, key; // 2 bytes (2)
uint8_t rxData, state, chCount, x, LED_count; // 5 bytes (7) // flags,
unsigned long keyTime; // 4 bytes (11)
uint16_t DMXstart, rxCount; // 4 bytes (15)
uint8_t dmxData[NUM_CHANS];  

void setup(void) {
  DDRB |= 0b00001100; // set data direction for debug etc
  PORTB |= INPUT_PULLUPS; // used when setting key inputs
  // EEPROM INIT (if uninitialsed, then init !)
  if ( eeprom_read_byte((unsigned char *) 2) ) {
    eeprom_write_byte((unsigned char *) 2, 0 ); EEPROMwrite(1);
  }
  // READ EEPROM
  DMXstart = eeprom_read_byte((unsigned char *) 0) + ( eeprom_read_byte((unsigned char *) 1) <<8 );
  cli();
  WS2812(); // clear LEDs
}

void loop(void) {
  cli();
  switch (state) {

    case WAIT_FOR_BREAK_START:
      asm ( "L_%=:\n\t" "sbic %0, %1\n\t" "rjmp L_%=\n\t" :: "I" (_SFR_IO_ADDR(PORT)), "I" (DMX_PIN)  ); // wait for break to start (HIGH->LOW)
      chCount = 0;
      while (chCount < 22 ) { // this value should really be <22 to measure 88us
        asm ("ldi %0,8\n break:\n dec %0\n brne break\n" :: "r" (8) ); // to give a 4us loop
        if (PINB & PINMASK) { chCount=0; }  // if High then reset count
        else { chCount++; }
      }
      while (!(PINB & PINMASK)) { }  // wait for break to expire
      if (!LED_count--) { // heartbeat indicator
        PORTB ^= LED_MASK; LED_count=16;
      }
      rxCount = chCount = 0; // reset byte counter
      state = WAIT_FOR_START_BIT; // set next state

    case WAIT_FOR_START_BIT:
      while (PINB & PINMASK) { } // wait while input is high
      uint8_t m=1; // start bit mask at 0b00000001
      asm ("here1:\n dec %0\n brne here1\n" :: "r" (5) ); // delay to align sample of first bit
      for (x=0; x<8; x++) {
        asm ("ldi %0,7\n nop\n nop\n bits:\n dec %0\n brne bits\n" :: "r" (7) );
        // the above line may require adjustment for internal resonator
        // generally the 2 x NOPs work, but I have seen some requiring either one NOP or even NONE
        PORTB |= DEBUG_PIN; // do not remove
        if (PINB & PINMASK) { rxData |= m; }  
        if (!(PINB & PINMASK)) { rxData &= ~m; }  
        m <<= 1; // shift mask right
        PORTB &= ~DEBUG_PIN; // do not remove
      }
      asm ("stop:\n dec %0\n brne stop\n" :: "r" (5) ); // post bit read delay, wait for stop bits
      
      if ( (PINB & PINMASK) && (chCount <= NUM_CHANS) ) { // stop bit found ?
          if ( rxCount >= DMXstart )  { dmxData[++chCount] = rxData; } // ensure captured channel is in our range
          rxCount++; // get next byte
          state = WAIT_FOR_START_BIT; // go and grab next byte
          break;
      }
      // if you get to here then all required channels have been captured, so dump rest of packet and do WS2812 stuff
      else {
        uint8_t t; for (x=1; x<NUM_CHANS; x+=3) {
          t=dmxData[x+1]; dmxData[x+1]=dmxData[x]; dmxData[x]=t; // rearrange RGB input to GRB for WS2812
        }
        WS2812(); // output data to devices
        state = WAIT_FOR_BREAK_START; // start all over again
        break;
      }
   }
}

void WS2812(void) {
  for (volatile uint8_t y=1; y<NUM_CHANS; y++) {
      asm volatile(
          "ldi  %3, 8\n\t"      // reset number of bits
        "nextbit:\n\t"          // label                      
          "sbi  %0, %1\n\t"     // SET OUTPUT HIGH
          "sbrs %4, 7\n\t"      // Skip output low if HiBit in value is set  
          "cbi %0, %1\n\t"      // SET OUTPUT LOW, early for a low
          "sbrc %4, 7\n\t"      // Skip output low if HiBit in value is clear  
          "cbi %0, %1\n\t"      // SET OUTPUT LOW, late for a high
          "rol  %4\n\t"         // shift value left to get to next bit
          "dec  %3\n\t"         // decrement nBits
          "brne nextbit\n\t"    // branch if bits not finished
          ::
          // Input operands         Operand Id (w/ constraint)
          "I" (_SFR_IO_ADDR(PORT)), // %0
          "I" (PORT_PIN),           // %1
          "e" (&PORT),              // %a2
          "r" (8),                  // %3
          "r" (dmxData[y])          // %4
        ); // asm
    } // x loop
    readKeys();
}

void readKeys(void) {
    uint8_t raw = ~PINB & KEYS; // read key in reverse
    if (raw != lastRaw) {
     keyTime = micros();
    }
    if ((micros() - keyTime) > DEBOUNCE && raw != key) {
      key = raw;
      if ( key == KEYS ) {
        EEPROMwrite(1); // reset DMX channel to 1
      }
      if ( (key == DN_KEY) && DMXstart > 1) {
        EEPROMwrite(--DMXstart); // decrease DMX channel & save
      }
      if ( (key == UP_KEY) && DMXstart < (uint16_t) (MAXCHANNELS - NUM_CHANS) ) {
        EEPROMwrite(++DMXstart); // increase DMX channel & save
      }
    }
    lastRaw = raw;
}

void EEPROMwrite(uint16_t val) {
  eeprom_write_byte((unsigned char *) 0, val);
  eeprom_write_byte((unsigned char *) 1, val>>8);
}


Regards, Bob
while (z--) { snoreEvenLouder(); }
www.smartshow.lighting - www.dmx512.lighting

mcnobby

while (z--) { snoreEvenLouder(); }
www.smartshow.lighting - www.dmx512.lighting

mcnobby

while (z--) { snoreEvenLouder(); }
www.smartshow.lighting - www.dmx512.lighting

Genesis92

great and interesting :)
The internal 9.6Mhz clock is ok over temperature range to have good timings for the DMX speed and asynchronous WS2812 chips?
Designing and building electronic systems for over 20 years (Research and development engineer in electronics)
French robotic trophies champion in 1999 (using first atmel SPI microcontroller...vintage Uuh! :D)

mcnobby

great and interesting :)
The internal 9.6Mhz clock is ok over temperature range to have good timings for the DMX speed and asynchronous WS2812 chips?

The WS2812 are fine, but I have found the DMX can be a bit flakey over temperature and even some t13 chips are different to others... in saying that you get a lot of bang for your buck with this code.. its just a bit of fun.. I managed to make 10 working boards that seemed reasonably stable, although some of them needed tweaking timing wise (see notes in code) - really this should be done using the bits that can finely adjust the resonator frequency
while (z--) { snoreEvenLouder(); }
www.smartshow.lighting - www.dmx512.lighting

Genesis92

Designing and building electronic systems for over 20 years (Research and development engineer in electronics)
French robotic trophies champion in 1999 (using first atmel SPI microcontroller...vintage Uuh! :D)

mcnobby

while (z--) { snoreEvenLouder(); }
www.smartshow.lighting - www.dmx512.lighting

Bianco

Have you tried using main() and while(1) rather than setup() and loop()?

It saves a few hundred bytes in some situations.

smeezekitty

Avoid throwing electronics out as you or someone else might need them for parts or use.
Solid state rectifiers are the only REAL rectifiers.
Resistors for LEDS!

mcnobby

There have been several occasions where I have had to use main() instead of loop() etc, I think it may have had something to do with hijacking timer0 (on other projects).

I have had to go the main() route on THIS project, not only to save bytes, but it seemed to be the only way to solve a _vector error that I couldnt trace.. I think I looked it up on line somewhere and the suggestion was to move to main(), and by doing that I didnt noticd any loss in core functions as I write a fair amount in asm these days

:)
while (z--) { snoreEvenLouder(); }
www.smartshow.lighting - www.dmx512.lighting

Bianco

Without setup(), many of the core functions won't work.
They will if you include the libraries, won't they?

Go Up
 


Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

Arduino
via Egeo 16
Torino, 10131
Italy