Electrical Engineering Stack Exchange is a question and answer site for electronics and electrical engineering professionals, students, and enthusiasts. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I would like to get an advice on handling tasks in a firmware.

I have 3 major tasks to do:

  1. Scanning for whether a switch is being pressed or not.

  2. Data transmission via SPI - To EEPROM

  3. Data Transmission via USB - To PC

I have managed to do a timer interrupt each 1 ms to see if whether the switch has been pressed or not. But the SPI transmission occurs every 6 hours only [takes 50ms to complete] and the USB transmission can occur anytime.

My question is how do I manage the tasks?

Should I disable the switch check once it enters state2/state3 and enable it after it exits the states?

How am I supposed to handle such situations?

share|improve this question
None of the above seem very good candidates for interrupts. If you are scanning the switches then this, by definition is not interrupt driven - you are scanning to find a switch that may have become closed since the last time you scanned. Apart from anything else, if you have many switches (arranged in a matrix) then background scanning is the sensible thing to do in a lot of occasions. Data transmission is dictated by the need to transmit data - it's usually the reception of data that folk consider to be important for an interrupt handler to work with. – Andy aka Feb 18 '14 at 11:04
i just have a single switch @Andyaka – Rookie91 Feb 18 '14 at 11:05
I would suggest polling that switch on a regular basis rather than have it generate an interrupt - think of switch bounce and what that may do if you don't handle the interrupt correctly. If you have a regular tick-timer, poll it on the tick I would say. – Andy aka Feb 18 '14 at 11:45
I assume f/w = FirmWare ? – Spoon Feb 18 '14 at 13:18
Can we assume that the SPI and USB are OUTGOING messages, and not coming in to your system?? – Scott Seidman Feb 18 '14 at 13:27

Most of the time it is good to handle the various things the processor needs to do with a combination of interrupts, tasks, and events. In this case, I'd use a periodic 1 ms interrupt for checking and debouncing the switch, a separate task to handle the USB stream from the host, and either events or another task to manage the SPI communication. USB requires some ongoing support even when you're not actually sending or receiving anything at the application layer. I'd make that a separate task or call the USB background processing routine regularly from the main event loop.

My usual architecture for such things contains the following constructs:

  • 1 ms periodic interrupt. 1 ms is a "long" time for most microcontrollers, so this takes only a small fraction of the CPU. This is where the debouncing logic for mechanical switches is performed. A switch signal needs to be seen in a state for 50 consecutive interrupts to be officially declared to be in that state. The rest of the system only sees the flag bits representing the debounced state of each switch input.

    This interrupt also increments one or more clock tick counters. Sometimes powers of 10 are useful, like 1 ms, 10 ms, 100 ms, and seconds. The interrupt routine increments each of these as appropriate, and the rest of the system just sees them change automatically.

  • Interrupts for other things that must be handled with low latency, like A/D readings. The A/D interrupt routine grabs the new reading from the A/D, possibly applies some low pass filtering, maybe applies scaling and offset, and leaves the final results in global variables for the rest of the system to read whenever it wants to.

  • A separate task to handle the reception of each communications stream. The information in serial communication streams is usually quite state-dependent. It therefore makes sense to process such streams using state machines. A task is basically a state machine where the PC is used as the state variable, except that its more maintainable, less obfuscated, and much more intuitive than a traditional state machine implemented as a computed GOTO on the state variable. Since the state for processing each communication stream is independent and asynchronous to other system state, it makes sense to give each stream its own task.

    Each task runs as a inifinite loop, except that it calls TASK_YIELD whenever it has nothing immediate to do or after any significant chunk of work. For receiving a byte stream, it is more convenient to write the code as if it goes out and gets the next byte, even though in reality bytes come in when they come in. The GET_BYTE routine usually starts in a loop that calls TASK_YIELD, checks for a new byte being available, and jumps back to the start of the loop if not. When a byte is available, it then returns with the new byte. This allows GET_BYTE to be called by the main task code at many places that are each context-dependent. For example, at the strart of the main task loop, it calls GET_BYTE to get the opcode of the next packet. The code than dispatches to the routine for that opcode, which calls GET_BYTE to get the first data byte, etc. This way the PC is automatically used as the state variable for parsing the stream.

  • One task, usually the original one so that nothing special needs to be done if there are no other tasks, runs the main event loop. This checks and handles all the little things that need to be handled occasionally. It calls TASK_YIELD at the top of the loop, then sequentially checks for events to handle. If a event is not pending, it jumps to the end of that section, which then proceeds to check the next event. If a pending event is found, it handles that event and jumps back to the top of the loop. If no pending events are found, then it hits the end of the loop, which simply jumps back to the start.

    This mechanism effectively prioritizes events with them checked in high to low priority order. Each event should be something that is relatively simple to handle and doesn't require waiting on anything else. This keeps the main event loop calling TASK_YIELD regularly. Break up more complicated proceedures into separate individual immediately-executable steps if necessary, usually by using a sequence of 1-bit flags to indicate ready for the next phase of processing. For example, one event might start a new A/D reading. Instead of waiting for that to be done, the A/D interrupt routine sets a flag when a new reading is available. That is then a separate event handled by the main event routine.

    Some events are time-based. Instead of setting flags for them in the clock interrupt routine, I usually have the main event routine check the global clock tick counters near the start of the loop to decide if any relevant time-based events need to be triggered. This has several advantages:

    • It keeps the clock interrupt routine clean. It is better for maintenence to not have main event logic hidden in other modules, like the clock module. This also allows for more easily using a generic clock module with minimum customization.

    • It keeps the logic local to the main event module. Someone doing maintenence later can see the whole logic in one place.

    • It doesn't lose ticks in case some processing takes longer than one tick interval. I keep the last known clock value in the main event routine for each timed event. These values are compared to the current clocks to determine whether there is a new tick or not. If so, the last known clock value is only incremented by one and the tick handled. If for some reason the system gets behind, it will catch up automatically with several of these events in succession every time they are checked, which will eventually get the last known clocks back in sync with the live clocks. If you get consistantly behind, then the overall system was badly designed and something else needs to be fixed.

A large number of template modules and a few examples are available as part of my PIC development environment. This includes nice coorperative multi-tasking managers for PIC 18 and the 16 bit PICs, templates for clock modules, the main event loop, and lots more.

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.