Has anyone ever used the GPIOR0~2 registers on an AVR?

This is not a technical question, so much as a question about Arduino power-user's programming habits.

Has anyone here ever used any of these general purpose registers? For what purpose and in what context?

Does anyone know of anyone who has (since I suspect I'm going to hear nothing but crickets on the first once)?

Not used them afaik, not under that name... do not recall a thread about it either.

What page number of the datasheet are these discussed?

I think they are mapped in ram, so you can use them as ram storage. No one else is using them. After reading about it, some confuse them with the general registers r0 and r1. But those not the GPIORx registers.

For the Arduino Uno with ATmega328P they are: GPIOR0, GPIOR1, GPIOR2.

I think they are cleared after a reset, while the unused ram is not cleared.
The single instruction bit set and bit clear (sbi, cbi) can be used. That means bits can be set and read in interrupts without problems.
[ADDED] @westfw, are GPIOR1 and GPIOR2 outside the I/O register range ? In the ATmega32U4 datasheet it is written that all the single single bit instructions work for all three GPIOx registers. That is not written in the ATmega328P and ATmega2560 datasheet.

If I remember it well, there are even more undocumented registers.

void setup()
{
  GPIOR0 = 0x12;
  GPIOR1 = 0x34;
  GPIOR2 = 0x56;
}

void loop()
{
}

When you want to find people who use them, I suggest to try http://www.avrfreaks.net.

GPIOR0 is sort-of interesting, since you can use it as a set of single-instruction accessible bits. I've thought of using it as an "error status" indicator in the arduino core; use a non-existent Digital Pin Number, set a bit. Try to use a function that doesn't work on the particular pin, set a different bit. Adds negligible code, and only slows down those sections of existing functions that aren't doing anything anyway. Add a new function "checkErrors()" that reports whether any errors have occured; you never have to call it if you don't want to, but it might provide some useful clues.

GPIOR1 and 2 are less interesting. I think it's been suggested that one of them be used to pass the original reset cause from the bootloader to the sketch (the current scheme is ... worse, I think.)

According to the datasheet they are cleard with a reset.
I had to try that:

unsigned int *pA = (unsigned int *) 1000;  // somewhere in the middle of the ram, hopefully not used.

char buffer[80];

void setup()
{
  Serial.begin( 9600);
  Serial.println();
  Serial.println( "-----------");
  Serial.println( "Test GPIORx");

  sprintf( buffer, "Old: GPIORx = 0x%02X, 0x%02X, 0x%02X, A = %04X",
    (unsigned int) GPIOR0, (unsigned int) GPIOR1, (unsigned int) GPIOR2, *pA);
  Serial.println( buffer);

  // Write with new values.
  unsigned int count = *pA;
  
  GPIOR0 = (byte) ++count;
  GPIOR1 = (byte) ++count;
  GPIOR2 = (byte) ++count;
  *pA = ++count;

  sprintf( buffer, "New: GPIORx = 0x%02X, 0x%02X, 0x%02X, A = %04X",
    (unsigned int) GPIOR0, (unsigned int) GPIOR1, (unsigned int) GPIOR2, *pA);
  Serial.println( buffer);

  Serial.println( "Reset the board.");
}

void loop()
{
}

Yes, they are cleared and the unused ram location is not cleared during a reset. So they are indeed I/O registers without any hardware connected to them.

Work for loop indices...

void setup()
{
  Serial.begin(115200);
  Serial.println(__FILE__);

  for (GPIOR0 = 0; GPIOR0 < 10; GPIOR0++)
  {
    for (GPIOR1 = 0; GPIOR1 < 10; GPIOR1++)
    {
      Serial.print(GPIOR0 * GPIOR1, HEX);
      Serial.print("\t");
    }
  }
}

void loop()
{
}

As these are registers, do they behave like volatile vars?
Could be useful in libraries that use interrupts e.g. for a rotary encoder.
make note on TODO list...

quick timing test for loop indexer.

20-25% slower than uint8_t in a simple double loop test,

//
//    FILE: GPIOR0_register.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.0.1
// PURPOSE: demo
//     URL: https://forum.arduino.cc/index.php?topic=518494
// HISTORY: 2017-12-23 0.0.1 initial version

uint32_t start;
uint32_t stop;

int freeRam () {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
};

volatile int a = 0;

void setup()
{
  Serial.begin(115200);
  Serial.println(__FILE__);

  Serial.println(freeRam());
  start = micros();
  for (GPIOR0 = 0; GPIOR0 < 10; GPIOR0++)
  {
    for (GPIOR1 = 0; GPIOR1 < 10; GPIOR1++)
    {
      a = GPIOR0 * GPIOR1;
      //      Serial.print(GPIOR0 * GPIOR1, HEX);
      //      Serial.print(", ");
      //      Serial.println(freeRam());
    }
  }
  stop = micros();
  Serial.println(stop - start);
  delay(1000);

  start = micros();
  for (uint8_t i = 0; i < 10; i++)
  {
    for (uint8_t j = 0; j < 10; j++)
    {
      a = i * j;
      //      Serial.print(i * j, HEX);
      //      Serial.print(", ");
      //      Serial.println(freeRam());
    }
  }
  stop = micros();
  Serial.println(stop - start);
  delay(1000);
}

void loop()
{

}

output

1758
132
100

Quick optimize test?

uint32_t start;
uint32_t stop;

void setup()
{
  Serial.begin(115200);
  Serial.println(__FILE__);

  start = micros();
  for (GPIOR0 = 250; GPIOR0; GPIOR0--);
  stop = micros();
  Serial.println(stop - start);

  delay(1000);
  start = micros();
  for (uint8_t i = 250; i; i--);
  stop = micros();
  Serial.println(stop - start);
  delay(1000);
}

void loop(){}

output

132
4

So the GPIOR0 as loop index of an empty loop is not optimized / removed by the compiler.

some other loopcounts

GPIOLOOP: 250 200 150 100 50
DURATION: 132 104 80  56  28

==> so first order estimate gives one empty loop takes 0.5uSec + 4 uSec overhead (= micros() call)

could be useful in handshakes or timing experiments without jumping into assembly

Fun!

This post is already one year old but I decided to post here a code example using GPIOR0 and GPIOR1.

I built a controller for a BLDC motor using one Arduino Nano. The controller consists of three half bridges, each controlled with a half bridge driver IR2184. Each half bridge driver is connected with two pins of port B of the Arduino. One for chip select and one to power the high side or low side mosfet. Dependent on the actual motor position, different pins of Port B have to be toggled in order to create a DC for the motor.

The DC is created with timer 1 with 16 kHz. The pins are set or cleared in interrupts. In order to archive smaller possible DC, the interrupt was programmed in assembler. It is an easy option to hand over the information what pin has to be toggled with the two registers GPIOR0 and GPIOR1 because it is easy to access them with assembler.

/*
  This example creates a DC signal on PORTB (Pin 8 to 13) for Arduino Nano dependent on the content of GPIOR0 and GPIOR1 with 16 kHz.
  Timer 1 is used to trigger the interrups and the interrupts are programmed in assembler in order to make smallest DC possible.
  Also the AD converter is started (optional) with high speed in order to finish within the duration of 16 kHz.
*/


// Interrupts ********************************************************************************************************
ISR(TIMER1_COMPB_vect, ISR_NAKED)       //GPIOR0 is written on port B.
{
  asm volatile(
    " push r16        \n"       //save r16 on stack
    " in r16, 0x1E    \n"       //load GPIOR0 in r16
    " out 0x05, r16   \n"       //write r16 on PortB
    " pop r16         \n"       //load r16 from stack
    " reti            \n");     //return from interrupt
}

ISR(TIMER1_COMPA_vect, ISR_NAKED)       //Timer 1 reached maximum; GPIOR1 is written on port B.
{
  asm volatile(
    " push r16        \n"       //save r16 on stack
    " ldi r16, 0xD6   \n"       //load B11010110 r16
    " sts 0x007A, r16 \n"       //write r16 on ADCSRA in order to start AD-conversion
    " in r16, 0x2A    \n"       //load GPIOR1 in r16
    " out 0x05, r16   \n"       //write r16 on PortB
    " pop r16         \n"       //load r16 from stack
    " reti            \n");     //return from interrupt
}

/*
  // This are the same interrupts without using assembler.
  
  ISR(TIMER1_COMPB_vect)       //GPIOR0 is written on port B.
  {
  PORTB = GPIOR0;
  }

  ISR(TIMER1_COMPA_vect)       //Timer 1 reached maximum; GPIOR1 is written on port B.
  {
  ADCSRA = B11010110;           // start AD conversion
  PORTB = GPIOR1;
  }
*/

void setup() { // initialize *************************************************************************************
  // initialize general purpose registers with 0
  GPIOR0 = B00000000; 
  GPIOR1 = B00000000; 

  // define Port B as output (pin 8 to 13)
  PORTB = B00000000;
  DDRB = B00111111;             // output for half bridge drivers

  //specifiy timer 1
  TCCR1A = B00000011;           //normal port-funktion(COM=0)
  TCCR1B = B00011001;           //modus 15 fast PWM; no prescaler ==> timer 1 counts clock cycles with 16 MHz
  TIMSK1 = B00000110;           //interrupt for COMPB and COMPA enable
  OCR1A = 999;                  //countervalue TOP
  OCR1B = 0;                    //initialize smallest DC in the beginning


  // spcifiy AD-converter (optional)
  DIDR0 = B11111000;            // deactivate digital input for the analog pins
  ADMUX = B01000111;            // the last 4 bits define the channel. This is A7 right now

}

void loop() {
  GPIOR1 = B00100000;             //as an example, D13 is driven high
  OCR1B = 100;                    //as an example, 10 % DC is applied

  /*
    Modification of OCR1B within the range 0 and OCR1A in order to define the right DC for the motor.

    Modification of GPIOR0 and GPIOR1 according to the position of the motor.

    read ADC and define the channel for the next conversion with ADMUX

  */
}
1 Like

Hmm, clearly these memory locations have completely the wrong names as they are nothing to do with I/O,
they just happen to be in an address block that is mainly hardware+I/O registers, and are only of use for
tight assembler coding, no compiler writer is going to bother supporting them (unless they cheekily use them
for some code fragments behind your back).

I'd deliberately ignore them. Perhaps on some other AVR chips those addresses are mapped to actual hardware, but I
think the motivation was single-cycle bit set and reset access for faster boolean variables in assembler when
the chip designers realized they had a few bytes left over in the "I/O" block.

clearly these memory locations have completely the wrong names as they are nothing to do with I/O,

They are essentially IO registers that don't DO anything, off in the IO address space where they are accessible via IN and OUT (shorter/faster than accessing "RAM") and one of them is even in the low address space where you can use SBI/CBI/etc instructions. As mleo showed, you can use them to make very tiny ISR functions:

ISR(TIMER1_COMPB_vect, ISR_NAKED)       //GPIOR0 is written on port B.
{
  asm volatile(
    " sbi GPIOR0, 1 \n"
    " reti          \n");     //return from interrupt
}

That'll even fit in the vector space of a m328 (although putting it there is likely to be "interesting")