1

I am currently working on a project that makes a plateua move back and forth using a stepper motor. On this plateau I have attached LEDs that create patterns. However, the plateau should be moving whilst the pattern is changing. With my current code this is not happening. I know that I have to work with millis, but when I am completely lost on how to do it. Could someone help me?

Here is my code:

#include <AccelStepper.h>

AccelStepper stepper(1, 42, 38);

int i;
int microStep = 46;

int currentAnimation = 0;
int count = 0;
int button = 13;

int state = LOW;
int lastState = LOW;

//bytes for patterns
byte a = B11110000;
byte b = B00010000;
byte c = B01000000;
byte e = B10100000;

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

void setup()
{
  DDRH = B1111000; // set PORT 6-9 of arduino mega high
  DDRB = B11110000; // set PORT 10-13 high

  stepper.setMaxSpeed(900.0);
  stepper.setAcceleration(400.0);

  pinMode(button, INPUT);
  state = digitalRead(button);

  pinMode(microStep, OUTPUT);
  digitalWrite(microStep, HIGH);
}

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

void loop()
{
  //This should happen constantly
  stepper.runToNewPosition(2000);
  delay(500);
  stepper.runToNewPosition(0);

  // These are the patterns, that should be played whilst the stepper is running. The patterns chaneg when you push the button
  if ( state == HIGH & lastState == LOW )
  {
    currentAnimation++;
  }

  if (currentAnimation % 3 == 0)
  {
    same();

  }
  if (currentAnimation % 3 == 1)
  {
    left();
  }
  if (currentAnimation % 3 == 2) {
    faster();
  }

  lastState = state;
  state = digitalRead(button);
}

//--------------------------------------------------------------------------------------------------------------------------------
//PATTERNS
void same() {
  for (int k = 0; k < 10; k++) //plays 10 times
  {
    PORTH = a;
    PORTB = a;
    delay(100);
    PORTH = 0;
    PORTB = 0;
    delay(100);
  }
}

void left() {
  for (int z = 0; z < 10; z++)
  {
    PORTH = e;
    PORTB = e;
    delay(100);
    PORTH = ~e;
    PORTB = ~e;
    delay(100);
  }

}

void faster() {
  for (int z = 0; z < 256; z++)
  {
    PORTH = z;
    PORTB = z;
    delay(100);
  }
  PORTH = 0;
  PORTB = 0;
}
2
  • The problem is that the animations (or actually the delay in them) will block the other code from running. But could you expand a little on what timing you want to achieve? You'll want to run stepper.runToNewPosition(xxx); every 500ms, and change the pattern each 100ms?
    – aaa
    Commented May 31, 2016 at 10:41
  • 1
    Actually, arduino.cc/en/Tutorial/BlinkWithoutDelay explains it rather well.
    – aaa
    Commented May 31, 2016 at 11:34

3 Answers 3

2

Since microcontrollers typically only have one "core" or "thread" they can only do "one thing at a time".

A microcontroller will step through your code and do everything in order (if you don't call interrupts).

Basically, when you put in a for-loop (or any other type of loop) or a delay, it will only continue with the rest of your code when that is done.

To solve this, and "do two things at a time" (or actually rapidly switch between things) you'll need either interrupts or non-blocking code (or both).

Interrupts

I see you're doing some direct port manipulation, which is really nice. You're writing bytes directly to the control registers of the microcontroller.

You can leverage on that, to set up a timer (check the ATMega328P datasheet). You may make this timer give an interrupt each 100ms, and execute a "frame" of your animation at that point. And the next frame at the next interrupt. So that the time between each frames can be used for other code.

Non-blocking code

Arduino has built-in millis(); which is basically an interrupt that fires every millisecond to keep track of the passed milliseconds. So you can use this instead of fiddling around with the timers (which may very well cause some of the Arduino's functions to fail (like millis() or the servo library).

To write non-blocking code, you'll not use delays, since they effectively throw away "processor time" by doing "nothing-instructions" all the time.

But if we take out the delays, the for-loops will run too fast. And a for-loop also doesn't really work without the delay. Because even if you use millis and wait for half a second, you can't do anything in between.

So we'll have to break up your code (specifically the animations) in chunks of code that can be executed withouth a delay. With only the need to delay in between these chunks.

Frames

Let's split up your animations in "frames". One of your animations same() works by PORTH&PORTB = A for 100msec and low for 100 msec.

void sameAnimation(unsigned char frame){//plays 20 times (1x on, 1x off) instead of 10x on-off.
  if(frame%2==0){//If even
    PORTH = a;
    PORTB = a;
  }else{//if not even.
    PORTH = 0;
    PORTB = 0;
  }
}

So based on the frame of the animation, the port will go high or low.

Now we look at the complete code, we have to change the motor every 500ms and have to change the animation frame every 100ms. And check a button.

We could put these in an interrupt that fires every 100ms, which would get reliable timing and minimum software overhead. Or use the millis(); (checked against the previous milis) to check if the last update was 100ms ago.

My go at the code is below, but you should be able to figure it out yourself.

void loop()
{
  unsigned char currentFrame = 0;

  //Could be done every round.

  lastState = state;
  state = digitalRead(button);
  if ( state == HIGH & lastState == LOW )
  {
    currentAnimation++;
    PORTH = 0;
    PORTB = 0;
  }

  // These are the patterns, that should be played whilst the stepper is running.

  if(millis() - previousMillis > 500){// Should change every 100ms
    previousMillis = millis();

    if (currentAnimation % 3 == 0)
    {
      sameAnimation(currentFrame++);
    }
    if (currentAnimation % 3 == 1)
    {
      leftAnimation(currentFrame++);
    }
    if (currentAnimation % 3 == 2) {
      fasterAnimation(currentFrame++);
    }

    if(currentFrame % 5){//Should change every 500ms, so every 5 frames.
      if(forward){
        stepper.runToNewPosition(0);
        forward = false;
      }else{
        stepper.runToNewPosition(2000); 
        forward = true;
      }
    }
  }
}

//--------------------------------------------------------------------------------------------------------------------------------
//PATTERNS
void sameAnimation(unsigned char frame){//plays 20 times (1x on, 1x off) instead of 10x on-off.
  if(frame%2==0){//If even
    PORTH = a;
    PORTB = a;
  }else{//if not even.
    PORTH = 0;
    PORTB = 0;
  }
}

void leftAnimation(unsigned char frame) {
  if(frame%2==0){//If even
    PORTH = e;
    PORTB = e;
  }else{//if not even.
    PORTH = ~e;
    PORTB = ~e;
  }
}

void fasterAnimation(unsigned char frame){
  PORTH = frame;
  PORTB = frame;
}

The button

Checking the button every round isn't really needed. You can use an interrupt on that pin, so that the code to check it's state and go to the next frame only happens when the button is actually pressed. (interrupt triggered on rising edge?).

2

The basic pattern goes like this:

when you start the timer, you essentially store the current millisecond. After this, in your loop code, among other things you should check if the time has passed by comparing current millisecond with the stored one. If their difference is equal or bigger of the desired delay interval, then the time has passed.

Make sure you use unsigned long variables to store times and check if time has passed with the condition used below (if(millis() - mark >= interval ){...}) so you will not have problem even after millis() rolls over.

If you need more help, do not hesitate to ask.

unsigned long interval=2000; //2000ms
unsigned long mark;
boolean enabled=true;

void setup() {
    Serial.begin(9600);
    Serial.println("starting timer");
    startTimer();
    
}

void loop() {
    check();
}

void startTimer(){
    mark=millis();
}

void check(){
    if(enabled){      
        if(millis() - mark >= interval ){
            
            enabled=false;
            Serial.println("time elapsed");
        }                          
        
    }
}
4
  • Hmm, this code will only run once, instead of in 2000ms intervals. I'm not sure if you/he were/was aiming for that.
    – aaa
    Commented May 31, 2016 at 11:41
  • it only aims to demonstrate the way one would use millis() to measure time. Apart from that, it is easily modifiable to auto restart -> just replace line enabled=false; in check() with startTimer();
    – foivaras
    Commented May 31, 2016 at 11:56
  • Ah, that's what the startTimer() function was for, I thought it was pretty silly to set up the mark in a function. You've got my vote, it's a good answer. It's very much like the blink tutorial.
    – aaa
    Commented May 31, 2016 at 12:16
  • It'd be fairly easy to create a library that allows multiple timers which can be easily started and checked. Which is a nice idea if you're going to have a lot of these projects.
    – aaa
    Commented May 31, 2016 at 12:21
2

You could try something along these lines, I haven't put all of the code in, but just how this could be done for the sweep timer using millis().

unsigned long sweepTime;
unsigned long animationTime;
int stepperPosition;


void setup()
{
    //... other setup code
    sweepTime = millis();       // initialise the variables for the sweep timer, and the animation timer
    animationTime = millis();
    stepperPosition = 2000;     // initialise the next stepper position
}

void loop()
{
    if (millis() - sweepTime > 500) //  check if more than 500 milliseconds have elapsed since last time we ran this.
    {
        stepper.runToNewPosition(stepperPosition);  // change stepper position
        sweepTime = millis();           // reset timer variable
        switch (stepperPosition) {      // need to change the value of the next position to set the stepper to
            case 0 : 
                stepperPosition = 2000;
                break;
            case 2000 :
                stepperPosition = 0;
                break;
        }
    }
    //... continuation of loop code
}

Additionally, I don't have my arduino with me, so can't verify the correctness of the code.

3
  • You're missing a case label on the second case of your switch. I would've used a if-else statement anyway. And the time variables should be unsigned long rather than long.
    – aaa
    Commented May 31, 2016 at 11:46
  • @Paul. Thanks for feedback, I have changed and corrected. I tend to use if-else for boolean comparisons - you're probably right in this case, but I personally think the switch allows for additional sweep values in the future.
    – Holmez
    Commented May 31, 2016 at 12:25
  • switch-case statements are extremely useful if you're skipping through states, and when you declare these states in an enum, the compiler even tells you whenever you did not implement one of the states, which is nice. I get your point, but if it's two things, a if-else is smaller and more logical. You could also argue that if-else is better since you can add other conditions to it ;) which isn't quite possible in a switch-case. But I agree that if-statements are often mis-used xD
    – aaa
    Commented May 31, 2016 at 12:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.