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.

TM4C123GE6PM: ADC triggered by timer

Part Number: TM4C123GE6PM

Hi 

I want to make a simple program using the ADC module of my Tiva TM4C123G launchpad. The idea is to configure a Timer to interrupt in a certain period of time (I want to running it at 350kHz), and trigger a conversion. I already made the code, but there must be something wrong in the ADC config. that doesn´t allow me to make more than one conversion.

any suggestions is welcome

Here is the code:

#include <stdint.h>
#include <stdbool.h>
#include "inc/tm4c123gh6pm.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/sysctl.h"
#include "driverlib/interrupt.h"
#include "driverlib/gpio.h"
#include "driverlib/timer.h"
#include "driverlib/adc.h"

uint32_t muestra[1];


int main(void)
{
    uint32_t ui32Period;
    
    SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);

    
    SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER1);
    TimerConfigure(TIMER1_BASE, TIMER_CFG_PERIODIC);
    ui32Period = (SysCtlClockGet()/350000) /2 ;
    TimerLoadSet(TIMER1_BASE, TIMER_A, ui32Period -1);
    TimerControlTrigger(TIMER1_BASE, TIMER_A, true);
   
// ********** ADC Config  ********** //

    SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
    GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_3);

    // Select sequencer 3 (FIFO depth 1)

    ADCSequenceDisable(ADC0_BASE, 3);
    ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_TIMER, 0);

    //Only one step config:

    ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_CH0 | ADC_CTL_IE | ADC_CTL_END);
    ADCSequenceEnable(ADC0_BASE, 3);

    //ADCIntClear(ADC0_BASE, 3);

    //Interrupts enable

    IntEnable(INT_TIMER1A);
    TimerIntEnable(TIMER1_BASE, TIMER_TIMA_TIMEOUT);
    IntMasterEnable();
    ADCIntEnable(ADC0_BASE, 3);
    IntEnable(INT_ADC0SS3);
    TimerEnable(TIMER1_BASE, TIMER_A);


    while(1)
    {
    }
}



void Timer1IntHandler(void)
{
    // Clear the timer interrupt
    TimerIntClear(TIMER1_BASE, TIMER_TIMA_TIMEOUT);
    ADCIntClear(ADC0_BASE, 3);
    ADCSequenceDataGet(ADC0_BASE, 3, muestra);
  
}


  • Israel Rebolledo said:
    there must be something wrong in the ADC config. that doesn´t allow me to make more than one conversion.

    Believe each of the following to require (your) further review:

    • TimerConfigure(TIMER1_BASE, TIMER_CFG_PERIODIC);     Might this prove more appropriate?    TIMER_CFG_A_PERIODIC - Half-width periodic timer
    • DRL  User Guide advises:  "The timer module is disabled before being configured and is left in the disabled state."    You have either "violated that" or relied upon "Default behavior" - neither ideal.
    • You do not detail,  "Why you believe that, "Only one conversion occurs."     At your 350KHz  interrupt rate - data is "machine-gunned" into "muestra[]" - and may not be "read/processed" in a timely fashion.
    • You have provided for,  "ADCIntEnable(ADC0_BASE, 3);" ...  yet that ADC Interrupt is,  "Nowhere revealed."    Such IS required...

    May I suggest that you reduce your data rate to a comfortable, "Human Readable Measure" (perhaps once per Second) - which allows better insight into the reality of your,  "Single Reading."

  • If you use the timer to trigger the ADC, it is actually the ADC interrupt routine that should be called. A while back I did an example with a timer interrupt generating an ADC conversion that used the uDMA to put the conversions into buffers. I have attached that CCS project in hope that it helps./cfs-file/__key/communityserver-discussions-components-files/908/8154.ADCwDMA.zip

  • Dear Bob, yo were right about the ADC interrupt, I appreciate a lot the example, actually I wanted to make a simple example reading the adc and then move forward to the uDMA transfer since I do not understand a lot how it works. But the example make it much easy.
  • Dear cb1_mobile

    First thanks a lot for the advices. I have check and fixed each the points you mentioned. Now it is working (I think) the main problem lied in the ADC Interrupt (rookie mistake), I set a breakpoint to watch the variable and it update the value correctly.

    But since I need it goes to 350kHz I am toggling a pin to measure it in the oscilloscope to test, two issues shows up

    a. when I am debugging the frecuency falls to ~10Hz, perhaps is normal, make sense to me that the refresh interval of the CCS is in milliseconds and it affects. When I stop the debug session the frequency is "normal" and goes to (b).

    b. The frecuency goes to 388kHz instead of 350kHz, could be that I am not toggling the pin properly?

    Here is the code with the changes

    uint32_t muestra[1];

    void ADC0IntHandler(void);

    void main(void)
    {
    uint32_t ui32Period;

    SysCtlPeripheralReset(SYSCTL_PERIPH_ADC0);
    SysCtlPeripheralReset(SYSCTL_PERIPH_TIMER1);

    SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN |
    SYSCTL_XTAL_16MHZ);

    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER1);

    // ***Timer Initialization***

    TimerConfigure(TIMER1_BASE, TIMER_CFG_A_PERIODIC);
    ui32Period = (SysCtlClockGet()/350000) /2 ;
    TimerLoadSet(TIMER1_BASE, TIMER_A, ui32Period -1);
    TimerEnable(TIMER1_BASE, TIMER_A);
    TimerControlTrigger(TIMER1_BASE, TIMER_A, true);

    // ***ADC Initialization***

    SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); // Enable clock to the ADC module 0
    SysCtlDelay(10);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
    GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_3); // AIN0

    IntDisable(INT_ADC0SS3);
    ADCIntDisable(ADC0_BASE, 3);
    ADCSequenceDisable(ADC0_BASE, 3);

    ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_TIMER, 0);

    ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_CH0 | ADC_CTL_IE |
    ADC_CTL_END);

    ADCSequenceEnable(ADC0_BASE, 3);
    ADCIntClear(ADC0_BASE, 3);
    ADCIntEnable(ADC0_BASE, 3);
    IntEnable(INT_ADC0SS3);

    IntMasterEnable(); // Enable processor interrupts

    while(1)
    {
    }
    }

    void ADC0IntHandler(void) {

    // Clear the interrupt status flag.
    ADCIntClear(ADC0_BASE, 3);

    ADCSequenceDataGet(ADC0_BASE, 3, muestra);

    if(GPIOPinRead(GPIO_PORTF_BASE, GPIO_PIN_1))
    {
    GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0);
    }
    else
    {
    GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 2);
    }
    }
  • Greetings and thank you.

    Israel Rebolledo said:
    The frequency goes to 388kHz instead of 350kHz, could be that I am not toggling the pin properly?


    That's both a valuable observation - AND - a strange one!     When you "impose a toggle function" - it is expected that such would, "Add Execution Time" - thus 'LOWERING'  your frequency - NOT 'INCREASING' it - don't  you agree?     Is it possible that you've "Reversed your reporting results" - and that "Adding the GPIO Toggle" - has indeed yielded the (lower) frequency?     A faster means of "GPIO Toggle" is via the use of an 'X-OR' family Operator...

    Beyond that - the clock source for your timer - (to me) is unspecified.    I usually avoid the internal (not especially precise) oscillator.   (love how marketing 'over-promotes!')     That difference in frequency is around 11% - you'll have to check (if you employed the internal oscillator) to see if that 11% is "in range."

    I've undergone dental surgery earlier this morning (not that I'm normally - less loopy) - but noted your, "Divide of SysClock by 350K - and then (again) by 2.    Perhaps - due to drug influence - I find that "divide by 2" suspect...    (meaning your frequency should be even higher!)

    Has your scope been recently calibrated - and as I've written (often) here - have you removed the scope's Ground Probe - so that a proper HF Measurement (minus over & under-shoot) can be achieved?   (you attach a very short wire to the probe's grounded "barrel" - which greatly reduces such over/under-shoot.)

  • I also wouldn't read the port before writing but maintain a simple flag.  Or even better set high at the beginning and low at the end, that gives you multiple measurements

    • Time between interrupts
    • Time spent in the interrupt
    • %CPU used (calculated from first two)
    • Jitter in frequency and processing time

    With a scope that can trigger on pulse width you can also look for missing samples.

    That divide by 2 looks suspect to me as well, I don't remember there being a factor of two prescale in the timers.

    Robert

  • Greetings,

    Returns the MASTER - we've "Sent out the hounds" - Two of Five returned (frost-bitten paws)!

    Three of your (neat) points I'll grant you - yet the "X-OR" toggle - for sure - executes faster... (should that be an objective - otherwise - your 4 'function detection' explains why (you) receive the (proper) BIG Bucks...

    Note that your 'frozen tundra' yielded ... 77°F (best kind of degrees) - followed by ... 31°F - the (very) next day. Hard to find (anyone) not made Sick. OH Canada - indeed!
  • Agree with cb1_mobile, those are total punchlines. I did what you recommend and the frequency has no issues now. The divide by 2 was also wrong, I left that division from a workbook's example, it was needed to get 50 per cent of the interrupt.
  • I will check the clock source instruction, although I have not seen it on the board examples that is why I relied on the internal oscillator. Many thanks!
  • If you employ the "internal" oscillator - recognize that "precision" may be 'more in the (accepting) eye of marketing' - than the cold stare of a "precision seeking" Client-User...