The Modular Approach - Using Modules

By keeping your most useful routines in a module, you can build programming libraries for other programs to reuse. Modules are also extremely useful for dividing large programs into small, more manageable pieces by storing different parts in separate modules.

The Module Identifier

Unlike your main program, a module always starts with an identifier. For example,

module USART

The reason a module must be given an explicit identifier is that many modules will use the same naming convention for public variables, subroutines and functions and we need a mechanism for accessing them from a program. For example, the Swordfish USART and LCD libraries both have an output subroutine called Write(). So the the question is: how do we call each output routine if they are both called Write()? The answer is simple, you use redirection.

Redirection is achieved by prefixing the variable, subroutine or function you want to access with the modules name. For example,

include "USART.bas"
include "LCD.bas"

SetBaudrate(br19200)
USART.Write("USART Write", 13, 10) // redirect to USART module
LCD.Write("LCD Write")             // redirect to LCD module

Notice the use of the dot notation to separate the module identifier from the routine called. If you did not use redirection, then all output would be sent via the USART module. This is because the USART library has been included before the LCD library. If you switched them around, then all output would be sent via the LCD module. Redirection is a really good way to document your programs, even if it is not actually needed. For example,

include "USART.bas"
SetBaudrate(br19200)
Write("Hello World"
, 13, 10)

could be written like this,

include "USART.bas"
USART.SetBaudrate(br19200)
USART.Write("Hello World"
, 13, 10)

The Interface - Private and Public

The most fundamental difference between a normal program and module declarations is the use of the private and public keywords. A declaration that is private is only available from within the module it is declared. This is in contrast to a declaration that is made public, which is available to other modules and programs. Module constants, types, structures, variables, aliases, subroutines and functions can all be made either private or public.

The separation of private and public parts of a module is often referred to as encapsulation, or information hiding, and enables you to create modules that are both reusable and robust. Private declarations are sometimes called helper declarations, because they help the module perform one or more specific tasks.  A public declaration is therefore the interface to a module. All Swordfish declarations are private by default. If you want other modules or programs to access them, they must be explicitly declared as public.

For example, the following code is a small snippet of code from the Swordfish DS18B20 library.

// the module name...
module
DS18B20

// import helper modules...
include "OW.bas"
  
// private constants and variables…
const
   owWriteSP = $4E,
   owReadSP = $BE
dim
  
FSP(9) as byte,   // scratch pad
  
FID(8) as byte    // device ROM ID

// Read DS1820 scratch pad, private function
function ReadSP() as boolean
   OW.Reset
   OW.WriteByte(owMatchROM)
   OW.WriteArray(FID)
   OW.WriteByte(owReadSP)
   Result = OW.ReadArray(FSP)
end function

// Read the device temperature, public function
public function GetTemp() as word
   OW.WaitForHigh
   ReadSP
   Result.Byte1 = FSP(1)
   Result.Byte0 = FSP(0)
end function 

The DS18B20 module imports the OneWire (OW) library and declares a number of private constants and variables, such as owWriteSP, owReadSP, FSP and FID. It also declares a private function called ReadSP(). None of these declarations can be accessed outside the scope of the module. For example, the following program would generate an error because ReadSP() and FID are private to the DS18B20 module.

// program demonstrates 'out of scope' call...
include "DS18B20.bas"
DS18B20.ReadSP
DS18B20.FID(0) = 10

However, the following call is perfectly legal, because the function GetTemp() has been declared as public.

// program demonstrates legal call to module...
include "DS18B20.bas"
dim Value as word
Value = DS18B20.GetTemp

Initialising

Occasionally, you may want to initialise your module when the PIC® starts executing. For example, there may be a number of private declarations which must be set in order for the module to function correctly. Swordfish modules allow you to add statements, just like a normal program, so that the module can be initialised properly.  Module statements are always executed before the main program executes. For example,

// the module name...
module Stack

// private variables...
dim StackItems(100) as byte
dim StackPointer as byte

// push byte onto stack...
public sub Push(pValue as byte)
   if StackPointer <= bound(StackItems)
then
      StackItems(StackPointer) = pValue
      inc(StackPointer)
  
endif
end sub

// pop item off stack...
public function Pop() as byte
   if StackPointer > 0 then
      dec(StackPointer)
   endif
   result = StackItems(StackPointer)
end function

// initialise stack pointer
StackPointer = 0 

In this example, the module manages an array of byte items that can be pushed and popped off a stack. Before the module can be used, it is essential that the stack pointer is initialized to zero. If not, the value of stack pointer may contain any value when the main program executes and the module would certainly fail.

Care must be exercised when using a module statement block, as any code included will be executed before the main program code begins executing. Don't fill it with unnecessary statements; just include code that is absolutely essential for the correct operation of a module.