1

I am trying to write a simple software PWM to fade some LEDs because I don't have enough PWM I/O Ports.

The code evaluates how long you want the LED to fade, and breaks it down into a number of steps. During each step, it will evaluate the period the LED should be on.

I have the following code:

class Led{
  public:
    Led(int pin);
    void setup();
    // void updateFadeDirection();
    void updateBrightness();

  private:
    int _pin;
    unsigned long _currentTime;
    unsigned long _startTime; // Very beginning of fade period 
    unsigned long _fadePeriod; // How long to fade on and off
    int _fadeStep; // current fade step i.e. 1/255
    int _fadeSteps; // How many steps to take until full brightness
    int _fadeDirection; // low to high or high to low?
    float _T_on; // Amount of time to turn LED on
    float _T_fadeStep; // Amount of time per step increment of brightness
    unsigned long _stepTime; // Start of current step period

    bool _evaluated;
};

Led::Led(int pin){
  _pin = pin;
}

void Led::setup(){
  pinMode(_pin, OUTPUT);
  _startTime = micros();
  _fadePeriod = 2*1000000; //microseconds(us)
  _fadeStep = 50; // start at 0 brightness here
  _fadeSteps = 100;
  _fadeDirection = 1;

  _T_fadeStep = _fadePeriod/_fadeSteps; // i.e. 2000000us/100steps = 20000us/step of brightness
  _T_on = (_fadeStep/_fadeSteps) * _T_fadeStep; // i.e. 20/100 * 20000us = 4000us on

  _stepTime = micros();
  delayMicroseconds(200);
}

void Led::updateBrightness(){
  _currentTime = micros();

  if(_currentTime - _startTime < _fadePeriod){
    if (_currentTime - _stepTime > _T_on){
      digitalWrite(_pin, LOW);
    } else {
      digitalWrite(_pin, HIGH);
    }

    if(_currentTime - _stepTime >= _T_fadeStep){
      // _fadeStep++;
      _stepTime = _currentTime;
      _T_on = (_fadeStep/_fadeSteps) * _T_fadeStep;
    }
  }
  else{
    digitalWrite(_pin, HIGH);
  }
}

Led LED0(0);

void setup() {
  LED0.setup();
}

void loop() {
  LED0.updateBrightness();
}

The current behaviour when powered on is the single LED will be turned off, then will turn on after 2 seconds.

In particular, it never seems to enter the else part

  if (_currentTime - _stepTime > _T_on){
    digitalWrite(_pin, LOW);
  } else {
    digitalWrite(_pin, HIGH);
  }

The problem is that the LED will never hit HIGH until the outer else is triggered.

I believe it has to do with _T_on being an int and the timestamps being unsigned long and me not casting it.

The expected behaviour is 2 seconds of dimmed (50%) light, and then a full 100% when it hits the outer else.

  • Nothing to do with int/unsigned long - I've just tried it. Not sure why you didn't try it if you thought that was the problem! – CharlieHanson Jan 18 '16 at 11:52
  • I'm going through this now and I'm slightly confused as to precisely what you're trying to achieve. Ultimately do you want to be able to choose starting brightness, end brightness, and the time it takes to go from one to the other? That seems the most obvious, but it also seems like you want to specifiy how many individual brightness levels there are inbetween. Also, do you have any timers spare that you aren't using? – CharlieHanson Jan 18 '16 at 12:06
  • 1
    I think you have your logic completely arse about face (to use a colloquial expression). I see nothing there that would actually perform any PWM operation at all, only turning the LED ON or OFF at something like 2 seconds. – Majenko Jan 18 '16 at 12:19
  • @Majenko: the LED HIGH at 2 seconds it just to show timestamps in microseconds work for my chip. The inner conditional block is what evaluates the duty cycle during each period (read: not the 2 second fadePeriod). @CharlieHanson: yes it's an LED fader using PWM, which you can set duration from 0 to full brightness, as well as how many steps in that duration (to simulate smoothness or harsh steps). I did try the int/unsigned long, but didn't work so thought I'd throw it up here for anyone to correct me. I am not using OCR1A/B. – PGT Jan 18 '16 at 16:41
  • Turns out the clock speed for my chip can't handle the software PWM that's fully asynchronous using timestamps. The amount of instructions exceed the individual periods of each duty cycle. Got it working and will do a write up on my findings in I have time. – PGT Jan 18 '16 at 16:44
1

As @ott-- has mentioned, because of the int datatype, _fadeStep/_fadeSteps will always evaluate to 0.

Here's the updated code along with some timing tweaks:

float fadeSteps = 150; // How many steps to take until full brightness

class Led{
  public:
    Led(int pin, float fadeStep=0);
    void setup();
    // void updateFadeDirection();
    void updateBrightness();

  private:
    int _pin;
    unsigned long _currentTime;
    unsigned long _startTime; // Very beginning of fade period 
    unsigned long _fadePeriod; // How long to fade on and off
    float _fadeStep; // current fade step i.e. 1/255
    int _fadeDirection; // low to high or high to low?
    unsigned long _T_on; // Amount of time to turn LED on
    unsigned long _T_fadeStep; // Amount of time per step increment of brightness
    unsigned long _stepTime; // Start of current step period

    bool _evaluated;
};

Led::Led(int pin, float fadeStep){
  _pin = pin;
  _fadeStep = fadeStep;
}

void Led::setup(){
  pinMode(_pin, OUTPUT);
  _startTime = micros();
  _fadePeriod = 2000000; //microseconds(us)
  _fadeDirection = 1;

  _T_fadeStep = _fadePeriod/2/fadeSteps; // i.e. 2000000us/100steps = 20000us/step of brightness
  _T_on = (_fadeStep/fadeSteps) * _T_fadeStep; // i.e. 20/100 * 20000us = 4000us on

  _stepTime = micros();
}

void Led::updateBrightness(){
  _currentTime = micros();
  if (_currentTime - _stepTime > _T_on){
    digitalWrite(_pin, LOW);
  } else {
    digitalWrite(_pin, HIGH);
  }

  if(_currentTime - _stepTime >= _T_fadeStep){
    _fadeStep += _fadeDirection;
    _stepTime = _currentTime;
    _T_on = (_fadeStep/fadeSteps) * _T_fadeStep;
  }

  if(_fadeStep >= fadeSteps-1) {
    _fadeDirection = -1;
  } else if(_fadeStep <= 1) {
    _fadeDirection = 1;
  }
}

Led LED0(0);
Led LED1(1,fadeSteps/5);
Led LED2(2,2*fadeSteps/5);
Led LED3(3,3*fadeSteps/5);
Led LED4(4,4*fadeSteps/5);

void setup() {
  LED0.setup();
  LED1.setup();
  LED2.setup();
  LED3.setup();
  LED4.setup();
}

void loop() {
  LED0.updateBrightness();
  LED1.updateBrightness();
  LED2.updateBrightness();
  LED3.updateBrightness();
  LED4.updateBrightness();
}

Some refactoring is needed to be more modular, but will fade (using software PWM) LEDs asynchronously on every digital OUT port on an attiny85. Can also set the starting brightness so that you can make an LED fading chaser.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.