Here is a concept version of your described device using the EventFuse and MsTimer2 libraries. It demonstrates how to set up multiple tasks that occur at different rates in a way that should be easy to understand and maintain.
There are six separate things happening here. The main loop is handling software PWM on pins 2 through 13. This happens at a fairly high frequency and is independent of the timing of the other tasks.
Independent of the main loop the MsTimer2 library generates an interrupt once every millisecond and calls the burn() function of the eventFuse object. The eventFuse object reduces the length of each active fuse by 1 and then calls the handler for each fuse that has reached zero.
Five fuses are active. They represent the tasks that handle the 'A' and 'B' blade LEDs, the 10-segment LED bargraph and the two blinky LEDs.
The 'A' and 'B' blades are handled by two fuses that call a single fuse handler. The fuses execute the handler at random intervals (accomplished by assigning a new random value to the fuse length each time the fuse burns down) and setting the associated blade brightness to a random value. While the code is set up to handle any mixture of red and blue active at the same time, there is currently no code to switch the blade colors.
The LED bargraph executes at a regular interval and simply adjusts the display offset of the pattern that is displayed on the bargraph.
I wasn't sure how you were planning to handle the red/blue colors of the blades, so I set it up with the assumption that there are a total of four independent LEDs, one each of red and blue for each of the two blades. All the LEDs are independently controlled, which allows for red, blue, or any mixture in between.
I did not include code for a switch. You could add support for it by connecting a switch to digital pin 18, setting it up as an input and then creating a new fuse and handler for it. The handler would increment a counter if the button was pressed or reset the counter to zero if it was not. If the counter reached a high value the handler would trigger a 'button pressed' routine and then lock out the counter (set it to a special value to prevent calling the button pressed routine again) until the button was released. The button press routine would adjust the blade state variables as desired. The purpose of the counter is to debounce the switch. An impact switch may not need debouncing (I'd recommend putting a one-shot on it to ensure a clean signal with a minimum duration).
I tested most of this, the random flickering works, the blinky leds work, and the bar graph appears to animate, but I was testing it with just one led, so it might need tweaking. There is one bug that seems to jam up the flicker feature after a few minutes.
Hope it's useful:
/* This sketch is free software; you can use, redistribute and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*/#include <DigitalToggle.h>
#include <MsTimer2.h>
#include <EventFuse.h>
// Map bar graph LEDs to output pins. Output
// pins need not be mapped in sequence.
byte GraphPins[] = {2,3,4,5,6,7,8,9,10,11};
// blinky LEDs
#define BlinkyLED1Pin 12
#define BlinkyLED2Pin 13
// Lookup table for blade LED pins and values.
// Assumes that each blade has a set of two
// independent LEDs, one red and one blue. All
// four LEDs can be controlled independently,
// allowing any mixture of red and blue values
// on either blade.
#define BladeA 0
#define BladeB 1
#define RedPin 0
#define BluePin 1
#define BladePin 0
#define BladeBrightness 1
byte Blades[2][2][2] = { // Red Blue
//pin,val pin,val
{{14, 0}, {15, 0}}, // Blade A
{{16, 0}, {17, 0}}}; // Blade B
// State indicator constants for blades
#define RandomRed 0
#define RandomBlue 1
#define AllRandom 2
// Blade state variables
byte BladeColor[] = {RandomRed,RandomBlue};
// Intensity map to be scrolled across bar graph.
// Can be any reasonable length, just add more
// values here, but only 10 values will be visible
// on the graph LEDs at one time.
byte GraphPattern[] = {0,50,100,150,200,250,200,150,100,50};
// Offset values for positioning the graph pattern
// on the graph.
int GOffsetStart = -sizeof(GraphPattern);
int GOffsetEnd = sizeof(GraphPattern);
int GraphOffset = GOffsetStart;
// Software pwm value, all outputs with a value less than
// or equal to the value of pwmVal will be off, all outputs
// with a value equal to the value of pwmVal will be on.
byte pwmVal = 0;
// Taking into consideration the current offset, return the
// pattern value for the specified index. This represents
// the LED intensity that should be displayed on the bar graph
// LED identifed by idx.
byte getPatternVal( byte idx )
{
int ofs = idx + GraphOffset;
return ((ofs<0)||(ofs>=sizeof(GraphPattern)))?0:GraphPattern[ofs];
}
// Pattern debug output vars
//int a, lastVal = 0;
void pwmHandler()
{
pwmVal++;
// Pattern debug output
//if (lastVal!=(a=getPatternVal(0)))Serial.println(lastVal=a,HEX);
// Bar graph LEDs
for( byte i=0; i<sizeof(GraphPins); i++ )
digitalWrite( GraphPins[i], getPatternVal(i) > pwmVal );
// Blade LEDs
for( byte i=0; i<2; i++)
for( byte j=0; j<2; j++)
digitalWrite( Blades[i][j][BladePin], Blades[i][j][BladeBrightness] > pwmVal );
}
// Animate the pattern by scrolling
void patternAnimationHandler(FuseID fuse, int userData)
{
// Adjust the graph offset, resetting to the start as necessary
if (++GraphOffset > GOffsetEnd)
GraphOffset = GOffsetStart;
}
void bladeHandler(FuseID fuse, int whichBlade)
{
// Set the value to a random brightness
switch (BladeColor[whichBlade])
{
case RandomRed:
Blades[whichBlade][RedPin][BladeBrightness] = random(255);
Blades[whichBlade][BluePin][BladeBrightness] = 0;
break;
case RandomBlue:
Blades[whichBlade][RedPin][BladeBrightness] = 0;
Blades[whichBlade][BluePin][BladeBrightness] = random(255);
break;
case AllRandom:
Blades[whichBlade][RedPin][BladeBrightness] = random(255);
Blades[whichBlade][BluePin][BladeBrightness] = random(255);
break;
}
// Hold the value for a random but short period.
eventFuse[fuse].fuseLen = random(10,20);
}
// This handles the calls from several different fuses.
// The digital pin that it is to operate on is passed
// in the blinkyPin parameter.
// The way this routine works to blink two separate LEDs
// may not be obvious. It's a little tricky, but simple
// once you work out how it is using the fuses.
void BlinkyLEDHandler(FuseID fuse, int blinkyPin)
{
// Toggle the BlinkyLED (should be going on)
digitalToggle( blinkyPin );
// If the output is on, schedule a call to toggle it
if (digitalRead( blinkyPin ))
eventFuse.newFuse( blinkyPin, 20, 1, BlinkyLEDHandler );
}
void tick()
{
// Burn the fuses. Note that since MsTimer2 calls
// tick inside an interrupt and because burn() will
// call the fuse handlers, the code in the handlers
// will be executing inside the interrupt as well.
// Therefore, it is best to keep them quick.
eventFuse.burn(1);
}
void setup()
{
// For debugging
// Serial.begin(115200);
// Set up all the relevant pins for output
for(byte i=2; i<17; i++)
pinMode(i, OUTPUT);
// Set up fuses to handle the bar graph animation, the two blades and the two blinky LEDs
// Notice that some of these use the same handler. The first parameter
// is passed into the handler so that it can access different data.
eventFuse.newFuse( 50, INF_REPEAT, patternAnimationHandler );
eventFuse.newFuse( BladeA, 1, INF_REPEAT, bladeHandler );
eventFuse.newFuse( BladeB, 1, INF_REPEAT, bladeHandler );
eventFuse.newFuse( BlinkyLED1Pin, 1000, INF_REPEAT, BlinkyLEDHandler );
eventFuse.newFuse( BlinkyLED2Pin, 750, INF_REPEAT, BlinkyLEDHandler );
// Set up Timer2 to call tick once per millisecond
MsTimer2::set( 1, tick );
MsTimer2::start();
}
void loop()
{
// The main loop handles calling the
// pwmHandler at the desired frequency.
// It will be interrupted periodically
// to update the target duty cycle (brightness)
// of the various LEDs.
pwmHandler();
delayMicroseconds(20);
}