Академический Документы
Профессиональный Документы
Культура Документы
REAL-TIME METHODS
Real-time programming is sometimes misconstrued as some sort of complex and magical process
that can only be performed on large machines with operating systems like Linux or Unix. Not so!
Embedded systems can, in many cases, perform on a more real-time basis than a large system.
A simple program may run its course over and over. It may be able to respond to changes to the
hardware environment it operates in, but it will do so in its own time. The term realtime is used
to indicate that a program function is capable of performing all of its functions in a regimented
way within a certain allotment of time. The term may also indicate that a program has the ability
to respond immediately to outside (hardware input) stimulus.
The AVR, with its rich peripheral set, has the ability to not only respond to hardware timers, but
to input changes as well. The ability of a program to respond to these real-world changes is
called interrupt or exception processing.
USING INTERRUPTS
An interrupt is just that, an exception, change of flow, or interruption in the program operation
caused by an external or internal hardware source. An interrupt is, in effect, a hardwaregenerated function call. The result is that the interrupt will cause the flow of execution to pause
while the interrupt function, called the interrupt service routine (ISR), is executed. Upon
completion of the ISR, the program flow will resume, continuing from where it was interrupted.
In an AVR, an interrupt will cause the status register and program counter to be placed on the
stack, and, based on the source of the interrupt, the program counter will be assigned a value
from a table of addresses. These addresses are referred to as vectors.
Once a program has been redirected by interrupt vectoring, it can be returned to normal operation
through the machine instruction RETI (RETurn from Interrupt). The RETI instruction restores
the status register to its pre-interrupt value and sets the program counter to the next machine
instruction following the one that was interrupted.
To create an ISR, the function that is called by the interrupt system, the ISR, is declared using the
reserved word interrupt as a function type modifier.
interrupt [EXT_INT0] void external_int0(void)
{
/* Called automatically on external interrupt 0 */
}
or
interrupt [TIM0_OVF] void timer0_overflow(void)
{
/* Called automatically on TIMER0 overflow */
}
The interrupt keyword is followed by an index, which is the vector location of the interrupt
source. The vector numbers start with [1], but since the first vector is the reset vector, the actual
interrupt vectors available to the programmer begin with [2].
ISRs can be executed at any time, once the interrupt sources are initialized and the global
interrupts are enabled. The ISR cannot return any values, since technically there is no caller,
and it is always declared as type void. It is for this same reason that nothing can be passed to the
ISR.
REAL-TIME EXECUTIVES
A Real-Time Executive (RTX) or Real-Time Operating System (RTOS) is a program, commonly
referred to as a kernel, that coordinates the management and execution of multiple subprograms or tasks. An RTX strictly coordinates program runtime operations, while an RTOS is
usually associated with extra functionality such as file management and other generalized I/O
operations. There are many real-time executives available for the AVR. Some are in the public
domain and are free of charge. Others are commercially licensed and supported but are still
inexpensive. In this section, we are only going to touch on the RTX, specifically the Progressive
Resources PR_RTX that is readily available and simple to configure and use.
An RTX utilizes a time-based interrupt (i.e., TIMER0) to guarantee that various subprogram
executions happen on time and as often as is required to obtain the desired operational results.
The beauty of an RTX is that the program operation, from a timing perspective, is defined in a
header block and the tasks are usually configured with a call into an RTX initialization function.
Characteristics such as the number of tasks allowed to be executing within the system, how often
the operation is to switch from one task to another, and whether tasks are to run in a round-robin
or top-to-bottom (sometimes called preemptive or priority) fashion, are defined by the header.
Since each subprogram stands somewhat alone, each is given its own stack space to work in
keeping the processes truly independent. Once the operational characteristics are defined, the
RTX is initialized with definitions of what tasks you wish to have run and the priority you would
like to have the tasks assigned to priority is important in that if for some reason a task could not
complete its function within the allocated amount of time, the RTX can take the operation back
to the highest priority task and start again. This allows for the important and generally timecritical tasks to get processor time no matter what is going on with lower priority or less
important tasks.
STATE MACHINES
State machines are a common method of structuring a program such that it never sits idle waiting
for input. State machines are generally coded in the form of a switch/case construct, and flags
are used to indicate when the process is to move from its current state to its next state. State
machines also offer a better opportunity to change the function and flow of a program without a
rewrite, simply because states can be added, changed, and moved without impacting the other
states that surround it.
State machines allow for the primary logical operation of a program to happen somewhat in
background. Since typically very little time is spent actually processing each state, more free
CPU time is left available for time-critical tasks like gathering analog information, processing
serial communications, and performing complex mathematics. The additional CPU time is often
devoted to communicating with humans: user interfaces, displays, keyboard services, data entry,
alarms, and parameter editing. User interfaces, displays, keyboard services, data entry, alarms,
and parameter editing can also be performed using state machines. The more thinly sliced a
program becomes through the use of flags and states, the more real-time it is. More things are
being dealt with continuously without becoming stuck waiting for a condition to change.
The format string specifications are used to tell the printf() function how many arguments to
process.
Specification Format of Argument
%c
outputs the next argument as an ASCII character
%d
outputs the next argument as a decimal integer
%i
outputs the next argument as a decimal integer
%u
outputs the next argument as an unsigned decimal integer
%x
outputs the next argument as an unsigned hexadecimal integer using lowercase
letters
%X
outputs the next argument as an unsigned hexadecimal integer using uppercase
letters
%s
outputs the next argument as a null terminated character string, located in SRAM
%%
outputs the % character
STRING PRINT FORMATTEDsprintf()
The string print formatted function has the standard form of
void sprintf(char *str, char flash *fmtstr [ , arg1, arg2, ...]);
The operation of this function is identical to printf() except that the formatted text is placed in the
null-terminated character string str instead of calling putchar().
PREPROCESSOR DIRECTIVES
Preprocessor directives are not actually part of the C language syntax, but they are accepted as
such because of their use and familiarity. The preprocessor is a separate step from the actual
compilation of a program and happens before the actual compilation begins. The most common
directives are #define and #include. The preprocessor directives allow you to do the following:
Include text from other files, such as header files containing library and user function
prototypes.
Define macros that reduce programming effort and improve the legibility of the source code.
Set up conditional compilation for debugging purposes and to improve program portability.
Issue compiler-specific directives to generalize or optimize the compilation.
THE #INCLUDE DIRECTIVE
The #include directive can be used to include another file in your source. There can be as many
files included as needed, or as are allowed by the compiler, and includes can be nested (meaning
that an included file can contain another, non-recursive, #include). Typically, there is a limit to
the depth of the nesting, and the limit will vary from one compiler to another. The standard form
of the statement is
#include <file_name>
or
#include "file_name"
The less than (<) and greater than (>) sign delimiters indicate that the file to be included is part
of a standard library or set of library files. The compiler will typically look for the file in the
\inc directory. When the file name is delimited within quotation marks, the compiler will first
look in the same directory as the C file being compiled. If the file is not found there, then the
compiler will look for the file in the default library directory
(\inc).
The #include statement can be located anywhere in the code, but it is typically used at the top of
a program module to improve the program readability.
THE #DEFINE DIRECTIVE
A #define directive should be thought of as a text substitution referred to as a macro.The
standard form of a #define is
#define NAME Replacement_Text
Typically, #define directives are used to declare constants, but the tag NAME and the
Replacement_Text may also have parameters. The preprocessor will replace the tag NAME
with the expansion Replacement_Text whenever it is detected during compilation.
If NAME contains parameters, the real ones found in the program will replace the formal
parameters written in the text Replacement_Text.
There are a couple of simple rules that apply to macro definitions and the #define directive:
Always use closed comments (/*...*/) when commenting the line of a #define.
Remember that the end of the line is the end of the #define and that the text on the left will be
replaced by all of the text on the right. For example, given the following macro definitions
#define ALPHA 0xff /* mask off lower bits */
#define SUM(a,b) a+b
the following expression
int x = 0x3def & ALPHA;
would be replaced with
int x = 0x3def & 0xff /* mask off lower bits */ ;
and this expression
int i=SUM(2,3);
would be replaced with
int i=2+3;
The diversity and capability of the #define directive is somewhat dependent on the sophistication
of the compilers preprocessor. Most modern compilers have the ability to redefine functions and
operations as well as the ability to sort out the parameters that go with them.
THE #ifdef, #ifndef, #else, AND #endif DIRECTIVES
The #ifdef, #ifndef, #else, and #endif directives can be used for
conditional compilation.
The syntax is
#ifdef macro_name
[set of statements 1]
#else
[set of statements 2]
#endif
#pragma promotechar
The ANSI standard for the C language treats characters as 16-bit values. This is not necessary (or
even desired) in a small, embedded system The ANSI character to integer operands promotion
(or conversion) can be turned on or off using the #pragma promotechar directive:
/* turn on the ANSI char to int promotion */
#pragma promotechar+
or
/* turn off the ANSI char to int promotion */
#pragma promotechar#pragma uchar
When character variables are declared, the default format is as follows:
char var; // declares a signed character variable
unsigned char var2; // declares an unsigned character variable
#pragma library
You may wish to create your own function library to be used as commonly as the standard I/O
library. Once your library has been created, it can be included in the program at compile time
using the #pragma library directive:
#pragma library mylib.lib
The creation of libraries is compiler specific; you should refer to the compiler users guide for
instruction on the required methods.