How to Write Software Without Errors

Published on January 2017 | Categories: Documents | Downloads: 46 | Comments: 0 | Views: 273
of x
Download PDF   Embed   Report

Comments

Content

How to write a program without errors
Introduction
About the error
By profession I am engaged in finding and correcting errors in other people's programs. Over time I
gained some collection of random bugs and tried to combine them in a single table and classified in order
to make some kind of a brochure on quickly finding and correcting the most common mistakes. However,
to make such a classification is not possible. The fact is that by highlighting 5-6 major mistakes, such as
wrong type cast confusion with signed and unsigned expressions neglect to perform the tests of function
arguments, etc. - I saw that the other errors too individual to be as- then generalize. The methods of
searching and say nothing, because it is much greater than errors themselves.
However, each time, correcting any mistake, for myself, I pointed out that it could not do, if something,
something. Over time I have learned that most of the mistakes are made because of the wrong approach
to programming. So the idea of writing the brochure "Localization and Error Corrections" passed into the
idea of writing the booklet "How not to make mistakes." And I think this is correct, because it is better to
learn to build, and not to restore the badly constructed. Most of the tips described me pretty banal and
simple. It's like, everyone knows them, but for some reason many do not adhere to.
What kind of error is involved. Of course, not those which are detected by the compiler translation of the
program. Until we fix these bugs, we will not be able to get the code that we will be able to flash the
controller. These errors are mostly mean that the text does not correspond to the rules of the
programming language, and, in fact, are blots. Also this material would not be desirable for the so-called
"scheduled" errors (wrong processing data coming from the outside, the user error, etc.).We will talk
about the implementation errors, ie, those that will be shown during the program. These errors are the
result of incorrectly programmed (or compiled) algorithm, the tolerance of conventions, inattention and
carelessness.

For whom is this guide
In this tutorial, I set out its approach to programming microcontrollers, led their own rules, which govern
the writing programs, and marked the moments when writing programs that pay special attention. I am
sure that my point of view is not the only one, and that somewhere I myself still have a lot to learn. So I'll
be glad not only if the benefit would be useful to someone, but also if someone decides to have something
to correct or supplement.
This article is intended for a wide range of programmers, so I tried not to touch the concept of evidencebased programming, and generally tried to give more practical advice than theory (which itself on the
"You") and formalism. Also in this tutorial, there is no description of methods to improve reliability
programs (self-test, triple redundancy, cross-checking activities, etc.).
Do not assume that after reading this guide your program will automatically be error-free, and even more
so that future errors will not be at all. Errors will still be, but the percentage of them is greatly
reduced. Moreover, in the writing of programs by the rules set out in this manual, the time will be spent in
two, three, five times more than those without rules. Write programs without errors - it is work, and
painstaking. Just unlike the hard work to find and correct the errors, this type of work can be planned and
formalization, ie is predictable. And the application of the rules herein at times reduce the time of
unpredictable debugging process, which, in practice, often takes much more time than the actual coding.
It is assumed that the reader knows what a controller, how it works, how to turn, finally, what is the
electric current, because without this knowledge is useless to deal with microcontrollers. So, as the old
joke goes: "Teach materiel!"

"Teach materiel!"
This is the most obvious recommendation. About it, you can not say, but for the sake of completeness
casual touch on this topic. A programmer who writes for the controller needs to know circuitry of the
device controller, the programming language in which he wrote the program, and especially the
compiler. One programmer who ignores the need to have this knowledge, loses the right to complain
about the fact that his program does not work.

Circuitry
At a minimum you need to know:
 basics of electronics (digital and analog)
 Ohm's and Kirchhoff's laws
 Typical schematics (RC-chain transistor switches)

Also, you need to know to set out the diode across the coil of the relay, the LEDs are included through a
current limiting resistor that mechanical contacts is such a thing as jitter, etc.

Controller
It is necessary to know the architecture of the controller, addressing modes, registers, and their
destination, peripherals and operation modes, operating temperature ranges, frequencies, feeds, etc., the
load capacity of the ports, etc.
Do not be shy to look into the company datasheets, there are answers to most questions. And that
someone is not working PORTA (while not disabled analog circuits), someone does not receive a "1" at the
output RA4, someone interrupt RB0 only works half the time, etc.

Language
Of course, you need to know the programming language itself.
For this assembly:
 instruction format;
 types and number of operands
For high-level languages are:
 the semantics of the operators;
 Data qualifiers and functions;
 data types;
 type conversion;
 Priorities of operations;
 pointers (or pointers to pointers);

Compiler
Developers of compilers for microcontrollers, aiming to adapt the platform-specific compiler, allow yourself
to depart from the standards of language, at the same time without disturbing them. Each compiler has its
own characteristics, the ignorance of which can lead to difficulties in porting or using other people's
libraries:
 set of directives;
 types of data (dimension, signed);
 qualifiers (near, far, const, rom);
 organization interrupts.

Programming steps
When TOR agreed and formalized task (translated from the problem-oriented requirements engineering:
the input / output data, modes, etc.) begins the process of programming.
1.

Planning (includes design, drafting an action plan, identifying the required resources).

2.

Encoding (recording of the program in machine language)

3.

Debugging (localization and correction of errors)

4.

Testing (performance testing and specification compliance)

5.

Project Documentation

6. Operation and maintenance
The trouble is that many neglect planning, and believe that debugging is displayed only if a mistake was
made in coding, as well as hope from the first to write without errors, and debugging process is not
considered. However, in reality debugging - this is a mandatory process that, in addition, account for most
of the time, effort and nerves. Encode even the most complex and intricate algorithm - it is not difficult,
it's hard to make it work, or if they were written correctly, make sure it is working. Therefore, the process
of debugging and testing the most durable and the most labor-intensive. However, the debug time can be
reduced by making the process more predictable and controllable - namely, the planned program.

Program Planning
For some reason, part of the planning of the program in general neglected. And at best, all of the planning
consists of counting the number of required I / O pin. I recall one old joke: "careless planned work takes 3
times the expected time, carefully planned - only two." It is possible to add: "unplanned work takes all the
time." Someone may say, "I am writing small programs out there that plan?". However, experience shows
that it is better for a small program to spend 10-15 minutes on planning (just paint on a sheet of paper)
than to spend 3-4 days in the search for errors, digging the internet and fighting off accusations of
lamerstvo forums on the net.

Below are the steps to your program :
1.

Paint algorithm

2.

Think through modules

3.

Think through data

4.

Divide the circumference of the controller between processes

5.

Take into account the physical properties of the body kit

6.

Provide for expansion

7. Provide a platform or compiler change
Consider each step in more detail.

Paint algorithm
Algorithm - is the DNA of the program - or rather, its genetic code. If it fails, the program will behave
incorrectly. Often, even in the preparation of the algorithm on paper float some branches that can be
ignored when writing a program "head on". The advantage is that the algorithm is compiled in abstract
terms, are no data types or variables, so you can focus on specific algorithmic moments, not thinking yet
about the details of implementation. In most cases it is necessary to make several algorithms: one
common for the whole program, describing the mode of working and switch between them, and the
algorithms of each mode separately. The detail algorithm will of course depend on the complexity of the
whole program and for each node individually.
The algorithm can be represented in block diagram form, and as a transition graph, plotted diagrams
signals, etc. Do not forget about the requirements for project documentation, for example, if the
documentation required to bring the algorithm in block diagram form, you do not need to draw it first in
the form of a transition graph, and then transferred to a flow chart.

Think through modules
We need to think in advance, which modules will meet our program, or rather, what it will be split into
modules. I will explain the advantages of modularity below, but here are our recommendations how to
split the program into modules.
First, the partition must be made on functional grounds. That is, one need not push the USART module
and output piezoelectric buzzer sound even if the specific implementation, and it seems appropriate. The
fact is that once written module can transfer to other projects, saving yourself from having to write the
same code for many times. What module is functionally isolated, the easier it will be transferred. However,
we should not rush to extremes and do separate modules for data transmission on the USART, for
receiving data from USART, to calculate and compare the checksum of the data received on the USART,
etc. Functionally, the module is complete, but bezysbytochnym.
It is also worth noting that in the division of its future program modules need to take into account the
availability of ready-made modules (or their or others').
Most of the modules can be divided into two types: system (operating signal level and iron) and
algorithmic (operating at data processing mode). A good example - working LCD. Suppose you want to
display information on the LCD HD44780. It is extremely unfortunate decision is one in which the output
function of specific data on the screen is called from the main program, and functions by working with
HD44780, called from the data output will be placed in the same module. Then it turns out that both types
of modules - algorithmic and system - blended into one, much more difficult in the future, for example,
the use of another type of indicator. If we divide the system is clearly functional and algorithmic, then
later change the type of indicator will result for us is just to replace the system module.

Think through data
Also in the planning stage, we define for themselves what data will operate our program. And it must
identify not only the purpose of the requested data and their volume, but also to foresee their location
(ROM or RAM, a specific bank RAM) and scope (for example, we see no reason to make the entire
program output data buffer i2c).

Divide the circumference of the controller between
processes
The peripheral controller modules help simplify some of the software components, and sometimes just
make them possible (for example, without the ADC, we can not measure the analog signal). But often it
happens that the software components that require the use of integrated peripherals, more than there is
on board with the controller. Therefore, the peripherals have to share between multiple tasks (a typical
example - timers). When designing a program to pre-allocate the peripherals between the tasks that will
help you choose the best settings for each module.
(I saw a program in which the misallocation of resources has led to what had to multiplex the control
module and a GSM-GPS-receiver on one USART. Program began to fail, and as far as I know, it did not
lead to a working condition.)

Take into account the physical properties of the body kit
Controller with our program will not live on its own, it will be surrounded by some external schematic
components, specialized chips, etc. When designing a program, you need to consider in advance the
characteristics of all devices connected to the controller, chains and chips.
For example, if the device will be buttons, it should be remembered that the mechanical contacts rattle
and rustling. It should immediately come up against the use of any additional counters and / or timers to
suppress these effects.
It is also worth to remember about "cold" start the outer periphery. For example, we have the LCD, which,
after the supply takes 10 ms for self-initialization, during which he will not hear the commands from the
outside. If this is not taken into account, it may happen that our LCD controller starts initialization about
the same time when the LCD completes its internal initialization. As a result, the LCD will then have time
to samoinitsializirvatsya and start receiving our data, we will not. And when we understand what it is, and
increase the delay before the initiation of up to 20 ms, we were surprised to find that now when you turn
on the power, for example, manages to switch back and forth work, because the new delay uninitialized
state control output keeps them enough so that the current in the coil of the relay had time to grow and
lead to fire.
If this device is envisaged to receive radio data, we must note that there is noise in the channel and that
such a reception signal do not "head" at the fronts and the need, as in the case of processing signals from
mechanical contacts, to get additional counters, timers, etc.
Examples of pro kit you can think of many more: competition on the bus, capacitive load, the external
interrupt sources, etc. All this must be taken into account before the writing of the text of the program,
providing pre-booking procedures and additional resources (memory and speed).

Provide for expansion
In the design of the program should be in advance to make sure that in the event of a significant
expansion in the future functional (and hence the code) will be able to replace the selected controller is
more potent with minimal costs. If we, for example, decided to use in our development of the controller
out of the lineup PIC16, and in the planning stage, we figured that would suit only 16F877, or 16F946, or
16F887 (in short - with a maximum amount of memory), so we chose the wrong line. In this case, you
need to take the PIC18, because it is likely that the program in the selected controller simply will not fit.
Often see on the forums soul cries, "help to optimize the program, but it will not go into PIC18F67J60!»
(Note: the controller out of the lineup PIC18, which has the maximum possible amount of ROM =
128K). This is the result of ill-considered choice of the controller in the planning phase, if this step was
carried out at all.
It is also necessary to consider that when debugging a program, we need some resources (this will be
discussed below).

Provide a platform or compiler change
Here we are, of course, that you need to stuff the program conditional compilation directives that allow
the best use of the capabilities of each compiler, which may have to transfer the program. It is just about
that:
 minimal use of the unique features of a particular compiler , and those pieces of code
that do so are written to block conditional directives (happens is that the compiler will deploy
a simple operation in which those traits, and - bleeding from the nose - you want to make a
brief and beautiful);

 do not use undocumented features of the compiler , some operations may be implemented
in various ways, for example, the operation may shift to the left after his leave of the carry
flag, and can and will change it, either by running the optimization replaces manual shift
anything, either as a result will one of the intermediate results obtained when calculating the
previous expression. In general, there is no guarantee that the carry flag will be set correctly.
 override data types. It is considered that the sign and the first dimension defined in the
standard for each type rather vague (such as HT-PICC18 type unsigned char default, while in
MPLAB C18 - sign, or in the CCS type int - 8 -bit unsigned, and in other compilers - A 16-bit
signed). Therefore, each project would be good to have the h-file overrides types
# If defined (__PICC__) | |

Note two types: S_ALU_INT and U_ALU_INT - it's signed and unsigned integers, with the dimension of the
machine word for a particular controller. Since operations on the elements that have a dimension data
bus, made the most optimal, sometimes it makes sense to use these types of data.
Note : hereinafter referred to as the visibility will be used for the standard types: char (8 bits), int (16
bits), long (32-bit).

Writing a program
When writing the text of the program should be guided by the two sets of rules:
 Encoding Rules
 design rules
The rules themselves can each have their own, but they should exclude the possibility of
misunderstandings.

Coding
 Observe the modularity
 Avoid conventions
 Do not make long and complex expressions
 Operator brackets
 Break and continue statements in nested loops
 Accuracy of the real numbers
 Integer division
 Rules for constants
 Conclude constants and macros operands in parentheses
 Enter into the body of macros in curly braces
 Rules for functions
 Use the watchdog
 Atomic access

Observe the modularity
We have already talked about splitting the program into modules, once again bring the benefits of
modularity :
 Mobility (easy to transfer to another program module)
 Visibility (easy search for definitions of specific functions)
 Substitutability (replacement of one module with other conditions change, for example, by
changing the external equipment)
Once we decided to split the program into modules, then you need to follow this decision to the end and
did not take any action in contravention of modularity. That is, our modules should have the
following characteristics :

 Identity
functions and variables related to one module, to determine precisely within this module. It is
understood that different transfer to another program module will result in that each new
software will override underpayment variables.
 Self-sufficiency
is not used in the module, the external variables of the upper modules. Again, the reason is
that when you transfer the module to another program in the new program will not only
redefine some variables, but also to restore the mechanisms of their work so that the module
behave correctly.
 Flexibility
to
configure
, for example, if it is a module for the bus i2c, then when you transfer to another project
should be able to choose (or set constants) address of the device, the bit address data, the
conclusions to which the device is attached i2c.

Avoid conventions
The program should avoid any construction that when read can be interpreted two ways, and that can,
under certain conditions behave correctly. The most common tolerances that allow themselves to
programmers - it limits the used types of non-compliance, the confusion of signed and unsigned variables,
neglect of type conversion. These tolerances will look at in the first place.

Data Types
It is necessary to define the type of variables that suits their purpose. For example, should not be a
variable that will be used as a numerical, identify the type of char. The number can be either signed char
or unsigned char. The very same char specifies a character variable. It is clear that for the controller, all
these variables - one and the same, but for the compiler, as well as for the person reading the text of the
program - it's two different things.
For example, a common mistake:
Wrong:
char Counter;
Counter = 10 ;
if ( Counter > =

0 ), ...;

This expression is properly processed MPLAB C18, whereas in the HT-PICC18 it will always return true. All
because of a language standard does not specify signed type char, and each developer compiler may
interpret it differently. In this example, the variable must be defined as follows:
Correct:
signed char
Counter;
Counter = 10 ;
if ( Counter > = 0 ) ...;

Cast
It is not necessary to hope that the compiler itself will always cast when the terms involved in the
heterogeneous variables and constants. You should always cast by hand, eliminating the inconsistencies of
expression programmer and the compiler.
Wrong:
int
I;
void * P;
P = & I;
Correct:
P =

( void * ) & I;

In a specific example, we are likely to get the right result, and without a cast (though perhaps the
nuances of single-byte near-pointers for PIC18). But it so happens that a programmer, relying on
automated type, blinked his absence in a more complex expression, for example, the expression contains
sub-expressions in parentheses, in which types will be given only after the sub-expressions, ie when, for
example, the significance is already lost (due to overcrowding).
The same applies to the transfer function parameters.

Byte address to the multi-byte variable
An example of misuse :

unsigned char Lo, Hi;
unsigned int
UI;
... Lo = * ( ( unsigned char * ) & UI + 0 ) ;
Hi = * ( ( unsigned char * ) & UI + 1 ) ;
The fact that the language standard does not provide for the order of alternation of bytes in multi-byte
objects.
Proper handling :
Lo = UI & 0xFF ;
Hi = UI >> 8 ;

Defining Functions
In determining the function should be a fully input and output types. If the function is defined simply:
myfunc ( )
, The compiler defaults to assume that it returns int, and takes no parameters. However, you should not
leave such uncertainty.
myfunc
myfunc
int myfunc
int myfunc

(
(
(
(

)
void )
)
void )

/ / wrong
/ / wrong
/ / wrong
/ / Properly

Let the operator
It should not be in the loop, while , as well as in statements if ... else use an empty statement:
while ( ! TRMT ) ;
TXREG = Data;

/ / waits for the buffer

Inadvertently omitted ';' misbehavior will result in the program. It is better to put in place an empty
statement continue or {} :
while ( ! TRMT )
TXREG = Data;

Continue ; / / waits for the buffer

About the switch statement
In the operator switch to:
 Identify branch default
 In each case set break
 Unused break close comments
Switch ( ... )
{
Case 0x00 :
... Break ;
Case 0x01 :
... / / Break; / / Putting the commented break, we allow ourselves to
understand / / that after processing conditions 0x01 we want to go to the code
/ / processing conditions 0x02, and not break accidentally missed Case 0x02 :
... Break ; Default : / / Mandatory branch, even if you are sure that
/ / expression in the switch always take one of the above / / values Break ;
/ / default branch also Break should end }

Uninitialized variables

You can not use uninitialized variables in the hope that the compiler itself Generate code to initialize them
(for example, resets after a reset).

The parentheses in complex expressions,
In some cases, complex expressions, it makes sense to arrange the brackets, even when there is
confidence in the correctness of the priorities of operations. Such expressions are easier to analyze,
since error can be not only the priority of operations, but also in the expression. It also reduces the
likelihood of introducing errors in the modification of the expression.

"This situation will never happen!"
This topic is generally a long talk. Here's an interesting piece of determining the type of a program sent to
me:
typedef struct
{
unsigned int
unsigned int
unsigned int
} T_CLOCK;

seconds : 6 ;
minutes : 6 ;
Hours
: 4 ,

/ / set to the wrong dimension

Pay attention to the dimension of the fields in this structure. Under a minute and 6 seconds allocated by
bit, to cover the entire range 0 .. 59. And in the hours instead of 5 bits allocated 4. The programmer who
wrote it, suggested that since the program will only run at 8 am, it is easier to select 4 bits (in the range
covering the remaining 16 hours) so that the entire structure has got into two bytes, and the program
itself to always add value 8 hours. Needless to say, that the fault was not long dolzhgo wait?
So do not forget about the first of Murphy's Law: "If the trouble can happen - it happens."

Dead cycles
Most programs are pieces of code that can potentially lead to a crash. The most common example waiting for the availability or confirmation with the external peripherals:
Wrong:
void lcd_wait_ready ( void )
{
while ( ! PIN_LCD_READY )
}

Continue ;

If an unexpected situation (open circuit, short circuit, condensate, etc.), then this series will never get
out. (Is not that only work WDT). Therefore, you should always provide the emergency exit of such
cycles. This can be done using a timer.
Correct:
char
lcd_wait_ready ( void )
{
TMR0
= - 100 ;
/ / Prepare for fixing the timer timeout
TMR0IF =
0 ; while ( ! PIN_LCD_READY ) { if ( TMR0IF ) return 0 ;
/
/ Exit with error code } return 1 ;
/ / Exit OK }

However, a timer to allocate for this sometimes costly, and it makes sense to work with a global variable
that will be reduced to interrupt the timer:
/ / Extract the interrupt handler
if
{

( TMR0IF && TMR0IE )
TMR0IF = 0 ;
TMR0 - = TMR0_CONST;
if

/ / Update the timer

( ! - g_WaitTimer )
/ / Check for overflow
g_Timeout = 1 ;
... }
... char
lcd_wait_ready ( void ) {
g_WaitTimer = 10 ;
/ / Prepare for fixing the timer timeout

g_Timeout = 0 ; while ( ! PIN_LCD_READY ) { if ( g_Timeout ) return 0 ; /
/ Exit with error code } return 1 ;
/ / Exit OK }

By the way, note that in an interrupt handler is not only monitored the particular interrupt flag (TMR0IF),
but also enable bit (TMR0IE). The fact that the PIC-controllers primary and secondary interrupts are
several Family processed by one processor. And if we have an interrupt on TMR0 disabled (TMR0IE = 0),
and we got to the handler from another source (eg RCIF), then without checking bits xxxIE we will treat
all disabled interrupts, who at the time of entry into the handler was installed flag xxxIF.

Do not make long and complex expressions
Such expressions are not only difficult to analyze, but they are also difficult to test and debug.
Wrong :
T = sqrt ( P * R / ( A + b * b + V ) ) + sin ( sqrt ( b * b - 4 * A * C ) / (
1 << SHIFT_CONST ) ) ;
Imagine that the program does the calculations wrong and it is suspected that the problem in this
expression. And this expression even in the simulator do not drive. Such language is desirable to split into
several sub-expressions:
Correct:
A = P * R;
B = A + b * b + V;
C = b * b - 4 * A * C;
D = ( 1 << SHIFT_CONST ) ; if ( B == 0 ) ...;
/ / Error: "divide by 0"
if ( C < 0 ),
...,
/ / Error: "the root of a negative number"
E = A / B;
If ( E < 0 ),
...,
/ / Error: "the root of negative number "
T = sqrt ( E ) + sin ( sqrt ( C ) / D ) ;

Now our expression is not only easy to read, but also easily tested and debugged, and also - and even has
a mechanism to protect against invalid input data (this mechanism could be left with a long expression,
but then some of the expression would have to be recalculated twice ).

Operator brackets
When writing a program fragments that contain nested loops or nested conditional statements, it is
desirable to arrange the curly bracket, even for single-line blocks.
Consider the example of a common mistake:
Wrong:
if ( A == 1 )
else C = 4 ;

if

( B ==

2 ) C =

3 ;

The intention of the programmer was: if A is equal to 1 , then the B , equal to 2 , set the C value of 3 ,
otherwise assign a C value of 4 . That is, programmer believed that C will be entered value 4 , if A is not
equal to 1 . But in fact, the compiler sees it differently: else applied to the nearest if , rather than to
one that is aligned with him in the text. In this case,else relates to the condition if (b == 2) .
Correct this condition had to be written as:
if
{

( A ==

1 )

if ( B ==
}
else C = 4 ;

2 ) C =

3 ;

Break and continue statements in nested loops
A frequent mistake is to use break or continue in nested loops or operator switch , based on the fact
that these operators will work within and outside the loop. Here is an example from a real program (it's a
little cut with, for clarity, and the error immediately catches the eye), which counts the number of positive
and negative items in the array, and the zero element is a sign of the end of the array.
Wrong:
Positive = Negative = 0 ;
for ( I = 0 ; I < MAX_ARRAY_SIZE; I + + )
{
Switch ( array [ I ] )
{
Case
0 :
Break ;
/ / Exit the loop (error as leave
/ / only of Switch)
Case
1 :
Positive + +; Break ;
Case - 1 :
Negative + +; Break ;
} }

Programmer decided to use to verify the design Switch , in which, among other things, in finding the
element with a value of 0 score was interrupted. However, it appears that thebreak in the thread case of
the program will display 0 Switch 'and instead of cycle for .
There are several ways to solve this problem. One option in this case - to use the optional if :
Correct:
Positive = Negative = 0 ;
for ( I = 0 ; I < MAX_ARRAY_SIZE; I + + )
{
if ( array [ I ] == 0 ) Break ;

/ / Exit cycle

Switch ( array [ I ] )
{
Case
1 :
Positive + + ; Break ;
Case - 1 :
Negative + +; Break ;
} }

However, it is not very effective, because leads to the re-calculation of the address of an array element
with indirect addressing. There are other solutions to the problem. The main thing - remember that
the Break / Continue interrupt / continue only the current loop (or Switch ).

Accuracy of the real numbers

When there is a need to work with real numbers with floating point, you need to carefully study the theory
of their structure (mantissa, order, signs). Some programmers who are not familiar with it, with a range of
+ / - 1.7e38 the illusion of omnipotence and the versatility of this type of data. In this kind of overlooked
important details such as the loss of significance, regulation, overflow, the relative error.
In one program I've seen a snippet:
Long L;
float F;
F = 0.0 ; for ( L = 0 ; L < 100000L ; L + + ) {
...;
F = F + 1.0 ;
...; }

(Note: The program has been used in the real dimension of the type 24-bit.)
At the site of ellipsis made some calculations, one of the parameters which were variable f. In this cycle,
the first two-thirds of the results turned out to be correct, and then there were deviations, and, more and
more strongly.
The following happens: when the variable f counts to the value of 65,536 (ie, exceed the limits of 16-bit
mantissa), its exponent (order) increased by one. The weight of the least significant digit mantissa turned
out to be 2. When the addition of two real numbers f 1.0 and at first made to bring them to a common
exponent, resulting in 1.0 turns to 0, as in addition to the two days of the order is aligned in a big way,
that is, weight of the least significant digit mantissa in both operands is 2, while there is a loss of
significance, and the result of addition of 65536.0 + 1.0 will be equal to 65536.0.
It is also worth noting that, in general, for real numbers can not be checked by multiplying the division.

Integer division
Rounding
Often there is so authorized by dividing whole numbers, as if the compiler itself should produce
rounding. So he does not, it should do the programmer. Typical error, for example, in calculating the rate
of USART'a:
Wrong:
# Define FOSC 200000000L
# define BAUDRATE 115200L
... SPBRG = FOSC / ( BAUDRATE * 16 ) - 1 ;
When calculating on paper it looks beautiful:
20000000 / (16 115 200 *) - 1 = 9.85
and rounded up to 10. Error rate in this case is:
E = | 10 - 9.85 | / 9.85 * 100% = 1.5%
That limits.
However, when we instruct the compiler to calculate the formula in the form in which we brought it, it will
produce a round against the rules of mathematics (<0.5 - downwards,> = 0.5 - in most), and just toss
the fractional part. The result will be a mistake:
E = | 9 - 9.85 | / 9.85 * 100% = 8.6%
, I.e. 4 times the permissible.
The same problem pops up when any calculations with integer variables and constants, where there is a
division operation.
In order to correct rounding is done, you need to add half the numerator denominator:
Correct:
SPBRG =

( FOSC + BAUDRATE * 8 )

/

( BAUDRATE * 16 ) - 1 ;

In general formula "A = B / C" by using integers program must be written as:

A = (B + C / 2) / C;
The operation of division by 2 (in terms of C / 2), it is advisable to replace the shift to the right by one
bit. Note that the division by 2 - is the same integer division, but the expression is recorded C / 2 instead
of (C +1) / 2, i.e. is not added to the numerator denominator half. Why? Without going into detail about
the differences of errors when divided by different values (in our example, divide by 2 and divide by C),
will give a trivial example, "to divide 1 by 1." The result of the division should be equal to 1:
(1 + 1/2) / 1 = (1 + 0) / 1 = 1
If we do when divided by 2 in fraction 1/2 we add half of the denominator to the numerator, we get:
(1 + (1 + 2/2) / 2) / 1 = (1 + 2/2) / 1 = (1 +1) / 1 = 2

The sequence of divisions and multiplications
UPDATE another recommendation: if the terms are present operations like multiplication and division, it is
better to rewrite the formula so that the first carried out the multiplication and then division:
Wrong:
A = B / C * D;
Correct:
A = B * D / C;
The reasons are obvious: since the precedence of the operations of multiplication and division of the
same, then the expression is evaluated from left to right, with the rounding will be performed after each
operation. Thus, in the first formula will be multiplied by D not only the ratio B / C, but also a rounding
error.
Consider the example, "2/3 * 3". The result of the expression must be equal to 2. First rewrite this
expression by rounding rules, as described above (in other 2/3 0 will result and the result of the
expression will also be zero)
(2/3) * 3 -> ((2 + 3/2) / 3) * 3 = ((2 + 1) / 3) * 3 = (3/3) * 3 = 1 * 3 = 3
As you can see, we got the wrong answer. Why? Because it has been multiplied by 3 and still a rounding
error ratio 2/3. The error was equal to 1/3, and multiplying it by 3, we got an extra one (the problem also
occurs when rounded down). Was first correctly perform multiplication, division and then, i.e. "2 * 3/3":
(2 * 3) / 3 -> (2 * 3 + 3/2) / 3 = (6 + 1) / 3 = 7/3 = 2
There is one subtle point - that overflow, which when multiplied together some numbers may be even
before the turn comes to the division operator. But it can be made at the design stage, taking the number
of calculations for larger capacity.

Rules for constants
 Do not use numeric constants
 Specify the type of the constant
 Specify constants meaningful values
 Two words about the verification of the correct setting of the constants
 Observe the system value
Do not use numeric constants
We are talking about non-numeric constants in the main part of the code. For example, the program is
supposed to use an array of dimension 25 items. Should not be included throughout the program to
explicitly specify the constant 25.
An example of the wrong approach:
char String [ 25 ] ;
... for ( I = 0 ; I <= 24 ; I + + ) String [ I ] = '';
Correctly in one place in the program to determine the constant, and then in the text to operate only its
name:
# Define STRING_SIZE 25

... char String [ STRING_SIZE ] ;
... for ( I = 0 ; I <= STRING_SIZE - 1 ; I + + ) String [ I ] = '';

The fact is that if for some reason have to change the dimensions of the array, you have to and
throughout the program to seek out all relevant dimensions of parts and fix them.Conceding just one such
piece, you can get but rarely manifests itself quite destructive bug. (Note that the operator used "<= 24"
instead of "<25", I specifically made for an example of such an option, it was clear that a change in the
dimension of the array a simple search and replace can not give the full effect.)
Therefore, all constants, is not conclusive (eg, one minute 60 seconds in a kilobyte 1024 bytes, when
translated into decimal number is always divisible by 10 and so on) should be given the names and the
program only work with named constants.

Specify the type of the constant
Wrong:
# Define FOSC 4000000
# define MAX_INTERATIONS 10000
# define MIDDLE_TEMPERATURE 25
If the definition of FOSC no doubts (compiler clearly understand that this is a 32-bit constant),
then MAX_ITERATIONS will be problems. If somewhere in the code to meet the expression:
( MAX_ITERATIONS *

10 )

, Then it will not be the result of 100,000, and ... -31072. Why? Because the default constant will be
treated as a signed 16-bit integer. Result 100000 goes beyond 16 bits, so it will be truncated to 0x86A0,
which corresponds to -31,072.
The same applies to the constants which must be real. If the program meets expression:
( MIDDLE_TEMPERATURE /

26 )

, The result is 0 instead of the estimated 25/26 = 0.96.
Correct:
# Define FOSC 4000000L
# define MAX_INTERATIONS 10000L
# define MIDDLE_TEMPERATURE 25.0

Specify constants meaningful values
Here is an example from a real program:
# Define BAUDRATE 24 / / 9600
The functions of the peripheral initialization was such a line:
SPBRG = BAUDRATE;
The program was to operate with a clock frequency of 4 MHz. For a constant frequency clock BAUDRATE
should have a value of 25, instead of 24. Thus, the speed error was 4%, which in general, is almost on
the verge of allowable. As a result, sometimes not taken the data that had been expected. In a
conversation with a programmer found that the program worked previously for USART at 19200 (constant
BAUDRATE = 12), but then because of the change of external equipment had to change the speed to
9600, and that the programmer made by multiplying the constant by 2.
But the problem is in the fact that even with the comment "9600" is the definition of the constant may
cause to reflect. After all, if you debug it turns out that something is wrong with the reception /
transmission on the USART, then this constant need to check the formula. Not to mention the fact that
choosing a different speed, or change the clock frequency of the constant need to recalculate. And do not
forget that the program can look not only the author, and for the stranger looking into the code like this is
tantamount to writing your own.
It is clear that in this example, the programmer simply made a mistake with (as was supposed at first to
add one, then multiply by 2, and then subtract 1), but the errors could have been avoided if the constant
wondered meaningful:
# Define FOSC 40000000L
# define BAUDRATE 9600L
Already on assignment register SPBRG translate the value into the desired form with the clock, also given
as a constant.

SPBRG =

( ( FOSC + BAUDRATE * 8 )

/

( BAUDRATE * 16 ) - 1 ;

So, it is better to set the constants in the form in which we expect them to be, that is:
 time specified in seconds, not in ticks 65536 ms;
 pressure - in pascals (or bars), rather than in units of ADC;
 Voltage - in volts;
 temperature - degrees;
 frequency - in hertz.
First, it will make the program more readable, and secondly, to help avoid mistakes that will be the result
of conversions from one system to another. Correct once to configure the transformation formula than
each time it is recalculated constants.

Two words about the verification of the correct setting of the constants
By setting a constant in an understandable form for us, at the same time I would like to be sure that its
value does not exceed the capacity of the controller. For example, BAUDRATE from our example to ensure
the accuracy rate of + / - 2%. Or the pressure that we ask in bars should be such that when translating it
into units of ADC, a value in the range 1023.Therefore, the program should be blocked incorrectly
specified constant conditional directives, and error messages. For example:
/ / Setting
# define FOSC 4000000L
# define BAUDRATE 9600L
/ / calculation errors (in percentage)
# define SPBRG_CONST ((FOSC BAUDRATE + * 8) / (BAUDRATE * 16) - 1)
# define REAL_BAUDRATE ((FOSC + (+ SPBRG_CONST 1) * 8) / ((SPBRG_CONST + 1) *
16))
# define BAUDRATE_ERROR ((100L * (BAUDRATE - REAL_BAUDRATE) BAUDRATE + / 2) /
(BAUDRATE))
/ / check error range + -2% .. 2%
# if BAUDRATE_ERROR <-2 | | BAUDRATE_ERROR> 2
# error "is not set properly constant BAUDRATE"
# endif
It looks difficult, but the difficulty is compensated by the fact that this formula should be tested and
debugged once.
Note. HTPICC in compilers and verification HTPICC18 constant BAUDRATE_ERROR # if directive will not
work (because of a compiler error ). Checks should be done either in runtime'e, or use a different formula.

Observe the system value
When setting the constant need to follow that system of value that is appropriate constant in nature. It
happens, do this:
Wrong:
TRISA =
TRISB =

21 ;
65 ,

/ / RA0, RA2, RA4 - input, RA1, RA3 - output
/ / 65 = 0x41 = 0b01000001

In this case, it is necessary to set a constant in a binary form (although it should be remembered that the
binary representation of the number does not provide a standard C, however, almost all compilers for PIC
realize such a record.) What will be the programmer to change one bit in this record?
Correct:
# Define TRISA_CONST 0b00010101 / / RA0, RA2, RA4 - inputs
# define TRISB_CONST 0b01000001 / / RB0, RB6 - inputs
... TRISA = TRISA_CONST;
TRISB = TRISB_CONST;
In this case clearly shows where the units - there are inputs, outputs zero.
It is also often occurs quite absurd record:
Wrong:
TMR1H =
TMR1L =

0xF0 ;
0xD8 ;

/ / Count 10000 cycles

Well even if it will provide commentary, although, as we have seen in a BAURDATE, comment does not
guarantee the correctness of the constant. Again, great difficulties arise from the translation of
constants. In this case, it is necessary to use a macro (there is subtlety controllers PIC18: TMR1H - this is
the buffer register, and it is important assignment sequence: first the eldest, then - the lowest one
language standard sequence is not provided).
Correct:
# Define TMR1_WRITE (timer) {TMR1H = timer >> 8; TMR1L timer = & 0xFF;}
... TMR1_WRITE ( - 10,000 ) ;

Conclude constants and macros operands in parentheses
A typical error in determining the numerical constants in the form of an expression - do not use brackets.
Here is an example of incorrect identification:
# Define PIN_MASK_1 1 << 2
# define PIN_MASK_2 1 << 5
... PORTB = PIN_MASK_1 + PIN_MASK_2;
This expression compiler to unfold like this:
PORTB = 1 << 2 + 1 << 5;
Let us remember that the priority of the operation "+" is higher than the priority of the shift, and we get
the expression:
PORTB = 1 << (2 + 1) = 5 << 1 << 8
That is, not at all what we expected. Errors can be avoided by taking the expression in the determination
of the constants in brackets, ie
the correct definition:
# Define PIN_MASK_1 (1 << 2)
# define PIN_MASK_2 (1 << 5)
... PORTB = PIN_MASK_1 + PIN_MASK_2;
Is translated into:
PORTB = (1 << 2) + (1 << 5);

From the same area are often made mistake - not bracketing macro operands . For example, we
have a macro:
Wrong:
# Define MUL_BY_3 (a) a * 3
It looks nice and simple, however, try to call a macro like this:
I = MUL_BY_3 ( 4

+

1 ) ;

And instead of the intended result 15 get the result 7. The fact that the macro will unfold in the following
expression:
i = 4 + 1 * 3
Errors can be avoided by taking a macro argument in parentheses:
Correct:
# Define MUL_BY_3 (a) (a) * 3
Then the expression becomes:
i = (4 + 1) * 3;

Enter into the body of macros in curly braces
Theme is similar to the previous one. Consider this example:
Wrong:
# Define I2C_CLOCK () NOP (); SCL = 1; NOP (); SCL = 0;
That is, pauses in a single cycle, then form a pulse two cycles. But such a definition may play a dirty trick
on us, if the macro will be used in the conditional statement:
if

( ... ) I2C_CLOCK ( ) ;

The compiler will launch a macro as follows:
if

( ... ) NOP ( ) ; SCL =

1 ; NOP ( ) ; SCL =

0 ;

As can be seen, only one condition is cut NOP (), and the pulse itself still being formed. Errors can be
avoided by taking the macro body in braces:
Correct:
# Define I2C_CLOCK () do {NOP (); SCL = 1; NOP (); SCL = 0;} while (0)
Such errors are extremely difficult to track because to view everything looks good, and the error will be
sought before the condition after the conditions in terms of conditions, but not in I2C_CLOCK ().
Note that the design used do {...} while (0) . If we did not use it, then setting else in this
condition:
if

( ... ) I2C_CLOCK ( ) ; else

return ;

would lead to a compiler error messages "inappropriate else". The fact is that we else before and after '}'
put ',', which is perceived by the compiler as the end of the operator if.Therefore, the brackets are used in
the form of a do {...} while.

Rules for functions
 Declare prototypes for all functions
 Check the input arguments for the correctness of functions
 Function return an error code
 Do not make a very large functions
Declare prototypes for all functions
For any function declared in a module, you have to declare the prototype of the file so as not to reflect on
calls where the function is defined: above or below the call. In addition, the study of the source code of
the program is immediately gives an overall picture: what is the function in a module, what parameters
they accept and what values are returned. In addition, it is possible to group the most important functions
of the file, and all the accessories to define somewhere at the bottom, so as not to hinder.
In the same header file to make prototypes of those functions that are called from other modules.

Check the input arguments for the correctness of functions
Before performing any operations on the data you need to make sure they are correct:
 Initialized pointers;
 Fall in the range of permissible values (for example, an item number in the array must be less
than the dimension of the array, so that there was no record in the side of the cell), a string
containing the name can not contain numbers.
 Correspond to reality (for example, the count of bits in a byte can not be greater than 8, the
temperature in the room can not be -128 degrees C);
Often, the range of permissible values and the corresponding reality - it's the same thing. However, there
are occasions when additional testing. For example, if the argument is a structure, you can first determine
what the value of each field individually falls in the range of valid values, and then on set to check the
conformity of the data to each other.

Function return an error code
It is desirable to provide for the possibility of any function to return an error code. For example, when
invalid input data, calculation errors (division by zero, overflow) on error with external devices. Moreover,
given that the very error can occur in the lowest level of nested functions, you must provide the ability to
transfer the values of the error up.

To return an error code is sometimes convenient to use the result of the unused values. But there is a
function that returns a result that can take any value from the set of values of the type used. For example,
the multiplication of two numbers, or the function of reading bytes of EEPROM. It may also be difficult
situation when nested functions return the results of different types that can prevent looping error
code. In such cases, it makes sense to use a global variable. However, this option is difficult when using
third-party libraries.
All codes should be pre-defined constants. It is undesirable to use them to determine the
structure enum with uninitialized values, as after the first release will be provided with all the
specifications, where the errors are painted by numbers, and the next version will add an additional error
code (wedged somewhere in the middle of the list), which is why half of the constant change
state. Therefore, they should be defined either through the # define , or through an initialized list of
transfers:
Enum ENUM_ERROR_CODES
{
/ / mathematical mistakes
ERROR_DIV_BY_ZERO = 0x100
ERROR_OVERFLOW,
ERROR_UNDERFLOW,
... / / Errors with EEPROM
ERROR_EEPROM_READ = 0x110
ERROR_EEPROM_WRITE,
ERROR_EEPROM_INIT,
... }
Note that the error codes generated functions - is not the error which can be shown to the user. For
example, "division by zero" on the screen washing machine will hostess in a panic, if the inside of her
favorite blouse. I do not presume to predict the actions of a superstitious man, if he is to bring the
hexadecimal representation of the number 57005.

Do not make a very large functions
This recommendation is similar to the recommendation, "Do not make long expressions." If the function is
obtained long, then it makes sense to split it into several functions. This will give us the following benefits:
 simplify the internal logic functions;
 make it more readable;
 simplify testing and debugging.

Use the watchdog
The watchdog timer controller has three applications:
 waking up the controller from SLEEP with a given inetervalom;
 work hard reset when suspended the program.
 For PIC-controllers young family - perform a soft reset
Consider, to begin the second application, ie Use it as an additional mechanism to protect from freezing. It
is clear that it is better to create the right nezavisayuschie algorithms and describe their correct
nezavisayuschimi programs, but, unfortunately, often in a well-functioning and pereotlazhennoy program
bugs to hide that under certain circumstances lead to a crash. In addition, it can be said that the complex
program with many states and parallel processes can be difficult to debug and test thoroughly. Complete
testing of these programs may be delayed for longer than the time to market cost-effective device. In
such cases, we have to go for a compromise between completeness and timing of release of the test,
ie, the program can get, roughly speaking, nedootlazhennoy. This does not mean that it will fail at every
step and give some results that do not meet specifications. This means that under certain conditions
(usually combined with emergency situations) is possible improper behavior. And here we can help out the
watchdog who will not let the program hang tight. Of course, it is not a panacea for malformed or
incorrectly programmed algorithm. He's just restart the program, but did not correct the
error. Nevertheless, it, at least, can recover the device.

When you need to handle the WDT
In many programs, see the "interesting" reception programmers: the bit configurations included WDT, and
in the program dumped it everywhere, anywhere, just not overflow has occurred: in the main loop of the
program, and to interrupt and delay functions. Will do so - and rejoice: "Oh, what I've written a solid
program! With vochdogom! "This approach is equivalent to the approach without the use of WDT with the
difference that a programmer there is another illusion about the resiliency of its program.

WDT reset task is not trivial at all, in each particular program needs its own algorithm. In the simplest
case, you can set up a separate variable, in which each sub-program will set its bit, and separate handling
routine WDT will check this variable, and only when all the required bits are set, will be cleaning
WDT. However, this method is suitable only for simple program with a small number of states, and with a
limited residence time in the same function.
For complex programs require the development of individual algorithms that would be tracked not only the
fact that some of the features, but also saw to it that the functions performed in the correct sequence,
and that the execution time of each function was within tolerance.
It would like to say a few words about the use of third WDT, ie software reset controllers in younger
families, as they do not have separate instructions, like the RESET PIC18. In systems with high demands
on reliability program should have mechanisms to control the correct operation. That is, monitor the stack,
the order of action execution times of individual units, settings, peripherals, etc. When deviations are
detected which can not be corrected on the fly (e.g., noted that the function does not put the call through
the CALL, and some other means), the only output - it is reset. But if the controllers of middle and senior
families have instructions that perform reset, the youngest of the family in the controllers is the only way
to reset the program - is to give the WDT count to the end.

What if there was a reset by WDT
Procedure for resetting the controller (not only by WDT) - this is a separate issue (quite large). Here I will
describe only a few words.
Do you need two things:
 Restore job (possibly make it invisible to users)
 Determine the cause of the reset.
With the first all clear all the variables corresponding to the current mode of operation, with the status of
processes, some current operating data, and so must be defined in the non-provisionable of RAM, ie to the
one in which the startup code does not write anything at reset. For HT-PICC such variables must be
defined with the qualifier persistent:
persistent unsigned
persistent unsigned

char Mode;
int
lastdata [ 10 ] ;

For MPLAB C18 they must be defined in the section udata, i.e. not initialized in the definition.
In the very beginning of the main reasons you need to check the reset (bit POR, BOR, TO, PO, RI, etc.),
and depending on their condition to make a decision about whether to restore the previous mode,
whether to start from scratch, go to emergency mode, etc.
Now determine why the reset. Much depends on the stage of development of the device and its
availability. Depending on whether we debug device testing whether wild or device operation, depending
on the availability of the device to the developer the ability of the device to notify the unauthorized reset,
the means of its connection to the outside world, and other factors - means of ascertaining the causes
may be different. We're not talking about a technique to obtain data that will help us determine the cause
of the reset of the controller.This can be anything: a dump of the data to a separate page external
EEPROM, send them via any interface for logging, the monitor display (or LCD), etc. I just point out what
kind of data can be useful in general:
 The contents of the stack
 Values pointer registers (FSR)
These data can often be concluded in what location has failed. If you use compilers from microchip to
obtain these data will have to write your own code startup, as proprietary function presets the preregisters FSR (WREG14, WREG15 for MCC30). In each particular case may require additional data: the
current regime, some indicators treatment of critical sections of code, etc. The more information, the
easier it will be to analyze the cause of the reset. But we must also look for a compromise, not to overload
the dump nonsense, but also to prevent the user to panic if the device is already in use.

Two words about the GOTO statement
Many reputable sources is strictly not recommended to use a goto statement to write programs in high
level languages. The main disadvantages of its use is called a strong deterioration in readability of the
program and its violation of structure. Yes, and it's uncertain that we jump through, performing goto (may
be through a variable declaration). It is also often referred to the fact that formally prove that any
program using goto, can be rewritten without it with full functionality. The arguments are very convincing,
and well-known Dijkstra his article "The arguments against the operator goto» all starts with the premise
"... a programmer - a function which depends inversely on the frequency of occurrence of goto statements
in their programs."

I myself do not like to use this operator and experiencing great difficulties with the analysis of other
people's code, where it is applied. However, with respect to microcontrollers with limited resources, I
would have justified the use of goto in some cases.

The output of nested loops
This example is more often than others. Indeed, the use of the goto statement looks pretty simple (and
obvious) decision to exit the cycles of the form (useful, for example, when searching for possible solutions
in multidimensional arrays)
for
{

( I =
for
{

0 ; I <

( J =
if

10 ; I + + )

0 ; J <

( ... )

15 ; J + + )

GOTO BREAK_LOOP;

}
}
BREAK_LOOP :
In fact, when reading this code is straightforward to find the mark, because within the meaning of the
operation clear that it is at the bottom, after the closing parenthesis of the upper loop. Ardent opponents
goto lead two alternative versions of code to get rid of this operator.
Option 1 - rewritten as a function of cycle.
void loop_func ( void )
{
int I, J;
for ( I = 0 ; I < 10 ; I + + )
{
for ( J = 0 ; J < 15 ; J + + )
{
if ( ... ) return ;
}
}
}
Option 2 - use a variable flag.
bool StopLoop = false ;
for ( I = 0 ; I < 10 && ! StopLoop; I + + )
{
for ( J = 0 ; J < 15 && ! StopLoop; J + + )
{
if ( ... )
{
StopLoop = true ;
Break ;
} ;
}
}
(In principle, there are alternatives, but they are context-dependent and, in general, are not applicable in
all cases)
Both versions are fully functional, but they have their little flaws when it comes to work with
microcontrollers that have inadequate resources. The first option is not very successful, because it
requires additional free level of the stack (remember that there are only 8 PIC16, and PIC18 - 32), which
can sometimes be critical. The second option requires the use of an additional variable, which is also
essential for controllers with low RAM. That is, using goto, we are able to save resources controller. (On
saving the rate I am not talking as against the cycle of 10x15 = 150 iterations once the call / return or two
extra checking the flag StopLoop be insignificant).

"Standard" label
Personally, I allow myself to use labels that can be versatile for almost any occasion. Such labels a little
bit. A typical example - the output of a function when an error occurs in the course of its
implementation. The error function can be detected at any stage of its execution (as when checking for
correct arguments, for example, when working with the outer periphery, and a checksum, etc.), while
leaving it requires resources and vacate set flag of the error, so that the normal return we may not
arrange simply because before every return will have to perform the same sequence of actions.
The main thing for me - the location of such labels is always unique (for example, it is clear that the label,
which becomes a function when an error is at the end of the function), they are safe from the point of

view of saving resources relevant (not any convenient programming algorithm of structured programming
techniques, there are cases where such methods make code less readable and more resource-intensive).

Optimization
Some operations, such as calculations or work with fast signals require optimizations for speed. In such
cases, I prefer to use goto statements instead of if ... else , Break , Continue, and so in order to
save run-time, even at the expense of clarity. These code sections should be carefully checked and rechecked and thoroughly commented.

Atomic access
Often the cause of errors in the programs may be an unintended mechanism of atomic access to the
register or variable. The simplest example: an 8-bit controller have 16-bit variable that is acting as the
main program and in an interrupt handler. Conflicts at work with it can occur if, during the reference to it
from the main body of the program, the interrupt handler which also wants to work with her. It's nothing
to worry about when and here and there to turn the variable to be read. But if one of the pieces of code
(or both) is recorded, it can cause problems.
unsigned

int ADCValue;

/ / Read ADC Result (made in
/ / abort
... / / Fragment ISR If ( ADIF && ADIE ) {
ADIF
= 0 ;
ADCValue = ( ADRESH << 8 ) | ADRESL;
/ / Read the last measurement
ADGO
= 1 ,
/ / Start the following }
... / / Fragment of the main body of the program
Data [ I ] = ADCValue;
/ / Note: Data [] - array uint'ov

I think no need to explain what would happen if, when reading ADCValue in the main program (and
reading will happen in two stages: first the low byte, then a senior), an interrupt upon completion of the
measurement ADC? In a nutshell, at the start of reading the variable contains the value
of 257 (0x101). The first is the low byte and low byte is copied to the array Data [I] ; further interrupt
occurs and ADCValue written again measured voltage, which since the last change was, for example, less
than 3 units to junior level, ie 254 (0x0FE).We return from the interrupt and continue to
copy ADCValue in Data [I] ; remains for us to copy the high byte, and he was equal to 0. It turns out
that in the Data [i] will be the value0x001 .
Often seen in people misunderstanding ways to combat this phenomenon. Many programmers find that
they save a qualifier volatile , ie sufficient to define a variable that is accessible in the main body of the
program and to interrupt this:
volatile

unsigned

int ADCValue;

- And the problem will be solved automatically by the compiler level. However, this is not the case. The
qualifier volatile merely tells the compiler that does not need to produce optimized code with the
variable (more on volatile read [9 ]). This in turn will allow the programmer to block interrupts for the
duration of the reference to it, but it must be done manually:
di ( ) ;
Data [ I ]
ei ( ) ;

= ADCValue;

More details about the atomic access recommend reading [6 ].

Registration
 Use convenient tools
 To give meaningful names to IDs
 The text to the same rules
 Implementing source code comments

Convenient tools
A very important component of a successful program - tools. With respect to the writing of the text of the
program - this is primarily a text editor. If it is comfortable, functional and has an intuitive interface, the
formatting will be done easily. In addition to the text, the editor can provide us:
 automatic indentation;
 Change the tabs to spaces;
 syntax highlighting;
 contextual substitution;
 cross-references within the source text or the whole project;
 call the compiler to build the project from the editor
(I recommend SlickEdit).

The naming identifiers
By specifying the names of variables, functions, constants, types, enumerations, macros, files, and so
forth, it makes sense to follow some rules:
 The name should be meaningful (not i, and Counter)
 The name should be meaningful (not Counter, and BitsCounter)
And below we present some specific rules for naming various objects in the program.

Naming functions
The function name in the module, which is expected to be called from other modules must start with the
prefix indicating the name of the module. Why do it? For example, in one program you are using a
memory 24LC64, for which define a function write_byte () . Then you write another program that
works with the internal EEPROM of the controller, and it is also defined a function write_byte () . And
then it took to do the project, which will be used and the external EEPROM, and the inside. This is where
you will encounter the consequences of improper naming, because the modules because of the same
name can not be combined into one program. Therefore, in the beginning it was necessary to provide
prefixes function names (and writing a byte - this operation is universal, it is in operation and the display
and with the modem and another with anything). i2c_write_byte - to work with 24LC64
memory; eeprom_write_byte - to work with Internal EEPROM; lcd_write_byte - for a LCD, etc.
In addition, the function name should be short and, if possible, the system must comply with the naming:
<module> _ <action> _ <object> [_ <suffix>] (may be in a different order, can be separated by a "_")
where:
<Module> _ <action> _ <object> [_ <suffix>]
 module - the name (or abbreviation of the name) of the module in which the function is defined;
 action - a verb that defines the purpose of the function (write, read, check, count, etc.)
 object - a noun, defining a parametric component functions (byte, checksum, string, data, etc.)
 suffix - optional field, reflecting some additional characteristic features (rom, timeout).
Of course, all the function names with the same brush is quite difficult to take, and certainly in any system
of naming will be the exception. But do not call the function like this:
Wrong function names:
CopyOneByteFromROMToRAM
();
Check
();
CompareAndGetMax ();
In conclusion, I must say that some of the features may be left reserved names: atof (), init (), printf (),
etc.

Naming constants
 Capital letters
 Words are separated by '_'
 Prefixed by the name of the module or functionality

/
#
#
/

/ Parameter Definitions I2C bus
define I2C_DEV_ADDR 0xA0
define I2C_ADDR_WIDTH 16
/ RGB color definitions

# define CL_RED 0xFF0000
# define CL_YELLOW 0xFFFF00
# define CL_GREEN 0x00FF00

Naming types
 Capital letters
 Words are separated by '_'
 As a prefix «T_», «TYPE_» etc.

typedef struct
{
unsigned Long
unsigned Long
unsigned Long
} T_CLOCK;

seconds : 6 ;
minutes : 6 ;
Hours
: 5 ;

Naming variables
 Words beginning with a capital letter
 Written without a separator
unsigned
signed

char
Long

BytesCounter;
XSize, YSize;

On the "Hungarian notation"
Some use a naming system in which each variable is assigned a prefix indicating its type. This can often
prove to be useful in the analysis of others (and often their own) code. For example:
for

( Counter =

0 ; Counter <

40000 ; Counter + + ) ...;

In this example, we can not say whether we use a variable in a given cycle or not. Counter variable can
be, firstly, the 8-bit and secondly, it may be a sign (i.e., always be less than 40000). And in order to
determine the proper use of a variable, we need to find the definition of a variable in the file. But if we
had originally envisaged in the type of a variable name, it will save us from the breaching of extra work:
for

( wCounter =

0 ; wCounter <

40000 ; wCounter + + ) ...;

Now, looking at this record, we can definitely say that there are no errors.
Also, the system can provide a naming scope of a variable.
Prefixes:
 Prefix scope
 No prefix - local or function parameter
 S_ - Static
 M_ - LAN module
 G_ - Global
 I_ - Processed in breaking
 Type prefix
 uc - unsigned char
 SC - signed char
 UI - unsigned int (n)
 Si - signed int (W)
Etc.
The name of our variable might look like:
static

unsigned

char s_ucRecCounter;

am,

Meeting her anywhere function, we immediately understand that:
 This variable is used to count the bytes received;
 This variable is of type unsigned char, ie can be 0 .. 255;
 This variable is static, ie retains its value after exiting the function.
Note . You can discover to have a set of reserved names for variables of general purpose, which occur
most frequently. For example: I, J - signed int; A, b - char; F - float; etc. But it is desirable to coordinate
these names, first names with the applicable literature, and secondly, within its own development te
that was not so, that one i - signed int, and the other i - unsigned int.
Benefits of Hungarian notation :
 Prevents the use of error types

 In a large code helps to avoid confusion in the variables
 Easier to read other people's code
Disadvantages of Hungarian notation :
 Degrade the readability of the code
 Expression of 3-4 variables is difficult to read
 The prefix can get very long
 Changing the type of a variable causes a change in its name to all files
 The prefix does not guarantee the correctness of the job type, because of what may turn out
false confidence in the correctness of the application of a variable:
static signed char S_ u cBytesCounter;

Text Formatting
It is obvious that the formatting you need for the program to be more visible. I recommend as an example
to read the document an_2000_rus.pdf , which describes the formatting of the text as formulated for
employees micrium. Not sure exactly follow the rules in it, but should look at what the authors focused on
the document and take it into service:
 Follow one style
 When working in a team as a team to follow the same rules
 Does not justify the lack of formatting the lack of time
I will not go into this document, but only briefly give you the highlights:

The text file should be divided into sections
When we take up a book, we know that it has in addition to the content part is masthead, table of
contents, graduation data, and often even the index, a reading list. In addition, we know that each
element of each of these groups is in one place rather than scattered throughout the pages. All these data
make it easier perception of the book: on the title page in a few words to say what (sometimes - for
whom) this book, the table of contents to quickly find the information interesting chapter on the content to understand the content, etc.
To make the text easier to read the program, it should also be broken down into parts (or sections), each
of which has its own meaning:
 Header file (with information about the file's purpose, the author of the text used by the
compiler, date, etc.);
 The history of changes
 Section include files
 Section defining constants
 Section macro definitions
 Section type definitions
 Section definitions of variables (global, then local)
 Section function prototypes (global, then local)
 The section describing functions.
For sections has its own rules:
 Each section should contain only those descriptions that correspond to it
 Sections should be united, and not scattered throughout the file.
 Each section must be preceded by a comment block is well marked with the name of the section.

Horizontal spacing
Horizontal spacing serve to underscore the visual structure of the program. I'm doing a block of
statements indented by 4 spaces to the right of the upper unit.

Vertical alignment
When defining variables: types - by types qualifiers - under the qualifiers, the variable names - under the
names, attributes - attributes under. In the program: when you assign a block of variables, it is desirable
to align the '=' sign.
static unsigned
static

char
int
double
char

BytesCounter;
Timer;
Price;
* P, C;

... {
BytesCounter = 0 ;
Timer
= 0 ;
Price
= 1.12 ;
}

I want to separate attention to the fact that the '*', indicating that the variable is a pointer is placed next
to a variable, not the type. Compare the two records:
char *
P, C;
and char * P, C;
The first entry may be mistakenly read as follows: variables p and c are of type char *. In fact, the pointer
will only p. The second entry is clearly reflected.

Do not make a line more characters than can fit on one screen
For example, if the function contains a lot of parameters, each parameter makes sense to write a new
line. Long expressions can also be split into lines.
int sendto ( SOCKET s,
const char
int
int
const struct sockaddr
int
) ;

* buf,
len,
flags,
* to,
tolen

Let me draw your attention that in determining the function of a few lines of vertical alignment of the rule
is not to use to the fullest. Try to rewrite this function, setting a qualifiers qualifiers, etc. It will look quite
ugly and completely unreadable, so in this case it is better to start with a definition of one column.

One line - one action
Separate functional units or structures (for, if, ...) blank lines
Gaps between the operands and operations
The following is an example of formatting the last three rules:
Wrong:
for ( I = 0 ; I < 10 ; I + + ) A [ I ] = 0 ;
if ( J < K ) { A [ 0 ] = 1 ; A [ 1 ] = 2 ; } else { A [ 0 ] = 3 ; A [ 1 ] = 4
; }
Correct:
for

( I =
A [ 0 ]
A [ 1 ]
} else {
A [ 0 ]
A [ 1 ]
}

0 ; I <
= 1 ;
= 2 ;

10 ; I + + ) A [ I ]

=

0 ; if ( J < K ) {

= 3 ;
= 4 ;

Comment
Why not write comments
 "Time is running out, there is no time to write"
 "This is a temporary code, it does not need to comment»
 "I remember it all"
 "My program is clear and no comment"
 "In the code, except me, nobody looks"
 "Comments make the text difficult to read and colorful of the program"
 "I then otkommentiruyut"
For whom comments are posted
Comment programmer is primarily written for the programmer himself. For the lack of comments is
necessary to pay time. More often - his, at least - a stranger.

Comments are

Often sent me to see a program that places a comment written only for him to be there. A feeling that the
person knows that it is necessary to write a comment, but does not know why.
What should be in the comments:
 Specification functions:
 making;
 input and output parameters (type and brief explanation);
 Appointment of a declared variable, defined constants, like, macro;
 Short, succinct, non-redundant description of the action or explanation to it;
 Tagging changes:
 version (or date) and the number of notes.
 Causes and description of the change is advisable to keep in a separate place, because,
first of all, the text description of the causes of changes and produced actions may turn
out to be cumbersome and clog the main text of the program, and secondly, one reason
may result in changes in several areas program.
 Note debug units and temporary structures
Example function specification:
/ ************************************************* ************
*
Function *: rs_buf_delete
*
------------------------------- * ----------------------------*
description *: Remove the N bytes of buffer
*
parameters *: uchar N - removed number of bytes
*
* on return: void
*
********************************************* **************** /
void rs_buf_delete ( uchar N )
{
...
Example markup changes:
/ ************************************************* ***
* Changes
* ...
* Version 1.6 (22.10.2009):
* 1. ...
* 2. ...
* ...
* 8. Ivanov II, 17.09.2009: In the function
* rs_buf_delete added check
on input argument * 0
* ...
************************* ************************** /
... void rs_buf_delete ( signed char N ) { / / * 1.6-8 * if (N <0 ) return; if
( N <= 0 ) return ;
/ / * 1.6-8 *
...

Example for the debug unit:
void rs_buf_delete (signed char N)
{
if (N <= 0) return;/*D*/ DebugCounter++;
/*D*/ PIN_DEBUG =1;
/*D*/ delay_ms(5);
/*D*/ PIN_DEBUG =0;
...

What in the comments should not be:
 Emotions
RB0 =

1 ;

/ / Without this crap does not work.

 Descriptions of obsolete acts
if

( BUFSIZE >

0 ) / / buffer is not empty, permission flag
/ / output is set

 Duplication of activities
BUFSIZE =

0 ,

/ / Zero variable indicating the
/ / size of buffer

 Useless information
if

( N <

15 )

/ / The best option comparison

 Incomprehensible acronyms and jargon:
A = X / Y + Z;

/ / B (* 1 *) t.n.u., bypassing

 False or misleading information:
if

( timer <

100

&& V <

10 ) / / If the voltage for 100ms
/ / was above 10 volts

Location Comments
Should not interfere with the code and text comments in a heap. For example:
Wrong approach:
/ * Initialize the port * /
PIN_DATA = 0 ;
PIN_CLC = 0 , / * Clear the buffer * / for ( I = 0 ; I < BUF_SIZE; I + + )
Buf [ I ] = 0 , / * Wait for data * / while ( ReadData ( ) ) {
...

Comments should be written so that they did not merge with the code. One of the options - bring them to
the field, aligning vertically.
The right approach:
PIN_DATA = 0 ,
/ * Initialize the port * /
PIN_CLC = 0 ; for ( I = 0 ; I < BUF_SIZE; I + + ) Buf [ I ] = 0 ,
/ * Clear
the buffer * / while ( ReadData ( ) ) / * Wait data * / {
...

Multi-line comments
When writing a multi-line comment (for example, describes the function), it is necessary that each line
started with a, meaning that this is a comment.
Wrong approach:
/ * This function reads the initial settings of the EEPROM,
a checksum. If it has
converged, then the installation will be accepted by default. * /
for ( I = 0 ; I < BUF_SIZE; I + + ) ...;
The right approach:
(Not very convenient for you: because of the presence of such a limiter right to edit comments tedious)
/ * This function reads the initial settings of the EEPROM, * /
/ * checking the checksum. If the checksum is not * /

/ * has converged, then the installation will be accepted by default. * /
for ( I = 0 ; I < BUF_SIZE; I + + ) ...;
(Alternative)
/ *
This function reads the * initial set of EEPROM,
* checking the checksum. If it has
converged *, then the installation will be accepted by default.
* /
for ( I = 0 ; I < BUF_SIZE; I + + ) ...;
/ / This function reads the initial settings of the EEPROM,
/ / checking the checksum. If the checksum is not
/ / converged, it will be accepted the default installation.
for ( I = 0 ; I < BUF_SIZE; I + + ) ...;
It is often convenient to use the horizontal separators for visual separation of logically different blocks in
the program:
/ / ------------------------------------------------ or
/ ************************************************* * /

The content of the comment
Let us remember that we talked about naming identifiers, and the use of named constants. Consider this
example:
Switch ( result )
/ / Check the status of the modem
{
Case 0 :
/ / Modem is turned off
... Break ;
Case 1 : / / The modem is ready for use
... Break ;
Case 2 : / / produces a dial-up modem
... Break ;
... }

If using meaningful names (and variables and constants), then the section area of the program would
have been understandable and without comment.
Switch ( ModemState )
{
Case MODEM_OFF :
... Break ;
Case MODEM_READY :
... Break ;
Case MODEM_CALLING :
... Break ;
... }

Thus, we have the opportunity to explain any comment nuance some situation or particular action.

Formulation
Tip : formulating a comment, always present, as if the other person is reading a comment. This will help
to make the wording clearer.

Debugging and Testing
Debugging, as well as testing - an integral part of the development. I took these concepts to one section,
because typically, these steps are made parallel. I must say that these two phases are complementary.
Consider the following points:
 Tools

 Provision for resources
 Plugs and testers
 Warnings when compiling
 Debug output
 Lock debug output
 Backup

Tools
In the arsenal of the programmer can be quite powerful tools: simulation, in-circuit debugger
log. analyzer, oscilloscope, etc. In general, it is unknown how many errors need to be detected and
corrected, so the process of debugging and testing can be very lengthy. Here, the availability of good tools
and the ability to work with him to help save time and make the process easier. So do not skimp on the
debugging tools.

Provision for resources
It is desirable also in the planning stage to provide the opportunity to take to debug controller with a stock
resource. Why do you need them? In the process of testing or debugging a program, we may need to
monitor the progress, or some internal variables. For this we need a supply of:
 Peripheral options
 memory to store the debugging code
 reserve for speed

Margin along the periphery
The inner periphery of the controller
There lots of options. At least - to have free terminals of the controller to be able to install them on the
logic level corresponding to the state of the program. If the device does not plan to use the module
USART, it would be convenient to leave the free terminals of the controller to which the module is
working. Then the debug information can be drained using the hardware capabilities of the controller. If
using USART still planned, and debugging information still want to send via RS-232 to a computer, you
can donate the system time and make the output function of software on any free output of the controller
(input function is not needed as often as output function is implemented quite simply ). Often it is possible
to throw off the debug information in the general flow of data (UART, over the air).
Also for debugging and testing may need a hardware timer to determine the speed of some sections or
reaction time for the event. So if there is a possibility during planning to book a hardware timer for the
needs of debugging, it is best to do it.
Outer periphery
For example, if you use the LDC, it is possible to provide a segment to output debug information. In the
EEPROM can provide a separate page, where to save the state of the controller after a reset, if you start it
becomes clear that the discharge was an emergency (for example, there can record the contents of the
stack, let the pointer itself and not preserved).
It is also possible for the board to provide additional buttons, extra LEDs.

Memory to store debugging code
Here, everything is clear: debugging code takes place in the ROM, and often requires some RAM-memory
cells. And it will be a pity if, because of debug functions and macros will not fit in the main program.

Provision for speed
Of course, routine debugging output is not performed immediately. It may happen that due to its
withdrawal program would simply not have time to work out its own algorithm. This should be considered
when choosing a clock controller, and when writing debug routines.

Plugs and testers
Stub functions
The testing can be useful to so-called stub - hollow functions corresponding to the specification, but a
substitute for calculations (or the results of some other operation) is certainly the right result. When it can
be helpful:
 When debugging need the results of another unwritten routines.
 When testing in the simulator, where there is no opportunity to work with some external devices
(eg, GPS);
 When testing code that contains the long-running functions are not involved in the test routines
(a typical example - the delay)

Function testers

When we are writing a new feature, we want to make sure that it works. It makes sense to write a small
function that will be testing a new, repeatedly running it, passing the test parameters. A further result is
possible, depending on the purpose of the function, observed visually or compare with the pattern. Such
functions can generate for our program a sequence of input parameters that can occur in the real world. It
can also be particularly useful when the program being debugged or tested in a simulator. It is important
that the function is triggered when testing it on your controller, or an analogue thereof (and not Builder C
+ +, Visual C + +, etc., which can be debugged just the layout functions), as every compiler has its own
characteristics (data types, automatic type conversion, working with the stack, and so on), each processor
- its architecture (Garvadrskaya and Neymonovskaya), the amount of available memory, etc.
The most difficult place in the scheme - the template. After all, we wrote a new function is to it we figured
this result. Where to get the pattern to match? There are three ways:
1.

In some cases it is possible to compare the results to form a third-party function. For example,
we know that the printf function because of its versatility is quite cumbersome, and not in
every case, the programmer can afford to use it, taking away the controller almost half the
memory and a few thousand cycles. So frequent, when the programmer writes his mini-printf,
which satisfies the requirements of the specification by the program. To test the efficiency of
its function in the testing phase, he can enjoy the benefits of the built-in function
printf. Moreover, this function can be tested automatically. The same method is suitable for
the verification of mathematical functions.

2.

The input parameters and output drive manually. Quite time-consuming process, so first of all
it is desirable to test the boundary values.

3.

Third Embodiment - taking data from the outside (for example, in automatic mode, PC)

Warnings when compiling
A serious mistake that programmers - is ignoring the warnings. Moreover, they are treated like annoying
flies: "Well, finally put together a program. Only your stupid warnings scored all the stats that did not read
it! "Meanwhile, the compiler complains not just. Here it is not our enemy, and an assistant. He reports that
the program has a structure as interpreted in two ways, which he broadcast in its sole discretion. The fact
that you can make a mistake, even writing it right from the point of view of language. For example, the
program can meet such expressions:
if ( A == b ) ...;
if ( A = b )
...; / / Warning: Assignment Inside Relational Expression
Depending on what we want to do, we can apply the first or second term in the parentheses of the
operator if. In the first expression, we check the variables a and b to equality, while in the second we
check a variable to 0 after assigning a value of the variable b. In general, the use of the second entry is
not recommended because much more obvious:
A = b;
if ( A ) ...;
That's why the compiler and gives us a warning that, in terms of the relationship is the assignment
operator, thus giving to understand that perhaps we had in mind the relational operator
"==". (Sometimes, though, there is a second entry point used when, for example, the code is crucial to
the run-time).
Also a frequent consequence of compiler warnings is sloppy attitude to cast. For example, the same
comparison operation:
signed
int
A;
unsigned int
b;
... if ( A > b ) ...;

/ / Warning: signed and unsigned comparison

we will get a warning if one variable is the sign, and the second - no. This warning is not talking about
that "caution! It may be a conflict, "and that the wrong types are selected by the programmer. If the
programmer confident in the correctness, it must make a cast by hand:

signed
int
A;
unsigned int
b;
... if ( ( signed Long ) A > ( signed Long ) b ) ...;
Thereby enabling the compiler to understand that the programmer aware of inappropriate types. We draw
attention to the fact that the cast is made to the sign type of larger capacity.
(In general, once again, with expressions that involve the variables of different types, you have to be
careful. Example, MPLAB C18 will not issue a warning to our example, that in turn will lead to incorrect
behavior of the program if they have a negative value.)

What if the compiler to emit a warning?
First of all you need to decide why he issued a warning. If the text is not clear, then you need to consult
the documentation for the compiler and read more information (often in the description of warnings are
the most common causes of their occurrence). The second - to bring the code to uniquely interpreted by
sight.

Debug output
In forming the debug information should be remembered that the error information to display to the user,
and information about the behavior of the program, withdrawn for a programmer - it's completely
different. Moreover, both in content and in the way of informing.
Users do not need to supply the hex codes on the display, and then on the phone to explain that it is
"variable mode took a strange value, now think about why that happened." Also, do not be in the final
version of the program to post all sorts of flashing LEDs and screams piezoelectric buzzer that when
debugging the programmer talked about a program's behavior or the course of the computation (and then
on the phone to explain that if the bulb blinked three times with 50 ms, it is not converged
checksum). That is, all debug information to be displayed to the programmer should be hidden from the
user (record in EEPROM, the transfer of the error code on the center console, if there is one).

Lock debug output
We must remember that the ability to output debug information is likely to have to leave the controller for
a lifetime. Sometimes a well-established and pereotlazhennye programs may be wrong. However, you
need to have the ability to disable:
 debug information (like the whole crowd, and individual nodes);
 plugs
 Testers (good idea to keep them in the program in the event of symptoms of errors during the
operation)
Block in two ways:
 at compile time, blocking all debug nodes conditional compilation directives:
# Define DEBUG_ENABLE
... / * D * / # ifdef DEBUG_ENABLE / * D * / PIN_DEBUG = 1 ;
/ * D * / # endif

(Then commenting out the definition DEBUG_ENABLE, we remove all of the code debugging
component). This method saves resources of the controller.
 on the software and hardware level (for example, controlling the state of the port):
if

( PIN_DEBUG_ENABLE ) PIN_DEBUG =

1 ;

(Then we can enable / disable debugging routines work in progress.) This method requires resources, but
it allows you to debug the device in real-world conditions.
The same goes for locking down the stub and functions checks. For example:
# Define DUMMY_GPS_IN
... char * GetGPSData ( void ) { # ifdef DUMMY_GPS_IN char test_string [ ] =
"$ GPRMC, A, 123456 , ... ";
return test_string;
# else / * Here code to work with real data from the GPS * / # endif }

In the last example, you can sometimes do without the # else, because within this block will be a fairly
large code, which is inconvenient. Of course, these blocks in the code should be visually isolated.

Backup
In the course of tracking (and sometimes development) programs need to back up, noting the date,
version number changes and compiler version. Moreover, the version should be preserved as a whole,
together with project files and HEX'om. It so happens that a small change results in a loss of the code
works, and having no backup is often difficult to determine which of the latest models has led to such
consequences.
However, to keep their entire version on every little modification quite expensive (both in time and
labor). Therefore, to solve these problems it is convenient to use special tools: version control systems
(VCS - version control systems).
Version control systems provide two main processes:
 the interaction of the developers working on the same project
 Maintain the current "slices" of the project in the database.
There are various implementations VCS: commercial and free, using a central server and distributed
storage. One of the most common - a free version control system Subversion open source. That it be
recommended for initial familiarization with VCS.
Subversion (or SVN) is a client-server system. The project database or repository is stored on a server,
and the developer is working with a local copy of the project, which "gives" him to the client
application. After working with a local copy, the developer retains the server changes to the project - thus
ensuring safe operation of the group and complete control over the proceedings. At any time, you can
remove one of the "slices" (in the terminology of SVN - revision) of the project and see the history of
changes, and also to compare the different versions. The most popular program Subversion client for
Windows - TortoiseSVN .

References
1.

E. Yoda "Structural design and construction programs," Mir, 1979

2.

B. Kernighan, R. Pike, "The Practice of Programming"

3.

A. Golub, "Enough Rope ... to shoot yourself in the foot"

4.

http://micrium.com/download/an2000.pdf - the formatting of the text of programs from
micrium (translation into Russianhttp://andromega.narod.ru/doc/micrium_an_2000_rus.pdf )

5.

S. McConnell, "Code Complete", Russian edition, 2005

6.

http://www.pic24.ru/doku.php/articles/mchp/c30_atomic_access - article about the atomic
access

7.

http://www.devdoc.ru/index.php/content/view/debugging_p1.htm - an article on debugging
and testing applications C + +. Though not related to the controllers, contains a lot of useful
tips.

8.

D. Van Tassel "Style, design, efficiency, debugging and testing programs," M. "The World",
1981

9.

http://wiki.pic24.ru/doku.php/osa/articles/volatile_for_chainiks - article "volatile for Dummies"

Viktor Timofeev, November 2009
[email protected]

Sponsor Documents

Or use your account on DocShare.tips

Hide

Forgot your password?

Or register your new account on DocShare.tips

Hide

Lost your password? Please enter your email address. You will receive a link to create a new password.

Back to log-in

Close