This thread has been locked.
If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.
Hi all,
I've painstakingly traced a stall in the CC3000 driver code to a call to nvmem_read() which happens during wlan_smart_config_process(). This call attempts to read the AES key from NVMEM on the CC3000, and is split into two parts - SimpleLinkWaitEvent() immediately followed by SimpleLinkWaitData(). I've managed to figure out that in between these calls, there is a delay between SpiPauseSpi() and SpiResumeSpi() that is causing an interrupt to be missed, meaning that the driver waits forever for data (the second step of nvmem_read()).
Here is a logic trace showing the problem. Note that line 6 shows the state of interrupt processing - enabled is HIGH, paused/disabled is LOW.
As you can see, IRQ goes low while interrupt handling is disabled. It resumes shortly, but by that point it is too late.
My question is - am I implementing resume and pause correctly? I'm simply disabling and enabling interrupts on the IRQ pin. If this is correct, it seems like I've hit some kind of race condition or even a bug in the library.
Thanks!
Vishal
I just realized that the odd thing about this is that IRQ goes low even though CS is disabled...what could that mean?
Just wanted to post an updated logic analyzer screenshot. The only change is in the IRQ Enabled? line, which should correctly display the interrupt state now. The window of time in which the interrupts are disabled is more obvious, but the real question is why the CC3000 interrupts at all when it isn't enabled.
The problem is the way I was pausing and resuming interrupts. Perhaps because I'm using the Arduino environment and not working as close to the metal as I would be with CCS or IAR, there is enough of a delay between pausing and resuming that I miss the IRQ low signal. What I did to avoid this is to swap out the regular interrupt handler with a dummy interrupt handler that simply sets a flag when an interrupt happens. Then, when interrupt handling is resumed, I swap them back and if the IRQ flag was set, pretend as if an interrupt just took place. This took care of things long enough for Smart Config to go through.
I still think it's a bug in the library if a race condition like this manifests due to a slight difference in timing, but I'm no expert.
I had the same problem with the LPC1837 processor. And this means unnecessary debug time. This should be publicly known in some kind of errata sheet. I solved it the same as you.
Hi Vishal,
VIshal Talwar said:there is enough of a delay between pausing and resuming that I miss the IRQ low signal.
What do you mean by a delay between pausing and resuming?
In this period (from pausing to resuming) there should not be any interrupts.
I think the issue is that you didn't really disabled the interrupt, otherwise how could it be (according to your scope capture) that interrupt arrived after pausing the SPI? (while IRQ Enabled? was low)
Yael
Hi Yael,
Sorry for the long delay replying, but I was on vacation for a while and am getting warmed up again. What I mean is basically what you yourself described in this thread:
http://e2e.ti.com/support/low_power_rf/f/851/p/260521/923417.aspx
"If the IRQ is low but you didn't receive any ISR, you can ask for the IRQ state (maybe you missed the ISR between the CS assertion and the interupt enable)."
I think what happened is interrupts were disabled when IRQ went low, and then when they were re-enabled I had already missed the IRQ pulse. Does this make any sense? It seems to be a case you guard against in some parts of the code.
Thanks,
Vishal
Also, I see what you are saying that an IRQ should not arrive between SPI pause and resume...but how would the CC3000 know this? Also, it looks like someone else said they had the same issue and solved it the same way just before your reply.
HI,
CC3000 should not be aware if you are masking interrupts or not but until the host wont lower the CS and raise it again, CC3000 should not raise the interrupt.
The race described could happen, but could be avoid with rechecking the interrupt status.
Yael
Hi Yael,
That's what I would have thought as well (about the CC3000 not raising IRQ when CS0 is HIGH) but I received information stating otherwise from the LSR forum:
http://www.lsr.com/ProductsForum/tabid/164/forumid/8/postid/2980/scope/posts/Default.aspx#2980
-Vishal
Hi Vishal,
I don't see any conflicting information from LSR.
LSR answer mentioned that once the IRQ is low, host should lower the CS (ENABLE).
Host can’t directly control the IRQ line but can do that indirectly by controlling the CS line.
If your host is masking the interrupts and an interrupt arrived, it will lower the CS only when the masking will be stop.
Why does your ENABLE line doesn't go low once the IRQ go low? If the answer is that you missed the interrupt, the solution could be checking this line again (IRQ line will stay low until you will response with the ENABLE line...).
If the line is still low, low the ENABLE line and read the data.
I think I understand you confusion from the information from LSR - this should relates to read or write operation:
If host wants to write information, it will low the CS and wait for IRQ to be low.
If CC3000 want to write, it will low IRQ and wait for CS to be low.
Yael
Thanks for the detailed response, Yael. I think you are confirming a few things:
1. That IRQ can fire at any time when the CC3000 wants to respond
2. That because of 1), I could miss the interrupt if my timing is off
3. That a missed interrupt isn't guarded against by the current version of the driver
Where you placed the red circle is exactly where I miss the interrupt, as IRQ Enabled (line 6) isn't high at the time. So you are suggesting a way around missing the interrupt by checking again, but I think my solution of never really disabling interrupts but simply flagging that an interrupt happened and acting on it when IRQ is "enabled" (echoed by Bostjan Ambrozic earlier in the thread) works fine as well.
For the record, the ported and patched version of the driver seems to be working without issue on the Teensy MCU so far.
Thanks,
Vishal
Please if anyone can tell me where can I put the changes in spi.c.
Thank you very much
/**************************************************************************** * File : spi.c * Date : 12/11/2012 (Menu "banner" reports actual build date) * Purpose : Wi-Go SPI interface driver to CC3000 Wi-Fi module * Author : Peter Fenn, Avnet Global Technical Marketing * Description: SPI interface driver between Host MCU (KL25Z) and CC3000 ***************************************************************************** ***************************************************************************** * * spi.c - CC3000 Host Driver Implementation. * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the * distribution. * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * *****************************************************************************/ //***************************************************************************** // //! \addtogroup link_buff_api //! @{ // //***************************************************************************** #include "hci.h" #include "spi.h" #include "evnt_handler.h" #include "Cpu.h" #include "Variabili.h" #define READ 3 #define WRITE 1 #define HI(value) (((value) & 0xFF00) >> 8) #define LO(value) ((value) & 0x00FF) #define HEADERS_SIZE_EVNT (SPI_HEADER_SIZE + 5) #define SPI_HEADER_SIZE (5) #define eSPI_STATE_POWERUP (0) #define eSPI_STATE_INITIALIZED (1) #define eSPI_STATE_IDLE (2) #define eSPI_STATE_WRITE_IRQ (3) #define eSPI_STATE_WRITE_FIRST_PORTION (4) #define eSPI_STATE_WRITE_EOT (5) #define eSPI_STATE_READ_IRQ (6) #define eSPI_STATE_READ_FIRST_PORTION (7) #define eSPI_STATE_READ_EOT (8) typedef struct { gcSpiHandleRx SPIRxHandler; unsigned short usTxPacketLength; unsigned short usRxPacketLength; unsigned long ulSpiState; unsigned char *pTxPacket; unsigned char *pRxPacket; }tSpiInformation; tSpiInformation sSpiInformation; // // Static buffer for 5 bytes of SPI HEADER // unsigned char tSpiReadHeader[] = {READ, 0, 0, 0, 0}; void SpiWriteDataSynchronous(unsigned char *data, unsigned short size); void SpiWriteAsync(const unsigned char *data, unsigned short size); void SpiPauseSpi(void); void SpiResumeSpi(void); void SSIContReadOperation(void); void SpiReadDataSynchronous(unsigned char *data, unsigned short size); void PORTA_IRQHandler(void); // The magic number that resides at the end of the TX/RX buffer (1 byte after the allocated size) // for the purpose of detection of the overrun. The location of the memory where the magic number // resides shall never be written. In case it is written - the overrun occured and either recevie function // or send function will stuck forever. #define CC3000_BUFFER_MAGIC_NUMBER (0xDE) /////////////////////////////////////////////////////////////////////////////////////////////////////////// //#pragma is used for determine the memory location for a specific variable. /// /// //__no_init is used to prevent the buffer initialization in order to prevent hardware WDT expiration /// // before entering to 'main()'. /// //for every IDE, different syntax exists : 1. __CCS__ for CCS v5 /// // 2. __IAR_SYSTEMS_ICC__ for IAR Embedded Workbench /// // *CCS does not initialize variables - therefore, __no_init is not needed. /// /////////////////////////////////////////////////////////////////////////////////////////////////////////// char spi_buffer[CC3000_RX_BUFFER_SIZE]; unsigned char wlan_tx_buffer[CC3000_TX_BUFFER_SIZE]; unsigned char Temp[30]; unsigned char Temp1[30]; LDD_TDeviceData *MySPIPtr; LDD_TDeviceData *MyE_CSPtr; LDD_TDeviceData *MyEIrqCC3000Ptr; LDD_TError Error; //***************************************************************************** // //! SpiClose //! //! \param none //! //! \return none //! //! \brief Cofigure the SSI // //***************************************************************************** void SpiClose(void) { if (sSpiInformation.pRxPacket) { sSpiInformation.pRxPacket = 0; } // // Disable Interrupt in GPIOA module... // tSLInformation.WlanInterruptDisable(); } //***************************************************************************** // //! SpiClose //! //! \param none //! //! \return none //! //! \brief Cofigure the SSI // //***************************************************************************** void SpiOpen(gcSpiHandleRx pfRxHandler) { sSpiInformation.ulSpiState = eSPI_STATE_POWERUP; sSpiInformation.SPIRxHandler = pfRxHandler; sSpiInformation.usTxPacketLength = 0; sSpiInformation.pTxPacket = NULL; sSpiInformation.pRxPacket = (unsigned char*)spi_buffer; sSpiInformation.usRxPacketLength = 0; spi_buffer[CC3000_RX_BUFFER_SIZE - 1] = CC3000_BUFFER_MAGIC_NUMBER; wlan_tx_buffer[CC3000_TX_BUFFER_SIZE - 1] = CC3000_BUFFER_MAGIC_NUMBER; // // Enable interrupt on the GPIOA pin of WLAN IRQ // tSLInformation.WlanInterruptEnable(); } //***************************************************************************** // //! This function: init_spi //! //! \param buffer //! //! \return none //! //! \brief initializes an SPI interface // //***************************************************************************** int init_spi(void) { MyEIrqCC3000Ptr = EIrqCC3000_Init(NULL); MyE_CSPtr = E_CS_Init(NULL); MySPIPtr = SPI_Init(NULL); return(ESUCCESS); } //***************************************************************************** // //! This function enter point for write flow //! //! \param buffer //! //! \return none //! //! \brief ... // //***************************************************************************** long SpiFirstWrite(unsigned char *ucBuf, unsigned short usLength) { // // workaround for first transaction // E_CS_ClrVal(MyE_CSPtr); // Assuming we are running on 24 MHz ~50 micro delay is 1200 cycles; WAIT1_Waitus(100); // SPI writes first 4 bytes of data SpiWriteDataSynchronous(ucBuf, 4); WAIT1_Waitus(100); SpiWriteDataSynchronous(ucBuf + 4, usLength - 4); // From this point on - operate in a regular way sSpiInformation.ulSpiState = eSPI_STATE_IDLE; E_CS_SetVal(MyE_CSPtr); return(0); } //***************************************************************************** // //! This function enter point for write flow //! //! \param buffer //! //! \return none //! //! \brief ... // //***************************************************************************** long SpiWrite(unsigned char *pUserBuffer, unsigned short usLength) { unsigned char ucPad = 0; // // Figure out the total length of the packet in order to figure out if there is padding or not // if(!(usLength & 0x0001)) { ucPad++; } pUserBuffer[0] = WRITE; pUserBuffer[1] = HI(usLength + ucPad); pUserBuffer[2] = LO(usLength + ucPad); pUserBuffer[3] = 0; pUserBuffer[4] = 0; usLength += (SPI_HEADER_SIZE + ucPad); // The magic number that resides at the end of the TX/RX buffer (1 byte after the allocated size) // for the purpose of detection of the overrun. If the magic number is overriten - buffer overrun // occurred - and we will stuck here forever! if (wlan_tx_buffer[CC3000_TX_BUFFER_SIZE - 1] != CC3000_BUFFER_MAGIC_NUMBER) { while (1); } if (sSpiInformation.ulSpiState == eSPI_STATE_POWERUP) { while (sSpiInformation.ulSpiState != eSPI_STATE_INITIALIZED); } if (sSpiInformation.ulSpiState == eSPI_STATE_INITIALIZED) { // // This is time for first TX/RX transactions over SPI: the IRQ is down - so need to send read buffer size command // SpiFirstWrite(pUserBuffer, usLength); } else { // We need to prevent here race that can occur in case 2 back to back packets are sent to the // device, so the state will move to IDLE and once again to not IDLE due to IRQ tSLInformation.WlanInterruptDisable(); while (sSpiInformation.ulSpiState != eSPI_STATE_IDLE); sSpiInformation.ulSpiState = eSPI_STATE_WRITE_IRQ; sSpiInformation.pTxPacket = pUserBuffer; sSpiInformation.usTxPacketLength = usLength; // Assert the CS line and wait till SSI IRQ line is active and then initialize write operation E_CS_ClrVal(MyE_CSPtr); // Re-enable IRQ - if it was not disabled - this is not a problem... tSLInformation.WlanInterruptEnable(); if ((tSLInformation.ReadWlanInterruptPin() == 0) && (sSpiInformation.ulSpiState == eSPI_STATE_WRITE_IRQ)) { SpiWriteDataSynchronous(sSpiInformation.pTxPacket, sSpiInformation.usTxPacketLength); sSpiInformation.ulSpiState = eSPI_STATE_IDLE; E_CS_SetVal(MyE_CSPtr); } } // Due to the fact that we are currently implementing a blocking situation // here we will wait till end of transaction while (eSPI_STATE_IDLE != sSpiInformation.ulSpiState); return(0); } //***************************************************************************** // //! This function enter point for write flow //! //! \param buffer //! //! \return none //! //! \brief ... // //***************************************************************************** void SpiWriteDataSynchronous(uint8_t *data, unsigned short size) { while (size) { Error = SPI_SendBlock(MySPIPtr, data, 1); while (!(TXBufferIsEmpty())) {SPI_Main(MySPIPtr);}; Error = SPI_ReceiveBlock(MySPIPtr,&Temp[size], 1); while (!(RXBufferIsFull())){SPI_Main(MySPIPtr);}; size--; data++; } } //***************************************************************************** // //! This function enter point for write flow //! //! \param buffer //! //! \return none //! //! \brief ... // //***************************************************************************** void SpiReadDataSynchronous(unsigned char *data, unsigned short size) { long i = 0; unsigned char *data_to_send = tSpiReadHeader; for (i = 0; i < size; i ++) { Error = SPI_SendBlock(MySPIPtr,&data_to_send[i],1); while (!(TXBufferIsEmpty())) {SPI_Main(MySPIPtr);}; Error = SPI_ReceiveBlock(MySPIPtr, &data[i], 1); while (!(RXBufferIsFull())){SPI_Main(MySPIPtr);}; } } //***************************************************************************** // //! This function enter point for read flow: first we read minimal 5 SPI header bytes and 5 Event //! Data bytes //! //! \param buffer //! //! \return none //! //! \brief ... // //***************************************************************************** void SpiReadHeader(void) { SpiReadDataSynchronous(sSpiInformation.pRxPacket, 10); } //***************************************************************************** // //! This function processes received SPI Header and in accordance with it - continues reading //! the packet //! //! \param None //! //! \return None //! //! \brief ... // //***************************************************************************** long SpiReadDataCont(void) { long data_to_recv; unsigned char *evnt_buff, type; // //determine what type of packet we have // evnt_buff = sSpiInformation.pRxPacket; data_to_recv = 0; STREAM_TO_UINT8((char *)(evnt_buff + SPI_HEADER_SIZE), HCI_PACKET_TYPE_OFFSET, type); switch(type) { case HCI_TYPE_DATA: { // // We need to read the rest of data.. // STREAM_TO_UINT16((char *)(evnt_buff + SPI_HEADER_SIZE), HCI_DATA_LENGTH_OFFSET, data_to_recv); if (!((HEADERS_SIZE_EVNT + data_to_recv) & 1)) { data_to_recv++; } if (data_to_recv) { SpiReadDataSynchronous(evnt_buff + 10, data_to_recv); } break; } case HCI_TYPE_EVNT: { // // Calculate the rest length of the data // STREAM_TO_UINT8((char *)(evnt_buff + SPI_HEADER_SIZE), HCI_EVENT_LENGTH_OFFSET, data_to_recv); data_to_recv -= 1; // // Add padding byte if needed // if ((HEADERS_SIZE_EVNT + data_to_recv) & 1) { data_to_recv++; } if (data_to_recv) { SpiReadDataSynchronous(evnt_buff + 10, data_to_recv); } sSpiInformation.ulSpiState = eSPI_STATE_READ_EOT; break; } } return (0); } //***************************************************************************** // //! This function enter point for write flow //! //! \param SpiPauseSpi //! //! \return none //! //! \brief The function triggers a user provided callback for // //***************************************************************************** void SpiPauseSpi(void) { tSLInformation.WlanInterruptDisable(); } //***************************************************************************** // //! This function enter point for write flow //! //! \param SpiResumeSpi //! //! \return none //! //! \brief The function triggers a user provided callback for // //***************************************************************************** void SpiResumeSpi(void) { tSLInformation.WlanInterruptEnable(); } //***************************************************************************** // //! This function enter point for write flow //! //! \param SpiTriggerRxProcessing //! //! \return none //! //! \brief The function triggers a user provided callback for // //***************************************************************************** void SpiTriggerRxProcessing(void) { // // Trigger Rx processing // SpiPauseSpi(); E_CS_SetVal(MyE_CSPtr); // The magic number that resides at the end of the TX/RX buffer (1 byte after the allocated size) // for the purpose of detection of the overrun. If the magic number is overriten - buffer overrun // occurred - and we will stuck here forever! if (sSpiInformation.pRxPacket[CC3000_RX_BUFFER_SIZE - 1] != CC3000_BUFFER_MAGIC_NUMBER) { while (1); } sSpiInformation.ulSpiState = eSPI_STATE_IDLE; sSpiInformation.SPIRxHandler(sSpiInformation.pRxPacket + SPI_HEADER_SIZE); } //***************************************************************************** // //! The IntSpiGPIOHandler interrupt handler //! //! \param none //! //! \return none //! //! \brief GPIO A interrupt handler. When the external SSI WLAN device is //! ready to interact with Host CPU it generates an interrupt signal. //! After that Host CPU has registrated this interrupt request //! it set the corresponding /CS in active state. // //***************************************************************************** //PF void IntSpiGPIOHandler(void) void PORTA_IRQHandler(void) { if (sSpiInformation.ulSpiState == eSPI_STATE_POWERUP) { /* This means IRQ line was low call a callback of HCI Layer to inform on event */ sSpiInformation.ulSpiState = eSPI_STATE_INITIALIZED; } else if (sSpiInformation.ulSpiState == eSPI_STATE_IDLE) { sSpiInformation.ulSpiState = eSPI_STATE_READ_IRQ; /* IRQ line goes down - we are start reception */ E_CS_ClrVal(MyE_CSPtr); // // Wait for TX/RX Compete which will come as DMA interrupt // SpiReadHeader(); sSpiInformation.ulSpiState = eSPI_STATE_READ_EOT; // // // SSIContReadOperation(); } else if (sSpiInformation.ulSpiState == eSPI_STATE_WRITE_IRQ) { SpiWriteDataSynchronous(sSpiInformation.pTxPacket, sSpiInformation.usTxPacketLength); sSpiInformation.ulSpiState = eSPI_STATE_IDLE; E_CS_SetVal(MyE_CSPtr); } } //***************************************************************************** // //! This function enter point for write flow //! //! \param SSIContReadOperation //! //! \return none //! //! \brief The function triggers a user provided callback for // //***************************************************************************** void SSIContReadOperation(void) { // // The header was read - continue with the payload read // if (!SpiReadDataCont()) { // // All the data was read - finalize handling by switching to teh task // and calling from task Event Handler // SpiTriggerRxProcessing(); } } //***************************************************************************** // //! Indication if TX SPI buffer is empty //! //! \param //! //! \return true or false //! //! \brief The function returns 1 if buffer is empty, 0 otherwise // //***************************************************************************** long TXBufferIsEmpty(void) { return(SPI_GetBlockSentStatus(MySPIPtr)); } //***************************************************************************** // //! Indication if RX SPI buffer is full //! //! \param //! //! \return true or false //! //! \brief The function returns 1 if buffer is full, 0 otherwise // //***************************************************************************** long RXBufferIsFull(void) { return(SPI_GetBlockReceivedStatus(MySPIPtr)); } //***************************************************************************** // // Close the Doxygen group. //! @} // //*****************************************************************************
Hi Vishal,
I seem to be facing the similar problem you are facing. When I send back to back packets to CC3000 I'm getting stuck in the infinite while loop.
1. Did your Interrupt managing workaround solve the problem completely? Are you able to send/receive packets correctly?
2. Did you make the SPI changes in latest SP and host driver versions as well?
Appreciate any help..
Vivek
Hi Vivek,
Yes, the interrupt-deferring solution continues to work for me with the latest drivers. Is it not working for you on the latest version? I noticed they added a bit of code to purportedly deal with a missed interrupt, as I mentioned in this thread:
http://e2e.ti.com/support/low_power_rf/f/851/p/293532/1024023.aspx#1024023
However, I simply do not understand the order of operations in that section of code. In any case I have not modified SpiWrite in the newer version of the driver, only the interrupt handling bit.
Vishal
Vishal Talwar said:pretend as if an interrupt just took place
Hi Vishal,
I'm still working on CC3000 module with no hope in sight. When you say you "Pretend as if an interrupt took place", what does that mean?
If you see that the IRQ flag is set once the Interrupts are resumed, do you force the micro to take IRQ interrupt?
Help is appreciated.
Thanks,
Vivek
Hi Vivek,
Sorry to hear that. I meant that I manually call the interrupt handler when interrupts are resumed if an interrupt was detected while disabled. I found a different approach used in the Adafruit library:
https://github.com/adafruit/Adafruit_CC3000_Library/blob/master/ccspi.cpp#L742
Clearly, missed interrupts are a major source of problems with this library.
-Vishal
Hi everyone
I don't really know exactly what happen, as I am actually a newcomer.
I had been struggling with the module: cannot recv nor recvfrom at all, either with TCP nor UDP.
I had been monitoring my IRQ with an LED (Because apparently it's okay to do so)
Having the problem, I modified the SPI.cpp (don't know the exact name)
as such:
void SpiResumeSpi(void) {
SPIInterruptsEnabled = 1;
if(Read_CC3000_IRQ_Pin()==0)
{
CC3000InterruptHandler();
}
}
It sort of works. At least I can now do a read from TCP.
I haven't tested much of it.
And I'm sorry if this is a thread resurrection, remembering the date of last post. I'm just excited.
I hope someone can confirm this as well.
Thanks.
I've also had the problem of not recv not working. The only fix I've found so far is to decrease the spi clock rate. With a clock greater than about 1 MHz the problem occurs.