Arduino mega 2560 slow to execute commands, but ping time is ok

I'm using Arduino for a project that requires low latency. Unfortunately, I noticed that even though incoming serial commands are fast, Arduino is executing commands really slowly.

I checked how much time does it take to send and receive data from Arduino. I got the time of around 2-3ms. That is OK, but the strange thing is that when I try to do some real work: turn off a led or move a stepper motor for example., it takes at least 8ms to execute that command. I know that because I have a fast camera in the same setup (it is a microscope) and I can measure that very easily.

I read a blog post about using "true c" and how you can get a much higher speed that way (https://www.instructables.com/Arduino-is-Slow-and-how-to-fix-it/). I tried it, but with no improvements.

Here is the Arduino code:

int i;

void setup() {
  Serial.begin(500000);

  #define LED_PIN 8
  
  #define L_STEP_PIN 60 //PF6
  #define L_DIR_PIN 61 //PF7
  #define L_ENABLE_PIN 56   // Active LOW
  
  #define R_STEP_PIN 54 //PF0
  #define R_DIR_PIN 55 //PF1
  #define R_ENABLE_PIN 38   // Active LOW

  pinMode(LED_PIN  , OUTPUT);
  digitalWrite(LED_PIN, LOW);

  // Left and right motors step pins
  pinMode(L_STEP_PIN, OUTPUT);
  pinMode(R_STEP_PIN, OUTPUT);

  // Left and right motors direction pins
  pinMode(L_DIR_PIN, OUTPUT);
  pinMode(R_DIR_PIN, OUTPUT);

  pinMode(L_ENABLE_PIN, OUTPUT);
  pinMode(R_ENABLE_PIN, OUTPUT);

  digitalWrite(L_ENABLE_PIN, LOW);
  digitalWrite(R_ENABLE_PIN, LOW);

}

void loop() {
  if (Serial.available() > 0)
  {
    Serial.read();
    //L_DIR_PIN, HIGH;
    PORTF |= _BV(PF7);
    
    //R_DIR_PIN, HIGH;
    PORTF |= _BV(PF1);
    
    for (i = 0; i < 100; i++)
    {
      //L_STEP_PIN, HIGH;
      PORTF |= _BV(PF6);
      
      //R_STEP_PIN, HIGH;
      PORTF |= _BV(PF0);
      
      delayMicroseconds(200);
      
      //L_STEP_PIN, LOW;
      PORTF &= ~_BV(PF6);
      
      //R_STEP_PIN, LOW;
      PORTF &= ~_BV(PF0);
    }
    
  }
}

And here is the python code:

try:
    arduino = open_serial_port(baudrate=500000, timeout=None)
except Exception as e:
    raise e

sleep(2)
print(f'Arduino Initialized')

print('Starting Camera...')
cam = camera.Camera()
cam.run()
sleep(1)

img1 = cam.get_img_data()
cv2.imwrite(f'focus_test/test_01.tif', img1)

######## START #########
start = time()

write_i8(arduino, 1)

# take an image
sleep(0.008)
img2 = cam.get_img_data()

elapsed = (time() - start) * 1000

print(f'Elapsed = {elapsed} ms')

cv2.imwrite(f'focus_test/test_02.tif', img2)

I first thought that the main problem is in the Serial.available() or Serial.read() function. But, the ping time using same functions is ok, so that can't be the culprit. It almost seems like Arduino is behaving normally, but for some reason, there is a lag in digital ports. I'm also using ramps 1.6 in the same setup. Can that be a reason?

You've got 20 milliseconds of delay in your response to every character received.

1 Like

Can this delay be shortened somehow? 20ms seems a lot

I don't know - you wrote it!

Hi, welcome to the forum.

You can move the control of the stepper motor to a external module, or use a library that don't use interrupts, such as the AccelStepper library.

I think you need your own interrupt routine for receiving serial data. In that interrupt routine you can turn a pin HIGH or LOW.
There is probably some code for that at avrfreaks.net in the "Projects" section.
A interrupt will take about 2.6 µs (Nick Gammon about interrupts) plus the code inside the interrupt. There are already two interrupts running: for the millis() function and incoming/outgoing messages from/to the computer.

A fast response and a stepper motor don't go well together. Can you turn on a solenoid actuator ?

Isn't that Serial.available () already calls an interrupt when a character arrives? Why would I need to write my interrupt routine? The code inside that if statement should be executed immediately.

Sorry, this is a lot of new information and I'm just trying to wrap my head around this

No, a character arrives, it is read by an interrupt written to a circular buffer (if there is room available) and the circular buffer pointer is updated.
Serial.available simply returns the difference between the circular head pointer and the tail pointer.

In this test I'm only sending one character. And it waits 8ms for the first step. I know that it takes 20ms for the whole loop to execute. That is not the problem

Also, people are reporting response rate in uS for the same code. For example: Example of interrupt-based version of reading serial port - #22 by nickgammon

I don't know how you're measuring the 8ms

I have a setup with a microscope and a camera :slight_smile: In python code, you can see that I take two images.

I take an image - > write to Arduino -> pause for a least 8ms -> take another image (camera is also pausing a little so it is more like 10-12ms)

Also, I already measured ping time. I know that Arduino responds in the range of 2-3ms... but still, stepper motors don't move till 8ms have passed

Stepper motors are mechanical - they have to overcome inertia.

I don't understand this - there is no reference in your code to ping.

Does the response time change if you do all the port output before the Serial.read? Since you are not using the data received, it doesn't really matter when you read it.

This is a fair point :slight_smile: So I tested it with only one 1/16 microstep. The results are the same. Also, as I already said, I tested the same code with LED and got same results :slight_smile:

"I don't understand this - there is no reference in your code to ping."
It is a basic test with Serial.read() and Serial.write() on Arduino side and pyseria.read and write on python side.

And the response time of this is measured. . . .how?

I tested it. Didn't change the results unfortunately :frowning:

Python code:

from time import sleep, time
from robust_serial.utils import open_serial_port
from robust_serial import write_i8, read_i8

try:
    arduino = open_serial_port(baudrate=115200, timeout=None)
except Exception as e:
    raise e

sleep(2)
print(f'Arduino Initialized')

times = []
n = 1000

for i in range(n):
    start = time()

    write_i8(arduino, 1)
    read_i8(arduino)

    elapsed = (time() - start)*1000
    times.append(elapsed)

average_time = (sum(times)/n)

print(f'Ping = {average_time} ms')

Arduino code:

#include <Arduino.h>

#include "order.h"
#include "parameters.h"

int8_t received_byte;

void setup() {
  Serial.begin(SERIAL_BAUD);

  pinMode(LED_PIN  , OUTPUT);
  digitalWrite(LED_PIN, HIGH);

}

void loop() {
  if (Serial.available() > 0)
  {
    // read the incoming byte:
    received_byte = Serial.read();
    Serial.write(received_byte);
  }
}

...and that's why we ask you to post all the code

The code is working. It is not problem in that :slight_smile:

#ifndef PARAMETERS_H
#define PARAMETERS_H

#define SERIAL_BAUD 115200
#define LED_PIN 8

#endif

I'm still unclear how this is being measured - this is purely on how the host measures the latency?

How do you know that the (unnamed) host isn't the problem?

I wrote a simple sketch to get a Mega (I tested it on a Mega ADK) to measure its own latency.
Feel free to pick holes in the code - I'm a looong G&T and a half bottle of Nero d'Avola down :smiley:

// Simple Mega Mega ADK serial latency measurement sketch
// Requires only one jumper wire between TX's tx pin and RX's rx pin
// e.g Tx2 to Rx1

const int32_t LINE_SPEED = 230400;
#define TX   Serial2
#define RX   Serial1
#define HOST Serial

const char pingString [] = "0123456789"; // UNIQUE character string - no repeats
const int length = strlen (pingString);

void setup() 
{
  uint32_t timestamps [length];
  delay (500);
  
  HOST.begin (1000000);  // Let's not introduce unnecessary timing uncertainty!
  TX.begin (LINE_SPEED);
  RX.begin (LINE_SPEED);
  HOST.println ("======");
  
  bool finished      = false;
  uint32_t startTime = micros ();
  uint32_t endTime;
  TX.print (pingString);

  while (!finished) {
    if (RX.available ()) {
      char rx = RX.read ();
      uint32_t stamp = micros ();
      for (int i = 0; i < length; i ++) {
        if (rx == pingString [i]) {
          timestamps [i] = stamp;
          if (i == (length - 1))
            finished = true;
        }
      }
    }
  }
  
  for (int i = 0; i < length; i++) {
    uint32_t elapsed = timestamps [i] - startTime;
    if (i > 0) {
      HOST.print (F("delta "));
      HOST.print (timestamps [i] - timestamps [i - 1]);
      HOST.print (F("us "));
    }
    HOST.print (F(" absolute "));
    HOST.print (elapsed);
    HOST.println (F("us"));
  }
}

void loop () {}