Go Down

Topic: attiny85 software pwm (Read 8117 times) previous topic - next topic

blark

Hi guys,

I'm using the Arduino IDE with my attiny85 chip to make a bike light with 5 LEDs. I want the light to have several modes, one of them being cylon mode (fade back and forth).

I just realized that the attiny only has two pwm channels in HW, and I have NO IDEA how to get started implementing this in software. I've been googling all day but the C examples I've found aren't helping me. I know this is possible because BlinkMs use the same chip and have three channels of PWM.

Can someone help me with basic PWM code?

Thanks everyone.

blark

Hi everyone, I've still been trying to achieve this. I found the following code here http://jaywiggins.com/2011/05/attiny-software-pwm-to-drive-rgb-led/

I used the Arduino IDE to write it to my attiny85 and voila it works. Now I need help figuring out how it works! :) I understand what's going on here about half the time but the other half where the code gets in to registers and what not leaves me kinda scratching my head.

If someone could help me out with a bit of explanation here so I might begin hacking I'd appreciate it.

Code: [Select]

// based largely on Atmel's AVR136: Low-Jitter Multi-Channel Software PWM Application Note:
// http://www.atmel.com/dyn/resources/prod_documents/doc8020.pdf

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define CHMAX 3 // maximum number of PWM channels
#define PWMDEFAULT 0x00 // default PWM value at start up for all channels

#define RED_CLEAR (pinlevelB &= ~(1 << RED)) // map RED to PB0
#define GREEN_CLEAR (pinlevelB &= ~(1 << GREEN)) // map GREEN to PB1
#define BLUE_CLEAR (pinlevelB &= ~(1 << BLUE)) // map BLUE to PB2

//! Set bits corresponding to pin usage above
#define PORTB_MASK  (1 << PB0)|(1 << PB1)|(1 << PB2)

#define set(x) |= (1<<x)
#define clr(x) &=~(1<<x)
#define inv(x) ^=(1<<x)

#define RED PB0
#define GREEN PB1
#define BLUE PB2
#define LED_PORT PORTB
#define LED_DDR DDRB

void delay_ms(uint16_t ms);
void init();

unsigned char compare[CHMAX];
volatile unsigned char compbuff[CHMAX];

int r_val = 0x00;
int g_val = 0x55;
int b_val = 0xAA;
float dim = 1;

int main() {
  init();
 
  int r_dir = 1;
  int g_dir = 2;
  int b_dir = 4;
 
  for(;;) {
    if (r_val > 254 - 1) {
      r_dir = -1;
    }
    if (r_val < 1 + 1) {
      r_dir = 1;
    }

    if (g_val > 254 - 3) {
      g_dir = -2;
    }
    if (g_val < 1 + 3) {
      g_dir = 2;
    }

    if (b_val > 254 - 4) {
      b_dir = -4;
    }
    if (b_val < 1 + 4) {
      b_dir = 4;
    }
   
    r_val += r_dir;
    g_val += g_dir;
    b_val += b_dir;

    compbuff[0] = r_val;
    compbuff[1] = g_val;
    compbuff[2] = b_val;

    delay_ms(50);
  }
}


void delay_ms(uint16_t ms) {
  while (ms) {
    _delay_ms(1);
    ms--;
  }
}

void init(void) {
  // set the direction of the ports
  LED_DDR set(RED);
  LED_DDR set(GREEN);
  LED_DDR set(BLUE);
 
  unsigned char i, pwm;

  CLKPR = (1 << CLKPCE);        // enable clock prescaler update
  CLKPR = 0;                    // set clock to maximum (= crystal)

  pwm = PWMDEFAULT;

  // initialise all channels
  for(i=0 ; i<CHMAX ; i++) {
    compare[i] = pwm;           // set default PWM values
    compbuff[i] = pwm;          // set default PWM values
  }

  TIFR = (1 << TOV0);           // clear interrupt flag
  TIMSK = (1 << TOIE0);         // enable overflow interrupt
  TCCR0B = (1 << CS00);         // start timer, no prescale

  sei();
}


ISR (TIM0_OVF_vect) {
  static unsigned char pinlevelB=PORTB_MASK;
  static unsigned char softcount=0xFF;

  PORTB = pinlevelB;            // update outputs
 
  if(++softcount == 0){         // increment modulo 256 counter and update
                                // the compare values only when counter = 0.
    compare[0] = compbuff[0];   // verbose code for speed
    compare[1] = compbuff[1];
    compare[2] = compbuff[2];

    pinlevelB = PORTB_MASK;     // set all port pins high
  }
  // clear port pin on compare match (executed on next interrupt)
  if(compare[0] == softcount) RED_CLEAR;
  if(compare[1] == softcount) GREEN_CLEAR;
  if(compare[2] == softcount) BLUE_CLEAR;
}


THANKS EVERYONE!

Erni

#2
Oct 16, 2011, 12:10 pm Last Edit: Oct 16, 2011, 12:18 pm by Erni Reason: 1
I have been looking for someway to do software pwm myself, but when i goggle, i end up with some code i don't understand, like your example.

So i tried to make a simple sketch on my Uno to do it, and it works, but maybe it will be too slow.

Anyway here is the idea:

A pwm periode is 255 long (in my example)

If you use spwm(255,1) the on loop wil use the whole period, meaning the LED will be on the whole period.
The same for spwm(0,1) the LED will be off the whole period.
But fx.; spwm(127,1) the LED wil be on half the period and off the other half, resulting in a LED dimmed to 50%.

Btw. if you use Coding Badly's core for the attiny85 you can use pwm on pin 1,2,4 , so you are only missing two.
Code: [Select]


void spwm(int freq,int spin){

// On period  
for (int x=0;x<freq;x++){
digitalWrite(spin,HIGH);
}

// Off period
for(int x=0;x<(255-freq);x++){
digitalWrite(spin,LOW);
}


} //spwm




Example using spwm function


Code: [Select]



int led1=12;
int led2=8;

void setup(){

pinMode(led1,OUTPUT);
pinMode(led2,OUTPUT);


}


void loop(){

for (int x=0;x<254;x++){
 spwm(x,led1);
delay(5);
}
for (int x=254;x>0;x--){
 spwm(x,led1);
delay(5);
}

for (int x=0;x<254;x++){
 spwm(x,led2);
delay(5);
}
for (int x=254;x>0;x--){
 spwm(x,led2);
delay(5);
}



}


void spwm(int freq,int spin){

// On period  
for (int x=0;x<freq;x++){
digitalWrite(spin,HIGH);
}

// Off period
for(int x=0;x<(255-freq);x++){
digitalWrite(spin,LOW);
}


} //spwm

Erni


Now i have tried it on a Attiny85, and as suspected the earlier version is too slow, so i have chnged the two for loops with delayMiroseconds.
There is a third argument in the function: sp which regulate the pwm speed

Code: [Select]


/*Software PWM on Attiny85
Ernst Christensen 16.okt. 2011
*/

int led[]={0,1,2,3,4};
int count=0;

void setup(){
for (int z=1;z<6;z++){

pinMode(led[z],OUTPUT);

} //for z

}


void loop(){
 
for (int x=0;x<255;x++){
  spwm(x,led[count],3);
}
for (int x=254;x>0;x--){
  spwm(x,led[count],3);
}

count++;
  if (count>4){
count=0;
}

}


void spwm(int freq,int spin,int sp){

  //on
digitalWrite(spin,HIGH);
delayMicroseconds(sp*freq);

// off
digitalWrite(spin,LOW);
delayMicroseconds(sp*(255-freq));


} //spwm


blark

Ok. I figured it out. It's possible and looks fantastic. It only took me like 25 hours. ;)

As soon as I clean the code up a bit I will post it. 5 channel software PWM on this little chip, I love it.


Scissorman

Any joy with that code?
I've been trying to "shinkify" my project onto an ATtiny85 and for some reason only one pin seems to be working and maybe I need more usable pins

It's just the Fire effect LED project from Michael McRoberts' book Beginning Arduino. I think I'm attempting to run before I can walk. I've changed the pin numbers in the code (not in the below example) for loading onto the Tiny but only one pin lights but it does flicker nicely.
Code: [Select]
// LED Fire Effect
int ledPin1 = 9;
int ledPin2 = 10;
int ledPin3 = 11;

void setup()
{
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
}

void loop()
{
  analogWrite(ledPin1, random(120)+135);
  analogWrite(ledPin2, random(120)+135);
  analogWrite(ledPin3, random(120)+135);
  delay(random(100));
}


I hope someone can point me in the right direction. I'm such a noob at this and I appear to be going in circles with my searches.

Erni

Hi
Your code is running fine on the attiny85, but remeber that you only have 3 pins with pwm, it is pin 0,1 and 4

so if you use these pins and changes the random() functions to:

analogWrite(ledPin1, random(255);

you will have a nice blinky.



Udo Klein

My website http://blog.blinkenlight.net/ demonstrates several approaches for software PWM. With small adaptations (for the pin count) they should run on your tiny as well.
Check out my experiments http://blog.blinkenlight.net

Go Up