I need to send data over serial often and with minimal lag.

The project I'm working on is a digital gearbox.

What that means is I am monitoring the RPM of a spindle, and then running a stepper at an exact ratio of the spindle speed in "real time". I can't tolerate any "out of sync-ness". It needs to behave like it's mechanically coupled at least for several minutes.

The trouble is, my arduino UNO is incapable (as far as I've tried), to do what I need it to do, AND run the stepper motor.

So, I've split the tasks over two arduinos. One measures the speed of the spindle and sets the gear ratio required. It sends the speed information to the other Arduino, who's only task is to take that speed and make sure the stepper runs at it.

For now I've set the gear ratio as fixed at 1:1, so that I can see how closely the stepper motor matches the spindle.

At slower speeds (<100 rpm) it's not bad. Trouble brews when there is a change of spindle speed. The stepper speed changes too but there is a lag. Maybe about a second.

At higher speeds (>200 rpm) there is a barely noticeable difference in spindle speed compared to stepper speed. It's close. It takes about 10 seconds for the spindle to overtake the stepper by one revolution. Again ,there is a lag when the spindle speed is changed. Even if the change is somewhat gradual.

Right now my code sends the speed data 4 times per spindle revolution. As an example, at 300 rpm, the "sending" arduino needs to send the "just measured" spindle speed data to the stepper arduino around 20 times per second. I honestly don't know if this is a lot or a little.

Long story short, could you look at my code and suggest ways I could make it (data transfer) more efficient?

You could also tell me that what I am trying to do is simply impossible. I honestly don't know if it's possible or not.

I'll post the code from both arduinos in the following reply.

Thanks.

This is the sending arduino

The second loop had all the code for selecting the ratio. I've deleted it for you and set the multiplier (ratio) at 1 because it was extensive.

#include <Wire.h> // Library for I2C communication
#include <LiquidCrystal_I2C.h> // Library for LCD
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 4);
float mult, rps, stepper_sps, sps_with_sign;
long int rps_Ton, rps_Toff, rps_time, rpm;

int Bi_1_1 = 0;
int Bi_1_2 = 0;
int Bi_1_4 = 0;
int Bi_1_8 = 0;
int Bi_2_1 = 0;
int Bi_2_2 = 0;
int Bi_2_4 = 0;
int Bi_2_8 = 0;
int rev_sw = 0;
int neg = -1;

#define Bi_sw_1_1 2
#define Bi_sw_1_2 3
#define Bi_sw_1_4 4
#define Bi_sw_1_8 5
#define Bi_sw_2_1 6
#define Bi_sw_2_2 7
#define Bi_sw_2_4 8
#define Bi_sw_2_8 9

#define rps_ip 10
#define dir_rev 11

void setup() {
  neg = -1;
  //set the pin as inputs or outputs
  pinMode(Bi_sw_1_1, INPUT_PULLUP);
  pinMode(Bi_sw_1_2, INPUT_PULLUP);
  pinMode(Bi_sw_1_4, INPUT_PULLUP);
  pinMode(Bi_sw_1_8, INPUT_PULLUP);
  pinMode(Bi_sw_2_1, INPUT_PULLUP);
  pinMode(Bi_sw_2_2, INPUT_PULLUP);
  pinMode(Bi_sw_2_4, INPUT_PULLUP);
  pinMode(Bi_sw_2_8, INPUT_PULLUP);

  pinMode(rps_ip, INPUT_PULLUP);
  pinMode(dir_rev, INPUT_PULLUP);

  Serial.begin(38400);
  lcd.init();
  lcd.backlight();
}

void loop() {
  measure_rpm();
  decode_thumbwheels();
  drive_display();
}

void measure_rpm() {

  rev_sw = digitalRead(dir_rev);

  rps_Ton = pulseIn(rps_ip, LOW); // measures how long the pulse input is on

  rps_Toff = pulseIn(rps_ip, HIGH);  // measures how long the pulse input is off

  rps_time = rps_Ton + rps_Toff; //measures a quarter revolution time, since there are 4 pulses per rev.

  rps = (float)250000.00 / rps_time; //calculates revs per second

  rpm = 60 * rps;

  lcd.setCursor(13, 3); // Set the cursor on the column and row.
  lcd.print ("DIR:");
  if ((rev_sw) == LOW)
  {
    stepper_sps = (float)rps * 400.00 * mult * neg; //make the speed negative for reverse *stepper_sps gets sent to the stepper adruino.*
    lcd.setCursor(17, 3); // Set the cursor on the column and row.
    lcd.print ("-->");
  }
  else
  {
    stepper_sps = (float)rps * 400.00 * mult; //make the speed positive for forward
    lcd.setCursor(17, 3); // Set the cursor on the column and row.
    lcd.print ("<--");
  }


}

void decode_thumbwheels() {
mult = 1
}

void drive_display() { //drive the display and send the data to the stepper arduino
  lcd.setCursor(1, 0); // Set the cursor on the column and row.
  lcd.print("SPINDLE RPM:");
  lcd.setCursor(1, 2); // Set the cursor on the column and row.
  lcd.print("THREAD SELECTED:");

  if (rpm > 0 && rpm < 1000) {


    Serial.println(stepper_sps); //this sends the speed to the Arduino that runs the stepper drive

    lcd.setCursor(3, 1); // Set the cursor on the column and row.
    if ( rpm < 10) {
      lcd.print("   ");
    }
    else if ( rpm < 100) {
      lcd.print("  ");
    }
    else if ( rpm < 1000) {
      lcd.print(" ");
    }
    lcd.print(rpm); // Print the spindle RPM
    lcd.setCursor(9, 1); // Set the cursor on the column and row.
    lcd.print("RPM      "); // Print the string RPM
  }
  if (rpm <= 0)
  {
    stepper_sps = 0;
    rpm = 0;
    Serial.println(stepper_sps);  //just send zero if the rpm is less than zero
    lcd.setCursor(3, 1); // Set the cursor on the column and row.
    lcd.print("0   "); // Print the spindle RPM as zero
  }
  lcd.setCursor(9, 1); // Set the cursor on the column and row.
  lcd.print("RPM      "); // Print the spindle RPM
}

This is the receiving arduino. It just gets the data and runs the stepper.

#include <AccelStepper.h> //include the accelstepper library
const byte numChars = 32;
char receivedChars[numChars];   // an array to store the received data

boolean newData = false;
AccelStepper stepper(AccelStepper::DRIVER, 9, 10); //let accelstepper know I'm using a driver and the step and direction pins

float dataNumber = 0;             // this will be the stepper speed number
void setup() {
  Serial.begin(38400);
  stepper.setMaxSpeed(20000);
  stepper.setSpeed(dataNumber); //keep the stepper stopped until speed data available
}

void loop() {
  recvWithEndMarker();
  showNewNumber();
  run_stepper();
}

void recvWithEndMarker() {
  static byte ndx = 0;
  char endMarker = '\n';
  char rc;

  if (Serial.available() > 0) {
    rc = Serial.read();

    if (rc != endMarker) {
      receivedChars[ndx] = rc;
      ndx++;
      if (ndx >= numChars) {
        ndx = numChars - 1;
      }
    }
    else {
      receivedChars[ndx] = '\0'; // terminate the string
      ndx = 0;
      newData = true;
    }
  }
}

void showNewNumber() {
  if (newData == true) {

    dataNumber = atoi(receivedChars);   // new for this version

    newData = false;
  }
}
void run_stepper() {
//  dataNumber;
  stepper.setAcceleration(1600.0);
  stepper.moveTo(2400);
  stepper.setSpeed(dataNumber); //run the stepper at the new commanded speed
  stepper.run();  //command to make the stepper run

}

I am a little surprised that a single Uno can't handle this. An interrupt to catch pulses and calculate the RPM every n pulses. Then use RPM to calculate the delay between steps and do the stepping manually. I'd expect that to be easily in reach for one Arduino.

wildbill:
I am a little surprised that a single Uno can't handle this. An interrupt to catch pulses and calculate the RPM every n pulses. Then use RPM to calculate the delay between steps and do the stepping manually. I'd expect that to be easily in reach for one Arduino.

One uno can't handle it as I've written it, and if there is a better way to write it I would love to implement that to make this work. I've been coding for exactly 3 weeks now, so I am on the steep side of the learning curve. I will research the use of interrupts to count pulses.
BUT, wouldn't delays between pulses to a stepper block the code and prevent counting of input pulses while the delay occurs?

Eric

I think I have a solution. After each spindle revolution I need to check the stepper position with respect to it. If it's off, I need to compensate for it the error during the next revolution.

In other words, at a 1:1 ratio (let's say), with one pulse per revolution, each encoder pulse is 360 degrees of spindle rotation. I'll send the stepper to try and do 360 degrees in that time. If it only goes 359 degrees, then the error gets added to the next rotation and it will be commanded to go 360 degrees plus the error which is 361 degrees.

In other words, I need to track the position of the stepper and get it to "hunt" around the spindle rotations.

Eric

First you have to consider a stepper motor ONLY moves in integer number of steps, while your motor may rotate in an integer number of revolutions, PLUS OR MINUS a fraction of an RPM.

Paul

pulseIn() is a very crude and time consuming way to time pulses when performance matters. Using an interrupt will consume far fewer CPU cycles.

And I suspect the real cause of your poor performance is the fact that you are writing to the LCD on every iteration of loop(). It should be sufficient to update the LCD twice per second and also the update should be done completely in a single iteration of loop().

I wrote a program that could measure and control the speed of a small DC motor from 2000 to 16,000 RPM and also receive wireless messages that contained data about the speed setting - no problem all on an Attiny84 running at 8 Mhz.

...R

PS ... it may be worth delegating all the display stuff to the second Arduino

evanandel:
BUT, wouldn't delays between pulses to a stepper block the code and prevent counting of input pulses while the delay occurs?

No. That's the reason for the interrupt. Much as use of delay (or delayMicroseconds in this case I expect) is discouraged, as an initial proof of concept, that's what I'd use.

Later on you can do your timing using millis but to see if it's feasible, delays will do the job.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.