📄 c515.txt
字号:
发信人: reflection (似水流年), 信区: EEtechnology
标 题: C51 Primer (4) Structure and layout
发信站: 南京大学小百合站 (Wed Nov 24 11:56:58 1999), 转信
4 Program Structure And Layout
4.1 Modular Programming In C51
This is possibly not the place to make the case for modular programming, but
a brief justification might be appropriate.
In anything but the most trivial programs the overall job of the software is
composed of smaller tasks, all of which must be identified before coding ca
n begin. As an electronic system is composed of several modules, each with a
unique function, so a software system is built from a number of discrete ta
sks. In the electronic case, each module is designed and perfected individua
lly and then finally assembled into a complete working machine. With softwar
e, the tasks are the building blocks which are brought together to achieve t
he final objective.
The overall program thus has a loosely-predefined modular structure which co
uld sensibly form the basis of the final software layout. The largest identi
fiable blocks within the program are the tasks. These are in turn built from
modules, which themselves are constructed from functions in the case of C.
The modules are in reality individual source files, created with a text edit
or. Grouping the software sections together according to the function with w
hich they are associated is the basis of modular programming.
Using the CEMS engine control system again as a real example, the task of ru
nning the engine is divided into the following tasks:
Task 1
Provide Timed Sparks For Ignition
Task 2
Provide controlled pulsewidths for fuel injection
Task 3
Allow alteration of tune parameters via terminal
Considering Task 1, this is in turn composed of modules thus:
Task 1, Module 1
Determine crank shaft position and speed
Task 1, Module 2
Measure engine load
Task 1, Module 3
Obtain required firing angle from look-up table
Taking module 2, a C function exists which uses an A/D converter to read a v
oltage from a sensor. It is part of the overall background loop and hence ru
ns in a fixed sequence. In module 1 an interrupt function attached to an inp
ut capture pin calculates engine speed and generates the ignition coil firin
g pulse. Module 3 is another function in the background loop and takes speed
and load information from the other modules constituting the ignition funct
ion, to calculate the firing angle. Obviously, data must be communicated fro
m the data collecting functions to the processing functions and thence to th
e signal generation parts across module boundaries.
In this case, the data flows are thus:
Commonly, the variables used are declared in the module that first supplies
them with data. Hence the engine_load would be defined in Module 2 as that i
s where its input data comes from.
In this system the data would be declared thus:
Module_1.c Module_3.c Module_2
.c
/* Global Data Declaration */ /* Global Data Declaration */ /* Globa
l Data Declaration */
unsigned char engine_speed unsigned char advance unsigned
char engine_load
/* External Data References */ /* External Data References */ /* Exter
nal Data References */
extern unsigned char advance extern unsigned char engine_speed extern u
nsigned char engine_load
The most important thing to note is how the data defined in another module i
s referenced by redeclaring the required data item but prefixed with "extern
".
Now, with a complete program spread across many different source files, the
problem arises of how data is communicated between modules (files) and how s
eparate C functions which lie outside of the home module may be accessed.
The next section illustrates how the linkage between modules is undertaken.
4.2 Accessibility Of Variables In Modular Programs
A typical C51 application will consist of possibly five functional blocks (m
odules) contained in five source files. Each block will contain a number of
functions (subroutines) which operate on and use variables in RAM. Individua
l functions will (ideally) receive their input data via parameter passing an
d will return the results similarly. Within a function temporary variables w
ill be used to store intermediate calculation values. As used to be done yea
rs ago in assembler, all variables (even the temporary ones) will be defined
in one place and will remain accessible to every routine.
This approach is very inefficient and would seriously limit the power of C p
rograms, as the internal RAM would soon be used up. The high-level language
feature of a clearly defined input and output to each function would also be
lost.
Similarly, an entire C program might be written within one single source fil
e. As has been said, this practice was common many years ago with simple ass
emblers. Ultimately the source program can get so big that the 640K of a PC
will get full and the compiler will stop. Worse than this, the ideal of brea
king programs into small, understandable chunks is lost. Programs then becom
e a monolithic block and consume huge amounts of listing paper...
There should therefore be a hierarchical arrangement of variables and functi
ons within a program; complete functional blocks should be identified and gi
ven their own individual source files or modules. Use should be made of the
ability to access external variables and functions to achieve small program
files!
The following should help explain:
MODULE1.c: **************************************************************
unsigned char global1 ; (1)
unsigned char global2 ;
extern unsigned char ext_function(unsigned char) ; (2)
/* Utility Routine */
int_function(x) (3)
unsigned char x ; (4)
{
unsigned int temp1 ; (5)
unsigned char temp2 ;
temp 1 = x * x ;
temp2 = x + x ;
x = temp1/temp2 ;
return(x) (6)
}
/* Program Proper */
main() (7)
{
unsigned char local1 ; (5)
unsigned char local2 ;
local2 = int_function(local1) ; (8)
local1 = ext_function(local2) ; (9)
}
end of MODULE1.c ***********************************************************
***
MODULE2.c: **************************************************************
extern unsigned char global1 ; (10)
ext_function(y)
unsigned char y ;
{
unsigned char temp ;
static unsigned char special ; (11)
special++ ;
y = temp * global1 ; (12)
return(y) ;
)
Line (1) declares variables which will be accessible from all parts of the p
rogram. Ideally, such global usage should be avoided but where an interrupt
has to update a value used by the background program, for example, they are
essential.
Line (2) makes an external reference to a function not defined in the curren
t module (block). This line allows all the functions in this MODULE to call
the external function.
Line (3) declares a function which is to be used by another function in this
module. These utility functions are placed above the calling function (here
"main()").
Line (4) declares the variable which has been passed-over by the calling fun
ction. When the variable left "main()", it was called "local1". Within this
function it is known simply as "x". The byte of ram is allocated to "x" only
while the 8051's program counter is within this function. At the closing },
x will vanish.
Line (5) like "x" above, these variables are simply used as intermediate val
ues within the function. They have no significance outside. Again, the byte
of RAM will be re-assigned within another function. However the locals defin
ed in "main()" will always exist as the C program is entirely contained with
in "main()".
Line (6) allows the result of the calculation to be passed back to the calli
ng function. Once back in "main()" the value is placed in "local2".
Line (7) defines the start of the C program. Immediately prior to the point
at which the program counter reachs main(), the assembler routine "STARTUP.A
51" will have been executed. This in turn starts at location C:0000, the res
et vector. Note that no parameters are passed to "main()".
Line (8) effectively calls the function defined above, passing the value "lo
cal1" to it.
Line (9) is like 8, but this time a function is being called which resides o
utside of the current module.
Line(10) links up with line(1) in that it makes "global1" visible to functio
n within MODULE 2.
Line(11) declares a variable which is local to this function but which must
not be destroyed having exited. Thus it behaves like a global except that no
other function can use it. If it were placed above the function, accessibil
ity would be extended to all functions in MODULE 2.
The physical linking of the data names and function names between modules is
performed by the L51 linker. This is covered in detail in section 8.
4.3 Building A Real Modular Program -
The Practicalities Of Laying Out A C51 Program
The need for a modular approach to program construction has been outlined ea
rlier. Here the practicalities of building easily maintainable and documenta
ble software is given, along with a trick for easing the development of embe
dded C programs using popular compilers such as the Keil C51.
4.3.1 The Problem
The simplest embedded C program might consist of just
/* Module Containing Serial Port Initialisation */ /* V24IN537.C */
void v24ini_537(void)
{
/* Serial Port Initialisation Code */
}
/* Module Containing Main Program */ /* MAIN.C */
/* External Definitions */
extern void v24ini_537(void) ;
void main(void) {
v24ini_537() ;
while(1) {
printf("Time = ") ;
}
This minimal program has only one purpose - to print an as yet incomplete me
ssage on the terminal attached to the serial port. Obviously, a single sourc
e file or "module" is sufficient to hold the entire C program.
Any real program will of course contain more functionality than just this. T
he natural reaction is to simply add further code to the existing main funct
ion, followed by additional functions to the MAIN.C source file. Unless acti
on is taken the program will consist of one enormous source file, containing
dozens of functions and interrupts and maybe hundreds of public variables.
Whilst compilers will still compile the file, the compilation time can becom
e greatly extended, meaning that even the smallest modification requires the
entire program to be re-compiled. A monolithic program is usually symptomat
ic of a lack of proper program planning and is likely to contain suspect and
difficult to maintain code.
The next stage in the sample program development is to add some means of gen
erating the time thus:
/* Module Containing Timer0 Initialisation */
/* T0INI537.C */
void timer0_init_537(void) {
/* Enable Timer 0 Ext0 interrupts */
} /*init_timer_0*/
/* Module Containing Timer0 Service Routine */
/* RLT_INT.C */
/* Local Data Declarations */
/* Clock Structure Template */
struct time { unsigned char msec ;
unsigned char sec ; } ;
/* Create XDATA Structure */
struct time xdata clock ;
bit clock_run_fl = 0 ; // Flag to tell timer0 interrupt
// to stop clock
/* External References */
extern bit clock_reset_fl // Flag to tell timer0 interrupt
// to reset clock to zero
/*** INTERRUPT SERVICE FOR TIMER 0 ***/
void timer0_int(void) interrupt 1 using 1 {
if(clock.msec++ == 1000) {
clock.sec++ ;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -