Файл: The quintessential PIC microcontroller (S. Katzen, 2000).pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 15.06.2025
Просмотров: 5328
Скачиваний: 0
144 The Quintessential PIC Microcontroller
The maximum delay is when COUNT is initialized to zero which gives an e ective value for N of 256.6 In this situation the number of cycles in this code fragment is 767, plus the few cycles calling the subroutine (2˜), clearing COUNT (1˜) and returning from the subroutine (2˜), giving a total of 772˜. With a clock rate of 4 MHz, each cycle takes 1 µs (see Fig. 4.4 on page 87) giving a total ceiling 772 µs delay.
Program 6.1 A 100 ms delay subroutine.
; ************************************************************
; * |
FUNCTION: |
Delays for around 100ms with a 4MHz crystal |
* |
||
; |
* |
ENTRY |
: |
None |
* |
; |
* |
EXIT |
: |
Flags and W altered; Files 30:1h zero |
* |
; ************************************************************
COUNT_H |
equ |
30h |
; |
2-byte counter |
COUNT_L |
equ |
31h |
; |
at File 30:1h |
Nequ d’130’ ; Delay parameter
DELAY_100MS |
|||
movlw |
N |
; Set up high count to 130, 1˜ |
|
movwf |
COUNT_H |
; 1˜ |
|
clrf |
COUNT_L |
; and low count to 256, 1˜ |
|
D_LOOP |
|||
decfsz |
COUNT_L,f |
; Decrement LS count to zero |
|
goto |
D_LOOP |
; taking in all H*[(256*3)-1]˜ |
|
decfsz |
COUNT_H,f |
; Repeat for the MS byte |
|
goto |
D_LOOP |
; taking in all (H*3)-1˜ |
|
FINI |
return |
||
Program 6.1 uses two register files to extend this count. Whenever COUNT_L has decremented to zero, COUNT_H is decremented. This inner loop takes (256 × 3) − 1 cycles in the normal way and is repeated H times, where H is the initial setting of COUNT_H. Only when this second byte reaches zero is the outer loop exited and execution returned to the caller. This second count contributes (H × 3) − 1 cycles to the total. We thus need to determine the unknown value of H to give a total of 100, 000 − 7 cycles, taking into account the call, return and the three setting up instructions.
H × [(256 × 3) − 1] + (H × 3) − 1 |
= 100, 000 − 7 |
|
767H + 3H − 1 |
= |
99, 993 |
H |
≈ |
130 |
This gives an actual delay of 100.107 ms; an error of less than 0.11%. Each incremental change in H gives an alteration of ±770 µs.
The maximum delay available with this structure is 197 ms; however, adding one or more nop instructions at the beginning of the inner loop
6If N is zero then it will decrement 00 → FF → FE → · · · 01 → 00 and exit.
6. Subroutines and Modules 145
will increase the total delay by H × 256 cycles and the same technique used in the inner loop adds H cycles for fine tuning. Example 6.3 gives an example of a very long triple count delay subroutine.
Our 100 ms delay program is an example of a double-void subroutine, in that no parameters (cf. signals in our hardware analog) are sent to it and nothing is returned – just the side e ect of a delay (and the alteration of two register files, W and some Status register flags). Most subroutines process parameters made available at entry time and provide data at return time.
As a simple example, consider the extension of Program 6.1 to give a delay of K × 100 ms, where K is a byte parameter ‘sent’ by the caller. The system view of this function is shown in Fig. 6.5 as a single input signal of range 1– 256, with no output signal – that is with a void output. This diagram also documents the location of all local variables used internally by the subroutine. This latter attribute is useful in checking for multiple usage of a file register between di erent subroutines and callers. Notice the double line vertical borders commonly used in flow diagrams to denote modules or subroutines.
Internal workspace
COUNT_H
COUNT_L
K
File 30h
File 31h
File 32h
K (1 -- 256) in W |
DELAY_K100MS |
Fig. 6.5 System view of K × 100 ms delay subroutine.
As there is only one input byte-sized parameter, the most convenient place to place K is in the Working register. Thus to call up a 5-second delay, the caller could use the sequence:
movlw |
50 |
; |
50 |
x 0.1s |
gives 5 seconds |
call |
DELAY_K100MS |
; |
Go |
to it! |
The actual subroutine itself in Program 6.2 implements the task list:
1.DO:
(a)Delay 100ms.
(b)Decrement K.
2.WHILE (K > 0).
3.End.
The actual coding simply copies the parameter from W into File 32h before entering the following delineated coding, which is identical to Program 6.1 and gives a single 100 ms delay. On completion of the delay K
146 The Quintessential PIC Microcontroller
Program 6.2 A K × 100 ms delay subroutine.
; ************************************************************
; * |
FUNCTION: |
Delays for around |
K x 100 ms @ 4MHz |
* |
||||
; * |
EXAMPLE |
: |
K |
= 100, delays |
10 seconds |
* |
||
; |
* |
ENTRY |
: |
K |
in W, range 1 |
- |
256 |
* |
; |
* |
EXIT |
: |
Flags and W altered; Files 30:1:2h zero |
* |
|||
; ************************************************************
COUNT_H |
equ |
30h |
; 2-byte counter |
COUNT_L |
equ |
31h |
; at File 20:1h |
K |
equ |
32h |
; Temporary storage for K |
Nequ d’130’ ; Delay parameter
DELAY_K100MS |
|||
movwf |
K |
; Put K away in a register file |
|
; Task 1: DO 100ms delay |
|||
movlw |
N |
; Set up high count to 130 |
|
movwf |
COUNT_H |
||
clrf |
COUNT_L |
; and low count to 256 |
|
DK_LOOP |
|||
decfsz |
COUNT_L,f |
; Decrement LS count to zero |
|
goto |
DK_LOOP |
; taking in all H*[(256*3)-1]˜ |
|
decfsz |
COUNT_H,f |
; Repeat for the MS byte |
|
goto |
DK_LOOP |
; taking in all (H*3)-1˜ |
|
; Task 2: Decrement K |
|||
decfsz |
K,f |
||
; Task 3: WHILE |
K > 0 |
||
goto |
DK_LOOP |
; REPEAT WHILE K > 0 |
|
FINI |
return |
||
is decremented in situ and the delay block repeated until K reaches zero. Thus the 100 ms block is repeated K times.
As K is tested for zero after the 100 ms delay is executed7 an initial value of K = 0 will be treated as K = 256, giving a delay range of 00.1– 25.6 s. Testing before the loop8 would give a range 0–25.5 s. Actually, the delay will be a few µs longer than the plain 100 ms delay subroutine, due to the three additional instructions outside the delineated code block.
As W is needed to set up COUNT_H it could not be used directly to hold K during the subroutine. In fact, if the caller had known that File 32h was used by the subroutine to hold K then it could have been passed directly through this register file. However, the less the caller has to know about the ‘innards’ of its subroutines the better it will be, on the basis
7Known to C programmers as a DO-WHILE loop. 8Known to C programmers as a WHILE loop.
6. Subroutines and Modules 147
that a subroutine should disturb its environment as little as possible. DELAY_K100MS is not very good in this respect, using three file registers for its internal use and altering the Working register. As an example of what could go wrong, Program 6.3 shows an implementation of the task list but calling the 100 ms block as the existing Program 6.1 subroutine; that is a nested subroutine. Here File 30h is used as a store for K oblivious to the fact the this register file is used by subroutine DELAY_100MS as one of its counters. The e ect of this interaction is to make K zero on return from DELAY_100MS, which when decremented at Task 2 will always give a non-zero outcome. Thus the delay is infinite and the system locks up! Simply changing K equ 30h to K equ 32h fixes the problem; but if another member of the team with responsibility for the DELAY_100MS subroutine alters its internal storage map without communicating this to other team members then catastrophe may occur! Thus even though each subroutine could have been passed when tested on its own, certain combinations of calling sequences could cause failure. We will return to this problem later.
Program 6.2 is still void in that no data was returned to the caller on exit. For our next example we will code a subroutine that will activate a decimal readout. Many numeric electronic displays are based on a selective activation of seven segments in the manner shown in Fig. 6.6.
Program 6.3 An alternative K × 100 ms delay subroutine.
; ************************************************************
; * |
FUNCTION: |
Delays for around |
K x 100 ms @ 4MHz |
* |
|||||
; * |
EXAMPLE : |
K |
= 100, delays |
10 |
seconds |
* |
|||
; * |
RESOURCE: |
DELAY_100MS |
called |
* |
|||||
; |
* |
ENTRY |
: |
K |
in W, range 1 |
- |
256 |
* |
|
; |
* |
EXIT |
: |
Flags and W |
altered; Files 30:1h zero |
* |
|||
; |
************************************************************ |
||
K |
equ |
30h |
; Temporary storage for K |
DELAY_K100MS |
|||
movwf |
K |
; Put K away in a register file |
|
; Task 1: DO 100ms delay DK_LOOP call DELAY_100MS
; Task 2: Decrement K
decfsz |
K,f |
; Decrement K |
||
; Task 3: WHILE |
K > |
0 |
||
goto |
DK_LOOP |
; REPEAT WHILE K > 0 |
||
FINI |
return |
|||
148 The Quintessential PIC Microcontroller
These segments are typically implemented using light-emitting diodes (see Fig. 11.13 on page 298) or electrodes in a liquid-crystal cell.
N (0 -- 9) in W
S (7-segment) in W
SVN_SEG
(a) System view
a
f b
g
e c
d
1000000 1111011 10100100 0110000 0011001 0010010 0000010 1111000 0000000 0010000
gfedcba
(b) The 7-segment font
Fig. 6.6 The 7-segment display.
The system description of our subroutine is shown in Fig. 6.6(a). Here the input signal is a 4-bit binary code representing the ten decimal digits as 0000 – 1001b in the Working register. The output, also in W, is the corresponding 7-segment code to activate the digit as listed in Table 6.2. This code assumes that a segment is lit/opaque on a binary 0 and unlit/clear on a binary 1.
Most MPU/MCUs deal with look-up tables by storing the codes as part of the program memory and copying the Nth byte out of the table as the mapping function. In the 12and 14-bit core PICs the Havard structure makes code in the Program store inaccessible to the program – but see Fig. 15.6 on page 445 for an exception. Instead, look-up tables are implemented as a series of retlw instructions, each returning a constant byte. This structure is shown in Table 6.2. As each retlw places an 8-bit code in W, I have arbitrarily made the unused bit 7 a logic 1.
In developing a coding based on this table structure, the mechanism for element N extraction is to execute the Nth retlw instruction. This will place the instruction literal in the Working register and then do a normal return form subroutine back to the caller. In the example shown, if N is seven then the 7th retlw is executed returning with the code
11111000b for in W.
The coding shown in Program 6.4 implements this selection mechanism by simply adding N, which is in W, to the lower byte of the Program Counter – that is PCL in File 2. PC then points to the Nth retlw as desired.
6. Subroutines and Modules 149
Table 6.2: The 7-segment lookup table showing byte[N] being extracted.
0 |
retlw b’11000000’ |
; |
||
1 |
retlw b’11111001’ |
; |
||
2 |
retlw b’10100100’ |
; |
||
3 |
retlw b’10110000’ |
; |
||
4 |
retlw b’10011001’ |
; |
||
5 |
retlw b’10010010’ |
; |
||
6 |
retlw b’10000010’ |
; |
||
N 7 |
||||
retlw b’11111000’ |
; |
= Table[N] |
||
8 |
retlw b’10000000’ |
; |
||
9 |
retlw b’10010000’ |
; |
||
Although this approach does work, there are limitations. Any alteration of the PCL register will cause both these eight bits together with the lowermost five bits of the PCLATH to be moved into the 13-bit PC
– as described in Fig. 4.3 on page 86. This means that if the instruction addwf PCL,f causes the 8-bit PCL to overflow or if the contents of the PCLATH does not match the upper bits in the full PC, then the outcome stored into the PC will not be as the programmer wished – see Example 6.7. This is not easy to check, as the programmer is unlikely to know in advance where the subroutine is located in memory, that is what value the PC will have at the beginning of the subroutine. Even if he/she checks the assembler listing file (see Table 8.1 on page 206) for the value of SVN_SEG, this can change if subsequent alterations are made to other parts of the
Program 6.4 The software 7-segment decoder.
PCL |
equ |
2 |
; Low byte of PC is at File 2 |
SVN_SEG addwf |
PCL,f |
; Add N to PCL giving PC + N |
|
; |
xgfedcba |
||
retlw |
b’11000000’ |
; Code for 0 |
|
retlw |
b’11111001’ |
; Code for 1 |
|
retlw |
b’10100100’ |
; Code for 2 |
|
retlw |
b’10110000’ |
; Code for 3 |
|
retlw |
b’10011001’ |
; Code for 4 |
|
retlw |
b’10010010’ |
; Code for 5 |
|
retlw |
b’10000010’ |
; Code for 6 |
|
retlw |
b’11111000’ |
; Code for 7 |
|
retlw |
b’10000000’ |
; Code for 8 |
|
retlw |
b’10010000’ |
; Code for 9 |
|
150 The Quintessential PIC Microcontroller
program. It is possible to devise code to allow this address boundary to be crossed, but at the expense of complexity – see Example 6.7. The Microchip application note AN556 Implementing a Table Read gives techniques for dealing with these problems.
The code in Program 6.4 takes no account of the possibility that the datum in W is greater than 09h. Of course it shouldn’t be, but robust code should cope with all contingencies even if it is technically erroneous. This is especially true if the code module is to be reusable for general-purpose applications. What would happen if this situation arose and how could you add to the code to gracefully return an error code, say −1, in this eventuality?
Using W to transfer information to and fro a subroutine is limited to a single byte datum each way. Where several pieces of information of byte or greater sizes are to be passed, then file registers must be pressed into service for this conduit. An example of this is shown in Program 6.5 where two byte datums, labelled MULTIPLICAND and MULTIPLIER are to be multiplied giving a 16-bit outcome labelled PRODUCT_L:PRODUCT_H.
Internal workspace |
|||||||||||
MULTIPLICAND_H |
File 30h |
||||||||||
MULTIPLIER |
(0 -- 255) in |
File 20h |
PRODUCT_L:PRODUCT_H |
||||||||
MUL |
|||||||||||
MULTIPLICAND |
(0 -- 255) in |
File 21h |
|||||||||
(0 -- 65,535) in File 2E:Fh |
|||||||||||
Fig. 6.7 System diagram for the byte multiplication subroutine.
The principle of the multiplication algorithm coded in Program 6.5 is a generalized version of that used by previous multiplication routines, such as Program 5.11 on page 133. Here the multiplier ten was decomposed to ×2+×8 which could be implemented by shifting once and three times respectively. In the more general case the multiplicand is shifted left and the nth shifted word added to the product if bit n of the multiplier is 1. Doing this eight times gives:
7
Product = (multiplicand<<n) × bit n
n=0
where the << operator denotes shift left.
Using this shift and add algorithm gives the task list:
1.Zero double-byte product.
2.Extend Multiplicand to 16 bits.
3.DO
(a)Shift Multiplier right once.