SLTF Consulting
Technology with Business Sense

 


 

Now that the processor's reset, here's how to get the code running

Scott Rosenthal
May, 1994

We'll do it in C. We'll use an RTOS. We'll purchase a communications library. You want to create how many icons? These are a few of the typical thoughts that come into play at the beginning of a software design, especially an embedded design. However, one thought I rarely hear is how to kick-start the software into performing all its miraculous activities. My last column (reference) addressed what happens in the first few microseconds after a microcomputer powers-up. This time, I'm going to take that discussion a step further and describe some of the activities a program must perform when a user first turns on the power.

Before the beginning

If you write a C program on a PC, you know it must include a function named main() that serves as the starting place for the program code. However, if main() was truly the program's starting place, then that function would also need to initialize a stack, set up the software floating-point package and initialize the data space. Yet I've never seen a main() function perform any of these feats. So how do these initialization steps take place? When you link a program (which is a whole story in itself), the linker also loads a special module. Depending on the development environment, this module goes by many names. For example, with the Manx Aztec C tools I use the name of the function is $begin(), but to keep things simple, I'll just refer to it as the loader(). Its job is to take the handoff from the operating system, perform the necessary initialization and run main().

Specifically at run time, DOS loads a program into the lowest available memory locations, allocates a small stack area for it and then transfers control to the start address stored in the .EXE header information. This address is the starting location of loader(). It's now up to loader() to perform all other housekeeping details necessary to successfully launch main() and the rest of the program.

The following steps outline tasks loader() must typically perform, but the list is by no means complete, and the sequence isn't necessarily important. Many compiler manufacturers have special requirements for their versions of loader(). What's important is to see the number of steps needed to successfully launch a program:

  1. Create a local stack area for the program.
  2. Clear the uninitialized memory-data area.
  3. Create and initialize a heap-memory area.
  4. Initialize the disk file and device-handling logic.
  5. Open communication channels to the standard C devices: stdin, stdout, stdprt and stderr.
  6. Initialize the floating-point package or math-coprocessor chip.
  7. Call main() to start the program running.

As you can see, loader() does a lot of work behind the scenes that significantly reduces your programming chores. If instead of coding in C you used assembly language, you'd have to supply a custom loader(). Thus, it's easy to appreciate how a high-level language can eliminate the mundane and allow you to concentrate on solving specific design issues.

The embedded twist

The PC world-compared to embedded development-can sometimes lull you into a false sense of security. One pitfall waiting for the uninitiated is the matter of a loader(). Not only must you worry whether the language supplier includes a loader(), you must also make sure the loader() works in your application. Even if you're lucky enough to get one (many compilers-such as Intel's C96 tool-don't come with one) the chances of it working with the widget you're developing are slim because no two embedded applications incorporate the same resources and so have different initialization requirements. For instance, some systems use disk drives and displays, while others have neither.

At this point in the discussion, one of several twists comes into play. In the PC world, DOS hands off to the loader(), which then hands off to the main(). But in an embedded system, which module hands off to the loader()? The answer was in my last column-the microcomputer's reset address. When the CPU first starts running at its reset address, the short piece of reset code should branch to the loader().

In my experiences, every one of my embedded systems' loader() functions has a few things in common: They all set up a stack area in memory; they all clear system RAM so that, when debugging, my programs always have the same starting memory images; and they set up pointers to a free-memory area that becomes the system's heap space. Other than these simple steps, all other functions I either group into a function called from within main() or let the functions self-initialize.

A monolithic approach

As far as I know, these two alternatives are the only techniques that exist for initializing an embedded system. Deriving from my years of programming in assembly language, I've always included a function, which I inherited from another engineer, that performed a massive system initialization. Originally named sysint(), short for system initialization (our language at the time only allowed 6-character names), and later renamed sysinit() (when a more-advanced language allowed 8-character names), it initialized all hardware to a known state, set global locations to their starting values and performed a bevy of other calls to set-up other functions such as a keypad FIFO.

As time went on and programs grew in size, sysinit() ballooned to ridiculous proportions. I developed a guideline for my office, as documented in our Software Standards and Development Manual, that says a function should be no more than 50 lines in length. So my choices were to either ignore a standard I authored or find another way to initialize my embedded systems. Deciding that sleeping well at night was more important than temporary expediency, I took the latter course. The technique I implemented was to allow functions, and therefore systems, to initialize themselves. This change in approach reduced the size of sysinit() while eliminating the potential programming error of failing to incorporate into sysinit() all the systems that needed initialization.

In C terminology, I use a static variable within a function as a flag to say whether the function has run yet. Remember when I said that during initialization my loader() clears all system memory? Due to this fact, I know that at initialization, my flags must be False until the function sets them True. For example, assume I declare a flag in a function named Initialized (see listing). I can test this variable each time the function runs. The first time the function executes, the flag is False, so the code runs the execution routines, sets the flag True and continues with whatever the functions should do. With each subsequent test, the True flag prevents the initialization code from running again.

Listing: self-initializing function logic
void SampleFunc(void) {	
	static int Initialized;	
	if (!Initialized) {		
	// ... perform the initialization ...		
		Initialized = TRUE;	
	}	
	// ... function guts ...
}
                

Some people might ask, why initialize memory when you can assign a value to a static variable? That point is valid on a PC, but many times embedded systems run from ROM and can't initialize static variables. The technique I outlined above works in both situations. Other people might argue that this method is wasteful of a computer's time and memory, and I agree. However today, processors are so fast and memory is so cheap that a little bit of waste in return for function encapsulation, better documentation and potentially fewer programming problems, is a dynamite tradeoff.

One caveat to keep in mind with this technique is reentrancy. If you're using an RTOS and all functions must be reentrant, be careful with these flags. Try to incorporate them into the data structure that the function works on. Finally, let your mind wander and find other techniques that keep initialization out of the monolith and put it where it belongs-as part of the function. PE&IN

Reference

Rosenthal, S, "System initialization requires as much care as the rest of your design," PE&IN, Mar 1994, pgs 73-74.



Copyright © 1998-2012 SLTF Consulting, a division of SLTF Marine LLC. All rights reserved.