software PWN on attiny85

I have a pin (PB2) that doesn’t have PWM on it and used the following code to get it to do software PWN. It works, however it doesn’t seem to fade all the way day to 0 brightness, any idea why this would be happening? The code implies it should go all the way down.

/*
Modified version of Ernst Christensen's code
*/

int redLED= 2;


void setup(){
  //for (int z=0;z<1;z++){
    pinMode(redLED,OUTPUT);
  //} //for z
}


void loop(){
  for (int x=0;x<255;x++){
    spwm(x,redLED,15);
  }
  for (int x=254;x>0;x--){
    spwm(x,redLED,15);
  }
}


void spwm(int freq,int spin,int sp){
  //on
  digitalWrite(spin,HIGH);
  delayMicroseconds(sp*freq);
  
  // off
  digitalWrite(spin,LOW);
  delayMicroseconds(sp*(255-freq));
}

hilukasz:
The code implies it should go all the way down.

Not at all...

This sequence:

    digitalWrite(spin,HIGH);
    delayMicroseconds(sp*freq);
    digitalWrite(spin,LOW);

Doesn't take 0 clock cycles to execute. The LED will be on for the time it takes to do multiply sp*freq, call/return from delayMicroseconds, do the digitalWrite(LOW), etc.

You might also get a CPU interrupt in the middle of it, slowing it down even more and causing bright flashes.

Try this:

void spwm(int freq,int spin,int sp){
  if (sp!=0) {
    //on
    digitalWrite(spin,HIGH);
    delayMicroseconds(sp*freq);
  
    // off
    digitalWrite(spin,LOW);
    delayMicroseconds(sp*(255-freq));
  }
}

fungus:

hilukasz:
The code implies it should go all the way down.

Not at all...

This sequence:

    digitalWrite(spin,HIGH);

delayMicroseconds(sp*freq);
    digitalWrite(spin,LOW);



Doesn't take 0 clock cycles to execute. The LED will be on for the time it takes to do multiply sp*freq, call/return from delayMicroseconds, do the digitalWrite(LOW), etc.

You might also get a CPU interrupt in the middle of it, slowing it down even more and causing bright flashes.

Try this:


void spwm(int freq,int spin,int sp){
  if (sp!=0) {
    //on
    digitalWrite(spin,HIGH);
    delayMicroseconds(spfreq);
 
    // off
    digitalWrite(spin,LOW);
    delayMicroseconds(sp
(255-freq));
  }
}

this acts the same way. I am running 8mhz if that matters? there is a small flash at the end of the fade down.

hilukasz:
I am running 8mhz if that matters?

Nope.

hilukasz:
there is a small flash at the end of the fade down.

Anything else you're not telling us...?

Maybe a video would help.

If you want a longer period where the LED is Off you can use the sketch below.

To avoid flicker don’t use the values 0 and 255 in your for loops.

It runs smoother if you use 8MHx than 1MHz

int redLED= 3;


void setup(){
  pinMode(redLED,OUTPUT);
}

void loop(){
  for (int x=1;x<254;x++){
    spwm(x,redLED,15);
  }
  for (int x=254;x>0;x--){
    spwm(x,redLED,15);
  }
}


void spwm(int freq,int spin,int sp){
  //on
  if  (freq>16){
    digitalWrite(spin,HIGH); //on
  }

  delayMicroseconds(sp*freq);

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

There is a long-standing bug in delayMicroseconds(), which was reported years ago but the core maintainers have chosen to ignore. If you pass zero to it, you get a very long delay instead of a very short delay. So your code will not work as expected when sp (or freq) is zero. Use “if (sp != 0) { delayMicroseconds(sp*freq); }” instead.

As fungus already explained, the various calls will take a non-zero amount of time to execute, which will also contribute to the problem.

[EDIT: The above assumes that the attiny85 core you are using has the same bug as the standard Arduino core.]

Another option would be to skip the delayMicroseconds(), and use the principle from BlinkWithoutDelay sketch.

int p=1;
unsigned  int fadeTime =900;
unsigned long int now =micros();
unsigned  int pwmTime=0;

void setup()
{
  pinMode (3, OUTPUT);
}

void loop()
{
  if((micros()-now)<pwmTime)
  { 
    PORTB |=(1<<PB3); 
  }
  else{
    PORTB &= ~(1<<PB3);
  }

  if((micros()-now)>fadeTime){
    now=micros();
    if(pwmTime==fadeTime){
      p=-1;
    }
    if(pwmTime==0){
      p=1;
    }
    pwmTime=pwmTime+(1*p);
  }


}// loop