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.
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.
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...
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
*/
}
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")