You are here

How to use the SPI (master) correctly | Cypress Semiconductor

How to use the SPI (master) correctly

Summary: 16 Replies, Latest post by Bob Marlowe on 21 May 2015 06:48 AM PDT
Verified Answers: 0
Last post
Log in to post new comments.
Aussie Susan's picture
42 posts

(By the way, please don’t say to look at the examples – I have they I cannot see how they cover my situations as described below.)

 I am trying to communicate with an MAX3421E (USB Host controller chip) using an SPI (master) component. The bit rate is 3MHz with 8-bit values. At this stage, I'm writing this as a polled interface - at some time in the future (as the project grows) I'll probably need to move to an interrupt driven approach.

There are two situations that I need to handle:

1) I need to read/write individual registers which means that I need to write a command byte and then either a value or dummy for the register value to be written or read.

2) I need to read/write a FIFO with up to 64 values (plus a leading command byte)

In the first case, I receive a 'status' value back as I send the command and, when reading a register, I will receive the register value with the 2nd byte.

In the second case I need to send the command (I can ignore the status value in this situation) and then send or receive up to 64 values from/to memory. When writing my values to the slave, I don’t need anything back so I’m OK with overflowing the FIFO. However I do call “xxx_ClearRxBuffer” afterwards to make sure it is clear before anything else uses the SPI component.

For both cases, I'm using the "xxx_PutArray" function to write the values to the SPI component and the only functions that seem to apply to read the values are "xxx_GetRxBufferSize" and "xxx_ReadRxData".

From what I understand, I need to ensure that there are always values to send to ensure that the \SS\ line stays low for the entire transaction (however many values long)

I have tried using the SPI component both with and without the software buffer and I'm getting different issues with both approaches.

Without the software buffer (using the 4 value FIFO for both Tx and Rx), I can send the 2 values for case #1 and I can receive the status and 2nd value OK. However, for case #2, I *think* it is OK for writing multiple values to the chip's FIFO but I have no idea how to read back the values.

So Question #1: What is the correct way to read back the received values when there are more than will fit into the FIFO.

My trials show that I seem to be able to use this configuration to send my 65 values and the slave gets these OK. However I will certainly overflow the 4-value FIFO. In the situation where I’m trying to read the values back from the I will need to somehow get the values form the FIFO *while* the other values are being sent.

Therefore Question #2: how can I ensure that the \SS\ line is kept low while sending multiple values to the slave AND reading back the received values?

To try to get around the above limitation, I tried using a 65 value software buffer for both Tx and Rx. In case #1 (i.e. 2 values exchanged)  I seem to need to look at both the “xxx_GetRxBufferSize” function value (which always seems to be 0) and the “xxx_ReadRxStatus” (and the xxx_STS_RX_FIFO_NOT_EMPTY bit) but that tells me a value is available – but fails to say there is a 2nd value received. It is as though the software buffer is not used for receiving as both values fit into the FIFO but I only seem to be able to read the first!

So, Question #3: how should you read fewer values (i.e. that will fit into the FIFO and not need the software buffer) when the software buffer is enabled?

Which also leads to Question #4: Is the only option to not use the \SS\ output of the device and manually handle this output pin in my code? Alternatively are there APIs that let me manipulate the \SS\ manually?




user_1377889's picture
9281 posts

A 3MHz transfer speed and a 60MHz CPU does not leave very much room for executing code. So you will have to use a fairly large buffer handled by the (already optimized) generated sources and use the appropiate APIs. When using buffers (128 to 256 is a good size for you to be on the right size) for both Rx and Tx you will have to forget about the FIFOs and just inspect as you already suspected the data using "xxx_GetRxBufferSize" (which is actually not a buffer size but a byte count)  and "xxx_ReadRxData".

You have a means to control the successful transmission by comparing _GetRxBufferSize() to the number of bytes you transmitted (which you normally know), so when you start to loose bytes something must be fishy.

The ss-line works well as long as a single transfer does not empty the FIFO, so when transmitting the register number and then the register value there might be a short ss-line glitch from which some devices tend to get hickups. So transfer both with a single call to _PutArray() and check for a complete transfer before continuing. Even the requred dummy bytes for reading results can / should be sent with the same _PutArray() when the device needs a single ss-low phase.

Do not touch (read) any SPI-status registers in the buffered mode! Some bits are "sticky" (reset when red) and this could infer with the internal interrupt handler.


Greetings to Down Under! (My daughter lives there)


Aussie Susan's picture
42 posts

 Hello Bob

(I hope your daughter lives up north somewhere in Australia as it is getting a bit cold down here in Melbourne at the moment!)

Thank you for the information. It would seem that the buffered option is the way to go. (Actually I found that part of the problem I had with that approach was that I had NOT turned on the global interrupts - but even doing that still leads to some problems.)

And yes, I am using the xxx_PutArray function for all of the transfers and the slave chip seems to be responding correctly so the \SS\ line appears to be controlled as it should.

However, I'm a bit puzzled by two of your comments: the first is that I should wait until the transfer is complete before continuing and the second is not to read the status registers in buffered mode beause reading the sticky bits will interfere with the buffering ISR (at least that is the way I read what you said).

Therefore, how can I tell that the values sent via the xxx_PutArray function have actually been sent if I don't use the xxx_ReadTxStatus and look for (say) the xxx_STS_SPI_IDLE? Is it reliable to look at the xxx_GetRxBufferSize and wait until that gets to the byte count or is there a "better" way?


HiZ's picture
27 posts



As Bob pointed out it is not recommended to directly read the sticky register. You can read the number of bytes left to understand that transmission is done or not.But having said that, I have also tested using the condition while (0u == (SPIM_ReadTxStatus() & SPIM_STS_SPI_DONE)) in case of SPI-DMA also and it had worked fine. Seems it will depend on the project.


user_1377889's picture
9281 posts

Sorry, Susan

I really overlooked your reply and HiZ triggered me to look at this thread again.

The resetting of the status flags will interfere with the interrupt driven circular buffer provided by the component.

So the Idea of using GetRxBufferSize() will help. You may always use constructs like

while(GetRxBufferSize()) ProcessData(); // Empty the recieve buffer

Moreover you may use a ClearRxBuffer() before you are expecting relevant data to empty the buffer and start anew.



Here: Trees are in blossom, we have got spring!

Aussie Susan's picture
42 posts

Thanks for the assistance guys - it is much appreciated.

However I have moved things along a bit and I've moved to a DMA approach which gives me a bit more flexibility. (I won't go into why now but this seems to be the most flexible approach that does what I need.) But I'm having problems getting an ISR triggered when the Rx DMA completes.

I am sure that the interrupt is correctly set up as I can use an API call to trigger it and the ISR is called. I am sure that the Tx DMA is correctly set up as I can send a variable number of bytes to the SPI and I can see the values being sent to the values being sent to the MOSI line.

The Rx DMA is set up almost exactly the same but, despite the fact  can see values being passed back on the MISO line (and the SPI worked previously without the DMA) there is never any data put into the Rx buffer.

The code is:


uint8 rxChannel;

uint8 rxTD;

uint8 rxBuffer[65];

uint8 txChannel;

uint8 txTD;

uint8 txBuffer[65];

uint8 dmaFlag;




dmaFlag = 1;



static void MAX3421_SetupTransfer( uint8 count, uint8* src, uint8* dst)


cystatus result;

uint8 state;

uint16 xferCount;

uint8 nextTD;

uint8 config;


result = CyDmaChSetExtendedAddress( txChannel, HI16( (uint32)src), HI16( (uint32)MAX3421SPI_TXDATA_PTR));

result = CyDmaTdSetAddress( txTD, LO16((uint32)src), LO16((uint32)MAX3421SPI_TXDATA_PTR));

result = CyDmaTdSetConfiguration( txTD, count, DMA_DISABLE_TD, TD_INC_SRC_ADR);

result = CyDmaChSetExtendedAddress( rxChannel, HI16((uint32)MAX3421SPI_RXDATA_PTR), HI16((uint32)rxBuffer));

result = CyDmaTdSetAddress( rxTD, LO16((uint32)MAX3421SPI_RXDATA_PTR), LO16((uint32)rxBuffer));

result = CyDmaTdSetConfiguration( rxTD, count, DMA_DISABLE_TD, TD_INC_DST_ADR);

dmaFlag = 0;

result = CyDmaChEnable( rxChannel, 1u);

result = CyDmaChEnable( txChannel, 1u);

// result = CyDmaChSetRequest( txChannel, CPU_REQ);



if( dmaFlag)

{ break; }

result = CyDmacError();

result = CyDmaTdGetConfiguration( rxTD, &xferCount, &nextTD, &config);

result = CyDmaTdGetConfiguration( txTD, &xferCount, &nextTD, &config);

result = CyDmaChGetRequest( txChannel);

result = CyDmaChGetRequest( rxChannel);




(All of the junk at the end of the function is me verifying that the DMA transfers have completed and there are no errors reported.)

Previously I have allocated the DMA channels and added the TDs etc.

I can't see my mistake in the Rx DMA setup but I can't see why I don't get values into the RxBuffer when the Tx side works perfectly.


user_1377889's picture
9281 posts

There is a nasty hidden bug in your code-snippet that (presumably) had not occured yet.

As a general rule:

Every global variable that gets altered in an interrupt handler must be declared as "volatile"

The reason is: "volatile" is an information for the C-optimization step telling that the variable might be changed from "outsides" of the current context. An example:


The above line is obviously a loop waiting for MyFlag becoming FALSE (0). The optimizer "sees" that within the loop the variable "MyFlag" is not altered, so the code can be changed by extracting the variable out of the loop and testing it only once leaving an infinite loop as the TRUE (!=0) case for "MyFlag".

The default optimization settings for the compile step in Creator are very weak, but this may change unannounced. At least when one of your project runs against flash size limits or is too time-consuming a compilation with "Release" settings will start the desaster: Project does not run, but cannot be debugged to find the cause.

Some more explanations here


So in your case, use

volatile uint8 dmaFlag;


Happy coding


Aussie Susan's picture
42 posts

 Is there a 'smiley' for a 'facepalm'?! That is something I often pull others up for in other forums and thanks for pointing it out.

I have tried a couple of other things: by not preserving the TD I can see that the byte count for both the rx and tx sides go to zero and the address pointers (to memory) increment as they should. 

Therefore it would appear that the DMA operations are occurring correctly, but I'm still not getting the ISR called and stil not getting values put into the destination memory.

Given my previous 'silly mistake' it is probably something very obvious but I must admit I can't see it right now.

(By the way, I should explain that the function I have shown will ultimately use the passed parameters. However when I was having the issue with receiving the data, I switched over to referencing the global buffers directly. That seemed to work for the Tx side and also when I switched back to using the parameter. However I've left the direct reference to the receive buffer in there while I get things sorted out.)


user_1377889's picture
9281 posts


I cannot see from your code-snippet what the cause of the not-receiving-from-DMA-error might be. Furthermore I miss the help of the IDE telling me the underlying definitions for variables etc just by hoovering with the mos over an identifier.

I even cannot see whether you enabled global interrupts. Can you please post your complete project, so that we all can have a look at all of your settings? To do so, use
Creator->File->Create Workspace Bundle (minimal)
and attach the resulting file.


user_14586677's picture
7646 posts

Did you issue the isr_StartEX for the ISR ?






/* Style Definitions */
{mso-style-name:"Table Normal";
mso-padding-alt:0in 5.4pt 0in 5.4pt;
mso-fareast-font-family:"Times New Roman";
mso-bidi-font-family:"Times New Roman";

CY_ISR_PROTO(MyIntFunc);      // Prototype declaration


CY_ISR(MyIntFunc)                         // Interrupt function definition


// Place code here



In  initialization part of the program


isr_StartEX(MyIntFunc);               // Start Interrupt with my handler


CY_ISR-macro have a look into the "System Reference Guide" under Help -> Documentation..


Regards, Dana.

Aussie Susan's picture
42 posts

Hi Dana - yes I have and, as I've said above (somewhere in all of the words) I have called the xxx_SetPending function and the ISR is called. My understanding is that this is a software way of doing the same thing as the hardware would do to set the pending bit in the appropriate register and so would show that the interrupt was correctly set up. Please correct me if I'm wrong.

Hi Bob - I actually have a copy of the project with me but I really need to do what I ask others to do i other forums and that is to create a minimal project that exhibits the problem. Right now there is a lot of other code that is noise with respect to this situation and is possibly going to make fault finding a bit harder in someone else’s code. When I get home tonight, I'll create the minimal project and post that.

I have turned on the global interrupts (at least I have now - as mentioned before I had missed out that step at the start) but I agree that I have probably not shown everything that I need to.

(Edit: Some hours later - just seen another post that mentions the DMA__TD_TERMOUT_EN option bit for the TD. I have NOT set this bit and that could be the reason why the interrupt was not triggered. I'll try this option tonight but I missed the requirement for this option bit entirely. The way the description for the 'nrq' line reads implies that the nrq is pulsed regardless. However the need is mentioned in the CyDmaTdSetConfiguration but I missed it before now.)


Log in to post new comments.