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.

TM4C1294KCPDT: Receive EPI data using uDMA

Part Number: TM4C1294KCPDT

Our application receives blocks of 6,964 bytes of data streamed from an FPGA every 50ms. Our initial implementation used a 32-bit EPI data bus with a request-to-send signal sourced by the FPGA. The request-to-send signal triggers an interrupt on PortP:5, when triggered the interrupt reads the EPI bus data and writes it into a buffer. Once a full block of data is received, the block of data is transmitted using uDMA on SPI0. The problem we are experiencing is that servicing the EPI data interrupt starves out the main thread resulting in the USB receive/transmit to stop operating. Therefore, we have been attempting to migrate the code to use uDMA to transfer the data from the EPI bus.

Our initial attempt made the following assumptions:
1. Can't do uDMA transfer directly between EPI0 and SPI0. Therefore, our design uses uDMA to receive block of data from EPI bus into a buffer. When a full block of data has been received on the EPI bus, the data is transmitted using uDMA to SPI0 using received EPI buffer.
2. Need to use the EPI clock signal to synchronize EPI reads with the FPGA. The EPI clock uses EPI31, therefore we need to reduce the EPI bus size to 24, 16, or 8. The uDMA transactions are limited to data sizes of 32, 16, or 8. Therefore, our EPI data bus needs to be reduced to 16-bits.
3. Renamed the request-to-send signal sourced by the FPGA to frame signal. The frame signal rising edge triggers the PortP:5 interrupt to start the uDMA transaction to receive the data from the FPGA. The frame signal falling edge triggers PortP:5 which triggers the uDMA to stream the received data on SPI 0.

Please comment if any of these assumptions are incorrect or misunderstood.

We have the following implementation that is not operating, the problem is likely a result of our interpretation/understanding of the Tivaware documentation and examples. Will you please review the attached code blocks and provide guidance on how to use uDMA to transfer data from the EPI data bus.

#define CALC_EPI_DIVIDER( epiClkRate )	( ROUND_D2UI(MASTER_CLOCK_FREQUENCY / epiClkRate) - 2 )										// Calculate EPI clock divider for requested EPI clock rate

static void configEPIPeripheral( void )
{
	uint16_t divider = CALC_EPI_DIVIDER( 1.0E6 );

	// Note : All ports enabled earlier
	// 	enablePeripheral( SYSCTL_PERIPH_GPIOX );																								// Call routine to enable general purpose I/O on requested port

	// Note : uDMA enabled earlier
	// enablePeripheral( SYSCTL_PERIPH_UDMA );                                                                                               	// Enable the uDMA controller at the system level
    // MAP_uDMAEnable( );																														// Enable the uDMA controller
    // MAP_uDMAControlBaseSet( m_uDMAControlTable );																							// Point at the control table to use for channel control structures


	MAP_uDMAChannelSelectSecondary( UDMA_DEF_TMR1A_SEC_EPI0RX );																				// Set default channel as EPI receive
	MAP_uDMAChannelAttributeDisable( UDMA_SEC_CHANNEL_EPI0RX, UDMA_ATTR_ALTSELECT | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK );				// Put the attributes in a known state for the uDMA SSI0RX channel
    MAP_uDMAChannelAttributeEnable( UDMA_SEC_CHANNEL_EPI0RX, UDMA_ATTR_USEBURST );																// Enable use burst mode attribute
    MAP_uDMAChannelControlSet( UDMA_SEC_CHANNEL_EPI0RX | UDMA_PRI_SELECT, UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_8 );	// Configure the control parameters for the primary control structure for the SSI0 TX channel

	enablePeripheral( SYSCTL_PERIPH_EPI0 );																		// Enable the EPI peripheral

	MAP_GPIOPinTypeEPI( GPIO_PORTA_BASE, PORT_A_EPI_PINS );														// Configure port A pins used for EPI
	MAP_GPIOPinConfigure( GPIO_PA6_EPI0S8 );																	// Configure EPI data signal 8
	MAP_GPIOPinConfigure( GPIO_PA7_EPI0S9 );																	// Configure EPI data signal 9

//	MAP_GPIOPinTypeEPI( GPIO_PORTB_BASE, PORT_B_EPI_PINS );														// Configure port B pins used for EPI
//	MAP_GPIOPinConfigure( GPIO_PB2_EPI0S27 );																	// Configure EPI data signal 27
//	MAP_GPIOPinConfigure( GPIO_PB3_EPI0S28 );																	// Configure EPI data signal 28
	MAP_GPIOPinTypeGPIOInput( GPIO_PORTB_BASE, PORT_B_EPI_PINS );												// Configure 16-bit EPI unused pins

	MAP_GPIOPinTypeEPI( GPIO_PORTC_BASE, PORT_C_EPI_PINS );														// Configure port C pins used for EPI
	MAP_GPIOPinConfigure( GPIO_PC4_EPI0S7 );																	// Configure EPI data signal 7
	MAP_GPIOPinConfigure( GPIO_PC5_EPI0S6 );																	// Configure EPI data signal 6
	MAP_GPIOPinConfigure( GPIO_PC6_EPI0S5 );																	// Configure EPI data signal 5
	MAP_GPIOPinConfigure( GPIO_PC7_EPI0S4 );																	// Configure EPI data signal 4

	MAP_GPIOPinTypeEPI( GPIO_PORTG_BASE, PORT_G_EPI_PINS );														// Configure port G pins used for EPI
	MAP_GPIOPinConfigure( GPIO_PG0_EPI0S11 );																	// Configure EPI data signal 11
	MAP_GPIOPinConfigure( GPIO_PG1_EPI0S10 );																	// Configure EPI data signal 10

	MAP_GPIOPinTypeEPI( GPIO_PORTH_BASE, PORT_H_EPI_PINS );														// Configure port H pins used for EPI
	MAP_GPIOPinConfigure( GPIO_PH0_EPI0S0 );																	// Configure EPI data signal 0
	MAP_GPIOPinConfigure( GPIO_PH1_EPI0S1 );																	// Configure EPI data signal 1
	MAP_GPIOPinConfigure( GPIO_PH2_EPI0S2 );																	// Configure EPI data signal 2
	MAP_GPIOPinConfigure( GPIO_PH3_EPI0S3 );																	// Configure EPI data signal 3

	MAP_GPIOPinTypeEPI( GPIO_PORTK_BASE, PORT_K_EPI_PINS );														// Configure port K pins used for EPI
	MAP_GPIOPinConfigure( GPIO_PK5_EPI0S31 );																	// Configure EPI data signal 31
//	MAP_GPIOPinConfigure( GPIO_PK6_EPI0S25 );																	// Configure EPI data signal 25
//	MAP_GPIOPinConfigure( GPIO_PK7_EPI0S24 );																	// Configure EPI data signal 24
	MAP_GPIOPinTypeGPIOInput( GPIO_PORTK_BASE, EPI0_SIGNAL_24_PK7 | EPI0_SIGNAL_25_PK6 );						// Configure 16-bit EPI unused pins

//	MAP_GPIOPinTypeEPI( GPIO_PORTL_BASE, PORT_L_EPI_PINS );														// Configure port L pins used for EPI
//	MAP_GPIOPinConfigure( GPIO_PL0_EPI0S16 );																	// Configure EPI data signal 16
//	MAP_GPIOPinConfigure( GPIO_PL1_EPI0S17 );																	// Configure EPI data signal 17
//	MAP_GPIOPinConfigure( GPIO_PL2_EPI0S18 );																	// Configure EPI data signal 18
//	MAP_GPIOPinConfigure( GPIO_PL3_EPI0S19 );																	// Configure EPI data signal 19
//	MAP_GPIOPinConfigure( GPIO_PL4_EPI0S26 );																	// Configure EPI data signal 20
	MAP_GPIOPinTypeGPIOInput( GPIO_PORTL_BASE, PORT_L_EPI_PINS );												// Configure 16-bit EPI unused pins

	MAP_GPIOPinTypeEPI( GPIO_PORTM_BASE, PORT_M_EPI_PINS );														// Configure port M pins used for EPI
	MAP_GPIOPinConfigure( GPIO_PM0_EPI0S15 );																	// Configure EPI data signal 15
	MAP_GPIOPinConfigure( GPIO_PM1_EPI0S14 );																	// Configure EPI data signal 14
	MAP_GPIOPinConfigure( GPIO_PM2_EPI0S13 );																	// Configure EPI data signal 13
	MAP_GPIOPinConfigure( GPIO_PM3_EPI0S12 );																	// Configure EPI data signal 12

//	MAP_GPIOPinTypeEPI( GPIO_PORTP_BASE, PORT_P_EPI_PINS );														// Configure port P pins used for EPI
//	MAP_GPIOPinConfigure( GPIO_PP2_EPI0S29 );																	// Configure EPI data signal 29
//	MAP_GPIOPinConfigure( GPIO_PP3_EPI0S30 );																	// Configure EPI data signal 30
	MAP_GPIOPinTypeGPIOInput( GPIO_PORTP_BASE, PORT_P_EPI_PINS );												// Configure 16-bit EPI unused pins

//	MAP_GPIOPinTypeEPI( GPIO_PORTQ_BASE, PORT_Q_EPI_PINS );														// Configure port Q pins used for EPI
//	MAP_GPIOPinConfigure( GPIO_PQ0_EPI0S20 );																	// Configure EPI data signal 20
//	MAP_GPIOPinConfigure( GPIO_PQ1_EPI0S21 );																	// Configure EPI data signal 21
//	MAP_GPIOPinConfigure( GPIO_PQ2_EPI0S22 );																	// Configure EPI data signal 22
//	MAP_GPIOPinConfigure( GPIO_PQ3_EPI0S23 );																	// Configure EPI data signal 23
	MAP_GPIOPinTypeGPIOInput( GPIO_PORTQ_BASE, PORT_Q_EPI_PINS );												// Configure 16-bit EPI unused pins

    MAP_EPIModeSet( EPI0_BASE, EPI_MODE_GENERAL );                                                          	// Enable read of EPI input data
	MAP_EPIDividerSet( EPI0_BASE, divider );																	// Set EPI clock rate = 1MHz
	MAP_EPIConfigGPModeSet( EPI0_BASE, EPI_GPMODE_CLKPIN | EPI_GPMODE_CLKGATE | EPI_GPMODE_DSIZE_16, 0, 0 );	// No Address, 32 bit Data 
//	MAP_EPIAddressMapSet( EPI0_BASE, EPI_ADDR_PER_BASE_A );  													// Address map for 32-bit interrupt 0xA000.0000
	MAP_EPIAddressMapSet( EPI0_BASE, EPI_ADDR_PER_BASE_NONE );  												// Address map for 16-bit set to none
	
	MAP_EPINonBlockingReadConfigure( EPI0_BASE, 0, EPI_NBCONFIG_SIZE_16, 0 ); 									// Configure non-blocking EPI reads

	MAP_GPIOIntTypeSet( GPIO_PORTP_BASE, GPIO_PIN_5, GPIO_BOTH_EDGES );											// Configure frame interrupt trigger on both edges
	IntRegister( INT_GPIOP5, epiIntHandler );																	// Register the requested interrupt handler
    MAP_GPIOIntClear( GPIO_PORTP_BASE, GPIO_PIN_5 );                                							// Clear interrupt before enabling
	MAP_GPIOIntEnable( GPIO_PORTP_BASE, GPIO_PIN_5 );															// Enable the requested GPIO interrupt
    MAP_IntEnable( INT_GPIOP5 );																				// Enable requested interrupt
}

static void enablePeripheral( uint32_t peripheralId )
{
    MAP_SysCtlPeripheralEnable( peripheralId );				// Enable requested peripheral
	while( ! MAP_SysCtlPeripheralReady( peripheralId ) );	// Wait until the enabled peripheral is ready
}

void epiIntHandler( void )
{
	MAP_GPIOIntClear( GPIO_PORTP_BASE, GPIO_PIN_5 );								        																											// Clear interrupt

	if( ! MAP_uDMAChannelIsEnabled( UDMA_SEC_CHANNEL_EPI0RX ) )																																			// If previous streaming data done
	{
		if( MAP_GPIOPinRead( GPIO_PORTP_BASE, GPIO_PIN_5 ) )																																			// If EPI frame rising edge
		{
			MAP_uDMAChannelTransferSet( UDMA_SEC_CHANNEL_EPI0RX, UDMA_MODE_BASIC, m_streamingData[ m_epiLoadPingPongInx ].spiAccess, (void *)(EPI0_BASE + EPI_O_READFIFO0), STREAMING_TOTAL_WORDS );	// Start DMA transfer from EPI0 RX			
			MAP_uDMAChannelEnable( UDMA_SEC_CHANNEL_EPI0RX );																																			// Enabling channel wiil issue a transfer request and data transfer begins
			MAP_EPINonBlockingReadStart( EPI0_BASE, 0, STREAMING_TOTAL_WORDS );																															// 
			m_epiLoadPingPongInx = ! m_epiLoadPingPongInx;																																				// Toggle the ping-pong index	
		}
		else																																															// Else, EPI frame fallsing edge
			m_epiDataRdy = true;																																										// Set EPI data ready flag
	}
	else 																																																// Else, channel is still enabled (busy)
		m_badStreamingEPIReadCnt++;																																										// Increment the bad EPI read counter		
}

  • The problem we are experiencing is that servicing the EPI data interrupt starves out the main thread resulting in the USB receive/transmit to stop operating. Therefore, we have been attempting to migrate the code to use uDMA to transfer the data from the EPI bus.

    Hi Patrick,

      Understand you are trying to use uDMA for EPI transfers. By default, USB has higher interrupt priority than EPI. A USB interrupt should preeempt EPI interrupt unless you disable interrupts when you are servicing the EPI ISR or you have reprogrammed the priority level between the two modules.

    1. Can't do uDMA transfer directly between EPI0 and SPI0. Therefore, our design uses uDMA to receive block of data from EPI bus into a buffer. When a full block of data has been received on the EPI bus, the data is transmitted using uDMA to SPI0 using received EPI buffer.

    This is correct. 

    2. Need to use the EPI clock signal to synchronize EPI reads with the FPGA. The EPI clock uses EPI31, therefore we need to reduce the EPI bus size to 24, 16, or 8. The uDMA transactions are limited to data sizes of 32, 16, or 8. Therefore, our EPI data bus needs to be reduced to 16-bits.

    Do you have a logic analyzer that you can confirm the EPI has the correct signals on the interface (e.g. clock, frame, rd/wr addr/data?

    3. Renamed the request-to-send signal sourced by the FPGA to frame signal. The frame signal rising edge triggers the PortP:5 interrupt to start the uDMA transaction to receive the data from the FPGA. The frame signal falling edge triggers PortP:5 which triggers the uDMA to stream the received data on SPI 0.

    Frame signal is a output from EPI, not an input. .Please find below description and the example timing diagram with Frame signal. Also refer to the datasheet for details. Note that per Frame description, the waveform of Frame is dependent on the WR/RD signals. As you can see in the example timing diagram, the Frame signal will follow RD signal. Perhaps you are referring to a custom Frame (acting as a trigger or enable) signal that is coming from FPGA. In that case, who do you do with the EPI Frame signal? You FPGA just ignores it? How does your FPGA know whether or not the reads by uDMA has finished or not? Or it just stays high for 50mS? What if uDMA does not finish the reads from FPGA in 50mS? In this case, you will be receiving only partial data as your falling edge Frame signal has already triggered the transfers to SPI. how would your FPGA know the transfers to SPI has completed before asserting the next rising edge of Frame signal? You will need to take this into account.

    FRAME Signal Operation
    The operation of the FRAME signal is controlled by the FRMCNT and FRM50 bits. When FRM50 is
    clear, the FRAME signal is high whenever the WR or RD strobe is high. When FRMCNT is clear, the
    FRAME signal is simply the logical OR of the WR and RD strobes so the FRAME signal is high during
    every read or write access, see Figure 11-22 on page 852.

    I will advice that you use divide and conqueror approach. First only implement uDMA transfer from EPI. Make sure all data are received in the buffer. Next, use uDMA to only transfer data from the buffer to SPI. When both are individually working, combine the two channels into the same application and work out the correct synchronization between the two channels so the reads (from EPI) will happen first before the writes (to SPI). 

  • Sorry for the confusion regarding the signal we called frame. This is NOT the EPI frame signal on EPIOS30 line. The signal we called frame, is sourced by the FPGA and received on the GPIO PortP:Pin5 configured as an interrupt. On the rising edge of the frame signal we want to trigger the start of uDMA receive using 16-bits EPI bus along with EPIOS31 configured as the EPI clock. The EPI clock is used by the FPGA to shift data onto the EPI bus, FPGA loads data on falling clock edge and micro-controller reads the EPI bus on rising clock edge. The falling edge of the frame signal is used to indicate when all data has been transmitted from the FPGA. This is used to trigger the uDMA transfer of the received EPI data buffer using SPI0. As for the suggested divide and conquer approach, we do have the SPI0 uDMA working. We are just concerned we did not configure the Tivaware functions correctly to start the EPI uDMA transfer.

    We connected a scope probe to EPIOS31 EPI clock signal and did NOT see it toggling, it is always high.

    We should NOT need to use the additional EPI handshake signals such as RD, WR, EPI FRAME since the FPGA is only transmitting using the EPI clock signal.

    Can you please confirm we are understanding how to configure the EPI and uDMA.

  • Hi,

    We connected a scope probe to EPIOS31 EPI clock signal and did NOT see it toggling, it is always high.

    This will be the first thing to diagnose the cause as without clock, your FPGA will not respond. Will you please take a look at this below post where Noah posted his working code for General Purpose mode albeit using CPU instead of uDMA to move the data from EPI. I will suggest you also do the same to have a working EPI using CPU first and later add the uDMA capability. 

    https://e2e.ti.com/support/microcontrollers/arm-based-microcontrollers-group/arm-based-microcontrollers/f/arm-based-microcontrollers-forum/1311611/tm4c129encpdt-using-epi-with-ti-rtos---configuring-epi-and-its-associated-gpio-for-general-purpose-mode?tisearch=e2e-sitesearch&keymatch=EPIConfigGPModeSet#

    I have also found the below code snippet that looks like a transfer from EPI to SSI is possible just by setting up one uDMA channel for EPI. 

    MAP_uDMAChannelControlSet( UDMA_SEC_CHANNEL_EPI0RX | UDMA_PRI_SELECT,
                                                            UDMA_SIZE_32 | UDMA_SRC_INC_32 |
                                                           UDMA_DST_INC_32 | UDMA_ARB_8 );

    MAP_uDMAChannelTransferSet( UDMA_SEC_CHANNEL_EPI0RX | UDMA_PRI_SELECT,
                                                           UDMA_MODE_AUTO, (void*)buf, (void*)(SSI0_BASE + SSI_O_DR),
                                                           size / 4);

    MAP_uDMAChannelSelectSecondary( UDMA_DEF_TMR1A_SEC_EPI0RX );

    MAP_uDMAChannelEnable( UDMA_SEC_CHANNEL_EPI0RX );