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.

CCS/EK-TM4C123GXL: High Frequency ADC Sampling

Part Number: EK-TM4C123GXL

Tool/software: Code Composer Studio

Howdy!

I am trying to configure the TIVA TM4C23G microcontroller to collect voltage samples at a frequency of 1 MHz.  I need to sample a pulse occurring at an arbitrary time using the ADC and store the data for later

analysis.  However, the sampling needs to be taken care of in the background by hardware as much as possible to free up the CPU for other tasks.

Additionally, it would be preferable if there was a way to obtain several data points before the pulse occurs.

I am currently thinking that this may be accomplished by using a timer interrupt to trigger sampling by the ADC and configuring the uDMA to collect the samples.  The pulse could be detected by an ADC interrupt,

which would call functions for data analysis or transmit the data over UART for processing elsewhere.  But I am unsure exactly how to configure the ADC to sample at such a high rate, and I am completely unfamiliar

with the uDMA.  Could someone shed some light on this for me?  Simple example code would also be extremely helpful.

Thank You in Advance!

Derek Janak59

  • Hello Derek,

    To get to that sampling rate, you almost certainly would need to use the uDMA to handle the ADC aspect. But that will be complicated to setup fully from scratch.

    If you aren't familiar with either module, I would suggest breaking the problem into two phases.

    Phase 1 would be to get the ADC working at a high speed, not sure if you'd crack 1MSPS but at least get above 500kSPS so you have confidence in the ADC aspect. Actually your post reminded me of another recent inquiry which was resolved on here for the same MCU and a lot of the example code was posted during the process. I think that this post would very helpful for you in the learning process: e2e.ti.com/.../631388

    Note that we also have example code for the ADC in the TivaWare examples under [Install Path]\TivaWare_C_Series-2.1.4.178\examples\peripherals\adc as well.

    Now then, Phase 2 would be to integrate the uDMA into the firmware to remove that overhead. That would get you good speed improvements as well. The uDMA piece is going to be a lot more complicated, and we don't have an example code for ADC+uDMA, but there are a lot of E2E posts which delve into the topic and many users posted snippets of their code. Actually the post I referenced above as a comment from community member Josue Pareja who posted a couple links to ADC+uDMA collateral.

    If you really want to understand the uDMA before delving into the ADC, we also have TivaWare examples for that, such as [Install Path]\TivaWare_C_Series-2.1.4.178\examples\boards\ek-tm4c123gxl\udma_demo - that won't have any ADC code, but it'd be good for initial learning.

    Also our documentation will be your friend throughout the process. While dense at times, both the device datasheet and the TivaWare documentation such as the Driverlib User Guide (SW-TM4C-DRL-UG-2.1.4.178.pdf) will provide a lot of relevant details.
  • Howdy Ralph,

    Thank you for your response.  I apologize for my late reply; I changed emails and had trouble accessing my E2E account.

    I have been working on the problem, and I seem to have the uDMA working.  I also appear to have achieved the 1MSPS sampling rate that I wanted.  The code is working for a continuous sampling case, but I am now trying to make it trigger 1024 samples using the external GPIO trigger.  However, there appears to be a problem with my ADC interrupt failing to clear.

    I am triggering the ADC via the external interrupt generated by pin B4.  I have removed all parts of my code related to the uDMA and sampling, and I am just focusing on the ADC interrupt.  The function appears to become stuck in the interrupt after the pin triggers it the first time, and the interrupt flag will not clear (ADCIntStatus returns 1).  Included below is the relevant code:

    void ADC0IntHandler()
    {
        uint32_t status;
        status = ADCIntStatus(ADC0_BASE, 0, true);
        ADCIntClear(ADC0_BASE, 0);
    
       //I tried this to make sure that there were enough cycles for the interrupt flag to clear.
      SysCtlDelay(16000000u / 3u);
    }
    
    void ConfigureADC(void)
    {
        ADCSequenceConfigure(ADC0_BASE, 0, ADC_TRIGGER_EXTERNAL, 0);
        GPIOADCTriggerEnable(GPIO_PORTB_BASE,GPIO_PIN_4);
    
        ADCSequenceStepConfigure(ADC0_BASE, 0, 0, ADC_CTL_CH0);
        ADCSequenceStepConfigure(ADC0_BASE, 0, 1, ADC_CTL_CH1);
        ADCSequenceStepConfigure(ADC0_BASE, 0, 2, ADC_CTL_CH2);
        ADCSequenceStepConfigure(ADC0_BASE, 0, 9, ADC_CTL_CH3);
        ADCSequenceStepConfigure(ADC0_BASE, 0, 4, ADC_CTL_CH4);
        ADCSequenceStepConfigure(ADC0_BASE, 0, 5, ADC_CTL_CH5);
        ADCSequenceStepConfigure(ADC0_BASE, 0, 6, ADC_CTL_CH6);
        ADCSequenceStepConfigure(ADC0_BASE, 0, 7, ADC_CTL_CH7 |ADC_CTL_IE | ADC_CTL_END);
    
        ADCSequenceEnable(ADC0_BASE, 0);
        ADCIntClear(ADC0_BASE, 0);
    }
    
    void ConfigureInterrupts(void)
    {
        IntMasterEnable();
    
        GPIOPadConfigSet(GPIO_PORTB_BASE,GPIO_PIN_4,GPIO_STRENGTH_2MA,GPIO_PIN_TYPE_STD_WPD);
        GPIOIntTypeSet(GPIO_PORTB_BASE,GPIO_PIN_4,GPIO_FALLING_EDGE);
    
        ADCIntEnable(ADC0_BASE, 0);
        ADCIntRegister(ADC0_BASE,0,ADC0IntHandler);
    
    }
    
    int main(void)
    {
        // Set the clocking to run directly from the crystal.
        SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN |
                SYSCTL_XTAL_16MHZ);
    
        // Initialize and setup the ports
        PortFunctionInit();
    
        ConfigureADC();
        ConfigureInterrupts();
    
        // Loop forever while the timers run.
        while(1)
        {
            status = ADCIntStatus(ADC0_BASE, 0, true);
        }
    }

  • Hello Derek,

    I am missing a few pieces of info still like the the PortFunctionInit function, and the ISR used for the GPIO trigger, it's hard for me to get a full picture without seeing those as well.

    Like, for example, it seems that all the SysCtlPeripheralEnable are not present but I can't tell if they are in the PortFunctionInit function or not.

    Additionally, not sure if this will be of help, but someone else on E2E posted code for GPIO triggered ADC: https://e2e.ti.com/support/microcontrollers/tiva_arm/f/908/p/430355/1543938#1543938

  • "to collect voltage samples at a frequency of 1 MHz. "

    read the errata first before you start, to make sure that the chip is capable of doing that.

    then put the chip to its paces and see if the performance is good for your application.

    DMA is the only way to go with that kind of speed.

    "Could someone shed some light on this for me? "

    read the datasheet and try to play with examples.
  • Derek Janak59 said:
    I am triggering the ADC via the external interrupt generated by pin B4.    The function appears to become stuck in the interrupt after the pin triggers it the first time, and the interrupt flag will not clear (ADCIntStatus returns 1).

    This is a "pure SWAG" - yet may prove a "reasonable" SWAG.     (one hopes)

    No evidence has been presented which reveals your  "PB4 GPIO Interrupt" to have been created,  then serviced - and cleared!      Indeed - you've well handled,  "ADC0's Interrupt Handler" - but NOT that for  PB4's GPIO!      While it is "unknown" (at least unclear) if such PB4 Interrupt proves helpful (or required) - your "entry" w/in the ADC0 Interrupt - and  then "Failure to Depart" - (may) indicate (some) linkage between  interrupts... (I note - NO OTHER "reasonable" theories - have been advanced!)

    To further  "promote my theory" - I present this "abstract" from the TM4C123 manual:    (note:  true copy follows:)

    10.2.2.1 ADC Trigger Source
    "Any GPIO pin can be configured to be an external trigger for the ADC using the GPIO ADC Control (GPIOADCCTL) register.    If any GPIO is configured as a non-masked interrupt pin (the appropriate bit of GPIOIM is set), and an interrupt for that port  is generated,  a trigger signal is sent to the ADC.

    If the ADC Event Multiplexer Select (ADCEMUX) register is configured to use the external trigger,   an ADC conversion is initiated.      See page 833."

    Indeed you've exploited, "PB4's  "ADC Triggering Capability"  (and good that) yet that  "MAY Not Excuse" the  (apparent)  ABSENCE  of  "PB4's handler & its servicing!"       (Maybe...)      (Note: SWAG Pennant hoisted - blowing hard Starboard...)

  • Howdy Ralph,

    I did not define an ISR for the GPIO trigger, and the PortFunctionInit() function is automatically generated by the TI pinmux utility. I do not have access to the PortFunctionInit code at the moment, but I will post it at the next opportunity. Other than that, the code shown represents my complete code, minus the uDMA.

    As Danny F suggested, I verified that the chip is capable of sampling at 1 MHz, and I was able to verify this in code when sampling continuously and triggering off of a timer. However, triggering off of the external pin has thrown a wrench into things.

    As cb1_mobile suggested, I did think that the failure to exit the ISR might be due to an asserted GPIO interrupt, so I tried to clear the GPIO interrupt flag on port B4 in a previous version of the code. This did not resolve my issue. I believe the function that I used was the following:

    uint32_t status = GPIOIntStatus(GPIO_PORTB_BASE, true);
    GPIOIntClear(GPIO_PORTB_BASE, status) ;

    Monitoring status with a watch expression, it remained zero even when the ADC interrupt was triggered. Further, the call to GPIOIntClear did not affect my problem.

    I also tried calling both the GPIOIntClear and ADCIntClear functions with 0xFFFF for the status parameter in the hope that this would clear all relevant interrupts, but this was unsuccessful as well.

    I have made sure that the event on the pin B4 only occurs once to trigger the interrupt the first time, so I doubt that it is reasserting the interrupt. To test this, I even tried grounding it following the trigger in order to verify that the floating pin was not continuously triggering interrupts.

    I am stumped, as are my coworkers. Please advise.

    Thank you,

    Derek
  • "However, triggering off of the external pin has thrown a wrench into things."

    if that's the case, you may want to step through the ISR, and count on how many cycles it takes to execute it, plus the isr overhead, to see if your isr is executing fast enough.

    to sample 1Msps on a 80Mhz mcu, you have to run through the isr in less than 80 ticks. with 20 or so ticks for the overhead, that means you have 60 ticks left. when you use the library functions from inside the isr, that may not be enough.

    I would try your current approach at lower speed to see if it works; if it does work at lower speed and not at higher speed, you know the problem. then try to speed up the isr as much as you can and roll your own rather than relying on the library.

    also, consider DMA. with ISR, the mcu has very little processing power left for anything else.
  • Thank you for responding to the suggestion to create - then properly service - the "ADC Triggering" Port B4 interrupt.

    I vote w/Danny in his suggestion of, "Reducing the ADC conversion rate." (I'd cut it in half - at minimum - for starters.) With the "facts at hand" - it is uncertain, "Just what, where, and when" your design is "breaking." Substantially slowing your conversion rate (temporarily) may enable the design to succeed - which best allows you to then, "selectively probe for weakness."

    I saw that PB4 and or "related" was set to, "Edge Trigger." Have you (really & recently) monitored "PB4's signal" - to insure that "One & only one Edge Pulse" (per desired conversion) arrives? It also proves useful to observe PB4's "waveshape" (good, clean, rapid rise/fall) as well as PB4's pulse width. (firm/I have noted a "slow transitioning" signal to (sometimes) FAIL as an "Edge Trigger.")

    Note too that "PB4" has (some) notoriety - perhaps your (temporary substitution) of another GPIO - may "highlight" any potential "limitations" which PB4 (may) introduce...
  • As this is a TI-run forum, I have to be careful in what I say - I have been warned for saying things TI employees dis-approve of.

    So I would suggest that you go back to what I recommended earlier : those words were carefully chosen. Read in between the lines and listen to what's not said.

    If you are lucky, some TI employees may provide you a short code piece that runs the ADC at 1Msps so you can experiment.

    Best of luck.

  • Poster Danny's (unaddressed) - most recent post - concerns.

    The suggestion of, "Slowing an MCU Operation" - to "Ease Task Success" - is IN NO WAY DEMEANING to (any) vendor!      Instead - it is a long, highly successful & well known "Diagnostic Technique" - which I know to be, "Standard Operating Procedure" - at both large & small firms - worldwide.

    The context of Danny's (most recent) writing IS unclear.    I've produced this writing so that a "Highly Proper & Appropriate" Diagnostic Method (slowing MCU execution) is  not (unfairly) rejected...

  • Hello Derek,

    Looking through your code further, I am wondering if perhaps this call needs to be shifted from your ConfigureADC function to the ConfigureInterrupts function. The ConfigureInterrupts function is where the GPIO pin type is set and I wonder if that is causing a conflict. Right now the main difference I am seeing in your implementation and the example code that (supposedly) works is the order of function calls.

    GPIOADCTriggerEnable(GPIO_PORTB_BASE,GPIO_PIN_4);

    I do agree with Danny and cb1 though, that setting this up for 1MHz sampling right away isn't the best approach. I'd suggest using a lower rate as well, and let's make sure you can trigger the ADC correctly as a whole, and then you can work to increase the speed.

    I'll try and run a reduced version of your code and the example I referenced before tomorrow to see what else could be going on.

  • Howdy All,

    I have previously tried to slow the execution of the ISR by including delays, while loops, and other code to slow execution.  The problem is not the execution speed of the ISR.  Also, this issue is not related to making the ISR go faster; it is to try to get the interrupt flag to clear.

    To reiterate: running the ADC at 1 Msps is no longer an issue;  it is clearing the external pin interrupt that is causing difficulty.  Additionally, I have been able to verify that PB4 is seeing only one edge.  Please read through my previous post to see what solutions I have already attempted; many of the suggestions that I have received are no different from my own debugging attempts as listed above.

    Included below is the PortFunctionInit() code as previously requested:

    void PortFunctionInit(void)
    {
        //
        // Enable Peripheral Clocks 
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC1);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    
        //
        // Enable pin PE1 for ADC AIN2
        //
        GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_1);
    
        //
        // Enable pin PE0 for ADC AIN3
        //
        GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_0);
    
        //
        // Enable pin PD3 for ADC AIN4
        //
        GPIOPinTypeADC(GPIO_PORTD_BASE, GPIO_PIN_3);
    
        //
        // Enable pin PE5 for ADC AIN8
        //
        GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_5);
    
        //
        // Enable pin PB4 for ADC AIN10
        //
        GPIOPinTypeADC(GPIO_PORTB_BASE, GPIO_PIN_4);
    
        //
        // Enable pin PD1 for ADC AIN6
        //
        GPIOPinTypeADC(GPIO_PORTD_BASE, GPIO_PIN_1);
    
        //
        // Enable pin PE2 for ADC AIN1
        //
        GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_2);
    
        //
        // Enable pin PB5 for ADC AIN11
        //
        GPIOPinTypeADC(GPIO_PORTB_BASE, GPIO_PIN_5);
    
        //
        // Enable pin PD0 for ADC AIN7
        //
        GPIOPinTypeADC(GPIO_PORTD_BASE, GPIO_PIN_0);
    
        //
        // Enable pin PD2 for ADC AIN5
        //
        GPIOPinTypeADC(GPIO_PORTD_BASE, GPIO_PIN_2);
    
        //
        // Enable pin PE3 for ADC AIN0
        //
        GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_3);
    
        //
        // Enable pin PE4 for ADC AIN9
        //
        GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_4);
    
        //
        // Enable pin PC5 for GPIOInput
        //
        GPIOPinTypeGPIOInput(GPIO_PORTC_BASE, GPIO_PIN_5);
    
        //
        // Enable pin PF1 for GPIOOutput
        //
        GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1);
    
        //
        // Enable pin PF3 for GPIOOutput
        //
        GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_3);
    
        //
        // Enable pin PF2 for GPIOOutput
        //
        GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_2);
    
        //
        // Enable pin PA0 for UART0 U0RX
        //
        GPIOPinConfigure(GPIO_PA0_U0RX);
        GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0);
    
        //
        // Enable pin PA1 for UART0 U0TX
        //
        GPIOPinConfigure(GPIO_PA1_U0TX);
        GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_1);
    }

    Thank you all for your help!

    Regards,

    Derek

  • Derek Janak59 said:
    Please read through my previous post to see what solutions I have already attempted; many of the suggestions that I have received are no different from my own

    Might you consider the following:

    • What you suggest - via the above - is that (many) "comb thru" multiple posts - in the attempt to,  "First FIND - then Glean fine detail" - in your  behalf.
    • Would it not be  far more effective  - and exhibit far greater, "helper care/concern" - if  YOU would gather (ALL of those past suggestions) - and highlight  them w/in your SINGLE - MOST RECENT POST?

    It is NOT my belief that those here have asked you to,  "Slow the execution speed of JUST  your ISR" - it is INSTEAD -  SLOWING the overall MCU's Speed of Code Execution (via the System Clock setting) - which impacts (most) everything - and which the experience of (several) here - believe  (may) prove illuminating.     (the current  (other) "attempts" have not (yet)  succeeded - thus this request...)

  • Hello Derek,

    Thank you for sharing that, I was able to run your code and I think I have figured out the issue.

    For interrupt enabling, it looks you need one more API call which doesn't seem to be present:

    IntEnable(INT_ADC0SS0);

    Once I added that in, looks like the INT clears successfully.

  • Howdy Ralph and Others,

    Thank you for all of your hard work trying to help me out. I am still having difficulty getting this code to work, and I have decided to try a different solution for the time being. I may revisit this issue at a later date, but I currently need to refocus on other areas of the code. I really appreciate all of your efforts on my behalf.

    Regards,

    Derek