Subroutines and Modules Part 2 (PIC Microcontroller)

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.125.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 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 anested 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 effect 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 x 100 ms delay subroutine.

Program 6.3 An alternative K x 100 ms delay subroutine.The 7-segment display.

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

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 12- and 14-bit core PICs the Havard structure makes code in the Program store inaccessible to the program – but see Fig. 15.6.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!inW.

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.

These segments are typically implemented using light-emitting diodes (see Fig. 11.13) or electrodes in a liquid-crystal cell.

Table 6.2: The 7-segment lookup table showing byte[N] being extracted.

The 7-segment lookup table showing byte[N] being extracted.

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.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 for the value of SVN_SEG, this can change if subsequent alterations are made to other parts of the 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.

Program 6.4 The software 7-segment decoder.

The software 7-segment decoder.

 

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.

System diagram for the byte multiplication subroutine.

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.Here the multiplier ten was decomposed to x2 + x8 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:

tmp18372_thumb_thumb

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.

(b) IF Carry bit is one THEN and Multiplicand to subproduct.

(c) Shift Multiplicand right once.

(d) Repeat WHILE product not zero.

4. End with 16-bit Product.

Program 6.5 declares the variables that are passed to and from the subroutine at the of the main program. Keeping all these global declarations in one part of the program and using a different file register for each overall global variable reduces the possibility of interaction but at the expense of rather extravagant use of scarce Data memory resources. Temporary local storage is declared within each subroutine as its need will be ‘thrown away’ after the subroutine is terminated. However, interaction can still occur in local storage where nested subroutine structures are used.

The coding follows the task list closely. The decision whether to add the left-shifted multiplicand to the subproduct is dependent on the state of the Carry flag when the multiplier is shifted right. This implements the conditional addition

product = product + (multiplicand<<n) x bit n

Rather than implementing this shift and conditional add process eights times, the summation loop is terminated whenever the multiplier residue is zero. This means that the execution time of the subroutine is variable, depending on the bit pattern of the multiplier. The worst-case scenario is when the multiplier is 255 (1 1 1 1 1 1 1 1 b). This takes 142 cycles including the two cycle call.

In order to use this subroutine, the caller copies the multiplicand into File 20h and multiplier into File 21 h. On return, the 16-bit product can be read at File 2E:Fh. As an example, consider that the bytes located at File 42h and File 46h are to be multiplied.

tmp18373_thumb_thumb

Most MPU/MCUs have software stacks which in addition to saving subroutine return addresses allow the programmer to push and pull data to and from memory to pass information between caller and subroutine. As the stack is a dynamic storage entity, growing where necessary to accommodate these passed and temporary variables and shrinking again when the subroutine terminates, this clearly is an efficient method of memory allocation. Furthermore, each call outwards in a nested structure opens a new stack frame for this dynamic storage as an extension to the stack. in this way the possibility of overlap between variable storage when using nested subroutines is virtually eliminated.

Program 6.5 The byte multiplication subroutine.

The byte multiplication subroutine.

 

High-level languages, such as C (see topic 9) are based around this stack model, which allows the creation and passing of variables only restricted by the amount of data memory that can be allocated to this stack.

The downside to this approach is the extra CPU resources necessary to support the creation and maintenance of the stack. One or more dedicated address registers or stack pointers are normally provided and address modes that facilitate access to variables in these stack frames are needed for efficient working. Even then, the outcome is normally slower and coding is longer than models based on fixed memory allocations.

The PIC CPU does not explicitly support a software stack. However, it is possible to simulate such a structure using Indirect addressing with the File Select Register (FSR = File 4) and INDirect File (INDF = File 0). As there is no stack pointer register per se, in the code fragment below the main routine has allocated File 0Ch as a Pseudo Stack Pointer, which we call PSP.

tmp18375_thumb[2]

The programmer also has to set aside a block of Data memory to hold the various stack frames. Here we are specifying that the Top Of Stack (TOS) address is File 2Fh. If the range File 2fh-0Dh is kept clear of absolute allocations then a total of 35 bytes is available for the stack. As the hardware stack holds the subroutine return address, all locations in the simulated software stack can be used for variable passing and local storage. However, if an 8-deep subroutine nest is going to be implemented then a bigger slice of available storage may well be necessary. The software stack is initialized by moving the literal 2Fh, named TOS, into the file holding the Pseudo Stack Pointer.

Next post:

Previous post: