AVR Compatibility: PORT and PIN registers?

The following simple sketch will blink the following:
Compiles and runs on: Uno (R1 I think), Leonardo, and Mega 1280

void setup() {
  // put your setup code here, to run once:
  pinMode(13, OUTPUT);
}

uint8_t counter = 0;
void loop() {
  // put your main code here, to run repeatedly:
  counter++;
  uint8_t portb_val = PINB;
  if (counter & 1) portb_val |= (1 << 5);
  else portb_val &= ~(1 << 5);
  PORTB = portb_val;
  delay(250);
}

And on the original UNO it blinks the LED.

But it fails to compile on the UNO R4 boards.

C:\Users\kurte\AppData\Local\Temp\.arduinoIDE-unsaved2023622-68696-1r9b6fh.1q52\sketch_jul22a\sketch_jul22a.ino: In function 'void loop()':
C:\Users\kurte\AppData\Local\Temp\.arduinoIDE-unsaved2023622-68696-1r9b6fh.1q52\sketch_jul22a\sketch_jul22a.ino:10:23: error: 'PINB' was not declared in this scope
   uint8_t portb_val = PINB;
                       ^~~~
C:\Users\kurte\AppData\Local\Temp\.arduinoIDE-unsaved2023622-68696-1r9b6fh.1q52\sketch_jul22a\sketch_jul22a.ino:10:23: note: suggested alternative: 'PIN'
   uint8_t portb_val = PINB;
                       ^~~~
                       PIN
C:\Users\kurte\AppData\Local\Temp\.arduinoIDE-unsaved2023622-68696-1r9b6fh.1q52\sketch_jul22a\sketch_jul22a.ino:13:3: error: 'PORTB' was not declared in this scope
   PORTB = portb_val;
   ^~~~~

exit status 1

Compilation error: 'PINB' was not declared in this scope

Yes I know that this is not an AVR board. But personally since the board is called an UNO, I would expect code that basic code that runs on one UNO should run on another...

Also note: This same sketch compiles and runs on a Teensy boards. I just ran it on a Micromod. The Teensy cores do this, by emulating some of these key AVR registers with c++ classes.
cores/teensy4/avr_emulation.h at master · PaulStoffregen/cores (github.com)

Where I have seen several libraries that manipulate the port registers are display libraries for parallel ports... like 8 pins for data maybe 4 pins for address.

Not sure the best place or way to ask questions like this?

@ptillisch and others hopefully you will have insight into this.

For example, should it be asked here on the forum? Or as github issues? Is there a centralized place, that these are discussed? Like when converting your code from earlier UNO boards to the R4, here are the following things, that are not supported and the suggest way to implement it on the new board?

Hope this makes sense.

1 Like

Yes, that is the fundamental flaw in calling it "UNO"!

IMO, they should not have called it UNO - for exactly this reason! :roll_eyes:

The fact is that the "UNO" (sic) R4 is a completely different beast from all the preceding UNOs - so sketches like this which rely upon the AVR architecture are not going to work.

1 Like

It is not a basic code. Basic arduino code for blink shown below:

void setup() {
  // put your setup code here, to run once:
  pinMode(13, OUTPUT);
}


void loop() {
 digitalWrite(13, HIGH);
delay(500);
 digitalWrite(13, LOW);
delay(500);
}

The code was compiled on any Uno without a problem.

2 Likes

Please define what you mean by "basic code"

For what is, I expect, the intended audience, a line of code like this, for instance

  if (counter & 1) portb_val |= (1 << 5);

will be very far from basic

2 Likes

It would be better to use LED_BUILTIN instead of the pin number 13, that will make the code portable to boards that use a different pin for the LED.

2 Likes

Any code that used direct port manipulation is going to be specific to a particular processor or family of processors. The UNO R4 suffers from the same problems as the UNO WiFi Rev2, which also uses a processor that is not directly compatible with the UNO (and also uses a different WiFi board than the UNO WiFi, making any code that uses that incompatible).

Also note the Nano, Nano Every, and the multiple Nano 33 boards, another example of confusing incompatiblity. Just the Nano alone suffers from having two incompatible variants of the bootloader.

The R4 Uno uses a Renesas R7FA4M1AB3CFM#AA0 and not a Microchip AVR MCU.

Have a look at the Renesas RA4M1 Group User Manual: Hardware, and see if you find the words PINB or PORTB mentioned.

When an automobile manufacturer changes from an internal combustion engine to an electric motor, but keeps the model name, do you expect all the old instruction manuals to work without changes?

Think that is the point that @KurtE is making. In some libraries that I worked on I had to add a ifdef(boadxxxx) and add the appropriate code for thats board port manipulation. @KurtE just put it in a sketch for an example.

If we look at the recent post by @ptillisch - Uno r4 Wifi gives <util\delay> not found error with SSH1106 display - UNO R4 / UNO R4 WiFi - Arduino Forum - you see the errors associated with not have the right code for the WiFi/Minima.

n file included from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\renesas_uno\1.0.2\libraries\Wire/Wire.h:23:0,
                 from c:\Users\per\Documents\Arduino\libraries\Adafruit_SH1106-master\Adafruit_SH1106.cpp:35:
c:\Users\per\Documents\Arduino\libraries\Adafruit_SH1106-master\Adafruit_SH1106.cpp: In member function 'void Adafruit_SH1106::begin(uint8_t, uint8_t, bool)':
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\renesas_uno\1.0.2\cores\arduino/Arduino.h:76:92: error: cannot convert 'volatile uint16_t* {aka volatile short unsigned int*}' to 'PortReg* {aka volatile unsigned char*}' in assignment
 #define portOutputRegister(port)    &(((R_PORT0_Type *)IOPORT_PRV_PORT_ADDRESS(port))->PODR)
                                                                                            ^
c:\Users\per\Documents\Arduino\libraries\Adafruit_SH1106-master\Adafruit_SH1106.cpp:179:19: note: in expansion of macro 'portOutputRegister'
     csport      = portOutputRegister(digitalPinToPort(cs));
                   ^~~~~~~~~~~~~~~~~~
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\renesas_uno\1.0.2\cores\arduino/Arduino.h:76:92: error: cannot convert 'volatile uint16_t* {aka volatile short unsigned int*}' to 'PortReg* {aka volatile unsigned char*}' in assignment
 #define portOutputRegister(port)    &(((R_PORT0_Type *)IOPORT_PRV_PORT_ADDRESS(port))->PODR)
                                                                                            ^
c:\Users\per\Documents\Arduino\libraries\Adafruit_SH1106-master\Adafruit_SH1106.cpp:181:19: note: in expansion of macro 'portOutputRegister'
     dcport      = portOutputRegister(digitalPinToPort(dc));
                   ^~~~~~~~~~~~~~~~~~
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\renesas_uno\1.0.2\cores\arduino/Arduino.h:76:92: error: cannot convert 'volatile uint16_t* {aka volatile short unsigned int*}' to 'PortReg* {aka volatile unsigned char*}' in assignment
 #define portOutputRegister(port)    &(((R_PORT0_Type *)IOPORT_PRV_PORT_ADDRESS(port))->PODR)
                                                                                            ^
c:\Users\per\Documents\Arduino\libraries\Adafruit_SH1106-master\Adafruit_SH1106.cpp:187:21: note: in expansion of macro 'portOutputRegister'
       clkport     = portOutputRegister(digitalPinToPort(sclk));
                     ^~~~~~~~~~~~~~~~~~
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\renesas_uno\1.0.2\cores\arduino/Arduino.h:76:92: error: cannot convert 'volatile uint16_t* {aka volatile short unsigned int*}' to 'PortReg* {aka volatile unsigned char*}' in assignment
 #define portOutputRegister(port)    &(((R_PORT0_Type *)IOPORT_PRV_PORT_ADDRESS(port))->PODR)
                                                                                            ^
c:\Users\per\Documents\Arduino\libraries\Adafruit_SH1106-master\Adafruit_SH1106.cpp:189:21: note: in expansion of macro 'portOutputRegister'
       mosiport    = portOutputRegister(digitalPinToPort(sid));
                     ^~~~~~~~~~~~~~~~~~
c:\Users\per\Documents\Arduino\libraries\Adafruit_SH1106-master\Adafruit_SH1106.cpp:197:11: error: 'class arduino::ArduinoSPI' has no member named 'setClockDivider'
       SPI.setClockDivider (SPI_CLOCK_DIV2); // 8 MHz
           ^~~~~~~~~~~~~~~
c:\Users\per\Documents\Arduino\libraries\Adafruit_SH1106-master\Adafruit_SH1106.cpp:197:28: error: 'SPI_CLOCK_DIV2' was not declared in this scope
       SPI.setClockDivider (SPI_CLOCK_DIV2); // 8 MHz
                            ^~~~~~~~~~~~~~
c:\Users\per\Documents\Arduino\libraries\Adafruit_SH1106-master\Adafruit_SH1106.cpp:197:28: note: suggested alternative: 'AGT_CLOCK_P402'
       SPI.setClockDivider (SPI_CLOCK_DIV2); // 8 MHz
                            ^~~~~~~~~~~~~~
                            AGT_CLOCK_P402
c:\Users\per\Documents\Arduino\libraries\Adafruit_SH1106-master\Adafruit_SH1106.cpp: In member function 'void Adafruit_SH1106::display()':
c:\Users\per\Documents\Arduino\libraries\Adafruit_SH1106-master\Adafruit_SH1106.cpp:551:28: error: 'TWBR' was not declared in this scope
       uint8_t twbrbackup = TWBR;
                            ^~~~

so whats the way to handle it in libraries or a good example.

Another example is in the Encoder library that i just updated direct port manipulation is used. So for the WiFi and Minima I added this:

#define IO_REG_TYPE			uint32_t
#define PIN_TO_BASEREG(pin)             (volatile uint32_t*)(portInputRegister(digitalPinToPort(pin)))
#define PIN_TO_BITMASK(pin)             (digitalPinToBitMask(pin))
#define DIRECT_PIN_READ(base, mask)     (((*(base)) & (mask)) ? 1 : 0)

haven't done any time tests - so don't ask. My guess is you could something similar for direct pin write (think there was another thread about digitalWriteFast.

Not sure about emulation but that is an option.

Sorry,

Again the example to blink the LED was just an example. Also it was and is a question to those in product support and developers or the like. Is there a central place to discuss different compatibility issues? Then hopefully once an issue is understood, which might mean, we are not going to handle it, or we suggest the code be changed to be like xxxxx, or yes this is an issue we wish to handle and it is issue #x on Github.

More interesting for the Port manipulation, is for example suppose you were using a display shield like:


The pins that were choosen for each of the signals was deliberate.
That is it made it easier to output each output to the display with only needing to do outputs to a couple of port registers, as they wanted the pins to update as simultaneous and as quick as possible.

What should libraries who did this, do now, to update the 12 or 13 signals per transfer?
And to make it more interesting, the MINIMA and WIFI do not have the same pin mappings.

Note: I started off with what I thought was a simple question, which I suspect the answer will be to punt. But hopefully with some guidelines on how best to do it.

@Merlin513 - Your response is what I thought the next topic should be. That is, why isn't the calls like portOutputRegister and the like not compatible? Which translate to how should libraries be written to allow the update if IO pins as fast as possible, without having to hard code specific stuff for these boards? So far there is no digitalWriteFast() and even if there was, it is not typically optimized to work for non-constant pin numbers. But again probably for another topic.

Was curious to see what the time difference so put together a quick sketch for timing (core and the micros fix incorporated)

Time to read pin using direct read 100 times: 38(0.38) microseconds
Time to read pin using digitalRead 100 times: 117 (1.17) microseconds

as expected quite a bit of a difference. Here is the sketch I used - let me know if anything should change:

/*copied from the coremark sketch */
extern "C" int ee_printf(const char *format, ...)
{
	va_list args;
	va_start(args, format);
	for (; *format; format++) {
		if (*format == '%') {
			bool islong = false;
			format++;
			if (*format == '%') { Serial.print(*format); continue; }
			if (*format == '-') format++; // ignore size
			while (*format >= '0' && *format <= '9') format++; // ignore size
			if (*format == 'l') { islong = true; format++; }
			if (*format == '\0') break;
			if (*format == 's') {
				Serial.print((char *)va_arg(args, int));
			} else if (*format == 'f') {
				Serial.print(va_arg(args, double));
			} else if (*format == 'd') {
				if (islong) Serial.print(va_arg(args, long));
				else Serial.print(va_arg(args, int));
			} else if (*format == 'u') {
				if (islong) Serial.print(va_arg(args, unsigned long));
				else Serial.print(va_arg(args, unsigned int));
			} else if (*format == 'x') {
				if (islong) Serial.print(va_arg(args, unsigned long), HEX);
				else Serial.print(va_arg(args, unsigned int), HEX);
			} else if (*format == 'c' ) {
				Serial.print(va_arg(args, int));
			}
		} else {
			if (*format == '\n') Serial.print('\r');
			Serial.print(*format);
		}
	}
	va_end(args);
	return 1;
}

#define IO_REG_TYPE			uint32_t
#define PIN_TO_BASEREG(pin)             (volatile uint32_t*)(portInputRegister(digitalPinToPort(pin)))
#define PIN_TO_BITMASK(pin)             (digitalPinToBitMask(pin))
#define DIRECT_PIN_READ(base, mask)     (((*(base)) & (mask)) ? 1 : 0)
#define pin1 2

void setup() {
  Serial.begin(9600);
  delay(5000);
  
}

void loop() {
  long start = micros();
  for(uint8_t j = 0; j < 100; j++)
    DIRECT_PIN_READ(PIN_TO_BASEREG(pin1), PIN_TO_BITMASK(pin1));
  long finish = micros();
  ee_printf("Time to read pin using direct read 100 times: %d(%f) microseconds\n", finish-start, (float)(finish-start)/100.f );
  delay(1000);
  start = micros();
  for(uint8_t j = 0; j < 100; j++)
    digitalRead(pin1);
  finish = micros();
  ee_printf("Time to read pin using digitalRead 100 times: %d (%f) microseconds\n", finish-start, (float)(finish-start)/100.f );
  delay(1000);
  Serial.println();
}

Found this out the hard way when I was playing with the encoder library. Read somewhere that portInputRegister is 16 bits but returns 32-bit value - something like that. This was based on seeing it setup that way it in pulse.cpp and then think I looked in FSP. Really forgot where

1 Like

When asking a question, that should be done here on the forum.

GitHub issues should only be used for well structured formal bug reports and feature requests and very tightly focused discussion about those bug reports and feature requests. The purpose of issues is only to efficiently track development tasks so submitting an issue with the goal of directly receiving information or getting assistance is not effective or responsible.

I am often frustrated to see community members make issues with such goals when I know they would have had much better results on the forum. When they are submitted to the the repositories I maintain, I redirect them to the forum and make an effort to follow up over here as needed.

Please note that I make these statements only in response to the quoted question as it applies to the use of the Arduino forum and Arduino's GitHub issue trackers by the community in general. I am not meaning to imply anything about the rest of your post or other forum and GitHub activities (which have been of great value to the Arduino projects I am involved in).

For actual discussion, the forum would be the place. As for resources on the subject, there is some relevant information in the documentation provided by Arduino. For example:

https://docs.arduino.cc/tutorials/uno-r4-wifi/cheat-sheet#usb-serial--uart

But I'm sure there could be more of it, and the form of the information targeted specifically to porting efforts. If anyone would like to request or propose the addition of such information to the official documentation, you can submit a formal request as an issue here:

Or better yet, submit the content yourself via a pull request:

https://github.com/arduino/docs-content/pulls

Some efforts were made to do that in the "Arduino megaAVR Boards" core of the Uno WiFi Rev2 and Nano Every:

I'm not sure how effective that was in increasing the compatibility of the previously existing code specific to the classic AVR architecture.

1 Like

At "only" 3x slower, digitalRead() on R4 is much better than on an R3 Uno.
Similarly, the speed penalty for using digitalWrite() is less than on an AVR. Sketches that needed direct port access on an Uno R3 may do OK with the Arduino functions on an R4...

I'm not sure how effective [ATmega4809 port emulation] was in increasing the compatibility

IMO, it was pretty awful. People used PORT/PIN when they needed faster operation, and the 4809 port emulation wasn't fast enough.

2 Likes

@ptillisch - thanks for all of the good information.

Thanks, I sort of figured that to be true for most cases. Sometimes unclear in the gray area. Like, options on how to update the internals of some of some functionality of the core. Things that years ago might have asked on the developer's email list.

I will take some more time to read through this document. As you mentioned there could be a lot more information up there, or at least pointers to other information.
Like in your cheat sheet link to Serial... That you have the ability to actually define another Serial port and an example on how to do so... Likewise for Wire on pins 0 and 1

@westfw @Merlin513 - good feedback.

Maybe? But as I know from other faster boards, faster is never fast enough :smiley:

Sorry I have not tried that board and as such the port emulation code.

It may only be me, but I with things like this, I look at it as mainly a barrier remover. That is each time you come up with cases like this, that the only advice given is to rewrite your code, is in many cases a total turnoff and they go elsewhere... At least that is my first knee jerk reaction.

And when transitioning to the new board, it hopefully helps to at least get your code up and running and, in many cases, it might be sufficient and if not, at least allows you to concentrate on replacing it.

If it were me, I would suggest:

  1. implement a digitalWriteFast and digitalReadFast. Either in the core itself or as a standard library.
  2. Consider adding at least a simple version of the PIN/PORT emulation.
    Even if it ends up with showing code like: x = PIND;
    ends up being translated to.
	inline int operator & (int val) const __attribute__((always_inline)) {
		int ret = 0;
		if ((val & (1<<0)) && digitalReadFast(0)) ret |= (1<<0);
		if ((val & (1<<1)) && digitalReadFast(1)) ret |= (1<<1);
		if ((val & (1<<2)) && digitalReadFast(2)) ret |= (1<<2);
		if ((val & (1<<3)) && digitalReadFast(3)) ret |= (1<<3);
		if ((val & (1<<4)) && digitalReadFast(4)) ret |= (1<<4);
		if ((val & (1<<5)) && digitalReadFast(5)) ret |= (1<<5);
		if ((val & (1<<6)) && digitalReadFast(6)) ret |= (1<<6);
		if ((val & (1<<7)) && digitalReadFast(7)) ret |= (1<<7);
		return ret;
	}

In some cases, maybe it could be done faster if you can do a couple of reads of native ports some shifts, ands and ors... But in this case not sure especially when it would have to be different for the two board:
MINIMA (P301, P302,P105,P104,P103, P102,P106,P107)
WIFI (P301, P302, P104, P105,P106,P107,P111,P112)

This would be very interesting if the librarys like mcufriend_kbv could be updated to support the R4 as i am currently stuck getting examples compiled for the R4 Wifi. Yeah you could use a R3 but the Wifi part makes it so powerful

Using PINB, PORTB makes your code incompatible with the R4 you can #define them to use
whats equivalent in the new processor. At least the new board was given a different Rev. ha ha.