Go Down

Topic: TLC5940 and ATtiny84 - working! (Read 2319 times) previous topic - next topic

fungus

Sep 24, 2012, 06:21 pm Last Edit: Sep 24, 2012, 10:52 pm by fungus Reason: 1
I just got a TLC5940 hooked up to a ATtiny84!

I might make the code into a proper library if anybody's interested, leave votes below...

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Coding Badly


Did you use existing USI code or roll your own?

fungus



Did you use existing USI code or roll your own?



I rolled my own...

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Coding Badly


If the offer still stands I am definitely interested in your code.  You do not need to take the time to turn it into a library.

fungus

#4
Mar 14, 2013, 08:26 pm Last Edit: Mar 14, 2013, 08:33 pm by fungus Reason: 1


If the offer still stands I am definitely interested in your code.  You do not need to take the time to turn it into a library.



The reason I never made a library is that it uses Timer0*, so millis(), delay(), etc. would stop working. That would cause a lot of support issues (driving the TLC5940 with hardware needs two timers).

The interrupt in the library is called at about 1ms intervals (with 8MHz CPU clock). It would be easy to increment a counter in there for program timing.

Here's the code FWIW: :)

Code: [Select]

#ifndef TINY940_H
#define TINY940_H
#include <avr/io.h>
#include <avr/interrupt.h>

/*------------------------------------------------------------------------
 User configurable values
------------------------------------------------------------------------*/

#ifndef NUM_TLC5940s
#define NUM_TLC5940s 1
#endif


/*------------------------------------------------------------------------
 Standard data types
------------------------------------------------------------------------*/
#include <stdint.h>
typedef uint8_t byte;
typedef int16_t int16;
typedef uint16_t uint16;

/*------------------------------------------------------------------------
 Speed of timers
------------------------------------------------------------------------*/
// Timer 0 flips OC0A every (GSCLK_TIM+1) clock cycles
// With a setting of "1" we get a complete clock pulse every 4 CPU cycles
// ie. A 2mHz GSCLK at 8mHz CPU speed
#define GSCLK_TIM 1

// How many CPU cycles in a complete GSCLK pulse
#define GSCLK_CYCLES (2*(GSCLK_TIM+1))

/*------------------------------------------------------------------------
 Chip-specific things

 Most of the pin definitions can't be changed
------------------------------------------------------------------------*/

#if defined(__AVR_ATtiny84__)

// Pin definitions for ATtiny84
#define BLANK_PIN PB0
#define XLAT_PIN PB1
#define GSCLK_PIN PB2      /* Must be OC0A */
#define SERIAL_CLK PA4
#define SERIAL_OUT PA5
#define SERIAL_DDR DDRA    /* Where the serial direction bits are */
#define SERIAL_PORT PORTA  /* Where the serial output pins are */

void initHardwareTimers()
{
 // Timer 0 -> 2MHz GSCLK output on OC0A
 TCCR0A=0; TCCR0B=0;   // Stop the timer
 TIMSK0 = 0;           // No interrupts from this timer
 TCNT0 = 0;            // Counter starts at 0
 OCR0A = GSCLK_TIM;    // Timer restart value
 TCCR0A = _BV(WGM01)   // CTC mode
         |_BV(COM0A0); // Toggle OC0A output on every timer restart
 TCCR0B = _BV(CS00);   // Start timer, no prescale

 // Timer 1 -> Generate an interrupt every 4096 GSCLK pulses
 TCCR1A=0; TCCR1B = 0; // Stop the timer
 TCNT1 = 0;            // Counter starts at 0
 OCR1A = 0;            // Where the interrupt happens during the count
 ICR1 = GSCLK_CYCLES*4096; // There's four clock cycles for every CSCLK and 4096 per PWM cycle
 TIMSK1 = _BV(OCIE1A); // Interrupt every time the timer passes zero
 TCCR1A = 0;           // No outputs on chip pins
 TCCR1C = 0;           // Just in case...
 TCCR1B = _BV(WGM12)   // Start timer, CTC mode
         |_BV(WGM13)   // Start timer, CTC mode
         |_BV(CS10);   // no prescale
}

#elif defined (__AVR_ATtiny85__)

// Pin definitions for ATtiny85
#define BLANK_PIN PB3
#define XLAT_PIN PB4
#define GSCLK_PIN PB0      /* Must be OC0A */
#define SERIAL_CLK PB2
#define SERIAL_OUT PB1
#define SERIAL_DDR DDRB    /* Where the serial direction bits are */
#define SERIAL_PORT PORTB  /* Where the serial output pins are */

void initHardwareTimers()
{
 // Timer 0 -> 2MHz GSCLK output on OC0A
 TCCR0A=0; TCCR0B=0;   // Stop the timer
 TIMSK = 0;            // No interrupts from this timer
 TCNT0 = 0;            // Counter starts at 0
 OCR0A = GSCLK_TIM;    // Timer restart value
 TCCR0A = _BV(WGM01)   // CTC mode
         |_BV(COM0A0); // Toggle OC0A output on every timer restart
 TCCR0B = _BV(CS00);   // Start timer, no prescale

 // Timer 1 -> Generate an interrupt every 4096 GSCLK pulses
 TCCR1=0;  GTCCR=0;    // Stop the timer
 TCNT1 = 0;            // Counter starts at 0
 OCR1A = 0;            // Where the interrupt happens during the count
 OCR1C = GSCLK_CYCLES; // Prescaler is 4096...total clocks is 4096*GSCLK_CYCLES
 TIMSK = _BV(OCIE1A);  // Interrupt every time the timer passes zero
 TCCR1 = _BV(CTC1)     // Start timer, CTC mode
         |_BV(CS10)    // Prescale=4096
         |_BV(CS12)
         |_BV(CS13);
}

#else
 unsupported_chip;
#endif

/*------------------------------------------------------------------------
 Interrupt handler - BLANK and XLAT are sent here
------------------------------------------------------------------------*/
// Set this flag whenever you want an XLAT bit to be sent
static bool doXLAT = false;

// Interrupt called at the end of each PWM cycle  - do BLANK/XLAT here
ISR(TIM1_COMPA_vect) // nb. *NOT*   "TIMER1_COMPA_vect"
{
 if (doXLAT) {
   PORTB |= _BV(BLANK_PIN)|_BV(XLAT_PIN);
   doXLAT = false;
   PINB = _BV(XLAT_PIN);      // XLAT low
 }
 else {
   PORTB |= _BV(BLANK_PIN);
 }
 PINB = _BV(BLANK_PIN);       // BLANK low
}

/*------------------------------------------------------------------------
 The TLC5940 controller
------------------------------------------------------------------------*/
class Tiny5940 {
 typedef byte pwm_index_type;
 byte pwmData[24*NUM_TLC5940s];
 void sendByte(byte b) {
   // Put the byte in the USI data register
   USIDR = b;
   // Send it
#define tick USICR = _BV(USIWM0)|_BV(USITC)
#define tock USICR = _BV(USIWM0)|_BV(USITC)|_BV(USICLK);
   tick;  tock;  tick;  tock;
   tick;  tock;  tick;  tock;
   tick;  tock;  tick;  tock;
   tick;  tock;  tick;  tock;
 }
public:
 void init() {
   cli();            // The system may already have interrupts running on the timers, disable them
   DDRB |= _BV(BLANK_PIN)|_BV(XLAT_PIN)|_BV(GSCLK_PIN);  // All port B pins as outputs
   PORTB |= _BV(BLANK_PIN);                              // BLANK pin high (stop the TLC5940, disable all output)
   initHardwareTimers();
   setAll(0);
   PINB = _BV(XLAT_PIN);
   PINB = _BV(XLAT_PIN);
   update();
   sei();
 }
 void setAll(uint16 value) {
   for (pwm_index_type i=0; i<16*NUM_TLC5940s; ++i) {
     set(i,value);
   }
 }
 void set(pwm_index_type channel, uint16 value) {
   if (channel < (16*NUM_TLC5940s)) {
     // Pointer to a pair of packed PWM values
     byte *pwmPair = pwmData+((channel>>1)*3);
     if (channel&1) {
       // Replace high part of pair
       pwmPair[1] = (pwmPair[1]&0x0f) | ((value<<4)&0xf0);
       pwmPair[2] = value >> 4;
     }
     else {
       // Replace low part of pair
       pwmPair[0] = value&0xff;
       pwmPair[1] = (pwmPair[1]&0xf0) | ((value>>8)&0x0f);
     }
   }
 }
 bool update() {
   // Still busy sending the previous values?
   if (doXLAT) {
     // Yes, return immediately
     return false;
   }

   // Set up USI to send the PWMdata
   byte sOut = _BV(SERIAL_CLK)|_BV(SERIAL_OUT);
   SERIAL_DDR |= sOut;     // Pins are outputs
   SERIAL_PORT &= ~sOut;   // Outputs start low
   USICR = _BV(USIWM0);    // Three wire mode

   // Send it...
   pwm_index_type numBytes = sizeof(pwmData);
   byte *pwm = pwmData+numBytes;   // High byte first...
   while (numBytes > 0) {
     sendByte(*--pwm);
     --numBytes;
   }

   // Tell the interrupt routine there's data to be latched
   doXLAT = true;

   return true;
 }
};


// Create a Tiny5940 for people to use
Tiny5940 tlc;

#endif


I think it's compatible with the regular TLC5940 library.
No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Coding Badly

(driving the TLC5940 with hardware needs two timers)


I believe the "standard" TLC5940 library also uses two timers.  I'm baffled.  Why is that necessary?

fungus


(driving the TLC5940 with hardware needs two timers)


I believe the "standard" TLC5940 library also uses two timers.


Yep, but the Mega328 has three timers so it can leave Timer0 alone.


I'm baffled.  Why is that necessary?


One has to generate GSCLK. The other has to count to 4096. There's no way to do it with a single timer.


No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Riccarr

Hi,

Two questions.

Would this code work on the Uno (with little or lots of modifications), and would it drive some servos off the TLC5940 ?

Thanks.

Coding Badly

#8
Mar 15, 2013, 12:12 am Last Edit: Mar 15, 2013, 07:33 am by Coding Badly Reason: 1

@fungus...

The other library use CLKO for GSCLK and a timer to count to 4096.  CLKO requires a fuse change which seems reasonable when working with an ATtiny processor.

Would that work for your project?

Coding Badly

Would this code work on the Uno (with little or lots of modifications)...


This works on the Uno...
http://code.google.com/p/tlc5940arduino/

Quote
...and would it drive some servos off the TLC5940 ?


From that same page...
Quote
Now supports servos! See the example (Sketchbook->Examples->Library-Tlc5940->Servos) and documentation.

fungus



@fungus...

The other library use CLKO for GSCLK and a timer to count to 4096.  CLKO requires a fuse change which seems reasonable when working with an ATtiny processor.

Would that work for your project?



I don't see any reason why not...the TLC5940 will accept up to 30MHz on GSCLK.

You'll have to change "GSCLK_CYCLES" to 1 and take out the setup for Timer0 in "initHardwareTimers()" in the source code.

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

fungus


Would this code work on the Uno (with little or lots of modifications)...


This works on the Uno...
http://code.google.com/p/tlc5940arduino/


My code was based on that... :)
No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Coding Badly


I did a quick sanity check.  I have an ATtiny84 with CKOUT (aka CLKO) connected to the TLC5940 GSCLK pin.  Everything else is connected an Uno.  No problems.

8 MHz, 4096 steps, that comes out to 512 microseconds.  I wonder how well blink-without-delay would work?  Is it really that important for BLANK to be fired accurately?

@fungus, what did you build with an ATtiny84 and a TLC5940?  How accurate does BLANK need to be?

fungus


8 MHz, 4096 steps, that comes out to 512 microseconds.  I wonder how well blink-without-delay would work?  Is it really that important for BLANK to be fired accurately?

@fungus, what did you build with an ATtiny84 and a TLC5940?  How accurate does BLANK need to be?


All LEDs turn off after 4096 pulses on GSCLK (when they go past their PWM value). The BLANK signal turns them on again and resets the internal counter for a new PWM cycle. It doesn't have to be perfect.

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

mixania

Wow! Nice job with the programming!
Arduino Uno R3
Mac OSX Lion

Go Up