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 interface between MSP430 and ADS7866

Other Parts Discussed in Thread: MSP430FR5739, ADS7866

Hi,

I'm a student and I'm trying to get two of your products to talk to each other effectively and I'm having a lot of trouble with it.  I'm attempting to read voltage values from your ADS7866 ADC to a MSP430FR5739 which is installed in the development kit.  I've set the eUSCI A0 to SPI mode and I've tried reading the values out manually and by using DMA to implement the transfer.  I can get something partially intelligible without using DMA to complete the transfer, however I'm not getting the resolution that I would expect from the device, which says to me that the data isn't getting transferred correctly.  When I use DMA, I can read data from the device that makes sense roughly half of the time - but again with poor resolution.  In either case, I'm not convinced that the data I read out of the ADC is actually valid.  

There is very little information online or anywhere else on using DMA to access the eUSCI peripherals, so I've muddled some code together from the sources I could find.  I am wondering if there's something I'm doing wrong, or if there is a way to make the read from the ADC more reliable.  My code for the DMA read of the ADC over SPI (4-pin) is below.  The clock generated by the eUSCI is ~1MHz:

Initialization function for the eUSCI to read the ADC:

/**********************************************************************************
 * This function initializes the eUSCI for SPI mode to communicate with the
 * external ADC.
 * It is configured for 4-pin SPI mode with an active low control signal
 * and is clocked off of the ACLK at a rate of ACLK/2.  Only the Rx, control signal
 * and clock pins are enabled since the ADC does not accept data input
 **********************************************************************************/
void Init_ADC(){
	UCA0CTLW0 |= UCSWRST;                   // **Put state machine in reset**
	//Initialize pins
	P1SEL1 |= BIT5 + BIT4;					//Pin 1.4 -> UCA0STE, Pin 1.5 -> UCA0CLK
	P2SEL1 |= BIT1+BIT0;							//Pin 2.1 -> UCA0RXD, Pin 2.0 -> UCA0TXD... don't need to activate it
											//and don't need to connect it to anything
	UCA0CTLW0 |= UCMST+UCSYNC+UCMSB+UCCKPL 	// 8-bit SPI master
			+UCMODE_2+UCSTEM+UCCKPH;						// 4-pin SPI with UCxSTE active low
											// Clock polarity high, MSB
	UCA0CTLW0 |= UCSSEL_1;                  // ACLK (2MHz)
	UCA0BR0 = 0x02;                         // /2 (clock div)
	UCA0BR1 = 0;                            //
	UCA0MCTLW = 0;                          // No modulation
	UCA0CTLW0 &= ~UCSWRST;                  // **Initialize USCI state machine**
	//Initialize DMA module for transfer from USCI to RAM (channels 1 and 0)
//DMA channel 1 = Block transfer, don't increment source or destination addresses, Level trigger (don't get that), Both source and destination are byte sized DMA1CTL = DMADT_1+DMASRCINCR_0+DMADSTINCR_0 + DMALEVEL + DMADSTBYTE+ DMASRCBYTE; //DMALEVEL +;// + DMAEN; // Single Transfer, no increment
//DMA channel triggers - software for channel 1, eUSCI Rx buffer for channel 0 DMACTL0 = DMA0TSEL__UCA0RXIFG + DMA1TSEL__DMAREQ;
//DMA channel 0 = Block transfer, don't increment source address, increment destination address, level trigger, both source and destination are byte sized DMA0CTL = DMADT_1+DMASRCINCR_0+DMADSTINCR_3 + DMALEVEL + DMASRCBYTE + DMADSTBYTE;// DMALEVEL +;// + DMAEN; // Single Transfer, no increment DMA0SA = &UCA0RXBUF; // Channel 0 source = eUSCI receive buffer DMA0SZ = 2; // Transfer 16 bits using DMA // Setup DMA1 // Transfer 2 bytes on DMA0 DMA1SZ = 2; // Transfer 2 bytes on DMA1 DMA1SA = &TxData; //Source address is variable containing dummy data to send DMA1DA = &UCA0TXBUF; // DMA channel 1 Destination is eUSCI transmit buffer }

The read function is here:

/*********************************************************************************
 * This function reads from the ADC over the eUSCI SPI interface.  The function
 * returns the data read by the ADC
 * input: select	-Selects which input to read from
 * 					1: Input Voltage
 * 					2: Input Current
 * 					3: Output Voltage
 ********************************************************************************/
int Read_ADC(int select){
	int reading = 0;
        DMA0DA = &reading;                           //DMA channel 0 destination is address of variable "reading"
	//Use the external MUX to decide which input to use
	switch(select){
	case ADC_VIN:
		P1OUT &= ~BIT2;
		P1OUT |= BIT1;
		break;
	case ADC_IIN:
		P1OUT &= ~BIT1;
		P1OUT |= BIT2;
		break;
	case ADC_VOUT:
		P1OUT |= BIT1 + BIT2;
		break;
	default:
		P1OUT |= ~(BIT1 + BIT2);
		break;
	}

	__delay_cycles(20);				// Delay before reading from ADC ~ .001s at 2 MHz to allow for mux to switch 

	DMA0CTL |= DMAEN;		// Enable DMA0
	DMA1CTL |= DMAEN+DMAREQ;		// Enable DMA1 and start transfer
	__delay_cycles(30);				// this delay is here because it seems necessary to allow time for the DMA to populate the memory 
                                                        //with the received data
	P1OUT |= ~(BIT1 + BIT2);                  //disconnect everything from ADC 

	return reading;
}

This is how the ADC_Read function is called:

	Vinread = (unsigned long)Read_ADC(ADC_VIN);

where ADC_VIN is a #defined constant.

Thank you in advance for any help you are able to provide on this matter.

-Chris Hack

  • Obviously you shall just get it working w/o DMA, only then introduce DMA
  • I know that.  I've got it kind of working without DMA... here's the code for that portion.

    eUSCI initialization to read the ADC:

    /**********************************************************************************
     * This function initializes the eUSCI for SPI mode to communicate with the
     * external ADC.
     * It is configured for 4-pin SPI mode with an active low control signal
     * and is clocked off of the ACLK at a rate of ACLK/2.  Only the Rx, control signal
     * and clock pins are enabled since the ADC does not accept data input
     **********************************************************************************/
    void Init_ADC(){
    	UCA0CTLW0 |= UCSWRST;                   // **Put state machine in reset**
    	//Initialize pins
    	P1SEL1 |= BIT5;					//Pin 1.4 -> UCA0STE, Pin 1.5 -> UCA0CLK
    	P2SEL1 |= BIT1;							//Pin 2.1 -> UCA0RXD, Pin 2.0 -> UCA0TXD... don't need to activate it
    											//and don't need to connect it to anything
    	P1DIR |= BIT4;
    	P1OUT |= BIT4;
    	UCA0CTLW0 |= UCMST+UCSYNC+UCMSB+UCCKPL 	// 8-bit SPI master
    			+UCMODE_0;//+UCSTEM+UCCKPH;						// 4-pin SPI with UCxSTE active low
    											// Clock polarity high, MSB
    	UCA0CTLW0 |= UCSSEL_1;                  // ACLK
    	UCA0BR0 = 0x02;                         // /2
    	UCA0BR1 = 0;                            //
    	UCA0MCTLW = 0;                          // No modulation
    	UCA0CTLW0 &= ~UCSWRST;                  // **Initialize USCI state machine**
    	UCA0IE |= UCRXIE;                       // Enable USCI_A0 RX interrupt
    
    }

    The read function (called exactly the same way as in the previous example):

    /*********************************************************************************
     * This function reads from the ADC over the eUSCI SPI interface.  The function
     * returns the data read by the ADC
     * input: select	-Selects which input to read from
     * 					1: Input Voltage
     * 					2: Input Current
     * 					3: Output Voltage
     ********************************************************************************/
    int Read_ADC(int select){
    	int reading = 0;
    	//Use the MUX to decide which
    	switch(select){
    	case ADC_VIN:
    		P1OUT &= ~BIT2;
    		P1OUT |= BIT1;
    		break;
    	case ADC_IIN:
    		P1OUT &= ~BIT1;
    		P1OUT |= BIT2;
    		break;
    	case ADC_VOUT:
    		P1OUT |= BIT1 + BIT2;
    		break;
    	default:
    		P1OUT |= ~(BIT1 + BIT2);
    		break;
    	}
    
    	__delay_cycles(20);                   // Delay before reading from ADC ~ .001s at 2 MHz
    
    	P1OUT ^= BIT4;
        UCA0TXBUF = TXDATA;             // Transmit characters
        while (UCA0STATW & UCBUSY);		// Wait for UCA0 to be ready
        reading = RXData;				// Grab first 8 bits of received data from eUSCI
        UCA0TXBUF = TXDATA;             // Transmit characters
    	reading <<=7;					// Bit-shift the reading to account for the rest of the data
    	reading |= RXData;				// Concantonate the data for the full reading
        while (UCA0STATW & UCBUSY);		// Wait for UCA0 to be ready
    //    UCA0TXBUF = TXDATA;             // Transmit characters
        P1OUT ^= BIT4;
        __enable_interrupt();
    	//Disconnect the ADC from the inputs
    	P1OUT |= ~(BIT1 + BIT2);
    
    	return reading;
    }
    

    The interrupt handler for the eUSCI:

    /*******************************************************************
     * eUSCIA0 Interrupt handler
     * This is used to communicate with the external ADC in SPI mode
     ******************************************************************/
    #pragma vector=USCI_A0_VECTOR
    __interrupt void USCI_A0_ISR(void)
    {
    	LPM2_EXIT;
    //  volatile unsigned int i;
    
      switch(UCA0IV)
      {
        case 0: break;                          // Vector 0 - no interrupt
        case 2:
               RXData = UCA0RXBUF;				//save data in global variable
               UCA0IFG &= ~UCRXIFG;				//Clear RX interrupt flag
    //           UCA0TXBUF = TXDATA;             // Transmit characters
               break;
        case 4:
              UCA0TXBUF = TXDATA;             // Transmit characters
              UCA0IE &= ~UCTXIE;			//disable TX interrupt
              break;
        default: break;
      }
    }

    Obviously, this takes two cycles to complete since the eUSCI buffer is only 8 bits long, and I can't quite get the reading to be received as something intelligible all the time.  Sometimes even with the same code both of these are exhibiting weird behavior where on one compilation things will work mostly correctly and on another compilation the value returned will be something absolutely outrageous or just plain wrong.  I can view the signals on a scope and they seem to be correct and do change with varying voltage input to the ADC, however the output value received does not always change to reflect the change I've seen in the input waveform on the scope.  It's supposed to be easier than this, shouldn't it?  These peripherals were made to interface...

    As always, thank you again in advance for any help you may be able to provide.

    -Chris Hack

  • For the code in your second post, I see that in the read function, you have interrupts not enabled. So how is the ISR supposed to change rxdata when an interrupt comes?
    I hope you declared rxdata as volatile, or the compiler will probably optimize access to it in a way that any ‘magic’ change by the ISR is not noticed at all.
    After reading=RXData, you don’t wait until new rxdata is there. Also, you shift reading by only 7 bit. Which means BIT7 of the new rxdata will overwrite BIOT0 of the old (shifted by 7) rxdata.
  • First of all, thank you all for your helpful responses.  I have been able to reliably transfer data from eUSCI A0 to memory without using DMA, however it still completely fails when attempting to read the eUSCI using DMA.  I have tried everything that I can think of to get this damn transfer to work to no avail.  I have noticed an interesting phenomenon with this:

    When I step through the transfer function during debug I can get the function to return a result (admittedly, a garbage result), however when I just step over the function there is no return value.  Any thoughts as to why that could be?

    Now, here is the code I'm using to set up the DMA transfer from eUSCIA0 to memory:

    /**********************************************************************************
     * This function initializes the eUSCI for SPI mode to communicate with the
     * external ADC.
     * It is configured for 4-pin SPI mode with an active low control signal
     * and is clocked off of the ACLK at a rate of ACLK/2.  Only the Rx, control signal
     * and clock pins are enabled since the ADC does not accept data input
     **********************************************************************************/
    void Init_ADC(){
    	UCA0IE |= UCRXIE;                       // Enable USCI_A0 RX interrupt
    	UCA0CTLW0 |= UCSWRST;                   // **Put state machine in reset**
    	//Initialize pins
    	P1SEL1 |= BIT5 + BIT4;                  //Pin 1.4 -> UCA0STE, Pin 1.5 -> UCA0CLK
    	P2SEL1 |= BIT1+BIT0;                            //Pin 2.1 -> UCA0RXD, Pin 2.0 -> UCA0TXD... don't need to activate it
    	//and don't need to connect it to anything
    	UCA0CTLW0 |= UCMST+UCSYNC+UCMSB+UCCKPL  // 8-bit SPI master
    			+UCMODE_2+UCSTEM+UCCKPH;                        // 4-pin SPI with UCxSTE active low
    	// Clock polarity high, MSB
    	UCA0CTLW0 |= UCSSEL_1;                  // ACLK (2MHz)
    	UCA0BR0 = 0x03;                         // /2 (clock div)
    	UCA0BR1 = 0;                            //
    	UCA0MCTLW = 0;                          // No modulation
    	UCA0CTLW0 &= ~UCSWRST;                  // **Initialize USCI state machine**
    	//Initialize DMA module for transfer from USCI to RAM (channels 1 and 0)        //DMA channel 1 = Block transfer, don't increment source or destination addresses, Level trigger (don't get that), Both source and destination are byte sized
    	DMA1CTL = DMADT_1+DMASRCINCR_3+DMADSTINCR_0 + DMALEVEL + DMADSTBYTE+ DMASRCBYTE; //DMALEVEL +;// + DMAEN; // Single Transfer, no increment        //DMA channel triggers - software for channel 1, eUSCI Rx buffer for channel 0
    	DMACTL0 = DMA0TSEL__UCA0RXIFG + DMA1TSEL__UCA0TXIFG;        //DMA channel 0 = Block transfer, don't increment source address, increment destination address, level trigger, both source and destination are byte sized
    	DMA0CTL = DMADT_1+DMASRCINCR_0+DMADSTINCR_3 + DMALEVEL + DMASRCBYTE + DMADSTBYTE;// DMALEVEL +;// + DMAEN; // Single Transfer, no increment
    	DMA0SZ = 2;                              // Transfer 16 bits using DMA
    	// Setup DMA1                            // Transfer 2 bytes on DMA0
    	DMA1SZ = 2;                              // Transfer 2 bytes on DMA1
    	__data16_write_addr((unsigned short) &DMA0SA,(unsigned long) &UCA0RXBUF);	// Source block address	DMA0SZ = 2;                              // Transfer 16 bits using DMA
    	__data16_write_addr((unsigned short) &DMA1SA,(unsigned long) &TxData);
    	__data16_write_addr((unsigned short) &DMA1DA,(unsigned long) &UCA0TXBUF);
    }

    Here is the code I'm using to actually do the read:

    /*********************************************************************************
     * This function reads from the ADC over the eUSCI SPI interface.  The function
     * returns the data read by the ADC
     * input: select	-Selects which input to read from
     * 					1: Input Voltage
     * 					2: Input Current
     * 					3: Output Voltage
     ********************************************************************************/
    int Read_ADC(int select){
    	int reading = 0;
    	__data16_write_addr((unsigned short) &DMA0DA,(unsigned long) &reading);
    	switch(select){
    	case ADC_VIN:
    		P1OUT &= ~BIT2;
    		P1OUT |= BIT1;
    		break;
    	case ADC_IIN:
    		P1OUT |= BIT2;
    		P1OUT &= ~BIT1;
    		break;
    	case ADC_VOUT:
    		P1OUT |= BIT1 + BIT2;
    		break;
    	default:
    		P1OUT &= ~(BIT1 + BIT2);
    		break;
    	}
    
    	__delay_cycles(10);				// Delay before reading from ADC ~ .0005s at 4 MHz
    	DMA1CTL &= ~DMAIFG;				//Clear DMA interrupt flag
    	DMA0CTL |= DMAEN+DMAREQ;		// Enable DMA0 and start transfer
    	DMA1CTL |= DMAEN+DMAREQ;		// Enable DMA0 and start transfer
    	UCA0IFG &= ~UCTXIFG;                      // Toggle TX IFG to trigger first
    	UCA0IFG |= UCTXIFG;                       // DMA transfer...
    	P1OUT &= ~(BIT1 + BIT2);
    
    	return reading;
    }

    The former function is called like this:

    ReadVal = (unsigned long)Read_ADC(ADC_VIN);

    I have verified that the DMA addresses are correct in the registers.  The reason I am using the USCI TX interrupt flag to trigger the DMA transfer is because I was unable to get a full 16 bit transfer cycle to happen when using the DMAREQ trigger.  If there is any other information that you need in order to help me to diagnose this issue, feel free to ask.  

    As always, thank you in advance for any help you can provide.

    -Chris Hack

  • - DMALEVEL can only be used for the external DMA trigger. Internal DMA triggers need to be used in edge trigger mode (see errata sheet)
    - The eUSCI has some problems with DMA - sometimes it simply fails to get the trigger and the communication ceases. Again see the errata sheet. No known workaround.
    - Manually changing IFG bits will not in all cases trigger a DMA.
    - to trigger DMA, the corresponding IE bit must not be set (ether IRQ or DMA, not both). However, the line in init_ADC that sets UCRXIE is void, as the very next line clears it again by setting UCSWRST.
    - I don't know why you tried to trigger an USCI DMA by DMAREQ - RXIFG and TXIFG are the right triggers to use.
    However, the first byte to be transmitted should be manually written to TXBUF, subsequent transfers are done by the DMA. Of course it reduces the number of TX DMA transfers by 1 then :)
    But I would say that a simple transfer of two bytes, especially sending just two bytes, is no job for a DMA. Simply writing two bytes to TXBUF (when TX is idle) does the same job (the first write on idle TX is immediately forwarded to the output, and TXBUF can instantly receive the next byte - but you can wait for TXIFG to be sure, if the USCI source clock is slower than MCLK).

**Attention** This is a public forum