Search Microcontrollers

Thursday, May 21, 2015

Cortex M3 - SPI / 1

I have been playing with SPI a few times, but never on a Cortex M3 using CMSIS.

That, by itself, it should be a good reason to dig into this topic, however I have a nice project in mind and it will require SPI communicatin, so... let's  get to it!

Some basic stuff first :
SPI uses four pins: MOSI (Master Out Slave In), MISO (you guess it), CLK (clock) , SSEL and as Chip enable/select to activate the slave, normally a simple GPIO pin on the master.

NXP Cortex M3s implement a variation of SPI called SSP (Synchronous Serial Port) which supports the "old" SPI.
In the LPC1768 there are two SSP peripherals called SSP0 and SSP1.

The LPC17xx manual says:

"The two SSP interfaces, SSP0 and SSP1 are configured using the following registers:

  1. Power: In the PCONP register, set bit PCSSP0 to enable SSP0 and bit PCSSP1 to enable SSP1.
    Remark: On reset, both SSP interfaces are enabled (PCSSP0/1 = 1).
  2. Clock: In PCLKSEL0 select PCLK_SSP1; in PCLKSEL1 select PCLK_SSP0. In master mode, the clock must be scaled down.
  3. Pins: Select the SSP pins through the PINSEL registers and pin modes through the PINMODE registers.
  4. Interrupts: Interrupts are enabled in the SSP0IMSC register for SSP0 and SSP1IMSC register for SSP1. Interrupts are enabled in the NVIC using the appropriate Interrupt Set Enable register.
  5. Initialization: There are two control registers for each of the SSP ports to be configured: SSP0CR0 and SSP0CR1 for SSP0, SSP1CR0 and SSP1CR1 for SSP1.
  6. DMA: The Rx and Tx FIFOs of the SSP interfaces can be connected to the GPDMA controller
Remark: SSP0 is intended to be used as an alternative for the SPI interface, which is included as a legacy peripheral. Only one of these peripherals can be used at the any one time" 
(@NXP LPC17xx user manual)

It seems to me this is a pretty good checklist.

1) Power ON


Power CONtrol for Peripherals (PCONP) is the register we use to turn the SSP (or any other) interfaces on:

  LPC_SC->PCONP |= (1 << 21); /* Enable power to SSPI0 block */
  LPC_SC->PCONP |= (1 << 10); /* Enable power to SSPI1 block */

Normally we just need one, I reported the lines for both so you can decide which one to use.

2) Clock in

The Peripheral clock selection is done via the PCLKSEL0 and 1.
Each peripheral uses 2 bits

00 : PCLK_peripheral = CCLK/4 00
01 : PCLK_peripheral = CCLK
10 : PCLK_peripheral = CCLK/2
11 : PCLK_peripheral = CCLK/8

SSP0 uses bits 11:10 of PCLKSEL1 and SSP1 uses bits 21:20 of PCLKSEL0

LPC_SC->PCLKSEL1 &= ~(3<<10);  /* PCLKSP0 = CCLK/4 */
LPC_SC->PCLKSEL1 |=  (1<<10);  /* PCLKSP0 = CCLK */

LPC_SC->PCLKSEL0 &= ~(3<<20);  /* PCLKSP1 = CCLK/4*/
LPC_SC->PCLKSEL0 |=  (1<<20);  /* PCLKSP1 = CCLK */

3) Pins

Here we will set MISO, MOSI and CLK pins, plus you need to remember to set 1 GPIO as output to enable the slave, in this example we will assume you are using GPIO P1.21 as SSEL (Slave Selection / Enable).
Normally SPI slaves are selected active when SSEL is LOW.

A summary of the available configurations for SSP pins :



 /* ----> SSEL : output set to high. */
LPC_PINCON->PINSEL3 &= ~(0<<10);   /* P1.21 SSEL (used as GPIO)   */
LPC_GPIO1->FIODIR   |=  (1<<21);   /* P1.21 is output */
LPC_GPIO1->FIOPIN   |=  (1<<21);   /* set P1.21 high*/
  
 /* ----> SSP0 : SCK, MISO, MOSI  */
LPC_PINCON->PINSEL3 &= ~(3UL<<8);       /* P1.20 cleared  */
LPC_PINCON->PINSEL3 |=  (3UL<<8);       /* P1.20 SCK0  */
   /* P1.23, P1.24 cleared        */ 
LPC_PINCON->PINSEL3 &= ~((3<<14) | (3<<16)); 
   /* P1.23 MISO0, P1.24 MOSI0    */ 
LPC_PINCON->PINSEL3 |=  ((3<<14) | (3<<16)); 

 /* ----> SSP1 : SCK, MISO, MOSI  */
LPC_PINCON->PINSEL0 &= ~(0x3F<<14);  /* P0.7,8,9 cleared  */
    /* ... an then set to function 2 */
LPC_PINCON->PINSEL0 |=  (2UL<<14) | (2UL<<16) | (2UL<<18);

4) Interrupts

As suggested by the checklist, we will use the SSP Interrupt Mask Registers SSPxIMSC.


As you can see, these interrupts are used to identify error conditions or FIFO status to ease buffered communication.
Since SSP is a synchronous communication (unlike the UART), there is no point in having a RX data ready interrupt.
In fact, each time you want a slave device to send you some data, using SPI / SPSS you explicitly have to ask for it (polling), meaning you should also be already listening for it to arrive, no need for an interrupt.

Should you need to implement some more advanced control, feel free to enable those interrupts, in that case you should check the SSPxRIS (Raw Interrupt Status), SSPxMIS (Masked Interrupt Status) and SSPxICR (Interrupt Clear) registers.

5) Initialisation

Ok, we definitely need this one.
What we need here is to accurately set the bit frequency, so that it will match the frequency supported by the slave device we are interfacing.
This is done via the Prescaler Register (CPSR) that divides the peripheral clock (pclk) we initially configured in step 2 (PCLKSEL0 and 1).

So, let's assume our processor is running at 100MHz, we fed the peripheral with the same clock speed (by providing a divider = 1) and now we want to obtain a 400KBit/s rate.
How do we do that?

#define sspKBps 400000
int prescaler = SystemCoreClock / sspKBps; 
/* 100.000.000/400.000 = 250 */

LPC_SSP0->CPSR = prescaler; /* for SSP0 */
LPC_SSP1->CPSR = prescaler; /* for SSP1 */

Now we need to use the SSP Control registers to specify how many bits should be transferred and which protocol -Frame Format- to use (remember, the SSP can do more than SPI, in fact it supprots TI and Microwire formats too). 
Also, we can further reduce the bit rate here by dividing the value of the prescaler, plus we can set clock phase and polarity.

  
/* SSP0 : 8Bit, SPI, CPOL=0, CPHA=0 */                                  
LPC_SSP0->CR0  = 0x0007;   
/* SSP1 : 8Bit, SPI, CPOL=0, CPHA=0 */                                  
LPC_SSP1->CR0  = 0x0007;   

The Control Register 1 (CR1) allows to enable the SSP port and to configure it as master or slave.



LPC_SSP0->CR1  = 0x0002;       /* SSP0 enable, master  */
LPC_SSP1->CR1  = 0x0002;       /* SSP1 enable, master  */

6) DMA

I am sure some day I will feel particularly brave and will have a go at it, for now, let's just say that we can enable DMA transfers for SSP ports using the SSPxDMACR registers.



Ok, with all this our SSP port should be ready for communication, at least on the Master side.

So, where do we put output data and from where do we get input?
There is a Data Register SSPxDR (we can use the low 16 bits) that is used both for RX and TX.
Why is this possible?
SSP (or SPI) is synchronous communication, meaning you cannot write and read at the same time on the line, or better, for every byte you send out, you will get a byte back.

The LPC17xx manual says :

"Write: software can write data to be sent in a future frame to this register whenever the TNF bit in the Status register is 1, indicating that the Tx FIFO is not full. If the Tx FIFO was previously empty and the SSP controller is not busy on the bus, transmission of the data will begin immediately. Otherwise the data written to this register will be sent as soon as all previous data has been sent (and received). If the data length is less than 16 bits, software must right-justify the data written to this register.

Read: software can read data from this register whenever the RNE bit in the Status register is 1, indicating that the Rx FIFO is not empty.
When software reads this register, the SSP controller returns data from the least recent frame in the Rx FIFO. If the data length is less than 16 bits, the data is right-justified in this field with higher order bits filled with 0s"

Looks like we need to check the Status Register (SSPxSR)


So, putting it all together, to exchange 1 byte we need to :

int SSP1_sendbyte(int out)
{
// enable your GPIO used as SSEL
 LPC_GPIO1->FIOCLR = 1<<21; // enable slave
 // while(!(LPC_SSP1->SR & 1)) ; // Wait until TX Empty
 // might need a grace period here, depending on the slave
 LPC_SSP1->DR = out; // output data
 // while(LPC_SSP1->SR & (1 << 4)); // Wait until SSP is busy
  while(LPC_SSP1->SR & (1 << 2)); // Wait until we have data in RX
 LPC_GPIO1->FIOSET = 1<<21; // disable slave
 return LPC_SSP1->DR;
}

In a future post we will test this with some SPI device.

No comments: