Timed loops with Moto Shiled chnaging Timer functions

I am trying to run a segment of code for about 10 seconds using a millis while loop. The issue is that the timer0, timer 1, timer 2 are being changed by the motor controller I am using. Right now when I run my code where it should be about 10 seconds. It runs from anywhere between 6 seconds to 20 seconds. Is there a way to get the timed loop more constant?

/*
Divides a given PWM pin frequency by a divisor.

The resulting frequency is equal to the base frequency divided by
the given divisor:

  • Base frequencies:
    o The base frequency for pins 3, 9, 10, and 11 is 31250 Hz.
    o The base frequency for pins 5 and 6 is 62500 Hz.
  • Divisors:
    o The divisors available on pins 5, 6, 9 and 10 are: 1, 8, 64,
    256, and 1024.
    o The divisors available on pins 3 and 11 are: 1, 8, 32, 64,
    128, 256, and 1024.

PWM frequencies are tied together in pairs of pins. If one in a
pair is changed, the other is also changed to match:

  • Pins 5 and 6 are paired (Timer0)
  • Pins 9 and 10 are paired (Timer1)
  • Pins 3 and 11 are paired (Timer2)

Note that this function will have side effects on anything else
that uses timers:

  • Changes on pins 5, 6 may cause the delay() and
    millis() functions to stop working. Other timing-related
    functions may also be affected.
  • Changes on pins 9 or 10 will cause the Servo library to function
    incorrectly.

Thanks to macegr of the Arduino forums for his documentation of the
PWM frequency divisors. His post can be viewed at:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1235060559/0#4
*/

Draco

// include the SPI library:
#include <SPI.h>

// L9958 slave select pins for SPI
#define SS_M4 14
#define SS_M3 13
#define SS_M2 12
#define SS_M1 11

// L9958 DIRection pins
#define DIR_M1 2
#define DIR_M2 3
#define DIR_M3 4
#define DIR_M4 7

// L9958 PWM pins
#define PWM_M1 9
#define PWM_M2 10    // Timer1
#define PWM_M3 5
#define PWM_M4 6     // Timer0

// L9958 Enable for all 4 motors
#define ENABLE_MOTORS 8

int pwm1, pwm2, pwm3, pwm4;
int dir1, dir2, dir3, dir4;
int ms = 0;
int cs = 0;
const int joyup = 15;
const int joydown = 16;
const int joyleft = 17;
const int joyright = 18;
const int joypress = 19;
//const int coin = 21;
const int front = 20;
const int left = 22;
const int clawup = 23;
const int clawdown = 24;
unsigned long currentime = 0;
int credit = 0;
int charge = 0;
long int gametime = 10;

void setup() {
  Serial.begin(9600);
  unsigned int configWord;

  // put your setup code here, to run once:
  pinMode(SS_M4, OUTPUT); digitalWrite(SS_M4, HIGH);  // HIGH = not selected
  pinMode(SS_M3, OUTPUT); digitalWrite(SS_M3, HIGH);
  pinMode(SS_M2, OUTPUT); digitalWrite(SS_M2, HIGH);
  pinMode(SS_M1, OUTPUT); digitalWrite(SS_M1, HIGH);

  // L9958 DIRection pins
  pinMode(DIR_M1, OUTPUT);
  pinMode(DIR_M2, OUTPUT);
  pinMode(DIR_M3, OUTPUT);
  pinMode(DIR_M4, OUTPUT);

  // L9958 PWM pins
  pinMode(PWM_M1, OUTPUT);  digitalWrite(PWM_M1, LOW);
  pinMode(PWM_M2, OUTPUT);  digitalWrite(PWM_M2, LOW);    // Timer1
  pinMode(PWM_M3, OUTPUT);  digitalWrite(PWM_M3, LOW);
  pinMode(PWM_M4, OUTPUT);  digitalWrite(PWM_M4, LOW);    // Timer0

  // L9958 Enable for all 4 motors
  pinMode(ENABLE_MOTORS, OUTPUT);  digitalWrite(ENABLE_MOTORS, HIGH);   // HIGH = disabled

  // set to max current limit and disable ISR slew limiting
  configWord = 0b0000010000001100;

  SPI.begin();
  SPI.setBitOrder(LSBFIRST);
  SPI.setDataMode(SPI_MODE1);  // clock pol = low, phase = high

  // Motor 1
  digitalWrite(SS_M1, LOW);
  SPI.transfer(lowByte(configWord));
  SPI.transfer(highByte(configWord));
  digitalWrite(SS_M1, HIGH);
  // Motor 2
  digitalWrite(SS_M2, LOW);
  SPI.transfer(lowByte(configWord));
  SPI.transfer(highByte(configWord));
  digitalWrite(SS_M2, HIGH);
  // Motor 3
  digitalWrite(SS_M3, LOW);
  SPI.transfer(lowByte(configWord));
  SPI.transfer(highByte(configWord));
  digitalWrite(SS_M3, HIGH);
  // Motor 4
  digitalWrite(SS_M4, LOW);
  SPI.transfer(lowByte(configWord));
  SPI.transfer(highByte(configWord));
  digitalWrite(SS_M4, HIGH);

  // Reduce the PWM frequency to about 8kHz
  // Note, this will screw up the timer functions that use Timer0 such as millis()
  setPwmFrequency(PWM_M1, 8);
  setPwmFrequency(PWM_M3, 8);
  pinMode(joyup, INPUT_PULLUP);
  pinMode(joydown, INPUT_PULLUP);
  pinMode(joyleft, INPUT_PULLUP);
  pinMode(joyright, INPUT_PULLUP);
  pinMode(joypress, INPUT_PULLUP);
  //pinMode(coin, INPUT_PULLUP);
  pinMode(front, INPUT_PULLUP);
  pinMode(left, INPUT_PULLUP);
  pinMode(clawup, INPUT_PULLUP);
  pinMode(clawdown, INPUT_PULLUP);
  attachInterrupt(2, coin, LOW); //Will interrupt the program to add credits when coin is incerted.
}


// *******************************************
// ************** Main Loop ******************
// *******************************************
void loop() {
  ms = 200; //Motor speed
  cs = 255; //Claw strength
  charge = 1;
  Serial.print("Credit = ");
  Serial.println(credit);
  if (credit >= charge)
  {
    currentime = millis();
    while ((gametime * 10000) >= (millis() - currentime))
    {
      if (digitalRead(joypress) == HIGH)
      {
        digitalWrite(ENABLE_MOTORS, LOW);
        while (digitalRead(clawdown) == LOW)
        {
          analogWrite(PWM_M3, ms);  digitalWrite(DIR_M3, 0);
        }
        analogWrite(PWM_M3, 0);  digitalWrite(DIR_M3, 0);

        digitalWrite(ENABLE_MOTORS, HIGH);
        delay(2000);
        digitalWrite(ENABLE_MOTORS, LOW);
        analogWrite(PWM_M4, cs);  digitalWrite(DIR_M4, 0);
        delay(3000);
        while (digitalRead(clawup) == LOW)
        {
          analogWrite(PWM_M3, ms);  digitalWrite(DIR_M3, 1);
        }
        analogWrite(PWM_M3, 0);  digitalWrite(DIR_M3, 0);
        while (digitalRead(left) == LOW)
        {
          analogWrite(PWM_M2, ms);  digitalWrite(DIR_M2, 0);
        }
        analogWrite(PWM_M2, 0);  digitalWrite(DIR_M2, 0);
        while (digitalRead(front) == LOW)
        {
          analogWrite(PWM_M1, ms);  digitalWrite(DIR_M1, 1);
        }
        analogWrite(PWM_M1, 0);  digitalWrite(DIR_M2, 1);

        delay(5000);
        analogWrite(PWM_M4, 0);  digitalWrite(DIR_M4, 0);

        digitalWrite(ENABLE_MOTORS, HIGH);
        delay(8000);
        credit = credit - charge;
        gametime = 0;
      }
      else
      {
        digitalWrite(ENABLE_MOTORS, LOW);
        while (digitalRead(joyup) == HIGH)
        {
          analogWrite(PWM_M1, ms);  digitalWrite(DIR_M1, 0);
        }
        analogWrite(PWM_M1, 0);  digitalWrite(DIR_M1, 0);
        while (digitalRead(joydown) == HIGH)
        {
          analogWrite(PWM_M1, ms);  digitalWrite(DIR_M1, 1);
        }
        analogWrite(PWM_M1, 0);  digitalWrite(DIR_M1, 0);
        while (digitalRead(joyright) == HIGH)
        {
          analogWrite(PWM_M2, ms);  digitalWrite(DIR_M2, 1);
        }
        analogWrite(PWM_M2, 0);  digitalWrite(DIR_M2, 0);
        while (digitalRead(joyleft) == HIGH)
        {
          analogWrite(PWM_M2, ms);  digitalWrite(DIR_M2, 0);
        }
        analogWrite(PWM_M2, 0);  digitalWrite(DIR_M2, 0);
        digitalWrite(ENABLE_MOTORS, HIGH);

      }
    }
    digitalWrite(ENABLE_MOTORS, LOW);
    while (digitalRead(clawdown) == LOW)
    {
      analogWrite(PWM_M3, ms);  digitalWrite(DIR_M3, 0);
    }
    analogWrite(PWM_M3, 0);  digitalWrite(DIR_M3, 0);

    digitalWrite(ENABLE_MOTORS, HIGH);
    delay(2000);
    digitalWrite(ENABLE_MOTORS, LOW);
    analogWrite(PWM_M4, cs);  digitalWrite(DIR_M4, 0);
    delay(3000);
    while (digitalRead(clawup) == LOW)
    {
      analogWrite(PWM_M3, ms);  digitalWrite(DIR_M3, 1);
    }
    analogWrite(PWM_M3, 0);  digitalWrite(DIR_M3, 0);
    while (digitalRead(left) == LOW)
    {
      analogWrite(PWM_M2, ms);  digitalWrite(DIR_M2, 0);
    }
    analogWrite(PWM_M2, 0);  digitalWrite(DIR_M2, 0);
    while (digitalRead(front) == LOW)
    {
      analogWrite(PWM_M1, ms);  digitalWrite(DIR_M1, 1);
    }
    analogWrite(PWM_M1, 0);  digitalWrite(DIR_M2, 1);

    delay(5000);
    analogWrite(PWM_M4, 0);  digitalWrite(DIR_M4, 0);

    digitalWrite(ENABLE_MOTORS, HIGH);
    delay(8000);
    credit = credit - charge;

  }
}
// ************** End Main Loop ******************


void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if (pin == 5 || pin == 6 || pin == 9 || pin == 10) { // Timer0 or Timer1
    switch (divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if (pin == 5 || pin == 6) {
      TCCR0B = TCCR0B & 0b11111000 | mode; // Timer0
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode; // Timer1
    }
  } else if (pin == 3 || pin == 11) {
    switch (divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x7; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode; // Timer2
  }
}
void coin()
{
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
  // If interrupts come faster than 200ms, assume it's a bounce and ignore
  if (interrupt_time - last_interrupt_time > 500)
  {
    credit = credit + 1;
  }
  last_interrupt_time = interrupt_time;
}

If the PWM code affects all three timers and "screws up" 'millis()', it's pretty pointless using 'millis()'-based timing and expecting any accuracy. I don't know of any fix for this. It's one or the other. Maybe an external time reference, triggering an interrupt? (Create your own 'millis()'.)

Also, you should declare 'credit' as 'volatile'. (Take a look at the 'attachInterrupt()' documentation in the reference.)
ie

volatile int credit;

I found something that works. Use 7500 for one second. So:

    while ((7500 * 10) >= (millis() - currentime))
    {
       loop here
     }

Will run for about 10 seconds. Found this out by playing with the variable and a stop watch. I tested it up to 30 seconds. Not accurate, but close enough.

Draco

OldSteve:
If the PWM code affects all three timers and "screws up" 'millis()', it's pretty pointless using 'millis()'-based timing and expecting any accuracy. I don't know of any fix for this. It's one or the other. Maybe an external time reference, triggering an interrupt? (Create your own 'millis()'.)

Also, you should declare 'credit' as 'volatile'. (Take a look at the 'attachInterrupt()' documentation in the reference.)
ie

volatile int credit;

Thank you for the volatile suggestion. I completely missed that!

Timer 0 has a default prescaler of 64. You are using a prescaler of 8. You can compensate by multiplying all your desired millis() values by 8.

You are close by multiplying 1000 x 7.5 to get one second, but if you use 8 you should be more accurate.

cattledog:
Timer 0 has a default prescaler of 64. You are using a prescaler of 8. You can compensate by multiplying all your desired millis() values by 8.

You are close by multiplying 1000 x 7.5 to get one second, but if you use 8 you should be more accurate.

Thanks, I'm a bit lost when it comes to fiddling with timers. Definitely better than my suggestion. :slight_smile:

Draco_Elessar:
Thank you for the volatile suggestion. I completely missed that!

At least I helped a little bit. :slight_smile:

cattledog:
Timer 0 has a default prescaler of 64. You are using a prescaler of 8. You can compensate by multiplying all your desired millis() values by 8.

You are close by multiplying 1000 x 7.5 to get one second, but if you use 8 you should be more accurate.

This worked. When I was doing the testing with the serial monitor. 7500 was a better value to use. But when the motors are in use in the machine 8000 was closer. Thank you!

Eventually I am going to add a GUI countdown timer. I am still reading up on how to do it properly. What I am going to have to change is multiply the values in the code by 8. Then have the screen print the right values. Or will there be more to it?

Draco

What I am going to have to change is multiply the values in the code by 8. Then have the screen print the right values. Or will there be more to it?

That should do it.