Multi-Threading making sketch not work

Hey guys, I haven’t been properly introduced, specially because I only found out about this forum just now (I know I know, I shouldn’t look for a comunity only when I need help… :cold_sweat:)

My name is Andre and I started playing with M/C’s a while ago. I have a very basic knowledge of how all this works, and I’m kinda stuck with this project me and a few friends are building.
Basically it is stepper motor and a display. It features 2 buttons to increase/decrease speed and another one to start/stop the spinning.
It should display the speed on an OLED display I got.

So, before I post the code, let me just try to explain what I did and what’s wrong.
I’m using a 20M020D stepper with a DRV8834 driver board. From what I learned, in order to make it spin, I provide a “pulse” to the board:

for (i = 0; i<10; i++)       // Iterate for 4000 microsteps.
  {
    digitalWrite(steppin, LOW);  // This LOW to HIGH change is what creates the
    digitalWrite(steppin, HIGH); // "Rising Edge" so the easydriver knows to when to step.
    delayMicroseconds(subdelay);      // This delay time is close to top speed for this
  }

Ok, very well. But I’m not a fan of using delays, specially because I need several stuff being done at the same time. So I found out about Multi-Threading and the TimedAction Library (http://playground.arduino.cc/Code/TimedAction). In order to use it with MicroSeconds instead of miliseconds, I tweaked it a little and called it TAMicro (that’s what you’re gonna see in the code, but it’s the exact same library only with millis() replaced for micros().

So I used that and pulsed the motor on a given interval, and used a function (included in the library) to change the speed (AKA, the frequency of the pulses). Worked like charm.

The library I’m using to work with the OLED display is the OzOLED (http://blog.oscarliang.net/arduino-oled-display-library/) and also works very well. Since writting on a display is kinda slow, I only update it on a 200ms basis. Also worked well with the motor.

The thing is, when I try everything at the same time, it drags and doesn’t work well (like it spins at 1rpm???).

I tried commenting sections of the code to try to find what part is slowing things down and I can’t really find it. It seems to be everything together that does it, not a single part.
I find it weird because the ATMega328 gets about 1MIPS per MHz. It must be powerfull enough to run this, I just need smart coding (wich I totally suck at!).

You’ll find several weird stuff in there, I’ll try to explain if you have any doubts:

#include <Wire.h>
#include <OzOLED.h>
#include <TAMicro.h>
#include <OneButton.h>

const byte DCR = 4;
const byte ICR = 5;
const byte MODEPin = 6;
const byte M0Pin = 14;
const byte M1Pin = 15;
const byte dirpin = 16;
const byte steppin = 17;

boolean startstop = true;
float rpm = 50;

unsigned long subdelay; // this value is what actually sets the speed on the motor
float stp;

TAMicro Update = TAMicro(200000,updt);
TAMicro Pulse = TAMicro(200000,pulse);
TAMicro Display = TAMicro(200000,displaySpeed);


OneButton increase(ICR, true);
OneButton decrease(DCR, true);
OneButton spinmode(MODEPin, true);


void setup() 
{
  Serial.begin(9600);
  OzOled.init();
  pinMode(dirpin, OUTPUT);
  pinMode(steppin, OUTPUT);
  pinMode(M0Pin, OUTPUT);
  pinMode(M1Pin, OUTPUT);
  digitalWrite(dirpin, LOW); // LOW = CCW; HIGH = CW rotation

 // each button can be clicked, double clicked and long pressed
 // resulting on different increases/decreases in the speed (+1, +3, +10rpm, respectively)


  increase.attachClick(increase1);
  increase.attachDoubleClick(increase3);
  increase.attachLongPressStart(increase10);
  increase.setClickTicks(300);
  increase.setPressTicks(400);

  decrease.attachClick(decrease1);
  decrease.attachDoubleClick(decrease3);
  decrease.attachLongPressStart(decrease10);
  decrease.setClickTicks(300);
  decrease.setPressTicks(400);

  spinmode.attachClick(startStop);
  spinmode.setClickTicks(300);

}

// ----------------------------------------------------------------- //

void increase1()
{
  rpm++;
  if (rpm > 999) rpm = 999;
}

void increase3()
{
  rpm+=3;
  if (rpm > 999) rpm = 999;
}

void increase10()
{
  rpm+=10;
  if (rpm > 999) rpm = 999;
}

void decrease1()
{
  rpm--;
  if (rpm < 50) rpm = 50;
}

void decrease3()
{
  rpm-=3;
  if (rpm < 50) rpm = 50;
}
void decrease10()
{
  rpm-=10;
  if (rpm < 50) rpm = 50;
}

void startStop()
{
  startstop = !startstop;
  
  if (startstop == false)
  {
    Pulse.disable();
    Serial.println("Stop!");
    return;
  }
  if (startstop == true)
  {
    Pulse.enable();
    Serial.println("Start!");
    return;
  }
}

// ----------------------------------------------------------------- //

void pulse()
{
  digitalWrite(steppin, LOW);
  digitalWrite(steppin, HIGH);
}


//This is weird stuff, but it has to do with not completely deleting the screen,
//which would take a "long" time to do. This way is much faster just to rewrite what's needed
void displaySpeed()
{
  OzOled.setCursorXY(0, 0);
  OzOled.printNumber(rpm);
   if (rpm < 100)
   {
     OzOled.setCursorXY(2, 0);
     OzOled.printString(" ");
   }
   OzOled.setCursorXY(3, 0);
   OzOled.printString("rpm");
}


//This function works as a gear shifter, depending on the speed,
//it uses the proper microstepping
void updt()
{
  if (rpm >= 600)
  {
    stp = 1;
    digitalWrite(M0Pin, LOW);
    digitalWrite(M1Pin, LOW);
  }
  else if (rpm >= 350 && rpm < 600)
  {
    stp = 0.5;
    digitalWrite(M0Pin, HIGH);
    digitalWrite(M1Pin, LOW);
  }
  else if (rpm < 350)
  {
    stp = 0.03125;
    pinMode(M0Pin, INPUT);
    digitalWrite(M1Pin, HIGH);
  }

 //This formula is what I eventually found out to be accurate to calculate RPM from the delay between pulses   
  subdelay = ((60000000 * stp) / rpm) / 20;
  Pulse.setInterval(subdelay); 
  Update.setInterval(subdelay); //These 2 lines update the frquency of the pulses and gear shifting
  Serial.print(rpm, 0);
  Serial.println(" rpm");
}


// ----------------------------------------------------------------- //

void loop()
{
  Update.check();
  Pulse.check();
  Display.check();
  increase.tick();
  decrease.tick();
  spinmode.tick();
}

Please let me know if you need any help understanding what I did there, I have a weird way of coding that should be a lot cleaner.
I really hope to get some help from the pros, this is the biggest project (Yuuuck! What a N00B!!! 8)) I’ve done and it’s driving me crazy!

Thanks in advance!

Given that you are scheduling things at intervals of hundreds of milliseconds, why are you using microsecond resolution everywhere? It looks as if you only want to do a few things per second, and if you get rid of the baggage the Arduino ought to do that with plenty of performance to spare. Since it isn't, I suggest you find out how long each iteration of loop() takes, and how much of that time is spent in each of the functions you call in it.

The objection I have to libraries (other than some very simple ones) is that you spend more time trying to figure out how the library works than you would need to write your own code - in this case using the Blink Without Delay technique.

An Arduino has only one processor so it can only pretend to do multi-threading and the example in the first post of this Thread is as good as any other approach to multi-threading and a lot easier to understand and modify.

...R

Robin2:
The objection I have to libraries (other than some very simple ones) is that you spend more time trying to figure out how the library works than you would need to write your own code - in this case using the Blink Without Delay technique.

An Arduino has only one processor so it can only pretend to do multi-threading and the example in the first post of this Thread is as good as any other approach to multi-threading and a lot easier to understand and modify.

...R

That really depends on the quality and skills of the author(s) of a specific library. The better ones come with example sketches that show in context how to use the methods and functions the library has to offer. Other libraries are so simple that just a quite look at their .h and .cpp files is all you really need. But there are surely some that seem very hard to understand and take lots of experimentation to get a feel for how to use them effectively.

retrolefty:
The better ones come with example sketches that show in context how to use the methods and functions the library has to offer.

In my experience it is very rare in the Open Source Software world to find comprehensive documentation - especially documentation that describes the limits within which it makes sense to use the code or which describes each function and its parameters in detail. If you are lucky you get a few examples that seldom explore exactlyw aht you want to do.

Even when you do get reasonably good documentation (I'm thinking of the Android API and the C/C++ API) it can take a great deal of thought and experiment to figure out how to do some things.

A big problem in the Arduino system is that there is no standard for libraries to describe the resources they use so that you can easily see what is incompatible with what.

And I don't believe Timer libraries add any value because using BWoD is very simple and transparent. (And nothing personal in this if you have written the best ever Timer library :slight_smile: )

...R

Robin2:
In my experience it is very rare in the Open Source Software world to find comprehensive documentation

Mine too. Even more rare to find documentation which has been maintained and is up to date.

I also agree that libraries for simple tasks such as carrying out timed actions have only marginal benefits because using the library API is barely any simpler than a well designed code fragment to time the action directly. IMO the novice user would be far better off coding it directly, so that they understand how simple it is to do and know exactly how it works.

Ok, first of all, I'm sorry for taking so long to reply to you guys, but I'm in exams at college so it makes things harder... :expressionless:
Anyway, thank you guys so much for the replies.

PeterH:
Given that you are scheduling things at intervals of hundreds of milliseconds, why are you using microsecond resolution everywhere? It looks as if you only want to do a few things per second, and if you get rid of the baggage the Arduino ought to do that with plenty of performance to spare. Since it isn't, I suggest you find out how long each iteration of loop() takes, and how much of that time is spent in each of the functions you call in it.

Peter, I need to use microseconds to make the pulses accurate for the revolutions in the motor. But after reading this, I realised everything else can be in millisecs, which I'm going to change!
The idea of checking how long the loop() takes is great, and I'm gonna implement it!

As to the other guys, I may very well ditch the libraries and write only the necessary code for it to work.
Well, thanks everyone for the inputs, but I wonder if anyone has any idea of what I actually messed up and made it so slow..

  1. Remove the Serial.print() in updt. If that print ("xxx RPM\n") is done more often than every 8ms or so, it will take about 8ms to complete, which is longer than the interval you're setting between calls to updt(). (Assuming I read it right.)
  2. or, increase the serial bitrate and/or decrease the interval used for Update; right now you're attempting to update at every step.
  3. You're expecting a lot of an 8bit microcontroller if you think it can run three threads, with floating point calculations and IO calls, at a granularity in the "less than a millisecond" range. Yeah, it can do one instruction in 62.5ns, which is pretty good. But those instructions don't map cleanly onto C statements, and a floating point divide operation will take nearly 1000 of those single instructions (~50us. And you have several.)
  4. Get rid of your floating point. Your use of the "stp" multiplier is easily replaced by an integer divisor. Also, only do these calculations when the speed actually changes. Keep your variables in more computationally convenient units so that they'll fit in a 16bit quantity. (so, calculate the microsecond step size in your increase/decrease functions. Actually, you can probably use floats there if you really want, since it's a human-scale time event...)