Chapter 2: Elements of BASIC Language
Introduction
This chapter deals with the elements of BASIC language and the ways to use them efficiently. Learning how to program is not complicated, but it requires skill and experience to write code that is efficient, legible, and easy to handle. First of all, program is supposed to be comprehensible, so that the programmer himself, or somebody else working on the application, could make necessary corrections and improvements. We have provided a code sample written in a clear and manifest way to give you an idea how programs could be written:
'****************************************************************************** ' microcontroller : P16F877A ' ' Project: Led_blinking ' This project is designed to work with PIC 16F877A; ' with minor adjustments, it should work with any other PIC MCU. ' ' The code demonstrates blinking of diodes connected to PORTB. ' Diodes go on and off each second. '****************************************************************************** program LED_Blinking main: ' Beginning of program TRISB = 0 ' Configure pins of PORTB as output PORTB = %11111111 ' Turn ON diodes on PORTB Delay_ms(1000) ' Wait for 1 second PORTB = %00000000 ' Turn OFF diodes on PORTB Delay_ms(1000) ' Wait for 1 second goto main ' Endless loop end. ' End of program
Through clever use of comments, symbols, labels and other elements supported by BASIC, program can be rendered considerably clearer and more understandable, offering programmer a great deal of help.
Also, it is advisable to divide larger programs into separate logical entities (such as routines and modules, see below) which can be addressed when needed. This also increases reusability of code.
Names of routines and labels indicating a program segment should make some obvious sense. For example, program segment that swaps values of 2 variables, could be named "Swap", etc.
2.1 Identifiers
Identifiers are names used for referencing the stored values, such as variables and constants. Every program, module, procedure, and function must be identified (hence the term) by an identifier.
Valid identifier:
- Must begin with a letter of English alphabet or possibly the underscore (_)
- Consists of alphanumeric characters and the underscore (_)
- May not contain special characters:
~ ! @ # $ % ^ & * ( ) + ` - = { } [ ] : " ; ' < > ? , . / | \ - Can be written in mixed case as BASIC is case insensitive; e.g.
First,FIRST,and fIrSTare an equivalent identifier.
Elements ignored by the compiler include spaces, new lines, and tabs. All these elements are collectively known as the “white space”. White space serves only to make the code more legible – it does not affect the actual compiling.
Several identifiers are reserved in BASIC, meaning that you cannot use them as your own identifiers (e.g. words function, byte, if, etc). For more information, please refer to the list of reserved words. Also, BASIC has a number of predefined identifiers which are listed in Chapter 4: Instructions.
2.2 Operators
BASIC language possesses set of operators which is used to assign values, compare values, and perform other operations. The objects manipulated for that purpose are called operands (which themselves can be variables, constants, or other elements).
Operators in BASIC must have at least two operands, with an exception of two unary operators. They serve to create expressions and instructions that in effect compose the program.
There are four types of operators in BASIC:
- Arithmetic Operators
- Boolean Operators
- Logical (Bitwise) Operators
- Relation Operators (Comparison Operators)
Operators are covered in detail in chapter 3.
2.3 Expressions
Expression is a construction that returns a value. BASIC syntax restricts you to single line expressions, where carriage return character marks the end of the expression. The simplest expressions are variables and constants, while more complex can be constructed from simpler ones using operators, function calls, indexes, and typecasts. Here is one simple expression:
A = B + C ' This expression sums up the values of variables B and C
' and stores the result into variable A.
You need to pay attention that the sum must be within the range of variable A in order to avoid the overflow and therefore the evident computational error. If the result of the expression amounts to 428, and the variable A is of byte type (having range between 0 and 255), the result accordingly obtained will be 172, which is obviously wrong.
2.4 Instructions
Each instruction determines an action to be performed. As a rule, instructions are being executed in an exact order in which they are written in the program. However, the order of their execution can be changed by means of jump, routine call, or an interrupt.
if Time = 60 then
goto Minute ' If variable Time equals 60 jump to label Minute
end if
Instruction if..then contains the conducting expression Time = 60 composed of two operands, variable Time, constant 60 and the comparison operator (=). Generally, instructions may be divided into conditional instructions (decision making), loops (repeating blocks), jumps, and specific built-in instructions (e.g. for accessing the peripherals of microcontroller). Instruction set is explained in detail in Chapter 4: Instructions.
2.5 Data Types
Type determines the allowed range of values for variable, and which operations may be performed on it. It also determines the amount of memory used for one instance of that variable.
Simple data types include:
| Type | Size | Range of values |
|---|---|---|
| byte | 8-bit | 0 .. 255 |
| char* | 8-bit | 0 .. 255 |
| word | 16-bit | 0 .. 65535 |
| short | 8-bit | -128 .. 127 |
| integer | 16-bit | -32768 .. 32767 |
| longint | 32-bit | -2147483648 .. 2147483647 |
* char type can be treated as byte type in every aspect
Structured types include:
Array, which represent an indexed collection of elements of the same type, often called the base type. Base type can be any simple type.
String represents a sequence of characters. It is an array that holds characters and the first element of string holds the number of characters (max number is 255).
Sign is important attribute of data types, and affects the way variable is treated by the compiler.
Unsigned can hold only positive numbers:
byte 0 .. 255 word 0 .. 65535
Signed can hold both positive and negative numbers:
short -128 .. 127 integer -32768 .. 32767 longint -2147483648 .. 214748364
2.6 Constants
Constant is data whose value cannot be changed during the runtime. Every constant is declared under unique name which must be a valid identifier. It is a good practice to write constant names in uppercase.
If you frequently use the same fixed value throughout the program, you should declare it a constant (for example, maximum number allowed is 1000). This is a good practice since the value can be changed simply by modifying the declaration, instead of going trough the entire program and adjusting each instance manually. As simple as this:
const MAX = 1000
Constants can be declared in decimal, hex, or binary form. Decimal constants are written without any prefix. Hexadecimal constants begin with a sign $, while binary begin with %.
const A = 56 ' 56 decimal const B = $0F ' 15 hexadecimal const C = %10001100 ' 140 binary
It is important to understand why constants should be used and how this affects the MCU. Using a constant in a program consumes no RAM memory. This is very important due to the limited RAM space (PIC16F877 has 368 locations/bytes).
2.7 Variables
Variable is data whose value can be changed during the runtime. Each variable is declared under unique name which has to be a valid identifier. This name is used for accessing the memory location occupied by the variable. Variable can be seen as a container for data and because it is typed, it instructs the compiler how to interpret the data it holds.
In BASIC, variable needs to be declared before it can be used. Specifying a data type for each variable is mandatory. Variable is declared like this:
dim identifier as type
where identifier is any valid identifier and type can be any given data type.
For example:
dim temperature as byte ' Declare variable temperature of byte type dim voltage as word ' Declare variable voltage of word type
Individual bits of byte variables (including SFR registers such as PORTA, etc) can be accessed by means of dot, both on left and right side of the expression. For example:
Data_Port.3 = 1 ' Set third bit of byte variable Data_Port
2.8 Symbols
Symbol makes possible to replace a certain expression with a single identifier alias. Use of symbols can increase readability of code.
BASIC syntax restricts you to single line expressions, allowing shortcuts for constants, simple statements, function calls, etc. Scope of symbol identifier is a whole source file in which it is declared.
For example:
symbol MaxAllowed = 234 ' Symbol as alias for numeric value
symbol PORT = PORTC ' Symbol as alias for Special Function Register
symbol DELAY1S = Delay_ms(1000) ' Symbol as alias for procedure call
...
if teA > MaxAllowed then
teA = teA - 100
end if
PORT.1 = 0
DELAY1S
...
Note that using a symbol in a program technically consumes no RAM memory – compiler simply replaces each instance of a symbol with the appropriate code from the declaration.
2.9 Directives
Directives are words of special significance for BASIC, but unlike other reserved words, appear only in contexts where user-defined identifiers cannot occur. You cannot define an identifier that looks exactly like a directive.
| Directive | Meaning |
|---|---|
Absolute |
specify exact location of variable in RAM |
Org |
specify exact location of routine in ROM |
Absolute specifies the starting address in RAM for variable (if variable is multi-byte, higher bytes are stored at consecutive locations).
Directive absolute is appended to the declaration of variable:
dim rem as byte absolute $22 ' Variable will occupy 1 byte at address $22 dim dot as word absolute $23 ' Variable will occupy 2 bytes at addresses $23 and $24
Org specifies the starting address of routine in ROM. For PIC16 family, routine must fit in one page – otherwise, compiler will report an error. Directive org is appended to the declaration of routine:
sub procedure test org $200 ' Procedure will start at address $200 ... end sub
2.10 Comments
Comments are text that is added to the code for purpose of description or clarification, and are completely ignored by the compiler.
' Any text between an apostrophe and the end of the ' line constitutes a comment. May span one line only.
It is a good practice to comment your code, so that you or anybody else can later reuse it. On the other hand, it is often useful to comment out a troublesome part of the code, so it could be repaired or modified later. Comments should give purposeful information on what the program is doing. Comment such as Set Pin0 simply explains the syntax but fails to state the purpose of instruction. Something like Turn Relay on might prove to be much more useful.
Specialized editors feature syntax highlighting – it is easy to distinguish comments from code due to different color, and comments are usually italicized.
2.11 Labels
Labels represent the most direct way of controlling the program flow. When you mark a certain program line with label, you can jump to that line by means of instructions goto and gosub. It is convenient to think of labels as bookmarks of sort. Note that the label main must be declared in every BASIC program because it marks the beginning of the main module.
Label name needs to be a valid identifier. You cannot declare two labels with same name within the same routine. The scope of label (label visibility) is tied to the routine where it is declared. This ensures that goto cannot be used for jumping between routines.
Goto is an unconditional jump statement. It jumps to the specified label and the program execution continues normally from that point on.
Gosub is a jump statement similar to goto, except it is tied to a matching word return. Upon jumping to a specified label, previous address is saved on the stack. Program will continue executing normally from the label, until it reaches return statement – this will exit the subroutine and return to the first program line following the caller gosub instruction.
Here is a simple example:
program test main: ' some instructions... ' simple endless loop using a label my_loop: ' some instructions... ' now jump back to label _loop goto my_loop end.
Note: Although it might seem like a good idea to beginners to program by means of jumps and labels, you should try not to depend on it. This way of thinking strays from the procedural programming and can teach you bad programming habits. It is far better to use procedures and functions where applicable, making the code structure more legible and easier to maintain.
2.12 Procedures and Functions
Procedures and functions, referred to as routines, are self-contained statement blocks that can be called from different locations in a program. Function is a routine that returns a value upon execution. Procedure is a routine that does not return a value.
Once routines have been defined, you can call them any number of times. Procedure is called upon to perform a certain task, while function is called to compute a certain value.
Procedure declaration has the form:
sub procedure procedureName(parameterList) localDeclarations statements end sub
where procedureName is any valid identifier, statements is a sequence of statements that are executed upon the calling the procedure, and (parameterList), and localDeclarations are optional declaration of variables and/or constants.
sub procedure pr1_procedure(dim par1 as byte, dim par2 as byte,
dim byref vp1 as byte, dim byref vp2 as byte)
dim locS as byte
par1 = locS + par1 + par2
vp1 = par1 or par2
vp2 = locS xor par1
end sub
par1 and par2 are passed to the procedure by the value, but variables marked by keyword byref are passed by the address.
This means that the procedure call
pr1_procedure(tA, tB, tC, tD)
passes tA and tB by the value: creates par1 = tA; and par2 = tB; then manipulates par1 and par2 so that tA and tB remain unchanged;
passes tC and tD by the address: whatever changes are made upon vp1 and vp2 are also made upon tC and tD.
Function declaration is similar to procedure declaration, except it has a specified return type and a return value. Function declaration has the form:
sub function functionName(parameterList) as returnType localDeclarations statements end sub
where functionName is any valid identifier, returnType is any simple type, statements is a sequence of statements to be executed upon calling the function, and (parameterList), and localDeclarations are optional declaration of variables and/or constants.
In BASIC, we use the keyword Result to assign return value of a function. For example:
sub function Calc(dim par1 as byte, dim par2 as word) as word dim locS as word locS = par1 * (par2 + 1) Result = locS end sub
As functions return values, function calls are technically expressions. For example, if you have defined a function called Calc, which collects two integer arguments and returns an integer, then the function call Calc(24, 47) is an integer expression. If I and J are integer variables, then I + Calc(J, 8) is also an integer expression.
2.13 Modules
Large programs can be divided into modules which allow easier maintenance of code. Each module is an actual file, which can be compiled separately; compiled modules are linked to create an application. Note that each source file must end with keyword end followed by a dot.
Modules allow you to:
- Break large code into segments that can be edited separately,
- Create libraries that can be used in different programs,
- Distribute libraries to other developers without disclosing the source code.
In mikroBasic IDE, all source code including the main program is stored in .pbas files. Each project consists of a single project file, and one or more module files. To build a project, compiler needs either a source file or a compiled file for each module.
Every BASIC application has one main module file and any number of additional module files. All source files have same extension (pbas). Main file is identified by keyword program at the beginning, while other files have keyword module instead. If you want to include a module, add the keyword include followed by a quoted name of the file.
For example:
program test_project include "math2.pbas" dim tA as word dim tB as word main: tA = sqrt(tb) end.
Keyword include instructs the compiler which file to compile. The example above includes module math2.pbas in the program file. Obviously, routine sqrt used in the example is declared in module math2.pbas.
If you want to distribute your module without disclosing the source code, you can send your compiled library (file extension .mcl). User will be able to use your library as if he had the source code. Although the compiler is able to determine which routines are implemented in the library, it is a common practice to provide routine prototypes in a separate text file.
Module files should be organized in the following manner:
module unit_name ' Module name
include ... ' Include other modules if necessary
symbol ... ' Symbols declaration
const ... ' Constants declaration
dim ... ' Variables declaration
sub procedure procedure_name ' Procedures declaration
...
end sub
sub function function_name ' Functions declaration
...
end sub
end. ' End of module
Note that there is no “body” section in the module – module files serve to declare functions, procedures, constants and global variables.