Moving SPI1 to different pins on Gen 3 devices

One often requested feature is to remap SPI1 from D2, D3, D4 to other pins. The most common reason is that pins D3 and D4 are used by the Ethernet FeatherWing, and are not easily reconfigured. This prevents using the SPI1 secondary SPI interface when using Ethernet.

As it turns out, you can map most ports on the nRF52840 on Gen 3 devices (Argon, Boron, Xenon) from user firmware!

This was also made worse because prior to Device OS 1.5.0, you could not share the Primary SPI interface used by the Ethernet FeatherWing with other SPI peripherals. With newer Device OS, you can, but sometimes you still might want to have two separate SPI busses.

Here’s the Ethernet FeatherWing with a Xenon (no mesh configured), connected and breathing cyan with Device OS 1.4.2, while a LIS3DH accelerometer is connected to SPI1 on alternative pins!

All of the magic is in the reconfigureSpi() function:

void reconfigureSpi() {
	// This is the magic for reconfiguring SPI.
	// This code only works on Gen 3 devices (Argon, Boron, Xenon, B Series SoM)

	// This is what we want to remap to, but you can use different pins.
	// SPI   Pin  Hardware Pin
	// SCK   A0   P0.3
	// MOSI  A1   P0.4
	// MISO  A2   P0.28

	// The pin mapping table is handy for finding the hardware pin numbers:
	// https://docs.particle.io/reference/hardware/pin-info/?m=table&sort=num

	// You must bring up SPI1 on the original pins first, because otherwise SPI1.begin() will
	// overwrite the reconfiguration and revert it back to the old pins.
	SPI1.begin();


	// SCK and MOSI need to be configured as OUTPUT. MISO is INPUT.
	pinMode(A0, OUTPUT); // SCK
	pinMode(A1, OUTPUT); // MOSI
	pinMode(A2, INPUT);  // MISO

	// CS is configured above in the LIS3DHSPI object construction. It doesn't affect pin reconfiguration.

	// We reconfigure SPI1, which is nRF52 SPIM2. The addresses are in the nRF52 Product Specification.
	uint8_t *pBase = (uint8_t *)0x40023000;

	// ENABLE offset 0x500
	uint32_t *pENABLE = (uint32_t *)&pBase[0x500];

	// PSEL.SCK offset 0x508
	uint32_t *pPSEL_SCK = (uint32_t *)&pBase[0x508];

	// PSEL.MOSI offset 0x50c
	uint32_t *pPSEL_MOSI = (uint32_t *)&pBase[0x50c];

	// PSEL.MISO OFFSET 0x510
	uint32_t *pPSEL_MISO = (uint32_t *)&pBase[0x510];

	// Standard pin config for SPI1
	// SCK   D2   P1.01
	// MOSI  D3   P1.02
	// MISO  D4   P1.08

	// Disconnect the old pins
	*pPSEL_SCK = pselConfig(false, 1, 1);	// D2
	*pPSEL_MOSI = pselConfig(false, 1, 2);	// D3
	*pPSEL_MISO = pselConfig(false, 1, 8);	// D4

	// Restore the old pins back to INPUT mode (MISO was already input)
	pinMode(D2, INPUT); // SCK
	pinMode(D3, INPUT); // MOSI

	// Disable SPIM
	*pENABLE = 0;

	// Reconnect to the new pins
	*pPSEL_SCK = pselConfig(true, 0, 3); 	// A0
	*pPSEL_MOSI = pselConfig(true, 0, 4); 	// A1
	*pPSEL_MISO = pselConfig(true, 0, 28);	// A2

	// Reenable SPIM
	*pENABLE = 0x7;

	// Log.info("SCK=%lx MOSI=%lx MISO=%lx", *pPSEL_SCL, *pPSEL_MOSI, *pPSEL_MISO);

}

This is unsupported and may break in the future. Beware!

Find out more here:

5 Likes

Rick, welcome as this work-around is for 1.4.2, I assume that waiting for release of 1.5.0 is really a better policy for Ethernet Featherwinged Gen3 devices given that 1.5.0 isn't that far away?