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.
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
[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