I am currently working on uDMA with ADC sampling. The following code is an attempt at adapting and simplifying an example I have followed for use on the Tiva C TM1294XL.
The goal is to simply retrieve the ADC samples so I can collect a few before sending them through Ethernet. This example is to be attached to a larger project where I've already created an Ethernet module. I am using uDMA so I can achieve 1M samples per second.
I would like the ADC input to be on GPIO PE0 and the output to be either a printout or an obvious buffer containing these samples.
The current state of this program is that it gets to the for(;;) loop once, then crashes (entering loader_exit() ). My prediction is that I have not set the uDMA up correctly. Any assistance or useful advice to what might be missing or incorrect would be highly appreciated.
#include <stdbool.h> #include <stdint.h> #include <string.h> #include <stdio.h> #include "inc/hw_ints.h" #include "inc/hw_memmap.h" #include "inc/hw_adc.h" #include "inc/hw_types.h" #include "inc/hw_udma.h" #include "inc/hw_emac.h" #include "utils/uartstdio.h" #include "utils/ustdlib.h" #include <xdc/runtime/System.h> #include "driverlib/debug.h" #include "driverlib/gpio.h" #include "driverlib/interrupt.h" #include "driverlib/pin_map.h" #include "driverlib/rom.h" #include "driverlib/sysctl.h" #include "driverlib/uart.h" #include "driverlib/adc.h" #include "driverlib/udma.h" #include "driverlib/emac.h" //***************************************************************************** // // The error routine that is called if the driver library encounters an error. // //***************************************************************************** #ifdef DEBUG void __error__(char *pcFilename, uint32_t ui32Line) { } #endif #define TXDESC_SIZE (4) #define EMAC_BUF_SIZE (1536) typedef uint16_t adc_sample_t; #define ADC_SAMPLE_BUF_SIZE (1024/sizeof(adc_sample_t)) uint8_t emacBufTx[EMAC_BUF_SIZE * TXDESC_SIZE] __attribute__(( aligned(4) )); // <-- Stores ADC samples here uint32_t tx_adc_index = 0; // feed buffer pointers to the ADC uDMA descriptors in order // // the ADC data is transferred by DMA directly into the EMAC buffers // when the ADC interrupt fires, it calls tx_EMAC() to have the EMAC immediately send the data out // // get_ADC_buf() uses tx_adc_index which stores the position of the ADC uDMA descriptor uint8_t * get_ADC_buf() { uint32_t i = tx_adc_index; tx_adc_index = (i + 1) & (TXDESC_SIZE - 1); return &emacBufTx[i * EMAC_BUF_SIZE]; } // feed ethernet frames to the EMAC // // tx_EMAC() uses txdesc_index which stores the position of the EMAC DMA descriptor void tx_EMAC() { /* Will expand this to send values through ethernet in packets, right now I just want to values are arriving */ /* Try to print out ADC values */ System_printf("ADC value: %d/n", emacBufTx[tx_adc_index * EMAC_BUF_SIZE]); System_flush(); } uint32_t adc_complete_count = 0; // uDMA descriptors used by the hardware are stored in this table // uDMAChannel* functions write to this table uint32_t udmaCtrlTable[1024/sizeof(uint32_t)] __attribute__(( aligned(1024) )); void uDMAErrorHandler() { System_printf("uDMAErrorHandler\n"); System_flush(); } void ADCprocess(uint32_t ch) { if ((((tDMAControlTable *) udmaCtrlTable)[ch].ui32Control & UDMA_CHCTL_XFERMODE_M) != UDMA_MODE_STOP) return; // store the next buffer in the uDMA transfer descriptor // the ADC is read directly into the correct emacBufTx to be transmitted uDMAChannelTransferSet(ch, UDMA_MODE_PINGPONG, (void *)(ADC0_BASE + ADC_O_SSFIFO0), get_ADC_buf(), ADC_SAMPLE_BUF_SIZE); adc_complete_count++; tx_EMAC(); } void ADCseq0Handler() { // this interrupt handler is optimized to use as few CPU cycles as possible // it must handle ADC_SAMPLE_BUF_SIZE samples in 1/125000 seconds // // optimizations include: // 1. choosing ADC_SAMPLE_BUF_SIZE to minimize overhead // 2. replacing calls to * functions with raw memory accesses (less portable) *(uint32_t *) (ADC0_BASE + ADC_O_ISC) = ADC_INT_DMA_SS0; // optimized form of ADCIntClearEx(ADC0_BASE, ADC_INT_DMA_SS0); ADCprocess(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT); // <-- Use primary control structure, why both? ADCprocess(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT); // <-- Use alternate control structure } /* * @brief Initialise uDMA for ADC * @params sysclock : System Clcok * @retval None */ void main() { #define ADC_TARGET (8*1000*1000) // 120 MHz/7 = about 17 MHz = 1.07143 MSps // Set System clock to 120MHz ui32SysClkFreq = SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480), 120000000); /* Added from original main */ SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); // Enable GPIO Port E GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_0 ); // Set Pin type to Analog IN SysCtlDelay(1); IntMasterEnable(); /* Added from adc_init() */ uint32_t div; if (sysclock < 2*ADC_TARGET) { div = ADC_CLOCK_SRC_PLL; } else { div = ADC_CLOCK_SRC_PLL | (((sysclock + ADC_TARGET - 1) / ADC_TARGET - 1) << 4); } #undef ADC_TARGET *(uint32_t *) (ADC0_BASE + ADC_O_CC) = div; ADCSequenceConfigure(ADC0_BASE, 0 /*SS0*/, ADC_TRIGGER_PROCESSOR, 3 /*priority*/); // SS0-SS3 priorities must always be different ADCSequenceConfigure(ADC0_BASE, 3 /*SS3*/, ADC_TRIGGER_PROCESSOR, 0 /*priority*/); // so change SS3 to prio0 when SS0 gets set to prio3 ADCSequenceStepConfigure(ADC0_BASE, 0 /*SS0*/, 0, ADC_CTL_TS); // ADC_CTL_TS = read temp sensor ADCSequenceStepConfigure(ADC0_BASE, 0 /*SS0*/, 1, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE, 0 /*SS0*/, 2, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE, 0 /*SS0*/, 3, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE, 0 /*SS0*/, 4, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE, 0 /*SS0*/, 5, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE, 0 /*SS0*/, 6, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE, 0 /*SS0*/, 7, ADC_CTL_TS | ADC_CTL_END | ADC_CTL_IE); // ADC_CTL_IE fires every 8 samples ADCSequenceEnable(ADC0_BASE, 0); uDMAEnable(); uDMAControlBaseSet(udmaCtrlTable); ADCSequenceDMAEnable(ADC0_BASE, 0); // disable some bits uDMAChannelAttributeDisable(UDMA_CHANNEL_ADC0, UDMA_ATTR_ALTSELECT /*start with ping-pong PRI side*/ | UDMA_ATTR_HIGH_PRIORITY /*low priority*/ | UDMA_ATTR_REQMASK /*unmask*/); // enable some bits uDMAChannelAttributeEnable(UDMA_CHANNEL_ADC0, UDMA_ATTR_USEBURST /*only allow burst transfers*/); // set dma params on PRI_ and ALT_SELECT uDMAChannelControlSet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT, UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_128); uDMAChannelControlSet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT, UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_128); uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG, (void *)(ADC0_BASE + ADC_O_SSFIFO0), get_ADC_buf(), ADC_SAMPLE_BUF_SIZE); uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG, (void *)(ADC0_BASE + ADC_O_SSFIFO0), get_ADC_buf(), ADC_SAMPLE_BUF_SIZE); IntEnable(INT_ADC0SS0); ADCIntEnableEx(ADC0_BASE, ADC_INT_DMA_SS0); uDMAChannelEnable(UDMA_CHANNEL_ADC0); /* Added from original main */ ADCSequenceConfigure(ADC0_BASE, 0 /*SS0*/, ADC_TRIGGER_ALWAYS, 3 /*priority*/); // <-- Assume Link Up uint32_t count = 0; for (;;) { count++; /* Loops here every cycle that the link is up */ if (count < 0x7fff) continue; // Check if count is up to 32767 = 15bits all 1s count = 0; adc_complete_count = 0; } }