No you don’t have to use the default SPI, I2C and UART pins that are marked on the pinout card and on the board header pin labels.
I2C and SPI pins have chosen defaults because in the Arduino IDE and CircuitPython, you can just create an I2C instance (for example), without passing in the IO and it’s still required to work, and many libraries work this way.
You are free to use any free IO on the headers for SPI and I2C and the ESP32 chip is smart enough to internally re-route them to keep them as a hardware peripheral (assuming you haven’t reached the limit of each peripherals hardware count) … it’s very cool.
Just remember to pass in the new IO you are using in the constructor to tell the instance you are not using the defaults, and to use the provided IO instead.
UART pins do have defaults in Arduino as well, but I don’t set what they are and require you to initialise which IO to use when you create your UART instance.
For example using IO10 as RX, and IO11 as TX in Arduino:
Serial1.begin(9600, SERIAL_8N1, 10, 11);
For reference, here’s the number of SPI, I2C and UART hardware peripherals available on each ESP32 chips on Unexpected Maker boards.
For the CS pin for SPI modules, you can connect that to any free IO on your board. Just remember to update the code you are using to reflect this change.
tags: SPI I2C UART Peripherals Pins Pinouts