Low-cost cooperative multitasking, Part 2 – Building an MP3 Player
In Part 1 of this article series, we saw the basics of a state machine-based multitasking system through a very simple example. In this second part, we will study the design methodology for building the software of our system. We shall discuss designing tasks, inter task communication, prioritizing, etc. by building an MP3 Player system that integrates USB, a file system, LCD and capacitive touch sensing.
Designing Your System Software
Here is a recap of the basic principles discussed in Part 1:
1. Each function is split into small subtasks or states that can be completed within a short amount of time.
2. Saving and restoring the context of a task is achieved by the state machine and state variables.
3. Each task performs a small portion of its job and relinquishes control back to the scheduler. The system is built by many such tasks/functions, which are called repeatedly.
4. This interleaved execution of different states of multiple tasks appears like simultaneous execution of all the state machines.
5. The scheduler is an infinite loop that repeatedly sifts through all the tasks and executes the tasks that are scheduled for the current iteration
6. Simple language constructs are used as much as possible to keep overhead to a minimum.
The design of a state machine-based multitasking system involves a top-down approach. While the scheduler itself sits at the top of the hierarchy, it is discussed in the final section of this article. First, let us see what approach should be used in designing the system’s software.
The system software design can be divided into the following three subcategories:
1. Subsystem definition and task break up
2. Task interactions and priority
3. Task scheduling
Subsystem Definition
As with any top-down design approach, we first break our system down into its major subsystems. The subsystems are those logical sets of jobs that belong to one single technology or functionality. As hinted in the previous line, a subsystem need not be a single function, but a set of functions that interact with each other. In the first step of designing, we should divide our system into top-level subsystems. Then, we can move on to identify each independent sub function within the subsystem.
Let us now consider a system that is more complex than the FM player discussed in part 1 of this article—a USB MP3 player that has an LCD display and touch-sense input. Let us assume that the system has the following hardware:
1. MP3 decoder ASIC
2. Capacitive touch-sense input pads
3. Segmented LCD display
4. A USB host microcontroller
The top-level break up of the system would look like Figure 1 below.

Figure 1: Block Diagram of a Hypothetical USB MP3 Player System
In this USB MP3 player system that we have constructed, we can identify various subsystems and their inner components:
1. USB subsystem
2. File system
3. MP3 subsystem
a. MP3 audio manager
i) Buffer manager
ii) File manager
iii) Playback manager
b. MP3 ASIC driver
4. Touch-sense input subsystem
a. Capacitive touch-sense driver
b. Touch-detection algorithms
5. Display subsystem
a. Display manager
b. Display driver
6. System monitor and housekeeping
From here, we move on to break up each subsystem component into functions and sub functions. For example, the MP3 audio manager will need to be broken up into MP3 ASIC-buffer management, file manager and MP3 playback manager, to handle modes such as fast forward, rewind, etc.
Any function that takes more than a fraction of a millisecond to execute must be made into a state machine, as illustrated in the first section of this article. Thus, at the end of the system break down, we have a collection of simple functions and many state machines.
Once all independent functions are identified, they must be grouped together into different tasks for scheduling. For example, the MP3 subsystem requires four state machines.
The “ASIC driver” and “buffer manager” need to run at top priority and consume throughput. The “file manager” and “playback manager” state machines can run more leisurely. It is also possible that the file manager and playback manager are grouped together in a single task.
Therefore, a task in our case is a set of function calls grouped together for scheduling. It is possible that there is no formal function with a task name at all in our system, but instead just a list of calls to our top-level state machines in our scheduler.
It is important to take the time to put together a good design of tasks. Too many tasks result in wasted code space and more scheduling overhead. Too few tasks and a single task utilize too much throughput, thereby reducing the multitasking effect.
The following rules apply:
1. Group two related state machines that depend upon each other in the same task.
2. If such functions take a long time to execute, then place them in different tasks and stagger their execution in the scheduler loop (more on staggering, later).
3. Schedule unrelated tasks together, as long as throughput balancing is not hampered.
4. Create separate tasks for slow response functions (e.g. input processing) even though they belong with other functions (e.g. touch-sense detecting). Schedule these tasks leisurely.


Loading comments... Write a comment