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.

Tuesday, May 19, 2015

Cortex M3 - UART / 2

Interrupts are cool.
No, seriously, if you played with serial communications you know that using an ISR helps a lot because you never know when the other device decides to send a few bytes on the line.
Asynchronous communication in full duplex baby!

So it is good to have an Interrupt Service Routine that gets the data for you from the line while you are busy doing other things.
Typically you are going to have a buffer with two pointers: one knows where to find the first location in the buffer where to write data and the other knows the first location where you did not get data out of the buffer yet.
If the two pointers point to the same lcoation, then there is no data to be extracted from the buffer.

In general this is a convenient way to buy some time and check every now and then if we received some data.


In the image above you can see an example where 3 bytes were received, so the write pointer points to the 4th location and 1 byte was read from the buffer, so the Read Ptr points to the 2nd location.
The two pointers have different values, so there is still data to be read in the buffer.
Obviously while you read and write you increment the relevant pointer, resetting it to zero when it reaches the end of the buffer.

This is one way to manage asynchronous communications, of course you could add some logic in your Interrupt handler routine to make your application react immediately i.e. when a specific character is received.
It is in genreral good practice, tho, to have as little logic as possible in the ISR routines, they should complete in the shortest amount of time possible.

Now, the funny thing is that the buffer we described is also called a FIFO (First In-First Out) buffer... which is already supported via hardware in the Cortex M3.

That's right, if you needed just a few bytes in your buffer, no need to set up an ISR, you could simply enable the internal FIFO and you can still do it even if you decide to use interrupts.

void SER_fifo(int uart,uint32_t enable,uint32_t dma,uint32_t trigger)
{
LPC_UART_TypeDef *pUart;
pUart = __get_uart_base(uart);
pUart->FCR |= (enable&0x1)|((dma&1)<<3)|((trigger&0x3)<<6);
}

The Fifo Control Register (FCR) can be used to enable the Fifo, define how many bytes should be received by it before triggering an IRQ and if it should use a dma channel to automatically copy the data to a specific memory location.
Neat eh?

@NXP

You can also reset the fifos (both the output fifo and the input fifo)... wait.. output fifo?
Yes, serial communication might be a bit slow for MCUs and imagine you need to send a few bytes out: the output fifo will be happy to store some of those bytes for you so you can go have fun doing other interesting tasks while it handles the communication.
You can do the same thing with an ISR routine or even use both at the same time.
Notice that you can activate both (RX and TX) fifos or none of them, but you cannot activate just RX and not TX or the other way round.

Now, let's see the interrupts.


The UARTxIER are the Interrupt Enable Registers for the various UARTS.
There are two interrupts we want to consider there :
- The one generated when a new byte is received (or when the number of bytes in the fifo reaches the trigger threshold, if enabled) and this is the RBR one.
- The other, generated when the line is ready to send out some new data, meet the THRE (Transmit Hold Register Empty) interrupt.
The third one is triggered when the status of the receive line changes.

#define IER_RBR  0x01
#define IER_THRE 0x02
#define IER_RXLS 0x04
pUart->IER = IER_RBR | IER_THRE | IER_RXLS;  

That line enables the uart interrupts... almost.
Actually we also need to enable the Uartx IRQ in the Nested Vector Interrupt Controller (NVIC)  which has a cool name because it does cool stuff.

switch (uart)
{
  case 0: NVIC_EnableIRQ(UART0_IRQn); break;
  case 2: NVIC_EnableIRQ(UART2_IRQn); break;
  case 3: NVIC_EnableIRQ(UART3_IRQn); break;
}

Now interrupts will be fired upon events and we should really do something with them.
To capture them we just need to define a function (one per each uart we use) with a specific name, I am using uart2 so :

void UART2_IRQHandler(void) __irq
{
   ...
}

Now we need to fill it with some good code, because I never had luck in succesfully compiling a statement like "...", so I guess it's not going to work for you either.

First thing, let's just say we want to have an input buffer which will be filled by the IRQHandler procedure.

#define UART_RX_BUFSIZE 32
uint8_t UART_RxBuf[UART_RX_BUFSIZE];
uint8_t bufWptr =0;
uint8_t bufRptr = 0;

Then we need to consider that the IRQHandler will be called for every kind of uart (uart2 in my case) related interrupt.
By "every" we actually mean as many as you can count with two fingers (or two bits), in other words : four.

This is the Interrupt Identification Register (IIR) which can be used to get the IntID, a 2 bit number that identifies which interrupt we are dealing with.



void UART2_IRQHandler(void) __irq
{
uint8_t intId;
intId = ((LPC_UART2->IIR)>>1)& 0x7;

switch (intId)
{
  case INTID_RXLS : break;// whatever
  case INTID_RDA  : 
while((LPC_UART2->LSR) & 0x1) 
                {
        UART_RxBuf[bufWptr++] = LPC_UART2->RBR;
        if (bufWptr >= UART_RX_BUFSIZE)
         bufWptr = 0;
}
break;
  case INTID_CTI  : break; // whatever
  case INTID_THRE : break; // whatever
}
}

This is a simple implementation that should provide RX interrupt handling, notice it does not check if our software buffer is overran.

Let's just add a couple of functions to get the data out of the buffer and check if we should do it.

int SER_hasBufData()
{
 return (bufWptr!=bufRptr);
}

uint8_t SER_getBufData()
{
 if (bufRptr>=UART_RX_BUFSIZE) bufRptr=0;
 return UART_RxBuf[bufRptr++];
}

And finally we can update the test program

int main(void)
{
  SystemInit();
  setpll0(25,2,3);
  SystemCoreClockUpdate();
  SER_init(2,115200);
  SER_fifo(2,1,0,0);
  while (1) 
  {
   delay(10000000);
   SER_putString (2, ".");
   while (SER_hasBufData())
   {
SER_putString (2, "Received : ");
SER_putChar(2,SER_getBufData());
SER_putString (2, "\r\n");
   }  
  }
  return 0;
}

As usual, you can get my full code here.