How are you using this book?

I would like feedback

For about 8 years the 3rd and 4th editions of C and the 8051 have been for sale and I’m guessing well over a thousand copies have been sold. Yet in all those years I have never had any feedback on the book…what it is used for…what things you like about it…what things you dislike…which sections do you use and which have you ignored. When I put together the 3rd edition I combined the contents of several earlier books (hardcover, published by Prentice Hall) and took out most of the assembly language to focus on C. In addition I included large sections about multitasking, serial  communications and electronic interfacing. In addition, with the significant improvement in ease of development with the Silicon Labs boards, I narrowed the examples down to two boards in their family (I got to know the boards before the start-up invention company was acquired by SL).

The closest thing to ‘feedback’ I have gotten is the couple of reviews on Amazon (which are mostly positive except for someone who thinks these editions have too many footnotes…clearly based on the earlier editions).

So here’s my request. If you are reading this blog, please either comment below or email me ( telling me if you use the book in a class (describe the class a little bit) or for hobbies, or for work (telling us a little about what you do if your company allows). Also, what target hardware do you use. In any case, please  identify what parts of the book are relevant to your uses.

Thank you.

Multitasking Strategies

Getting the computer to manage several tasks seemingly simultaneously is the heart of multitasking. You can choose to write tasks separately as though nothing else ever happens—separate modules. The ideas involved are not difficult to visualize if you compare them with everyday activities, but they differ from those of normal programming. You should develop the frame of mind that says, “If it isn’t ready yet, go on. I’ll come back to it later. Right now there may be something else to do.” Tasks may be inter-related; then you will have to plan signaling between them. Once you’ve understood the ideas of this section, it will be easy to move to complicated systems involving many calls with many parameters. Realtime systems are becoming increasingly important as the dedicated microcontroller becomes more and more prevalent. Knowing the concepts, you can have a role in the implementation. Several different approaches to multitasking are described next.

Cooperative multitasking–Round Robin

If priority is not important, no special hardware is needed for a cooperative multitasking scheme where each task politely pauses at frequent points to “go to the end of the line” or “wait ‘till the next time around” and allow other tasks to get a chance to do some work. The weakness is that if some task gets added that doesn’t cooperate, there is no protection in the operating system and all the other tasks can be blocked. This might better be called “multiple tasking.” It is a round-robin system where tasks run one after the other in turn, without the idea of preemption or priority that are essential for external realtime events. Although this approach can include interrupt service routines, attention is centered on what really should be called the background tasks. In such a scheme each task waits its turn to run. If a task has nothing to do at the time, it immediately passes control to the next task, but if it has a lot to do, it can spend all the time it needs to finish. Such a system avoids the overhead of an operating system, but requires the programmer’s close attention to avoid long latency (delay in getting back around the loop to the task needing attention). You have to be sure to break big tasks into several small tasks or scatter around intermediate pauses to be sure the running task doesn’t hog the processor.

Time slice

The time slice multitasking approach addresses some of these problems. It provides protection from hogging the processor by arbitrarily switching out long-running tasks at regular intervals. With this approach, tasks usually run round-robin unless a task takes more than an allotted amount of time. At that point the task is put on the shelf and the next task in line gets to run. The first task goes to the end of the line and is allowed to continue running when its turn comes around again. There must be a timer-driven interrupt marking off time slices so control of timing is removed from the running task. This approach is good for data processing applications and was the heart of early time-shared computing systems, before the personal computer revolution. In its basic form, though, it doesn’t allow the feature of preemption needed for non-deterministic systems—ones where urgent tasks may unexpectedly or unpredictably need immediate attention.


A scheduler is somewhat related to a time-slice system in that it keeps track of time for the various tasks. Typically, however, there are some tasks that are short and repetitive, such as scanning external inputs or sending regular outputs. These short tasks may come frequently or only occasionally and at irregular intervals. Any left-over processor time is used for a background task, which is often a much less urgent data-processing or decision-making activity.

Priority-based, preemptive multitasking

There are a confusing number of combinations of these types of multitasking when you begin to add in priority of tasks. More urgent tasks can go to the head of the line or preempt the running task. A common combination is a priority-based, preemptive multitasking operating system where equal priority tasks run in round-robin fashion and the system switches tasks based on both timer events and external hardware events. These events may be periodic events like the ticks of a clock for a scheduler, or dynamically changing events based on inputs from outside hardware. An event is anything that might cause the system to change tasks. It could be an external interrupt, an internal signal or message sent from another task, or the expiration of a waiting period. The primary point is, events can lead to a change in which task is running.


For discussion of multitasking, we need to define a task. In its simplest form a task is any one thing to done by a controller. Think of an army with an overall mission to accomplish and many individual soldiers to carry it out. The overall mission, ”Drive out that dictator,” is the job, but the tasks might be, ”Drive this supply truck to the front lines,” or, ”Sit in this trench until ordered to advance.” For a controller, a task might be to wait for and process input from a keyboard or to keep a motor running at a set speed. A task can be as simple as a single routine having a few lines of code, or it can be an entire set of nested routines. Task divisions can be arbitrary as long as they relate to the functional parts of the overall job. Most important, tasks represent a way of thinking that logically divides the job and leads to the effective use of the microcontroller.


Should a task ‘move to the head of the line’ when other tasks have been waiting longer to run? Having different priorities in a system implies that some activities are more important or more urgent than others. We are used to degrees importance in the realm of politics and big business, and we expect to yield to fire trucks and ambulances because they have greater urgency. The same thing can be applied to individual tasks. Issuing a pulse to a stepper motor may be more urgent than the computing an average. Stepper pulses must come at regular times, whereas a computation can be done whenever time is available. The first-level processing of incoming readings is more important than subsequent processing if the new readings will be overwritten by the next readings if they aren’t processed and moved. In a realtime system the priority may be a measure of importance or urgency, or it can reflect the duration of a task–a very short task could run with higher priority because it will be over and done quickly, rather than any inherent degree of importance.


Should a task not just move to the head of the line, but stop and replace the task that is currently running? If so, it has preempted the running task. The concept isn’t difficult but the implementation can be challenging because the preempted task could be in the middle of something that shouldn’t be interrupted and, at a minimum, the various registers must be saved so the preempted task can resume where it left off when it gets to run again.

Realtime Thinking

Most courses treat multitasking as an advanced topic. I hope your perspective will change as you come to see that it is the most fundamental part of efficient programming and deserves your attention as early as possible. Once you understand it, you can have your microcontroller look like it is doing many things all at the same time—it will never seem to be unavailable and will always be checking for something to do.

Beyond single-program thinking

If you have used only traditional techniques of programming, you will find, as your applications grow more time-critical, that a single program approach becomes awkward. When you have several external hardware devices that need to be served at the same time, your challenge is to make sure each device is satisfied.

Perhaps all your software has only had one thing happens at a time…in sequence (that is why computers were called sequential machines). If a speech chip had to say something, you programmed the processor to put out the appropriate code, pulse the write line, and then wait until the phrase was done before going on to check for some new input from the keypad. Nothing could be recognized while the processor was waiting for the ‘done’ from the speech chip. To move on in your programming skills you must develop a new way of thinking about program flow…you can’t let the flow get stuck if something isn’t ready yet.


Although the term means different things to different people, I apply realtime to any system that respond to inputs and supply outputs fast enough to meet user or external hardware requirements. For example, a keyboard entry system is realtime if it gives you some feedback quickly enough for you to feel confident that the system “heard” you. If a “beep” is fed back to you within 100mSec, you feel confident that the system recognized the input “right away.” So, a keyscan routine that repeated every 100mSec would probably meet the requirement of fast enough. Likewise, your eyes and brain cannot assimilate new digital display information more quickly than perhaps 5 times a second….numeric values that are rapidly changing might better be updated only a few times a second. On the other hand, a stepper motor ought to be sent a new step pulse at a regular time with millisecond precision, so this is a much more critical time requirement. Most serial ports have a single-character buffer, so at 9600 baud every incoming character must be picked up within about 1mSec (10 bits @9600 bits/Sec=960 char/Sec »1mSec). Outgoing (asynchronous) characters can go whenever the processor is not busy, assuming there is no other time constraint on message transfer. The same considerations would apply to collecting data via an A-D converter. Depending on how rapidly the incoming voltage is changing, the reading might need immediate retrieval or might be held for quite some time. How rapidly should a flow valve be adjusted for a process? How quickly should a motor be supplied a new speed setting? All of these are questions relating to what is fast enough.

Efficient Design With Microcontrollers (Part 3)

Efficient software is easy-to-understand software that runs faster and requires less code.

Software Development Strategies

  1. Top-Down Development This is an approach to programming which relates to understandability and the use of functions. Essentially, it is an approach that starts with the broad overview (the “top”) and then moves (“down”) to the details. Using such an approach, you first write the main program (the main() function in C). In doing so, you make liberal use of functions that you have yet to write and assume that later on you will write them and that they will perform in specified ways. For example, if your final application needs a printing device, during development call a (at that moment non-existent) printf() function with a pointer to the desired message. If you need an A-D reading, during early development assume some function will exist to return the reading when called. Once you’ve written the main program, the “down” of “top-down” consists of writing the first level of functions. These in turn may use other functions, which you will also write later. Finally, you write the detailed drivers—the most basic functions. (If you would like to see an example of this 3-level programming, start with Figure 21.1 on page 236 and then follow the code in the rest of that chapter.) Continue reading

Efficient Design With Microcontrollers (part 2)

Efficient Electronic Hardware [taken from C and the 8051]

  1. Use as few supply voltages as possible. With the right analog devices, a logic supply (usually 5V, but recently going as low as 2.7V) can often suffice. Microphone signals and most sensor signals never get that big. There is no law that all analog processing must be done with ±15V. Often, if the signal goes into an A-D converter and the signal-to-noise ratio is not a problem, it is unnecessary to amplify it to such levels.
  2. Use as little current as you safely can, and design so the high current drains are the shorter duration ones. Continue reading