Delicious Bookmark this on Delicious      HOME > > ARDUINO LANGUAGE COURSE  t.o.c.

PAGE TITLE

This is one of a collection of pages which, together, attempt to show you "everything" about the Arduino's programming language.

There is a page for you with more information about the project in general, and the way these pages are organized, if you want that.

Please visit my page about power browsing notes sometime.

This page, and the software it references, ©TK Boyd, 1/2010.

========

THIS IS STILL IN A "raw" STATE

Come back later if you don't like typos, etc.

The code WORKED... but in displaying it as HTML

distortions may have crept in

========

This is one page in a series which track the deveopment of an Arduino based Access Control device (electronic lock).

============

This the third and last of a long series of tutorials, but they are, I hope!, worth slogging through.

If you haven't read the first in the series, you really need to, at least to see the definition of the problem we are setting out to solve.

===

In this next stage, we will finish the project. We will re-work the program so that if both LEDs are on, or both LEDs are off, the user presses the green switch to open the door, and if only one LED is on, the user presses the yellow switch to open the door. This is a rather pathetic rule which would leave you with a not very secure door... but that much is easy to program. After we have that working, there's just one more stage of development to complete: The conversion of the program to work as before, but with a fancier "When should the door be opened" rule.

The biggest thing to be created in this next stage of development is the part that says the Arduino looks to see what buttons you have down at a particular instant. That instant comes a fixed period of time after you have pressed the first button.

One way to do that sort of thing is to use millis... Say the first button is pressed when millis returns 987,654,000... i.e. the Arduino has been running for about 987,000 seconds. And say you want the system to check what buttons you have pressed ten seconds after the first press...

Well, you just add 10,000 to 987,654,000 to learn the time, in millis, when the check should take place. The great thing about using this approach is that other things can be going on while the 10 seconds passes. While the Arduino is doing a "delay()", it DOES NOT take care of "other business".

The problem with this approach is that there is a "biggest number", beyond which millis won't go. It is a bit like a car with 5 wheels on it's odometer. When it has been 99,999 miles, the odometer goes back to zero. The millis number is very much larger, but you still have a special circumstance about every 50 days.... and special circumstances have to be provided for... and that can be messy.

So, we WON't be using something like....

Go round and round a loop 'til a key pressed.
Look at millis.
Add 10,000 to the number you get, and
    store it in "CheckButtonsAt"
Go round and round a second loop until
    millis is bigger than then number
    in "CheckButtonsAt"
CheckButtons.

It would ALMOST work... but not in the case we will discuss in a moment. Why MIGHT we use a millis-based answer, instead of the "delay" answer we are going to use? (Hmmm... I painted myself into a corner, again!... but I want to talk a bit about the pros and cons of a "millis answer", so please bear with me?)

If the Arduino is doing a "delay", it can't do anything else. A more user friendly solution would be something that would be difficult to describe, in the detail needed to implement it, and more difficult still to program. That more user friendly answer would wait for the user to press a button, any button. The program would then wait to see if the user pressed down any more buttons. It would record all buttons pressed between the pressing of the first until the release of the last one being held, and then ask if the combination of all the buttons which were once down is the right answer to the challenge that was on the LEDs previously. (Remember that at the speed of the electronics you can't release the buttons at the same time.) The "corner" I spoke of painting myself into is the fact that this system doesn't need any timing. For the sake of the example, we'll add the largely unnecessary additional requirement that the user completes the task within a set number of seconds. In THAT scenario, "delay" can't be used because you couldn't have the Arduino "looking" to see the other button presses at the same time as it was timing the process.

In this "answer" to the access control problem, be careful that you are keeping the buttons of the correct response down long enough. You must keep them down until the access control device either says "Right" or "Wrong", by flashing the LEDs slowly or rapidly, respectively. If you enter the CORRECT code, but release the buttons too soon, your answer will still be treated as WRONG. Your eye and nerves may fool you into THINKING you held the buttons down long enough when you didn't. If the device seems to be misbehaving, not opening when it should, examine your button pressing carefully.

Turning to the problem with the millis answer....

Let's say the biggest number millis can be is 999,999,999. (It is actually a much bigger number (4,294,967,295), and let's say that by bad luck, you press your first button when millis is returning 999,999,990.

When you add 10,000 to 999,990,000, instead of getting what you "should", 1,000,000,000, you will get 9,000. So now, your test to see if it is time to check the buttons yet will "pass" the first time you look. Millis will be at something like 999,990,500... more than 9,000... but it SHOULDN'T pass.

You might think, okay then... we'll make the rule that we check the buttons not when millis becomes MORE THAN the "check it now" number, but when it EQUALS the "check it now" button. The problem with that is that you will probably miss the exact "check it now" value. You might, say, check millis when it was 8,990 and a moment later you check it again and now it is 9,001... you've missed the 9,000 you were waiting for.

If you work it all out very carefully, it may be that the worst thing that happens, once every 50 days or so, is that you don't get adequate time to press the rest of the buttons when you start to put in the code. But "such things are too high for me" as it says in the bible. Until I NEED to use millis for this sort of thing, I will find another approach.

Before I go on: Millis are great for timing things, a la stopwatch. Even if you get an overflow event, you can deal with it. Let's start with a nice simple case where there is no roll over. For instance, suppose the thing you were timing started at millis=987,000,000 and ended at millis=987,001,234. the event took 1.234 seconds. Easy.

Now for a case WITH roll over. Suppose millis rolls over at 999,999,999 and that the event started at 999,999,000 and ended at 234. That event also took 1.234. If you see that the "end" time was "before" the "start" time, you can deduce that an overflow took place. It is still easy to calculate the time.

Before we leave millis, let me mention that there is also a micros for timing to a very, very high resolution.

So... we aren't going to use millis to create the interval between the first button's press and the moment when the computer looks to see whether the user is pressing the right buttons. What will we use?

Good old "delay". If anything else had to be going on during this time, delay wouldn't work... but nothing else DOES have to go on, so we CAN use delay.

What's "wrong" with the "delay" answer: Users will have to be educated as to the way the system works. Maybe the odd requirement I'll explain in a moment could be marketed as a "special security feature"!

When users come to the access control device, they will look at which LEDs are lit, apply "the rule", and press the "right" buttons for the current pattern on the LEDs. With most devices we are familiar with, as soon as the right combination of keys is down, the door should open. With our device, the user must wait a bit, the user must wait until the moment that the program says, "Okay, the user has now had enough time to get the right buttons down... are they down NOW?"

Sorry for the tangles in this tutorial. "Millis" is a good thing... it just wasn't right for this. I will try one day to re-write this, move the millis discussion to a more relevant context.

And maybe one day I can come up with a way to make the access device open as soon as the right buttons are down without making it too vulerable to a "trial and error" approach to finding the combination to open it. An exercise for the student??

So here, at last, is the program mostly done. All that is "wrong" with this version is that the "rule" for what you press to open the lock is too simple.

/*FEAa1AccCont4th: access control system-  4th stage of development
ver 27 Dec 09

Requires 3 LEDs, two switches.
One of the LEDs is standing in for an electromechanical strikeplate

Two of the LEDs present a "challenge" to the user
If the user holds down the correct combination on
the switches for a short while, the "lock" opens for
a time.

The lock doesn't open WHEN the user presses the right
combination on the switches... that would make trial and
error too easy. Rather, the software looks at the switches
at a pre-determined moment, and if they are properly
set at that moment, the door opens... or at least the "lock" LED turns
on for a set period.

*/

const byte LEDlock=13;//Pin where lock would be connected.
const byte LED1=12;//LED1 connected here (MS bit)
const byte LED0=11;//LED0 connected here (LS bit)

const byte bGrn0=4;//pin green switch is on
const byte bYel1=5;//pin yellow switch is on
// N.B.: That is "wye-ee-ell-one" for YELlow, bit ONE

byte bNumToShow;

void setup()
{
   //Serial.begin(9600);//for debug work only
   pinMode(LEDlock,OUTPUT);
   pinMode(LED0,OUTPUT);
   pinMode(LED1,OUTPUT);

   pinMode(bGrn0,INPUT);
   pinMode(bYel1,INPUT);

   setLEDs(0,0);
   LockDoor();
   }

void loop()
{
  bNumToShow=random(0,4);//Pick 0,1,2 or 3 to appear on LEDs
  showInBinary(bNumToShow);//Show it on LEDs.

  while ((!boSwitchPressed(bYel1))&&(!boSwitchPressed(bGrn0)))
     {;};//Wait for a switch to be pressed

  delay(600);//User must finish getting right buttons pressed during this delay

  if (boCorrectButtonsPressedNow(bNumToShow))
       {SaySuccessAndOpenDoorDuringMessage();}
          else {SayFailed();};

  randomSeed(BottomOfMillis());//Only NEEDED once, but it does no
  //harm to do it every time through the loop.

}//end of function "loop"

boolean boSwitchPressed(byte bWhichSwitch)
//In this version, the function does NOT wait for the switch
//to be released.
  {
   boolean boAns=false;//What is in this will eventually be passed
     //back to the calling code. Assume switch NOT pressed,
     //for the moment.
   if (digitalRead(bWhichSwitch)==LOW)
     {
        delay(20);//Wait for bounces to pass
        boAns=true;
     }//end of "if..."
     return boAns;
  }//end of boSwitchPressed

void SayFailed()
{
   FlashLEDs(12,60);
}//end of SayFailed

void SaySuccessAndOpenDoorDuringMessage()
//That name looks silly... but it will appear only
//once, and the extra information will help you
//be clear about what is done inside the function.

{
  UnlockDoor();
  FlashLEDs(6,200);
  LockDoor();
}//end of SaySuccess...

void setLEDs(byte bL1, byte bL0)
//Easily extended for more LEDs
{
   if (bL0==0) digitalWrite(LED0,LOW);
               else  digitalWrite(LED0,HIGH);
   if (bL1==0) digitalWrite(LED1,LOW);
               else  digitalWrite(LED1,HIGH);
 }

void FlashLEDs(byte bReps,byte bRate)
//Call this a number of times to cause the LEDs to
//flash on-off-on-off. The number in bRate controls
//how fast the LEDs flash. The number in bReps
//determines how many flash cycles happen.
{
  for (int i=0;i<bReps;i++)
   {
      setLEDs(1,1);
      delay(bRate);
      setLEDs(0,0);
      delay(bRate);
   }//end of "for..."
}

void LockDoor()
//Simple at the moment... but it is
//still worth "packaging" this in its
//own function, in case the program
//has to be adapted for an unusual
//locking device.
{
digitalWrite(LEDlock,LOW);
}

void UnlockDoor()
//See LokDoor rems.
{
digitalWrite(LEDlock,HIGH);
}

void showInBinary(byte bNum)
/*Set LED1 on or off depending on
left hand digit in binary representation
of number in bNum...

And set LED0 on or off depending on
right hand digit in binary representation
of number in bNum.

For debug: send what's in bNum to the serial monitor,
    so we can check that what the LEDs are showing is right...
(The int() stuff around bNum converts it from byte type
   data, which println can't handle, to an equivalent
   int type datum, which println IS okay with.)
*/
{
//Serial.println(int(bNum));

//Set LED1 on or off...
if ((bNum & B10)==B10)
  {digitalWrite(LED1,HIGH);}
  else
  {digitalWrite(LED1,LOW);};

//Set LED0 on or off...
  if ((bNum & 1)==1)
  {digitalWrite(LED0,HIGH);}
  else
  {digitalWrite(LED0,LOW);};
}

boolean boCorrectButtonsPressedNow(byte bToDecode)
//The byte in bToDecode is what is showing on
//the LEDs. The user must apply whatever rule the
//access control system is applying, and press
//the switches accordingly.
{
  boolean boReturn=false;//Assume for the time being that
    //user does NOT press right switches.

  //Fetch and record the current state of the switches...
  boolean boStateOfGrn0;
      boStateOfGrn0=boSwitchPressed(bGrn0);
  boolean boStateOfYel1;
      boStateOfYel1=boSwitchPressed(bYel1);

  //Now look at various "correct" answers, and if any
  //are present change boReturn to true

  //If LEDs NOT *BOTH* on, then it should be yellow switch pressed, and
  //only yellow switch...
  if (
     ((bToDecode==B10)||(bToDecode==B01))
     &&
     ((boStateOfYel1)&&(!boStateOfGrn0))
     )
  {boReturn=true;}

  //If BOTH on, or BOTH OFF, then it should be green switch pressed, and
  //only green switch...
  if (
     ((bToDecode==B11)||(bToDecode==B00))
     &&
     ((!boStateOfYel1)&&(boStateOfGrn0))
     )
  {boReturn=true;}

  return boReturn;
}

long BottomOfMillis()
/*You don't have to understand everything here...
But the more you master, the better off you will
be.

The MAIN thing to understand is that this function
returns a number that depends on how long it has
been since the processor was last reset. As that
is measured in milliseconds, it is very, very
unlikely that you will get the same number twice
if you call this function after some delay which
has been affected by human reponse times.

This function returns the "bottom part" of whatever
millis is returning at the time the function is
called. What I mean by that, roughly speaking, is....

Millis is an "unsigned long" type datum. Among other
tbings this means that it is made up of many digits.
Slightly distorting things, just so I can discuss this
in decimal terms, lets say that "millis" can be a
number up to 999,999. And lets say that I want a
number of no more than three digits.

The "long()" funtion MAY be chopping unsigned long
numbers down as follows....

123,456 becomes 123
123,987 becomes 123... etc.

I want something more like...
 123,456 becomes 456
 123,987 becomes 987....

... and that, roughly speaking, is what this function
does with the number it fetches from millis.

It uses a bitwise AND to accomplish its task.

The "0xFFFFFF" is just a quick way of writing a
very big... but not too big!... number which
in binary would be all ones. The decimal
"equivalent", in essense, would be a number
consisting of a lot of 9's*/

{
long lngReturn;
lngReturn=(millis()&0xFFFFFF);
return lngReturn;
}

Well! We're almost there! The next listing shows the "all singing, all dancing" variant of the access control solution. It has all the features I was working towards through the stages you have seen.

Now when users see a pattern of LEDs on the device, i.e. they see the "challenge", they must work out their response by a more complex rule. (Or just memorize the correct responses to the different possible challenges.)

The rule is explained in the rems within the program, near the top of the sourcecode. Putting the definition of what will open the door inside the sourcecode, by the way is a Good Idea! The rules may get changed. If they were explained here, in this web document, but implemented within the sourcecode, it would be easy for discrepancies to arise. With the documentation in the same document as the implementation, there is less excuse for discrepancies if they manage to creep in anyway.

With just two LEDs and switches, the device is still not very secure... a burglar has a 1 in 3 chance of getting the answer. But add an LED or two, and a switch for each LED, and the device rapidly becomes hard to defeat. The extensions required to the code would be minimal, and simple.

Oh hell. I thought I was nearly "done" with the first draft of all of this... and then I recalled another feature I promised, which was not in the program at the time I thought I was nearly done.... the "make people wait longer and longer between tries if they enter a wrong response to a challenge".

Okay. If the code is well written, it should not be hard to add.

Here's the plan:

We'll need another global variable. We try to avoid them, but there are times... like this... when it is reasonable to use them... carefully.

The global variable I'm going to create will be called bFailuresInARow.

It will, during "setup()" be initialized at zero.

Each time a user enters a wrong response, the number in bFailuresInARow will be increased by 1.

Each time a user enters a correct response, bFailuresInARow will be reset back to zero.

During "SayFailed()", how long the LEDs flash rapidly will be affected by the number in bFailuresInARow.

Easy! I hope it is obvious to you where, i.e. in what functions, those "ingredients" needed to be added? Having a well structured program makes doing alterations easy!

(Even with an excursion onto a tangent, doing proper in-line rems, and tweaking the values... oh yes, and making a big boo-boo and having to undo that, and do properly what I needed to do... adding the feature took 20 minutes. The "boo-boo" was making bFailuresInARow alter the length of each flash instead of altering the number of flashes done during "SayFailed()".)

Just before we get to the sourcecode for the current version of the final stage of this project's development, a note about an important issue: Testing.

Once I thought this was "working", I drew up a little grid. I had a row for each of the possible challenges, and a column for each of the possible responses. I drew a box around the cell for the RIGHT response for each challenge. I then "played" with the device... making sure that every one of the nine possible combinations of challenge and response gave the expected result. And that they did so more than just once.

I didn't do that because I was being good... I did it because the program wasn't, at that point, working. I thought that the grid might help me find where the fault lay.

In the end, the grid didn't help... although I DID do proper testing later, to be "good".

The reason I couldn't find the bug was that I'd gone too far, too fast. I'd done all of....

(STUFF TO INSERT HERE.... SORRY... MAYBE
YOU CAN INFER WHAT....
WILL TRY TO GET THIS EDITED UP
FOR YOU... BUT IT WON'T BE SOON.
REMIND ME ANY TIME AFTER JAN 2010)

... without testing the individual parts. I'd failed to proceed carefully though bottom up development.

I had to go back a few steps, write code to check each of the following individually, by writing messages to the serial monitor.

It was only then that I noticed that I'd written...

if (bToDecode&B10==B10)...

instead of...

if ((bToDecode&B10)==B10)...

The extra brackets force the software to do the (bToDecode&B10) operation first, which results in a number. THEN the software asks if that number is the same as "B10", which, I remind you, is just binary "10", i.e. decimal 2.

Previously, it was probably following some rule which said "ask if B10 is the same as B10, and then do a bitwise and between the answer (true or false) to what we did first, and interpret that answer as a "true or false". Don't worry too much about what all that might mean. Just learn that a few parentheses to ensure things happen in YOUR order, not the machine's, can cure all sorts of things.

Oh! There's another little "gotcha" lurking in ifs that I might as well mention here....

You may well one day type....

if (bTmp=5)....

... when you meant to type....

if (bTmp==5)....

(The latter is much more likely in a Good Program. The former means SOMETHING, but what it means isn't as clear as what we have in the second line.)

The "gotcha" is that the Arduino compiler won't complain about the version with just one equals sign... but it won't work the way you might have meant it to work. When you want the "if..." statement to do something if what's in the variable bTmp is a 5, then write the line with TWO equals signs. Sigh.

And one last little "By the way...": Think that what you see below is a "big" program for a "little" Arduino? I bought some "old stock" from ModernDevice.com in 6/09, but it was still new enough to have the Atmega 328.. and the following code takes about 2800 bytes out of the 30,000 or so that is available. (I.e., roughly speaking, a program ten times bigger than this would fit in my little Arduino.

The following has already been published by me in another page, the "skip directly to answer" page.

/*FEAa1AccContFinal: access control system-  All singing, all dancing
ver 1 Jan 2010
Final Stage edition of AccCont started 30 Dec 2009

Written during final stages of a nasty cold, and
while strained back muscles (snow shovelling) were
healing. << (I often include such "pointless" odds
and ends here, because it is, to me, fun to be
reminded of things later.)

The VERSION is the version of this STAGE in the development
of the program "FEAa1AccCont". This potentially confusing
matter of "version" versus "stage" doesn't usually occur
in my work, as early "stages" are overwritten as the programming
progresses. It was only because FEAa1AccCont was being used
as the basis of a tutorial for....

http://sheepdogguides.com/

... that the need to introduce "stages" as well as "versions"
arose.

==============================

This program....

Requires 3 LEDs, two switches.
One of the LEDs is standing in for an electromechanical strikeplate

Two of the LEDs present a "challenge" to the user
If the user responds by holding down the correct
combination of switches for a short while, the
"lock" "opens" for a time, i.e. the LED standing
for the lock switches on. (That LED could be
replaced with an electro-mechanical strikeplate
very easily... and with no changes to the software.)

The lock doesn't open WHEN the user presses the right
combination on the switches... that would make trial and
error too easy. Rather, the software looks at the switches
at a pre-determined moment, and if they are properly
set at that moment, the door opens... or at least the "lock" LED turns
on for a set period.

The correct answer will be as follows....

If LEDs show:         Then press (x) or don't press (-)....
Left LED Right LED        Left button Right button
off      off          -   -  (See futher notes below later)
off      on           *   *
on       off          *   -
on       on           -   *

You don't have to memorize the table above. To know what
buttons to press, use the following rule....

a) Complement the LED pattern. (That means that if an
     LED is on, then in the complement it is off.
b) Treat what you have as a binary number.
c) Add one to that binary number.
d) Throw away any extra digit created by a "carry".

Here are all four cases done for you by
those steps....

11 Starting with both LEDs on...
00 The complement
01 The result of adding 1

01 Starting with left off, right on...
10 The complement
11 The result of adding 1

10 Starting with left off, right on...
01 The complement
10 The result of adding 1

00 Starting with both LEDs off...
11 The complement
Adding 1 would give you 100.
Throw away the carried digit... leaves you with...
00... so press NEITHER key to answer the "off/off" challenge.
(the program does not offer this challenge for two reasons...
    a) The device would look "off" if presenting zero as
          the challenge
    b) Users would have to remember to press AND QUICKLY
          RELEASE a button to answer the "zero" challenge.
          Without the "press and release", the countdown
          to "check buttons now" would never start.

So, in the version presented here, there are only three
challenges... making solution by trial and error almost
possible. But it would be easy to add more LEDs, more
buttons, which would making many more challenges to find
answers for.

(The exclusion the "zero" challenge was added between
the 4th stage of the program's development and the
final stage. The change was trivial,
because the program was well structured.
Can you find where the change was made?)
*/

const byte LEDlock=13;//Pin where lock would be connected.
const byte LED1=12;//LED1 connected here (MS bit)... left hand LED
const byte LED0=11;//LED0 connected here (LS bit)

const byte bYel1=5;//pin yellow switch is on... left hand switch
const byte bGrn0=4;//pin green switch is on
// N.B.: "Yel1" is "wye-ee-ell-one" for YELlow switch, bit ONE

byte bNumToShow;
byte bNumShownPreviously=0;
/*This variable above is part of the mechanism to ensure that
the same challenge never arises twice in a row. The variable
must be initialized, or there will be unpredictable results
the first time @if (bNumToShow==bNumShownPreviously)" is
encountered. As we we will never (see other) present the
challenge defined by bNumToShow=0, we can safely initialize
bNumShownPreviously to zero. (Even if it were initialized to
something else, the code would "work", with the tiny "flaw"
that the first challenge presented would never be whatever
bNumShownPreviously was initialized to.*/

byte bFailuresInARow=0;

void setup()
{
   //Serial.begin(9600);//for debug work only
   pinMode(LEDlock,OUTPUT);
   pinMode(LED0,OUTPUT);
   pinMode(LED1,OUTPUT);

   pinMode(bGrn0,INPUT);
   pinMode(bYel1,INPUT);

   LockDoor();

   bFailuresInARow=0;
   }

void loop()
{
  bNumToShow=bPickAChallenge(1,3,bNumShownPreviously);
 /*The previous line originally simply @bNumToShow=random(1,4)"... which
        set bNumToShow to 1,2 or 3.
    Don't include zero in the things that can be presented as
        the challenge, i.e. always use 1 or more for the first parameter.
        (See notes in header for the reasons for avoiding zero.)*/
  bNumShownPreviously=bNumToShow;/*Update contents of bNumShownPreviously,
      ready for the next time we need to know what was most resently shown*/

  showInBinary(bNumToShow);//Show it on LEDs.

  while ((!boSwitchPressed(bYel1))&&(!boSwitchPressed(bGrn0)))
     {;};//Wait for a switch to be pressed

  delay(600);//User must finish getting right buttons pressed during this delay

  /*Beware: If you think the program "isn't working", be careful that you are
  keeping the buttons of the correct response down long enough. You must keep
  them down until the access control device either says "Right" or "Wrong".
  If you enter the CORRECT code, but release the buttons too soon, your answer
  will still be treated as WRONG.
  */

  if (boCorrectButtonsPressedNow(bNumToShow))
       {SaySuccessAndOpenDoorDuringMessage();}
          else {SayFailed();};

  randomSeed(BottomOfMillis());//Only NEEDED once, but it does no
  //harm to do it every time we pass through this loop.

}//end of function "loop"

boolean boSwitchPressed(byte bWhichSwitch)
//In this version, the function does NOT wait for the switch
//to be released.
  {
   boolean boAns=false;//What is in this will eventually be passed
     //back to the calling code. Assume switch NOT pressed,
     //for the moment.
   if (digitalRead(bWhichSwitch)==LOW)
     {
        delay(20);//Wait for bounces to pass
        boAns=true;
     }//end of "if..."
     return boAns;
  }//end of boSwitchPressed

void SayFailed()
{
   word wHowManyFlashCycles=6;
   //Th following multiple @if@s are a bit crude..
   //but they Do The Job!
   if (bFailuresInARow>0) {wHowManyFlashCycles=12;};
   if (bFailuresInARow>1) {wHowManyFlashCycles=40;};
   if (bFailuresInARow>3) {wHowManyFlashCycles=60;};
   if (bFailuresInARow>5) {wHowManyFlashCycles=100;};
   //(Could be extended)
   FlashLEDs(wHowManyFlashCycles,60);
   if (bFailuresInARow<255) bFailuresInARow++;
   //Don't do the "add 1" to bFailuresInARow if it is 255,
   //because you will get overflow. A byte-type variable
   //can't hold 256.
}//end of SayFailed

void SaySuccessAndOpenDoorDuringMessage()
//That name looks silly... but it will appear only
//once, and the extra information will help you
//be clear about what is done inside the function.

{
  UnlockDoor();
  FlashLEDs(6,200);
  LockDoor();
  bFailuresInARow=0;
}//end of SaySuccess...

void setLEDs(byte bL1, byte bL0)
//Easily extended for more LEDs
{
   if (bL0==0) digitalWrite(LED0,LOW);
               else  digitalWrite(LED0,HIGH);
   if (bL1==0) digitalWrite(LED1,LOW);
               else  digitalWrite(LED1,HIGH);
}//end of "setLEDs"

void FlashLEDs(byte bReps,byte bRate)
//Call this a number of times to cause the LEDs to
//flash on-off-on-off. The number in bRate controls
//how fast the LEDs flash. The number in bReps
//determines how many flash cycles happen.
{
  for (int i=0;i<bReps;i++)
   {
      setLEDs(1,1);
      delay(bRate);
      setLEDs(0,0);
      delay(bRate);
   }//end of "for..."
}//end of FlashLEDs

void LockDoor()
//Simple at the moment... but it is
//still worth "packaging" this in its
//own function, in case the program
//has to be adapted for an unusual
//locking device.
{
digitalWrite(LEDlock,LOW);
}

void UnlockDoor()
//See LokDoor rems.
{
digitalWrite(LEDlock,HIGH);
}

void showInBinary(byte bNum)
/*Set LED1 on or off depending on
left hand digit in binary representation
of number in bNum...

And set LED0 on or off depending on
right hand digit in binary representation
of number in bNum.

For debug: send what's in bNum to the serial monitor,
    so we can check that what the LEDs are showing is right...
(The int() stuff around bNum converts it from byte type
   data, which println can't handle, to an equivalent
   int type datum, which println IS okay with.)
*/
{
//Serial.println(int(bNum));

//Set LED1 on or off...
if ((bNum & B10)==B10)
  {digitalWrite(LED1,HIGH);}
  else
  {digitalWrite(LED1,LOW);};

//Set LED0 on or off...
  if ((bNum & 1)==1)
  {digitalWrite(LED0,HIGH);}
  else
  {digitalWrite(LED0,LOW);};
}//end of showInBinary

boolean boCorrectButtonsPressedNow(byte bToDecode)
/*Don't overlook the "bo" at the start of the name.
This function is asking "ARE the correct buttons
pressed now?" It isn't, for instance, establishing
a state in which the right buttons ARE pressed.

The byte in bToDecode is what is showing on
the LEDs. The user must apply whatever rule the
access control system is applying, and press
the switches accordingly.*/
{
  boolean boReturn=false;//Assume for the time being that
    //user has NOT pressed the right switches.

  //Fetch and record the current state of the switches...
  //Yellow switch is left hand switch.
  boolean boStateOfYel1;
      boStateOfYel1=boSwitchPressed(bYel1);
      //"true" means switch is closed
  boolean boStateOfGrn0;//Create variable
      boStateOfGrn0=boSwitchPressed(bGrn0);//Fill it

  //Put the state of the LEDs into easily accessed booleans...
  boolean boStateOfLED1;
     if ((bToDecode&B10)==B10)
       {boStateOfLED1=true;}
         else {boStateOfLED1=false;}
  //"true" means LED on

  boolean boStateOfLED0;
     if ((bToDecode&1)==1)
       {boStateOfLED0=true;}
         else {boStateOfLED0=false;}

 /*Followiong for debug...

Serial.print(int(bToDecode));
Serial.print("  ");
serialdb("Yel",boStateOfYel1);
serialdb("Grn",boStateOfGrn0);
serialdb("LED1",boStateOfLED1);
serialdb("LED0",boStateOfLED0);
Serial.println("");


... end of debug lines*/


  /*Now look at various "correct" answers, and if any
  are present change boReturn to true.

  We could write some "clever" code to calculate the
  correct answers.... but I'm not clever enough to read
  that reliably, so I'm just going to do the job
  "the hard way".

  In each of the following blocks, the rem a the
  top will explain the LED challenge and the
  correct response with the following code:

  "off on  * *"

  ... will mean if LEDs are "off" and "off", in that
  order, then the correct responmse is: both buttons pressed.*/

//off      on           *   *
  if (
     ((!boStateOfLED1)&&(boStateOfLED0))
     &&
     ((boStateOfYel1)&&(boStateOfGrn0))
     )
      {boReturn=true;};

//on       off          *   -
  if (
     ((boStateOfLED1)&&(!boStateOfLED0))
     &&
     ((boStateOfYel1)&&(!boStateOfGrn0))
     )
      {boReturn=true;};

//on       on           -   *
  if (
     ((boStateOfLED1)&&(boStateOfLED0))
     &&
     ((!boStateOfYel1)&&(boStateOfGrn0))
     )
       {boReturn=true;};

//off      off: This challenge will not be issued.

  return boReturn;
}//end of boCorrectButtonsPressedNow

void serialdb(char sPrompt[8],boolean boVal)
{
Serial.print(sPrompt);
if (boVal)
 {Serial.print(":true   ");}
    else {Serial.print(":false  ");};
}

long BottomOfMillis()
/*You don't have to understand everything here...
But the more you master, the better off you will
be.

The MAIN thing to understand is that this function
returns a number that depends on how long it has
been since the processor was last reset. As that
is measured in milliseconds, it is very, very
unlikely that you will get the same number twice
if you call this function after some delay which
has been affected by human reponse times.

This function returns the "bottom part" of whatever
millis is returning at the time the function is
called. What I mean by that, roughly speaking, is....

Millis is an "unsigned long" type datum. Among other
tbings this means that it is made up of many digits.
Slightly distorting things, just so I can discuss this
in decimal terms, lets say that "millis" can be a
number up to 999,999. And lets say that I want a
number of no more than three digits.

The "long()" funtion MAY be chopping unsigned long
numbers down as follows....

123,456 becomes 123
123,987 becomes 123... etc.

I want something more like...
 123,456 becomes 456
 123,987 becomes 987....

... and that, roughly speaking, is what this function
does with the number it fetches from millis.

It uses a bitwise AND to accomplish its task.

The "0xFFFFFF" is just a quick way of writing a
very big... but not too big!... number which
in binary would be all ones. The decimal
"equivalent", in essense, would be a number
consisting of a lot of 9's*/

{
long lngReturn;
lngReturn=(millis()&0xFFFFFF);
return lngReturn;
}//end of BottomOfMillis

byte bPickAChallenge(byte bLowInc, byte bHiInc, byte bPrev)
/*Picks a number in the range bLowInc to bHighInc, INCLUSIVE...
i.e. bPickAChallenge(1,3,0) will pick either 1,2 or 3.

Note that this is slightly different from what the Ardino's
built-in "random" does. "random(1,3)" will only return 1 or 2,
not 3.

The last parameter defines a number that may NOT be returned.
It is used to provide a mechanism, which in conjunction with
other code, allows the programmer to ensure that users do not
see the same challenge twice in a row.*/

{
byte bTmp;
bHiInc=bHiInc+1;//to allow for the different range setting
  //conventions of my bPickAChallenge and the Arduino's "random()";
do {bTmp=random(bLowInc,bHiInc);} while(bTmp==bPrev);
  return bTmp;
}//end of bPickAChallenge

And that's it!!!

Enjoy... while, please, remembering what I said about the pages about the development of the access control device being only in rough draft so far.




   Search this site or the web      powered by FreeFind

Site search Web search
Site Map    What's New    Search

The search engine is not intelligent. It merely seeks the words you specify. It will not do anything sensible with "What does the 'could not compile' error mean?" It will just return references to pages with "what", "does", "could", "not".... etc.

SPELL your search term properly. When I review search logs, it is amazing how many people ask the engine to search for something meaningless.


Why does this site cause a script to run? I have my web-traffic monitored for me by eXTReMe tracker. They offer a free tracker. If you want to try it, check out their site. And if there are Google ads on the page, they are run with scripts, too.


Click here to return to Arduino COURSE table of contents.
Click here to go to the author's home page.

Ad from page's editor: Yes.. I do enjoy compiling these things for you... hope they are helpful. However.. this doesn't pay my bills!!! If you find this stuff useful, (and you run an MS-DOS or Windows PC) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!
Click here to visit editor's freeware, shareware page.


Here is how you can contact this page's editor. This page, and the software it references, ©TK Boyd, 1/2010.

Valid HTML 4.01 Transitional Page WILL BE tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org