9. High-Level Language 237
to indent each begin from the immediately preceding line(s). This makes it easier to ensure each begin is paired with an end. However, the compiler is oblivious of the style the programmer uses. In this case line 10 is the corresponding end brace. Between lines 2 and 10 is the body of the function summation().
Line 3: There is only one variable that is local to our function. Its name and type are defined here. Thus sum is of type unsigned long. In C all objects have to be defined before they are used. This tells the compiler what properties the named variable has; for example its size (16 bits), to allocate storage and its arithmetic properties (unsigned). At the same time sum is given an initial value of zero. The complete statement is terminated by a semicolon, as are all statements in C.
Line 4: In evaluating sum we need to repeat the same process as long as n is greater than zero. This is the purpose of the while construction introduced in this line. The general form of this loop construct is:
while(true)
{
do this; do that;
do the other;
}
The body of the loop, i.e. is the set of statements that appears between the following left and right braces of lines 5 and 8, is continually executed as long as the expression in the brackets evaluates as non-zero – anything non-zero is considered true by C. This test is done before each pass through the body. In our case the expression n>0 is evaluated. If true, then n is added to sum. n is then decremented and the loop test repeated. Eventually n>0 computes to false (zero) when n reaches zero and the statement following the closing brace is entered (line 9).
Line 5: The opening brace defining the while body. Notice that for style it is indented.
Line 6: The expression to the right of the assignment = is evaluated to sum + n and the resulting value given to the left variable sum. In adding an 8-bit to a 16-bit variable, C will automatically extend to 16-bits – see Table 9.1, lines 14 and 15.
Line 7: The value of n is decremented, as commanded by the -- Decrement operator.3 This is equivalent to the statement n = n - 1; As an alternative, most C programmers would incorporate this into the while test expression thus: while(--n > 0).
3The analogous Increment operator ++ has given the name C++ to the next development of the C language.
238 The Quintessential PIC Microcontroller
Line 8: The end brace for the while body. Again note how the opening (line 5) and closing braces line up. The compiler does not give a hoot about style; this is solely for human readability and to reduce the possibility of errors.
Line 9: The return instruction passes one parameter back to the caller, in this case the completed value of sum. The compiler will check that the size of this parameter matches the prefix of the function header in line 1, that is unsigned long. This returned parameter is the value of the function, i.e. the function can be used as a variable in the same way as any other. Thus, if we had a function called sqr_root() that returned the square root of a constant passed to it (see Program 9.2), then the statement in the calling program:
x = sqr_root(y);
would assign the returned value of sqr_root(y) to x. Line 10: The closing brace for function summation().
We see from Fig. 9.1 that the output from the compiler is assemblylevel code, which can then be assembled and linked with other modules4 in the normal way. To illustrate this process, Table 9.1(a) shows the assembly-level code generated when the C code of Program 9.1 is passed through the Custom Computer Services (CCS), Inc cross-C compiler. This is a low cost C compiler (≈ $100) that can be integrated with MPLAB – see Fig. 9.3. The resulting listing file of Table 9.1(a) shows each line of C source code as a comment together with the resulting assembly-level code. Two minor changes were made to the source code to generate this illustrative listing:
•The function was renamed main() from summation() as each C program must at the very least have a main() function. This root function is similar to any other C function but causes the compiler to set up the software environment – see below.
•The initial #pragma directive tells the compiler to generate code suitable for the PIC16F84 device.
It is instructive to look at how the compiler has translated this program.
long main(int n)
Entry to the main() function is always at the Reset vector 000h. First the PCLATH SPR is zeroed and then execution jumps past the Interrupt vector to the start of the main block of code at 005h. Here the Status and File Select registers are cleared.
4Some of which can be functions hand-coded in native assembly-level language for e ciency, and from libraries supplied with the compiler or bought in.
9. High-Level Language 239
Table 9.1 Resulting assembly-level CCS compiler output after linking. (continued next page).
CCS PCW C Compiler, Version 2.606, 5056
|
|
ROM used: |
23 |
(2%) |
|
|
|
|
23 |
(2%) including unused fragments |
|
|
RAM used: |
8 (12%) at main() level |
|
|
|
|
8 (12%) worst case |
|
|
Stack: |
0 locations |
0000 |
3000 |
00001 |
MOVLW |
00 |
|
0001 |
008A |
00002 |
MOVWF |
0A |
|
0002 |
2805 |
00003 |
GOTO |
005 |
|
0003 |
0000 |
00004 |
NOP |
|
|
0004 |
0000 |
00005 |
NOP |
|
|
0000 |
|
00006 |
................... |
|
#pragma device PIC16F84 |
0000 |
|
00007 |
................... |
|
long main(int n) |
0000 |
|
00008 |
................... |
|
{ |
0007 |
0192 |
00009 |
CLRF |
12 |
|
0008 |
0193 |
00010 |
CLRF |
13 |
|
0000 |
|
00011 |
................... |
|
long sum = 0; |
0005 |
0184 |
00012 |
CLRF |
04 |
|
0006 |
0183 |
00013 |
CLRF |
03 |
|
0000 |
|
00014 |
................... |
|
while(n>0) |
0009 |
0891 |
00015 |
MOVF |
11,F |
000A |
1903 |
00016 |
BTFSC |
03,2 |
000B |
2812 |
00017 |
GOTO |
012 |
|
0000 |
|
00018 |
................... |
|
{ |
0000 |
|
00019 |
................... |
|
sum = sum + n; |
000C |
0811 |
00020 |
MOVF |
11,W |
000D |
0792 |
00021 |
ADDWF |
12,F |
000E |
1803 |
00022 |
BTFSC |
03,0 |
000F |
0A93 |
00023 |
INCF |
13,F |
0000 |
|
00024 |
................... |
|
--n; |
0010 |
0391 |
00025 |
DECF |
11,F |
0000 |
|
00026 |
................... |
|
} |
0011 |
2809 |
00027 |
GOTO |
009 |
|
0000 |
|
00028 |
................... |
|
return sum; |
0012 |
0812 |
00029 |
MOVF |
12,W |
0013 |
008D |
00030 |
MOVWF |
0D |
|
0014 |
0813 |
00031 |
MOVF |
13,W |
0015 |
008E |
00032 |
MOVWF |
0E |
|
0000 |
|
00033 |
................... |
|
} |
0000 |
|
00034 |
................... |
|
|
0016 |
0063 |
0035 SLEEP |
|
|
SYMBOL TABLE |
|
|
|
|
LABEL |
|
|
|
VALUE |
_RETURN_ |
|
|
0000000D |
MAIN.N |
|
|
00000011 |
MAIN.SUM |
|
|
00000012 |
MAIN |
|
|
|
00000005 |
(a): Assembly-level code listing file generated by the CCS compiler.
240 The Quintessential PIC Microcontroller
Table 9.1: (continued). Resulting assembly-level CCS compiler output after linking.
:1000000000308A000528000000008401830192016D
:100010009301910803191228110892070318930AF3
:0E0020009103092812088D0013088E0063005A
:00000001FF
;PIC16F84
(b): Executable Intel machine-code file.
This initialization phase is a feature of the main() function so that the ‘useful’ code can run from Reset in a known software state or environment. A C program typically comprises many functions but only main() will set up this environment.
long sum = 0;
The CCS compiler reserves two bytes for a long object. In this case File 12:13h stores sum low:high bytes. To zero these two GPRs the compiler has generated two clrf instructions:
clrf |
12 |
; |
Clear |
sum_low |
clrf |
13 |
; |
Clear |
sum_high |
|
|
|
|
|
while(n>0){
The compiler has allocated File 11h for the single-byte int object n. n has been given a value by the calling function which has placed a datum in File 11h which this function is going to operate on.
The while statement is implemented by testing n for zero and if true jumping to the the exit return statement.
movf |
11,f |
; Test |
for zero |
btfsc |
STATUS,Z |
; |
IF not Zero THEN skip |
goto |
012 |
; |
ELSE |
to to instruction in 012h (return) |
|
|
|
|
|
sum = sum + n;
This is implemented as an Add a single byte to a double byte operation thus:
movf |
11,w |
; Get |
n |
|
addwf |
12,f |
; Add |
and update |
low byte sum |
btfsc |
STATUS,C |
; |
Skip |
over if no Carry |
incf |
13,f |
; |
ELSE |
increment |
high byte sum |
|
|
|
|
|
|
|
Many C programmers use the alternative statement sum+=n; which states sum augmented by n.
- -n;
Now decrement the single byte in File 11h.
9. High-Level Language 241
In more complicated expressions the placement of the -- decrement operator (and the analogous ++ operator) before or after the object can a ect the outcome. Where it appears before, such as in:
number = --n + 4;
then the value of n is first decremented before being added to 4. In the following case:
number = n-- + 4;
n is added to 4 and then decremented.
In our example the logic of the program is una ected if the operator is pre-decrement or post-decrement. However, the compiler in the latter case adds an extra instruction to bring n down into the Working register before it is decremented in situ as it thinks that some computation involving the original value of n is to be performed.
}
The while loop is repeated by going back to the loop test, which is located starting at 009h.
goto 009
return sum;
At the end of a function returning a long object the CCS compiler places the two bytes in the fixed GPRs File 0D:Eh ordered low:high. Thus this code fragment simply copies the two bytes in File 12:13h; i.e. sum, into the return locations.
movf |
12,w |
; Copy |
low byte sum |
|
|
movwf |
0D |
; and |
put |
in return |
slot |
low |
movf |
13,w |
; |
Copy |
high byte sum |
|
|
movwf |
0E |
; |
and |
put |
in return |
slot |
high |
|
|
|
|
|
|
|
|
Specifically the main() function is terminated by the sleep instruction – see page 256. Normally a function is terminated by a return to the caller function.
The final machine code file is shown in Table 9.1(b) and gives a total length of only 23 instruction including the one-o environment settings. This is similar to a handcrafted assembly-level equivalent in length and therfore execution time.
C-level programs can be compiled and simulated in the IDE environment of Microchip’s MPLAB – see page 221. The screen shot of Fig. 9.3 shows windows into both the C-level source code and the resulting assembly-level code. Although simulation is at the latter level the
242 The Quintessential PIC Microcontroller
Fig. 9.3 Simulating our example program in MPLAB.
C code is highlighted in the appropriate place corresponding to the simulated assembly-level instruction. The Watch window shows the state of the two C objects int n (corresponding to the assembly label MAIN.N) and long sum (i.e. MAIN.SUM). The compiler generates the system symbol _RETURN_ to label the two GPRs File 0D:0Eh and this can be monitored in the normal way.
Using C to implement source code gives the programmer access to structures, operators and libraries appropriate to a modern high-level language. Nevertheless, any coding language of use in an embedded MPU/MCU target must be able to address locations in Data memory and specific bits in a datum. This enables the programmer to get into a SPR and monitor and change flags. In Part 3 of this book we will use both assembly and C codings to interact with internal and external hardware. However, it will be useful to introduce the use of C in a ‘bit banging’ role here.
Consider a program fragment that has to check the state of the Timer 0 (TMR0) SPR at File 01h and if it is decimal 24 zero it. This is how it might be done in C.
9. High-Level Language 243
#define TMR0 *(unsigned int *)0x01
{
if(TMR0 == 24)
{
TMR0 = 0;
}
}
The directive #define (all C directives are prefixed #) replaces the name TIMER0 with the incantation5 *(unsigned int *)0x01 whenever it is used in following text. Normally such directives appear at the beginning of the code and may be #included as a header file in the same manner as Table 8.4 on page 209. The replacement is the number 0x01 – 0x is the C language prefix for hexadecimal. This constant is converted into the form of an address, or in C terminology a pointer, using the cast (unsigned int *). This states reading right to left “pointer to an unsigned int”. In the CCS compiler an int is an 8-bit object; i.e. a byte, and the * operator means “pointer to”. The leading * operator coming to the left of the object reads “contents of”. Thus the expression if(TMR0 == 24) implements the test “if the contents of TMR0 is equivalent to 24 THEN DO the following. The == operator means “equivalent to” and returns true or false. Finally the statement TMR0 = 0 replaces the contents of TMR0 by 00h. The single = operator having the normal arithmetic function of assignment as opposed to the == comparison operation. A table of all C operators is given in Appendix C for reference.
The assembly-level code generated by the CCS compiler for the above C code is:
movlw 16h ; Prepare to compare with 24d (18h) subwf 01,w ; by taking from the contents of TIMER0 btfsc STATUS,Z ; Skip if not equal
clrf 01 ; ELSE clear TIMER0 at File 01
as we would expect.
The C language has the normal bitwise logic operators of AND (&), Inclusive-OR (|), eXclusive-OR (ˆ) and NOT (˜), as specified in Appendix C. As we can access specific Data store addresses these operations can be used to invert, set or clear any bit or bits in any File register as described in Chapter 2. For example, if we wish as part of an interrupt handler function to test the INTF flag (bit 1) of the INTCON SPR then we could use the following code:
5The CCS compiler has the non-standard #byte directive to declare an identifier at an absolute File store address; i.e. #byte TIMER0 = 1.