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.

TM4C123GH6PM: SPI slave on TM4C123GXL Launchpad

Part Number: TM4C123GH6PM

Hello,

I am a student working on a senior design project utilizing the Tiva tm4c123gh6pm arm processor and am struggling to get the SPI slave interface to work correctly.

So far I have used an example from this form post, https://e2e.ti.com/support/microcontrollers/tiva_arm/f/908/t/302734?SPI-Slave-problem

and modified it to use SSI0 instead of SSI2. When testing the code I cannot read more than one byte from the master, a raspberry pi, before the Tiva hangs in an infinite loop in the function SSIDataGet() shown below waiting for data. The master has the clock set to 122kHz.

void
SSIDataGet(uint32_t ui32Base, uint32_t *pui32Data)
{
    //
    // Check the arguments.
    //
    ASSERT(_SSIBaseValid(ui32Base));

    //
    // Wait until there is data to be read.
    //
    while(!(HWREG(ui32Base + SSI_O_SR) & SSI_SR_RNE)) // hangs here forever
    {
    }

    //
    // Read data from SSI.
    //
    *pui32Data = HWREG(ui32Base + SSI_O_DR);
}

The oscilloscope output shows the data as being sent correctly but it seems it is not being processed by the Tiva and I am unsure as how to fix this.

Channel 1 is clk, 2 is MOSI, 3 is MISO, 4 is CS.

Any advice would be greatly appreciated, here is my full code below.

//*****************************************************************************
//
// spi_slave.c - Example demonstrating how to configure RX timeout interrupt in
// SPI slave mode.
//
// Copyright (c) 2013 Texas Instruments Incorporated.  All rights reserved.
// TI Information - Selective Disclosure
//
//*****************************************************************************
#include <stdbool.h>
#include <stdint.h>
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_ssi.h"
#include "inc/hw_types.h"
#include "driverlib/ssi.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "driverlib/sysctl.h"
#include "driverlib/pin_map.h"
#include "driverlib/uart.h"
#include "utils/uartstdio.h"

//*****************************************************************************
//
//! \addtogroup ssi_examples_list
//! <h1>SPI Slave (spi_slave)</h1>
//!
//! This example configures the SSI0 as SPI Master, SSI2 as SPI Slave on an
//! EK-LM4F232 evaluation board.  RX timeout interrupt is configured for SSI2.
//! Three characters are sent on the master TX, then SSI2 RX timeout interrupt
//! is enabled. The code then waits for the interrupt to fire.  Once the
//! interrupt is fired the data from slave RX FIFO is read and compared to the
//! transmitted packet and the appropriate status is displayed.  If everything
//! goes well you should see a "Test Passed." message on the terminal window.
//! The status messages are transmitted over UART0 at 115200 baud and 8-n-1
//! mode.
//!
//! This example uses the following peripherals and I/O signals on EK-LM4F232.
//! You must review these and change as needed for your own board:
//! - SSI0 peripheral
//! - GPIO Port A peripheral (for SSI0 pins) (available near the SD card slot)
//! - SSI0CLK - PA2
//! - SSI0Fss - PA3
//! - SSI0Rx  - PA4
//! - SSI0Tx  - PA5
//!
//! - SSI2 peripheral
//! - GPIO Port M peripheral (for SSI2 pins) (available right below the OLED)
//! - SSI2CLK - PH4
//! - SSI2Fss - PH5
//! - SSI2Rx  - PH6
//! - SSI2Tx  - PH7
//!
//! For this example to work, the following connections are needed on the
//! EK-LM4F232 evaluation board.
//! - SSI0CLK(PA2) - SSI2CLK(PH4)
//! - SSI0Fss(PA3) - SSI0Fss(PH5)
//! - SSI0Rx(PA4)  - SSI2Tx(PH7)
//! - SSI0Tx(PA5)  - SSI2Rx(PH6)
//!
//! The following UART signals are configured only for displaying console
//! messages for this example.  These are not required for operation of SSI0.
//! - UART0 peripheral
//! - GPIO Port A peripheral (for UART0 pins)
//! - UART0RX - PA0
//! - UART0TX - PA1
//!
//! This example uses the following interrupt handlers.  To use this example
//! in your own application you must add these interrupt handlers to your
//! vector table.
//! - SSI2IntHandler.
//!
//
//*****************************************************************************

//*****************************************************************************
//
// Number of bytes to send and receive.
//
//*****************************************************************************
#define NUM_SSI_DATA 7

//*****************************************************************************
//
// Global variables used in interrupt handler and the main loop.
//
//*****************************************************************************
volatile unsigned long g_ulSSI0RXTO = 0;
unsigned long g_ulDataRx0[NUM_SSI_DATA] = {0};

//*****************************************************************************
//
// Interrupt handler for SSI0 peripheral in slave mode.  It reads the interrupt
// status and if the interrupt is fired by a RX time out interrupt it reads the
// SSI0 RX FIFO and increments a counter to tell the main loop that RX timeout
// interrupt was fired.
//
//*****************************************************************************
void
SSI0IntHandler(void)
{
    unsigned long ulStatus = 0, ulIndex = 0;

    //
    // Read interrupt status.
    //
    ulStatus = SSIIntStatus(SSI0_BASE, 0);

    //
    // Check the reason for the interrupt.
    //
    if(ulStatus & SSI_RXTO)
    {
        //
        // Interrupt is because of RX time out.  So increment counter to tell
        // main loop that RX timeout interrupt occurred.
        //
        g_ulSSI0RXTO++;

        //
        // Read NUM_SSI_DATA bytes of data from SSI2 RX FIFO.
        //
        for(int ulIndex = 0; ulIndex < NUM_SSI_DATA; ulIndex++){
            SSIDataGet(SSI0_BASE, &g_ulDataRx0[ulIndex]);
        }
    }

    //
    // Clear interrupts.
    //
    SSIIntClear(SSI0_BASE, ulStatus);
}

//*****************************************************************************
//
// This function sets up SPI0 to be used as Master in freescale mode.
//
//*****************************************************************************
void
InitSPI0(void)
{
    //
    // The SSI0 peripheral must be enabled for use.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);

    //
    // For this example SSI0 is used with PortA[5:2].  GPIO port A needs to be
    // enabled so these pins can be used.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);

    //
    // Configure the pin muxing for SSI0 functions on port A2, A3, A4, and A5.
    // This step is not necessary if your part does not support pin muxing.
    //
    GPIOPinConfigure(GPIO_PA2_SSI0CLK);
    GPIOPinConfigure(GPIO_PA3_SSI0FSS);
    GPIOPinConfigure(GPIO_PA4_SSI0RX);
    GPIOPinConfigure(GPIO_PA5_SSI0TX);

    //
    // Configure the GPIO settings for the SSI pins.  This function also gives
    // control of these pins to the SSI hardware.  Consult the data sheet to
    // see which functions are allocated per pin.
    // The pins are assigned as follows:
    //      PA5 - SSI0Tx
    //      PA4 - SSI0Rx
    //      PA3 - SSI0Fss
    //      PA2 - SSI0CLK
    //
    GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_5 | GPIO_PIN_4 | GPIO_PIN_3 |
                   GPIO_PIN_2);

    //
    // Configure and enable the SSI0 port for SPI slave mode.
    //
    SSIConfigSetExpClk(SSI0_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0,
                       SSI_MODE_SLAVE, 122000, 8);
    //
    // Enable the SSI0 module.
    //
    SSIEnable(SSI0_BASE);
}

int main(void)
{

    SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);
    InitSPI0();
    SSIDataPut(SSI0_BASE, 0xED);
    // rx interrupt
    SSIIntEnable(SSI0_BASE, SSI_RXTO);
    IntEnable(INT_SSI0);

    while(1); // debug halt here and check buffer

}

  • Greetings,

    For a 'first ever' posting here - we must say, 'Good Job!'

    However - (even) a good job may be improved - let's start w/your Scope-Cap.    (which otherwise - proves essential & most complete)

    Note that I've 'imported' the Channel Names/Labels  into the 'cap' - where they are 'far better noted!

    Follows - comments aimed to assist your 'Spi Slave Success:'

    • your code has (many) elements - of 'Little importance/bearing' - to your issue.   While (quick/easy) for you (to copy/paste) - you've increased the EFFORT (forced) upon your helpers - never good!
    • many of that past code's comments - have not been modified - to reflect your code changes.    Again - adds to your helper's distress.
    • why was the (strange) 122KHz SPI Master Clock setting chosen?     Is it (certain) that your TM4C can 'dial-in' (with sufficient accuracy) to that frequency?   (Might that account for the failed, 2nd Byte - as the clock errors accumulate with data progression?)
    • Is your SPI ISR  'SSI0IntHandler' properly included w/in your 'Start-Up' file?
    • Note that you 'Clear that ISR' - with the (very last) instruction - w/in the handler.    The MCU manual and/or the Driver User Guide - inform & advise that such 'ISR Clearing' proves 'Likely to FAIL'  - when positioned "so late" w/in the ISR.
    • You've elected FreeScale Mode 0- and the MCU Manual notes: 
        • SPO=0 and SPH=0 However, in the case of continuous back-to-back transmissions, the SSInFss signal must be pulsed High between each data word transfer because the slave select pin freezes the data in its serial peripheral register and does not allow it to be altered if the SPH bit is clear. Therefore, the master device must raise the SSInFss pin of the slave device between each data transfer (I assume this means BYTES) to enable the serial peripheral data write.    If true - your selection of 'FreeScale Mode 0'  - may be doomed...
    • That 'Driver User Guide' suggests use of a 'Test to confirm that a peripheral is (truly) ready.'
        • SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);
          //
          // Wait for the SSI0 module to be ready.
          //
          while(!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI0))    //  it is believed that your SSI peripheral 'succeeds' - yet your 'GPIO_A set-up' violates this (safe_ practice.)

    • Your SPI reception - 'lives & dies' - via an SPI Interrupt.    Yet the MCU manual notes: 'Receive FIFO service (when the receive FIFO is half full or more!)   Yet somehow - as you report - it appeared to trigger with the first received byte.
    • Had you noted that your ISR 'qualifier' (which enables your read of SPI Data) is 'locked' to  'SSI_RXTO.'      And that such 'SPI time-out' occurs - after 32 SPI clocks - have 'gone missing!'    Has that been checked - and properly measured and/or verified?    And - might the fact that such 'time-out' (had occurred) - prior to the arrival of the Master's initial SPI transmission?    (Meaning that you'd GET the first byte - but NO MORE!)    (until another time-out)

    This should prove a (reasonable) start - it is unfortunate that there are few (very few) vendor examples centered upon the (more complex) SPI Slave.

    It should be noted - you must confirm a solid (and always present) shared ground connection - between your 'pi' and LPad...

  • the ISR function was correctly added to the vector table. I changed the code in the beginning of the initSSI0 to the following

    void
    InitSPI0(void)
    {
        //
        // The SSI0 peripheral must be enabled for use.
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);
        while(!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI0))

    as well as moved the interrupt clear higher up in the ISR routine as suggested. Also you are correct that FreeScale Mode 0 might be an issue, changing it to FreeScale Mode 3 seems to do the trick and make it read the data correctly in both directions. I added some data to the TX buffer as a test and it seems that everything works correctly. Thank you for the help.

  • While 'thanks for the help' is appreciated - and thanked - should not a Green Stamp attach to that  earlier, 'Issue Identifying post' - as well as yours - alone?

    As ALL three of your SPI issues (were) earlier identified & presented:

    • waiting for peripheral to (truly) be ready
    • reposition the ISR 'Clear' - higher up in the sequence
    • choose a 'better matched' FreeScale Mode

    You reported 'struggling' w/the SPI management - yet one post here - followed by ONE Quick yet Detailed Response - and you are now, 'On the Air!'

    The award of 'This Resolved' seems (beyond) Fairly Earned...

    [edit]  Via such (gentle) prompting - the proper post (the ANSWERING - not the CONFIRMING ONE) has (now) received 'proper' recognition.

  • It would be 'useful' to (others) here - if you'd describe the occurrence & frequency of the SPI_RX Interrupt. MCU Manual notes 'half filled FIFO' (meaning 4 SPI receptions) - prior to the Interrupt being triggered! Would you be 'good enough' to confirm?    Merci...

    Also (not to be too greedy) - might you describe (account for) the 'inter-character' spacing (delay) which your scope-cap details?     (that's shown in  BLUE - (top trace/Master Clock) - did you 'program' that spacing?) 

    Note too - our 'purist on staff' (never moi) notes that you have not adopted the suggestion to 'Label your Scope Channels (beyond) 1, 2, 3, 4 - which proves SO pedestrian - and time/effort EATING!    (helpers forced to 'scroll up' - to reinforce their 'Channel Identification!')    Downstream - this is BOUND to 'bite you' - Channel LABELS prove most helpful - especially when 'under stress!'

  • I am still using the SSI_RXTO interrupt. Without modification, the number of interrupts is 1 since the function SSIDataGet blocks until data is received and does this 7 times. When modified to clear the buffer every interrupt shown below the value of g_ulSSI0RXTO is 7 which makes sense since 7 bytes where transmitted and received.

    while(SSIDataGetNonBlocking(SSI0_BASE, &g_ulDataRx0[ulIndex]))
        ulIndex++;

    It is my understanding that the SSI_RXTO interrupt triggers when there is a RX timeout as you say though I do not know where the "32 clocks' comes from. It looks like it triggers when there is a single missing clock that denotes  the separation between frames though I have not verified this in the documentation.

    Also I am not sure what you mean by "inner character spacing", if you mean the spacing between the pulses inside each byte frame i believe it is the bits sampled by the slave at each clock falling edge.

  • anthony speake said:
    there is a RX timeout as you say though I do not know where the "32 clocks' comes from

    • 32 Clocks: That's easy:   Section 15.3.3, Pg 956   your MCU Manual:

    "The receive FIFO has a time-out period that is 32 periods at the rate of SSInClk (whether or not SSInClk is currently active) and is started when the RX FIFO goes from EMPTY to not-EMPTY.  If the RX FIFO is emptied before 32 clocks have passed, the time-out period is reset."   

    • INTER-CHARACTER SPACING  (Not 'Inner' character!)     (if you are becoming an engineer - you need to 'know' the language... misquoting another - proves (never) good!)

    Did your 'pi' place such 'spacing'  - or was that programmable - and provided by you?   (Do note - that once again - the Channel is (properly) labeled!    BFD (I hear you) - yet I've seen guys LOSE HOURS - by 'committing Channel Numbers to 'memory' - and then (later) discovering they were WRONG!   And that IS a VERY BFD!)

    • SSIDataGet():  And indeed - just as you (very) well noted - (and I (completely) missed) the 'blocking enforced by SSIDataGet() will (indeed) - insure that a 'SPI time-out' occurs.   (and you'll 'get' your seven interrupts.)     For 'real-world usage' - that 'blocking' will (often) prove unacceptable - and you'll then have to deal w/'Less regular, SPI RX interrupts.

    Good job - thank you - are we (now) in 'general agreement?'   (I harped on the Channel Marking - as I've seen guys get into BIG Trouble - by "being lazy."   My attempt is to encourage (your) 'good habits.'

    BTW - did you note - that just as 'Patient Draping' is employed in a surgical setting - I 'removed all extraneous' from your 'Scope Cap' - so that 'ONLY the essence' remained - which greatly aids 'FOCUS!'

  • Apologies, yes, the raspberry pi's internal SPI hardware places the spacing, I only load the TX FIFO buffer with my data through an API function call "bcm2835_spi_transfernb()" documented here. https://www.airspayce.com/mikem/bcm2835/group__spi.html

    function provided below for reference.

    /* Writes (and reads) an number of bytes to SPI */
    void bcm2835_spi_transfernb(char* tbuf, char* rbuf, uint32_t len)
    {
        volatile uint32_t* paddr = bcm2835_spi0 + BCM2835_SPI0_CS/4;
        volatile uint32_t* fifo = bcm2835_spi0 + BCM2835_SPI0_FIFO/4;
        uint32_t TXCnt=0;
        uint32_t RXCnt=0;
    
        /* This is Polled transfer as per section 10.6.1
        // BUG ALERT: what happens if we get interupted in this section, and someone else
        // accesses a different peripheral? 
        */
    
        /* Clear TX and RX fifos */
        bcm2835_peri_set_bits(paddr, BCM2835_SPI0_CS_CLEAR, BCM2835_SPI0_CS_CLEAR);
    
        /* Set TA = 1 */
        bcm2835_peri_set_bits(paddr, BCM2835_SPI0_CS_TA, BCM2835_SPI0_CS_TA);
    
        /* Use the FIFO's to reduce the interbyte times */
        while((TXCnt < len)||(RXCnt < len))
        {
            /* TX fifo not full, so add some more bytes */
            while(((bcm2835_peri_read(paddr) & BCM2835_SPI0_CS_TXD))&&(TXCnt < len ))
            {
    	    bcm2835_peri_write_nb(fifo, bcm2835_correct_order(tbuf[TXCnt]));
    	    TXCnt++;
            }
            /* Rx fifo not empty, so get the next received bytes */
            while(((bcm2835_peri_read(paddr) & BCM2835_SPI0_CS_RXD))&&( RXCnt < len ))
            {
    	    rbuf[RXCnt] = bcm2835_correct_order(bcm2835_peri_read_nb(fifo));
    	    RXCnt++;
            }
        }
        /* Wait for DONE to be set */
        while (!(bcm2835_peri_read_nb(paddr) & BCM2835_SPI0_CS_DONE))
    	;
    
        /* Set TA = 0, and also set the barrier */
        bcm2835_peri_set_bits(paddr, 0, BCM2835_SPI0_CS_TA);
    }