JalV2 User's Guide



Table of Contents

1. Introduction

2. Conventions Used in This Text

3. Program Structure

4. Identifiers

5. Types

5.1 BIT

5.2 BYTE[*cexpr]

5.3 SBYTE[*cexpr]

5.4 Universal

6. Constants

6.1 Literal

6.2 Named

7. Variables

8. Expressions

9. Flow Control

9.1 BLOCK

9.2 CASE

9.3 FOR

9.4 FOREVER

9.5 IF

9.6 REPEAT

9.7 WHILE

10. Sub-programs : Procedures and Functions

11. Pseudo-variables

12. Interrupts

13. Tasks

14. Assembly



1. Introduction

This text is not meant to be a programming tutorial – the reader is expected to have some programming background and understand concepts like procedure, functions, variables, etc. Many good web sites and a yahoo discussion group exist to help with any questions.

JALv2 builds on the original JAL which is loosely based on Pascal. Like Pascal, identifiers are not case sensitive and procedures and functions can be defined and nested. Unlike Pascal statement blocks do not require an explicit BEGIN and END statement, and variables, procedures, and functions can be defined anywhere in the program.

Much like C, there are very few keywords and built-in functions. With the availability of sub-programs, any function can be created in the language itself. This simplifies the compiler and supporting libraries. The exception to this is multiplication, division, and modulus. These are far easier to create within the compiler and make a much more logical looking language. For example, instead of writing:

z = multiply ( x, y )

we need write only:

z = x * y

This also allows for some optimization by the compiler -- if y is a power of two, the multiply or divide is replaced by a shift of the appropriate amount.

The only other exception to the built-in rule is:

_usec_delay(cexpr)

This built-in generates the correct number of instructions to guarantee the delay is exactly the required length (assuming interrupts are not enabled). This is accomplished using one, two or three nested loops along with however many nop instructions are required to complete.

JAL is a free flowing language -- there is no limit on the length of a line. An entire program can occupy a single line! Comments are preceded by either two dashes ("--") or a semi-colon (";"). Once a comment mark has been found, the comment continues until the end of the current line.

2. Conventions Used in This Text

Fixed Font : JALv2 program

UPPER CASE : a JALv2 keyword

expr: a constant or variable expression

cexpr: a constant expression (one consisting of only constant operations)

[...] : an optional argument

{ ... | ... } : choose one of the options

statement : a single JALv2 statement (assignment, flow control, etc.)

statement block : one or more JALv2 statements; implies a block

3. Program Structure

A program is a series of statements. Like BASIC, the statements simply begin. Procedures and Functions are skipped unless explicitly called. Identifier scoping looks like:



Program Scope: A,B,C

Procedure proc1: A1,B1,C1


Procedure proc2: A2,B2,C2

Procedure proc2a: a2a, b2a, c2a

Procedure proc2b: a2b, b2b, c2b




Here, all procedures can see the global identifiers (A, B, C). The global procedure (main program) can see proc1 and proc2, but not identifiers defined in proc1, or proc2 (so it cannot see proc2a or proc2b). Proc2 can see proc2a and proc2b. Proc2a and proc2b can see A2, B2, C2, proc1, A, B, and C

4. Identifiers

An identifier may begin with an underscore (“_”) or a letter, and may be followed by any number of letters, numbers, or underscores. By convention, all identifiers beginning with “_” are reserved by the compiler for internal use.

JAL is not case sensitive, so program, Program, and PROGRAM all represent the same identifier. The scope, or life, of an identifier is the closest enclosing block. Blocks are either implicitly started (for example, whenever a statement block is encountered), or explicitly with the BLOCK command. All identifiers within a block must be unique, but the same identifier may be used inside nested blocks. For example:

VAR BYTE x

VAR BIT x

is an error, because x has been defined twice. However,

VAR BYTE x

BLOCK

VAR BIT x

...

END BLOCK

is not an error, because x (the BIT) is defined in a different block. This is generally a bad idea since when reading the program one might be confused about what x is.

5. Types

There are three of variables in JALv2 – BIT, BYTE, and SBYTE.

5.1 BIT

A single bit variable useful for addressing individual pins in a port for example.

5.2 BYTE[*cexpr]

A single or multi-byte unsigned variable. cexpr, if present, sets the width (in BYTEs). JALv2 has two useful aliases, WORD which is BYTE*2, and DWORD which is BYTE*4. These are not the only allowed sizes. It is useful to have BYTE*3 for some fixed-point operations, or even BYTE*8 to prevent overflow in operations.

5.3 SBYTE[*cexpr]

A single or multi-byte signed variable. cexpr, if present, sets the width (in BYTEs). Again JALv2 has two useful aliases, SWORD which is SBYTE*2, and SDWORD which is SBYTE*4.

5.4 Universal

The UNIVERSAL type is the type assumed for unnamed constants and literals. Internally, it is 32 bits wide, either signed or unsigned. When used in an operation with another type, the result is that of the other operand. In the event that the UNIVERSAL constant will not fit within the other operand and warning will be generated.

6. Constants

6.1 Literal

A literal constant is simply a number. It has the UNIVERSAL type. By default, literal constants are assumed to be decimal, but this can be overridden with the following prefixes:

0b... ; defines a binary constant

0q... ; defines an octal constant

0x... ; defines a hexidecimal constant

After the prefix, the underscore character can be used to group the digits, for example:

0b0000_1111_0011_1100

any underscores are ignored.

x” ; defines a character constants. The result is the ASCII value of the character

xyz”; defines a character string. The result is a universal array, with each element representing a single character.

{ cexpr1, cexpr2, ... } ; defines a universal array.

6.2 Named

Syntax:

CONST [type] identifier1 “=” cexpr1[, identifier2 “=” cexpr2...]

CONST [type] identifier1"[" [cexpr] "]" "=" "{" cexpr1[, cexpr2...] "}"

If [type] is present, the identifier will be of that type, otherwise it will be UNIVERSAL. The value identifier marked CONST will retain the same throughout its life.

In the second form, an array of values is created. If cexpr is present, it must be greater than zero and the number of initializers must match the value of cexpr. If it is not present, it's value is derived from the number of initializers. To access an element of a constant array, use bracket notation: x[5] or x[ii]. Array elements are numbered 0 through cexpr - 1. A constant array is limited by available program space in the destination device.

7. Variables

Syntax:

VAR [VOLATILE] type identifier[ ["[" cexpr "]"] ] [ “=” expr1 ][, identifier2 [“=” expr2]...]

Variables are much like named constants, except their values can change throughout the life of a program, and their types must be explicitly set. type is one of: BIT, BYTE[*cexpr], SBYTE[*cexpr], WORD, SWORD, DWORD, or SDWORD. The VOLATILE keyword, if present, guarantees the following:

If "[" cexpr "]" is present, cexpr must be a value greater than 0. This defines an array of values. To access any element in the array, one uses brackets, as in : x[5] or x[y]. Array elements begin with index 0 and continue through cexpr - 1. The array size is limited to largest available data space. Arrays, like all variable types, must fit within a single data bank.

Normally, the compiler determines where each variable is placed, but it is often useful for variables to be placed at specific locations (for example, to take advantage of the special function registers of the PIC). This is accomplished using the AT clause:

VAR [VOLATILE] type identifier AT { cexpr1 | var } [“:” cexpr2] [“=” ...]

This will place identifier at absolute location cexpr1, or will share the location of var. If type is BIT, the variable can optionally be placed at an absolute bit position at the location. Note that a variable must fit entirely within a data page. For example, putting a WORD variable at location 0x7f will lead to undefined behaviour.

Sometimes it is useful for a variable to be renamed. To create an alias, the syntax is:

VAR [VOLATILE] type identifier1 IS identifier2

8. Expressions

Now that we have variables and constants, it's time to create expressions. An expression is simply value (variables or constants) and operators. The operators of JALv2 are:

Operator

Operation

Result

count

returns the number of elements in an array

UNIVERAL

whereis

returns the location of an identifier

UNIVERSAL

defined

determines if an identifier exists

UNIVERAL

-

Unary – (negation)

Same as operand

!

1's complement

Same as operand

!!

Logical

BIT

+

Unary + (no operation)

Same as operand

*

Multiplication

Promotion2

/

Division

Promotion2

%

Modulus division (remainder)

Promotion2

+

Addition

Promotion2

-

Subtraction

Promotion2

<<

Shift left

Promotion2

>>1

Shift right

Promotion2

<

Less than

BIT

<=

Less or Equal

BIT

“==”

Equality

BIT

!=

Unequal

BIT

>=

Greater or Equal

BIT

>

Greater Than

BIT

&

Binary AND

Promotion2

|

Binary OR

Promotion2

^

Binary Exclusive OR

Promotion2

1 shift right: If the left operand is signed the shift is arithmetic (sign preserving). If unsigned, it is logical.

2 promotion: The promotion rules are tricky, here are the cases:

If one of the operands is UNIVERSAL and the other is not, the result is same as the non-UNIVERSAL operand.

If both operands have the same signedness and width, the result is that of the operands.

If both operands are the same width, and one is unsigned, the result is unsigned.

If one operand is wider than the other, the other operand will be promoted to the wider type.

9. Flow Control

9.1 BLOCK

Syntax:

BLOCK

statement block

END BLOCK


This simply creates a new block. Any variables defined in this block go out of scope at the block. Mainly useful with the CASE statement (below).

9.2 CASE

Syntax:

CASE expr OF

cexpr1[, cexpr1a...] “:” statement

[ cexpr2 “:” statement ]

...

[ OTHERWISE statement ]

END CASE


expr is evaluated and compared against each cexpr listed. If a match occurs, the statement to the right of the matching cexpr is executed. If no match occurs, the statement after OTHERWISE is executed. If there is no OTHERWISE, control continues after END CASE. Unlike Pascal, the behaviour is completely defined if there is no matching expression.

Unlike C (but like Pascal) there is no explicit break. After a statement is processed, control proceeds past the END CASE.

Each cexpr must evaluate to a unique value.

9.3 FOR

Syntax:

FOR expr [USING var] LOOP

statement block

[exit loop]

END LOOP

statement block is executed expr times. If USING var is defined, the index is kept in var, beginning with zero and incrementing to expr. If var is not large enough to hold expr, a warning is generated. If [exit loop] is used, the loop is immediately exited.

9.4 FOREVER

Syntax:

FOREVER LOOP

statement block

[exit loop]

END LOOP

statement block is executed forever unless [exit loop] is encountered, in which case the loop is immediately terminated.. This is commonly used for the main loop in a program because an embedded program like this never ends.

9.5 IF

Syntax:

IF expr THEN

statement block

[ELSIF expr2 THEN

statement block]

[ELSE

statement block]

END IF

This creates a test, or series of tests. The statement block under the first expr that evaluates to a non-zero value will be executed. Any number of ELSIF clauses are allowed. If no expr evaluates to true and the ELSE clause exists, the statement block for the ELSE clause will be executed.

A special case of the IF statement is when any expr is a constant. In this case, the statement block is not parsed. This can be used for block comments.

IF 0

this is a dummy block that won't even be parsed!

END IF

9.6 REPEAT

Syntax:

REPEAT

statement block

[exit loop]
UNTIL expr

statement block will be executed until expr evaluates to a non-zero value, or until [exit loop] is encountered.

9.7 WHILE

Syntax:

WHILE expr LOOP

statement block

[exit loop]
END LOOP


statement block will be executed as long as expr evaluates to a non-zero value, or until [exit loop] is encountered. This is similar to REPEAT above, the difference being the statement block of REPEAT loop will always execute at least once, whereas a that of a WHILE loop may never execute.


10. Sub-programs : Procedures and Functions

Syntax:

PROCEDURE identifier [ “(“ [VOLATILE] type { IN | OUT | IN OUT } identifier2 [, ...] “)” IS

statement block

END PROCEDUREs



FUNCTION identifier [ “(“ [VOLATILE] type { IN | OUT | IN OUT } identifier2 [, ...] “)” RETURN type IS

statement block

END FUNCTION

The only difference between a PROCEDURE and a FUNCTION, is the former does not return a value, while the later does. The procedure identifier exists in the block in which the procedure is defined. A new block is immediately opened, and all parameters exist in that block. A parameter marked IN will be assigned the value passed when called. A parameter marked OUT will assign the resulting value to parameter passed when called. While in a sub-program, a new keyword is introduced:

RETURN [expr]

When executed, the sub program immediately returns. If the sub program is a FUNCTION, expr is required. If it is a PROCEDURE, expr is forbidden.

A sub-program is executed simply by using its name. If parameters are specified in the sub-program definition, all parameters are required, otherwise none are allowed. A FUNCTION can be used anywhere a value is required (in expressions, as parameters to other sub-programs, etc). There is no limit to the number of parameters.

JAL is a pass by value language. Conceptually, an IN parameter is read once when the sub-program enters, and an OUT parameters is written once when the sub-program returns. This is not always desired. For example if a sub-program writes a string of characters to the serial port (passed as parameter), only the last character written will be sent. For this case we need VOLATILE parameters. These are either read each time used (IN) or written each time assigned (OUT). This is accomplished using pseudo variables (see below). If the value passed is not a pseudo-variable, a suitable one is created.

Procedures and functions can be nested.

Example:

FUNCTION square_root (WORD IN n) RETURN WORD IS

WORD result

WORD ix

ix = 1

WHILE ix < n LOOP

n = n – ix

result = result + 1

ix = ix + 2

END WHILE

RETURN result

END FUNCTION



xx = square_root(xx)


Recursion is fully supported but due to the overhead it is discouraged.

11. Pseudo-variables

Syntax:

PROCEDURE identifier “'” PUT “(“ type IN identifier2 “)” IS

statement block

END PROCEDURE

FUNCTION identifier “'” GET RETURN type IS

statement block

END FUNCTION

A pseudo-variable is a sub-program, or pair of sub-programs that work as if they are variables. If a 'PUT procedure is defined, any assignment to the sub-program is actually a call to the 'PUT procedure. Similarly, if a 'GET function is defined, any time the associated value is used is an implicit call to the function.

If both a 'GET and 'PUT sub-program are defined, the parameter type of the 'PUT must match the return type of the 'GET.

12. Interrupts

Syntax:

PROCEDURE identifier IS

PRAGMA INTERRUPT [FAST]

statement block

END PROCEDURE



PRAGMA INTERRUPT tells JAL that this procedure can only be called by the microcontroller's interrupt processing. Any number of procedures can be defined as an interrupt handler. When an interrupt occurs, first the microprocessor state is saved, then control passes to the first procedure marked as an interrupt handler. Control continues to pass to each interrupt handler until the last, then the microprocessor state is restored and the interrupt ended. The programmer is responsible for clearing whatever bits caused the interrupt to happen. A procedure marked as an interrupt handler cannot be called directly from elsewhere in the program. Beyond that, an interrupt handler can do anything any other procedure can do. The order the interrupt handlers are called is undefined, the only guarantee is each handler will be called at each interrupt, and will only be called once.

If an interrupt handler executes a sub-program that is also executed by the main body of the program, that sub-program will be marked recursive and incur the recursion overhead each time it is called.

If FAST is declared, the interrupt handler will only save the minimum amount of state necessary. This must be used with great care – although the microprocessor state is saved, state used internally by the compiler is not. As such, only a completely assembly sub-program should be used. Any JAL statements might invalidate the internal state of the compiler. If any interrupt handler is marked FAST then only one interrupt handler is allowed.

13. Tasks

Syntax:

TASK identifier [ “(“ parameter list “)” ] IS

statement block

END TASK



JALv2 introduces the concept of tasks which are a form of co-operative multi-tasking. Unlike preemptive multi-tasking, where control passes from one task to another automatically, control will only pass when a task specifically allows it. Due to the architecture of a PIC, true multi-tasking is very difficult. Tasks can only be started by the main program, or within another task. Tasks are started with:

START identifier [ “(“ parameter list “)” ]

When a task is ready to allow another to run, it executes:

SUSPEND

To end the task, simply RETURN or allow the control to pass to the end of the task. If tasks are used, the compiler must be passed the argument, “-task n,” where n is the number of concurrent running tasks. Remember that the main program itself is a task, so if you plan to run the main program plus two tasks, you'll need to pass in, “-task 3”.

Finally, only one copy of the body of a task should be run at a time. The following would be an error because it attempts to run two copies of task1 at the same time:

START task1

START task1

FOREVER LOOP

SUSPEND

END LOOP



14. Assembly

Finally, when all else fails, one can resort to in-line assembly. This can be in the form of a single statement:

asm ...

or an entire block:

assembler

...

end assembler

Using assembly should be a last resort – it is needed only when either a feature is not possible using JAL (for example, the TRIS and OPTION codes), or when speed is of the essence. JALv2 includes the entire assembly language set in the PIC16F87x data sheet, several instructions from earlier micro controllers, and several common macros.

To guarantee the correct data bank is selected when accessing a file register, use one of the following:


bank opcode...

or

bank f


the former takes the file register from the command, the later takes it directly.


Similarly, to guarantee the correct page bits are set (for goto or call), use one of the following:


page opcode ...

or

page lbl


Again, the former takes the label from the command, the later takes it directly.

Normally, the codes to set or clear the bank or page bits are only generated when necessary. If the correct bits are already in the correct states, no further commands are generated. If you need to guarantee the codes are always generated, use the following pragmas:


pragma keep page

pragma keep bank


The former will keep any page bits, the later and bank bits. These affect the entire sub-program in which they are declared.

To declare a local label for use in calls and/or gotos:


local identifier1[, identifier 2...]


Once declared, a label is inserted into the assembly block by making it the first part of a statement, followed by a ':':


identifier: opcode...


The available opcodes are listed below. For a full description see the appropriate data sheet.


addwf f, d

andwf f, d

clrf f

clrw

comf f, d

decf f, d

decfsz f, d

incf f, d

incfsz f, d

iorwf f, d

movf f, d

movwf

nop

rlf f, d

rrf f, d

subwf f, d

swapf f, d

xorwf f, d

bcf f, b

bsf f, b

btfsc f, b

btfss f, b



addlw k

andlw k

call l

clrwdt

goto l

iorlw k

movlw k

retfie

retlw k

return

sleep

sublw k

xorlw k

option1

tris2 t




1 option moves the contents of W into the OPTION register. It is deprecated on microcontrollers that have OPTION mapped into the special function registers.

2 tris moves the contents of W into the specified TRIS register. It is deprecated on microcontrollers that have the TRISx registers mapped into the special function registers.


f - a file register, this can be a variable name or a number

d - destination, must evaluate to 0 (result in W), or 1 (result in f)

k - a constant between 0 and 255

t - a constant 5, 6, 7, 8 or 9

b - a constant 0 - 7

l - a label


In addition to the real opcode, the following common pseudo-ops are also available:


opcode

description

addcf f, d

btfsc _status, _c ; incf f, d

adddcf f, d

btfsc _status, _dc ; incf f, d

b l

goto l

bc l

btfsc _status, _c ; goto l

bdc l

btfsc _status, _dc ; goto l

bnc l

btfss _status, _c ; goto l

bndc l

btfss _status, _dc ; goto l

bnz l

btfss _status, _z ; goto l

bz l

btfsc _status, _z ; goto l

clrc

bcf _status, _c

clrdc

bcf _status, _dc

clrz

bcf _status, _z

lcall l

page call l

lgoto l

page goto l

movfw f

movf f, w

negf, d

comf f, f ; incf f, f

setc

bsf _status, _c

setdc

bsf _status, _dc

setz

bsf _status, _z

skpc

btfss _status, _c

skpdc

btfss _status, _dc

skpnc

btfsc _status, _c

skpndc

btfsc _status, _dc

skpnz

btfsc _status, _z

skpz

btfss _status, _z

subcf f, d

btfsc _status, _c ; decf f, d

subdcf f, d

btrfsc _status, _dc ; decf f, d


Finally, the following can be used to define data:


db cexpr1, cexpr2[, cexpr3, cexpr4...]

Places values cexpr1:cexpr2... directly into the code. cexpr1 is the MSB, and cexpr2 the LSB. On a 14 bit core, 0 <= cexpr1 < 64, 0 <= cexpr2 < 256.

db "..."

Place a string of values directly into the code. This is analogous to:

db 0, "a", 0, "b", ...

dw cexpr1[, cexpr2...]

As above, but each cexpr is placed in the program flash as a word. On a 14 bit core, 0 <= cexpr < 16384

dw "..."

Place a string directly into the code. This is the same as `dw "a","b", "c"...'

ds cexpr1[,cexpr2...]

This packs two characters at a time into the code. 0 <= cexpr < 128

ds "..."

As above, using string notation


Although these put data directly into the code, one needs to create the necessary functions to read the data (which is out of the scope of this document).

An example of using strings as above:


FUNCTION string_entry_get(BYTE IN ix) RETURN BYTE is

ASM local str_table, readit

ASM GOTO readit

ASM str_table: DB "this is a test string"

ASM readit:

WORD pos

BYTE ch


pos = WHEREIS(str_table) + IX

;

; code to read program FLASH at pos into ch would follow

;

RETURN ch

END FUNCTION