c & c++ - Subroutines

Contact
PC-compilers
last-news
PIC-microcontroller
AVR-microcontroller
Supports
weakly newsletter
c++
Counter
DS1821Thermostat Programmer
pic-asm-foundations
=> first programme
=> Time delays
=> Subroutines
=> Input
=> Further Input
free links downloads
free downloads
mikroc library
PWM software
Home



 

Subroutines explained:

We have already used subroutines in our example 4-bit counter. They hopefully made sense at the time, but as is often the case, there is slightly more to them that at first appears. So now is a good time to look in more detail, uncovering some essential facts along the way.

How does a programme run?

We've made the assumption that one line executes after another, in a logical fashion - and this is quite right for the most part. But, what actually happens inside the PIC?

It's time to introduce the concept of a programme counter. This is a register that is reset to zero at power-on, or reset, and always contains the address in programme memory of the next instruction to be executed. As the next instruction is fetched from programme memory, this register is automatically incremented. The only exception to this rule is when the instruction causes a branch.

The program counter is available to us - we can write to it at will, and as we'll see later this opens the door to some very powerful techniques. But for now, refer to the memory map, and note PCL, which is at memory address 02h - it's also mapped to 82h in Bank 1.

Just to clarify things, this short program has been shown in table form to highlight how PCL relates to commands in memory. This simple program flashes an LED connected to RB0 (bit 0 of PORTB - pin 6) - only don't try to use it because as it stands, there is no time delay so it will flash far too fast to be visible to the naked eye.

Memory
location
       
  PCL  
    ORG 0       ;Reset vector
0
         
 
0
  clrf PORTA ;all of porta low
1
1
  clrf PORTB ;all of portb low
2
2
  bsf STATUS, RP0     ;change to bank1
3
3
  clrf TRISA ;all of porta outputs
4
4
  clrf TRISB ;all of portb outputs
5
5
  bcf STATUS, RP0 ;back to bank0
6
 
       
6
6
Main_loop   bsf PORTB, 0 ;Set RB0 - LED on
7
7
  bcf PORTB, 1 ;Clear RB0 - LED off
8
8
  goto Main_loop ;and repeat...
9 6

This table attempts to demonstrate the behaviour described above. PCL starts at zero, and changes to 1 as soon as the first instruction has been fetched. This simple pattern repeats until the PIC gets to the goto in memory location 8 - PCL changed to 9 as the fetch occurred, but upon decoding the instruction and realising it was a "goto", it changed PCL to equal 6. It knew that 6 was the required value because the assembler realised that Main_loop is in fact a label for memory location 6. In actual fact, this simple modification of the programme counter is all that a goto does.

 But this isn't the whole story. Remember, data memory is 8 bits, which means a total of 256 different values. PCL exists in data memory, which means that it's only able to refer to 256 different locations in programme memory. This might be OK for small PICs, but the PIC16F84 has 1024 words of programme memory, which needs 10 bits. These extra bits are stored in a register called PCLATH (0Ah). So, while PCL stands for program counter low - PCLATH stands for programme counter latch (holding).

As the latter name suggests, it's slightly more complicated than that. Strictly speaking, PCLATH is not the high bits of PC (program counter). Rather, it's a holding register that we can set up prior to doing a computated goto. We will look at this later in much more detail.

Subroutines:

We know already that we can jump to a subroutine using a "call" statement, and we've also seen that the program will return to where it left off when the processor meets a "return". So how does "call" differ from "goto"?

Memory
location
       
  PCL  
         
 
10
  incf Counter, f   ;Increment Counter  
11
11
  call Output ;Output
12 100
12
  movwf Loop ;W = Loop
13
 
       
 
100
Output   movfw Counter ;W = Counter
101
101
  movwf PORTB ;Output W to portb
102
102
  return      ;done...
103 12

This table shows two snippets of code taken from a larger program. The first 3 lines are an extract from the main programme, where a variable called Counter is incremented, then a subroutine called Output is called. This subroutine simply writes the current value of Counter to PORTB. The actual code is not really important, but the program flow is.

The first line shown above happens to be in memory location 10, hence PCL is pointing to 11, the next location. When the processor fetches and decodes this next instruction, it realises that it is a "call". As before with "goto", the PIC will load PC with the new address, which is 100 in this case. But, before doing this, it will store the current value of PCL somewhere safe - this is how it knows where to go when it meets a "return". Understanding this is key!

This "safe house" is called a "stack", which is a commonly found construct in microprocessors. It is a simple buffer where the last number "pushed" onto the stack will be the first number taken from the stack. Think of a simple pile of playing cards on your desk - just don't shuffle them!

This "last-on, first off" behaviour is what allows us to "nest" subroutines. You saw this when we looked at the timing routines - it is perfectly acceptable to call a subroutine from within a subroutine:

PROCESSOR STACK:
0
1
2
Loop   movlw d'100'        
  call WaitNms        
    WaitNms   movwf timer_lcl    
      etc...    
      call loop1ms    
        loop1ms   addlw d'255'
          etc...
          return
      etc...    
      return    
  rest of code...        

This diagram shows a simplified program that creates a 100ms delay using the delay routines studied before. Starting at the top left, the main program puts 100 into the working register and calls WaitNms. At this point, the return address is pushed onto the stack, and the stack pointer is incremented, moving us across the page. We then enter the WaitNms subroutine, represented by the different background colour. When the processor meets "call loop1ms", it pushes another return address onto the stack, and jumps to another subroutine. At the end of "loop1ms", the processor meets the first "return" statement, and "pops" the last number from the stack. This moves us left, as the stack pointer is decremented. The processor will meet the next return, and again will retrieve the return address from the stack, and we're back in our main program.

The stack is a separate section of memory - it is not in programme memory or data memory, and we can not access it in any way. It is entirely managed by the processor during subroutine operations. This is in contrast to some larger processors that let you store your own variables there.

 VERY IMPORTANT:

The stack is subject to one major limitation - it can only contain 8 locations!

This means that you CAN NOT nest more than 8 subroutines. This is vitally important! Subroutines are an excellent way of making code structured and logical, but use them carefully! Unfortunately, there is no mechanism within the processor to tell you that you have exceeded the maximum number of locations.

Worse than that, the stack operates as a circular buffer - this means that when you push the ninth location onto the stack, it wraps around and overwrites the first location in the stack! Using the example above, imagine that the WaitNms subroutine somehow managed to call to many nested subroutines and used too many locations in the stack - when the processor meets the return, it will try to recover the return address from the stack and find the wrong address because it was overwritten during the subroutine. At this point, the program will return to the wrong point and probably crash!

When writing your program, be careful to match every call with a return. Don't jump out of a subroutine back into the main programme loop because the stack will quickly overflow.

Optimising subroutines:

This next tip might seem somewhat esoteric, but it's surprising how frequently you'll be able to implement this.

Wait1Second  movlw    d'250'    ;250mS
             call     WaitNms   ;
             movlw    d'250'    ;250mS
             call     WaitNms   ;
             movlw    d'250'    ;250mS
             call     WaitNms   ;
             movlw    d'250'    ;250mS
             call     WaitNms   ;
             return

This subroutine generates a 1 second delay by calling WaitNms four times with each time taking 250 milliseconds. Note that the last time we call WaitNms - when returning from the WaitNms subroutines, we meet a return statement, and the Wait1Second subroutines ends.

But, contrast it with these two lines:

             movlw    d'250'    ;250mS
             goto     WaitNms   ;

When the processor arrives at the goto statement, it will jump to the WaitNms subroutine instead of calling it. At the end of the WaitNms subroutine, the processor will meet a return. This return actually serves to mark the end of the Wait1Second subroutine.

PROCESSOR STACK:
0
1
2
Start   bsf PORTB,0        
  call Wait1Second        
    Wait1Second   movlw d'250'    
      call WaitNms    
        WaitNms   movwf
          etc...
          return
      movlw d'250'    
      call WaitNms    
        WaitNms   movwf
          etc...
          return
      movlw d'250'    
      call WaitNms    
        WaitNms   movwf
          etc...
          return
      movlw d'250'    
      goto WaitNms    
    WaitNms movwf    
      etc...    
      return    
  rest of code...        

This diagram attempts to show this. Note how the subroutines are called normally the first three times, but for the last time, the program just jumps to WaitNms. This shortcut has saved a programme memory word, and a couple of processor cycles. Although it might not seem like a big deal, I promise you that you'll be able to use this often, and the savings soon stack up.

Summary and conclusions:

In this section, we've looked in detail at the mechanism of goto's, subroutines, and the programme counter. We will return to these subjects in the future.

On to the next section - Input.

Today, there have been 1 visitors (7 hits) on this page!
This website was created for free with Own-Free-Website.com. Would you also like to have your own website?
Sign up for free