Confusing pin numbering setting in IDE 2.2.1; GPIO vs. Arduino

I understand that some libraries can impose their own pin numbering conventions, but I still find the behavior of the "Pin Numbering" setting confusing. It is not even the opposite of what I expected, but something more complicated. Either there is a bug in the IDE or I need a clearer explanation of what this setting is supposed to do.

This may be important: I am using the Arduino Nano ESP32. The display and temperature sensors are models I have been using without trouble for years with Arduino Megas.

I have two simple tests, One uses SPI communication and one uses OneWire. Both give results I don't understand.

For each type of hardware there are 6 trials. Three are with "By Arduino pin" and three are with "By GPIO number". With each of set of three I tried the Arduino integer numbers, the GPIO integers, and the Arduino numbers with "D" in front.

Graphics, SPI

First, a graphics test with SPI (a 2.4" display from Adafruit). This uses pin D6 for DC and D7 for CS. Values are on #define lines. The code is a slightly modified version of the graphicstest.ino example that installs with the ILI9341 library.

  • By Arduino pin
    • 6, 7. fail
    • 9, 10. fail
    • D6, D7. fail
  • By GPIO pin
    • 6, 7. SUCCESS
    • 9, 10. fail
    • D6, D7. fail

It seems wrong that the "GPIO" pin setting works with the Arduino numbering and nothing else does. I at least expected some symmetry between the two settings. The GPIO numbers don't work with either setting.

Temperature Sensor, OneWire

Second, reading a DS18B20 temperature sensor with the OneWire and DallasTemperature libraries. This reads data on Arduino pin D10 which is GPIO21. Values are on #define lines. The code is the Simple.ino example that installs with the DallasTemperature library.

  • By Arduino pin
      1. fail
      1. SUCCESS
    • D10. fail
  • By GPIO pin
      1. fail
      1. SUCCESS
    • D10. SUCCESS

A very different pattern. The Arduino integer doesn't work with either setting and the GPIO number works either way. Only the "D10" entry is affected by the setting and it fails in Arduino pin mode!

It appears that to use these together I will have to use the "by GPIO number setting" with bare integer Arduino numbers for the SPI device and GPIO or D-format numbers for the OneWire device. Very strange.

Please help. I can get working code by trial and error but this feels like trouble waiting to happen as I build my actual project with 4 sensors, display, microSD, and 8 relays, some of which are on a shift register. Even when it works for me, it will be another source of failure when I hand the code off to others who are unaware of these issues.

Thanks!

This is IDE 2.2.1 on Windows 11.

Libraries:

Using library SPI at version 2.0.0 in folder:

C:\Users\list\AppData\Local\Arduino15\packages\arduino\hardware\esp32\2.0.13\libraries\SPI 
Using library OneWire at version 2.3.7 in folder: C:\Users\list\Documents\Arduino\libraries\OneWire 
Using library DallasTemperature at version 3.9.0 in folder: C:\Users\list\Documents\Arduino\libraries\DallasTemperature 
Using library Adafruit ILI9341 at version 1.5.14 in folder: C:\Users\list\Documents\Arduino\libraries\Adafruit_ILI9341 
Using library Adafruit GFX Library at version 1.11.9 in folder: C:\Users\list\Documents\Arduino\libraries\Adafruit_GFX_Library 
Using library Adafruit BusIO at version 1.14.5 in folder: C:\Users\list\Documents\Arduino\libraries\Adafruit_BusIO 
Using library Wire at version 2.0.0 in folder: C:\Users\list\AppData\Local\Arduino15\packages\arduino\hardware\esp32\2.0.13\libraries\Wir

It is important and as your question has nothing to do with IDE 2.x I have moved it to the Nano ESP32 category of the forum

For what it is worth I too find the Pin Numbering option confusing

Hmm. It's not clear how you can tell this isn't related to the IDE when the IDE settings affect the behavior of the code, but if someone in the ESP32 category takes this on, I'll be happy.

My conclusion that the problem is not related to IDE 2.x is based on a number of other topics relating to Pin Numbering when using the Nano ESP32

If it turns out to be wrong then please accept my apologies and the topic can always be moved back to IDE 2.x

Here is your answer
Nano ESP32 Selecting Pin Configuration | Arduino Documentation

Regards

Edit: It wasn't an answer, sorry.

That is not an answer, rather it is an explanation of how the pin numbering on the Nano ESP32 is at best inconvenient and at worst a mess

Do you have to use such a kluge when using other ESP32 development boards ? Answer, no. You just use the pin numbers printed on the board

Thanks @VeloSteve for the clean reporting! I am sorry to see such confusion. Trial and error will definitely not happen on our watch, so let's dig in!

Very confused by these results. Can you double check the "GPIO number / D6, D7" case? If you confirm this I will try to replicate on similar hardware in the next few days to try and understand the issue.

That is basically what I was expecting: D10 is 10 in the first case and 21 in the second, hence the symmetry. Note that the "Arduino / 21" case is a stroke of luck - it really depends on how each library uses the pin numbers they are given.

As a rule, never mix GPIO numbers with Arduino pin numbers in a sketch - it's either one or the other. Since you are using many libraries I would have suggested you to use "By GPIO number" and the pin labels (D6, D7, D10) everywhere.

Thanks to everyone for all the prompt and useful replies.

I repeated the GPIO / D6 / D7 case and it still fails. After a working sketch runs it shows what was there before, or if I power cycle I get a white screen with a faint flicker when an amber LED on the Nano flashes. I never paid attention to that LED before. When the sketch is working it flashes with each major screen change.

The code is below, just in case.

Steve

/***************************************************
  This is our GFX example for the Adafruit ILI9341 Breakout and Shield
  ----> http://www.adafruit.com/products/1651

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/


#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

// Works with no modification on ESP32.
// Trying to understand pin settings.
// 6, 7 by Arduino pin - fail
// 9, 10 by Arduino pin - fail
// D6, D7 by Arduino pin - fail
// 6, 7 by GPIO pin - success
// 9, 10 by GPIO pin - fail
// D6, D7 by GPIO pin - fail

#define TFT_DC D6
#define TFT_CS D7

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);


void setup() {

  Serial.begin(9600);
  delay(2000);
  Serial.println("ILI9341 Test!");
  tft.begin();
  delay(1000);
  // read diagnostics (optional but can help debug problems)
  uint8_t x = tft.readcommand8(ILI9341_RDMODE);
  Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDMADCTL);
  Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDPIXFMT);
  Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDIMGFMT);
  Serial.print("Image Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDSELFDIAG);
  Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX); 
  
  Serial.println(F("Benchmark                Time (microseconds)"));
  delay(10);
  Serial.print(F("Screen fill              "));
  Serial.println(testFillScreen());
  delay(500);

  Serial.print(F("Text                     "));
  Serial.println(testText());
  delay(3000);

  Serial.print(F("Lines                    "));
  Serial.println(testLines(ILI9341_CYAN));
  delay(500);

  Serial.print(F("Horiz/Vert Lines         "));
  Serial.println(testFastLines(ILI9341_RED, ILI9341_BLUE));
  delay(500);

  Serial.print(F("Rectangles (outline)     "));
  Serial.println(testRects(ILI9341_GREEN));
  delay(500);

  Serial.print(F("Rectangles (filled)      "));
  Serial.println(testFilledRects(ILI9341_YELLOW, ILI9341_MAGENTA));
  delay(500);

  Serial.print(F("Circles (filled)         "));
  Serial.println(testFilledCircles(10, ILI9341_MAGENTA));

  Serial.print(F("Circles (outline)        "));
  Serial.println(testCircles(10, ILI9341_WHITE));
  delay(500);

  Serial.print(F("Triangles (outline)      "));
  Serial.println(testTriangles());
  delay(500);

  Serial.print(F("Triangles (filled)       "));
  Serial.println(testFilledTriangles());
  delay(500);

  Serial.print(F("Rounded rects (outline)  "));
  Serial.println(testRoundRects());
  delay(500);

  Serial.print(F("Rounded rects (filled)   "));
  Serial.println(testFilledRoundRects());
  delay(500);

  Serial.println(F("Done!"));

}


void loop(void) {
  for(uint8_t rotation=0; rotation<4; rotation++) {
    tft.setRotation(rotation);
    testText();
    delay(1000);
  }
  delay(4000);
}

unsigned long testFillScreen() {
  unsigned long start = micros();
  tft.fillScreen(ILI9341_BLACK);
  yield();
  tft.fillScreen(ILI9341_RED);
  yield();
  tft.fillScreen(ILI9341_GREEN);
  yield();
  tft.fillScreen(ILI9341_BLUE);
  yield();
  tft.fillScreen(ILI9341_BLACK);
  yield();
  return micros() - start;
}

unsigned long testText() {
  tft.fillScreen(ILI9341_BLACK);
  unsigned long start = micros();
  tft.setCursor(0, 0);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
  tft.println("Hello World!");
  tft.setTextColor(ILI9341_YELLOW); tft.setTextSize(2);
  tft.println(1234.56);
  tft.setTextColor(ILI9341_RED);    tft.setTextSize(3);
  tft.println(0xDEADBEEF, HEX);
  tft.println();
  tft.setTextColor(ILI9341_GREEN);
  tft.setTextSize(5);
  tft.println("Groop");
  tft.setTextSize(2);
  tft.println("I implore thee,");
  tft.setTextSize(1);
  tft.println("my foonting turlingdromes.");
  tft.println("And hooptiously drangle me");
  tft.println("with crinkly bindlewurdles,");
  tft.println("Or I will rend thee");
  tft.println("in the gobberwarts");
  tft.println("with my blurglecruncheon,");
  tft.println("see if I don't!");
  return micros() - start;
}

unsigned long testLines(uint16_t color) {
  unsigned long start, t;
  int           x1, y1, x2, y2,
                w = tft.width(),
                h = tft.height();

  tft.fillScreen(ILI9341_BLACK);
  yield();
  
  x1 = y1 = 0;
  y2    = h - 1;
  start = micros();
  for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color);
  x2    = w - 1;
  for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color);
  t     = micros() - start; // fillScreen doesn't count against timing

  yield();
  tft.fillScreen(ILI9341_BLACK);
  yield();

  x1    = w - 1;
  y1    = 0;
  y2    = h - 1;
  start = micros();
  for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color);
  x2    = 0;
  for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color);
  t    += micros() - start;

  yield();
  tft.fillScreen(ILI9341_BLACK);
  yield();

  x1    = 0;
  y1    = h - 1;
  y2    = 0;
  start = micros();
  for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color);
  x2    = w - 1;
  for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color);
  t    += micros() - start;

  yield();
  tft.fillScreen(ILI9341_BLACK);
  yield();

  x1    = w - 1;
  y1    = h - 1;
  y2    = 0;
  start = micros();
  for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color);
  x2    = 0;
  for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color);

  yield();
  return micros() - start;
}

unsigned long testFastLines(uint16_t color1, uint16_t color2) {
  unsigned long start;
  int           x, y, w = tft.width(), h = tft.height();

  tft.fillScreen(ILI9341_BLACK);
  start = micros();
  for(y=0; y<h; y+=5) tft.drawFastHLine(0, y, w, color1);
  for(x=0; x<w; x+=5) tft.drawFastVLine(x, 0, h, color2);

  return micros() - start;
}

unsigned long testRects(uint16_t color) {
  unsigned long start;
  int           n, i, i2,
                cx = tft.width()  / 2,
                cy = tft.height() / 2;

  tft.fillScreen(ILI9341_BLACK);
  n     = min(tft.width(), tft.height());
  start = micros();
  for(i=2; i<n; i+=6) {
    i2 = i / 2;
    tft.drawRect(cx-i2, cy-i2, i, i, color);
  }

  return micros() - start;
}

unsigned long testFilledRects(uint16_t color1, uint16_t color2) {
  unsigned long start, t = 0;
  int           n, i, i2,
                cx = tft.width()  / 2 - 1,
                cy = tft.height() / 2 - 1;

  tft.fillScreen(ILI9341_BLACK);
  n = min(tft.width(), tft.height());
  for(i=n; i>0; i-=6) {
    i2    = i / 2;
    start = micros();
    tft.fillRect(cx-i2, cy-i2, i, i, color1);
    t    += micros() - start;
    // Outlines are not included in timing results
    tft.drawRect(cx-i2, cy-i2, i, i, color2);
    yield();
  }

  return t;
}

unsigned long testFilledCircles(uint8_t radius, uint16_t color) {
  unsigned long start;
  int x, y, w = tft.width(), h = tft.height(), r2 = radius * 2;

  tft.fillScreen(ILI9341_BLACK);
  start = micros();
  for(x=radius; x<w; x+=r2) {
    for(y=radius; y<h; y+=r2) {
      tft.fillCircle(x, y, radius, color);
    }
  }

  return micros() - start;
}

unsigned long testCircles(uint8_t radius, uint16_t color) {
  unsigned long start;
  int           x, y, r2 = radius * 2,
                w = tft.width()  + radius,
                h = tft.height() + radius;

  // Screen is not cleared for this one -- this is
  // intentional and does not affect the reported time.
  start = micros();
  for(x=0; x<w; x+=r2) {
    for(y=0; y<h; y+=r2) {
      tft.drawCircle(x, y, radius, color);
    }
  }

  return micros() - start;
}

unsigned long testTriangles() {
  unsigned long start;
  int           n, i, cx = tft.width()  / 2 - 1,
                      cy = tft.height() / 2 - 1;

  tft.fillScreen(ILI9341_BLACK);
  n     = min(cx, cy);
  start = micros();
  for(i=0; i<n; i+=5) {
    tft.drawTriangle(
      cx    , cy - i, // peak
      cx - i, cy + i, // bottom left
      cx + i, cy + i, // bottom right
      tft.color565(i, i, i));
  }

  return micros() - start;
}

unsigned long testFilledTriangles() {
  unsigned long start, t = 0;
  int           i, cx = tft.width()  / 2 - 1,
                   cy = tft.height() / 2 - 1;

  tft.fillScreen(ILI9341_BLACK);
  start = micros();
  for(i=min(cx,cy); i>10; i-=5) {
    start = micros();
    tft.fillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i,
      tft.color565(0, i*10, i*10));
    t += micros() - start;
    tft.drawTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i,
      tft.color565(i*10, i*10, 0));
    yield();
  }

  return t;
}

unsigned long testRoundRects() {
  unsigned long start;
  int           w, i, i2,
                cx = tft.width()  / 2 - 1,
                cy = tft.height() / 2 - 1;

  tft.fillScreen(ILI9341_BLACK);
  w     = min(tft.width(), tft.height());
  start = micros();
  for(i=0; i<w; i+=6) {
    i2 = i / 2;
    tft.drawRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(i, 0, 0));
  }

  return micros() - start;
}

unsigned long testFilledRoundRects() {
  unsigned long start;
  int           i, i2,
                cx = tft.width()  / 2 - 1,
                cy = tft.height() / 2 - 1;

  tft.fillScreen(ILI9341_BLACK);
  start = micros();
  for(i=min(tft.width(), tft.height()); i>20; i-=6) {
    i2 = i / 2;
    tft.fillRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(0, i, 0));
    yield();
  }

  return micros() - start;
}

I should have mentioned that the graphics hardware is Adafruit's 2.4" TFT LCD.

I don't seem to be able to use the microSD slot on the graphics card consistently, but as a graphics display it works fine for my simple needs.

Steve

If that us expected to work then why have the "Pin Numbering" option in the IDE ?

Thanks for the information @VeloSteve. I don't have hardware handy, but I did try your sketch on an unconnected Nano ESP32 with the following dummy "configuration":

#define TFT_DC LED_RED
#define TFT_CS LED_GREEN

... just to see these via the RGB LED.

  • With either GPIO or Arduino mode, they blink with the same pattern.
  • Using the debugger, I can see that the SPI pins end up to the same places as well.
  • I have looked into the Adafruit_SPITFT library, and that plays no weird tricks: it's just a wrapper that calls into SPI and digitalWrite().

All of this suggests that this particular library should "just work", and with either menu setting! :face_with_spiral_eyes:

I also tested the combination you mentioned "working" (By GPIO / 6 / 7) and it does output on pins labeled D3/D4 - these two:

Is that where the display's DC and CS signals are connected?

Note: the yellow LED you mentioned (LED_BUILTIN) also doubles as the SCK SPI signal. So every time the hardware SPI sends a byte, you'll see it flash :wink:

I have been an idiot. I was moving between a breadboard setup where I could clearly see the pin labels D6 and D7 and a PCB prototype which actually connects DC and CS to D3 and D4. The tests I described used the PCB and I should have been using D3 and D4 all along.

But you had figured that out.

The successes were accidental. It seems to have happened because I picked up pin numbers from the schematic which numbers the pins around the perimeter starting from the board's TX1. That just happens to match up schematic pins 5 through 10 with GPIO 5 through 10. When I specified "6" expecting Arduino D6 it worked in GPIO mode because GPIO 6 is D3.

With the correct numbers:

  • Arduino pins
    • 3, 4. success
    • D3, D4. success
    • 6, 7. fail
  • By GPIO Pin
    • 3, 4. fail
    • D3, D4. success
    • 6, 7, GPIO. success

These results make sense. With integer values Arduino numbers work with the Arduino setting and GPIO numbers work with the GPIO setting. Someone has made the effort to see that the "D" values are interpreted correctly either way.

I was using the correct pin for the temperature sensor tests. There I was just seeing the known (but inconsistent) behavior of the OneWire library.

My sincere apologies for making this much more work than it should have been.

I can't wait to use analog pins as digital. Then I can have 5 numbering systems where A0 = D17 = GPIO1 = ADC1_CH0 = schematic pin 19. I promise to be more careful there.

1 Like

:rofl:

No worries, glad everything was sorted out in the end! :slight_smile:
Happy hacking! :hammer_and_wrench:

Well, maybe you figured out some pilot error in breadboarding @VeloSteve but I seem to be suffering some similar confusion. And no breadboarding error here.

I have a little learning project I've been intermittently playing with. In my copious free time. :rofl: I started on a Nano Every. Just because. And I had a happy dalliance with a Seeduino too but that's beside the point. My code's IO stuff all leans on the Nano Every (and presumably "legacy" Nano?) pinout format. Not much to it. 5VDC power in, addressable RGB output on pin 2 (const int RGB_Pin = 2) with a 330 ohm resistor and a pushbutton input on pin 4 (const int Button_Pin = 4). Simple, right?

const int RGB_Pin = 2
const int Button_Pin = 4
pinMode(Button_Pin, INPUT_PULLUP);
Adafruit_NeoPixel strip(LED_Count, RGB_Pin, NEO_GRB + NEO_KHZ800);

So I unplug the Every, plug in the ESP32, same wiring, get that bad boy recognized in the IDE, upload my sketch and.... darkness.

I check the Serial Monitor for some debugging code going to serial.println, looks like the pushbutton is being recognized. But nothing at all is happening on the RGB strip.

Assuming something's wrong with D2 I try pin 6 and D6 in the code and wiring. No joy. Then I start moving the wiring around while the ESP32 is humming along and happen to find the RGB strip signal on D3.

Change my code to pin 5 and the signal winds up on D2 where I wanted it.

And yet the button is fine on D4 both on the Every and the ESP32.

And, it doesn't seem to change anything if I select "by Arduino pin (default)" or "by GPIO number (legacy)" under the IDE's Tools / Pin Numbering and re-upload the sketch. No, wait, that's wrong... Setting it to GPIO definitely borks the pushbutton. :upside_down_face:

I don't understand what's going on here except to the extent that interpretation of the RGB output is subject to the Neopixel library rather than what's in the IDE. But it's enormously annoying. (IDE 2.2.1 on Windows).

For now I'm getting around the annoyance with "#if defined(ARDUINO_ARCH_ESP32)", just for the sake of consistency on the breadboard.

If someone can offer me some insight into this I'd really appreciate it. Is this an IDE bug or does the Neopixel library maybe need a tweak to deal with the pin numbering bit in the IDE? Or both? Can I report this to someone or someplace? Should I?

Thanks....

Exactly which ESP32 board do you have ?

It seems to me from other posts on the forum that the setting to choose which pin numbering system to use causes more problems than it solves

It should be easy. Choose "by Arduino pin" and use the pin names printed on the board, just like any other Arduino board. Behind the scenes the pin number will be converted to the corresponding chip GPIO pin number, just like any other Arduino board, and you never need to know the actual GPIO pin number being used

@UKHeliBob, it's an Arduino Nano ESP32. Are there flavors of this particular board? On the IDE's info screen all else I see is VID 0x2341 and PID 0x0070. And a s/n.

Since this ESP32 forcing my hand, this is the first I ever even looked at the pin numbering option. By Arduino pin was in fact the default.

Seriously, whatever is supposed to happen behind the scenes, doesn't seem to get passed through the NeoPixel library.

Thanks...

There is only one, vanilla flavoured version of the Nano ESP32

The pin numbering problem seems to have been caused by the desire by the desire by the Arduino team to use the Nano form factor and name for the board

On most, possibly all, ESP332 development boards the pin numbers printed on the board relate directly to the chip GPIO pin number so no translation is required

In trying to provide 2 different pin numbering schemes the Arduino team seem to have managed to deliver the worst of both worlds

I am sorry but I don't think that I can offer any more help

Thanks @UKHeliBob. Oddly (or not, lol) the numbering change-ability seems to work just fine for the pushbutton, and I'm inclined to believe that if I swapped the button and strip wiring assignments the code would still follow the button just fine, just not the strip.

So for now it's ifdefs for the win.

With what little I know of how the IDE works, I'm thinking I'm going to flag this as an IDE bug. I get the feeling that the pre-processor seems to have trouble with either just this library code or, judging by reading some other confused experimenters' comments all around the interwebz, possibly much if not maybe even all library code.

Thanks again for having a read!

EDIT: I get the sense that this is one of those things that if they fix it now, some people are gonna have to go running to update their code. :rofl:

Please, please follow this advice to the letter. Use 'By GPIO number' and replace all mentions of a pin by number with its name (for pin D2, use the symbol D2, not the number 5 or even 2).