ATtiny85 + TLC5940

dc42:

fungus:
The CPU spends most of its time generating a clock output on a pin at about 4MHz (the tlc5940 needs a clock signal for the PWM). That's 100% in my book...

Why not use one of the 2 counter/timers in the ATtiny85 to generate the 4MHz clock?

V1.0 of the library did that... but using timers to generate the signals needs both timers and all five I/O pins to make it work. I wanted to get it down to three pins so I could attach a couple of potentiometers/switches/whatever. You could even use a timer to play music on one of the other pins if you wanted to.

Besides, what would you do with all those extra CPU cycles? :slight_smile: In real life you'd probably only use 1% of them anyway (especially if you can't attach any external devices because you ran out of I/O pins - that kinda limits your processing to basic color sequencing).

Edit: I guess if you want to chain a lot of TLC5940s together the CPU power needed to update them all might become significant - even for basic color fades. In that case I'd dig out the v1.0 version of the library (and use a Tiny84 if I needed some free pins).

fungus:
OK, here's a copy of my library...hope somebody finds it useful!
artlum.com - Arduino

thank you very much! i will try it asap! :wink:

well, i tried it and it worked very well: Rainbow effect with RGB LEDs using 3 TLC5940's controlled by an ATtiny85 ƎR - YouTube
thank you for sharing it! :wink:
i had some problems because you suggested not to use millis and delays, but i see that the problems are caused only by delays, so we can use millis and micros... we just need to do the update method very often, right?
i've a question, i'm not an expert of making libraries, but to make this library work in an attiny84 do i just need to add this line?

#if defined (__AVR_ATtiny85__) || (__AVR_ATtiny84__)

or is there some register settings to do?
thanks again :wink:

?R:
well, i tried it and it worked very well: Rainbow effect with RGB LEDs using 3 TLC5940's controlled by an ATtiny85 ƎR - YouTube
thank you for sharing it! :wink:

No problem.

It's fun to think that the teeny chip in the corner is driving something that many LEDs... :slight_smile:

?R:
i had some problems because you suggested not to use millis and delays, but i see that the problems are caused only by delays, so we can use millis and micros... we just need to do the update method very often, right?

I don't think millis() will do anything useful. I turned off the hardware timer in setup() (if it's ticking then there's a bug!)

Time spent in between calls to update() is time the LEDs aren't lit up. I think you'd have to do a lot before you notice them get visibly dimmer, but bear it in mind.

?R:
i've a question, i'm not an expert of making libraries, but to make this library work in an attiny84 do i just need to add this line?

#if defined (__AVR_ATtiny85__) || (__AVR_ATtiny84__)

or is there some register settings to do?
thanks again :wink:

I just tried it and there's a line in setup() that doesn't compile. The line is:

  TCCR1 = 0;

It should work if you replace that line with:

#if defined (__AVR_ATtiny85__)
  TCCR1 = 0;
#else
  TCCR1A = TCCR1B = 0;
#endif

fungus:
I don't think millis() will do anything useful. I turned off the hardware timer in setup() (if it's ticking then there's a bug!)

Time spent in between calls to update() is time the LEDs aren't lit up. I think you'd have to do a lot before you notice them get visibly dimmer, but bear it in mind.

yes i noticed that, and i used millis and micros to synchronize the duration of functions and other things, i think it easier to use than the method that you used in your example code... i do something like this, and it acts like a delay, but it's useful for other things too:

#define TIME 100
...
_time=millis();
while(millis()- _time < TIME) tlc5940.update();

fungus:
I just tried it and there's a line in setup() that doesn't compile. The line is:

  TCCR1 = 0;

It should work if you replace that line with:

#if defined (__AVR_ATtiny85__)

TCCR1 = 0;
#else
  TCCR1A = TCCR1B = 0;
#endif



ok thank you, after i'll try that if i've time ;)

Whoa ?R, are those 2pin rgb fade leds or are those 4 pin rgb terminated leds? They look to only have 2 pins.

funkyguy4000:
Whoa ?R, are those 2pin rgb fade leds or are those 4 pin rgb terminated leds? They look to only have 2 pins.

they are RGB LEDs with common anode, 4 pins, they are controlled by 3 TLC's

about the tiny84, i'm trying it, but i think there is a problem with the port mapping, because in the tiny85 you set the pin PB0, PB1 and and PB2 but in attiny84 should be PA6 PA5 and PA4, do you agree with me? or am i wrong?

i modified this part and it compiles:

#if defined (__AVR_ATtiny85__) || (__AVR_ATtiny84__)
/*------------------------------------------------------------------------
  Configuration
------------------------------------------------------------------------*/
// How many TLC chips are attached. Each chip needs 32 bytes of RAM.
// The ATtiny85 only has 512 bytes of RAM and you need some left over
// for the rest of your program. Do the math...
#ifndef NUM_TLC5940s
#define NUM_TLC5940s 3
#endif

/*------------------------------------------------------------------------
  Pin assignments
------------------------------------------------------------------------*/
// You could move the BLANK_XLAT_PIN if you wanted to
// but the USI pins are fixed.
#if defined (__AVR_ATtiny85__)
#define BLANK_XLAT_PIN PB0
#define USI_OUT_PIN PB1
#define USI_CLK_PIN PB2
#else
#define BLANK_XLAT_PIN PA6
#define USI_OUT_PIN PA5
#define USI_CLK_PIN PA4
#endif

after i'll try the hardware, but i think that with those modifications that library can be called "Tiny_TLC5940" :slight_smile:
what do you think about? am i wrong?

?R:

funkyguy4000:
Whoa ?R, are those 2pin rgb fade leds or are those 4 pin rgb terminated leds? They look to only have 2 pins.

they are RGB LEDs with common anode, 4 pins, they are controlled by 3 TLC's

about the tiny84, i'm trying it, but i think there is a problem with the port mapping, because in the tiny85 you set the pin PB0, PB1 and and PB2 but in attiny84 should be PA6 PA5 and PA4, do you agree with me? or am i wrong?

Oh, yes, those pins need to change, too. :slight_smile:

PA6 PA5 and PA4 is correct but you'll also have to change all references to PORTB, DDRB and PINB into PORTA, DDRA and PINA.

Maybe you can #define those, too:

#if defined (__AVR_ATtiny85__)
#define BLANK_XLAT_PIN PB0
#define USI_OUT_PIN PB1
#define USI_CLK_PIN PB2
#define USI_DDR DDRB
#define USI_PORT PORTB
#define USI_PIN PINB
#else
#define BLANK_XLAT_PIN PA6
#define USI_OUT_PIN PA5
#define USI_CLK_PIN PA4
#define USI_DDR DDRA
#define USI_PORT PORTA
#define USI_PIN PINA
#endif

Then use (eg.) "USI_DDR" instead of "DDRB" in the code...

The code then becomes:

/*------------------------------------------------------------------------
  Bitbang driver for ATtiny85+TLC5940 using only three pins
------------------------------------------------------------------------*/
#ifndef _TEENY5940_H
#define _TEENY5940_H

#if defined (__AVR_ATtiny85__) || (__AVR_ATtiny84__)
/*------------------------------------------------------------------------
  Configuration
------------------------------------------------------------------------*/
// How many TLC chips are attached. Each chip needs 32 bytes of RAM.
// The ATtiny85 only has 512 bytes of RAM and you need some left over
// for the rest of your program. Do the math...
#ifndef NUM_TLC5940s
#define NUM_TLC5940s 1
#endif

/*------------------------------------------------------------------------
  Pin assignments
------------------------------------------------------------------------*/
// You could move the BLANK_XLAT_PIN if you wanted to
// but the USI pins are fixed.
#if defined (__AVR_ATtiny85__)

#define BLANK_XLAT_PIN PB0
#define USI_OUT_PIN PB1
#define USI_CLK_PIN PB2

#define USI_DDR DDRB
#define USI_PORT PORTB
#define USI_PIN PINB

#else

#define BLANK_XLAT_PIN PA6
#define USI_OUT_PIN PA5
#define USI_CLK_PIN PA4

#define USI_DDR DDRA
#define USI_PORT PORTA
#define USI_PIN PINA

#endif

/*------------------------------------------------------------------------
  The TLC5940 controller
------------------------------------------------------------------------*/
#define NUM_LEDS (16*NUM_TLC5940s)   /* 16 LEDs per chip */

class Teeny5940 {
  typedef uint8_t byte;
  typedef uint16_t pwm_val;
  pwm_val pwmVals[NUM_LEDS];
public:
  // ----------------------------------------
  // Initialize - call this in your "setup()"
  // ----------------------------------------
  void init() {
    USI_DDR |= _BV(BLANK_XLAT_PIN);    // Set pin as 'output'
    USI_PORT |= _BV(BLANK_XLAT_PIN);   // BLANK pin high (stop the TLC5940, disable all output)
    // Set all PWM values to zero
    clear();
  }
  
  // --------------------------
  // Set all PWM values to zero
  // --------------------------
  void clear() {
    byte *p = (byte*)pwmVals;
    byte *e = p + (NUM_LEDS*sizeof(pwm_val));
    while (p!=e) {
      *p++ = 0;
    }
  }
  
  // ------------------------------------------------------------------------
  // Set the PWM value of one of the PWM channels, 'value' in range [0..4095]
  //
  // eg. "set(2,2048)" sets LED 2 to 50% on, 50% off
  // ------------------------------------------------------------------------
  void set(byte channel, pwm_val value) {
    if ((channel&1)!=0) {
      pwmVals[channel] = value<<4;    // update() expects data for the odd channels to be shifted...
    }
    else {
      pwmVals[channel] = value&0x0fff;
    }
  }

  // ------------------------------------------------------------------------
  // Call this in your main loop to perform one complete PWM cycle
  //
  // With the Tiny85's internal 8mHz clock this takes almost exactly
  // one millisecond to execute :-)
  // ------------------------------------------------------------------------
  void update() const {
    // Set BLANK and XLAT low for the PWM cycle
    USI_PORT &= ~_BV(BLANK_XLAT_PIN);

    // Set up USI (USI drives the TLC5940 clocks and serial data input)
    byte sOut = _BV(USI_CLK_PIN)|_BV(USI_OUT_PIN);
    USI_DDR |= sOut;         // Pins are outputs
    USI_PORT &= ~sOut;       // Outputs are low
    USICR = _BV(USIWM0);     // Three wire mode

// Macros to drive the USI clock
#define tick USICR=_BV(USIWM0)|_BV(USITC)
#define tock USICR=_BV(USIWM0)|_BV(USITC)|_BV(USICLK)
#define clock8()   tick; tock;  tick; tock;  tick; tock;  tick; tock;  tick; tock;  tick; tock;  tick; tock;  tick; tock;

    // We need to send 4096 clock pulses per PWM cycle, ie. 512 bytes of data.
    // There's only 24 bytes of real data per TLC5940 so we send some dummy data first.
    byte dummySize = (512-(24*NUM_TLC5940s))/2;
    while (dummySize != 0) {
      clock8();      // Send clock pulses for dummy data...
      clock8();
      --dummySize;
    }

    // Now send the real PWM data
    // nb. This sequence of instructions is the result of a lot of
    // compiles/disassembles to get the best possible code output
    // from the compiler. Don't change it...
    byte numPairs = 8*NUM_TLC5940s;
    const pwm_val *pwm = pwmVals+NUM_LEDS-2;
    while (numPairs != 0) {
      const pwm_val hi = pwm[1];
      USIDR = hi>>8;
      clock8();
      const pwm_val lo = pwm[0];
      USIDR = hi|(lo>>8);
      clock8();
      USIDR = lo;
      clock8();
      pwm -= 2;
      --numPairs;
    }

    // Set BLANK and XLAT high while you do your updates
    USI_PIN = _BV(BLANK_XLAT_PIN);
  }
};


// A driver for you to use
Teeny5940 tlc5940;


#else
  unsupported_chip;
#endif

#endif /*_TEENY5940_H*/

very well, after i'll try it :wink:

i just tried and it works with attiny84 too :wink:

This is going to sound really newbish but how do you guys program your ATtinys? I have tried the High Low Tech tutorial numerous times and it doesn't work. Also I have a hard time finding ATtiny85 cores.
Any help?

funkyguy4000:
This is going to sound really newbish but how do you guys program your ATtinys?

I use an ISP programmer I bought on eBay

eg.: arduino isp programmer for sale | eBay

When I started out I just poked wires into the cable (see photos at the start of this thread).

Now I use a little board I made, see here:

http://arduino.cc/forum/index.php/topic,134673.0.html

As an experiment I've designed/ordered some little PCBs for an ATtiny85 version of that (nb. they didn't arrive yet...)

funkyguy4000:
I have tried the High Low Tech tutorial numerous times and it doesn't work. Also I have a hard time finding ATtiny85 cores.
Any help?

I think that was the page I used when I started out but I admit I never did "Arduino as ISP", I bought an ISP programmer.

I usually don't bother with cores any more. The chips are very easy to program directly and writing the code is part of the fun!

The real problem would be in using other libraries that rely on having SPI, etc. Adapting code that needs those things would be a challenge. I don't use other code much though (it rarely fits exactly) and I can always use a Mega328 if I really need to...

PS: This is my PCB for the Tiny85duino... :slight_smile:

Tiny85duino.png

i use an arduino as ISP programmer and with this core: Google Code Archive - Long-term storage for Google Code Project Hosting.

Google Code Archive - Long-term storage for Google Code Project Hosting.

I have made a litlle step by step using this core . Programming ATtiny85 with ArduinoISP

And i recommend using Coding Badly's TinyISP. It gives you all the debug benefits you are used to from the Arduino

Erni:
And i recommend using Coding Badly's TinyISP. It gives you all the debug benefits you are used to from the Arduino

Serial comms? The problem with that is the Tiny85 has very limited pins. Dedicating some of them to debug output is difficult.

PS: This is why I do most of my Tiny85 coding on a Tiny84... they're similar enough that the code can run on both chips without much effort and the Tiny84 has enough pins left over to use for debugging.

( I use LEDs and a little screen for debug output...you can see my development environment here: http://arduino.cc/forum/index.php/topic,139345.0.html )

When using the TinyISP you use the MISO pin to communicate, so it is not dedicated to debug output.

Anyway it is a nice little programming board you have made.
Any link to the display ?

Erni:
When using the TinyISP you use the MISO pin to communicate, so it is not dedicated to debug output.

It's still a pin not being used to light up LEDs... :frowning:

Erni:
Anyway it is a nice little programming board you have made.
Any link to the display ?

This is the one I bought: http://www.ebay.com/itm/170842973484

(also available from other sellers...)
http://www.ebay.com/itm/170929165280
http://www.ebay.com/itm/160889283903
http://www.ebay.com/itm/250986036479
http://www.ebay.com/itm/180945872898
...

nb. There's some cheaper OLED screens on eBay but those ones run off 5V and have an extra controller chip that can draw text, etc. all by itself - no need for font data or bitmap manipulation code on the Arduino. I definitely think it's worth the extra $5 for that feature (nb. I didn't know this when I bought mine, I got lucky...)

Thanks fungus, good to know those features i might not be so lucky

i'm using fungus's library and i want to try to consume less energy.
is there any way to say to the TLC "go to sleep" as we do with atmel microcontrollers?
do you think that just stopping the communication with it could be enough? or usign the method init?

void init() {
    USI_DDR |= _BV(BLANK_XLAT_PIN);    // Set pin as 'output'
    USI_PORT |= _BV(BLANK_XLAT_PIN);   // BLANK pin high (stop the TLC5940, disable all output)
    // Set all PWM values to zero
    clear();
  }

is the fact that it disable all output useful or not?
thanks in advance! :wink: