Stepper PWM measurement

Hi all,

I am creating an Arduino Mega2560 Torch Height Controller for my CNC plasma table. The CNC is a Langmuir Crossfire table, which runs on Mach3 using effectively an RNR Motion breakout board (BOB) (https://buildyourcnc.com/item/electronicsAndMotors-electronic-component-breakout-Mach3-USB-Board). The board is powered from the laptop USB.

I am running a NEMA17 Stepper off a TB6600 driver. When I connect the TB6600 directly to the BOB the stepper works perfectly so I know Mach3 and BOB are working correctly (without the Arduino).

Now, I am trying to have the BOB output to the Arduino, and the Arduino pass through the signals to the TB6600. Attached is the wiring diagram and here is my code thus far:

#define ZdirectionIN 3 //input DIR from BOB
#define ZpulseIN 2     //input PUL from BOB
#define ZdirectionOUT 24  //Output DIR to TB6600
#define ZpulseOUT 22      //Output PUL to TB6600
boolean Zdirection = LOW; //direction state
boolean Zpulse = LOW; //pulse state

volatile unsigned long timer_start; //micros when ZpulseIN goes HIGH
volatile int pulse_time; //this is the pulse width in micros

void setup() {
  pinMode(ZdirectionIN, INPUT);
  pinMode(ZpulseIN, INPUT);
  pinMode(ZdirectionOUT, OUTPUT);
  pinMode(ZpulseOUT, OUTPUT);
  timer_start = 0;
  attachInterrupt(digitalPinToInterrupt(ZpulseIN), ZtriggerPUL, CHANGE);
  Serial.begin(115200);
}
void loop() {
  if (pulse_time != 0) {
    Zpulse = !Zpulse; //inverts pulse state
    digitalWrite(ZdirectionOUT, Zdirection);
    digitalWrite(ZpulseOUT, Zpulse);
    delayMicroseconds(pulse_time);
    Serial.print("pulse_time: ");
    Serial.println(pulse_time);
  }
}

void ZtriggerPUL()
{
  Zdirection = digitalRead(ZdirectionIN); //read the direction
  if (digitalRead(ZpulseIN) == HIGH)      
  {
    timer_start = micros();
  }
  else
  {
    if (timer_start != 0)  //only execute if the timer has been started previously
    {
      pulse_time = ((volatile int)micros() - timer_start); //difference between timer_start and micros() is the PWM pulse width.
      timer_start = 0;        //restart the timer
    }
  }
}

Desired state:

  • Nothing happens unless I click the up/down arrows in Mach3
  • When I click and hold the up/down arrows in Mach3, the stepper moves accordingly, then stops when I release the arrow.

Actual state:

  • Nothing happens until I click the up arrow in Mach3
  • When I click the UP arrow, the stepper moves up but when I release the arrow, the stepper moves down, until either:
  • I click the UP arrow again, in which case it moves up again.
  • Or the lead screw bottoms out, then the stepper instantly reverses direction and a much faster speed, and then bottoms out at the other extreme and goes back down at the original speed
  • When I click the DOWN arrow, nothing happens

Also, when this line is as shown: “if (digitalRead(ZpulseIN) == HIGH)”
pulse-time = ~1000 micros

When this line is “if (digitalRead(ZpulseIN) == LOW)”
pulse-time = ~24 micros. I thought this was interesting.

Any help is appreciated!! I read maybe I need to implement a circular buffer??

Wiring_Diagram-Zaxis.pdf (180 KB)

I think your logic is broken - when the interrupt comes in and ZpulseIN is HIGH, it starts a timer.
When the interrupt comes in and ZpulseIN is low it captures the elapsed time in pulse_time.

That seems reasonable, but the logic in loop will then repeatedly output pulses of this length
forever until a new interrupt comes in.

You need to understand how to communicate between the main code and the ISR correctly.

Firstly declare all the relevant variables volatile - you missed Zdirection.

Secondly design a protocol for the ISR to request the main code for a new pulse, and for the
main code to cancel that request when it does it. Typically you'd use a single bool variable for
this, set HIGH only by the ISR and set LOW only by the main code.

Since the Arduino MEGA is an 8 bit processor, you also need to use critical sections for any
communication between main code and ISR that's more than a single one-byte variable.

For instance the main code might do something like:

  noInterrupts() ;  // inhibit interrupts to see a consistent state of the volatile variables
  bool request_seen = pulse_request ;
  int pulse_duration = pulse_time ;
  bool direction = Zdirection ;
  pulse_request = false ;  // prevent duplicate responses
  interrupts() ;    // let interrupts fly again.

  // now can handle our side of things at leisure using local variables only
  if (request_seen)
  {
    Zpulse = !Zpulse ;
    if (Zpulse)  // only output half the pulses
    {
      digitalWrite (ZdirectionOUT, direction) ;
      digitalWrite (ZpulseOUT, HIGH) ;
      delayMicroseconds (pulse_duration) ;
      digitalWrite (ZpulseOut, LOW) ;
    }
  }

There are many issues still - you can't just output every other pulse, you need to count the step position
taking direction into account, and only output pulses if that count varies by 2 from the last time you output
a pulse.

There's no need at all to time the pulses BTW, only the rising edge of the step signal matters for timing.

Thanks so much for you help! I follow most of what you said, but could use clarification on the following:

MarkT:
There are many issues still - you can't just output every other pulse, you need to count the step position
taking direction into account, and only output pulses if that count varies by 2 from the last time you output
a pulse.

Why do I want to output every other pulse - don't I want to output a pulse for every pulse that is received?

There's no need at all to time the pulses BTW, only the rising edge of the step signal matters for timing.

My intent is to only time pulses to control the stepper speed, via this line in the main loop: delayMicroseconds(pulse_duration);
Am I inadvertently timing something else?

I've spent some time trying to implement your recommendations and below is what I came up with. Now this is what happens:

  • Nothing happens until I click the up arrow in Mach3
  • When I click the UP arrow, the stepper moves up and stalls at the end of the lead screw. I have to reset to stop it.
  • When I click the DOWN arrow, nothing happens
#define ZdirectionIN 3 //input DIR from BOB
#define ZpulseIN 2     //input PUL from BOB
#define ZdirectionOUT 24  //Output DIR to TB6600
#define ZpulseOUT 22      //Output PUL to TB6600

boolean Zpulse = LOW; //pulse state
volatile boolean Zdirection = LOW; //direction state
volatile bool pulse_request = false;
volatile unsigned long timer_start; //micros when ZpulseIN goes HIGH
volatile unsigned int pulse_time; //this is the pulse width in micros
volatile unsigned long step_count;

void setup() {
  pinMode(ZdirectionIN, INPUT);
  pinMode(ZpulseIN, INPUT);
  pinMode(ZdirectionOUT, OUTPUT);
  pinMode(ZpulseOUT, OUTPUT);
  timer_start = 0;
  attachInterrupt(digitalPinToInterrupt(ZpulseIN), ZtriggerPUL, CHANGE);
  Serial.begin(115200);
}

void loop() {

  noInterrupts() ;  // inhibit interrupts to see a consistent state of the volatile variables
  bool request_seen = pulse_request ;
  int pulse_duration = pulse_time ;
  boolean direction = Zdirection ;
  unsigned long count = step_count ;
  step_count -- ;
  pulse_request = false ;  // prevent duplicate responses
  interrupts() ;    // let interrupts fly again.

  if (request_seen)
  {
    while (count > 0)
    {
      Zpulse = !Zpulse; //inverts pulse state
      if (Zpulse) // only output half the pulses
      {
        digitalWrite(ZdirectionOUT, direction);
        digitalWrite(ZpulseOUT, HIGH);
        delayMicroseconds(pulse_duration);
        Serial.print("pulse_duration: ");
        Serial.println(pulse_duration);
        digitalWrite (ZpulseOUT, LOW) ;
      }
      count --;
    }
  }
}

void ZtriggerPUL()
{
  if (digitalRead(ZpulseIN) == HIGH)
  {
    Zdirection = digitalRead(ZdirectionIN); //read the direction
    timer_start = micros();
  }
  else
  {
    if (timer_start != 0)  //only execute if the timer has been started previously
    {
      pulse_time = ((volatile int)micros() - timer_start); //difference between timer_start and micros() is the PWM pulse width.
      pulse_request = true;
      step_count ++;
      timer_start = 0;        //restart the timer
    }
  }
}