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.

SPI Sniffer with the MSP430

Other Parts Discussed in Thread: MSP430FR5739

I am currently working on an SPI Sniffer for the MSP430FR5739.  Specifically, what I want to do is allow the MSP430 intercept SPI transactions coming from the SPI Master, and store those transactions into the FRAM. 

The way I would like to do this is by using a circular buffer and a SPI Rx Interrupt Routine.  Below is what I have implemented.

#define FRAM_START 0x1800

volatile unsigned char Temp_SPI_Read;
volatile unsigned char byte_count = 0;
volatile unsigned char SPI_Transmit = 0;
volatile unsigned char *FRAM_ptr;
volatile int First = TRUE;

/*************CIRCULAR BUFFER*************/
typedef struct { unsigned char value; } ElemType;

// Circular buffer object //
typedef struct
{
    int         size;   // maximum number of elements
    int         start;  // index of oldest element
    int         end;    // index at which to write new element
    ElemType   *elems;  // vector of elements
} CircularBuffer;

CircularBuffer cb;
ElemType elem = {0};
int testBufferSize = 0x100; // arbitrary size //

void cbInit(CircularBuffer *cb, int size)
{
    cb->size  = size + 1; // include empty elem //
    cb->start = 0;
    cb->end   = 0;
    cb->elems = (ElemType *)calloc(cb->size, sizeof(ElemType));
}

void cbFree(CircularBuffer *cb)
{
    free(cb->elems); // OK if null //
}

int cbIsFull(CircularBuffer *cb) {
    return (cb->end + 1) % cb->size == cb->start; }

int cbIsEmpty(CircularBuffer *cb) {
    return cb->end == cb->start; }

// Write an element, overwriting oldest element if buffer is full. App can
// choose to avoid the overwrite by checking cbIsFull(). //
void cbWrite(CircularBuffer *cb, ElemType *elem) {
    cb->elems[cb->end] = *elem;
    cb->end = (cb->end + 1) % cb->size;
    if (cb->end == cb->start)
        cb->start = (cb->start + 1) % cb->size; // full, overwrite //
}

// Read oldest element. App must ensure !cbIsEmpty() first. //
void cbRead(CircularBuffer *cb, ElemType *elem) {
    *elem = cb->elems[cb->start];
    cb->start = (cb->start + 1) % cb->size;
}


/*
 * main.c
 */
int main(void)
{
	FRAM_ptr = (unsigned char *)(FRAM_START+SPI_Transmit);

    WDTCTL = WDTPW | WDTHOLD;	// Stop watchdog timer

    /*************CLOCK INITIALIZER*************/
    //Writes password to enable CS register access
	CSCTL0_H = 0xA5;

	//Set max. DCO setting to 24MHz
	CSCTL1 |= DCOFSEL0 + DCOFSEL1 + DCORSEL;

	// Sets the MCLK source to DCOCLK,
	// the SMCLK source to DCOCLK,
	// and the ACLK source to VLOCLK
	CSCTL2 = SELM0 + SELM1 + SELS0 + SELS1 + SELA0;

	// Sets the MCLK clock divider to 0,
	// the SMCLK clock divider to 0,
	// and the ACLK clock divider to 0,
	CSCTL3 &= 0x00;

	// Enables conditional module requests for
	// MCLK, SMCLK, and ACLK
	CSCTL6 |= MCLKREQEN + SMCLKREQEN + ACLKREQEN;


	/*************TEMPRETURE SENSOR CONTROL*************/
	// Turn off temp sensor
	REFCTL0 |= REFTCOFF;
	REFCTL0 &= ~REFON;


	/*************GPIO INITIALIZER*************/
	GPIO_init();


	/*************SPI INITIALIZER*************/
	UCA0CTLW0 |= UCSWRST;					// **Put state machine in reset**
	// 4-pin, 8-bit SPI slave
	// Clock polarity high, MSB
	// 4-Pin SPI, w/ UCxSTE Active Low
	UCA0CTLW0 |= UCSYNC + UCMSB + UCMODE1 + UCCKPH;
	UCA0CTLW0 |= UCSSEL_2;					// ACLK
	UCA0BR0 = 0x02;							// /2
	UCA0BR1 = 0;							//
	UCA0MCTLW = 0;							// No modulation
	UCA0CTLW0 &= ~UCSWRST;					// **Initialize USCI state machine**
	UCA0IE |= UCRXIE;// + UCTXIE;			// Enable USCI_A0 RX interrupt


	/*************CONFIGURE MPU*************/
	MPUCTL0 = MPUPW;                          // Write PWD to access MPU registers
	//Add any changes to the MPU here
	MPUCTL0 = MPUPW+MPUENA;                   // Enable MPU protection

	__bis_SR_register(LPM0_bits + GIE);       // Enter LPM0, enable interrupts


	/*************INITIALIZE BUFFER*************/
	cbInit(&cb, testBufferSize);

	while (1)
	{
		if(SPI_Transmit > 2048)
		{
			cbFree(&cb);
			break;
		}

		/* Remove and print all elements */
		while (!cbIsEmpty(&cb)) {
			cbRead(&cb, &elem);
			FRAM_ptr = (unsigned char *)(FRAM_START+SPI_Transmit);
			*FRAM_ptr = elem.value;
			SPI_Transmit++;
		}
	}
}

#pragma vector=USCI_A0_VECTOR
__interrupt void USCI_A0_ISR(void)
{
	STROBE1_TOGGLE;
	elem.value = UCA0RXBUF;
	cbWrite(&cb, &elem);
	UCA0TXBUF = 0x00;
	STROBE1_TOGGLE;
	__bic_SR_register_on_exit(CPUOFF);// Wake up to setup next TX
}

I know that the SPI Interrupt is working by looking at the data in the FRAM, and I am also using a Beagle SPI Sniffer to verify that my code works.  However, what I am finding out is that the data isn't being correctly written to the FRAM until about 1700 transactions.

I am first wondering if my approach is the best option for this problem.  I intially tried without the buffer, and my results were much worse.  I've also tried to increase the buffer size, but I did not find any of the SPI transactions written to the EEPROM.  Any suggestions would be appreciated.

  • You shall use DMA instead. Download source code examples ad check DMA-related. You will find some that uses USCI DMA transfers.

  • When sniffing SPI transfers, be sure to synchonize with th eslave chip select. SPI is a bitstream. Without syncing to the CS signals, the USCI will 'receive' bits on any edge on the clock line, even if it is just the master's port pin setup. As a result, you will get all your data bytes bit-shifted.

  • llmars said:
    You should use DMA instead.

    I forgot about DMA, and I should have realized that this module would be useful for wheat I am trying to implement.  I'll try it out and come back with questions.

    Jens-Michael Gross said:
    When sniffing SPI transfers, be sure to synchonize with th eslave chip select.

    I have implemented a interrupt for the CS bit.  Below is what I wrote for the interrupt:

    // Port 1 interrupt service routine
    // Resets the SPI just in case
    #pragma vector=PORT1_VECTOR
    __interrupt void Port_1(void)
    {
    	UCA0CTL1 |= UCSWRST;
    	UCA0CTL1 &= ~UCSWRST;
    	UCA0IE |= UCRXIE;
    	P1IFG &= ~BIT4;                   // Clear P1.4 IFG
    	P1IE = 0;
    	__bic_SR_register_on_exit(CPUOFF);// Wake up to setup next TX
    }

    As you can see, I implement the CS interrupt to reset the SPI.  However, I have found that my code only enters the SPI interrupt instead of this interrupt.  I'm guessing that since it happens at the same time, then only one interrupt would run at a time.

    I don't know if this is overkill, but below is where I initialize the SPI to use the CS pin.

    // 4-pin, 8-bit SPI slave
    // Clock polarity high, MSB
    // 4-Pin SPI, w/ UCxSTE Active Low
    UCA0CTLW0 |= UCSYNC + UCMSB + UCMODE1 + UCCKPH;

    Again, I'll see if I can implement DMA into this code.  Thanks again for all of the pointers so far.  Please provide any more suggestions that you may think are neccesary.

  • dauletle said:
    However, I have found that my code only enters the SPI interrupt instead of this interrupt.  I'm guessing that since it happens at the same time, then only one interrupt would run at a time.

    At a time, sure. You have only one CPU :)

    However, the port pin interrutp should come 8 clock ticks before the RX interrupt. Also, your port ISR enables the RX interrupt, so there should be no interrupt triggered before the port ISR is called. (btw: do you ever disable the interrupt again when CS is de-asserted?). Also, there normally should be a (small) delay between asserting CS and the first clock cycle. If not, the (real) slave won't have time to setup its output register before the clock comes. Each slave datasheet specifies the minimum CS/CLK delay (and the master has to know and obey it).

  • Jens-Michael Gross said:
    btw: do you ever disable the interrupt again when CS is de-asserted?

    I don't think I disable the interrupt when the CS is de-asserted, and I'm not very sure how I would do that inside the ISR either.  However, I do know that the SPI master sends up to three bytes in one transmission (aka, while the CS pin is cleared by the SPI Master).

    More on my DMA setup in a little bit

  • I'm getting a little bit confused on how to implement the DMA into my SPI receiver.  Below is an example of the code I've implemented to initialize the DMA.

    DMA0SZ = 1;		// Block size
    DMACTL0 = DMA0TSEL__DMAREQ ;	// DMAREQ triggered
    
    // DMADT = 0 (Single Transfer),
    // DMADSTINCR = 11 (Destination address is incremented),
    // DMASRCINCR = 0 (Source address is unchanged),
    // DMADSTBYTE = 1, (Select destination as byte)
    // DMASRCBYTE = 1, (Select source as byte)
    // DMALEVEL = 1, (Level sensitive, high level selection)
    // DMAIE = 0, DMA Interrupt disabled
    DMA0CTL = DMASRCINCR1 + DMASRCINCR0 + DMASRCBYTE + DMADSTBYTE;// + DMALEVEL;
    
    __data16_write_addr((unsigned short) &DMA0SA,EUSCI_A0_BASE+0x0D); //RXBUF Register
    __data16_write_addr((unsigned short) &DMA0DA,(unsigned long) 0x1800);
    
    DMA0CTL |= DMAEN;	// Enable DMA0

    Ideally, what I would like to do is move the data received in the SPI Receive Buffer to the FRAM.  After the data is moved, then increment the destination address for the next SPI transmission received.  Thanks again for all of the pointers so far.  Please provide any more suggestions that you may think are neccesary. Below is how I've set up my SPI Receive Interrupt:

    #pragma vector=USCI_A0_VECTOR
    __interrupt void USCI_A0_ISR(void)
    {
    	DMA0CTL |= DMAREQ;                // Trigger block transfer
    	UCA0TXBUF = 0x00;
    	__bic_SR_register_on_exit(CPUOFF);// Wake up to setup next TX
    }
  • Sorry, I forgot to add.  The result of this was the same as if I used my previous approach.  In this case, only a few sequential bytes received from the SPI Receive Buffer are saved into the memory. 

  • dauletle said:
    I'm not very sure how I would do that inside the ISR either.

    After getting the falling edge interrupt, reprogram the pin for rising edge interrupt. In the ISR, then clear the IE bit.

    Well, if you don't disable the interrupt, you don't need to enable it. inside the ISR too. Alternatively, keep UCSWRST set until you detect CS going low (first instruction in the ISR!). And put the USCI into SWRST once you detected CS going high again. This ensures bit synchronization with the master.
    Note that the master may send any number of bogus clock pulses during CS high. Not necessarily a multiple of 8, which will bring the USCI out of sync if it is not guarded somehow.

    dauletle said:
    However, I do know that the SPI master sends up to three bytes in one transmission (aka, while the CS pin is cleared by the SPI Master).

    Well, a sniffer shouldn't make this kind of assumptions :)

  • Jens-Michael Gross said:
    After getting the falling edge interrupt, reprogram the pin for rising edge interrupt. In the ISR, then clear the IE bit.

    I'm not sure if I understand that completely.  I know that the interrupt vector for Port 1 is the same regardless on how I set the trigger.  In this case, do I set up the ISR for Port 1 to be conditional on if the Pin is set or cleared? If that is the case, this is how I initialize the GPIO pin for the SPI CS functionality:

    // Configure SPI Pins
    // Pin 1.5 set for Serial Clock Out (UCA0CLK)
    // Pin 1.4 set for SS (Slave Select) (UCA0STE)
    P1SEL1 |= SPI_CLK + SPI_SS;
    P1OUT &= ~(STROBE1 + STROBE2 + STROBE3 + STROBE4);
    P1DIR |= STROBE1 + STROBE2 + STROBE3 + STROBE4;
    
    // Terminate Unused GPIOs
    // P1.0 - P1.6 is unused
    P1OUT &= ~(BIT6 + BIT7);
    P1DIR &= ~(BIT6 + BIT7);
    P1REN |= (BIT3 + BIT6 + BIT7);
    
    P1IES |= BIT4;            // PxIFG flag is set with a high-to-low transition
    P1IE = BIT4;              // P1.4 interrupt enabled
    P1IFG &= ~BIT4;           // P1.4 IFG cleared

    Then, this would be how I setup the Port 1 ISR

    // Port 1 interrupt service routine
    #pragma vector=PORT1_VECTOR
    __interrupt void Port_1(void)
    {
    	if (P1IN & BIT4 == 0)	//If Pin 1.4 is cleared
    	{
    		UCA0CTL1 &= ~UCSWRST;	//eUSCI reset released for operation.
    		UCA0IE |= UCRXIE;	// Enables SPI Rx Interrupt
    		P1IES &= ~BIT4;		// PxIFG flag is set with a low-to-high transition
    	}
    	else //if (P1IN & BIT4 == 1)	//If Pin 1.4 is set
    	{
    		UCA0CTL1 |= UCSWRST;	// Keep UCSWRST set until CS goes low
    		P1IES |= BIT4;		// PxIFG flag is set with a high-to-low transition
    	}
    	P1IFG &= ~BIT4;		// Clear P1.4 IFG
    	//P1IE = 0;			//I was clearing this, but it may disable the ISR
    	__bic_SR_register_on_exit(CPUOFF);// Wake up to setup next TX
    
    }

    I made some changes to my original code. For starters, I found that I was waiting for the low-to-high edge of Pin 1.4.  This would explain why the MSP430 would never enter the Port 1 ISR (since the SPI ISR would already be running).  I also found that if the MSP430 would enter the Port 1 ISR, then it might be disabled with the instruction "P1IE = 0", thus, it would never enter that ISR again.

    I'll try this out in a little bit.  Please let me know if my code or logic is not correct in any way in regards to this issue.

  • dauletle said:
    Then, this would be how I setup the Port 1 ISR

    looks good. However, I'd rather check for the P1IES bit too. So you won't deadlock in case you missed the next edge for some reason (e.g. a spike, or because you were in a different ISR too long) But that's rare error condition handling.

    dauletle said:
    __bic_SR_register_on_exit(CPUOFF);// Wake up to setup next TX

    Setup next TX? A sniffer shouldn't TX anything. (however, a sniffer should listen on both, RX and TX, so you would need a second SPI for sniffing the TX direction too)

  • Jens-Michael Gross said:
    Setup next TX? A sniffer shouldn't TX anything.

    Whoops.  The comment was incorrect.  This line of the code just wakes up the CPU for the next RX instruction.

    Jens-Michael Gross said:
    I'd rather check for the P1IES bit too.

    Changed the code so that it would prevent any deadlock.  Below is the updated Port 1 Interrupt

    // Port 1 interrupt service routine
    #pragma vector=PORT1_VECTOR
    __interrupt void Port_1(void)
    {
    	STROBE1_TOGGLE;
    	if (P1IES & BIT4 == 1)	//If P1IFG flag is set with a high-to-low transition
    	{
    		UCA0CTL1 &= ~UCSWRST;	//eUSCI reset released for operation.
    		UCA0IE |= UCRXIE;	// Enables SPI Rx Interrupt
    		P1IES &= ~BIT4;		// P1IFG flag is set with a low-to-high transition
    	}
    	else //if (P1IES & BIT4 == 0)	//Else if P1IFG flag is set with a low-to-high transition
    	{
    		UCA0CTL1 |= UCSWRST;	// Keep UCSWRST set until CS goes low
    		P1IES |= BIT4;		// P1IFG flag is set with a high-to-low transition
    	}
    	P1IFG &= ~BIT4;		// Clear P1.4 IFG
    	//P1IE = 0;
    	__bic_SR_register_on_exit(CPUOFF);// Wake up to setup next RX
    
    }

    I've tested the code, and it looks like not only is it failing to enter the Port 1 ISR (from verifying it using the oscilloscope and toggling a GPIO pin), but it is no longer writing any SPI transmissions to the FRAM.

  • Alright, so I've reverted back to writing the data into the FRAM using this code

    #pragma vector=USCI_A0_VECTOR
    __interrupt void USCI_A0_ISR(void)
    {
    	STROBE1_TOGGLE;
    	*FRAM_ptr = UCA0RXBUF;
    	UCA0TXBUF = 0x00;
    	SPI_Transmit++;
    	FRAM_ptr = (unsigned char *)(FRAM_START+SPI_Transmit);
    	STROBE1_TOGGLE;
    	__bic_SR_register_on_exit(CPUOFF);// Wake up to setup next RX
    }

    I'm now experiencing the same problem as in the beginning of this forum, where close to 1800 transactions occur before data is written to the FRAM.  Is there anything else I should try?

  • >volatile unsigned char SPI_Transmit = 0;

    Is SPI_Transmit still a byte? If so, you're cycling through the first 256 bytes of your (FRAM) buffer over and over, so all you'll see is the last 256.

  • Bruce McKenney47378 said:
    Is SPI_Transmit still a byte? If so, you're cycling through the first 256 bytes of your (FRAM) buffer over and over, so all you'll see is the last 256.

    Oops! SPI_Transmit is being used as a counter to increment through the memory.  I just set it as a int, and now it is capturing the SPI transactions after 256 bytes have read.  While this is much better than capturing after 1800 transactions, it isn't perfect yet.

    I first thought that reason why there is still a delay is because the MSP430 needs time to initialize all the modules before it can start running the logic in the SPI ISR.  However, I have found with other projects in the past that this cannot possibly be the case. In my previous projects with the MSP430 and SPI, I was able to capture the first bytes coming from the SPI Master, as well as respond. 

    Also, even though there might be a chance for the module initializations to take time, there is no way that they can take so much time that it would allow 256 bytes to transact undetected before the MSP430 is ready.  From my commercial SPI Sniffer, I have found that about 64.4ms pass before the MSP430 is able to capture transactions.  Since I am setting my clock to run at 24MHz, that means that about 1,545,720 clock cycles have passed before the MSP430 can capture SPI transactions!

    Thank you again for this suggestion.  If anyone else can point out any other flaws in my code, that would be appreciated.

  • I've figured it out.  For starters, I've changed the location of where the memory was being written.  Specifically, I've changed it from where the information memory is located (0x1800) to a segment of memory set by the MPU.  Below is how I set it up.

    	/*************CONFIGURE MPU*************/
    	MPUCTL0 = MPUPW;                          // Write PWD to access MPU registers
    
    	//Add any changes to the MPU here
    	MPUSEG = 0x0804;                          // B1 = 0xC800; B2 = 0xD000
    	                                          // Borders are assigned to segments
    
    
    	MPUCTL0 = MPUPW+MPUENA;                   // Enable MPU protection

    I've also set up the write to location command at the beginning of the main code.

    int main(void)
    {
    	FRAM_ptr = (unsigned char *)(FRAM_START+SPI_Transmit);

    It now works great.  Thanks again for the help.

**Attention** This is a public forum