Вы находитесь на странице: 1из 10

Embedded C language

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.

Mixing C and Assembly


Mixing the code written in a high level language like 'C and Assembly language is useful in the
following scenario:
Assembly routines are mixed with C in situations where the entire program is written in C and
the cross compiler in use do not have a built in support for implementing certain features like
interrupt service routine functions (ISR) or if the programmer wants to take advantage of the
speed and optimize code offered by machine code generated by hand written assembly rather
than cross compiler generated machine code. When accessing certain low level hardware, the
timing specifications may be very accurately. Writing the hardware/peripheral access routine in
processor/controller specific assembly language and invoking it form C is the most advised
method to handle such situations. mixing C and assembly is little complicated in the sense the
programmer must be aware of how parameter are passed from the C routine to assembly and
values are returned from the assembly routine to C and how assembly routine is invoked from
the C code.
This C function on cross compilation generates the following SRC file.
NAME TESTCODE
?PR?_my_assembly_function?TESTCODE
SEGMENT CODE
PUBLIC _my_assembly_func
;
#pragma SRC
;
unsigned char my_assembly_func (
RSEG ?PR?_my_assembly_func?TESTCODE
USING
0
_my_assembly_func:
{
; SOURCE LINE # 4
; return (argument+1);
; and retvals
; SOURCE LINE # 5
MOV A,R7
INC A
MOV R7,A
;}
;
SOURCE LINE # 6
?
C0001:
RET
;
END OF _my_assembly_func
END
The special compiler directive SRC generates the assembly code corresponding to the C function
and each lines of the source code is converted to the corresponding assembly instruction.

Standard I/O functions


STANDARD INPUT FUNCTIONS
The input functions of a standard library include the get string, gets(), scan formatted, scanf(),
and scan string formatted, sscanf(), functions. Just as the root output function is putchar(), the
root input function is getchar(). In all cases, getchar() is called to gather the necessary data. So by
changing the operation of getchar(), the data source can easily be altered.
GET STRINGgets()
The standard form of gets() is
char *gets(char *str, unsigned char len);
This function uses getchar() to input characters into the character string str, which is terminated
by the new line character. The new line character is replaced with a null (\0) by the gets()
function. The maximum length of the string is len. If len characters were read without
encountering the new line character, then the string is terminated with a null and the function
ends. The function returns a pointer to str.
SCAN FORMATTEDscanf()
The scan formatted function has the standard form of
signed char scanf(char flash *fmtstr [ , arg1 address, arg2
address, ...]);
This function performs formatted text input, using getchar(), according to the format
specifications in the fmtstr string. The format specification string fmtstr is constant and must be
located in FLASH program memory. Just as in the printf() function, fmtstr is processed by the
scanf() function. fmtstr can be made up of constant characters to be expected in the input stream,
as well as special-format commands or specifications. As scanf () is processing the fmtstr, it
inputs data using the getchar() function and either matches the received character to the one
found in fmtstr or converts a series of characters for each argument according to the format
specifications that may be embedded within the fmtstr string. A percent sign (%) is used to
indicate the beginning of a format specification. Each format specification is then related to an
argument, arg1, arg2, and so on, in sequence. There should always be an argument for each
format specification and vice versa.
The arguments, arg1, arg2, and so on, are the address of or pointers to variables. (Not using an
address or a pointer as an argument in this function can lead to some very disappointing results!)
The function returns the number of successful entries or 1 on error.
The described implementation of scanf() format specifications is a reduced version of the
standard C function. This is done to meet the minimum requirements of an embedded system.
A full ANSI-C implementation would require a large amount of memory space, which in most
cases would make the functions useless in an embedded application.

SCAN STRING FORMATTEDsscanf()


The scan string formatted function has the standard form of
signed char sscanf(char *str, char flash *fmtstr [ , arg1 address,
arg2 address, ...]);
This function is identical to scanf() except that the formatted text is read from the null terminated
character string str, located in SRAM.
PUT STRING FLASHputsf()
The put FLASH (constant) string function has the standard form of
void putsf(char flash *str);
This function uses putchar() to output the null-terminated character string str, located in FLASH,
followed by a new line character.
PRINT FORMATTEDprintf()
The print formatted function has the standard form of
void printf(char flash *fmtstr [ , arg1, arg2, ...]);
This function outputs formatted text, using putchar(), according to the format specification in the
constant string fmtstr. The format specification string fmtstr is a constant string and must be
located in FLASH program memory. fmtstr is processed by the printf() function. fmtstr can be
made up of constant characters to be output directly as well as by special format commands or
specifications. As printf() is processing the fmtstr, it outputs the characters and expands each
argument according to the format specifications that may be embedded within the string. A
percent sign (%) is used to indicate the beginning of a format specification. Each format
specification is then related to an argument, arg1, arg2, and so on, in sequence. There should
always be an argument for each format specification and vice versa.
When the format specifications are used, there are some rules and modifiers that apply:
All numeric values are right aligned and left padded with spaces.
If a 0 (zero) character is inserted between the % and d, i, u, x, or X, then the number will be
left padded with 0s.
If a (minus) character is inserted between the % and d, i, u, x, or X, then the number will
be left aligned.
A width specification between 1 and 9 can be inserted between the % and d, i, u, x, or X to
specify the minimum width of the displayed number.

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

If macro_name is a defined macro name, then the #ifdef expression


evaluates to TRUE and the set_of_statements_1 will be compiled. Otherwise,
the set_of_statements_2 will be compiled. The #else and set_of_statements_2
are optional. Another version is
#ifndef macro_name
[set of statements]
#endif
If macro_name is not defined, the #ifndef expression evaluates to TRUE. The
rest of the syntax is the same as that for #ifdef. The #if, #elif, #else, and
#endif directives can be used for conditional compilation.
#if expression1
[set of statements 1]
#elif expression2
[set of statements 2]
#else
[set of statements 3]
#endif
If expression1 evaluates to TRUE, the set_of_statements_1 will be compiled.
If expression2 evaluates to TRUE, the set_of_statements_2 will be compiled.
Otherwise, the set_of_statements_3 will be compiled. The #else and
set_of_statements_3 are optional.
Conditional compilation is very useful in creating one program that runs in several
configurations. This makes maintaining the program (or the product the program runs in) easier,
since one source code can apply to several operating configurations. Another, more common, use
of conditional compilation is for debugging purposes. The combination of conditional
compilation and standard I/O library functions can help you get a program up and running faster
without equipment such as in-circuit emulators and oscilloscopes.
THE #pragma DIRECTIVE
The #pragma directive allows for compiler-specific directives or switches. #pragma statements
are very compiler dependent, so you should always refer to the compiler users guide when using
these controls.
#pragma warn
The #pragma warn directive enables or disables compiler warnings. This would be used to
disable warnings that may be viewed as a nuisance by the programmer. These can include
warnings such as variable declared but never referenced or possible loss of precision.
Disabling the warnings should not be done on a permanent basis; it could cause a useful warning
to be missed, leaving you wondering why your program is yielding bad results. The best course
of action is to declare, code, and cast until the warnings are all gone.

/* Warnings are disabled */


#pragma warn/* Write some code here */
/* Warnings are enabled */
#pragma warn+
#pragma opt
The compilers code optimizer can be turned on or off using the #pragma opt directive. The
optimizer is responsible for improving the compilers generated assembly language output. This
is accomplished by reducing the assembly output size, arranging it to run faster, or both. The
#pragma opt directive must be placed at the start of the source file, and the default is
optimization turned on:
/* Turn optimization off, for testing purposes */
#pragma optor
/* Turn optimization on */
#pragma opt+
#pragma optsize
A program that is optimized for size may actually run a little slower because common code is
converted to subroutines, and subroutine calls replace the common code. A program that is more
in-line (not optimized for size) runs faster because there is no additional overhead from the
calls to and returns from subroutines. If the code optimization is enabled, you can optimize some
of the program, or all, for size or speed using the #pragma optsize directive:
/* The program will be optimized for minimum size */
#pragma optsize+
/* Place your program functions here */
or
/* Now the program will be optimized for maximum execution speed */
#pragma savereg
The automatic saving and restoring of registers R0, R1, R22, R23, R24, R25, R26, R27, R30,
R31, and SREG during interrupts can be turned on or off using the #pragma savereg directive.
This control is used to manually reduce any potential excess overhead associated with saving and
restoring the machine state in an interrupt service routine.
#pragma regalloc
The automatic allocation of global variables to registers can be turned on or off using the
#pragma regalloc directive:
/* the following global variable will be automatically allocated to a register */
#pragma regalloc+
unsigned char alpha;
/* the following global variable will not be automatically allocated to a register and will be
placed in normal SRAM */
#pragma regallocunsigned
char beta;

#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.

Вам также может понравиться