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.

Is there a better way to code the interrupt? ADC



The code below works fine as I did manage to get the ADC to work. I set to continuously read from the analog input of PE1. But I still use an if statement to poll the status of the pin.

PE1_Dig_Input = ADC1SS3FIFO;
if(PE1_Dig_Input >= 1000 && PE1_Dig_Input < 2000

Am i still polling?

#define SYSCTL 0x400FE000U //this is the base HEX address for System controller
#define HB_GPIOE_BASE 0x4005C000U //Hex address for High performance Bus PORTE
#define HB_GPIOF_BASE 0x4005D000U //Hex Address for High performance Bus for PORTF
#define VTABLE_BASE 0xE000E000U //Hex address for Vector Table for Interrupts
#define ADC1_BASE 0x40039000U //No one wants to see the number 0x40039000 over and over again

//some offset for GPIO addresses defined to make reading the code easier
#define RCGCGPIO_OFFSET 0x608U #define GPIOHBCTL_OFFSET 0x06CU #define GPIO_DEN_OFFSET 0x51CU #define GPIO_DIR_OFFSET 0x400U #define GPIO_DATA_OFFSET 0x3FCU // //All this code is for ADC particularly for ADC1 Sample Sequencer 3 #define ADC_OFFSET 0x638U // This offset needs to be #define AFSEL_OFFSET 0x420U #define GPIO_AMSEL_OFFSET 0x528U #define ADCACTS_OFFSET 0x000U #define ADCEMUX_OFFSET 0x014U #define SSPRI_OFFSET 0x020U #define ADCT1MUX3_OFFSET 0x0A0U #define ADCSSCTL3_OFFSET 0x0A4U //this offset is for initializing end of sequence and enable raw interrupts #define ADCIM_OFFSET 0x008U #define ADCISC_OFFSET 0x00CU //ADC interrupt service clear #define ADC1SS3FIFO_OFFSET 0x0A8//What I primarily know about FIFO is that its where ADC info is stored #define ADC1SS3_Handler_OFFSET 0x104U #define RCGCGPIO_PORT (*((unsigned int*)(SYSCTL + RCGCGPIO_OFFSET))) //this enables a GPIOPORT of my choice, but to be clearer I have the intentions of for PORTF #define GPIOHBCTL_PORT (*((unsigned int*)(SYSCTL + GPIOHBCTL_OFFSET))) //enables me to use High performance Bus #define HB_GPIOEDEN (*((unsigned int*)(HB_GPIOE_BASE + GPIO_DEN_OFFSET))) //Digitally enable AHB_PORFTE to be used #define HB_GPIOEDIR (*((unsigned int*)(HB_GPIOE_BASE + GPIO_DIR_OFFSET))) // I'll have to enable the HBGPIOE to input #define HB_GPIOE_AMSEL (*((unsigned int*)(HB_GPIOE_BASE + GPIO_AMSEL_OFFSET))) #define GPIOAFSEL_E (*((unsigned int*)(HB_GPIOE_BASE + AFSEL_OFFSET))) #define ADC1_ACTSS3 (*(unsigned int*)(ADC1_BASE + ADCACTS_OFFSET))//use sample sequence 3 #define ADC1EMUX (*(unsigned int*)(ADC1_BASE + ADCEMUX_OFFSET)) #define ADC1SSPRI (*(unsigned int*)(ADC1_BASE + SSPRI_OFFSET))//sample sequence priority #define ADC1SSMUX3 (*(unsigned int*)(ADC1_BASE + ADCT1MUX3_OFFSET))//use analog input to determine what to add in #define ADCSSCTL3 (*(unsigned int*)(ADC1_BASE + ADCSSCTL3_OFFSET)) #define ADCSS3IM (*(unsigned int*)(ADC1_BASE + ADCIM_OFFSET)) #define ADCISC (*(unsigned int*)(ADC1_BASE + ADCISC_OFFSET)) #define ADC1SS3FIFO (*(unsigned int*)(ADC1_BASE + ADC1SS3FIFO_OFFSET)) #define ADC1SS3_Handler_EN (*(unsigned int*)(VTABLE_BASE + ADC1SS3_Handler_OFFSET)) #define GPIOAFSEL_F (*((unsigned int*)(HB_GPIOF_BASE + AFSEL_OFFSET))) #define HB_GPIOFDEN (*((unsigned int*)(HB_GPIOF_BASE + GPIO_DEN_OFFSET))) //Digitally enable AHB_PORFTE with some Data it can use #define HB_GPIOFDIR (*((unsigned int*)(HB_GPIOF_BASE + GPIO_DIR_OFFSET))) #define HB_GPIOFDATA (*(((unsigned int*)(HB_GPIOF_BASE + GPIO_DATA_OFFSET)))) #define RCGCADC (*(unsigned int*)(SYSCTL + ADC_OFFSET)) #define SetEn_0_31_OFFSET 0x100U //enables vector Interrupt numbers from 0 to 31, for this project our vector interrupt number for timer0A is 19 #define RED (1U << 1) #define BLUE (1U << 2) #define GREEN (1U << 3) volatile static unsigned long int PE1_Dig_Input = 0; void ADC1SS3_Handler ( void ) { ADCISC = (1U << 3); //clear interrupt before using it } int main() { RCGCADC = (1U << 1); //enable me to use ADC (X) RCGCGPIO_PORT = (1U << 4) | (1U << 5); // I've enabled GPIOPORTE and PortF aswell (X) GPIOHBCTL_PORT = (1U << 4) | (1U << 5); // High performance Bus PORTE and PortF (X) HB_GPIOFDEN |= (RED | BLUE | GREEN); // enabled RED HB_GPIOFDIR |= (RED | BLUE | GREEN); // I've set RED as an output GPIOAFSEL_F = 0x00U; //This is for ADC, using PORTE1 HB_GPIOEDIR = (0U << 1); // HBGPIOE_Pin_1 is now an input, shifting 1 bits to the left indicates I wish to use HBGPIOE1 as an input (X) GPIOAFSEL_E = (1U << 1); //This is the Alternative function select, it allows me to use PE1 (X) HB_GPIOEDEN = (0U << 1); //using HB_GPIOE 1 and disabling the Digital Enable (X) HB_GPIOE_AMSEL = (1U << 1); //analog mode select, I shifted 1 to the left becauseI wanted to use PE1, which has analog input of 2, so 1 << 1 = 10 which 2 in binary (X) ADC1_ACTSS3 = (0U << 3);//clear the bits (X) ADC1EMUX |= (0xF << 12); //This is the trigger select 0xF mean continous(X) ADC1SSPRI = (0x3U << 12); //(X) supposed to be lowest priority ADC1SSMUX3 = 2; ADCSSCTL3 = 0x6;//0x6 or 3'b110 (X) ADCSS3IM = (1U << 3); //enable my type of interrupts to be used (X) ADC1_ACTSS3 |= (1U << 3); //enable analog digital converstion Sample sequencer 3 ADCISC = (1U << 3); //clear interrupt before using it ADC1SS3_Handler_EN = (1U << 19); while(1) { PE1_Dig_Input = ADC1SS3FIFO; if(PE1_Dig_Input >= 1000 && PE1_Dig_Input < 2000) HB_GPIOFDATA = GREEN; else if(PE1_Dig_Input < 1000) { HB_GPIOFDATA = RED; } else if (PE1_Dig_Input > 2000 && PE1_Dig_Input < 3000) { HB_GPIOFDATA = BLUE; } else if (PE1_Dig_Input > 3000 && PE1_Dig_Input < 4000) { HB_GPIOFDATA = BLUE|RED; } else if (PE1_Dig_Input > 4000) { HB_GPIOFDATA = BLUE|GREEN; } } }
  • Don't use DRM, use the TIVAWare calls.

    As near as I can tell all you do in the interrupt is flag that the interrupt is complete. You don't actually read the A/D values.

    In main you never check to see if there are A/D values to read. Not only are you polling but you are reading values that could well be nonsense.

    Robert
  • Ok I can start using tivaware calls. I heard though though that programming like this gives better performance?

  • Hello Sunny,

    All the points the Robert mentioned are very important.

    I wanted to highlight the DRM point that he raised. It is not a good practice to access the registers or memory directly. Please use TivaWare APIs. It is not feasible to maintain even a reasonably size project written in DRM and it is a nightmare to debug, both for the person who has written it and for others.

    Another point on your code is to use as many comments as possible. It will help in debugging and maintaing the code.

    Thanks,
    Sai
  • Sunny Chan said:
    I heard though though that programming like this gives better performance?

    Several points

    • Have you measured and determined if you need extra performance? If not you are wasting time and resources on useless frills.
    • How much extra time have you budgeted for duplicating TIVAWare functionality?
    • The performance improvement possible is rather small. The function call overhead on these processors is small to begin with and less on leaf function (those that call no other functions)
      • When you do need performance (measure first), you will likely find that except in quite restricted subsets it's not going to be easy to improve on using the TIVAWare library.
      • And this is why you measure. You need to determine if you are improving and how much. Any improvement might be so small as to make the extra effort involved worthless.
    • You don't benefit from library improvements

    DRM is a useful addition to TIVAWare for adding capabilities not supported or in specific cases perhaps improving performance (where measurements indicate).

    Robert

  • Thank you for the helpful response. I'll probably start using the Tivaware's API, I'm starting with the wikibook. For future cases when I write interrupt handlers, do I put the if statements inside my interrupt handlers? 

  • The interrupt should service the peripheral. In the case of the A/D that would mean acknowledging the interrupt and reading the values, storing the results somewhere, perhaps filtering them.

    While you could do most of your processing in the interrupt that is usually not recommended. It makes the interrupts very long and it becomes difficult to manage timing. Therefore the usual method is to do your processing on the interrupt result outside of the interrupt.

    You current case is quite simple and so either method works well. The problems show up with larger more complex projects.

    Normally if you had something doing a check similar to what you show, it would run off of a timed loop. Every x ms it would make the comparisons to the latest values and update the outputs as required. If human visuals are all that's required then x could easily be 50. If this was something more like a current limit you might move the comparison into the interrupt for faster response, the side effect of doing so is that other interrupts could well be delayed.

    Robert
  • Ahh! I did write a interrupt where I blinked my onboard LED red every 1 second.
    Are you suggesting I program a general purpose timer to check the status of my ADC pin? My goal is try my best to write an interrupt driven program.

    I apologize as I'm a bit new to embedded systems. Correct me if I'm wrong but could an external interrupt help in this situation?
  • Sunny Chan said:
    My goal is try my best to write an interrupt driven program.

    Why?

    Interrupts are a tool, not a goal.

    Sunny Chan said:
    Are you suggesting I program a general purpose timer to check the status of my ADC pin?

    No. I'm suggesting you match your program structure to the problem. Run the A/D interrupt as frequently as necessary to capture your signal. Run your output at a rate suitable for the output.

    Presumably you have other logic than this that you are planning on implementing. It will also have it's own timing requirements. That is why the usual recommendation is to keep interrupts short and do most of the work outside of the interrupts. The obvious choices for you are a super-loop polling loop where you use interrupts to perform time sensitive I/ (A/D input and filtering, ticking clock, serial I/O etc...) while performing the processing in the polling loop as required; or an RTOS, like Free-RTOS, where again the interrupts do the time sensitive I/O and the various threads perform the bulk of the processing. The RTOS gives you the advantage of inter-process communication and control over priorities.

    It is possible to write an interrupt driven run to completion tasking system on these processors but that's a bit advanced for your current expertise I think.

    Robert

  • Hi Sunny,

    Looking at your DRM code; notice have not configured MOSC or assigned ADC1 a sample rate clock source. Have recently myself attempted HWREG calls to SYSCTL registers to configure MOSC in linear procedure was daunting.

    Good reason to start with Tivaware calls to first configure the main clock source, assign ADC1 a sample rate clock source and divisor for 1Msps/15Mhz or 2Msps/30Mhz. After configuring MOSC make calls to enable GPIO/ADC1 peripherals, set sleep & clock gating modes. Typically SYSCLK derived from PLL clock divided down is ADC/GPIO peripherals main clock source (POR default) when peripherals are enabled.

    All good reasons to start basic study the examples in Tivaware bundle as Robert & Sai mentions.
  • The reason why I want to have a interrupt driven program is to conserve as much processing power from the launchpad as possible. I'd figure it'd be good best practices and saves the resources to do other things. Agreed, interrupts are tools and I'd like to try to use them, I guess that's my goal haha.

    I'm not sure what you mean by super polling, and I'm unclear how you filter A/D. The closest thing I can think of could be a hardware solution? But as I'm new is there much point into writing a interrupt driven program or is it the same as with DRM?(Matching the program structure to the problem?) as I've had the overall impression that it was important to make the most out of hardware.

    Thank you Robert this has been enlightening, as for writing a interrupt driven run I'm willing to try to approach it, even if it's just a small program so I can get it in as habit. 

    Sunny

  • Hey BP thanks for the response. It looks like your right, I didn't configure the MOSC, and I'm wondering right now what would be the difference if I did(genuinely curious). Totally agreeing with you on using the Tivaware calls, atleast for the beginning.



    I've set the ADC to continuously sample. -> ADC1EMUX |= (0xF << 12);
    I'm assuming I've given it some kind of clock this way?
  • Hi Sunny,

    >I'm assuming I've given it some kind of clock this way?

    That directive only does what it says it does, no more than that. How your MCU is working without setting the MOSC likely using default oscillator, (TM4C129xx) PIOSC=16Mhz. Most important to configure MOSC/PLL clock sources for a SYSCLK main source for proper 1-2MSps ADC0/1 -- refer to datasheet main clock tree, ADC section. Again import and or review example projects in the Tivaware library bundle, CCS makes it easy to program your launch pad and run the debug emulator, view peripheral registers, Runtime interrupt actions etc..

    http://software-dl.ti.com/tiva-c/SW-TM4C/latest/index_FDS.html

  • Sunny Chan said:
    I'm not sure what you mean by super polling

    This is likely to be a long reply. I'm going to try and summarize the various tasking approaches

    Loop

    The most basic and simplest executive. A simple loop that check inputs, process them and produces outputs.

    Inter-Process Communication

    There's really only one process so there isn't any IPC. Much information is kept is global variable accessible to all parts of the system

    Pros

    Simple. Easy to use, easy to create. All you need is to have a working startup and there isn't anything left to write.

    Only single stack required.

    Cons

    Doesn't scale. If the processing for some input is lengthy it block further checking of inputs and setting of output. This is dealt with in an adhoc fashion by inserting checks in the middle of other processing (additional input checking etc...). This quickly become a tangled mess.

    Pausing is difficult

    Adding Interrupts

    Because of the timing issues of a loop interrupts are often added to deal with peripherals that need a low-latency response. This turns into two forms of interrupt processing

    Interrupt Light

    The interrupts are short and do minimal processing. They might set outputs (for instance shut down in response to a limit) but that will be kept to a minimum. Most interrupts simply service the peripheral, providing it with the next input and/or buffering its output for the main program.

    There may still be overflows because the latency of the processing in the main loop will delay the servicing of the interrupts inputs and required outputs.

    Interrupt Heavy

    Also known as foreground/background.

    Most of the processing is done in the interrupts, the main process is either idle or does slow work that does not fit well in an interrupt (like updating EE).

    Now because there is so much work done in the interrupts they start blocking each other and increasing the time from when the interrupt is flagged to when the interrupt routine starts (latency). This can be partially dealt with using interrupt priorities but if you start doing the analysis required for that there is another, I think superior, approach; the Run To Completion Executive. More on that later

    Example

    Most low to Mid-range PLCs use this model for the user programmable portion. The logic runs in a simple loop, it starts at the beginning and runs in a straight line until it reaches the end and then simply goes back to the beginning.

    Super Loop

    An effort to bring order to the loop is the super loop. The basic concept is to break the program up into separate elements and then each of these elements is broken into small pieces of code that only take a short amount of time. The main portion of the program then simply scans the inputs making them globally available and them calls each element in turn. Each element then executes only one of the small pieces of code before returning to the main.

    Inter Process Communication

    Communication between elements (at this point we do have separate processes, I've just been calling them elements) is fairly straight forward.  You just use global variables.

    Pros

    Still fairly simple and does not require an executive

    Single stack

    Independent processes with private memory are possible

    Fits naturally with State Chart processes

    Cons

    Timing is hard to maintain, breaking each process into small pieces is difficult if the timing doesn't fall at natural boundaries but, say, in the middle of a nested loop.

    Easy for a single process to block all others.

    Processes have to maintain their state information between successive calls.

    Prioritization of processes is difficult

    Pausing is difficult

    Adding Interrupts

    Super loop suffers from the same timing issues as loop but not as severe until the programmer(s) lose control of the breaking of each process into pieces. Interrupts play the same role in a super loop as in the interrupt light version of the loop. An interrupt heavy version is unusual simply because there is usually not enough reason to add that kind of complexity to the background process

    Cooperative Tasking

    An improvement on the super loop is cooperative tasking. Now each process has its own stack and the program switches between processes by saving the current position of one process on its stack and switching to a second stack and recovering its position from its stack. This switching is done at controlled points in the program either by an explicit call to a yield or a call to a support function that yields (such as pauses, or waiting on a buffer or ...). Cooperative executives can come with or without priority schemes to help schedule processes.

    Pros

    Each process has its own stack, there is no need to explicitly remember its current state that is now the responsibility of the yield function.

    Can be quite efficient

    If the executive supports priorities much of the interrupt processing can be split off into a process improving the interrupt latency.

    Cons

    Added overhead of an executive

    Need to sprinkle yields to maintain timing, this can become a maintenance issue. Insufficient yields may increase the latency for a task, too may adds extra overhead.

    Pauses and similar timing are now (usually) part of the executive.

    Multiple stacks

    Inter Process Communication

    Usually the executive support several inter process communication types (FIFOs, Semaphores, Buffers, etc...) These simplify communication between processes and now a process may simply wait for information to arrive before running.

    Adding Interrupts

    Timing issues are often less severe than with a super loop but the same basics drive the use of an interrupt light approach. Communication with the processes in the tasking environment will require special care, often using a restricted subset of the IPC methods.

    Interrupt heavy techniques will usually make the systems timing characteristics poorer.

    Examples

    Novell Netware's Server and early versions of MS Windows were based on cooperative tasking.

    Preemptive Tasking

    The next step is a move to preemptive tasking.Now the switch between processes (also called tasks) is done outside of the tasks control and can occur anytime during the task execution. An interrupt may cause a task to be stopped or started at any time. In addition (like cooperative tasking) a called to an executive function (pause, read or write FIFO) may also cause a switch.

    In real time systems these are usually priority based so that the highest priority available task always runs first. Some systems add time slicing as well where tasks of the same priority will share the process switching tasks at fixed time intervals.

    Pros

    Timing is now handled by the executive

    High priority processes have preference

    Cons

    Since a task can be interrupted at any time IPC primitives are needed for most communication between processes. This is usually not a big concern.

    The executive adds some overhead

    There is usually a timer built into the executive. The finer grained the timing interval the more overhead it adds.

    Multiple stacks

    Inter Process Communication

    The executive usually supports a variety of techniques for communicating between processes. These deal with any of the details to prevent problems due to a task switch coming while communication is occurring

    Adding Interrupts

    Adding interrupts is much the same as for a cooperative tasking executive. There is usually a subset of IPC that can be used. Interrupts are often split into two parts, one that handles the most immediate needs of the interrupt and communicates with the second part. this second part is part of the executive and can run at a different priority. it does the bulk of the work. This improves latency while still allowing fairly complex responses. You can see how this becomes a driver approach.

    Example

    Free-RTOS. many commercial and free executive available.

    Run To Completion Executive

    Finally there is the Run To Completion Executive. In this case each process simply runs until it is finished. It is not allowed to wait for any reason. No waiting for an I/O operation to finish, no pauses.

    The simple form of this looks like a super loop with worse timing characteristics but less worry about saving state. The interesting form (and the one that makes most sense to use) is the Preemptive Run To Completion Executive. By adding priorities to the tasks and allowing higher priority tasks to interrupt the current task you get a simple and powerful executive.

    This has a lot in common with an interrupt heavy version of the loop. Switching to a new task is done by saving the current task position on the stack and starting the new task. When the new task finishes it returns to the previous task where it left off. In this way a task can be interrupted by higher priory tasks multiple times before completing

    Pros

    Single Stack

    Simple (Each process acts like an interrupt)

    Fits reactive systems very nicely

    Latencies are low for high priority tasks.

    Relatively easy to build but it does require experience to do so.

    In systems with programmable interrupt priorities the interrupt system can be used as the scheduler. Starting a task then become the equivalent of flagging an interrupt. If is a higher priority than the current task then it will run, otherwise it will wait until all higher priority tasks are complete before starting.

    Cons

    Some analysis is required to set priorities properly. Hint: see Rate Monotonic Analysis. Despite the name it's really quite simple, Priority is assigned by frequency of task or latency required.

    No waiting allowed. If you need to wait the task must exit and then restart when the wait is over (or trigger a separate task for after the wait)

    Not may freely available executives available

    Inter Process Communication

    Can be difficult. Since no waits are allowed conditions like buffer under/overflows must either be ignored (throwing away data) or treated as errors. Most IPC is done is the same fashion as the communication between interrupts and the main process in a loop or super loop.

    Communication from a lower priority task to a higher involves blocking at least tasks to that higher priority from running. Managing this can become a burden.

    Adding Interrupts

    One of the real strengths of this executive. generally there is little no no difference between interrupts and tasks so they fit into the system cleanly. Like co-operative and preemptive tasking systems interrupts may be split into multiple parts at different priorities. This is especially true if the interrupt priorities are fixed, in that case doing this split allows the programmer to effectively re-prioritize a portion of the interrupt response.

    Additional Notes

    I think I've covered the major executives and techniques used in small systems. Large systems add more variations (especially in scheduling techniques) and you will find many variations on these themes with some systems sharing elements from multiple types. I certainly haven't been exhaustive and I'm sure there are techniques I've missed and certainly there are likely examples for some of the systems that I've neglected.

    I did say this would be long and if you've made it this far then thank you for you patience and attention.

    Robert

  • Sunny Chan said:
    I'm unclear how you filter A/D. The closest thing I can think of could be a hardware solution?

    You must have a hardware filter on the A/D. A simple RC filter low pass filter to keep the input frequencies below the Nyquist frequency is often sufficient. The capacitor acts as the needed charge reservoir (TI calls it a freewheeling capacitor). Look for bookshelf or in the Tags and you will find a reference to TO documentation from their seminars on A/D interfacing with more details.


    However, I was referring to SW filtering here. The simplest is an average but that's rather rigid. A little harder to understand but more flexible and using fewer resources is a single pole IIR filter. Some references will tell you that they can only be implemented using floating point but I have done so in integer, you just have to maintain a higher intermediate resolution.

    Beyond that filters can get quite complex.

    You should at least have a provision for inserting simple filtering and I'd recommend an IIR filter as your default starting point.

    Robert

  • Sunny Chan said:
    The reason why I want to have a interrupt driven program is to conserve as much processing power from the launchpad as possible.

    I think that goal seems more clear than it actually is. Arguably polling is more conservative of processing power than interrupts.

    It does, however, depend on what you mean by conserving processing power and why you want to do it.

    There is a story (probably apocryphal and I don't have a handy reference) that in the early mini-computer days a manufacturer want to optimize their instruction set so they profiled a working computer to see what instructions were most frequently executed. They then went and optimized the most frequent instructions. Upon retesting they measured the performance and found it had not improved. Further investigation showed they had succeeded in optimizing the idle loop. The computer successfully sat there doing nothing faster.

    Robert

  • There is a story ...

    I might add an appropriate quote from a software guy who's name momentary slipped my mind: "Premature optimization is the root of all evil."

    Start thinking about optimization once you got you project running and stable. Doing that before requires a lot of experience, which you not seem to have.

  • Robert Adsett72 said:

    However, I was referring to SW filtering here. The simplest is an average but that's rather rigid. A little harder to understand but more flexible and using fewer resources is a single pole IIR filter. Some references will tell you that they can only be implemented using floating point but I have done so in integer, you just have to maintain a higher intermediate resolution.

    If I attempt to average the data I'm getting from the ADC module, I'd figure I'd have to take the raw data, put it into SRAM memory of the tm4c then later fetch the memory from SRAM to average it. Is this the correct method? If it is how would I do it with the tm4c API?

  • Sunny Chan said:
    If I attempt to average the data I'm getting from the ADC module, I'd figure I'd have to take the raw data, put it into SRAM memory of the tm4c then later fetch the memory from SRAM to average it. Is this the correct method?

    That's essentially correct.

    Sunny Chan said:
    If it is how would I do it with the tm4c API?

    I don't think the API affects it.

    Robert