Run motor until condition is met

Hi Guys, I'm new to Arduino so bear with me. I know a bit of C++ as well from RobotC

I hooked up a little motor to the arduino PWM ports. The motor has a built-in degree counter. When the degrees are even the signal is HIGH, and when the degrees are uneven the signal is LOW, meaning there's a 180 HIGH's in a full 360° rotation.

Right now I'm just testing to get a feel for the programming. I'm going to build an H-bridge in a few days when my parts arrive and like my program to work like this (only an example):

Run motor CW 80°
wait 1s
Run motor CW 175°
wait 5s
Run motor CCW 210°
wait 0,2s
Run motor CW 55°

It's a linear program sequence I need with many many lines. The amount of degrees it should turn would be calculated by a variable in a separate program sequence if possible that counts the amounts of degrees it turns.

Right now I just wrote a really simple program - proof of concept if you will. It's not working though, I don't think my counting function works properly as the motor just keeps on spinning and spinning.

int degree;

void setup() {
pinMode(9, OUTPUT);
pinMode(13, OUTPUT);
pinMode(2, INPUT);
}

void loop() 
{
if (degree != 90) // If degree count doesn't equal 90 then
{ 
  analogWrite(9, 102); //Run at 40% power
}
else 
{
 analogWrite(9, 0); //Stop Motor
}



if (digitalRead(2) == HIGH) // On-board motor switch
{ 
  digitalWrite(13, HIGH);
  degree++; 
}
else
{
  digitalWrite(13,  LOW);
}
}

How would I go about programming a linear program like that without interference from secondary tasks like counting the degrees?

if (digitalRead(2) == HIGH) // On-board motor switch
{
  digitalWrite(13, HIGH);
  degree++;
}

You want to look at the state change detection example. You want to increment the counter when the pin BECOMES high, not when it IS high.

Seen the blinkWithoutDelay example yet?

That's because the loop will loop much more often then the motor per degree. And you only check for high, not becoming high!

Also, make a indentation after a {. And use a variable for the pins.

Try:

const byte LedPin = 13;
const byte MotorPin = 9;
const byte CounterPin = 2;

volatile unsigned int degree;

void setup() {
  pinMode(MotorPin, OUTPUT);
  pinMode(LedPin, OUTPUT);
  
  attachInterrupt(digitalPinToInterrupt(CounterPin), counterInterrupt, CHANGE); //interrupt every time the sensor changes
  
  analogWrite(MotorPin, 102);
}

void loop(){
  if (degree > 90) // If degree bigger then 90 degree, stop
  {
    analogWrite(MotorPin, 0);
  }


  //Back to 0 after 260 degree
  if(degree >= 360){
    degree = 0;
  }
  
  //Led on @ uneven degrees
  digitalWrite(LedPin, degree % 2);
}

void counterInterrupt(){
  degree++;
}

PaulS:
You want to look at the state change detection example. You want to increment the counter when the pin BECOMES high, not when it IS high.

Ok I can do that, but what kind of benefit would it have? Isn't it basically the same outcome?

MarkT:
Seen the blinkWithoutDelay example yet?

I have now, and with this could I potentially have 1000's of lines of linear code, and at the start of the code, my counter function with no interference from each other?

Seabottom:
Ok I can do that, but what kind of benefit would it have? Isn't it basically the same outcome?

Nooooooo, really no. The loop runs for like a million times a second. And with just the statement

if (digitalRead(2) == HIGH)

You will increment the variable degree a lot in the short time it's high. So you must find the moment it changes. Thats why I used the interrupt with parameter CHANGE.

Seabottom:
I have now, and with this could I potentially have 1000's of lines of linear code, and at the start of the code, my counter function with no interference from each other?

If you code it a bit okay I don't think you need more then 100 lines of code :wink: Tip, make functions.

septillion:
Nooooooo, really no. The loop runs for like a million times a second. And with just the statement

if (digitalRead(2) == HIGH)

You will increment the variable degree a lot in the short time it's high. So you must find the moment it changes. Thats why I used the interrupt with parameter CHANGE.

I honestly never thought of that. It makes sense now.
I'll try and see what I can do with the information provided. I'll let you know.

I've been trying for hours now to see how I could make this program the simplest as possible.

The reason why I need to control the motors direction and speed all the time is because I've built a Motion Simulator like the ones you see on big vendor fairs, and I need a static program to run in sync with a video.
This I would simply accomplish by pushing a button on the arduino and the play button on the video at the same time.

Therefore the program is going to get a lot of identical lines, but all with different values regarding power and degrees.
If there's memory headroom I'd like to add some fading on the PWM signals later on.

I gave up at the code you see below. I figured that the "while" loop would only run when the condition is true, but the motor keeps on going through the main loop ignoring the delays and while conditions, but accepts the power and degree values.
I also know that I should not use delays but millis instead but I'm just doing some proof on concept here.

EDIT: Apparently I can't do links, most likely because of my post count. Just delete "%22http//" and it should work.

void loop()
{
  while(degree < 360)
  {
  analogWrite(Motorpin, 140);
  delay(1000);
  }
  degree = 0;
  while(degree < 180)
  {
  analogWrite(Motorpin, 80);
  delay(1000);
  }
  degree = 0;
}

This I would simply accomplish by pushing a button on the arduino and the play button on the video at the same time.

That is never going to happen.

Therefore the program is going to get a lot of identical lines, but all with different values regarding power and degrees.

That would be rather stupid. Create a function. Call it with different power and degree values. You've noticed, I'm sure, that there are not digitalRead1(), digitalRead2(), digitalRead3(). etc. functions. There is ONE digitalRead() function that can read from any digital pin.

I also know that I should not use delays but millis instead but I'm just doing some proof on concept here.

Painting yourself into a corner your are. But, hey, it's your room, your paint, and your carpet in the hall. Feel free to head off in the wrong direction until it is too late.

  while(degree < 360)
  {
  analogWrite(Motorpin, 140);
  delay(1000);
  }

The value in the degree variable does not change while the while loop is running. Expecting the while loop to ever end is pointless.

PaulS:
That would be rather stupid. Create a function. Call it with different power and degree values. You've noticed, I'm sure, that there are not digitalRead1(), digitalRead2(), digitalRead3(). etc. functions. There is ONE digitalRead() function that can read from any digital pin.

Yes I've been reading up on that as well. I actually found an extensive and easy to understand guide here, but I fail to understand how I would be able to implement it into my own project. I guess I could use boolean values to call one or all 3 functions when needed with a value written to an integer something like the mock-up below.

void loop ()
if start_button == 1
{
  M1_CW = 1
  M1_Speed = 255
  M1_degrees = 180
}

void motor1 ()
if (m1_CW == 1 && M1_degrees < degree)
{
  writeanalog(12, m1_speed)
}
else if (m1_CCW == 1 && M1_degrees < degree)
{
  writeanalog(11, m1_speed)
}
else if (M1_CW && M1_CCW == 0)
{
  writeanalog(10, 0)
  writeanalog(11, 0)
}
degree = 0;

PaulS:

Seabottom:
I also know that I should not use delays but millis instead but I'm just doing some proof on concept here.

Painting yourself into a corner your are. But, hey, it's your room, your paint, and your carpet in the hall. Feel free to head off in the wrong direction until it is too late.

TBH, I only use delays now because the milis is so confusing to use.
Actually, while writing this response I just looked up the BlinkWithoutDelay example again and once I removed all of the comment clutter everything became SO much easier to understand. I will use millis from now on then.

PaulS:

 void loop()

{
  while(degree < 360)
  {
  analogWrite(Motorpin, 140);
  delay(1000);
  }
  degree = 0;
  while(degree < 180)
  {
  analogWrite(Motorpin, 80);
  delay(1000);
  }
  degree = 0;
}



The value in the degree variable does not change while the while loop is running. Expecting the while loop to ever end is pointless.

I do not understand what you mean. If I load this code onto the arduino, my motor turns 360° at 140 speed, and immediately proceeds to turn 180° at 80 speed, then loops around and starts over, skipping all of the delays on the way, which is what I don't understand.

If I load this code onto the arduino, my motor turns 360° at 140 speed, and immediately proceeds to turn 180° at 80 speed, then loops around and starts over, skipping all of the delays on the way, which is what I don't understand.

I don't, either, and I find it difficult to believe. Add Serial.print() statements in the while loops. I do not believe that the code is doing what you think it is.

The attachInterrupt works well for the digital pins, but I realized that I need all the digital pins for the 3 motors (4 inputs per H-bridge), which means I have to use the analog pins for each tachometer.

Since I need 3 tachometers to be measured in realtime, I am forced to use the PinChange function.

I've been looking on for like 2 or 3 hours now and I found a few articles that explain how to do it, but nothing I seem to do works, even if I copy it directly and only change the pin numbers.

Could any of you help me with that?

Seabottom:
When the degrees are even the signal is HIGH, and when the degrees are uneven the signal is LOW, meaning there's a 180 HIGH's in a full 360° rotation.

How would I go about programming a linear program like that without interference from secondary tasks like counting the degrees?

So you need to count when the counter CHANGES from low to high, and from high to low.

int mostRecentRead;
boolean motorIsRunning;
int stopMotorAt;

void countMotor() {
  if(!motorIsRunning) return;

  int sense = digitalRead(SENSOR);

  if(sense == mostRecentRead) return;

  mostRecentRead = sense;

  if(--stopMotorAt <= 0) {
    stop_the_motor();
    motorIsRunning = false;
  }
}

void startMotor(int deg) {
  if(deg == 0) return;

  if(deg>0) {
    motorIsRunning = true;
    stopMotorAt = deg;
    start_the_motor_CW();
  }
  else {
    motorIsRunning = true;
    stopMotorAt = -deg;
    start_the_motor_CCW();
  }
}

OK, so we can move the motor and count the degrees. Now you need to run a sequence of moves:

class Program {
public:
  boolean move;
  unsigned int pauseMs;
  int degrees;

  Program(boolean m, unsigned int ms, int deg): move(m), pauseMs(ms),  degrees(deg) {}
}

const int PROGLEN = 7;

Program prog[PROGLEN] = {
  Program(true, 0, 80), // 80 deg CW
  Program(false, 1000, 0), // pause 1000ms
  Program(true, 0, 175), // 175 deg CW
  Program(false, 5000, 0), // pause 5000ms
  Program(true, 0, -210), // 210 deg CCW
  Program(false, 200, 0), // pause 200 ms
  Program(true, 0, 55) // 55 deg CW
};


int currentProgramStep = -1; // we use -1 to mean "stopped"
unsigned long pauseStartMs;

void loop() {
  countMotor(); // always do this at the top of the loop

  if(currentProgramStep == -1) {
    if(we need to start the program) {
      currentProgramStep = 0;
      startCurrentStep();  
    }
  }
  else 
  if(currentProgramStepFinished()) {
    currentProgramStep++;
    if(currentProgramStep >= PROGLEN) {
      currentProgramStep = -1;
    }
    else {
      startCurrentStep();  
    }
  }
}

void startCurrentStep() {
  if(prog[currentProgramStep].move) {
    startMotor(prog[currentProgramStep].deg);
  }
  else {
    pauseStartMs = millis();
  }
}

boolean currentProgramStepFinished() {
  if(prog[currentProgramStep].move) {
    // if the current step is a move, then we have finished when 
   // countmotor turns the motor off 
    return !motorIsRunning;
  }
  else {
    // otherwise, we have finished the current step on tomeout
    return millis() - pauseStartMs >= prog[currentProgramStep].pauseMs;
  }
}

Something like that. After that, you can go more complicated by having a number of programs to pick from.

Other way to do this is to make the class polymorphic. Oh, and instead of using a boolean to indicate if the current program step is a move or a wait, you can use an enumerated value. If you do this, you can have three set types: move, wait, and stop. This would allow you to discard PROGLEN, which means you could have several programs to pick from with different lengths.