How (embedded) C code works.
Let's walk through
what happens when power is applied to a
microcontroller.
First, power comes up and a circuit places the device in reset by
holding the reset
line low for a brief period. In keeping with AVR's "Just come
up and go" philosophy,
there is an internal circuit that does this for you. There is
also an external pin.
The processor gets to the starting location of your code from a jump
instruction in the
interrupt/reset vector table. See the full (not summary) data
sheet for your favorite
part and look under Interrupts,
subsection Interrupt
vectors. The vector table
holds jump instructions,
not addresses. Remember, this is a Modified
Harvard Architecture
processor and has separate data and instruction busses and memory
spaces. Different
size processors will have one or two instructions per table entry.
There will be some startup code at this point which does things like
set the stack pointer,
turn on the interrupt enable flag, and initialize data.
Technical details of memory initialization appear here on the nongnu.org website.
When you declare a file-scope static variable in C that is initialized
and that variable
is not a const variable,
The variable is placed in RAM and initialized.
Let me illustrate:
int x=5;
int main(int argc, char *argv[]) {
int y=6;
static int z=7;
char *str = "testing";
while (1) {
/* your
code goes here */
}
}
Remember, that in C, local
variables are held on the stack. This means that
when a
variable is declared, the variable is given space by the compiler to
exist in the stack.
When the function exits, that variable is gone (its lifetime
is over). That's why
you never want to return a pointer to a locally declared variable in a function. When the
function exits, the stack frame that holds the variable is gone, and the pointer points to
whatever may be in whatever stack frame is using that memory at the time.
In this case, the y=6
is allocated in the stack frame and initialization code is put at the
beginning
the function to set the initial value to 6.
Now, notice that x is not declared inside a function. It's
not a local (or, technically an automatic)
variable (see Kergnihan & Ritchie for details). These
variables are placed in a segment by the linker,
and there they sit for the entire time the processor runs.
(This would not be exactly true in larger
embedded systems with an operating system that supports processess, but
we're talking about
small-processor, embedded C here.)
So, we have what's called a "file scope" variable. It's
global, and never "disappears" by going out
of scope when a function ends.
Now, a good question to ask would be, how does the 5 get put into x?
The CPU starts up, and the
RAM is in an indeterminate state. There's a space the right
size for an int
in RAM, but we need to
initialize it. Well, for each staically allocated variable
that gets put in the RAM segment, there is another
variable of the same size allocated in FLASH, with the initial values
of these variables inside there.
This segment is copied to RAM by the startup code and now we have
statically allocated variables
that are initialized.
ANSI C and other modern standards specify that uninitialized variables implicitly get set to zero. There
will be another segment that isn't initialized from FLASH. This segment does, however, need to get
cleared to zeros. The startup code does this as well.
These are the most important steps (and there are others in the startup
code) that run before main() starts
running. When the startup code is done, its last operation is
to jump to the address where main() starts.
There are some curious things about main you could ask, like "what
about argc and argv?" "What happens
when main exits?" These depend on the compiler implementation and
really are oddball questions that don't
need to get answered here. Generally in embedded programming
you may or may not get something
in argc/argv, or you may not. main() does not generally
exit. I do know that if you exit from main()
in the Win-AVR compiler, it will jump to the reset location and
effectively restart the processor from the
beginning.
How about that static
int z=7 in main()?
The static
keyword tells the compiler to allocate the
variable as if it were file scope (like the variable x), but it won't
be globally visible now because it's hidden
in a function. The variable has an eternal life, but can't be
accessed by just any function. This also has the
effect of creating a variable that won't change from function call to
function call.
This also can have an effect on re-entrancy:
if a function is called, and an interrupt arrives and a
second
copy of the function starts running, what will happen? Both will see the static variable and could produce
undesired side effects. Keep
this in mind if you are writing an interrupt handler, and remember that declaring
a variable file-scope generally won't make
this problem go away.
static
variables are somewhat rare. They have their place, but there
are generally better options.
Now for that char *str.
Remember, in C "this is
a string" places the string in a ROM location,
the location of which is a char *.
It then provides a pointer to this string as the value.
You can change what
str
points to by reassigning it, but the "this is
a string" will remain in FLASH, just like it
was. The
pointer to it is inaccessible, so if you reassign str,
you will lose the location. So, copy str elsewhere
if you
need it later.