Write non-blocking code

Hi, in my project I need to run a stepper for a longer period uninterrupted and read data from a temperature sensor every second. What would be the best way to write the code so it can read the sensor without noticeably interrupting the stepper? Any thoughts on pseudo threads, interrupts or cooperative multitasking? All thoughts and pointers are welcome.

If your stepper motor driver supports pulse control via a timer, you can generate motor pulses in a hardware interrupt. Use a hardware timer to generate stepper pulses at a fixed interval. Use the main loop to read the temperature sensor.

You don't tell what components you have, but for example to read analog sensor it takes maybe ~0.1ms, is that noticeable for your stepper approach?

  • Move the stepper 1 step (stop moving at destination), then read the sensor, then move the stepper 1 step (stop moving at destination) etc . . .
    However, you can adjust the number of steps as necessary.

I think I may know the root cause of this problem. If you can describe what the stepper is doing, and I am correct, I can tell you the solution.

Wow, thanks for all the responses so far! the components used are a Nano, in combination with a BYJ48 with a ULN2003 driver and a DS18B20 temperature probe. I’m going to control the stepper and read the temperature over serial, and for that I devised a relative simple sketch that reads a code comparable to gcode and either runs the stepper or reads the temperature. The code is now blocking, but I’m assessing a non-blocking route going forward.

Hope that helps!

TIA

  • Do you understand Post #4 ?

I believe I do, but just to cross-check. I imagine it to be a FIFO command buffer where stepper commands are queued and processed incrementally in the loop, while temperature commands are handled immediately. The stepper commands are processed until their steps reach zero, at which point they're removed from the buffer. To illustrate I prepared a simplified pseudo-code snippet:

void loop() {
  // Read and handle incoming commands
  readCommandsFromSerial();
  
  // If a stepper command is received, add it to the buffer
  if (cmd == STEPPER) {
    pushStepperCommand(steps, direction);
  }
  
  // Process stepper commands from the buffer
  if (stepperBuffer not empty) {
    processCurrentStepperCommand();
    if (currentCommandCompleted) removeCommandFromBuffer();
  }
  // Process temperature command if received
  if (cmd == TEMP) {
    processTemperatureCommand();
  }
}

Would this be considered a form of cooperative multitasking?

  • Let’s say you need to move the stepper 500 steps CCW.
  • We enable a non blocking stepper TIMER to run every 1ms.

When this TIMER expires we move the stepper CCW 1 step and decrease a counter from 500 to 499 . . .
When the counter reaches 0, we disable this TIMER.
It takes the stepper 500ms to get to its destination.

  • To prevent blocking, we read the DS18B20 every 1 second at which time we use xxx.setWaitForConversion(false); and start a new conversion.



  • Doing the above, the stepper and reading of the sensor essentially happens at the same time.
1 Like

Thank you very much, LarryD. Your explanation is clear and aligns with what I had in mind. However, I’m still a bit unclear about the use of a non-blocking timer. Why not just use the loop function itself as a timer? Are there any specific advantages to implementing a non-blocking timer instead? I assume this timer would also operate within the scope of the loop function, correct?

Non-blocking code is like a musical score. Every instrument is heard at the right time, rather than first hearing one instrument, then the next, and so on.

3 Likes
  • Not sure what libraries you are using.
    *However, reading the DS18B20 must be made non blocking.
    Moving the stepper must be made non blocking.
    *Not doing so, makes sketches choppy and unresponsive.

  • Do you know how to make a non blocking TIMER using the BWD technique ?

There's this library for reducing the blockingness of reading a DS18B20:

And if you use a stepper library like MobaTools, you can plan non-blocking stepper movement.

The rest is being careful to not monopolize the processor so it is free to handle all the other bits that need handling.

Define "longer period". At what step rate?

Reading in a DS18B20 sensor with the standard onewirelibrary takes around 750 milliseconds. The sensor in itself needs this time for measuring the temperature.

The command to start the measuring needs less than a millisecond but the measuring needs the 750 milliseconds.

No chance to run a steppermotor in parallel without special tricks like a hardware-timer based interrupt.

This is what the MobaTools-library is doing.
All the detail stuff how to create the steppulses, counting steps, determining rotationdirection etc. are done inside the library. Once you have understood the functioncalls very comfortable to use.

If using the Mobatools for the steppermotor would not be enough to make it run smoothly there is a non-blocking library for onewire-sensors

What this library is doing is:
sending the measure temperature command and to not wait for the answer but instead "coming back" . If measurement has finished is calling a callback-function where the user can write code what to do now as the new temperature value is ready to use. Usually a flag-variable is set to true.

This means you have to check for "is new temperature ready" very often to get the point in time when the 750 milliseconds since start measuring have passed by.

You can install the MobaTools and the non-blocking onewire-library from the library-manager of the Arduino-IDE.

Attention ! the name of the non-blocking library for DS18B20 is called NonBlockingDallas

This is the demo-code provided with the library

If you look inside the code the function loop() has a single line

void loop() {
  temperatureSensors.update();
}  

You have to call this function frequently all the time If you want the most actual temperature value

What prevent you to use a standard "blink without delay" code to do it without any need of hardware timer?

Why do you need a library for it? Just send a command and do other things.

The standard "blink without delay" code is best to get started to design the own OOP based timer function.

The functioncalls of the standard onewire-library itself are blocking.

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS_PIN);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

  sensors.requestTemperatures(); // <<<<== this functioncall is BLOCKING

  tempSensor1 = sensors.getTempCByIndex(0) * 1.8 + 32; // Gets temperature value based on index, converts to Celsius
  tempSensor2 = sensors.getTempFByIndex(1) * 1.8 + 32; // Gets temperature value thermistor based on index, converts to Fahrenheit

Sure if you want the hassle of doing all the low-level bitbanging stuff from scratch you can write code yourself.

No it is not for the following reasons:

  • The blink without delay example does NOT emphasize the fundamental difference that non-blocking timing works very very different than delay()

  • The blink without delay example uses non-intuitive variable names

  • The blink without delay example scatters multiple variables across the code

Without emphasising the fundamental difference between delay() and non-blocking timing a new user will try to see a delay()-similar thing in the BWD-code.

And this will confuse the new user to the max because the pure waiting nature of function delay() is soooooo different from non-blocking timing that trying to get your head around how the hell does this BWD-code wait? MUST fail.

Everybody that has been online for more than four weeks has watched that 95% of all beginners have difficulties to understand this Blink without understand ehm sorry
blink without delay-example code.

Here is a tutorial that take time to explain on an everyday example how non-blocking timing works

It is not true.
OneWire library itself is not blocking, only the read function in DallasTemperature is blocking.
So you don't need

You only need to do a three high level OneWire commands -

ds.reset();
ds.select(addr);
ds.write(0x44);    

for start measure and

ds.reset();
ds.select(addr);
ds.write(0xBE);    

for reading.

1 Like