MEGA: same code lines takes different times to execute each iteration

Hello everybody,

I don't know why each interation of this loop takes different times (look at the clock):

long result = 0;
int bit_status;

for (int i = 23; i >= 0; --i) {
  PORTD |= (1 << clk_pin);//WRITE HIGH ON CLOCK
  bit_status = PINK & (1 << dat_pin);//READ DATA <-----------------------------
  if(bit_status) result |= (1L<<i);//<-----------------------------------------------------
  PORTD &= ~(1 << clk_pin);//WRITE LOW ON CLOCK
}

PORTD |= (1 << clk_pin);//WRITE HIGH ON CLOCK
__asm__("nop");
__asm__("nop");
PORTD &= ~(1 << clk_pin);//WRITE LOW ON CLOCK

And, if I use this other code (which is worse) i get that very interesting shape in which every iteration takes less time:

long result = 0;
int bit_status;

for (int i = 23; i >= 0; --i) {
  PORTD |= (1 << clk_pin);//WRITE HIGH ON CLOCK
  result |= ((PINK >> dat_pin) & 1L) << i;//<------------------------------------------
  PORTD &= ~(1 << clk_pin);//WRITE LOW ON CLOCK
}

PORTD |= (1 << clk_pin);//WRITE HIGH ON CLOCK
__asm__("nop");
__asm__("nop");
PORTD &= ~(1 << clk_pin);//WRITE LOW ON CLOCK

Can anyone enlighten me?

If in an iteration it receives a logic 1 it doesn't last longer.
But, in the next iteration, it will take longer no matter what it receives.

Me neither, but it might be related to something in your sketch that was left out of your post, and this may in turn depend on the hardware of the unspecified board you're using.

Context is everything!

Thank you for your answer. The board is the MEGA (it was specified in the title).

The entire code is:

#define LOADCELL_1_SCK_PIN  18//VI D3
#define LOADCELL_2_SCK_PIN  19//HI D2
#define LOADCELL_3_SCK_PIN  20//HD D1
#define LOADCELL_4_SCK_PIN  21//VD D0

#define LOADCELL_1_DOUT_PIN  A12//VI K4
#define LOADCELL_2_DOUT_PIN  A13//HI K5
#define LOADCELL_3_DOUT_PIN  A14//HD K6
#define LOADCELL_4_DOUT_PIN  A15//VD K7

const int dat_pin = 4;//K
const int clk_pin = 3;//D

/*#define BIT_CHECK(port, pin) ((port) & (1 << (pin)))

result |= (BIT_CHECK(PINK, dat_pin) != 0) << i;*/

void setup() {
  
  pinMode(LOADCELL_1_DOUT_PIN, INPUT);
  pinMode(LOADCELL_2_DOUT_PIN, INPUT);
  pinMode(LOADCELL_3_DOUT_PIN, INPUT);
  pinMode(LOADCELL_4_DOUT_PIN, INPUT);

  pinMode(LOADCELL_1_SCK_PIN, OUTPUT);
  pinMode(LOADCELL_2_SCK_PIN, OUTPUT);
  pinMode(LOADCELL_3_SCK_PIN, OUTPUT);
  pinMode(LOADCELL_4_SCK_PIN, OUTPUT);

  pinMode(A8, OUTPUT);//K0

  Serial.begin(115200);
}

//datasheet nos da los tiepos mĂ­nimos (200ns).

void loop() {

  if( !(PINK & (1 << dat_pin)) ) {//READ

    //PORTK |= 1;//A8 HIGH

    long result = 0;
    int bit_status;

    //Las lecturas no pueden ser interrumpidas
    //noInterrupts(); //quizás poner prioridad top.

    //noInterrupts();
    
    for (int i = 23; i >= 0; --i) {
      PORTD |= (1 << clk_pin);//WRITE HIGH 
      //result |= (PINK & (1 << dat_pin))?(1L<<i):0; //READ
      //result |= ((PINK & (1 << dat_pin))?1L:0)<<i; //READ PEOR
      //result |= ((PINK >> dat_pin) & 1L) << i;//MUCHO PEOR
      //result |= (long)((PINK & (1 << dat_pin)) != 0) << i; //peor
      bit_status = PINK & (1 << dat_pin); //esta es la linea que tarda más si antes recibí un uno.
      if(bit_status) 
        result |= (1L<<i);
      //result |= bit_status?(1L<<i):0;
      PORTD &= ~(1 << clk_pin);//WRITE LOW
    }
    //quizas le cuesta leer
    //falla la prediccion
    //

    //Ciclos extra que determinan la ganancia de la iguiente lectura: 1 ciclo = 128.
    PORTD |= (1 << clk_pin);//WRITE HIGH
    __asm__("nop");
    __asm__("nop");//dos está bien.
    PORTD &= ~(1 << clk_pin);//WRITE LOW

    //Volvemos a habilitar interrupts.
    //interrupts();

    //Rellena con unos o ceros por signo.
    result |= (result & 1UL << 23 ? 0xFFUL : 0x00UL) << 24;

    //PORTK &= ~1;//A8 LOW

    Serial.println(result);   
  }
   
}

You have ALL the the reason. If I delete the line "Serial.println(result); " it all works fine.
WHY??
Plus, I can't check the data :..(
How can I maintain the Serial.print without increasing times?

Ah.

Use Serial.flush();
But it's a bit of a kludge since the Serial stuff will still take time.

Well I don't care if the Serial takes its time AFTER reading all the Data. What I don't want is that it makes the iterations that are before it, last longer I don't know why...

If you want Serial to consistently take the same amount of time to send data, you will need to always send the same number of characters, as well as using Serial.flush()

Serial.flush() doesn't help :frowning:

As The iterations only get "extended" if the previous data bit received was HIGH, I don't don't think it is the Serial actually sending data what slows the iteration.

Don't youy think so?
Very misterious :smiling_face_with_tear:

What if you uncomment your noInterrupts() and interrupt() statements.
The Serial.println() call itself doesn't take much time. but the actual transfer is done by interrupts.

Doesn't change anything.

Serial does use interrupts to transfer data from the transmit buffer to the Serial hardware, that will affect very tight timing.

1 Like

Well, since the problem went away when you removed the Serial print, evidently it's related to the latter. And yes, I think it's the ISR that handles the UART communication that messes up your timing.

In your place I'd try putting a Serial.flush() followed by a delay(50) or so after the Serial.println and see if that helps. If so, you know you need to somehow allow the Serial buffer to do its thing before you do critical timing stuff again.
Alternatively you could postpone the Serial.print to a moment when you expect the controller to have more time.
Another solution involves moving from the Mega to something that can send UART stuff using DMA, but frankly, that's likely overkill.

Btw, is it intentonal that your critical timing routine runs as long as the input pin is LOW, and not just once?

I have removed Serial.begin() and obviously the Serial.println() and it continues "extending" the readings (I have declared result as a global):

#define LOADCELL_1_SCK_PIN  18//VI D3
#define LOADCELL_2_SCK_PIN  19//HI D2
#define LOADCELL_3_SCK_PIN  20//HD D1
#define LOADCELL_4_SCK_PIN  21//VD D0

#define LOADCELL_1_DOUT_PIN  A12//VI K4
#define LOADCELL_2_DOUT_PIN  A13//HI K5
#define LOADCELL_3_DOUT_PIN  A14//HD K6
#define LOADCELL_4_DOUT_PIN  A15//VD K7

const int dat_pin = 4;//K
const int clk_pin = 3;//D

/*#define BIT_CHECK(port, pin) ((port) & (1 << (pin)))

result |= (BIT_CHECK(PINK, dat_pin) != 0) << i;*/

void setup() {
  
  pinMode(LOADCELL_1_DOUT_PIN, INPUT);
  pinMode(LOADCELL_2_DOUT_PIN, INPUT);
  pinMode(LOADCELL_3_DOUT_PIN, INPUT);
  pinMode(LOADCELL_4_DOUT_PIN, INPUT);

  pinMode(LOADCELL_1_SCK_PIN, OUTPUT);
  pinMode(LOADCELL_2_SCK_PIN, OUTPUT);
  pinMode(LOADCELL_3_SCK_PIN, OUTPUT);
  pinMode(LOADCELL_4_SCK_PIN, OUTPUT);

  pinMode(A8, OUTPUT);//K0

  //Serial.begin(9600);
}

//datasheet nos da los tiepos mĂ­nimos (200ns).
long result;
void loop() {

  if( !(PINK & (1 << dat_pin)) ) {//READ

    //PORTK |= 1;//A8 HIGH

    result = 0;
    int bit_status;

    //Las lecturas no pueden ser interrumpidas
    //noInterrupts(); //quizás poner prioridad top.

    //noInterrupts();
    for (int i = 23; i >= 0; --i) {
      PORTD |= (1 << clk_pin);//WRITE HIGH 
      //result |= (PINK & (1 << dat_pin))?(1L<<i):0; //READ
      //result |= ((PINK & (1 << dat_pin))?1L:0)<<i; //READ PEOR
      //result |= ((PINK >> dat_pin) & 1L) << i;//MUCHO PEOR
      //result |= (long)((PINK & (1 << dat_pin)) != 0) << i; //peor
      bit_status = PINK & (1 << dat_pin); //esta es la linea que tarda más si antes recibí un uno.
      if(bit_status) 
        result |= (1L<<i);
      //result |= bit_status?(1L<<i):0;
      PORTD &= ~(1 << clk_pin);//WRITE LOW
    }
    //quizas le cuesta leer
    //falla la prediccion
    //

    //Ciclos extra que determinan la ganancia de la iguiente lectura: 1 ciclo = 128.
    PORTD |= (1 << clk_pin);//WRITE HIGH
    __asm__("nop");
    __asm__("nop");//dos está bien.
    PORTD &= ~(1 << clk_pin);//WRITE LOW

    //Volvemos a habilitar interrupts.
    //interrupts();

    //Rellena con unos o ceros por signo.
    result |= (result & 1UL << 23 ? 0xFFUL : 0x00UL) << 24;

    //PORTK &= ~1;//A8 LOW

       
    
    
  }
  //Serial.println(result);
}

image

So it was a coincidence that removing the print line it got corrected. It's not the Serial (it would have been "corrected" with the noInterrupts() I think).

It's something VERY VERY strange!

And if result is local it gets corrected:
image

#define LOADCELL_1_SCK_PIN  18//VI D3
#define LOADCELL_2_SCK_PIN  19//HI D2
#define LOADCELL_3_SCK_PIN  20//HD D1
#define LOADCELL_4_SCK_PIN  21//VD D0

#define LOADCELL_1_DOUT_PIN  A12//VI K4
#define LOADCELL_2_DOUT_PIN  A13//HI K5
#define LOADCELL_3_DOUT_PIN  A14//HD K6
#define LOADCELL_4_DOUT_PIN  A15//VD K7

const int dat_pin = 4;//K
const int clk_pin = 3;//D

/*#define BIT_CHECK(port, pin) ((port) & (1 << (pin)))

result |= (BIT_CHECK(PINK, dat_pin) != 0) << i;*/

void setup() {
  
  pinMode(LOADCELL_1_DOUT_PIN, INPUT);
  pinMode(LOADCELL_2_DOUT_PIN, INPUT);
  pinMode(LOADCELL_3_DOUT_PIN, INPUT);
  pinMode(LOADCELL_4_DOUT_PIN, INPUT);

  pinMode(LOADCELL_1_SCK_PIN, OUTPUT);
  pinMode(LOADCELL_2_SCK_PIN, OUTPUT);
  pinMode(LOADCELL_3_SCK_PIN, OUTPUT);
  pinMode(LOADCELL_4_SCK_PIN, OUTPUT);

  pinMode(A8, OUTPUT);//K0

  //Serial.begin(9600);
}

//datasheet nos da los tiepos mĂ­nimos (200ns).
void loop() {

  if( !(PINK & (1 << dat_pin)) ) {//READ

    //PORTK |= 1;//A8 HIGH

    long result = 0;
    int bit_status;

    //Las lecturas no pueden ser interrumpidas
    //noInterrupts(); //quizás poner prioridad top.

    //noInterrupts();
    for (int i = 23; i >= 0; --i) {
      PORTD |= (1 << clk_pin);//WRITE HIGH 
      //result |= (PINK & (1 << dat_pin))?(1L<<i):0; //READ
      //result |= ((PINK & (1 << dat_pin))?1L:0)<<i; //READ PEOR
      //result |= ((PINK >> dat_pin) & 1L) << i;//MUCHO PEOR
      //result |= (long)((PINK & (1 << dat_pin)) != 0) << i; //peor
      bit_status = PINK & (1 << dat_pin); //esta es la linea que tarda más si antes recibí un uno.
      if(bit_status) 
        result |= (1L<<i);
      //result |= bit_status?(1L<<i):0;
      PORTD &= ~(1 << clk_pin);//WRITE LOW
    }
    //quizas le cuesta leer
    //falla la prediccion
    //

    //Ciclos extra que determinan la ganancia de la iguiente lectura: 1 ciclo = 128.
    PORTD |= (1 << clk_pin);//WRITE HIGH
    __asm__("nop");
    __asm__("nop");//dos está bien.
    PORTD &= ~(1 << clk_pin);//WRITE LOW

    //Volvemos a habilitar interrupts.
    //interrupts();

    //Rellena con unos o ceros por signo.
    result |= (result & 1UL << 23 ? 0xFFUL : 0x00UL) << 24;

    //PORTK &= ~1;//A8 LOW

       
    
    
  }
  //Serial.println(result);
}

If you never use the value stored in result, the compiler will likely remove it during optimization.

I know but if I make it volatile it changes nothing regarding the timing problem.
And if I use it on Serial.println, I aldo get the problem.

So the problem must be another thing.

Thanks all three of you for your answers.

Pretty weird, indeed. What other hardware do you have connected to this Mega?

A simple sensor I don't think you will know about it. You send it clock signals and it answers you as we see in the Data line of the pictures. That's not the problem: we can see it answers correctly. And even when more than one consecutive data bit is HIGH (an so HIGH voltage level is maintained for some time), we also get the problem => for sure the sensor it's not the problem.

And I think we are all forgetting about the extrange case I showed earlier:

Doesn't seem interrupted by anything.

Also I have a LCD 16x2 connected. But there is no code in this test sketch that refers to it.
The pins connected are:

//LCD
#define RS A6
#define E A7
#define D4 A8
#define D5 A9
#define D6 A10
#define D7 A11