Friday, September 21, 2018

Arduino Due - SPI

Introduction

The Arduino Due has 2 SPI units on board and when configured as Master uses 4 chip select lines to allow selection of up to 16 slaves per SPI unit. The SPI Controller is a sophisticated controller that can be configured in a variety of ways that should solve most serial communications needs.

Electrical Warning

The Arduibo Due is powered by 3.3V and it's I/O lines ARE NOT 5V tolerant. Each pin can source a current of 3 mA or 15 mA, depending on the pin, or sink a current of 6 mA or 9 mA, depending on the pin with a total of 130mA DC Output Current on all I/O lines.

I will not only discuss the way in which SPI can communicate using the SPI Controller but I will also show how it can be done using a method known as bit banging where each bit is clocked to the bus manually. I have provided two projects in the download; one using the onboard SPI Controller and the other using the bit bang method.

SPI using SPI Controller

As we have done is previous articles we need to start by initializing the I/O lines that the SPI Controller will be using which are; Master In Slave Out (MISO) as input, Master Out Slave In (MOSI) as output, Serial Peripheral Clock (SPCK) as output and the Peripheral Chip Select (NPCS0) as output. All the lines are on PORTA and all will be using Peripheral A. If you have been following this series of Arduino Due articles this should look familiar to you and if not you may want to read my article Arduino Due PIO 101.


void InitPIO()
{
	//Because we are using PORTB.PIN25 in peripheral B mode
	//	we need to enable the clock for that line.
	PMC->PMC_PCER0 |= _BV(ID_PIOA);
	
	//configure for input
	PIOA->PIO_PDR |= PIO_PA25;
	PIOA->PIO_ODR |= PIO_PA25;		//Input
	
	PIOA->PIO_PDR |= PIO_PA26;		//MOSI
	PIOA->PIO_OER |= PIO_PA26;		//MOSI	Output
	PIOA->PIO_ABSR &= ~PIO_PA26;	//Peripheral A
	
	PIOA->PIO_PDR |= PIO_PA27;		//SPCK
	PIOA->PIO_OER |= PIO_PA27;		//SPCK	Output
	PIOA->PIO_ABSR &= ~PIO_PA27;	//Peripheral A
	
	PIOA->PIO_PDR |= PIO_PA28;		//NPCS0
	PIOA->PIO_OER |= PIO_PA28;		//NPCS0	Output
	PIOA->PIO_ABSR &= ~PIO_PA28;	//Peripheral A	
	PIOA->PIO_PUER |= PIO_PA28;		//pull-up
}

    

Initializing the SPI Controller is fairly straight foeward, it resembles other Atmel chips in that there are the same 4 modes determined by the CPHA and CPOL registers values, we will be using MODE0 in this examples which menas that the inactive state of SPCK is low and the data is changed on the leading edge of SPCK. And since we are only interfacing to a single pheriperal we will only be using a single chip select line.

A note here about peripheral selection that I found kind of confusing so I want to go over it in a little more detail. When interfacing to a single device set the PS value in the Mode Register to 0 (PS = 0 = Fixed Peripheral Select) and you only have to set the value once in the mode registers PCS field. But if you are interfacing to more than one device set PS in the Mode Register to 1 (PS = 1= Variable Peripheral Select) and then when transmitting data set the PCS field in the Transmit Data Register to the appropriate value along with the data to be sent.


void InitSPI()
{
	//Enable clock for the SPI0 peripheral
	PMC->PMC_PCER0 |= _BV(ID_SPI0);
	//Disable the SPI0 peripheral so we can configure it.
	SPI0->SPI_CR = SPI_CR_SPIDIS;
	//Set as Master, Fixed Peripheral Select, Mode Fault Detection disabled and
	//	Peripheral Chip Select is PCS = xxx0 NPCS[3:0] = 1110
	SPI0->SPI_MR = SPI_MR_MSTR | SPI_MR_MODFDIS | 0x000e0000;
	
	//SPCK baudrate = MCK / SCBR = 84MHz / 128 = 656250Hz
	SPI0->SPI_CSR[0] |= 0x00008000;		
	
	//Enable the SPI0 unit
	SPI0->SPI_CR = SPI_CR_SPIEN;
}

    

The write is pretty straight forward in that you just have to transfer the value to the Transmit Data Register and if the unit is enabled it will automatically start the transfer. In this example before starting the transfer I check to make sure the last transmission has completed then after I store the value in the register I wait until the controller transfers the value to the serializer thus freeing us the register.


void SPIWrite(uint8_t val)
{
	//Wait for previous transfer to complete
	while ((SPI0->SPI_SR & SPI_SR_TXEMPTY) == 0);
	
	//load the Transmit Data Register with the value to transmit
	SPI0->SPI_TDR = val;	
	
	//Wait for data to be transferred to serializer
	while ((SPI0->SPI_SR & SPI_SR_TDRE) == 0);
}

    

SPI using Bit Bang

The initialization in this example uses the PIO Controller to directly manipulate the I/O ports so we set them up accordingly.


void InitPIO()
{
	//We have an input so we need to clock it
	PMC->PMC_PCER0 = _BV(ID_PIOC);
	
	//Configure pins
	PIOC->PIO_PER |= PIO_PC13;	//MISO	
	PIOC->PIO_PER |= PIO_PC14;	//MOSI	
	PIOC->PIO_PER |= PIO_PC15;	//SPCK	
	PIOC->PIO_PER |= PIO_PC16;	//NPCS0
	
	//Configure MISO as input the rest out.
	PIOC->PIO_ODR |= PIO_PC13;	//MISO	Input
	PIOC->PIO_OER |= PIO_PC14;	//MOSI	Output
	PIOC->PIO_OER |= PIO_PC15;	//SPCK	Output
	PIOC->PIO_OER |= PIO_PC16;	//NPCS0	Output
	
	PIOC->PIO_PUDR |= PIO_PC15;	//SPCK - Inactive low
	PIOC->PIO_PUER |= PIO_PC16;	//CS is active low
}

    

We just want to enable the chip select line by setting it to a low state, transfer the data then set the chip select line back to it's default state.


void SPIWrite(uint8_t val)
{
	//Set the chip select line low
	PIOC->PIO_CODR = PIO_PC16;
	
	TransferData(val);
	
	//Chip select line high
	PIOC->PIO_SODR = PIO_PC16;
}

    

This is the method that actually transfers the data out to the MOSI pin and receives data from the slave on the MISO pin. This is done manually by setting the bit bit on the output line, pulsing the clock allowing the slave to retrieve it and then checking the input pin for the slaves bit.


uint8_t TransferData(uint8_t data)
{
	volatile unsigned char result = 0xff;
	
	//Go through the bits, MSB first and transfer them out.
	for (int i = 0; i < 8; i++)
	{
		//Set the MOSI bit appropriately
		if (data & 0x80)
			PIOC->PIO_SODR = PIO_PC14;
		else
			PIOC->PIO_CODR = PIO_PC14;
		
		PIOC->PIO_SODR = PIO_PC15;
		delay();
		PIOC->PIO_CODR = PIO_PC15;
		delay();
		
		result <<= 1;
		
		//Read input bit by bit and shift it into result
		if (PIOC->PIO_PDSR & PIO_PC13)
			result |= 0x01;
		
		data <<= 1;
	}
	
	PIOC->PIO_SODR = PIO_PC14;
	
	return result;
}    

    

Summary

SPI is an important serial communcation protocol to learn as a lot of periperals use it as a means of communications. I have uncovered a little of what the SPI Controller on the SAM3X8E chip is capable of but I hope have provided enough information to allow you to understand and create your own SPI library.

References

[1]MicroChip.com, SAM3X/SAM3A Series Complete, data sheet, Atmel-11057-32-bit-Cortex-M3-Microcontroller-SAM3X/SAM3A Datasheet
[2]Gaynomad, Due pinout diagram, http://forum.arduino.cc/index.php/topic,132130.0.html
[3]Arduino Due, schematics, eagle files, and more, https://www.sparkfun.com/products/11589
[4]Wikipedia.com, Serial Peripheral Interface Bus http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
Top ^