While loop as a condition for limit switches

I have a program where I am asking several stepper motors to run and perform various actions (like a CNC machine). I have three limit switches for three different motors. In theory, the program will never drive a motor into a switch, but since I am writing each program from scratch, if I mistype or measure a distance incorrectly, a motor will run to far and hit a switch, and probably damage some parts, and at that point, I want it to stop everything.

I already plan on using a open ended statement to get the program to pause and wat for user input:

while (Start==0){
  Start=customKeypad.getKey();
  }
//Program pauses and waits for user to press "1" on the keypad.

So my questions is: Can I wrap the entire program (basically almost everything that happens in the void loop) in a while loop where it will only operate as long as all three limit switches are returning a "HIGH"?

Here is the code:

int LimitP= 2;
int LimitX= 3;
int LimitY= 4;
int buzzer = 5;
char Start=0;
//I have not included all the motor pins, motros set up, lcd etc... in this code to make it cleaner.
void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
pinMode(LimitP, INPUT_PULLUP);
pinMode(LimitX,INPUT_PULLUP);
pinMode(LimitY,INPUT_PULLUP);
pinMode(buzzer,OUTPUT);
delay(1000); //Setuptime
}

void loop() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("READY");
lcd.setCursor(0,10);
lcd.print("Press 1 to start");
Serial.println("Press 1 to start");
  while (Start==0){
  Start=customKeypad.getKey();
  }
//Program pauses and waits for user to press "anykey" on the keypad to start the program. 
//This keeps it from automaticaly running a soon as it has power.

int p=digitalRead(LimitP);
int x=digitalRead(LimitX);
int y=digitalRead(LimitY);
delay(5);

  while(p==HIGH && x==HIGH && y==HIGH){
   int p=digitalRead(LimitX);
   int x=digitalRead(LimitX);
   int y=digitalRead(LimitX);
  //MY CODE FOR MOTORS GOES HERE>>>>>>>>>>
  //>>>>>>>>>>
  // Etc for the entire code>>>>>>>>>>>>>>
  
}
//If the while loop is broken, then a buzzer will soundtwice
//and it will await user input to prevent the code from looping again
tone(buzzer,1900,100);
delay(1000);
noTone(buzzer);
delay(1000);
tone(buzzer,1900,100);
delay(1000);
noTone(buzzer);
delay(1000);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("LIMIT SWITCH HIT");
Serial.println("Press 1 to start");
lcd.setCursor(0,1);
lcd.print("PROGRAM ABORT");
Serial.println("PROGRAM ABORT");
lcd.setCursor(0,2);
lcd.print("Press 1 to start");
Serial.println("Press 1 to start");
while (Start==0){
  Start=customKeypad.getKey();
  }
//Program pauses and waits for user to press "any key" on the keypad.

}

Do you mean the tool must be manually draw away from switch to let program go further?

Why LimitX is so important? thrice counted.
Say, what should happen wenn everything goes good and endswitch not hit?

Please post an example of the code you are suggesting to wrap in a while loop.

The short answer to your question is “no”, but only because I am guessing that your code is not structured to take normal advantage of how loop is meant to be used.

Look at (google)

  arduino blink without delay

and

  arduino two things at once

or

  arduino several thins at once

and also

  arduino finite state machine

and for fun

  arduino traffic lights

You have to think about things a bit differently to monitor switches whilst doing something else with these tiny computers Arduinos are.

a7

1 Like

loop does what its name says: it is loop-ing

Loop is running in cirkles anyway.

Depending how "dangerous" it really is and what consequences an emergency-stop has there are different possabilities to make the machine stop:

using normally closed switches as limit-switches that are directly wired in series with your emergency-stop circuitry that switches all power off.
This can be done by a relay that works in a selfholding mode. The selfholding is only active as long as all switches that should be in normally closed position are closed. The power is only conducted to the machine if the contact is in active closed position. As soon as one of the switches opens the power conducted to the machine is interrupted: emergency-stop

Using normally closed switches ensures that all things like broken wires or loose contacts will be detected as soon as possible. The machine stops.
If you use a normally open switch a broken wire is not detected and in case of an emergency the machine would not stop!
In an emeregncy-stop circuitry all switches are in series to open the circuit as soon as any switch opens or a wire is broken.

This is all done by direct wiring. No software needed at all. This gives the highest reliability.
Even your microcontroller could "hang" and then checking the limit-switches won't work.

Using hardware-interrupts for the limit switches combined with a global variable. The interrupt service-routine (in short ISR) could switch off power by a digitalWrite or setting a flag-variable to true and all your functions in your program check for this global variable beeing false for normal execution

void myFunction() {
  if (emergency == false) {
    //all your code for normal operation
  }
}

You would have to implement this in all while-loops to stop the while loops

while (some condition || emergency == false) {
  // do your thing
}

while (some condition) {
  // do your thing
  if ( || emergency == true) {
    break;
  }
}  
  1. go professional and use the indutrial standard:

using state-machines which means your code has only one loop that is loop-ing
all looping, all repeating is organised with

void loop().

This requires

zero

other loops. Zero for-loops, zero while-loops, zero do-while-loops

This requieres a real different thinking how to make code work.
using state-machines is the industrial standard.

best regards Stefan

2 Likes

Your Halt should cut power directly, bypass the controller that may be code blocked.

You need to learn to code without blocking, what delay() does. You will be able to have independent processes run smoothly interleaved.

A button or matrix of buttons gets a handler task that reads buttons, debounces transitions (contact switch changes are "dirty", may look like 12 taps in 1500 microsecs so we "debounce") and updates button status variable(s, if multiple buttons) for other tasks to read and trigger on.

A led handler may simply read a button's state and output that on the led.

One task should blink led13 as a status light. If it quits or is stuck or uneven or slow, there's a problem. You can't have a status light and continuous-watched button without non-blocking code.

How To Make Multiple Things Happen Together.

Handling Serial without blocking -- contains a simple State Machine example.

Those two lessons are key to writing smooth automation.

Something you could do is sense a cutter approach a stop, slow the feed, stop and not crash? Arduino can watch a dozen digital sensors many times per millisecond.

Thanks everyone. I am a hobbyist and and very new to learning all of this as well so I appreciate all the input. And thank you to GoForSmoke, That article about how to make multiple things happen together was a very good read. I wish more programmers understood how to communicate these concepts in simple laymen terms for us non-engineers :slight_smile:

That is Nick Gammon's blog. Go up one link and see all the basic topics he has covered to the same standard.

The state machine concept is critical to learn, it lets you make one function that you always run do different things depending on what has been done, the process state which may only be to check if a pin changed state, if not then let the next code run and if so then process the input may involve many states to keep the steps short -- non-blocking.

With functions that run over and over, they run as tasks. Share data/messages/triggers between tasks through variables. My button handler updates a status byte that over tasks may read and act upon.

If you write tight and keep all task steps short, you will have no trouble running void loop() averaging over 50KHz to keep response time small. I had a lot of practice before 1990 on single-thread systems. AVRs remind me of those only faster, smaller, cheaper, more accessible. You can think on times scales in micro and milli-seconds.

I worked in a metal fab shop and learned to write code because it was easier than trying to teach a programmer what I knew from design school. That was 1980. I've had a while to put some things in words since.

You have to do to grow the connections, reflexes into your head and typing arms and hands same as if you were to learn piano (guilty) or other instrument. Practice makes proficience.

1 Like

thanks for the link. I am currently breaking in the machine. trial and error, working out bugs, getting my hands oily and wet and dirty but enjoying it immensely, its quite rewarding to see this project take shape and slowly turn into what I wanted it to be.

I'm going to read through Nicks blog when I have time. I figure this version 1.0 is going to be pretty rudimentary but at least it will test my proof of concept before I invest too many thousands of $ in it...lol

1 Like

The non-blocking event-driven approach is like a frame you can put the pieces that run together on. Buttons get a button task, wheels get motor and brakes tasks. As long as the task bugs are worked out (can do in a small sketch) and it doesn't block, it should run. Check for pin/interrupt/name conflicts if not.

It's a very code-flexible approach as tasks are modular and run independent at the void loop() level. Changing a switch to a different sensor, you only need change the handler and if the sensor gets more info then consider adding a process task to handle that as opposed to changing multiple already debugged fast running tasks.

Here is a task to run in any non-blocking sketch you're trying out.
You can use it as a base to build on, take the top comments off but give credit for copy and pasted segments.
Loop Counter tells you about how fast your code will begin to respond to change.

The numbers should open your eyes. With no OS in the way, 16MHz is fast!

// loopCounter.ino 
// add-a-sketch_loop_counter 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Apr 29/2018 by GFS. Compiled on Arduino IDE 1.6.9.
// This sketch counts times that loop has run each second and prints it.
// It uses the void LoopCounter() function that does not block other code.

#define microsInOneSecond 1000000UL

void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Loop Counter, free by GoForSmoke\n" ));
  Serial.println( F( "This sketch counts times that loop has run each second and prints it." ));
}

void LoopCounter() // tells the average response speed of void loop()
{ // inside a function, static variables keep their value from run to run
  static unsigned long count, countStartMicros; // only this function sees these

  count++; // adds 1 to count after any use in an expression, here it just adds 1.
  if ( micros() - countStartMicros >= microsInOneSecond ) // 1 second
  {
    countStartMicros += microsInOneSecond; // for a regular second
    Serial.println( count ); // 32-bit binary into decimal text = many micros
    count = 0; // don't forget to reset the counter 
  }
}

void loop()  // runs over and over, see how often with LoopCounter()
{
  LoopCounter(); // the function runs as a task, the optimizer will inline the code.
}

If you need more pins, a bigger board/chip may be more $ than adding other "pin multiplier" chips like using 2 input shift registers and a load of diodes to read an 8x8 button box using the 4 pin SPI bus.

If you have a socketed Uno (dev board by design) the ATmega328 is the top of an AVR chip family; ATmega48/88/168/328. Each step down halves all 3 memory spaces. 328 has 32k flash, 2k RAM, 1k EEPROM while the 48 as 4k flash, 256b RAM, 128b EEPROM.

Every chip in that line fits the socket and has the other feature pins and ports.
If your project uses less, most do, the code will run on a big enough alternate.

Or use the Uno to program an AVR that doesn't fit the Uno socket that's plugged into a breadboard instead. The Mighty 1284 has 32 IO pins out of 40 pins wide format that is LOL! the same size as the 8088 CPU. It has 16K RAM and 2 serial ports. Before 2020 I got 4 for $5.50 ea from Mouser.

Some things you can't do with AVR's, you can with ARM boards. PJRC sell a 600MHz M4F (FPU) with loads of flash and RAM and features like audio quality DAC. And the board has built-in microSD, a serious process runs a log donit?

1 Like

That first blog should take an hour if that to read but unless you try it out, mess with the code until you have it in long term memory, you don't have it.

You can use blocking code to blink a led and use an interrupt to watch a stop/start button but watch you don't have something else using it.

In 1ms blocked you lose 16000 cpu cycles. A void loop that watches buttons and turns leds on/off and counts and displays loops/sec uses on average 320 cycles per loop.

I hope that you spend as much time developing those beginner intro lessons as you do trying to find a shortcut.

1 Like

That's a nice demo sketch. Its an easy path to duplicate and adjust the LoopCounter function into other purposes.

I put it into Wokwi as LoopCounter.ino - Wokwi Arduino and ESP32 Simulator

Here's a screenshot:

You missed the point if you only have one function in void loop().

The loop counter is a task. ADD other tasks and use the counter results to tell how well you wrote them. When I wanted leds to blink while flickering I wrote a task to make them flicker and another task to make that blink. Guess how easy that was to debug?

When you write non-blocking, for some people 2ms is not blocking. I need to stay in practice or I lose this, I want to make sure that an analog read is the longest thing running in any pass through void loop().

That simulation is almost twice as fast as an Uno.

1 Like

I noticed that, or wondered. I was going to try IRL against the wokwi.

It really shouldn't be twice as fast, I think the wokwi boys strive to maintain a high fidelity experience.

A sim might not run as fast in real time, but the clock in the corner and the % show "simulated real time" and that should be exact.

Off the top of my head, wouldn't it have to be, for things like millis() to function correctly?

And even if you can 'splain how millis() might could work, there are many other things that would break if the "processor" ran faster.

The wokwi runs the same too chain and puts the code on a simulated AVR…

When I get back from the beach, def some experiments to perform, and if this turns up I am sure it will be of interest over there in wokwi-land.

I mention again my use of a wiggled output pin and my frequency/duty cycle meter…

And I can't find it now, but our friend @drmpf has a nice objecty thing that can display some statistics.

a7

The millis() function is always +/- 1 milli() just in how it works.
If closer timing is desird, use micros() that is granular at 4 usecs, 1 milli / 250.

Do I have to dig an Uno out? Nahhhh, wokwi can lose some hair but, lemmeno how much?

I didn't mean just one function in loop(), I meant that LoopCounter is a good template for building additional tasks. Except for microsInOneSecond and Serial.begin(...) it is completely self-contained.

Here's an example where I just used it with a different function in loop() QuickChargeControl.ino - Wokwi ESP32, STM32, Arduino Simulator

Here's output from a physical mega:




  Loop Counter, free by GoForSmoke

This sketch counts times that loop has run each second and prints it.
147961
148573
148574
148573
148575

I wonder if it is some Serial output issue -- I think I remember reading something odd about baud rates on Wokwi.

code from #13 in the lab…

wokwi simulated:

  Loop Counter, free by GoForSmoke

This sketch counts times that loop has run each second and prints it.
197890
198754
198755
198753
198755

UNO on desk in front of me:


  Loop Counter, free by GoForSmoke

This sketch counts times that loop has run each second and prints it.
197927
198739
198740
198739
198740
198740
198740

or was it the other way 'round? In any case wokwi <-> real life very close enough.

In theory it should produce identical output. But there is no gross speed discrepancy.

Not for me to run to ground at this time. :expressionless:

a7

1 Like

That would be the loopTime class in the my SafeString library.
loopTimer times in us and prints stats, max/min and average, every 5sec (by default) and excludes its own processing time.
See Simple Multitasking Arduino on any board without using an RTOS for examples of use.

If time is critical you should avoid raw prints as they can block your loop.
See Arduino Serial I/O for the Real World Non-Blocking Text I/O using the SafeString library which covers non-blocking input and non-blocking output and adding extra input / output buffering.

If you are driving steppers you might also find this library/tutorial useful
Stepper Speed Control Library
It is a rewrite of the AccelStepper library to allow speed control of the stepper motor and includes automatic ramp down when approaching the set hard limits.

It is possible that the compiler optimizes better than IDE 1.6.