Pages: [1] 2   Go Down
Author Topic: TLC5940 and ATtiny84 - working!  (Read 1808 times)
0 Members and 1 Guest are viewing this topic.
Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 144
Posts: 5331
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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...

« Last Edit: September 24, 2012, 03:52:38 pm by fungus » Logged

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

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 197
Posts: 12747
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


Did you use existing USI code or roll your own?
Logged

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 144
Posts: 5331
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


Did you use existing USI code or roll your own?


I rolled my own...

Logged

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

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 197
Posts: 12747
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


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.
Logged

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 144
Posts: 5331
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


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: smiley

Code:
#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.
« Last Edit: March 14, 2013, 02:33:49 pm by fungus » Logged

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

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 197
Posts: 12747
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

(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?
Logged

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 144
Posts: 5331
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

(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.


Logged

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

Portland, Oregon, USA
Offline Offline
Newbie
*
Karma: 0
Posts: 39
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 197
Posts: 12747
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


@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?
« Last Edit: March 15, 2013, 01:33:11 am by Coding Badly » Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 197
Posts: 12747
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 144
Posts: 5331
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


@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.

Logged

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

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 144
Posts: 5331
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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... smiley
Logged

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

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 197
Posts: 12747
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


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?
Logged

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 144
Posts: 5331
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Logged

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

Westbrook, CT
Offline Offline
Full Member
***
Karma: 2
Posts: 139
"Why should I bother with made-up games when there are so many real ones going on." (c) Kurt Vonnegut
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Wow! Nice job with the programming!
Logged

Arduino Uno R3
Mac OSX Lion


Pages: [1] 2   Go Up
Jump to: