14 LED "knight rider" with hardware PWM

Now, this is still a work in progress, but I really like what I've done so far and thought it was share-worthy. Basically, this is a combination of an arduino and two ATTiny2313s to make a 14 LED bargraph type thing with individual hardware control of each LED. No software PWM means easier customizability, and more reliability for checking serial and stuff. Basically, the arduino controls the middle 6 LEDs and then sends 4 serial bytes to each ATTiny over a software serial port (1 byte per LED). I'm still piecing the project together, but the point is I have all the pieces and I really like what I have so far. I will post pictures once I finish breadboarding. The way you make the patterns is pretty simple. You have an array "matrix" 14 tall (1 byte for each LED) and however long you want. The software first sends the serial commands to each ATTiny and then updates its own code. I will post more once the project comes along.

wow :o that was literally the best experience I've ever had with micros. Everything came together flawlessly, there were literally less than three times when I even had to debug anything, and all of those were minor issues. I think only two times actually... BTW there are two red LEDs in the center so you can see where it is.

From the youtube description: An arduino hooked up to two ATtiny2313 microcontrollers for a total of 14 PWM pins, for which I have made a very simple way to assign the values in a 14-character array to their duty cycles. Each LED has 8-bit resolution (256 brightness levels) and can show any pattern you want, at any speed you want.





Nice job man, I like the fade effects, I'd love to see some of your code and how the communication with the ATtiny's works as I'm thinking of doing something similar to lighten the load of my Arduino in some projects. :sunglasses:

sure thing. On arduino:

#include <NewSoftSerial.h>
NewSoftSerial right(2,4);
NewSoftSerial left(7,8);
int ra[3][14]={//this is the effect pattern for the "race" function
  {
    255,0,0,255,0,0,255,255,0,0,255,0,0,255  }
  ,
  {
    0,0,255,0,0,255,0,0,255,0,0,255,0,0  }
  ,
  {
    0,255,0,0,255,0,0,0,0,255,0,0,255,0  }
  ,

};
int io[20][14]={//this is the effect patter for the "inout" function (knight rider)
  {
    0,0,0,0,0,0,255,255,0,0,0,0,0,0  }
  ,
  {
    0,0,0,0,0,255,200,200,255,0,0,0,0,0  }
  ,
  {
    0,0,0,0,255,200,150,150,200,255,0,0,0,0  }
  ,
  {
    0,0,0,255,200,150,100,100,150,200,255,0,0,0  }
  ,
  {
    0,0,255,200,150,100,50,50,100,150,200,255,0,0  }
  ,
  {
    0,255,200,150,100,50,0,0,50,100,150,200,255,0  }
  ,
  {
    255,200,150,100,50,0,0,0,0,50,100,150,200,255  }
  ,
  {
    255,150,100,50,0,0,0,0,0,0,50,100,150,255  }
  ,
  {
    255,100,50,0,0,0,0,0,0,0,0,50,100,255  }
  ,
  {
    255,50,0,0,0,0,0,0,0,0,0,0,50,255  }
  ,
  {
    255,0,0,0,0,0,0,0,0,0,0,0,0,255  }
  ,
  {
    200,255,0,0,0,0,0,0,0,0,0,0,255,200  }
  ,
  {
    150,200,255,0,0,0,0,0,0,0,0,255,200,150  }
  ,
  {
    100,150,200,255,0,0,0,0,0,0,255,200,150,100  }
  ,
  {
    50,100,150,200,255,0,0,0,0,255,200,150,100,50  }
  ,
  {
    0,50,100,150,200,255,0,0,255,200,150,100,50,0  }
  ,
  {
    0,0,50,100,150,200,255,255,200,150,100,50,0,0  }
  ,
  {
    0,0,0,50,100,150,255,255,150,100,50,0,0,0  }
  ,
  {
    0,0,0,0,50,100,255,255,100,50,0,0,0,0  }
  ,
  {
    0,0,0,0,0,50,255,255,50,0,0,0,0,0  }
};
const int a=3;
const int b=5;
const int c=6;
const int d=9;
const int e=10;
const int f=11;
const int reset=12;
const int time=50;
void update(int set[14]);
void inout();
void race();
char mode;
void setup(){
  right.begin(9600);//begin software serials
  left.begin(9600);
  pinMode(reset, OUTPUT);
  digitalWrite(reset, LOW);//pull the reset pins on both ATTinys low to sync up the programs
  delay(2);//wait to make sure the pins get pulled low
  digitalWrite(reset, HIGH);
  delay(5);//this is probably too much time, but it lets the ATTiny run their setup routine
}

void loop(){

inout();//call whichever effect you want in the loop repeatedly
}

void update(int set[14]){//this updates all the LEDs
  left.print(byte(set[0]));//this prints an 8 bit value for each LED on the attinys
  right.print(byte(set[10]));
  left.print(byte(set[1]));
  right.print(byte(set[11]));
  left.print(byte(set[2]));
  right.print(byte(set[12]));
  left.print(byte(set[3]));
  right.print(byte(set[13]));
  analogWrite(a,set[4]);
  analogWrite(b,set[5]);
  analogWrite(c,set[6]);
  analogWrite(d,set[7]);
  analogWrite(e,set[8]);
  analogWrite(f,set[9]);
}
void race(){
  for(int i=0;i<3;i++){
    update(ra[i]);
    delay(time);
  }
}
void inout(){
  for(int i=0;i<20;i++){
    update(io[i]);
    delay(time);
  }
}

on the attiny

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
int main(void)
{
             
      DDRB |= (1<<PB2); 
      DDRB |= (1<<PB3); 
      DDRD |= (1<<PD5);
      DDRB |= (1<<PB4);
      TCCR0B = 0;                     // stop timer 0
      TCCR0A = (1<<WGM00)|(1<<WGM01); // select fast pwm mode 3
      TCCR0A |= (1<<COM0A1);          //Clear OC0A on Compare Match when up-counting. Set OC0A on Compare Match when down-counting.
      OCR0A = 0x00;                   //duty cycle
      OCR0B = 0x00; 
      TCCR0B |= (1<<CS00);            // no prescaling
      TCCR0A |=  (1<<COM0B1); //enable pD5
      TCCR1B = 0;                     // stop timer 1
      TCCR1A = (1<<WGM12)|(1<<WGM10); // select fast pwm mode 3
      TCCR1A |= (1<<COM1A1);          //Clear OC0A on Compare Match when up-counting. Set OC0A on Compare Match when down-counting.
      TCCR1B |= (1<<1);            // no prescaling
      TCCR1A |=  (1<<COM1B1);            //enable the last PWM pin
      UBRRL=51;
      //BAUD RATE=(F_CPU/(BAUD*16))-1 (ROUNDED) 9600 baud
      UCSRB=(1<<RXEN)|(1<<TXEN);
      //ENABLE RX/TX
      UCSRC=(1<<UCSZ1)|(1<<UCSZ0);
      //8 DATA BITS 1 STOP BIT
      OCR0A=0;//set PWM duties to 0
      OCR0B=0;
      OCR1B=0;
      OCR1A=0;
      
      
      while(1){

            while(!(UCSRA&(1<<RXC)));//WAIT FOR INCOMING DATA
            OCR0A=UDR;//recieve incoming character, assign to duty cycle
            while(!(UCSRA&(1<<RXC)));//WAIT FOR INCOMING DATA
            OCR0B=UDR;//recieve incoming character, assign to duty cycle
            while(!(UCSRA&(1<<RXC)));//WAIT FOR INCOMING DATA
            OCR1A=UDR;//recieve incoming character, assign to duty cycle
            while(!(UCSRA&(1<<RXC)));//WAIT FOR INCOMING DATA
            OCR1B=UDR;//recieve incoming character, assign to duty cycle

      }
      return 0;
      
      }

Just out of curiosity, why did you do this with the hardware PWM?

You could accomplish the same thing with SoftPWM Library.

http://code.google.com/p/rogue-code/wiki/SoftPWMLibraryDocumentation

This video shows how you can do it on 8 LEDs, but you could easily extend it to up to 16 LEDs. You still get 255 PWM values, plus you can define fade speeds (automatic fades).

Here is the source for the 8 LED version (on a LEDHead):

#include <SoftPWM.h>

#define DELAY 40

uint8_t leds[8] = {22, 23, 26, 27, 28, 29, 30, 31};

void setup()
{
  SoftPWMBegin();

  for (int i = 0; i < 8; i++)
    SoftPWMSet(leds[i], 0);

  SoftPWMSetFadeTime(ALL, 30, 200);

}

void loop()
{
  int i;

  for (i = 0; i < 3; i++)
  {
    SoftPWMSet(leds[i+1], 255);
    SoftPWMSet(leds[6-i], 255);
    SoftPWMSet(leds[i], 0);
    SoftPWMSet(leds[7-i], 0);
    delay(DELAY);
  }
  
  delay(250);
  
  for (i = 3; i > 0; i--)
  {
    SoftPWMSet(leds[i-1], 255);
    SoftPWMSet(leds[8-i], 255);
    SoftPWMSet(leds[i], 0);
    SoftPWMSet(leds[7-i], 0);
    delay(DELAY);
  }

  delay(250);
}

b

A couple reasons: the first being that I wanted to save clock cycles to do math if I need to, and the second being that I want the possibility of extended I/O control with the ATTinys.

OK, I see. Clever idea with the attinys... read the byte (UDR) and shove it into the OCR register. Can't get sweeter/simpler than that!

FYI: SoftPWM only consumes about 20% CPU cycles. Hardware PWM, of course, consumes 0%.

b

that's good to know about SWPWM! And my absolute most efficient idea with the attiny was a logic analyzer project that took a single character, and then used OR to assign the state of a pin to 1 bit of the character. So a single serial character was 8 samples from the pin. That means a megasample per second at 8mhz! However, processing always messes up my serial adapter for some reason, so it didn't work for more than a few seconds.

Thanks for posting the code, the way you handle the communication between chips is a lot simpler than I thought it would be!

It was a hard decision for me actually... I was originally going to use a single-wire system with address bytes, then I was going to use a two-wire system with sync bytes, and then I decided I would just reset each ATTiny at the beginning, and figured they would always be in sync since they have so much time to listen between bytes. I haven't run it for longer than a minute or two, but I bet it would keep working.