Pages: [1]   Go Down
Author Topic: How to get single passes through loop?  (Read 1599 times)
0 Members and 1 Guest are viewing this topic.
Artisan's Asylum, Somerville, MA, USA
Offline Offline
Newbie
*
Karma: 0
Posts: 37
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I am trying to create some blinking light effects using some TLC5940's and dumb RGB strip LED's...

I'm starting with the TLC Basic Use program's "Night-Rider" sequence, with plans to eventually have multiple bars cycling back and forth...

In order to better see what my code is doing, I want to have a push-button that will let me run through the loop code once, then pause until I push the button again, etc.  This should step the LED sequence one time per button push...

I am trying to adapt the basic Arduino Digital library "State Change Detection" code to do this, and am having trouble getting it to work.

The first problem seems to be with my step code - I set up the serial reporting of the button state, and commented out the TLC part, so I ended with the following sketch....  The circuit is mostly the basic setup for two TLC's in series, but only actually using channels 0-23.

I added an N/O microswitch pushbutton between ground and pin 6.  I'm using the internal pullup resistor so the line goes LOW when the button is pushed...

Code:
  // button SWITCH VARIABLE
const int buttonPin = 6;
// int button = 1;
int buttonPushCounter = 0;   // counter for the number of button presses
int buttonState = 0;         // current state of the button
int lastButtonState = 1;     // previous state of the button


// Use TLC library
#include "Tlc5940.h"

// Define number of chanels
const int chan_no = 24;

/// START SETUP /////////////////////////////

void setup() {
 
  // button SWITCH SETUP - NOTE switch ON = 0
  pinMode(buttonPin,INPUT);
  digitalWrite(buttonPin, HIGH);
//   button = digitalRead(buttonPin);

  // initialize serial communication at 9600 bits per second
  Serial.begin(9600);
  delay(1000);
 
   /* Call Tlc.init() to setup the tlc.
     You can optionally pass an initial PWM value
     (0 - 4095) for all channels.*/
  Tlc.init();
}

 // Direction - channel number increment / decrement selector.
  // 1 increments, -1 decrements
 
  // start w/ incrementing
 
  int direction = 1;


void loop() {
 
    // read the pushbutton input pin:
  buttonState = digitalRead(buttonPin);

  // compare the buttonState to its previous state
  if (buttonState != lastButtonState) {
    // if the state has changed, change the counter
    if (buttonState == LOW) {
      // if the current state is HIGH then the button
      // went from off to on: increment the counter
      buttonPushCounter++;
      }
    else {
     
      // if the current state is LOW then the button
      // went from on to off: decrement the counter
      buttonPushCounter--;   
    }
  }
  // save the current state as the last state,
  //for next time through the loop
  lastButtonState = buttonState;
 
  Serial.print ("buttonState =  ");
        Serial.print (buttonState);
        Serial.print (" ButtonPushCounter = ");
        Serial.println (buttonPushCounter);


   delay (1000);
//  button = digitalRead (buttonPin);
     if ( 1 == buttonPushCounter) {
       // step to next LED ???  (may not be in best place)
     }
   }   

What I find is that instead of just getting a momentary change in the counter when I push the button, (just enough to trigger a pass through the rest of the code) and then returning to the non-pushed state, and not changing at all when I release the button, I get the serial output follows the state of the button - when not pushed the buttonState is low (which it should be) and the counter is 0,  and both states reverse when I push the button and stay that way until I release it...


Sample output....
Code:
.... // button not pushed
buttonState =  1 ButtonPushCounter = 0
buttonState =  1 ButtonPushCounter = 0 // button pushed
buttonState =  0 ButtonPushCounter = 1
buttonState =  0 ButtonPushCounter = 1
buttonState =  0 ButtonPushCounter = 1
buttonState =  0 ButtonPushCounter = 1
buttonState =  0 ButtonPushCounter = 1
buttonState =  0 ButtonPushCounter = 1
buttonState =  1 ButtonPushCounter = 0 // button released
buttonState =  1 ButtonPushCounter = 0
buttonState =  1 ButtonPushCounter = 0

What is the correct way to arrange the code so that each button push (but NOT a release) will trigger a single pass through the rest of the code?

Thanks,
ex-Gooserider
Logged

London
Offline Offline
Edison Member
*
Karma: 46
Posts: 1368
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Try removing the else{...} statement.
Logged

Manchester (England England)
Online Online
Brattain Member
*****
Karma: 603
Posts: 33416
Solder is electric glue
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Your code bocks are wrong. You only need to do the printing stuff on a change. Put the cursor after the { and you will see the matching } hi lighted.
You will see this is not the one you think it is.
Logged

Artisan's Asylum, Somerville, MA, USA
Offline Offline
Newbie
*
Karma: 0
Posts: 37
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Again a long delay, while I got the hardware side working better...  In addition, I did a bit of a rethink on this, and think I've come up with a better solution that will work for any time this sort of single pass thing is needed...

It has the advantage (I think, but haven't verified yet) that it will be easy to trim out once the development is done and you want to get to running the actual programs...

Essentially my new approach is rather than trying to stop the code while in the loop function, I have created a separate function that sits and loops until the "step" button is pushed, then returns to the main loop for one pass until it's called again at the end of the loop...

A first draft sketch - I've got some calls to the TLC library in it, but don't actually do anything with it - that's next...  smiley-roll-sweat

I also have a lot of serial print functions to show it working, but will probably comment them out once I start doing other code.

Code:
const int buttonPin = 4; // N/O push button attached to any pin not otherwise needed
int buttonPush = 1;   // State of button - goes low when pushed, then returns to high
int buttonState = 0;         // current state of the button
int lastButtonState = 1;     // previous state of the button

// Use TLC library
#include "Tlc5940.h"

// Define number of chanels
const int chan_no = 24;

/////////////////////////////////////////////////////////

void setup() {

  // button SWITCH SETUP - NOTE switch ON = 0
  pinMode(buttonPin,INPUT);
  digitalWrite(buttonPin, HIGH);

  // initialize serial communication at 9600 bits per second
  Serial.begin(9600);
  delay(1000);

  /* Call Tlc.init() to setup the tlc.
   You can optionally pass an initial PWM value
   (0 - 4095) for all channels.*/
  Tlc.init();
}

// Direction - channel number increment / decrement selector.
// 1 increments, -1 decrements

// start w/ incrementing

int direction = 1;

///////////////////////////////////////////////

void loop() {   

  delay (10);
  Serial.print("In LOOP buttonState = ");
  Serial.println(buttonState);
  Serial.print("buttonPush = ");
  Serial.println(buttonPush);
//////////////////////////////////////////////////////////////////
INSERT TEST CODE HERE!!!!  NOTE, function must be called as last step in any repeating loop!
////////////////////////////////////////////
  //Must read or define button variables in order for function test to reset 
 
  buttonState = digitalRead(buttonPin);
  buttonPush=HIGH;
  stopactFunction ();
}

void stopactFunction () {

  delay (10);
  Serial.print("beginning of stopactFunction buttonState = ");
  Serial.println(buttonState);
  Serial.print("buttonPush = ");
  Serial.println(buttonPush);

  while (HIGH == buttonPush) {  //loop until buttonPush goes low
    delay (10);
    Serial.print("Waiting - buttonState = ");
    Serial.println(buttonState);
    Serial.print("buttonPush = ");
    Serial.println(buttonPush);

    delay (200);
    // read the state of the pushbutton value:
    buttonState = digitalRead(buttonPin);
    // compare the buttonState to its previous state
    if (buttonState != lastButtonState) {
      // if the state has changed, check for button pushed or released
      if (buttonState == LOW) {
        // if the current state is LOW then the button
        // pushed: lower the state and break out of loop
        buttonPush = LOW;
      }
      else {
        // if the current state is HIGH then the button
        // released, make sure the state stays high
        buttonPush = HIGH;   
      }
      lastButtonState = buttonState;
    }
  }
  return;

} // END stopactFunction


Logged

Offline Offline
Faraday Member
**
Karma: 61
Posts: 2879
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

You can't really make loop()  run only once.

What you need to do,  is put the code which does what you want into another function.

Then call that function from loop(),   whenever you want to.   If you don't want to run your function,
loop() will just run over and over again without doing anything,   until your criterion to run the actual
function are met.
Logged

Artisan's Asylum, Somerville, MA, USA
Offline Offline
Newbie
*
Karma: 0
Posts: 37
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

You can't really make loop()  run only once.

I will end up wanting loop to run continuously, but for development, I want it to run one pass at a time - basically so that I can see exactly what it's doing, and be sure that it's what I really want...  If I just let it rip, I can't be sure it's doing what I think it is because stuff is to fast.

What I'm doing right now is just using a push button to break out of the do-nothing loop, which runs the main loop once, and goes back into the do-nothing loop.  This lets me put in serial debug stuff to make sure I am getting the value changes I want, look at the lights to see if they are doing right, and so on.

Quote
What you need to do,  is put the code which does what you want into another function.
What is the difference between doing that, and having another function that does nothing?

Quote
Then call that function from loop(),   whenever you want to.   If you don't want to run your function,
loop() will just run over and over again without doing anything,   until your criterion to run the actual
function are met.
  This seems like essentially doing the same thing I'm doing, just backwards...  I run the code that does what I want in loop, then jump into a "do nothing" function...

The advantage I see is that my method is easier to put back into "normal" operation - just comment out the call to the do nothing function, and the loop runs normally...

 smiley-confuse I may be missing something, but I don't see any advantage to your approach over mine?

ex-Gooserider
Logged

Manchester (England England)
Online Online
Brattain Member
*****
Karma: 603
Posts: 33416
Solder is electric glue
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
  This seems like essentially doing the same thing I'm doing, just backwards...  I run the code that does what I want in loop, then jump into a "do nothing" function...
No it is not the same thing. How are you going to get out of the "do nothing function"?

Quote
but I don't see any advantage to your approach over mine?
And is yours working?
Logged

Artisan's Asylum, Somerville, MA, USA
Offline Offline
Newbie
*
Karma: 0
Posts: 37
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
  This seems like essentially doing the same thing I'm doing, just backwards...  I run the code that does what I want in loop, then jump into a "do nothing" function...
No it is not the same thing. How are you going to get out of the "do nothing function"?

As in the sketch above - I repeatedly read the state of the button, and compare it to the previous state.  If it changes, I check to see if it went from high to low (button pushed) or low to high (button released) - if button pushed, I break out, if released I do nothing.

This takes three variables, "buttonState", "lastButtonState"and "buttonPush".  Button state is the actual status of the button as of the last read.  LastButtonState is the button status on the previous pass, and buttonPush is whether or not the button was pressed (not released)

As long as buttonPush is high, the function loops.  on each loop, it checks for a change in button status.  If it sees one it checks to see whether it was a push or a release.  If it was a push, it lowers buttonPush, which breaks out of the function and returns to the main loop.

In the main loop, the last step before calling the do-nothing function is to set buttonPush back to high, so that the while loop in the function will trigger.

Code:
void stopactFunction () {

  delay (10);
  Serial.print("beginning of stopactFunction buttonState = ");
  Serial.println(buttonState);
  Serial.print("buttonPush = ");
  Serial.println(buttonPush);

  while (HIGH == buttonPush) {  //loop until buttonPush goes low
    delay (10);
    Serial.print("Waiting - buttonState = ");
    Serial.println(buttonState);
    Serial.print("buttonPush = ");
    Serial.println(buttonPush);

    delay (200);
    // read the state of the pushbutton value:
    buttonState = digitalRead(buttonPin);
    // compare the buttonState to its previous state
    if (buttonState != lastButtonState) {
      // if the state has changed, check for button pushed or released
      if (buttonState == LOW) {
        // if the current state is LOW then the button
        // pushed: lower the state and break out of loop
        buttonPush = LOW;
      }
      else {
        // if the current state is HIGH then the button
        // released, make sure the state stays high
        buttonPush = HIGH;   
      }
      lastButtonState = buttonState;
    }
  }
  return;

} // END stopactFunction


Quote
Quote
but I don't see any advantage to your approach over mine?
And is yours working?

I haven't started stuffing code in the main loop other than the serial print stuff I was using to test, but it appears to be working well.  According to the monitor, I get one pass through the main loop, then go into "waiting" until I push the button.  At that point I get another pass, and go back to waiting regardless of whether I hold the button down or not.  Releasing the button does nothing. 

The only action that isn't 100% as expected is if I push and release the button repeatedly very fast, in which case I don't reliably catch the later presses.  I'm sure that this is just a problem of the delays that I have in the code keeping the button state change from being seen - which isn't a big deal, I'm sure I could fix it by reducing or eliminating the delays, but I'm not sure I'll bother...

The only other part that I need to watch for is that I place the do-nothing function call in a place where it will execute - the code can't be branching such that it would be skipped over, and if there is an inner loop that keeps the code from reaching the end of the main loop, I need to make sure that the function call is inside that loop.

ex-Gooserider
Logged

Offline Offline
Jr. Member
**
Karma: 0
Posts: 90
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I think you might be better putting a line that waits at the beginning of the main loop, and then put the code to change state in an interrupt.

Something like (this is untested):
Code:
volatile boolean wait = false;

void setup() {
   attachInterrupt(0, interruptFn, RISING);
}

void loop() {
  while (wait); // Will spin here until wait == false
  // do something...
  wait = true;
}

void interruptFn() {
  wait = false;
}


Read the docs on interrupts: http://arduino.cc/en/Reference/AttachInterrupt
Logged

Artisan's Asylum, Somerville, MA, USA
Offline Offline
Newbie
*
Karma: 0
Posts: 37
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Interesting, but may be more fancy than I need - remember that the entire single pass stuff goes away in the finished code. 

This approach may not be the optimal way to go for perfect code, but it appears that it will work well enough to allow my development to be finished...

ex-Gooserider
Logged

London
Offline Offline
Edison Member
*
Karma: 46
Posts: 1368
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Why all the complication?
Code:
while(digitalRead(buttonpin)==HIGH);
delay(2000);
will just sit there until the button is pushed. The delay is there to give you 2 seconds to release the button. You can increase this if you want.
Logged

Sydney, Australia
Offline Offline
Sr. Member
****
Karma: 6
Posts: 397
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Interesting, but may be more fancy than I need - remember that the entire single pass stuff goes away in the finished code. 

This approach may not be the optimal way to go for perfect code, but it appears that it will work well enough to allow my development to be finished...

ex-Gooserider


I think you are making this harder than it needs to be

You take all your code that is going to do the job in the main loop when you go into product and move that out of the loop function and create a new function underneath the loop function (well technically for anything else other than Arduino it should be before the main loop) call this new function -

void TemporaryFunctionOnly ()
{
//code goes here

}


In you main loop() function - all you do is test for a button press which it appears you now understand

If you get a button press you call your temporary function - otherwise do nothing - no delays etc needed

When you are ready to go into production you simply cut and paste all the code from the temporary function and drop it into the loop function - very easy to move backwards and forwards this way

Craig


Logged

Artisan's Asylum, Somerville, MA, USA
Offline Offline
Newbie
*
Karma: 0
Posts: 37
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for the suggestions, but as I mentioned in my post back on the 1st, I have got it working OK....

Henry, I looked at that, and while your idea works, (I think, I didn't test it) it is a balancing act that doesn't really fit...- if the delay is to short you get multiple cycles if the button is still pushed, if to long you have to wait for the delay before it will see the next push.  If I want to see a particular state (I found there were problems when reaching the top and bottom of the channel range in the main code) I might want to cycle the button really fast to skip over the parts that aren't of interest, which I don't think your method would reliably allow...

Craig - I think we are both doing the same thing, just backwards - I put the wait-for-button-push code in the function call, and the do-the-task code in the main loop...   Last step in the main loop is to call the function - which sits until the button is pushed, when it returns and lets the main loop run another cycle.

The only thing that's tricky is to get the function call in the right place if there is an "inner loop" in the main function, in which case you need to put it inside that loop.  Also it might be an issue if you have branching code that jumps around, similar deal...

I also have a bunch of serial print statements in the main code so I can see just what it's doing, but that aren't really needed otherwise.

As it sits, I can run the sketch in "single pas" or "normal" mode just by commenting out the function call.  When I am ready for "production" code, I will just trim out the function call, and delete the function, along with all the diagnostic serial print lines...

IMHO my cleanup is a little easier, but there isn't enough difference to worry about...

ex-Gooserider
Logged

Pages: [1]   Go Up
Jump to: