Bascom and AVR, Subroutines and Functions


Subroutines

You use subroutines and functions in a Bascom program when you have identical pieces of code at different places in the program. Suppose you have this program:
Dim ADCval as byte
Dim ADCchannel as byte
Const ADCreadaddress = &H66

'Read first ADC channel
ADCchannel=0
I2cstart
I2cwbyte ADCreadaddress
I2cwbyte ADCchannel
I2crbyte ADCval
Print "ADC voltage on channel: " ; ADCchannel; " is: " ; ADCval
'Read second ADC channel
ADCchannel=1
I2cstart
I2cwbyte ADCreadaddress
I2cwbyte ADCchannel
I2crbyte ADCval
Print "ADC voltage on channel: " ; ADCchannel; " is: " ; ADCval
Which could also be written as:
Dim ADCval as byte
Dim ADCchannel as byte
Const ADCreadaddress = &H66

Declare Sub ADCread(byval ADCchannel as byte, ADCval as byte)

'Read first ADC channel
ADCchannel=0
Call ADCread(AdDCchannel, ADCval)
Print "ADC voltage on channel: " ; ADCchannel; " is: " ; ADCval
'Read second ADC channel
ADCchannel=1
Call ADCread(AdDCchannel, ADCval)
Print "ADC voltage on channel: " ; ADCchannel; " is: " ; ADCval
 
Sub ADCread(byval ADCchannel as byte, ADCval as byte)
  I2cstart
  I2cwbyte ADCreadaddress
  I2cwbyte ADCchannel
  I2crbyte ADCval
End Sub
The subroutine is now the only place in the program where the ADC voltage is read.In the main program the subroutine is called twice to read the ADC voltage, the first time for ADCchannel 0, the second time for ADCchannel 1.
Using subroutines has the advantage of having code that is repeatedly called in only one place in the program. If you need to change the code, you will have to do it in only one place. If you have similar pieces of code in your program and you need to change something, you will have to make changes in all occurrences of that code. Someday, you will forget one place and you are in trouble.
So, we have a subroutine that is called with:
Call ADCread(ADCchannel, ADCval)
The subroutine gets a symbolic name (ADCread) and has none or one or more arguments. The arguments in this example are ADCchannel and ADCval. The subroutine uses ADCchannel to address the correct ADC channel number. The ADC value read is placed in ADCval and returned by the subroutine to the calling program when the subroutine reaches the End Sub statement. So, these arguments can work both ways: from calling program to subroutine and from subroutine to calling program.

There is an option to specify that arguments we pass to the subroutine must remain unchanged. In the Sub statement they must be specified as Byval. Bascom will then make a copy of the variable and pass the copy to the subroutine. The original remains unchanged whatever happens in the subroutine. In the example above we do not want that in the subroutine the value of ADCchannel is changed, so we pass it using Byval. The opposite, Byref, is the default. You do not need to specify it explicitly. Byref means that Bascom will not make a copy of the variable but passes a reference of the variable to the subroutine. Any change the subroutine makes to the variable will show up in the main program as well.

Subroutines must, as with variables, be declared before they are used:
Declare Sub ADCread(byval ADCchannel, as byte, ADCval as byte)
Another example of using a subroutine: (use a schematic with standard Lcd attached to the AT90S2313)
$regfile = "2313def.dat"
$crystal = 4000000

Const Barmaxchar = 16
Dim Forcounter As Byte
Dim Logval As Byte

Declare Sub Bar(logvalue As Byte)

Deflcdchar 0 , 14 , 10 , 14 , 21 , 14 , 10 , 17 , 17        'jumping man-a
Deflcdchar 1 , 14 , 10 , 21 , 21 , 14 , 10 , 10 , 17        'jumping man-b
Deflcdchar 2 , 32 , 32 , 32 , 32 , 32 , 32 , 32 , 32        'empty box
Deflcdchar 3 , 16 , 16 , 16 , 16 , 16 , 16 , 16 , 0         '1/5 box
Deflcdchar 4 , 24 , 24 , 24 , 24 , 24 , 24 , 24 , 0         '2/5 box
Deflcdchar 5 , 28 , 28 , 28 , 28 , 28 , 28 , 28 , 0         '3/5 box
Deflcdchar 6 , 30 , 30 , 30 , 30 , 30 , 30 , 30 , 0         '4/5 box
Deflcdchar 7 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 0         'full box
Cls
Cursor Off
Do
  'Generate random numbers to display as bar
  For Forcounter = 1 To 40
  Logval = Rnd(40)
    Call Bar(logval)
    Waitms 500
  Next Logval
  'Count down to create diminishing bar
  For Logval = 40 To 1 Step -1
    Call Bar(logval)
    Waitms 250
  Next Logval
Loop
End

Sub Bar(logvalue As Byte)
  Local Lognumboxes As Byte
  Local Logbarremainder As Byte
  Local Logboxnumber As Byte
  Locate 1 , 1
  Lognumboxes = Logvalue / 5                                'calc number of boxes in bar
  Logbarremainder = 5 * Lognumboxes                         'and determine remainder
  Logbarremainder = Logvalue - Logbarremainder
  For Logboxnumber = 1 To Lognumboxes                       'number of boxes to lcd
    Lcd Chr(7)
  Next Logboxnumber
  Logbarremainder = 2 + Logbarremainder                     'make remainder point to
  Lcd Chr(logbarremainder)                                  'correct lcd char and output char to lcd
  Lognumboxes = Lognumboxes + 1                             'Fill remainder with spaces
  For Logboxnumber = Lognumboxes To Barmaxchar
    Lcd " "
  Next Logboxnumber
End Sub
This example shows that the variable names used in the Call Sub and in the Sub definition do not have to be the same, as long as they are defined in the Dim and Declare statements.
The main program generates a random number to display on the Lcd as a bar. It calls the subroutine Bar with the Logvalue to display. In the subroutine, first the number of 'full boxes' to display is calculated. The remainder is calculated to point to a pre-defined character with a width of one to four pixelcolumns. Lastly, the leftover of the bar is filled with blanks to erase any previously written characters.

Functions

Functions are similar to subroutines, but they must return at least one result back to the calling program. This works like this:
Dim DDSWord as Long
Const DDSClock = 100000000 '100MHz
Declare Function MakeDDSWord(byref Frequency as Long) as Long

DDSWord = MakeDDSWord(Frequency)

Function MakeDDSWord(byref Frequency as Single) as Long
  MakeDDSWord = 2 ^ 32
  MakeDDSWord = MakeDDSWord / DDSClock
  MakeDDSWord = MakeDDSWord * Frequency
End Function
In DDSWord = MakeDDSWord(Frequency), the function MakeDDSWord is called with Frequency as argument. The result of the calculation, which is MakeDDSWord is put in place of MakeDDSWord(Frequency).
A function must return at least one result, but it is also possible (but not logical) to change the arguments in the function call itself, for as long as they have not been specified as Byref.

The Local variable
In a subroutine or function, variables can be dimensioned to exist only within the subroutine or function. They are not accessible in the main program. Dimensioning uses the Local keyword:
Local Temporaryval as Integer
Local Justfornow as Long
All variable types, except for Bit, can be declared as Local. Bit variables are always 'Global'.

TOC