Laserwaterpas ontvanger

Vaak zijn de eigenschappen van I/O pinnen beter voor sinken (schakelen naar ground) dan voor sourcen (schakelen naar voeding). Daarbij trek je, als je naar de ground schakelt, geen stroom uit de ontkoppelcondensators van de microcontroller, en die zijn bij Arduino's toch al wat beperkt. Die ontkoppelcondensators blijven op die manier helemaal beschikbaar voor het voeden van de interne circuits van de microcontroller.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

@ Sparky,

Ik ben zojuist bezig geweest met de code aan te passen zodat de poorten overeenkomen met hoe ik ze uiteindelijk aan wil sluiten. Ook heb ik de poorten namen gegeven zodat ik de omnummering enkel bovenin de code hoefde te doen. Zie onder de volledige code.
Ik vraag me nu alleen af of ik iets vergeten ben of iets niet goed gedaan heb want de relais schakelen nu niet goed mee, ze schakelen soms wel en soms niet. Zou jij hier eens naar kunnen kijken en evt zeggen wat ik verkeerd heb gedaan?

Tot slot heb ik in onderstaande regel de ! voor Inputcounters verwijderd omdat ik een hoge output wil hebben op de poorten voor de LEDs omdat ik deze via een ULN2803 schakel. Heb ik dit goed gedaan?

code:


    digitalWrite( led_top + Index, InputCounters[ Index ] );

Hier de volledige code:

code:


#define MAX_INTERVAL 150 // max time a LED can be off while blinking, in ms
#define STABILITY_THRESHOLD 50 // time the input state must be stable before movement starts, in ms

#define DO_NOTHING  0x00
#define MOVE_UP   0x01
#define MOVE_DOWN 0x02
#define MOVE_FAST 0x04
#define MOVE_UPFAST (MOVE_UP | MOVE_FAST)
#define MOVE_DOWNFAST (MOVE_DOWN | MOVE_FAST)

// what to do for each combination of inputs; any combination of 2 or more active inputs means
// do nothing, only the combinations with exactly one active input, except the middle (neatral) input
// result in movement. These are positions 0x01 (1) for up fast, 0x02 (2) for up slow, 0x04 (4) for neutral,
// 0x08 (8) for down slow, 0x10 (16) for down fast. Note the array index starts at 0
const unsigned char Actions[ 32 ] = 
  { DO_NOTHING, MOVE_UPFAST, MOVE_UP, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, // 0..7
    MOVE_DOWN, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, // 8..15
    MOVE_DOWNFAST, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, // 16..23
    DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING }; // 24..31

unsigned short InputCounters[ 5 ] = { 0, 0, 0, 0, 0 };
unsigned char ReadInputs, FilteredInputs, PreviousInputs = 0, AutomaticMode = 0, JoystickInput;
unsigned long StabilityCount = 0, ActivityCounter = 0;

int input_pushbutton = 1; // pushbutton for automatic mode
int input_joy_up = 2; // joystick UP
int input_joy_down = 3; // joystick DOWN
int input_top = 4; // input top LED
int input_up = 5;
int input_mid = 6;
int input_down = 7;
int input_bottom = 8; // input bottom LED
int led_error = 9; // Error output
int led_auto = 10;  // LED for automatic mode
int output_up = 11; // move UP output
int output_down = 12; // move DOWN output
int output_slow = 13; // move SLOW output
int led_top = A0; // Indication lED move UP
int led_up = A1;
int led_mid = A2;
int led_down = A3;
int led_bottom = A4; // Indication lED move DOWN
int led_stable = A5; // LED for stable status


void setup()
{
  // pin 0 unused; causes problems when loading new software
  pinMode( input_pushbutton, INPUT ); // pushbutton for automatic mode
  pinMode( input_joy_up, INPUT ); // joystick UP
  pinMode( input_joy_down, INPUT ); // joystick DOWN
  pinMode( input_top, INPUT ); // input top LED
  pinMode( input_up, INPUT );
  pinMode( input_mid, INPUT );
  pinMode( input_down, INPUT );
  pinMode( input_bottom, INPUT ); // input bottom LED
  pinMode( led_error, OUTPUT ); // Error output
  pinMode( led_auto, OUTPUT ); // LED for automatic mode  
  pinMode( output_up, OUTPUT ); // move UP output
  pinMode( output_down, OUTPUT ); // move DOWN output
  pinMode( output_slow, OUTPUT ); // move FAST output (low=fast)
  pinMode( led_top, OUTPUT ); // Debugging status LEDs
  pinMode( led_up, OUTPUT );
  pinMode( led_mid, OUTPUT );
  pinMode( led_down, OUTPUT );
  pinMode( led_bottom, OUTPUT );
  pinMode( led_stable, OUTPUT );
}

void loop()
{
  unsigned char Index;
  
  FilteredInputs = 0;
  ReadInputs = ( digitalRead( input_top ) ) | ( digitalRead( input_up ) << 1 ) | ( digitalRead( input_mid ) << 2 ) | ( digitalRead( input_down ) << 3 ) | ( digitalRead( input_bottom ) << 4 );

  // this loop monitors each input for recent activity; when an input is active, the timer for that inputs
  // is set to MAX_INTERVAL, and starts counting down from there; when it reaches 0, the input is considered
  // inactive. MAX_INTERVAL should be set slightly longer than the maximum time the LED for an input can be off
  // when blinking
  for( Index = 0; Index < 5; Index++ )
  {
    if( ReadInputs & ( 1 << Index ) )
      InputCounters[ Index ] = MAX_INTERVAL;

    if( InputCounters[ Index ] )
    {
      InputCounters[ Index ]--;
      FilteredInputs |= 1 << Index;
    }

    // write each input filter status to a debug LED (A0 + 3 is the same as A3)
    digitalWrite( led_top + Index, InputCounters[ Index ] );
  }

  if( !FilteredInputs ) // if all timers have expired, not a single input active
    ActivityCounter = 2000; // set timer for 2 seconds; only when signal is lost should all timers expire

  if( ActivityCounter )
  {
    ActivityCounter--;
    FilteredInputs = 0; // only after 2 seconds without any expired timers should the outputs become active
  }
  
  // now check if the input state has been stable for a number of successive cycles, to prevent movement
  // when 2 LEDs are blinking, since one can always be detected slightly earlier or later than the other
  // If current and previous input states are different, reset stability counter
  if( PreviousInputs != FilteredInputs )
    StabilityCount = STABILITY_THRESHOLD;

  // now store the current input state for the next cycle
  PreviousInputs = FilteredInputs;

  // if stability counter has not yet reached the threshold, increment it, and force the input state to 0
  // to prevent any movement
  if( StabilityCount )
  {
    StabilityCount--;
    FilteredInputs = 0;
  }
  
  // write Stability status to debug LED
  digitalWrite( led_stable, !StabilityCount );

  // write Error LED status; LOW when an error occured (LED with resistor to supply)
  digitalWrite( led_error, FilteredInputs );

  JoystickInput = digitalRead( input_joy_up ) | ( digitalRead( input_joy_down ) << 1 ); // 0x01 = UP, 0x02 = DOWN

  if( JoystickInput )
    AutomaticMode = 0;
  else
    if( digitalRead( input_pushbutton ) ) // pushbutton for automatic mode
      AutomaticMode = 1;

  if( AutomaticMode )
  {
    // lookup what to do for this state and write to outputs
    digitalWrite( output_up, Actions[ FilteredInputs ] & MOVE_UP );
    digitalWrite( output_down, Actions[ FilteredInputs ] & MOVE_DOWN );
    digitalWrite( output_slow, ( !( Actions[ FilteredInputs ] & MOVE_FAST ) ) && Actions[ FilteredInputs ] );
  }
  else
  {
    digitalWrite( output_up, JoystickInput == 0x01 ); // valve UP when joystick UP is active (and DOWN isn't)
    digitalWrite( output_down, JoystickInput == 0x02 ); // valve DOWN when joystick DOWN is active (and UP isn't)
    digitalWrite( output_slow, 0 ); // fast mode
  }

  digitalWrite( led_auto, AutomaticMode );
  delay(1); // actual loop interval will be slightly longer, because it also takes some time to execute
}

Alvast bedankt weer!

Aangenomen dat de pinnummers kloppen met de werkelijkheid, zie ik niet direct wat er mis zou zijn. Ik zie wel dat je MAX_INTERVAL hebt verlaagd van 200 naar 150; als de LEDs bij het knipperen langer uit zijn dan dat, werkt het niet meer. Waarom heb je dat gedaan, en werkt het wel als je dat terug zet?

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Op 26 september 2020 22:42:46 schreef SparkyGSX:
Ik zie wel dat je MAX_INTERVAL hebt verlaagd van 200 naar 150; als de LEDs bij het knipperen langer uit zijn dan dat, werkt het niet meer. Waarom heb je dat gedaan, en werkt het wel als je dat terug zet?

Aha dat is wel een goeie. In mijn testopstelling heb ik niet meer de ontvanger zelf als input maar een los knipperend LEDje, deze zal ik niet snel genoeg hebben laten knipperen denk ik. Morgen even proberen.
Die 150ms werkt trouwens prima op de ontvanger, ik heb hier een beetje mee zitten spelen om te kijken hoe ver ik kon gaan. Vandaar de veranderde waarde.

Ok, dat is opgelost. De Led stond idd te langzaam ingesteld.
Ik heb nog wel een andere vraag. Zodra ik de fototransistors belicht geven de output Leds vrijwel direct een signaal maar de relais schakelen pas na +/- 2 seconden. Is hier een reden voor en kan ik dit nog finetunen? Ik kan die delay namelijk niet in de code terug vinden.

EDIT:
Inmiddels ben ik erachter denk ik, het komt omdat ik hem handmatig van de ene transistor naar de volgende beweeg en als ik dit niet snel genoeg doe zal hij dit beschouwen als "lost signal". Als ik heel snel beweeg gaat dit wel goed. Dit zal ook de praktijk zijn want op de ontvanger zit er vrijwel geen tijd tussen de ene led en de andere.

[Bericht gewijzigd door BartL op 27 september 2020 17:30:10 (34%)]

@Sparky,

Ik heb vandaag eindelijk de mogelijkheid gehad om het geheel op de machine te testen. Het systeem werkt nog niet helemaal naar behoren vanwege het simpele feit dat ik iets gemist heb bij het opstellen van de specs. Het blijkt namelijk dat wanneer de laser op grotere afstand staat het mogelijk is 2 secties tegelijk te laten branden. Dus bijvoorbeeld neutraal en 1 daarboven. In de code is dit nu zo geprogrammeerd dat hij dan niets doet maar dit zou dus aangepast moeten worden. Eigenlijk moet hij altijd reageren behalve als de onderste en de bovenste tegelijk knipperen (geen signaal). Zou je dit nog aan kunnen passen in de code?

Alvast bedankt weer!!

Voor de volledigheid, dit is de code die ik nu gebruik.

code:


#define MAX_INTERVAL 150 // max time a LED can be off while blinking, in ms
#define STABILITY_THRESHOLD 50 // time the input state must be stable before movement starts, in ms

#define DO_NOTHING  0x00
#define MOVE_UP   0x01
#define MOVE_DOWN 0x02
#define MOVE_FAST 0x04
#define MOVE_UPFAST (MOVE_UP | MOVE_FAST)
#define MOVE_DOWNFAST (MOVE_DOWN | MOVE_FAST)

// what to do for each combination of inputs; any combination of 2 or more active inputs means
// do nothing, only the combinations with exactly one active input, except the middle (neatral) input
// result in movement. These are positions 0x01 (1) for up fast, 0x02 (2) for up slow, 0x04 (4) for neutral,
// 0x08 (8) for down slow, 0x10 (16) for down fast. Note the array index starts at 0
const unsigned char Actions[ 32 ] = 
  { DO_NOTHING, MOVE_UPFAST, MOVE_UP, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, // 0..7
    MOVE_DOWN, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, // 8..15
    MOVE_DOWNFAST, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, // 16..23
    DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING }; // 24..31

unsigned short InputCounters[ 5 ] = { 0, 0, 0, 0, 0 };
unsigned char ReadInputs, FilteredInputs, PreviousInputs = 0, AutomaticMode = 0, JoystickInput;
unsigned long StabilityCount = 0, ActivityCounter = 0;

int input_pushbutton = 1; // pushbutton for automatic mode
int input_joy_up = 2; // joystick UP
int input_joy_down = 3; // joystick DOWN
int input_top = 4; // input top LED
int input_up = 5; // input 2nd LED from top
int input_mid = 6; // input neutral LED
int input_down = 7; // input 2nd LED from bottom
int input_bottom = 8; // input bottom LED
int led_error = 9; // Error output
int led_auto = 10;  // LED for automatic mode
int output_up = 11; // move UP output
int output_down = 12; // move DOWN output
int output_slow = 13; // move SLOW output
int led_top = A0; // Indication lED move UP
int led_up = A1;
int led_mid = A2;
int led_down = A3;
int led_bottom = A4; // Indication lED move DOWN
int led_stable = A5; // LED for stable status


void setup()
{
  // pin 0 unused; causes problems when loading new software
  pinMode( input_pushbutton, INPUT ); // pushbutton for automatic mode
  pinMode( input_joy_up, INPUT ); // joystick UP
  pinMode( input_joy_down, INPUT ); // joystick DOWN
  pinMode( input_top, INPUT ); // input top LED
  pinMode( input_up, INPUT );
  pinMode( input_mid, INPUT );
  pinMode( input_down, INPUT );
  pinMode( input_bottom, INPUT ); // input bottom LED
  pinMode( led_error, OUTPUT ); // Error output
  pinMode( led_auto, OUTPUT ); // LED for automatic mode  
  pinMode( output_up, OUTPUT ); // move UP output
  pinMode( output_down, OUTPUT ); // move DOWN output
  pinMode( output_slow, OUTPUT ); // move FAST output (low=fast)
  pinMode( led_top, OUTPUT ); // Debugging status LEDs
  pinMode( led_up, OUTPUT );
  pinMode( led_mid, OUTPUT );
  pinMode( led_down, OUTPUT );
  pinMode( led_bottom, OUTPUT );
  pinMode( led_stable, OUTPUT );
}

void loop()
{

  unsigned char Index;
  
  FilteredInputs = 0;
  ReadInputs = ( digitalRead( input_top ) ) | ( digitalRead( input_up ) << 1 ) | ( digitalRead( input_mid ) << 2 ) | ( digitalRead( input_down ) << 3 ) | ( digitalRead( input_bottom ) << 4 );

  // this loop monitors each input for recent activity; when an input is active, the timer for that inputs
  // is set to MAX_INTERVAL, and starts counting down from there; when it reaches 0, the input is considered
  // inactive. MAX_INTERVAL should be set slightly longer than the maximum time the LED for an input can be off
  // when blinking
  for( Index = 0; Index < 5; Index++ )
  {
    if( ReadInputs & ( 1 << Index ) )
      InputCounters[ Index ] = MAX_INTERVAL;

    if( InputCounters[ Index ] )
    {
      InputCounters[ Index ]--;
      FilteredInputs |= 1 << Index;
    }

    // write each input filter status to a debug LED (A0 + 3 is the same as A3)
    digitalWrite( led_top + Index, InputCounters[ Index ] );
  }

  if( !FilteredInputs ) // if all timers have expired, not a single input active
    ActivityCounter = 2000; // set timer for 2 seconds; only when signal is lost should all timers expire

  if( ActivityCounter )
  {
    ActivityCounter--;
    FilteredInputs = 0; // only after 2 seconds without any expired timers should the outputs become active
  }
  
  // now check if the input state has been stable for a number of successive cycles, to prevent movement
  // when 2 LEDs are blinking, since one can always be detected slightly earlier or later than the other
  // If current and previous input states are different, reset stability counter
  if( PreviousInputs != FilteredInputs )
    StabilityCount = STABILITY_THRESHOLD;

  // now store the current input state for the next cycle
  PreviousInputs = FilteredInputs;

  // if stability counter has not yet reached the threshold, increment it, and force the input state to 0
  // to prevent any movement
  if( StabilityCount )
  {
    StabilityCount--;
    FilteredInputs = 0;
  }
  
  // write Stability status to debug LED
  digitalWrite( led_stable, !StabilityCount );

  // write Error LED status; LOW when an error occured (LED with resistor to supply)
  digitalWrite( led_error, !FilteredInputs );

  JoystickInput = digitalRead( input_joy_up ) | ( digitalRead( input_joy_down ) << 1 ); // 0x01 = UP, 0x02 = DOWN

  if( JoystickInput )
    AutomaticMode = 0;
  else
    if( digitalRead( input_pushbutton ) ) // pushbutton for automatic mode
      AutomaticMode = 1;

  if( AutomaticMode )
  {
    // lookup what to do for this state and write to outputs
    digitalWrite( output_up, Actions[ FilteredInputs ] & MOVE_UP );
    digitalWrite( output_down, Actions[ FilteredInputs ] & MOVE_DOWN );
    digitalWrite( output_slow, ( !( Actions[ FilteredInputs ] & MOVE_FAST ) ) && Actions[ FilteredInputs ] );
  }
  else
  {
    digitalWrite( output_up, JoystickInput == 0x01 ); // valve UP when joystick UP is active (and DOWN isn't)
    digitalWrite( output_down, JoystickInput == 0x02 ); // valve DOWN when joystick DOWN is active (and UP isn't)
    digitalWrite( output_slow, 0 ); // fast mode
  }

  digitalWrite( led_auto, AutomaticMode );
  delay(1); // actual loop interval will be slightly longer, because it also takes some time to execute

}
[\Code]

Dat kan, maar dan kun je ook zelf aanpassen; het enige wat daarvoor moet gebeuren, is de Actions tabel uitbreiden. De bovenste ingang heeft index 1, de tweede van boven 2, en als allebei aan zijn wordt dat 3; op die plaats staat nu "DO_NOTHING", dus daar kun je "MOVE_UP" of "MOVE_UPFAST" neerzetten. Hetzelfde geldt voor neutraal + omhoog op plaats 2 + 4 = 6, neutraal + omlaag op 4 + 8 = 12, en de onderste 2 LEDs op 8 + 16 = 24.

Dit is wel aangenomen dat beide LEDs echt aan zijn, en niet heel hard heen- en weer staan te knipperen; in dat laatste geval zou er extra hardware of software nodig zijn.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Aha, dan snap ik nu (eindelijk) waar die regels voor dienen.
Dit heb ik net getest en dat werkt inderdaad. Nu loop ik alleen tegen het volgende probleem aan. Doordat er wat vering in de machine zit (waarschijnlijk deels omdat ik hem onbelast stationair test) schiet hij nu steeds de neutrale stand voorbij waardoor hij blijft bijsturen. Dit kan ik verhelpen door de oliestroom nog verder te reduceren maar dan wordt de gehele machine te langzaam om er nog goed mee te kunnen werken. Misschien zou ik dit op kunnen lossen door index 6 en 12 weer op DO_NOTHING te zetten. Maar daarmee accepteer ik eigenlijk ook tegelijk een groter bereik als neutrale stand. Als er dus mogelijkheden zijn om dit anders op te lossen...

Eh, tja met de beschikbare informatie van de sensoren zijn de mogelijkheden een beetje beperkt; het enige wat ik zo direct kan bedenken is een extra ventiel toevoegen om een paar seconden nadat door de neutraal bent gegaan een lagere snelheid aan te houden.

Eigenlijk heb je dat een redelijk fundamenteel probleem; blijkbaar blijft het ding nog even bewegen nadat het ventiel gesloten is, klopt dat? Komt dat door de rek in de leidingen, of is er iets anders dat veert en nog even door blijft drukken?

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

De machine deint inderdaad een beetje. Dit komt bijvoorbeeld door het veren van de lange trekdissel die eraan zit en door het veren van de wielen waar de machine op rijdt.
Nog een ventiel erbij zie ik niet zo zitten eigenlijk dan wordt het allemaal nogal complex.
Even simpel gedacht;
Zou het niet mogelijk zijn om index 6 en 12 op DO_NOTHING terug te zetten om ervoor te zorgen dat het neutraal bereik (tijdelijk) iets vergroot wordt. Daarna vervolgens het ventiel nog X seconden aansturen wanneer hij Y seconden in die stand (6 of 12) blijft staan?
Met die timers kan ik dan wat spelen totdat hij goed werkt.

Dat zou wel kunnen, ik ga even nadenken over hoe dat er enigszins fatsoenlijk aan toegevoegd kan worden.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Even een update.

Nu ik 6 en 12 op DO_nothing heb gezet kan ik hem wel met acceptabele snelheid laten werken. Hoe dit uiteindelijk in het werk zal reageren is even afwachten. Wanneer er een paar kuub grond voor de schuif ligt zal hij anders reageren dan zoals nu zonder belasting op het beton.
Ik moet nog een paar dingen hydraulisch aanpassen en wanneer dit is gebeurt zal ik laten weten of het nog nodig is de Code eventueel aan te passen.

@Sparky,

Ik heb het systeem nog wat aangepast en hij begint nu redelijk goed te werken.
Eigenlijk heb ik (voor nu) nog 2 puntjes die ik graag aangepast zou willen hebben.

1. De klep die bediend wordt om hem langzaam te laten bewegen zou in de neutraal stand geactiveerd moeten blijven of anders later pas uit moeten schakelen. Dit omdat ik het systeem niet met een 3/2klep maar met een 2/2klep heb uitgevoegd met de smoring er als by-pass omheen. Dit werkt prima maar bij het naderen van de neutraalstand (2/2 klep dicht) wordt er druk opgebouwd tegen het smoorventiel. Wanneer de neutraalstand dan bereikt is en de klep weer open gaat schokt de machine nogal omdat deze druk dan ineens afgelaten wordt.

2. In de praktijk blijkt het beter te zijn om de machine toch te laten zakken of heffen in de "eruit gelopen" stand boven- of onderin. Als dit eventueel pulserend gaat ivm de afwijkende knipper frequentie is niet zo erg.

Hieronder de actuele code.

code:


#define MAX_INTERVAL 150 // max time a LED can be off while blinking, in ms
#define STABILITY_THRESHOLD 50 // time the input state must be stable before movement starts, in ms

#define DO_NOTHING  0x00
#define MOVE_UP   0x01
#define MOVE_DOWN 0x02
#define MOVE_FAST 0x04
#define MOVE_UPFAST (MOVE_UP | MOVE_FAST)
#define MOVE_DOWNFAST (MOVE_DOWN | MOVE_FAST)

// what to do for each combination of inputs; Every single input except for the middle input will result in movement.
// An input in combination with the input next to it wil also result in movement. All other combinations means DO_NOTHING.
// These are positions 0x01 (1) for up fast, 0x02 (2) for up slow, 0x04 (4) for neutral,0x08 (8) for down slow, 0x10 (16) for down fast. 
// The movement combinations are: 3(up fast + up slow (1+2)), 6(up slow + neutral (2+4)), 12(neutral + down slow (4+8)) and 24(up slow + up fast (8+16)).
// Note the array index starts at 0
const unsigned char Actions[ 32 ] = 
  { DO_NOTHING, MOVE_UPFAST, MOVE_UP, MOVE_UP, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, // 0..7
    MOVE_DOWN, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, // 8..15
    MOVE_DOWNFAST, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, // 16..23
    MOVE_DOWN, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING, DO_NOTHING }; // 24..31

unsigned short InputCounters[ 5 ] = { 0, 0, 0, 0, 0 };
unsigned char ReadInputs, FilteredInputs, PreviousInputs = 0, AutomaticMode = 0, JoystickInput;
unsigned long StabilityCount = 0, ActivityCounter = 0;

int input_pushbutton = 1; // pushbutton for automatic mode
int input_joy_up = 2; // joystick UP
int input_joy_down = 3; // joystick DOWN
int input_top = 4; // input top LED
int input_up = 5; // input 2nd LED from top
int input_mid = 6; // input neutral LED
int input_down = 7; // input 2nd LED from bottom
int input_bottom = 8; // input bottom LED
int led_error = 9; // Error output
int led_auto = 10;  // LED for automatic mode
int output_up = 11; // move UP output
int output_down = 12; // move DOWN output
int output_slow = 13; // move SLOW output
int led_top = A0; // Indication lED move UP
int led_up = A1;
int led_mid = A2;
int led_down = A3;
int led_bottom = A4; // Indication lED move DOWN
int led_stable = A5; // LED for stable status


void setup()
{
  // pin 0 unused; causes problems when loading new software
  pinMode( input_pushbutton, INPUT ); // pushbutton for automatic mode
  pinMode( input_joy_up, INPUT ); // joystick UP
  pinMode( input_joy_down, INPUT ); // joystick DOWN
  pinMode( input_top, INPUT ); // input top LED
  pinMode( input_up, INPUT );
  pinMode( input_mid, INPUT );
  pinMode( input_down, INPUT );
  pinMode( input_bottom, INPUT ); // input bottom LED
  pinMode( led_error, OUTPUT ); // Error output
  pinMode( led_auto, OUTPUT ); // LED for automatic mode  
  pinMode( output_up, OUTPUT ); // move UP output
  pinMode( output_down, OUTPUT ); // move DOWN output
  pinMode( output_slow, OUTPUT ); // move FAST output (low=fast)
  pinMode( led_top, OUTPUT ); // Debugging status LEDs
  pinMode( led_up, OUTPUT );
  pinMode( led_mid, OUTPUT );
  pinMode( led_down, OUTPUT );
  pinMode( led_bottom, OUTPUT );
  pinMode( led_stable, OUTPUT );
}

void loop()
{

  unsigned char Index;
  
  FilteredInputs = 0;
  ReadInputs = ( digitalRead( input_top ) ) | ( digitalRead( input_up ) << 1 ) | ( digitalRead( input_mid ) << 2 ) | ( digitalRead( input_down ) << 3 ) | ( digitalRead( input_bottom ) << 4 );

  // this loop monitors each input for recent activity; when an input is active, the timer for that inputs
  // is set to MAX_INTERVAL, and starts counting down from there; when it reaches 0, the input is considered
  // inactive. MAX_INTERVAL should be set slightly longer than the maximum time the LED for an input can be off
  // when blinking
  for( Index = 0; Index < 5; Index++ )
  {
    if( ReadInputs & ( 1 << Index ) )
      InputCounters[ Index ] = MAX_INTERVAL;

    if( InputCounters[ Index ] )
    {
      InputCounters[ Index ]--;
      FilteredInputs |= 1 << Index;
    }

    // write each input filter status to a debug LED (A0 + 3 is the same as A3)
    digitalWrite( led_top + Index, InputCounters[ Index ] );
  }

  if( !FilteredInputs ) // if all timers have expired, not a single input active
    ActivityCounter = 2000; // set timer for 2 seconds; only when signal is lost should all timers expire

  if( ActivityCounter )
  {
    ActivityCounter--;
    FilteredInputs = 0; // only after 2 seconds without any expired timers should the outputs become active
  }
  
  // now check if the input state has been stable for a number of successive cycles, to prevent movement
  // when 2 LEDs are blinking, since one can always be detected slightly earlier or later than the other
  // If current and previous input states are different, reset stability counter
  if( PreviousInputs != FilteredInputs )
    StabilityCount = STABILITY_THRESHOLD;

  // now store the current input state for the next cycle
  PreviousInputs = FilteredInputs;

  // if stability counter has not yet reached the threshold, increment it, and force the input state to 0
  // to prevent any movement
  if( StabilityCount )
  {
    StabilityCount--;
    FilteredInputs = 0;
  }
  
  // write Stability status to debug LED
  digitalWrite( led_stable, !StabilityCount );

  // write Error LED status; LOW when an error occured (LED with resistor to supply)
  digitalWrite( led_error, !FilteredInputs );

  JoystickInput = digitalRead( input_joy_up ) | ( digitalRead( input_joy_down ) << 1 ); // 0x01 = UP, 0x02 = DOWN

  if( JoystickInput )
    AutomaticMode = 0;
  else
    if( digitalRead( input_pushbutton ) ) // pushbutton for automatic mode
      AutomaticMode = 1;

  if( AutomaticMode )
  {
    // lookup what to do for this state and write to outputs
    digitalWrite( output_up, Actions[ FilteredInputs ] & MOVE_UP );
    digitalWrite( output_down, Actions[ FilteredInputs ] & MOVE_DOWN );
    digitalWrite( output_slow, ( !( Actions[ FilteredInputs ] & MOVE_FAST ) ) && Actions[ FilteredInputs ] );
  }
  else
  {
    digitalWrite( output_up, JoystickInput == 0x01 ); // valve UP when joystick UP is active (and DOWN isn't)
    digitalWrite( output_down, JoystickInput == 0x02 ); // valve DOWN when joystick DOWN is active (and UP isn't)
    digitalWrite( output_slow, 0 ); // fast mode
  }

  digitalWrite( led_auto, AutomaticMode );
  delay(1); // actual loop interval will be slightly longer, because it also takes some time to execute

}