HOME - - - - - - - - Table of contents, my Arduino "How To" articles
Other material for programmers     

An Arduino Pulse Counter- useful in own right

.. but also as a lesson in Good Design/ Programming

filename: aht2sw-counter.htm

I have a big perpetual motion machine pendulum, Arduino powered, of course, as a kinetic sculpture.

I decided to make a clock with it, and the Arduino software presented here was the first stage of that project.

The software counts the swings of the pendulum as time goes by. It can count pretty well anything you want it to... its usefulness doesn't end with pendulums! I'd love to hear of any projects you put it to work in.

But, my wants today: A count of pendulum swings. Once I have that, I'm pretty sure that turning the count into a time-of-day, hh:mm, clock will be straight-forward.

The counter here is one "just" done in software for simple, low frequency pulse trains... say 0.5 hz (up to two pulses a second. Probably good for faster, but not, say, audio frequencies.)

This tutorial stops once the basics have been covered. Feel "cheated"? Not a "proper clock"? Well, for you I did an extension of this tutorial, with a more "ordinary", "finished" digital clock, regulated by the old fashioned pendulum. But I recommend that you start here, if you want to understand the programming. What is here is the basis of what is there.

Much fancier "counter" solutions are possible, of course. I've written a guide to using the counter in the ATmega chip, too.

A little more detail

I have a pendulum. There's a magnet on the end of it. There's an electromagnet just underneath where the pendulum swings. And some reed switches which the magnet on the pendulum close for a moment as the pendulum swings past them. And finally, an Arduino which, based on when the reed switches are closed, briefly energizes the electromagnet at just the right moment to give the pendulum a little push once in every swing. There are LEDs which come on when the reed switches close.

That was all there before I started my counter/ clock project.

I'm going to tap into that system to use one of the reed-switch-driven LEDs as the "sensor" for my swings counter.

I am going to use one of Sparkfun's nice serially driven 4 digit 7 segment LED displays to display the count of pendulum swings. You can do without this... just change any "SevenSegLED.print(" lines to "Serial.println(", and look on the Arduino IDS's serial monitor for the output.

But this is more than just the code for a counter

You are, as I said, very welcome to just take the code, use it as the basis for something you are doing.

But I hope some readers will fetch the code, read through it, just to see examples of things that I find help me get good solutions done quickly. Examples of "good programming".

Let's have a look at the loop() subroutine...

void loop() {
int iToDisplay;

if (boWaitingForDetected)//============ then ===== 1
{
  if (boPendInFrontOfSensor())//======= then ===== 2
  //now in HAS BEEN detected. Stay here 'til NOT
  //  detected
  {
    LEDOn(pPendInFrontOfLED,true);
    do//============================= do/while === 3
    {
      delay(5);
      } while (boPendInFrontOfSensor());
      boWaitingForDetected=false;
      //end of do... while loop------------------- 3
  }//no ; here//----- end of then/ else starts === 2
  else//Pend no longer in front of sensor
  {
  }//end of else of if (boPendInFrontOfSensor())
}//no ; here//------- end of then/ else starts === 1
else //do the things that should be done once,
//going into has been detected, which then sets us
//back to waiting for detected....
{
  LEDOn(pPendInFrontOfLED,false);
  ulPulsesFrmPendulum++;
  iToDisplay=ulPulsesFrmPendulum % 10000;
  SevenSegLED.print("v");//To reset display module
  delay(50);
  SevenSegLED.print(iToDisplay);
  //Jobs done... now go back to...
  boWaitingForDetected=true;
}//end of else of if (boWaitingForDetected)

}//end of loop()

The program creates a very simple "state machine".

It only has two states... "WaitingForDetected" and "Not WaitingForDetected".

"WaitingForDetected" is a somewhat compressed "The machine is waiting for a time when the pendulum has been detected in front of the sensor which "sees" it when it is at a particular stage in it's cycle.

boWaitingForDetected is a variable, which is managed by the software, to put the whole system "in" the right "state".

It is initialized as "true" during setup()... because that works. (A feeble "explanation"... but it is true!)

While the "machine" is in the "waiting" state, it constantly checks the sensor... has the pendulum been detected yet? See how what is going on in the program can be explained with nearly "natural" English, when the program is written with that as one of the design goals?

HOW does it "watch" for the "pendulum"? At this level in the discussion, I don't care! That's why I "packaged" that detail in the "boPendInFrontOfSensor()" function... a Boolean function, i.e. a function which "returns" either true or false.

"boPendInFrontOfSensor()": (BOoolean: Is the PENDulumn IN FRONT OF the SENSOR? (i.e. the reed switch).

We'll come back to the details of the code for that.. and where to find those details... later.

I hope that already you can see easily that if we were in the "waiting for (the pendulum being in front of the detector to be) detected state, that a soon as the pendulum is detected, we then enter a tight loop until it is no longer detected.

At this point, the state machine "changes state"... well, the value in the variable controlling the state (boWaitingForDetected) is changed, and nothing else changes until we start though the loop() subroutine again, at which time, we quickly head down a different path... the "machine" is in a new state, dealing with what it should while in that state. (To quote the relevant part of the loop() code, from, above:)

  LEDOn(pPendInFrontOfLED,false);
  ulPulsesFrmPendulum++;
  iToDisplay=ulPulsesFrmPendulum % 10000;
  SevenSegLED.print("v");//To reset display module
  delay(50);
  SevenSegLED.print(iToDisplay);
  //Jobs done... now go back to...
  boWaitingForDetected=true;

Note that, due to the last line, we are only in this other state briefly, once per detection of the pendulum. It doesn't matter that we are here briefly. We are "here", this is a different state from the system's usual state. We do have a (very simple) state machine.

Some details, in case there were worries...

In those 8 lines...

  LEDOn(pPendInFrontOfLED,false);

Turns off an LED set up to confirm detection of pendulum is being been sensed... when that is the case!

  ulPulsesFrmPendulum++;

This is the heart of the program. ulPulsesFrmPendulum is the count of pendulum swings. The "++" adds 1 to whatever's in the variable before this line is executed.

  iToDisplay=ulPulsesFrmPendulum % 10000;
  SevenSegLED.print("v");//To reset display module
  delay(50);
  SevenSegLED.print(iToDisplay);

iToDisplay is a "scratch" variable. It is filled with what you get if you cut the value in ulPulsesFrmPendulum down to just four digits. (123456 would turn into 3456, for instance) (the "cutting down" is done by the "% 1000". Search on "modulo" for the details.)

Why cut it down? Because the display I am using can only show 4 digits.

SevenSegLED.print("v"); is a code to "reset" the display I am using. Doing that before doing the SevenSegLED.print(iToDisplay) means that digits from previous reports aren't left behind, as might be the case when the count is below 999. (The delay(50) is to give the display time to do the reset which has been requested.)

So! That's "all"?

So. That's all that's needed for a counter program? Well.... yes and now.

That's the heart of it!

A few things have to be done during setup().

And, somewhere, we need the details of two subroutines:

1) the boolean function "boPendInFrontOfSensor()", and

2) the simple procedure "LEDOn", which has two parameters. The first allows the user to say WHICH (of several) LED should be turned on. And the second makes it possible to use LEDOn both for turning LEDs on and for turning them Off. (The second parameter is a boolean. Send "true", and LEDOn turns the LED on. It is "true" that (the) LED (is) On, get it? Send false, and the LED goes off. "False" that it is "on".)

Details of boPendInFrontOfSensor()

Here are the details of boPendInFrontOfSensor().

Yes- they had to be in the program somewhere... but would it have been helpful to have all this "stuff" in the main body of the code, in what was in the code for loop()? No! At that level, all you want to know is that there's a function you can call that will return "true" if the pendulum is currently being "seen" by the pendulum sensor.

boolean boPendInFrontOfSensor()
{
  if ((digitalRead(pPendulumSensorButton)==LOW) ||
    (digitalRead(pPendulumSensor)==HIGH)
   )
  {return true;}// no ; here
  else
  {return false;};
};//end of boPendInFrontOfSensor()

Hmmm... what's this about digitalRead(pPendulumSensor), and reading pPendulumSensorButton?

No big deal... and it would have been an unnecessary distraction, if all this appeared in loop. Also, FINDING it, if we WANTED to look at the details would not have been so easy, before we "broke it out", put it in the function.

The reason for pPendulumSensor and pPendulumSensorButton is that I set up a button which, when pressed, because of this code, simulates the pendulum being in front of the sensor. It made development easier.

And here's the code for the LEDOn procedure:

void LEDOn(byte bWhich,boolean boOn)
//call LEDOn(3,true) and the LED on pin 3 will be turned on.
//call LEDOn(3,false) to turn it off.
{
  if (boOn)
    {digitalWrite(bWhich,HIGH);}//no ; here
    else
    {digitalWrite(bWhich,LOW);};
}//end LEDOn

Again, we gain clarity by "hiving this off", "out from underfoot" in the places where we want to turn an LED on or off.

There's another advantage here.

We could, of course, simply put....

  digitalWrite(9,HIGH);

.. into the code at any place we wanted to turn on the "Pendulum(in front of)sensor LED". (Assuming that the LED for that message is on line 9.. and that we remember which line it is on. And that "HIGH" makes it on, LOW makes it off. (Some things work the other way around. LEDs can, if you wire them a way few people do.)

But which is most immediately clear:...
"LEDOn(pPendulumSensor,true)", or
"digitalWrite(9,HIGH)"?

There's another thing going on here.

If you use digitalWrite(9,HIGH), and subsequently decide you want THAT LED on pin 7, you need to find all of the references to 9, change them. And while in theory that's easy with search and replace, it often goes wrong.

If somewhere you have assigned "9" to pPendulumSensor, then you only have to change that, and the effect goes to all the places it should, and no place else. Using "const" is a great way to do that. I'm told "const" is better than "define".

What were the lessons?

So: For "good" programs...

"Parcel things up" into subroutines. Use variables with meaningful names.

Re-arrange your code

If you look at the sourcecode for this app, you won't immediately see the two bits of code I've just quoted. You won't see the details of boPendInFrontOfSensor()" or "LEDOn" on the same page as the core routines, setup() and loop().

The bits of code with the details of the subroutines are on separate tabs. (I use the term here in the sense of a "tabbed" dialog. I an not using it in the text layout sense, where if you press the "tab" key, on the keyboard, a non-visible character, Ascii(9), is inserted to insert a variable horizontal space, to make columns of material look nice on the page.

tabs for sourcecode management, Arduino IDE

The image at the left shows our project in the IDE. We are looking at the main page of the code, the one with const declarations, global variables, loop(), setup(). (And not a lot else, at this point). It is the only tab in the IDE when you are getting started with Arduino.

But if you look above the three arrows sharing "OTs", which stands for "Other TabS", you will see three additional tabs. ("SevenSegLED", "Support_app_specific", and "Support_generally_Useful"). I added these tabs myself.

It is easy to create a new tab. You merely click the button at the right, the one I've labeled "MT" (Make Tab).

How to use a tab

A tab is a place to put code. A place to put it where it won't be "underfoot".

If you "get clever", you can tie things up in knots. If you want to be clever, you can do fancy things with tabs.

As long as you don't add extensions when you name a tab, things will stay simple. Start tab names with a letter. Use more letters and/or digits, and maybe the odd under_score to finish the name. Whatever you do, don't put a period (full stop) in the name. That would "add an extension". If you have followed my advice, when you save the application, you will get extra .ino files... one for each of the tabs, each named with whatever name you gave the tab. (My app was saved, originally, as "LazyNamedClcok-18c14". So it should live in a folder called "LazyNamedClcok-18c14", and there will be a "LazyNamedClcok-18c14.ino" file in that folder. (The ".ino" part is an extension. When I added the "SevenSegLED" tab, a "SevenSegLED.ino" file was added to the folder. There will be a separate file for each of the tabs.

All of which you can mostly ignore!

You have two things to remember...

1) When you start a new subroutine, do at least the skeleton of it on the main tab, at first. Once you have made a start, and it compiles and runs, then simply copy/paste the whole thing from the main tab onto one of the other tabs. Before you try to run it, though, near the top of the code on the main tab, put a copy of the first line of the subroutine's code. For instance, the relevant bit for the LEDOn subroutine is...

void LEDOn(byte bWhich,boolean boOn)

I generally preface these "forward declarations" with a comment line saying something like...

//Forward declarations for subroutines held in tabs

That's it! Simple! And it makes your code less messy, so you see mistakes more easily. Needles hide more easily in haystacks than they do in the middle of a large sheet of white paper/

Sourcecode

The full sourcecode is available for the pulse counter discussed above. It is simply implemented, using software alone, not the internal hardware counters inside the chip at the heart of the Arduino. (See my other tutorial, if you want to use the hardware counters.)





   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.
In addition to the tutorials for which this page serves as Table of Contents, I have other sites with material you might find useful.....

Sequenced set of tutorials on Arduino programming and electronics interfacing.
Tutorials about the free database supplied with Open Office/ Libre Office.

Some pages for programmers.
Using the parallel port of a Windows computer.


If you visit 1&1's site from here, it helps me. They host my website, and I wouldn't put this link up for them if I wasn't happy with their service.




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 Sheepdog Software (tm) freeware, shareware pages.. Material on this page © TK Boyd 12/18


And if you liked that, or want different things, here are some more pages from the editor of these tutorials....

Click here to visit the homepage of my biggest site.

Click here to visit the homepage of Sheepdogsoftware.co.uk. Apologies if the "?FrmAht" I added to that link causes your browser problems. Please let me know, if so?

Click here to visit editor's pages about using computers in Sensing and Control, e.g. weather logging.



To email this page's editor, Tom Boyd.... Editor's email address. Suggestions welcomed!


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


Why does this page cause a script to run? Because of the Google panels, and the code for the search button. Why do I mention the script? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....