In this course a universal DCC decoder for signals and switches is build step by step. This course is to encourage everyone to start loving the Arduino platform to be used in model railroad automation. This is part 2 of the course containing examples of LED fading. These are the examples (work in progress):
- Brightness simple starting example
- Brightness without delay function
- Brightness without delay 4 LEDs
- Fading without delay
- Fading without delay in micro secons
- Fading without delay 4 LEDs and different fading starts
- Fading using hardware PWM output: UNDER CONSTRUCTION (14)
- Fading without delay 4 LEDs and blinking: UNDER CONSTRUCTION (15)
- Four aspect signal with fading: UNDER CONSTRUCTION (16)
- Switch control: UNDER CONSTRUCTION (17)
Fading is a lot more complex. In these cases it is wise to start with a smaller example instead of trying to extend the previous example.
Dimming a LED is done through pulse width modulation. At a speed invisible for the human eye the LED is switched on during a certain period and off. The ratio on-off will give the impression that the LED is dimmed.
This example has one LED. Experiment with the values of HIGHTIME and LOWTIME. The sum of HIGHTIME and LOWTIME is the PWM cycle period. In the example this is 20 msec so the LED will flicker at 50 Hz. Lower frequencies are irritating to the eye. Setting the brightness to 1 msec on and 19 msec off is still visible. Flickering at 50 Hz can be made visible in two ways: turn your head fast while looking at the LED and using the camera of a mobile phone.
09_BrightnessStart.ino
const int SIGNALPIN = 13;
const int HIGHTIME = 1;
const int LOWTIME = 19;
void setup() {
// initialize digital pin as an output.
pinMode(SIGNALPIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(SIGNALPIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(HIGHTIME); // wait for HIGHTIME msec
digitalWrite(SIGNALPIN, LOW); // turn the LED off by making the voltage LOW
delay(LOWTIME); // wait for LOWTIME msec
}
Now we rewrite the example above to get rid of the delay function. The smallest time division is one step in the PWM cycle. The LED is switched on at the beginning of the cycle and switched off at the moment the cycle reaches the presetted brightness (ledBrightness) of the LED. If ledBrightness = 0 the LED is always off (we need this in a later example).
SignalControl.ino
const int SIGNALPIN = 13;
const int PWMTIME = 20; // time in msec for one PWM cycle.
const int PWMINCREASE = 1; // each PWM cycle is divided into steps of PWMINCREASE msec
int pwmCycle; // each PWM cycle this starts with 1 and increases each PWMINCREASE msec up to PWMTIME
unsigned long previousMillis; // will store last time PWM cycle was updated
int ledBrightness; // will store brightness as the PWM width value between 0 and PWMTIME
void SignalControl_Init() {
// initialize digital pin 13 as an output.
pinMode(SIGNALPIN, OUTPUT);
pwmCycle = 1;
ledBrightness = 2; // lowest brightness for this experiment
}
void SignalControl_Blink() {
unsigned long currentMillis = millis(); // current time
if (currentMillis - previousMillis >= PWMINCREASE) { // PWMINCREASE msec have passed since last time
previousMillis = currentMillis; // wait for next moment
pwmCycle = pwmCycle + PWMINCREASE; // next PWMINCREASE msec within one PWM cycle
if (pwmCycle > PWMTIME) { // if one PWM cycle is completed, repeat by starting at 1
pwmCycle = 1;
};
int ledState;
if (ledBrightness >= pwmCycle) { // switch LED on or off during one cycle at the moment the setting of the brightness is reached
ledState = HIGH; // HIGH means set LED on
} else {
ledState = LOW; // LOW means set LED off
}
digitalWrite(SIGNALPIN, ledState); //set output according to ledState
}
}
10_BrightnessWithoutDelay.ino
void setup() {
// initialize signal output
SignalControl_Init();
}
// the loop function runs over and over again forever
void loop() {
SignalControl_Blink();
}
The example above is extended to four ledState
SignalControl.ino
const int NUMBEROFPINS = 4;
const int SIGNALPINS[NUMBEROFPINS] = {13, 12, 10, 9};
const int UP = 1; // increase brightness
const int DOWN = -1; // decrease brightness
const int PWMTIME = 20; // time in msec for one PWM cycle. 20 msec means 50 Hz cycle.
const int PWMINCREASE = 1; // each PWM cycle is divided into steps of PWMINCREASE msec
int pwmCycle; // each PWM cycle starts with 1 and increases each PWMINCREASE msec up to PWMTIME
int brightCycle; // count PWM cycles
int ledBrightness[NUMBEROFPINS] ; // will store brightness as the PWM width value between 0 and PWMTIME
int ledDimOrBright[NUMBEROFPINS]; // should led get brighter so UP (+1) of must it dimm DOWN (-1)
const int BRIGHTINCREASECYCLES = 2; // after BRIGHTINCREASECYCLES times a complete PWM cycle brightness is increased or decreased
unsigned long previousMillis; // will store last time PWM cycle was updated
void SignalControl_Init() {
// initialize digital pin 13 as an output.
for (int i = 0; i < NUMBEROFPINS; i++) {
pinMode(SIGNALPINS[i], OUTPUT);
ledDimOrBright[i] = UP;
}
ledBrightness[0] = 1; // some brightness for this experiment
ledBrightness[1] = 6; // some brightness for this experiment
ledBrightness[2] = 11; // some brightness for this experiment
ledBrightness[3] = 17; // some brightness for this experiment
pwmCycle = 1;
brightCycle = 0;
}
void SignalControl_Blink() {
unsigned long currentMillis = millis(); // current time
if (currentMillis - previousMillis >= PWMINCREASE) { // PWMINCREASE msec have passed since last time
previousMillis = currentMillis; // wait for next moment
pwmCycle = pwmCycle + PWMINCREASE; // next PWMINCREASE msec within one PWM cycle
if (pwmCycle > PWMTIME) { // if one PWM cycle is completed, repeat by starting at 1
pwmCycle = 1;
brightCycle ++; // increase cycle number
if (brightCycle > BRIGHTINCREASECYCLES) { // it is time to increase or decrease brightness
brightCycle = 0;
for (int i = 0; i < NUMBEROFPINS; i++) {
ledBrightness[i] = ledBrightness[i] + ledDimOrBright[i]; // increase or decreas brightness depening on direction
if (ledBrightness[i] == PWMTIME) { // if at maximum, for now invert to dimming
ledDimOrBright[i] = DOWN;
};
if (ledBrightness[i] == 1) { // if at minimum, for now invert to more bright. ledBrightness = 0 is used for the state off
ledDimOrBright[i] = UP;
};
};
};
};
for (int i = 0; i < NUMBEROFPINS; i++) {
digitalWrite(SIGNALPINS[i], (ledBrightness[i] >= pwmCycle) ? (HIGH) : (LOW)); //set output according to the position in the cycle
};
};
}
11_BrightnessWithoutDelay4LEDs.ino
void setup() {
// initialize signal output
SignalControl_Init();
}
// the loop function runs over and over again forever
void loop() {
SignalControl_Blink();
}
Now the example above is extended to get a fading effect.The brightness is slowely increased until maximum and then decreased at the same speed until minimum.
SignalControl.ino
const int SIGNALPIN = 13;
const int UP = 1; // increase brightness
const int DOWN = -1; // decrease brightness
const int PWMTIME = 20; // time in msec for one PWM cycle. 20 msec means 50 Hz cycle.
const int PWMINCREASE = 1; // each PWM cycle is divided into steps of PWMINCREASE msec
int pwmCycle; // each PWM cycle starts with 1 and increases each PWMINCREASE msec up to PWMTIME
int brightCycle; // count PWM cycles
int ledBrightness; // will store brightness as the PWM width value between 0 and PWMTIME
int ledDimOrBright; // should led get brighter so UP (+1) of must it dimm DOWN (-1)
const int BRIGHTINCREASECYCLES = 2; // after BRIGHTINCREASECYCLES times a complete PWM cycle brightness is increased or decreased
unsigned long previousMillis; // will store last time PWM cycle was updated
void SignalControl_Init() {
// initialize digital pin 13 as an output.
pinMode(SIGNALPIN, OUTPUT);
ledBrightness = 1;
ledDimOrBright = UP;
pwmCycle = 1;
brightCycle = 0;
}
void SignalControl_Blink() {
unsigned long currentMillis = millis(); // current time
if (currentMillis - previousMillis >= PWMINCREASE) { // PWMINCREASE msec have passed since last time
previousMillis = currentMillis; // wait for next moment
pwmCycle = pwmCycle + PWMINCREASE; // next PWMINCREASE msec within one PWM cycle
if (pwmCycle > PWMTIME) { // if one PWM cycle is completed, repeat by starting at 1
pwmCycle = 1;
brightCycle ++; // increase cycle number
if (brightCycle > BRIGHTINCREASECYCLES) { // it is time to increase or decrease brightness
brightCycle = 0;
ledBrightness = ledBrightness + ledDimOrBright; // increase or decreas brightness depening on direction
if (ledBrightness == PWMTIME) { // if at maximum, for now invert to dimming
ledDimOrBright = DOWN;
};
if (ledBrightness == 1) { // if at minimum, for now invert to more bright. ledBrightness = 0 is used for the state off
ledDimOrBright = UP;
};
};
};
digitalWrite(SIGNALPIN, (ledBrightness >= pwmCycle) ? (HIGH) : (LOW)); //set output according to the position in the cycle
}
}
The big change between this code and the previous example starts after pwmCycle = 1; Also note the improvement at the bottom with a so called 'conditional assignment'. Use this program to experiment with fade timing.
12_FadingWithoutDelay.ino
void setup() {
// initialize signal output
SignalControl_Init();
}
// the loop function runs over and over again forever
void loop() {
SignalControl_Blink();
}
On observing the fading of the LEDs in the previous example it is obvious that the incremental brightness steps are not garnular enough. The human eye can see the 20 steps in which we divide the increment in intensity of the LED. In another example the granularity is much smaller. So the next step will be to divide the 20 msec into X steps of Y μs each. We will increase X until it is not visible by the human eye.
Also we will set an output pin to 1 when entering the big piece of code setting and clearing the LED and to 0 when we are done. In this way we hope to see how much processing time is spend in that code using an oscilloscope.
Now we extend as previously done the example over 4 LEDs.
SignalControl.ino
const int NUMBEROFPINS = 4;
const int SIGNALPINS[NUMBEROFPINS] = {13, 12, 10, 9};
const int UP = 1; // increase brightness
const int DOWN = -1; // decrease brightness
const int PWMTIME = 20; // time in msec for one PWM cycle. 20 msec means 50 Hz cycle.
const int PWMINCREASE = 1; // each PWM cycle is divided into steps of PWMINCREASE msec
int pwmCycle; // each PWM cycle starts with 1 and increases each PWMINCREASE msec up to PWMTIME
int brightCycle; // count PWM cycles
int ledBrightness[NUMBEROFPINS] ; // will store brightness as the PWM width value between 0 and PWMTIME
int ledDimOrBright[NUMBEROFPINS]; // should led get brighter so UP (+1) of must it dimm DOWN (-1)
const int BRIGHTINCREASECYCLES = 2; // after BRIGHTINCREASECYCLES times a complete PWM cycle brightness is increased or decreased
unsigned long previousMillis; // will store last time PWM cycle was updated
void SignalControl_Init() {
// initialize digital pin 13 as an output.
for (int i = 0; i < NUMBEROFPINS; i++) {
pinMode(SIGNALPINS[i], OUTPUT);
ledDimOrBright[i] = UP;
}
ledBrightness[0] = 1; // some brightness for this experiment
ledBrightness[1] = 6; // some brightness for this experiment
ledBrightness[2] = 11; // some brightness for this experiment
ledBrightness[3] = 17; // some brightness for this experiment
pwmCycle = 1;
brightCycle = 0;
}
void SignalControl_Blink() {
unsigned long currentMillis = millis(); // current time
if (currentMillis - previousMillis >= PWMINCREASE) { // PWMINCREASE msec have passed since last time
previousMillis = currentMillis; // wait for next moment
pwmCycle = pwmCycle + PWMINCREASE; // next PWMINCREASE msec within one PWM cycle
if (pwmCycle > PWMTIME) { // if one PWM cycle is completed, repeat by starting at 1
pwmCycle = 1;
brightCycle ++; // increase cycle number
if (brightCycle > BRIGHTINCREASECYCLES) { // it is time to increase or decrease brightness
brightCycle = 0;
for (int i = 0; i < NUMBEROFPINS; i++) {
ledBrightness[i] = ledBrightness[i] + ledDimOrBright[i]; // increase or decreas brightness depening on direction
if (ledBrightness[i] == PWMTIME) { // if at maximum, for now invert to dimming
ledDimOrBright[i] = DOWN;
};
if (ledBrightness[i] == 1) { // if at minimum, for now invert to more bright. ledBrightness = 0 is used for the state off
ledDimOrBright[i] = UP;
};
};
};
};
for (int i = 0; i < NUMBEROFPINS; i++) {
digitalWrite(SIGNALPINS[i], (ledBrightness[i] >= pwmCycle) ? (HIGH) : (LOW)); //set output according to the position in the cycle
};
};
}
14_FadingWithoutDelay4LEDs.ino
void setup() {
// initialize signal output
SignalControl_Init();
}
// the loop function runs over and over again forever
void loop() {
SignalControl_Blink();
}
On receiving a command the lights of the current aspect must be dimmed during a dimming period and the lights of the new aspect must get brighter during a dimming period.