teemuatlut / TMCStepper

MIT License
515 stars 201 forks source link

TMC 2130 with Teensy 4.1 Intermittent SPI Errors #179

Open graham-jessup opened 3 years ago

graham-jessup commented 3 years ago

I'm using a variant of the "Stallguard" example to tune the stallguard settings of my TMC 2130 SPI driver (from BigTreeTech), but I am having an SPI issue where the value for drv_status.sg_result returns 0 about half the time. SPI communication appears to be working for the most part, I can intialize the driver and run the stepper, but the real-time monitoring of the stallguard value is not very useful with all the zeros interspersed.

Am I missing a larger compatibility issue between the TMCStepper library and the Teensy 4.1?

What could be causing the SPI communication to intermittently fail?

Hardware: Teensy 4.1 BigtreeTech TMC2130 V3.0 SPI 24V power supply Nema 17 Stepper

Here's an example of what I'm seeing in the serial monitor image

This is what I'm seeing with a logic analyzer reading the SPI communication when the drv_status.sg_result returns zero. image

Here's a photo of my setup: image

And finally the code:

/**
   Author Teemu Mäntykallio

   Plot TMC2130 or TMC2660 motor load using the stallGuard value.
   You can finetune the reading by changing the STALL_VALUE.
   This will let you control at which load the value will read 0
   and the stall flag will be triggered. This will also set pin DIAG1 high.
   A higher STALL_VALUE will make the reading less sensitive and
   a lower STALL_VALUE will make it more sensitive.

   You can control the rotation speed with
   0 Stop
   1 Resume
   + Speed up
   - Slow down

//This example has been modified to use Accelstepper library and to work on a Teensy 4.1 using Hardware SPI.

*/
#include <TMCStepper.h>

#define MAX_SPEED        40 // In timer value
#define MIN_SPEED      1000

#define STALL_VALUE      9 // [-64..63] ---------------------------------------------------------------------------------<<<<<

#define EN_PIN           32 // Enable
#define DIR_PIN          31 // Direction
#define STEP_PIN         25 // Step
#define CS_PIN           10 // Chip select
#define SW_MOSI          26 // Software Master Out Slave In (MOSI)
#define SW_MISO          33 // Software Master In Slave Out (MISO)
#define SW_SCK           27 // Software Slave Clock (SCK)
#define DIAG1_PIN        30 // Diag1 Pin

#define R_SENSE 0.11f // Match to your driver
// SilentStepStick series use 0.11
// UltiMachine Einsy and Archim2 boards use 0.2
// Panucatt BSD2660 uses 0.1
// Watterott TMC5160 uses 0.075

unsigned long currentMillis;
unsigned long startMillis;
const unsigned long stallInterval = 2000;

// Select your stepper driver type
TMC2130Stepper driver(CS_PIN, R_SENSE);                           // Hardware SPI
//TMC2130Stepper driver(CS_PIN, R_SENSE, SW_MOSI, SW_MISO, SW_SCK); // Software SPI
//TMC5160Stepper driver(CS_PIN, R_SENSE);
//TMC5160Stepper driver(CS_PIN, R_SENSE, SW_MOSI, SW_MISO, SW_SCK);

using namespace TMC2130_n;

// Using direct register manipulation can reach faster stepping times
//#define STEP_PORT     PORTF // Match with STEP_PIN
//#define STEP_BIT_POS      0 // Match with STEP_PIN

//ISR(TIMER1_COMPA_vect) {
//  //STEP_PORT ^= 1 << STEP_BIT_POS;
//  digitalWrite(STEP_PIN, !digitalRead(STEP_PIN));
//}

///ACCEL STEPPER STUFF

#include <AccelStepper.h>
AccelStepper stepper = AccelStepper(stepper.DRIVER, STEP_PIN, DIR_PIN);

//END ACCELSTEPPER STUFF

void setup() {
  //  bool shaft = true; //This controls stepper direction

  SPI.begin();
  Serial.begin(250000);         // Init serial port and set baudrate
  while (!Serial);              // Wait for serial port to connect
  Serial.println("\nStart...");

  pinMode(EN_PIN, OUTPUT);
  pinMode(STEP_PIN, OUTPUT);
  //  pinMode(CS_PIN, OUTPUT);
  pinMode(DIR_PIN, OUTPUT);
  //  pinMode(MISO, INPUT_PULLUP);
  digitalWrite(EN_PIN, LOW);
  pinMode(DIAG1_PIN, INPUT_PULLUP);

  driver.begin();
  driver.toff(4); //
  driver.blank_time(24);
  driver.rms_current(750); // mA
  driver.microsteps(16);
  driver.TCOOLTHRS(0xFFFFF); // 20bit max. TCOOLTHRS is the speed above which Stallguard will be enabled (High TCOOLTHRS = low speed)

  driver.THIGH(0);
  driver.semin(0); //Coolstep threshold - SG value below which coolstep will turn off. Set to 0 to disable coolstep.
  driver.semax(2);
  driver.sedn(0b01);
  driver.sgt(STALL_VALUE);
  driver.diag1_stall(1); // Turn on (?) Diag 1 when Stallguard hits 0

  // Accelstepper Setup
  stepper.setMaxSpeed(3733); // 100mm/s @ 80 steps/mm 3733 step/sec = 70rpm
  stepper.setAcceleration(4000); // 2000mm/s^2
  stepper.setEnablePin(EN_PIN);
  stepper.setPinsInverted(false, false, true);
  //stepper.enableOutputs();

  //  bool shaft = false; //This controls stepper direction

  // Set stepper interrupt
  //  {
  //    cli();//stop interrupts
  //    TCCR1A = 0;// set entire TCCR1A register to 0
  //    TCCR1B = 0;// same for TCCR1B
  //    TCNT1  = 0;//initialize counter value to 0
  //    OCR1A = 256 + (20 * 3); // = (16*10^6) / (1*1024) - 1 (must be <65536)
  //    // turn on CTC mode
  //    TCCR1B |= (1 << WGM12);
  //    // Set CS11 bits for 8 prescaler
  //    TCCR1B |= (1 << CS11);// | (1 << CS10);
  //    // enable timer compare interrupt
  //    TIMSK1 |= (1 << OCIE1A);
  //    sei();//allow interrupts
  //  }
}

bool shaft = true;
bool homed = false;
bool homing = false;
bool dir = false; //Direction true = tighten/close --- Direction false = loosen/open

void loop() {
  //  driver.shaft(shaft);
  static uint32_t last_time = 0;
  uint32_t ms = millis();
  currentMillis = millis();
  //  shaft = !shaft;
  //  driver.shaft(shaft);

  while (Serial.available() > 0) {
    int8_t read_byte = Serial.read();
#ifdef USING_TMC2660
    if (read_byte == '0')      {
      TIMSK1 &= ~(1 << OCIE1A);
      driver.toff(0);
    }
    else if (read_byte == '1') {
      TIMSK1 |=  (1 << OCIE1A);
      driver.toff(driver.savedToff());
    }
#else
    if (read_byte == '0')      {
      //      TIMSK1 &= ~(1 << OCIE1A);
      stepper.stop();
      stepper.disableOutputs();
    }
    else if (read_byte == '1') {
      //      TIMSK1 |=  (1 << OCIE1A);
      //      digitalWrite( EN_PIN,  LOW );
      homed = false;
      homing = false;
      dir = false; //Direction true = tighten/close --- Direction false = loosen/open
      //      stepper.setCurrentPosition(0);
      //      Serial.println(stepper.distanceToGo());
      stepper.enableOutputs();
      Serial.println("Recv 1");

    }
#endif
    //    else if (read_byte == '+') {
    //      if (OCR1A > MAX_SPEED) OCR1A -= 20;
    //    }
    //    else if (read_byte == '-') {
    //      if (OCR1A < MIN_SPEED) OCR1A += 20;
    //    }
  }

  if ((ms - last_time) > 100) { //run every 0.1s
    last_time = ms;

    DRV_STATUS_t drv_status{0};
    drv_status.sr = driver.DRV_STATUS();

    Serial.print("0");
    Serial.print(",");
    Serial.println(drv_status.sg_result, DEC);
//    Serial.print(",");
    //    Serial.print(drv_status.drv_status, DEC);
    //    Serial.print(",");
//    Serial.println(driver.cs2rms(drv_status.cs_actual), DEC);

  }

  if (stepper.distanceToGo() == 0) {
    stepper.disableOutputs();
    //      //delay(2000);
    //      stepper.move(-3200 * 10); // Open valve 10 rotations to "top out"
    //      stepper.enableOutputs();
    //      homing = true;
  }

  if (stepper.distanceToGo() == 0 && homed == false && homing == false) {
    stepper.disableOutputs();
    //delay(2000);
    stepper.move(-3200 * 10); // Open valve 10 rotations to "top out"
    startMillis = currentMillis;
    stepper.enableOutputs();
    homing = true;
    dir = false; //Opening valve
  }

  int diagStatus = digitalRead(DIAG1_PIN);
  if (diagStatus == LOW && currentMillis - startMillis >= stallInterval && dir == false && homed == false) {
    digitalWrite( EN_PIN, HIGH );
    stepper.setCurrentPosition(0);
    //    stepper.stop(); //Stop with deacceleration
    startMillis = currentMillis;  //add current time to counter to reset interval.
    //    shaft = !shaft;
    //    driver.shaft(shaft);
    //    digitalWrite( EN_PIN, LOW );
    stepper.move(3200 * 10); // Move 10 rotations to "close" the valve and find home
    dir = true;
    stepper.enableOutputs();
  }

  if (diagStatus == LOW && currentMillis - startMillis >= stallInterval && dir == true && homed == false) {
    digitalWrite( EN_PIN, HIGH );
    startMillis = currentMillis;  //add current time to counter to reset interval.
    stepper.setCurrentPosition(0);
    stepper.moveTo(-3200 * 1); // Move two rotations "open" on the valve
    homing = false;
    homed = true;
    dir = false;
    stepper.enableOutputs();
  }
  stepper.run();
}
teemuatlut commented 3 years ago

You should decouple the step generation from the communication. What happens is the motor very briefly stops when the MCU executes everything else in the loop and that throws off the readings. You can use an interrupt like in the original example.

graham-jessup commented 3 years ago

The communication error occurs even when not driving the motor, the serial monitor screenshot above is while no steps are being generated. Normal behavior for sg_result would be a constant value (799 in this case), I think, but it's still being replaced with zeros intermittently.

graham-jessup commented 3 years ago

I figured out the issue: The Teensy 4.1 runs SPI communication too fast for the TMC2130, and it results in frequent failed communications where the TMC2130 returns "0" for the stallguard value. Specifically, the chip select (CS) pin is switched too fast between SPI datagrams.

Solution (using software SPI): In "TMC2130Stepper.cpp" I added a delayMicroseconds(1); line to the switchCSPin function (line 63) void TMC2130Stepper::switchCSpin(bool state) { digitalWrite(_pinCS, state); delayMicroseconds(1); //GrahamJ Edit - slows down CS signal <--- Added delay here }

I also experimented with adding delays to the SW_SPI.cpp file to slow down the SPI clock frequency, but that ended up not being necessary.

I have not tried hardware SPI with this fix, I don't know if it uses the same switchCSPin function. EDIT: This fix also works for hardware SPI on the Teensy 4.1.