You are on page 1of 11

SPI

SPI can be used as a simple and efficient way of communication between FPGAs and other chips.

The SPI project



Part 1: What is SPI? Part 2: A simple implementation Part 3: Application

We used a KNJN Saxo-L board for this project.

Links

A SPI overview from Microchip.

What is SPI?
SPI is a simple interface that allows one chip to communicate with one or more other chips.

How does it look?


Let's start with a simple example where only two chips have to communicate together. SPI requires 4 wires to be used in between the two chips.

As you can see, the wires are called SCK, MOSI, MISO and SSEL, and one of the chip is called the SPI master, while the other the SPI slave.

SPI fundamentals
Basically: 1. 2. 3. 4. 5. It is synchronous. It is serial. It is full-duplex. It is not plug-and-play. There is one (and only one) master, and one (or more) slaves.

In more details: 1. 2. 3. 4. 5. A clock is generated by the master, and one bit of data is transferred each time the clock toggles. Data is serialized before being transmitted, so that it fits on a single wire. There are two wires for data, one for each direction. The master and slave know beforehand the details of the communication (bit order, length of data words exchanged, etc...) The master is always the one who initiates communication.

Because SPI is synchronous and full-duplex, every time the clock toggles, two bits are actually transmitted (one in each direction).

Simple transfer
Let's assume that the master and slave expect 8-bits data transfers, with MSB transmitted first. Here's how would look a single 8-bits data transfer.

The line MOSI is the "master output" while MISO is the "slave output". Since SPI is full-duplex, both lines toggles simultaneously, with different data going from master-to-slave, and slaveto-master. In more datails:

1. 2. 3.

The master pulls SSEL down to indicate to the slave that communication is starting (SSEL is active low). The master toggles the clock eight times and sends eight data bits on its MOSI line. At the same time it receives eight data bits from the slave on the MISO line. The master pulls SSEL up to indicate that the transfer is over.

If the master had more than one 8-bits data to send/receive, it could keep sending/receiving and de-assert SSEL only when it is done.

Multiple slaves
An SPI master can communicate with multiples slaves by connecting most signals in parallel and adding SSEL lines, or by chaining the slaves.

With the multiple SSEL lines technique, only one SSEL line is actived at a time, and slaves that are not selected must not drive the MISO line.

How fast is it?


SPI can easily achieve a few Mbps (mega-bits-per-seconds). That means it can be used for uncompressed audio, or compressed video.

Links

The Serial Peripheral Interface Bus from Wikipedia

SPI - A simple implementation


ARM processor
To get an opportunity to test our newly acquired SPI knowledge, we use a Saxo-L board. It has an ARM7 processor (LPC2138) and a Cyclone FPGA (EP1C3), connected by a SPI bus. The ARM is used as a SPI master, while the FPGA is used as a SPI slave. The Saxo-L ARM processor has actually two SPI interfaces, one called SPI0, and a more advanced one called SPI1/SSP. They are both equally easy to use. We are using SPI1/SSP on Saxo-L, as it is prewired on the board.

SPI master - C ARM code


Using SSP is just a matter of initializing a few registers, and then writing/reading data to send/receive automatically. void main(void) { // initialize SSP SSP0CPSR = 0x02; // SSP max speed SSP0CR0 = 0x07; // SSP max speed, 8 bits SSP0CR1 = 0x02; // SSP master mode PINSEL1 = 0x2A8; // SSP mode for pins P0.17 to P0.20 while(1) { // send two bytes SSP0DR = 0x55; // one nice thing about the SSP is that it has a 8-words deep FIFO SSP0DR = 0x54; // so here we write the data to be sent without worrying // now wait until both bytes are sent while(!(SSP0SR & 0x01)); // now we can read the two bytes received... and do anything with them int data1 = SSP0DR; int data2 = SSP0DR; // ... } }

SPI slave - HDL FPGA code


Now for the SPI slave in the FPGA. Since the SPI bus is typically much slower than the FPGA operating clock speed, we choose to over-sample the SPI bus using the FPGA clock. That makes the slave code slightly more complicated, but has the advantage of having the SPI logic run in the FPGA clock domain, which will make things easier afterwards. First the module declaration. module SPI_s l ave(c l k , SCK, MOSI, MISO, SSEL, LED);

input clk ; input SCK, SSEL, MOSI; output MISO; output LED; Note that we have "clk" (the FPGA clock) and an LED output... a nice little debug tool. "clk" needs to be faster than the SPI bus. Saxo-L has a default clock of 24MHz, which works fine here.

We sample/synchronize the SPI signals (SCK, SSEL and MOSI) using the FPGA clock and shift registers. // sync SCK to the FPGA clock using a 3-bits shift register reg [2:0] SCKr; always @(posedge clk) SCKr <= {SCKr[1:0], SCK}; wire SCK_risingedge = (SCKr[2:1]==2'b01); // now we can detect SCK rising edges wire SCK_fallingedge = (SCKr[2:1]==2'b10); // and falling edges // same thing for SSEL reg [2:0] SSELr; always @(posedge clk) SSELr <= {SSELr[1:0], SSEL}; wire SSEL_active = ~SSELr[1]; // SSEL is active low wire SSEL_startmessage = (SSELr[2:1]==2'b10); // message starts at falling edge wire SSEL_endmessage = (SSELr[2:1]==2'b01); // message stops at rising edge // and for MOSI reg [1:0] MOSIr; always @(posedge clk) MOSIr <= {MOSIr[0], MOSI}; wire MOSI_data = MOSIr[1]; Now receiving data from the SPI bus is easy.

// we handle SPI in 8-bits format, so we need a 3 bits counter to count the bits as they come in reg [2:0] bi tcn t ; reg byte_rece ived; / / high when a byte has been rece ived

reg [7:0] byte_data_rece ived; always @( posedge clk) begin i f(~SSEL_active) bitcnt <= 3'b000; else i f(SCK_risingedge) begin bitcnt <= bitcnt + 3'b001; // implement a shift-left register (since we receive the data MSB first) byte_data_received <= {byte_data_received[6:0], MOSI_data}; end end always @(posedge clk) byte_received <= SSEL_active && SCK_risingedge && (bitcnt==3'b111); // we use the LSB of the data received to control an LED reg LED; always @(posedge clk) if(byte_received) LED <= byte_data_received[0]; Finally the transmission part. reg [7:0] byte_data_sent; reg [7:0] cnt; always @(posedge clk) if(SSEL_startmessage) cnt<=cnt+8'h1; // count the messages always @(posedge clk) if(SSEL_active) begin if(SSEL_startmessage) byte_data_sent <= cnt; // first byte sent in a message is the message count else if(SCK_fallingedge) begin if(bitcnt==3'b000) byte_data_sent <= 8'h00; // after that, we send 0s else

byte_data_sent <= {byte_data_sent[6:0], 1'b0}; end end assign MISO = byte_data_sent [7] ; / / send MSB f i r s t

// we assume that there is only one slave on the SPI bus // so we don't bother with a tri-state buffer for MISO // otherwise we would need to tri-state MISO when SSEL is inactive endmodule We have established communication between the ARM and the FPGA!

Running the code


As we step through the ARM code, we can see the LED changing state, and the data returned by the FPGA.

Now let's see if we can do useful things with SPI.

SPI - Application
LCD interface
Since we already know how to drive a graphic LCD panel, in particular in text mode, let's try to write text out from the LPC. From the FPGA point of view, the LCD controller uses a few blockrams to hold the font, characters to display, etc... So we just have to make sure that SPI data gets into the blockrams.

From the ARM point of view, the function that sends data to the LCD blockrams is called "SSP_WriteBlock". // function used to write in the LCD blockrams void SSP_WriteBlock(char* ob, int len, int addr); void LCD_PutString(char* s, int x, int y) { // the blockram that holds the characters starts at address 0, and have 80 characters per line SSP_WriteBlock(s, strlen(s), x+y*80); } void main(void) { SSP_init(); LCD_PutString("Hello world!", 0, 0); LCD_PutString("FPGA4FUN.COM - where FPGAs are fun.", 4, 3); LCD_PutString("Char set:", 0, 7); int i; for(i=0; i<128; i++) LCD_PutChar(i, i, 8); LCD_Cursor_off(); } After configuring the FPGA with the LCD controller, and running the ARM code, here's what we get:

More ideas of projects



Interface to a SPI flash memory Create a complete console or GUI on the LCD Create an embedded scope using Flashy+FPGA+ARM+LCD ...

Your turn to experiment!

You might also like