Tiger BASIC - Reference
Bottom
Alphabetical list of keywords
All keywords can be completely upper or lower case, i.e. 'AND' or 'and'.- abs()
- addr()
- and
- append
- arccos
- arcsin
- arctan
- asc()
- bit
- bitreg
- case
- chr()
- cos
- cosh
- const
- default
- dim
- edit
- edit_numeric
- edit_text
- else
- elsif
- end
- endif
- endsel
- exit_edit
- float()
- for
- force_log
- gosub
- goto
- hex()
- if
- include
- integer()
- let
- ln
- log
- mem
- modbus_read()
- modbus_write()
- next
- or
- print
- reg
- rem
- repeat
- return
- scale()
- select
- serial_input
- serial_pointer
- set
- sin
- sinh
- sqr
- step
- tan
- tanh
- then
- to
- until
- write
- xor
- addr()
- abs()
Functional list of keywords
Comments and command separator
Top
- Assignments and definitions
Top
Conditionals
Top
Unconditional jumps
Top
Loop constructs
Top
Operators
Basic arithmetical operators
Higher math operators
Comparison operators
Logical bit operators
Top
- Special functions
Top
Display commands
Top
Communication and parsing commands
Top
Syntax
Expressions
Expressions represent a specific value and are used for assignments and in conditions. Simple expressions consist of only a number or a variable. Complex expressions are constructed by using arithmetic, logical or higher math operators.
Meters of the Tiger series can handle up to 10 levels of nested expressions.Example:
#A = &CH1 + arcsin ( &CH2 * ln &CH3 )
Expressions using basic arithmetic or logical bit operators
+, -, *, /, ^, ()
and, or, xorSyntax
exp ::= "-" exp
exp ::= "(" exp ")"
exp ::= exp op exp
op ::= "+" | "-" | "*" | "/" | "^" | and | or | xorExample:
#A = &CH1 ^ -( &Ch2 / 10 )
#B = #A and 0x07 // bit 0-2 of #A
Please note that AND and OR coincide with the operators to combine conditions. Therefore, logical bit operations have to be put inside parentheses in conditions
Expressions using higher math operators
arccos, arcsin, arctan, cos, cosh, ln (base e), log (base 10), sin, sinh, sqr, tan, tanh
Syntax
exp ::= unop exp
unop ::= arccos | arcsin | arctan | cos | cosh | ln | log | sin | sinh | sqr | tan | tanhExample:
#A = sqr &CH1
Top
Conditions
Conditions are used to compare the value of a variable or a register to an expression. They occur in the IF..THEN construct and the REPEAT..UNTIL loop.<, <=, =, >=, >, <>
on, off, true, false
and, orSyntax
condition ::= exp comp exp
condition ::= bit comp bit
condition ::= "(" condition ")"
condition ::= condition { comb_op condition }
comp ::= "<" | <= | "=" | >= | ">" | <>
bit ::= bitreg | bitvalue
bitvalue ::= on | off | true | false
comb_op ::= and | orExample:
if #A >= &CH1 - #offset then
write " too high "
endif
if |CAPTURE_PIN = off and |RECEIVE_READY = true then
gosub read_serial_data
endif
Please note that AND and OR coincide with the logical bit operators. Therefore, logical bit operations have to be put inside parentheses in conditions:
if (&DISPLAY and 0x00000001) = 0 then
// display value is even
endif
Top
Comments and command separator
:, \
Tiger BASIC has no line number but expects each command to be on a single, separate line.
To place more than one command in one line you can use the colon as a separator.
To write a single command in more then one line you can use the backslash at the end of the line.Example:
dim A[] = [ "Hello ", \
"World" ]
write A[0] : append A[1]
//, rem
Comments are started either by '//' or by REM and continue up to the end of the line. Whereas '//' can be placed everywhere, REM works like a command. Therefore it has to be separated with a colon from any preceding commands.Example:
// if the line contains only the comment
rem the comment markers don't differ
write "Hello " // but when preceded with other commands
append "World" : rem REM needs a separator
Top
Assignments and definitions
The following commands allow you to define names for variables, registers, and constants. These names have to be unique and may only contain letters, numbers and underscores, however they cannot start with a number. Please note that all user defined names as well as the pre-defined register names are case sensitive (i.e. TIMEOUT is not the same as TimeOut).=, let, set
All variables are global and have to be assigned an initial value before use (i.e. regarding to their first occurence in the source code). Variables are only available for numeric values. The variable type (integer, float) is indicated by a prefix (#, %).
There are also a great number of pre-defined variables which refer to registers or bit registers and are prefixed with '&' or '|', respectively. These do not have to be assigned before their first use.
To assign a variable, you can simply use the '=' operator or you can explicitly use the LET command for registers or the SET command for bit registers.
Example:
#A = -100
let %FloatValue_1 = 100.0
&CH1 = #Amem
With the MEM command registers can be preset when the macro is downloaded. This is typically used for meter setup and to initialize USER_MEMORY registers. MEM can also be used to initialize register arrays.Example:
mem &USER_MEMORY_1 = 0
// preset setpoint 1-3
mem &SETPOINT1[] = [ 1000, 2000, 3000 ]
bit, reg
To improve code readability, users can define other names for the predefined registers using the REG or BIT command. The name is prefixed with '&' or '|' respectively.Example:
reg &WaterTemperature = &CH1
bit |Phase_1 = |LED1bitreg
The BITREG command allows the user to address single bits of a register by name. The bits are named starting with bit 0 and have a '|' as prefix. Not all bits have to be named.Example:
bitreg &CODE2 = [ ,,,,,, |FREQ_50HZ, |FAST_MODE ]const
Number values can be named with the CONST command to improve code readability. It also allows to set a value that is used multiple times throughout the code in a single spot. Constants have no prefix and are replaced at compile time with their values (so the compiled code does not change whether you use constants or not).Example:
const ButtonTimeout = 40
const DefaultScaleFactor = 0.125
There are a few pre-defined constants for the CHR() command as well as for modbus communications.dim
String or integer arrays can be defined with DIM, outside of any macro at the top of the program. The first element is refered to with index 0. Integer arrays can contain only 16bit unsigned values.Example:
dim A[] = [ "0 Butane", "1 Oxygen", "2 Hydrogen", "3 Nitrogen" ]
dim B[] = [ 100, 101, 104, 109, 116, 125, 136, 149, 164, 181, 200 ]
...
write A[0] // this is equivalent to: write "0 Butane"
Top
Conditionals
The following two statements allow you to execute a sequence of commands when a certain condition is met.if..then ... endif
if..then ... else ... endif
if..then ... elsif ... else ... endifSyntax
if_statement ::= if condition then statement_sequenceThe IF statement can have any number of ELSIF branches but only one ELSE. Please note that the ENDIF command is mandatory.
{ elsif condition then statement_sequence }
[ else statement_sequence ]
endifExample:
if |SP1 = on then
&DATA_SOURCE_TOTAL1 = addr(&CH1)
elsif |SP2 = on then
&DATA_SOURCE_TOTAL1 = addr(&CH2)
else
&DATA_SOURCE_TOTAL1 = 0
endsel
select ... case ... endsel
select ... case ... default ... endselSyntax
select_statement ::= select var_reg
The SELECT statement allows you to test a register for fixed values. The register can be either integer or floating point and might even be an array index. It needs at least one CASE branch with at least one immediate value as well as the ENDSEL at the end. The optional final DEFAULT case matches any value
case immediate { "," immediate } ":" statement_sequence
{ case immediate { "," immediate } ":" statement_sequence }
[ default: statement_sequence ]
endsel
var_reg ::= var | var "[" exp "]"
The first match with the actual register value will lead to the execution of that branch. After that the execution continues after the ENDSEL command (unlike the switch statement in C, C++ which needs an explicit break).Example:
select &PumpState[#curPump]
case pOFF:
// do nothing
case pLOW, pMEDIUM:
write "Pump" + #curPump
case pHIGH:
write "Alarm"
endsel
Top
Unconditional Jumps
In contrast to the conditionals above, unconditional jumps allow you to resume command execution at another position in your code. This position has to be identified by a label.
A label consists of a string of letters, numbers, and underscores which has to be unique and cannot start with a number. It has to be placed by itself on the left hand margin on a new line followed only by a colon. The colon is only used to define the label but it is not part of the label name. All user defined labels are case sensitive. Only the pre-defined macro names (e.g. MAIN_MACRO) are case insensitive to avoid confusion with user defined labels. However, if you jump to pre-defined macro the label name has to match exactly (case sensitive) how you defined it in your code.
Although these jump commands are unconditional they are often used within conditionals.goto
The GOTO command simply jumps to the label position and continues with the command execution there. This allows you to write code that is very hard to read. However, there are cases where it actually simplifies the code, for example when you encounter a fatal error condition and you have to stop your application completely.Example:
error:
|Pump1 = off
|Pump2 = off
#state = sERROR
write "ALARM"
end
...
select #state
case pHIGH:
if &Temperature = FATAL then
goto error
endif
endsel
gosub, return, end
The GOSUB command (GO to SUBroutine) is similar to the GOTO command. However, before it jumps to the label position it remembers its current position. Then the execution continues at the label position until it encounters and END or RETURN command. At that point it will return back to the position where the GOSUB command was issued and continue with the execution.
This mechanism - jump to another code segment and return back after you are finished - is usually referred to as a subroutine. The pre-defined macros are basically only special subroutines. The END and RETURN commands are identical, but RETURN reflects the nature of a subroutine (that it returns back to the position where it was called) better. However, we recommend to use END with the pre-defined macros and RETURN with user defined subroutines to indicate this difference.
Subroutines can be called from within subroutines (also called nested subroutines) but only four levels deep. You can also GOSUB to a pre-defined macro.Example:
RESET_MACRO:
&TOTAL1 = 0
reset_cycle:
#counter = 0
#cycle = CYCLE_INIT
end
MAIN_MACRO:
if |CAPTURE_PIN = on then
gosub reset_cycle
endif
...
In this example the macro will execute only the last part of the RESET_MACRO when the capture pin is connected to common. But it is easier to read when you do it this way:
RESET_MACRO:
&TOTAL1 = 0
// last thing to do, so we don't have to get back
goto reset_cycle
end
reset_cycle:
#counter = 0
#cycle = CYCLE_INIT
return
MAIN_MACRO:
if |CAPTURE_PIN = on then
gosub reset_cycle
endif
...
Top
Loop Constructs
In most programming languages loop constructs are essential, either to iterate over a list or array, or to wait for an event. However, Tiger meters already have a fixed cycle time (macro loop) which restricts the execution time for any command sequence. Therefore loop contstructs in Tiger BASIC are only of limited use.
As the meter is always in a loop, waiting for an event is easily accomplished with a state machine (SELECT) or a simple IF command. But when you want to iterate over a register array within a single macro loop it is your responsibility to make sure that the whole loop can be finished in time.
You can check the time it takes to execute your macro by polling the &CPU_LOADING register.for ... next
Syntax
for_loop ::= for var "=" exp to exp [ step exp ] statement_sequence next varThe FOR loop uses a counter variable which is initialized (start value) and then increased with every NEXT command by the STEP value (default is 1). When the counter reached the TO (end) value the loop is terminated and the execution continues after the NEXT statement. As long as the counter is between the start and the end value the command sequence between FOR and NEXT will be executed.Example:
for #counter = 0 to 31
&TABLE1_OUTPUT1[#counter] = &USER_MEMORY_1[#counter]
next #counter
This example updates the output of linearization table 1 with values stored in USER_MEMORY 1 to 32.
repeat ... until
Syntax
repeat_loop ::= repeat statement_sequence until conditionThe REPEAT command leads to a so called non-rejecting loop as it executes the command sequence within at least once before checking the condition. If the condition is false the loop back to the repeat command and execute the sequence again.
As most registers are only updated by the operating system before each macro loop the condition should only rely on register changes by the macro within the loop.Example:
#digits = MAX_VALUE
print "&CH1 ="
repeat
print " "
#digits = #digits / 10
until #digits < &CH1
print &CH1
In this example the value of CH1 is printed to the serial port with leading spaces so that subsequent calls will output CH1 with right alignment.
This example also shows, that the REPEAT loop still needs some kind of counter (#digits) for the end condition which has to be initialized, too. Thus a FOR loop or a SELECT statement with all possible values of #digits would improve the readability.
Top
Special Functions
include
The INCLUDE command allows you to include code that is stored in a different file into the current file for compilation. Files can be included recursively up to 10 levels. The included file does not have to be a complete macro. Most likely, included files contain only constants, re-definitions of registers, or subroutines.Example:
include "C:\BasicLibrary\SerialProtocol.bas"
force_log
With the FORCE_LOG command the macro triggers the meter to take a log sample.Example:
if &TIMER1 > LOG_TIMEOUT then
&TIMER1 = 0
FORCE_LOG
endif
abs()
Syntax
abs_function ::= abs "(" exp ")"
The ABS() function returns the absolute value of an expression, i.e. it multiplies the expression with -1 if it is negative.Example:
#diff = abs(&CH1 - &CH2)
addr()
Syntax
addr_function ::= addr "(" var ")"
The ADDR() function returns the register number of its argument. The register number is needed for DATA_SOURCE registers and to read/write a register of a remote meter.Example:
// show the value of #diff on the display
&DATA_SOURCE_DISPLAY1 = addr(#diff)
asc(), chr()
Syntax
asc_function ::= asc "(" """ char """ ")"
chr_function ::= chr "(" ASCII_int ")"
The ASC() function returns the ASCII value of its character argument. This can be used to set the last digit character.
The CHR() function returns the character with the ASCII value of its integer argument, it is the reverse function to ASC(). This can be used to add whitespace characters to serial strings. The values of the most common whitespace characters are predefined as constants:
BELL, CR, FF, LF, ESC, NULL, BS, TAB, VTABExample:
mem &TEXT_CHARACTER_CH1 = asc("T")
print "Hello, World!" + CHR(CR) + CHR(LF)
scale()
Syntax
scale_function ::= scale "(" exp "," table_num ")"
table_num ::= "1" | "2" | "3" | "4"
With the SCALE() function you can apply a linearization table to a value in the macro.Example:
// apply lintable 2 to the sum of CH1, CH2, and CH3
#lin_out = scale(&CH1+&CH2+&CH3, 2)
Top
Display Commands
append, write
Syntax
append_statement ::= append text_string
write_statement ::= write text_string
write_port_statement ::= write const_int text_string
text_string ::= dim_array_name "[" const_int "]"
text_string ::= const_reg | sub_string { "+" sub_string }
sub_string ::= quoted_text | var | chr_function
const_reg ::= var | var "[" const_int "]"
The WRITE command allows you to display a message to the display. If the text does not fit on the display it is scrolled (the use of leading and trailing spaces is recommended in that case). The text can consist of one or more substrings concatenated with '+'. It can be either a quoted string, the return value of the CHR() function, or a register. In the latter case, the register is replaced with the current value of the register in the text. Arrays can only be used with a constant index.
If the WRITE command is used with an empty string ("") the display buffer is flushed. This can be used to interrupt a currently scrolled message.
The APPEND command allows you to add more text to the current message.
Example:
dim space[] = [ " ___", "___ " ]
const LEADING = 0
const TRAILING = 1
write "" // interrupt previous message
write space[LEADING]
append "Setpoint 1 = " + &SETPOINT1
append space[TRAILING]
Mutliple display meters of the Tiger 320 Series can only write to the default display (top display on DI503T, bottom display on DI602T and DI802T). Dual display meters (DI602T, DI802T) of the Tiger 380 series can write to both displays. The optional port infix after the WRITE command specifies which display should be used. The APPEND command will always use the same display as the last WRITE command.Example:
write 1 "Hello " // top display
write 2 "World!" // bottom/default display
It is not possible to scroll messages simultaneously on more than one display at a time.
In edit mode you cannot write to display 1 as it is used for the edit value.
edit, edit_numeric, edit_text, exit_edit
Syntax
edit_statement ::= edit constant | edit var_reg
edit_numeric_statement ::= edit_numeric
edit_text_statement ::= edit_text dim_array_name []
exit_edit_statement ::= exit_edit [ var_reg ]
var_reg ::= var | var "[" exp "]"
The EDIT commands refer to the edit mode of the meter. EDIT starts the edit mode with the mandatory parameter for the initial value and EXIT_EDIT - with an optional register to store the edit value - ends it.
The EDIT_TEXT command allows you to specify a text array that is shown instead of the numeric edit value (which is used as the index of that array). With EDIT_NUMERIC you can switch back to the standard numeric display.Example:
dim ExitEnter[] = [ " Exit", " Enter" ] const mEXIT = 0
const mENTER = 1
...
MAIN_MACRO:
if |CAPTURE_PIN = on and &STATE = 0 and &EDIT_STATE = 0 then
edit mEXIT
&EDIT_MIN = mEXIT
&EDIT_MAX = mENTER
edit_text ExitEnter[]
&STATE = eENTER_SETUP
endif
end
EDIT_MACRO:
select &STATE
case eENTER_SETUP:
exit_edit #temp
if #temp = mEXIT then
&STATE = 0
else
// continue with next value
endif
...
endsel
end
Top
Communication and parsing commands
print
Syntax
print_statement ::= print text_string
print_statement ::= print const_reg print_format
print_port_statement ::= print const_int text_string
print_port_statement ::= print const_int const_reg print_format
text_string ::= reg_string | sub_string { "+" sub_string }
sub_string ::= quoted_text | reg | chr_function
const_reg ::= var | var "[" const_int "]"
print_format ::= "," const_int [ "," ASCII_int ]
The PRINT command is used to send ASCII data to the serial output. Similar to the WRITE command strings are concatenated with '+' and register names (const_reg) are replaced with the value of the register. However, subsequent PRINT commands are concatenated (so there is no need for an APPEND).
The PRINT command with an empty string ("") argument resets the serial buffer (but not the serial pointer!). Please note that the meter has only one serial buffer for transmit and receive (half duplex communication).
For meters with multiple serial ports (not available on Tiger 320) the intended port can be specified with the port infix (defaults to 1 if omitted). Each serial port has its own buffer, flags, and pointers.
Example:
print "" // flush the buffer
print "Channel 1 = " + &CH1 + chr(CR) + chr(LF)
print 2 "Hello, World!" // print to second port
The formatted print allows you to restrict the width of the printed register. The first parameter are the number of digits, it is positive for right alignment and negative for left alignment. The optional second parameter is the ASCII character that is used to fill up any spaces (default is 'SPACE' = 32).Example:
// print CH1 left aligned, 8 chars wide, use '-' to fill up the trailing spaces
print &CH1, -8, asc("-")
serial_input
Syntax
serial_condition ::= serial_input [ const_int ] str_op comp_string [ "+" comp_string ]
str_op ::= "=" | <>
comp_string ::= quoted_string | chr_function
With the SERIAL_INPUT command you can compare the RECEIVE_BUFFER with a constant string. It starts with the first character of the buffer with a case sensitive match. If the string in the buffer is longer than the compare string the remaining characters are ignored.
For meters with multiple serial ports (not supported on Tiger 320) the optional port infix can be used to specify the serial buffer.Example:
if SERIAL_INPUT 2 = "SR" then
// string in serial buffer 2 starts with 'SR'
endif
float(), hex(), integer(), serial_pointer
Syntax
parse_func ::= num_func "(" serial_pointer [ const_int ] ["," const_int ] ")"
num_func ::= float | hex | integer
Starting with the Tiger 380 these extended parsing functions are available to convert strings into numbers. The SERIAL_POINTER (with optional port infix) points to the position in the buffer where the parsing starts, the second parameter indicates the maximum number of characters to be parsed. If the parsing function finds a valid number representation, it returns that number value and sets the SERIAL_POINTER after the parsed section.
The INT function ignores any decimal points in the pattern, the FLOAT function can handle only one decimal point as well as a two digit signed exponent (e.g. -123.456e-10), the HEX function reads only hexadecimal numbers (0-9a-fA-F) and is limited to 8 chars as the Tiger can only store 32bit registers.
Please be aware that there is a &SERIAL_POINTER register, too. While this contains the actual index (which can be read or written to), the special command SERIAL_POINTER without the '&' is to be used with the parsing functions.
Example:
// read integer from index 8 to index 13 of buffer 2
&SERIAL_POINTER2 = 8
#data = int(serial_pointer 2, 6)
// read float variable from current position in default buffer (port 1)
%scale = float(serial_pointer)
modbus_read(), modbus_write()
Syntax
mb_read ::= modbus_read "(" const_int "," const_int "," var [ "," mb_flag ]")"
mb_write ::= modbus_write "(" const_int "," var "," const_int [ "," mb_flag ]")"
mb_flag ::= MB_BYTE | MB_SHORT | MB_24 | MB_24_SWAPPED | MB_LONG | MB_LONG_SWAPPED | MB_FLOAT | MB_FLOAT_SWAPPED
With the Tiger 380 we introduced a Modbus Master mode. The two modbus functions allow you to read or write a modbus register of a remote device. The first paramter is the device ID of the remote meter, the second parameter is the source (the address to read or the local register to write), and the third paramter is the target (where the read data is to be stored or the register that is written on the remote device).
The optional fourth parameter specifies the data type. If it is not specified the type of the local register is assumed. While the Modbus standard only allows for 16bit registers, many Tiger registers are 24 or 32 bit. Therefore two consecutive 16bit registers are read/written to transmit the additional data. Pleas note that for this reason the 24/32bit modbus register numbers on the Tiger 320 differ from the register numbers in ASCII mode.
An optional port infix can be used to specify the serial buffer.
Both commands should only be invoked within the MODBUS_MASTER_MACRO which can wait for the reply longer than a macro loop.
Please note that the accessible register addresses use the default offset of 40000. Thus the ADDR() function can be used to determine the address to talk to another Tiger meter of the same series. Coil/bit addresses (0x and 1x range) are not supported, addresses in the 3x range have to be specified explicitely with the 30000 offset.
Example:
const TIGER320_RESULT = 40513
MODBUS_MASTER_MACRO:
|LED1 = off : |LED2 = off
if #modbus = mbREAD then
// read CH3 from other Tiger 380
modbus_read 2 (5, addr(&CH3), &RESULT)
|LED1 = on
elsif #modbus = mbWRITE then
// write to a Tiger 320
modbus_write 2 (7, &RESULT, TIGER320_RESULT)
|LED2 = on
endif
end
See our Tiger BASIC Tutorial for further details.
Vista, CA, 2005 March 21