Hardware components | ||||||
| × | 1 |
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!
/*
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;
}
}
#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;}
#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