True speed difference in loop() between R3 and R4

I put this at the top of the loop() function:

  digitalWrite(somePin, digitalRead(somePin) == LOW ? HIGH : LOW);

with somePin having been placed in OUTPUT pin mode.

Measure the frequency of that pin, it will be half the loop frequency.

With a bit more code, you can get the loop frequency, and by measuring the duty cycle on somePin see how busy your loop is, and how much time is left over you could be doing something more.

For example, I have a neopixel animation that runs at 100 Hz, with a busy period of 80 percent of the 10 milliseconds each frame gets.

So I might not add too much without needing to lower the frame rate.

a7

It might be interesting to time an endless while loop to avoid the sketch returning to main()

You could make that
PINB=4;
to toggle D2

Nice!

I always forget that odd feature of the AVR ports.

And that is way faster as well as fewer characters. Since the idea is to have minimal impact…

a7

The clock frequency does directly affect how long it takes the processor to execute an instruction, that is determined by the hardware of the processor itself. As has been pointed out, there is code outside of loop that runs in main(), but there are also other things that will affect timing, such as the interrupt routine for the millis timer, which executes approximately once per millisecond regardless of the clock frequency, as well as background processing if you are sending/receiving data on the serial port (the serial port itself is implemented in hardware, but the code transferring data between the serial hardware and the rx/tx buffers is an interrupt routine). loop() timing will also be greatly affected by overflowing the serial transmit buffer.

Which is a problem if you are wanting the code to run on an R4, which does not have an AVR processor.

THX.

I always forget I know nothing about the R4, except now I do and that is that "R4" is as bad a name for a new board as "Nano Every". :expressionless:

a7

Definitly. Arduino gives you at best 1/4 of the performance your uC can do barebones, but you get easier portability. That's why I said "who cares", 'cause when your Arduino is to slow, just get one more beefy.

An AVR processor has a highly optimised IO; it actually only takes one instruction to set a port. That instruction also only takes on cycle.

Is this also the case for other processors like the ARM based ones?

Notes:

  1. Not talking about the overhead of the hardware abstraction layer.
  2. I live in the AVR world; just curious.

In general, no.

Once you have loaded up address (of IO port) and value (of bits) registers, you can manipulate the pins in a single instruction ("store value to address", essentially), so you can potentially do things like pin-toggling in a loop very quickly, but there is always that setup of the registers - the minimum code to write something (in isolation) is three instructions (and one of them is probably a memory access that takes more than one cycle.) On top of that, the IO ports are frequently connected to a relatively low-priority bus, and take more than one clock cycle to write, and flash memory is usually slower than the CPU speed so that it may introduce "wait states" for program and data access. (writing cycle-accurate deterministic code for most ARM processors is ... annoying.)

On the bright side, this means that the "penalty" for going through the Arduino functions like digitalWrite() is less than it would be, compared to the difference between an SBI avr instruction and the AVR digitalWrite() - the ARM has no special instructions for writing the IO ports, so it isn't slowed down by the fact that the bits and ports are variables.

A bit of a tangent, but there was a thread on I/O benchmarks (including the abstraction layer) that's a bit hard to find with the new forum software. It includes benchmarks for several ARM MCUs.

The take away, as "westfw" notes above is that ARM I/O can be plenty fast, but implementations with the abstraction layer are all over the place.

https://forum.arduino.cc/t/microcontroller-i-o-adc-benchmarks/315304

"Bit Banding" has mostly gone away, apparently (I hear it didn't interact very well with memory caches and buses, at the hardware level.)
However, GPIO "peripherals" with separate "Set", "Clear", "Toggle" registers that can operate on single bits (or multiple single bits) are very common, and allow for the same desirable Speed and Atomicity behavior.

Even with bit-banding, you had to load the (bit-banded) address, load a 0 or 1 into a data register, and do a store via the address register. The handy "store a constant to a memory address" or even "store a register to a fully specified 32bit address" instructions don't exist in ARM (nor in Cortex-M, anyway.)

Just for giggles, I ran the following two little sketches and measured the frequency on the D2 pin.

R3

void setup() {
   pinMode(2, OUTPUT);
}

bool state = false;

void loop() {
   if( state ) {
      PORTD |= bit(2);
   } else {
      PORTD &= ~bit(2);
   }
   state = !state;
}

Measured frequency on D2: 482KHz

Much slower than PIND = bit(2); (that was up around 1.59MHz), but I wanted to see how the R3 & R4 did on more or less the same code and I didn't know off the top of my head how to quickly do a port bit toggle on the R4.

R4

void setup() {
   pinMode(2, OUTPUT);
}

bool state = false;

void loop() {
   if( state ) {
      R_PORT1->PODR |= bit(5);
   } else {
      R_PORT1->PODR &= ~bit(5);
   }
   state = !state;
}

Measured frequency on D2: 798KHz

The first time I did it that way, but the R3 had to do the read step so I hobbled the R4 too. IIRC, with POSR/PORR the R4 was about 1.14MHz.

I suppose if I kept the if( state ) bit in both it would still be "a fair fight". Let me just try that... R3: 795KHz, R4: 1.14MHz.

(And my memory was correct: with just void loop { PIND = bit(2); }, the R3 measured 1.59MHz.)

Now this was a little interesting, and I'm not sure I believe it. (Mind you, it's probably got about as much relevance as sport statistics, and we seem to eat them up too!)

R3: 2.65MHz

void setup() {
   pinMode(2, OUTPUT);
}

void loop() {
      PIND = bit(2);
      PIND = bit(2);
}

R4: 2.99MHz

void setup() {
   pinMode(2, OUTPUT);
}

void loop() {
   R_PORT1->POSR = bit(5);
   R_PORT1->PORR = bit(5);
}

It doesn't. The compiler is smart enough to optimize the single-bit read/write to the single SBI/CBI instructions. Sure, they take two cycles (on the Uno CPU, anyway), but it's not the same as having separate read/modify/write steps in the program.

You could could try:

void loop() {
  for(;;) {
    R_PORT1->POSR = bit(5);
    R_PORT1->PORR = bit(5);
  }
}

vs.

void loop() {
  for(;;) {
    PIND = bit(2);
  }
}

Fwiw, there’s a pretty extensive write up on the avr pin toggling methods here: Maximum pin toggle speed - #6 by westfw
Sheesh. 2008. I feel old. (I am old!)

Just on the overhead on the call to loop() each time, I'm betting it was off a lot longer than it was on.

Having slept on it, I guess I don't find the R3 results all that surprising; the frequency when toggling the pin twice in loop() is is in the neighbourhood of double what I saw when toggling the pin once in loop() (and it's in line with what westfw reported back in 2008). But the R4 results are quite a bit slower than I would have expected. I could dig into the assembler code to see what's going on, but to be honest, it really doesn't matter and my interest has passed. :slight_smile: