erkr
Published © LGPL

SW Fading & dimming LEDS by normal IO's without PMW

Smooth real time Fade & Dimm up to 20 LEDS with normal digital IO's This is done by an ISR routine swithing the LEDS between 1.5 and 2 Khz

AdvancedShowcase (no instructions)3,044
SW Fading & dimming LEDS by normal IO's without PMW

Things used in this project

Story

Read more

Code

LEDDimmer.ino

C/C++
/*
  documentation at http://arduino.cc
 */
#include "myLEDS.h"

#define ISR_FREQUENCY 31000L
const long callFrequency = numberOfLEDS * FADING_RESOLUTION * 55L; // 55 instead of 50 for rounding up the frequency needed
const long callFraction = ((callFrequency % ISR_FREQUENCY) *10L) / ISR_FREQUENCY;
ISR(TIMER2_COMPA_vect) { // 30khz
   static byte counter=0;
   // ideal callFrequency for LED_ISR() = numberOfLEDS * FADING_RESOLUTION * 50;
   // this routine maps the 30khz interupts on the required number of calls within a 10% accuracy
   // For know configurations, this can be made simpler
   long fraction = callFrequency;
   while (fraction >= ISR_FREQUENCY) {
       fraction -= ISR_FREQUENCY;
       LED_ISR(); // multi calls inside one interrupt is allowed as long is doesn't exceed the number of LEDS
   }
   if (counter < callFraction ) // only callFraction times out of 10 true
      LED_ISR();
   if (++counter == 10)
     counter = 0;
}

// the setup function runs once when you press reset or power the board
void setup() {
  Serial.begin(9600);
  Serial.println("LED dimmer v1.0");
  Serial.print("callFrequency: ");  Serial.println(callFrequency);
  Serial.print("callFraction : ");  Serial.println(callFraction);
  InitializeAllLEDS();
  // Timer 2 is used to dimm or fade LEDS independent of what the main loop is doing 
  
  TCCR2B = (TCCR2B & 0xF8) | 0x01; // 31khz, must match with ISR_FREQUENCY
  TIMSK2 |= _BV(OCIE2A);  // Activate the timer  interrupt.
  Serial.println("Serial input commands: +/- for adapting dimmer");
}

// the loop function runs over and over again forever
void loop() {  
  static unsigned long mask=0x66666666;
  static unsigned long lastMillis = 0;
 
   processSerialInput(); // handle serial input commands
   // you can do whatever else you like while the leds are controlled
  
  // invert all leds once per 2 second
  if (millis()-lastMillis > 2000) {
#ifdef DEBUG_LEDS
      long count = debugCounter;
      debugCounter=0;
      Serial.print("Calling Freq: "); // this  should be around 50(Hz)
      count /= 2L*numberOfLEDS * FADING_RESOLUTION;
      Serial.println(count);
#endif
      lastMillis = millis();
      ChangeAllLEDS(mask);
      if (mask & 1)
        mask = 0x8000000 | (mask>>1); 
      else
        mask = (mask>>1);
  }
}

void  processSerialInput() 
{
  char c=Serial.read();
  switch(c) {
     case '+':
       SetDimmer(GetDimmer()+1);
       Serial.print("Dimmer: "); Serial.println(GetDimmer());
       break;
     case '-':
       SetDimmer(GetDimmer()-1);
       Serial.print("Dimmer: "); Serial.println(GetDimmer());
       break;
     case -1:
       break;
     default:
       Serial.println("Not supported; press +/- for adapting dimmer");
     break;
  }  
}

myLEDS.cpp

C/C++
#include "Arduino.h"
#include "myLEDS.h"

// NOTE: max fading period (if dimming is set to 0) = MAX_DIMM_VALUE * 20msec
#if (FADING_RESOLUTION==40)
  #ifndef SLOW_FADING
    static byte briLUT[MAX_DIM_VALUE+1] = {0,10,17,23,28,31,33,35,37,38}; // support the logaritmic sensitivity for brightness
  #else
    static byte briLUT[MAX_DIM_VALUE+1] = {0,5,10,14,17,20,23,25,27,29,31,33,35,37,38}; // slower fading version
  #endif
#elif (FADING_RESOLUTION==30)
  #ifndef SLOW_FADING
    static byte briLUT[MAX_DIM_VALUE+1] = {0,8,14,19,23,25,26,27,28}; // support the logaritmic sensitivity for brightness
  #else
    static byte briLUT[MAX_DIM_VALUE+1] = {0,4,8,11,14,17,19,21,23,24,25,26,27,28}; // support the logaritmic sensitivity for brightness
  #endif
#elif (FADING_RESOLUTION==20)
  #ifndef SLOW_FADING
    static byte briLUT[MAX_DIM_VALUE+1] = {0,5,9,12,14,16,17,18}; // support the logaritmic sensitivity for brightness
  #else
    static byte briLUT[MAX_DIM_VALUE+1] = {0,3,5,7,9,11,13,14,15,16,17,18}; /// slower fading version
  #endif
#else
  #error select a valid FADING_RESOLUTION
#endif

#ifdef DEBUG_LEDS
unsigned long debugCounter=0;
#endif

static unsigned long _curLEDS=0, //current value for fading
                     _newLEDS=0, //target value for fading
                     _outputLEDS=0; // actual digital output 
unsigned long _prepLEDS=0;//uncommitted new led settings   

static byte _LEDFade[numberOfLEDS];// intensity per individual LED
static byte _dimmLevel=0; // 0 = no dimming

int getLED(int number) {return (_prepLEDS & (1L<<number)) > 0;}

//boolean getNewLED(int number) {return (_newLEDS & (1L<<number)) > 0;}
//boolean getCurLED(int number) {return (_curLEDS & (1L<<number)) > 0;}
//boolean getOutput(int number) {return (_outputLEDS & (1L<<number)) > 0;}


void InitializeAllLEDS() {
  _newLEDS = 0; 
  _curLEDS= 0;
  _prepLEDS = 0;
  _outputLEDS=0;
  _dimmLevel = 0;
  for (int i=0; i<numberOfLEDS; i++) {
    pinMode(i+pinLEDSBaseAddr, OUTPUT);
    _LEDFade[i] = MAX_DIM_VALUE+1; // LEDS fully dimmed (off)
  }
}




void SetDimmer(unsigned int Dimm) { // input range 0..MAX_DIM_VALUE,; for dimming. 0=full light
  noInterrupts();
  _dimmLevel = constrain(Dimm, 0, MAX_DIM_VALUE);
  interrupts();
  //Serial.print("Dimmer:");  Serial.println(_dimmLevel);
}

int GetDimmer() {
  return _dimmLevel;
}

void LED_ISR() {  // hook this routine into a 20kHZ timer (e.g. the IR remote)
   static byte offset=0;
   static byte ledNumber=0;
   boolean newIsOn = ((long)(_newLEDS & (1L<<ledNumber))>0);
   boolean curIsOn = ((long)(_curLEDS & (1L<<ledNumber))>0);
   boolean outputHigh = ((long)(_outputLEDS & (1L<<ledNumber))>0);
   
#ifdef DEBUG_LEDS
   debugCounter++;  // only neded for debugging
#endif
   // once per LED loop update fading values ( if new != cur )      
   if (offset == 0)  {
     if (!newIsOn && curIsOn) { // LED is fading from on to off
        _LEDFade[ledNumber]++;
        if (_LEDFade[ledNumber] > MAX_DIM_VALUE ) {  // value reached
          _curLEDS &= ~(1L<<ledNumber); // set current LED is off
          _LEDFade[ledNumber] = MAX_DIM_VALUE;
        }
     }
     else if (newIsOn && !curIsOn) { // LED is fading from off to on
        _LEDFade[ledNumber]--;
        if (_LEDFade[ledNumber]<=_dimmLevel) { // value reached
          _curLEDS |= (1L<<ledNumber); // set current LED is on
          _LEDFade[ledNumber] = _dimmLevel;
        }
     }
     else if (newIsOn && curIsOn)  { // LED is not fading, just track changes in dimm level
        if (_LEDFade[ledNumber] > _dimmLevel)
           _LEDFade[ledNumber]--;
        else if (_LEDFade[ledNumber] < _dimmLevel)
           _LEDFade[ledNumber]++;
     }
   }
   
   // some logic to only write differences to the digital outputs (time consuming calls)!!   
   if ( newIsOn || curIsOn )  {  //  handle normal dimming
     if (briLUT[_LEDFade[ledNumber]] == offset) {
       if (!outputHigh)  { 
         digitalWrite(ledNumber+pinLEDSBaseAddr, HIGH);
         _outputLEDS |= (1L<<ledNumber);
       }
     }     
     else if (offset == 0 && outputHigh)  
     {
       digitalWrite(ledNumber+pinLEDSBaseAddr,LOW);
       _outputLEDS &= ~(1L<<ledNumber);
     } 
   } 
   else if (outputHigh) { // should be off
      digitalWrite(ledNumber+pinLEDSBaseAddr,LOW);
   }

   // simulate a loop for all offsets over all leds
   ledNumber++;
   if (ledNumber==numberOfLEDS) { // full cycle over all leds, so next offset
       ledNumber=0;
       offset++; 
       if (offset > FADING_RESOLUTION)
          offset=0;
   }
}

void CommitLEDS() {  //commit new led settings
  noInterrupts();
  _newLEDS = _prepLEDS;
  interrupts();
}

void ChangeAllLEDS(unsigned long mask) {
  _prepLEDS = mask;
  noInterrupts();
  _newLEDS = _prepLEDS;
  interrupts();
}

boolean getNewLED(int number) {return (_newLEDS & (1L<<number)) > 0;}
boolean getCurLED(int number) {return (_curLEDS & (1L<<number)) > 0;}
boolean getOutput(int number) {return (_outputLEDS & (1L<<number)) > 0;}

myLEDS.h

C/C++
#ifndef myLEDS_h
#define myLEDS_h

#include "arduino.h"

// +++++++++++++ SECTION to configure as you prefer +++++++++++++++++++
// I/O mapping. Program assumes that pinning of ALL  leds are expected to be consecutive in order to be loop over them. 
#define pinLEDSBaseAddr   22
#define numberOfLEDS  20L // max 32 leds!!!

// Select the prefered resolution of for fading/dimming. Note: Together with the number of LEDs 
// this determines the timer frequency needed); Higher resolutions require more CPU!!
//#define FADING_RESOLUTION  20L  // Requires calling frequency F=1kHZ per LED
//#define FADING_RESOLUTION  30L  // Requires calling frequency F=1,5kHZ per LED
#define FADING_RESOLUTION  40L  // Requires calling frequency F=2kHZ per LED

#define SLOW_FADING //pure what you want, has no influence on CPU load, but determines the number of dimming levels as well

// note: my Arduino mega R3 can handle up to ca 25 leds at 40 dimm stepts and up to 32 leds at 30 dimm steps
// +++++++++++++ SECTION END +++++++++++++++++++

// #define DEBUG_LEDS
#ifdef DEBUG_LEDS
extern unsigned long debugCounter;
#endif

// NOTE: max fading period (if dimming is set to 0) = MAX_DIMM_VALUE * 20msec
#if (FADING_RESOLUTION==40)
  #ifndef SLOW_FADING
    #define MAX_DIM_VALUE 9 // 0=NO DIMMING; (you need to change briLUT if you adapt this value)
  #else
    #define MAX_DIM_VALUE 14 // select this one for slower dimming (need to change the briLUT as well)
  #endif
#elif (FADING_RESOLUTION==30)
  #ifndef SLOW_FADING
    #define MAX_DIM_VALUE 8 // 0=NO DIMMING; (you need to change briLUT if you adapt this value)
  #else
    #define MAX_DIM_VALUE 13 // select this one for slower dimming (need to change the briLUT as well)
  #endif
#else
  #ifndef SLOW_FADING
    #define MAX_DIM_VALUE 7 // 0=NO DIMMING; (you need to change briLUT if you adapt this value)
  #else
    #define MAX_DIM_VALUE 11 // select this one for slower dimming (need to change the briLUT as well)
  #endif
#endif

// LEDS are controlled via an ISR. Just call LED_ISR() in a dedicated or exiting timer routine (e.g the IR remote ISR is running at 20kHZ)
// The calling frequency F to this routine must be: F = #LEDS * FADING_RESOLUTION * 50 (HZ)
// You can call LED_ISR() multile times in the ISR as long the number of calls inside the ISR are less then the number of LEDS!!!
void LED_ISR(); //ISR routine;  calling frequency F = #LEDS * FADING_RESOLUTION * 50 (HZ)

void InitializeAllLEDS(); // call once in setup()

int getLED(int number); // get a prepared (uncommited) led value 

extern unsigned long _prepLEDS;
inline void setLED(int number, int value) { // prepare new value for a particular led
  if (value ) {
    _prepLEDS |= (1L<<number);
  }
  else {
    _prepLEDS &= ~(1L<<number);
  }
}

boolean getNewLED(int number);
boolean getCurLED(int number);
boolean getOutput(int number);

void CommitLEDS(); // commit all preprared led changes defined in _prepLEDS
void ChangeAllLEDS(unsigned long mask); // change and commit all leds in one call


void SetDimmer(unsigned int Dimm); // 0..MAX_DIM_VALUE = 0%-95%
int  GetDimmer();

#endif

Credits

erkr

erkr

4 projects • 19 followers

Comments