Hi there,
And welcome here…
SO the Xiao Wiki always has a pin out Silk pin numbers i.e. (D3) or GPIO4 for example,
otherwise the Short answer for the ESP32-S3 (e.g., XIAO ESP32S3 “Plus”):
- There are no AVR-style “PORTA/PORTB”. Instead, the S3 exposes two 32-bit GPIO input registers:
- bits 0…31 → GPIO0…31
- bits 0…31 of a second register → GPIO32…63
- In Arduino (which uses ESP-IDF under the hood), you can snapshot all pins at once via the IDF GPIO structs/regs and then mask the bits you care about.
1) One-shot read of “all pins”
Drop this at the top of your sketch:
#include "soc/gpio_struct.h" // GPIO.in / GPIO.in1
#include "soc/gpio_reg.h" // REG_READ(), etc.
static inline uint64_t read_all_gpio()
{
// Low 32 (GPIO0..31)
uint32_t lo = GPIO.in;
// High 32 (GPIO32..63) live in GPIO.in1.val (bit0 == GPIO32)
uint32_t hi = GPIO.in1.val;
return ( (uint64_t)hi << 32 ) | lo;
}
2) Map your Arduino pin list to a mask (so you only look at your pins)
Arduino’s core has digitalPinToGPIO(pin)
which converts the Arduino pin number to the SoC GPIO number. Build a mask once, then use it every read:
// Build a 64-bit mask from your Arduino pin list
uint64_t make_mask(const int *pins, size_t n)
{
uint64_t m = 0;
for (size_t i = 0; i < n; ++i) {
int gpio = digitalPinToGPIO(pins[i]); // Arduino pin -> GPIO#
if (gpio >= 0) {
m |= (uint64_t)1 << gpio;
}
}
return m;
}
// Example: read only these pins in one shot
const int MY_PINS[] = { D0, D1, D2, D3, D4, D5 }; // <- your Arduino pins
uint64_t my_mask;
void setup() {
// Set your pins as inputs (with pullups if you need)
for (int p : MY_PINS) pinMode(p, INPUT_PULLUP);
my_mask = make_mask(MY_PINS, sizeof(MY_PINS)/sizeof(MY_PINS[0]));
Serial.begin(115200);
}
void loop() {
uint64_t snap = read_all_gpio(); // snapshot of *all* GPIOs
uint64_t only_mine = snap & my_mask; // keep only your pins
// test one pin, e.g., D2:
int gpioD2 = digitalPinToGPIO(D2);
bool d2_high = (only_mine >> gpioD2) & 1;
// or print the whole masked word
Serial.printf("mask=0x%016llX vals=0x%016llX\n",
(unsigned long long)my_mask,
(unsigned long long)only_mine);
delay(10);
}
3) “Which pins are already allocated / should I ignore?”
It depends on your sketch and board settings, but common ESP32-S3 caveats:
- USB (native) uses GPIO19 (D-) and GPIO20 (D+) if you’re using USB-CDC; don’t poke them then.
- Boot/strapping pins you generally leave alone: GPIO0, GPIO3, GPIO45, GPIO46.
- JTAG defaults (if enabled) occupy GPIO39…GPIO42.
- Your board (XIAO “Plus/Sense”) may route certain GPIOs to onboard devices (SD, I²C, display, etc.). Those are still readable, but you should exclude them if actively used by those peripherals.
f you’re unsure about your board’s Arduino-pin → GPIO mapping, you can print it at runtime:
void dump_pin_map(const int *pins, size_t n) {
for (size_t i = 0; i < n; ++i) {
int gpio = digitalPinToGPIO(pins[i]);
Serial.printf("Arduino pin %-3d -> GPIO%-2d\n", pins[i], gpio);
}
}
Make a MY_PINS list and mask that avoids the occupied lines. 
HTH
GL
PJ 