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

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

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

  • 1,383 views
  • 0 comments
  • 3 respects

Components and supplies

About this project

I was building a “Berlin Uhr” (look it up if you like) with dozens of LEDS for displaying the time. My challenge was to switch 20 LEDs smoothly and adjust their brightness to the daylight

No Arduino board has enough PWM based IO's for this and I didn't want to add more electronics than the standard serial resistor per led.

Succeeded:

I used fast power mode switching. This enables to use ordinary IO’s for fading and dimming. An ISR (interrupt Service Routine) at 30kHz switches all individual LED’s at least ounce per 20msec. By switching these LED’s fast enough and adapt the ratio of when they are switched on or off, the brightness will be controllable without any flickering!

My approach easily works for 20 LEDS at ouce on an Arduino MEGA2560 R3 Development Board with many Digital IO's. There was even enough CPU room left for handling music playback and Web pages in parallel!

Code

LEDDimmer.inoC/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.cppC/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.hC/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

Comments

Similar projects you might like

IOT Based Health Care System

Project showcase by Team Techie

  • 17,883 views
  • 16 comments
  • 42 respects

4-Stroke Digital Clock With Arduino

Project showcase by LAGSILVA

  • 11,103 views
  • 9 comments
  • 39 respects

Timer Based Laser Wall Clock

by screwpilot

  • 4,390 views
  • 6 comments
  • 4 respects

3D Controller Bot

Project tutorial by 4 developers

  • 11,835 views
  • 1 comment
  • 42 respects

Automated Snake Enclosure with Camera

Project showcase by hagakure

  • 6,308 views
  • 9 comments
  • 17 respects

Arduino Digital Clock Version 2

Project showcase by Arduino World

  • 5,518 views
  • 0 comments
  • 19 respects
Add projectSign up / Login