Multiple SPI chip select question

I am developing on a backplane which includes a Mega2560, an ITDB02s TFT display with an SD card adapter(SPI), and multiple other SPI devices. I am accessing the ITDB02s’s SD card SPI interface using the TinyFAT library by Henning Karlsen. The library assumes there is no other SPI devices besides the SD card, so it defaults to the Mega’s pin 53 as the SPI ‘SS’ select signal. Basically, the SS is defined as bit 0 on PORTB. This works, but I want to have other SPI devices, so I need to have a range of unique chip select addresses. Unfortunately, there is no more room to manuever in PORTB, which is where TinyFAT implements its chip select routine.

Checking the backplane, Mega pins D42-49 are available. I believe this corresponds to PL7-0 on PORTL. I am not very experienced with direct port addressing, but I modified the tinyFAT code to implement the chip select with PORTL. It sounded like a good idea, but so do a lot of things. Maybe someone can spot something in my code. Below is the pertinent TinyFAT code with the original code commented out. Any help with this appreciated. Free pizza if you have something that will work.

//byte SS = 0;  ( default bit 0 PORTB = Mega pin D53 )
byte SS = 7; // ( bit 7, PORTL = Mega D42 )   // new

void SPI_SS_HIGH()
{
	//PORTB |= _BV(SS);  
	PORTL |= _BV(SS);    // new
}

void SPI_SS_LOW()
{
	//PORTB &= ~_BV(SS);
	PORTL &= ~_BV(SS);  // new
}

byte mmc::initialize(byte speed) {
  byte  i;
  uint16_t counter;
  uint32_t answer;

  disk_state = DISK_ERROR;

  // setup SPI I/O pins

  //PORTB |=  _BV(SCK) | _BV(SS) | _BV(MISO); // set SCK+SS hi (no chip select), pullup on MISO
  //DDRB  |=  _BV(SCK) | _BV(SS) | _BV(MOSI); // set SCK/MOSI/SS as output
  //DDRB  &= ~_BV(MISO);   // set MISO as input

  PORTB |=  _BV(SCK)  | _BV(MISO); // set SCK, pullup on MISO        // new
  DDRB  |=  _BV(SCK)  | _BV(MOSI); // set SCK/MOSI as output         // new
  DDRB  &= ~_BV(MISO);             // set MISO as input              // new 
                               
  PORTL |=  _BV(SS); // set SS                                       // new  
  DDRL  |=  _BV(SS) ; // set SS as output                            // new

.
.
.

I thought I could just change the SPI port addressing. It just locks up the Arduino. What am I missing here?

You have to set Pin 53 (hardware Slave Select) as an OUTPUT even is you don't use it. If it is set as an INPUT then the SPI hardware will be in Slave mode.

You also need to set all chip selects high before doing anything with SCLK, MOSI. With multiple SPI devices I would recommend two things:

Read the SPI parts of each datasheet carefully to see which of the 4 SPI modes are used and whether the device really is SPI compliant (ie completely ignores SCLK/MOSI when deselected and tri-states MISO when deselected. Some devices that share pins for I2C and SPI are not at all SPI compliant, note).

Use real external pull-up resistors on all the CS lines - this means you know all devices are inactive until you enable them. If a device has its own pull-up on its CS then you don't need one of course. The issue is that its actually impossible to prevent some of the CS from being active at reset time since the microcontroller sets them as inputs and they can float to any value.

If you don't use pull-up resistors then do this at least:

set all CS lines HIGH and enable them as OUTPUTs. Initialise the SPI hardware. Initialise each SPI device in turn sending whatever command is needed to reset it to a known state.

Another complexity with sharing SPI bus is that you may have to switch the mode (clock polarity etc) and possibly speed between devices. You may also have to disable interrupts across SPI calls if any device uses an SPI call in its interrupt handler(s).

Thanks for the responses. I got it to work. If you are testing an SD interface, it can help to put an SD card in the slot. MarkT and johnwasser's suggestion about explicitly setting the default SS address as an output, even if it is not going to be used, is probably a good idea. I implemented it but my code had other problems. I have not tested it without the explicit assignment, so I can't say if it is critical. I guess you guys split a virtual pizza.

Apparently, to address the 8th bit, it is _BV(7), and not _BV(B10000000). Also, I had a "type" problem setting a variables to the PORTx pointers.

However, now the user can assign unique chip select addresses with a single call at run time. I have tested it out and I can access the SD with the SPI chip select set to any of the Mega pins numbers corresponding to Mega PORTL.

Yikes it's been 20 years since I had to untangle C indirect pointer addressing. Time for a beer.

// This does not work:

  uint8_t SSPORT = PORTL;
  uint8_t SSDDR =  DDRL;
  SSPORT |=  _BV(SS); // set SS
  SSDDR  |=  _BV(SS) ; // set SS as output

// But this does:

volatile uint8_t *SSPORT = &PORTL;
volatile uint8_t *SSDDR =  &DDRL;
*SSPORT |=  _BV(SS); // set SS
*SSDDR  |=  _BV(SS) ; // set SS as output

vanduino: Yikes it's been 20 years since I had to untangle C indirect pointer addressing. Time for a beer. ...

volatile uint8_t *SSDDR =  &DDRL;
...
*SSDDR  |=  _BV(SS) ; // set SS as output

Wouldn't this be easier? ...

DDRL |= _BV (SS);

Or keep it readable:

pinMode (53, OUTPUT);  // or whatever the pin was

I have the capability of installing multiple SPI devices on the backplane. Each configuration creates the possibility of conflicting assignments for the SPI chip selects. I can reserve 8 pins on the backplane, corresponding to Arduino Mega pins D42-49 (PL0-PL7), as well as the default D53 (PB0). That makes 9 pins on 2 ports available as chip selects for separate SPI devices. A device may have one chip select assignment for one configuration, but need to be reassigned for another. A device may share a driver with another SPI device. So if I edit the drivers to take a chip select at run time from my reserve pool, I can avoid conflicts. That is the reason for using the pointers *SSDDR and *SSPORT, because as SS changes, the DDR and PORT can change. The modified code for the assignment routine in TinyFAT below:

void mmc::setSSpin(const uint8_t _pin) {
	#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
		if (_pin==53) { 
			SSPORT = &PORTB;
			SSDDR = &DDRB;
			SS=0;
		}
		else if ((_pin>=42) and (_pin<=49)) {
			SSPORT = &PORTL;
			SSDDR = &DDRL;
			SS= 49 - _pin;
		}
	#else	if ((_pin>=8) and (_pin<=10))	{ 
				SS=_pin-8;
				SSPORT = &PORTB; 
				SSDDR = &DDRB;
			}
	#endif
	}

Anyhow, the backplane now appears to handle multiple SD cards without conflict, using the modified TinyFAT. Hopefully, drivers for other SPI devices can be similarly modified as needed.

Am I to understand the problem is solved?

Solved. Thanks.