Home
Downloads
Licenses Contact

OshonSoft

20th Anniversary

20th Anniversary of the
OSHONSOFT PROJECT
(2001 - 2021)

AVR Simulator IDE
BASIC Compiler Reference Manual


Table Of Contents:

General info


About variables


Mathematical and logical operations


Standard Basic language elements


Complex expressions evaluation


Subroutines


Microcontroller related language elements


Special Basic language elements


Structured language support (procedures and functions)


Using internal EEPROM data memory


Using internal A/D converter module


Using interrupts


String data type related basic elements


Modbus firmware implementation


Interfacing MMC/SD/SDSC/SDHC cards implementation


Serial communication using internal hardware UART


Software UART implementation


Interfacing character LCDs


I2C communication with external I2C devices


Support for software master SPI (Serial Peripheral Interface) communication


Interfacing graphical LCDs with 128x64 dot matrix


Interfacing Radio Control (R/C) servos


Interfacing Stepper Motors


Interfacing 1-WIRE devices


OshonSoft PID Controller Library


OshonSoft PID Auto Tuning Library


Advanced features


#define directive parameters

Library support


• General info

Basic compiler editor is composed of editor panel (for user program editing) and source explorer (for easy navigation through all elements of user program - variables, symbols, constants, subroutines, procedures and functions). Editor formats and colorizes entered lines of user program, that simplifies the debugging process.

In all the user-defined program element names, that is in all the variable names, names of the procedures, functions, subroutines, constants, symbols and labels, both lower-case and upper-case characters can be used, along with the underscore and numeric characters. A numeric character will not be accepted by the compiler to be the leading character in the element name.

The primary output of the compiler is an assembler source file. However, with an appropriate command from the menu it can be assembled and even loaded in the simulator with a single click. Menu commands and options are rich, as well as the commands from the right-click pop-up menus for the editor and source explorer. Basic compiler's assembler output contains many useful comment lines, that makes it very helpful for educational purposes, also.

Show Warnings
If Show Warnings option is enabled, in the Warnings window Basic compiler will show information about unused declarations, subroutines, procedures and functions in the user basic program.

Do Not Compile Unused Code
If this option is enabled, Basic compiler will not compile unused declarations, subroutines, procedures and functions, in order to save memory resources.

Initialize Variables On Declaration
If this option is enabled, Basic compiler will reset to zero all memory locations allocated for variables, at the position of their declaration in the basic program. This option is useful for beginners, because SRAM memory is filled with random values at device power-up, and it is easy to make a mistake to assume that all variables are reset to zero at power-up. Experienced users can save some program memory, by disabling this option and taking control of variable initial values by user program where necessary.

Optimize Variables Declaration
This option will turn on the compiler internal routine that will optimize the variables declaration order based on the usage frequency of the variables. In this way, the most frequently used variables will be stored in lower SRAM memory locations, resulting in possibly smaller size of the generated code.

Dynamic Temporary Variables Management
If dynamic management is enabled, temporary variables created by compiler will not reserve allocated SRAM memory permanently. Allocated memory will be released immediately after use, thus minimizing the total SRAM memory required to compile the program. As a consequence, temporary compiler variables will not be listed in the Watch Variables tool.

• About variables

The following data types are supported:
Bit - 1-bit, 0 or 1
Byte - 1-byte integers in the range 0 to 255
Word - 2-byte integers in the range 0 to 65,535
Long - 4-byte integers in the range 0 to 4,294,967,295
Short - 1-byte signed integers in the range -128 to 127
Integer - 2-byte signed integers in the range -32,768 to 32,767
LongInt - 4-byte signed integers in the range -2,147,483,648 to 2,147,483,647
Single - 4-byte single precision floating point numbers, 7 digits precision, modified IEEE754 standard
String - arrays of bytes containing ASCII character codes, 8-100 bytes (plus one string termination byte) long
Byte, Word, Long, Short, Integer and LongInt variable types will be sometimes referred to as 'all integer data types'.
With Single variable type added to the list, those variable types will be sometimes referred to as 'all numeric data types'.

Variables can be global (declared in the main program, before the End statement) or local (declared in subroutines, procedures and functions). Variable name used for a variable with global scope can be used again for local variable names. The compiler will reserve separate memory locations for them. The total number of variables is limited by the available microcontroller SRAM memory. Variables are declared using DIM statement:
   Dim i As Bit
   Dim j As Byte
   Dim k As Word
   Dim x As Long
   Dim y As Single
   Dim str_var As String

Dim statement allows multiple declarations in one line of code by using comma-separated list of variable names:
   Dim k1, k2, k3 As Word

If necessary, variable address can be specified during declaration:
   Dim x As Byte @ 0x062

It is possible to use one-dimensional arrays for Byte, Word, Long and Single variables. For example:
   Dim x(10) As Byte
declares an array of 10 Byte variables with array index in the range [0-9].
An array of Byte variables can contain up to 1024 elements, up to 512 array elements is available for Word variables, for Long and Single variables the upper limit is 256 array elements. These values also depend on the available SRAM memory.

RESERVE statement allows advanced usage by reserving some of the SRAM locations to be used by in-code assembler routines. For example:
   Reserve 0x065

High and low byte of a Word variable can be addressed by .HB and .LB extensions (dot notation). Individual bits can be addressed by .0, .1, ..., .14 and .15 extensions. It is possible to make conversions between Byte and Word data types using .LB and .HB extensions or directly:
   Dim x As Byte
   Dim y As Word
   x = y.HB
   x = y.LB  'This statement is equivalent to x = y
   y.HB = x
   y.LB = x
   y = x  'This statement will also clear the high byte of y variable

High word (composed by bytes 3 and 2) and low word (composed by bytes 1 and 0) of a Long (Single) variable can be addressed by .HW and .LW extensions. Byte 0 can be addressed by .LB and byte 1 by .HB extensions. The third and the fourth byte of Long and Single variables can be addressed by .3B and .4B extensions. Individual bits can be addressed by .0, .1, ..., .31 extensions. For example:
   Dim i As Byte
   Dim j As Word
   Dim x As Long
   i = x.LB
   j = x.HW

All general purpose working and I/O registers are available as Byte variables in basic programs. Individual bits of a Byte variable can be addressed by .0, .1, .2, .3, .4, .5, .6 and .7 extensions or using official names of the bits. The lists of these predefined variables and official bit names for the selected AVR device can be viewed by selecting Show System Variables and Show System Bit Names commands from the Options menu of the basic compiler window.
   Dim x As Bit
   Dim y As Byte
   x = y.7
   y.6 = 1
   R26 = 10
   XH = R26
   DDRD.1 = 1
   DDRB = 255
   PORTD.1 = 1
   PORTB = 255
   GIMSK.PCIE = 1
   SREG.SREG_I = 1

Standard short forms for accessing port registers (PORTx) and individual chip pins (PORTx.n) are also available (PA, PB, PC, ... can be used as Byte variables; PA0, PA1, PA2, ..., PB7, PC0, ... are available as Bit variables). It should be noted that these short forms can not be used to address port and pin input states. PINx and PINx.n should be used for that purpose ('x' represents the port numbering letter; 'n' represents the bit number).
   PB = 0xff
   PD5 = 1

It is possible to address/index individual bits in Byte and Word data type variables with a Byte, Word or Long index variable using the dot (.) operator (dot notation). Indexing is 0-based. Index variable value should be in 0-7 range for bits in Byte variables, and 0-15 range for bits in Word variables.
Example 1:
   Dim i As Byte
   Dim j As Byte
   i = 0xff
   For j = 0 To 7
      i.j = 0
   Next j

Example 2:
   Dim i1 As Word
   Dim i2 As Word
   Dim j As Byte
   
   i1 = 0x55aa
   For j = 0 To 15
      i2.j = i1.j
   Next j

It is possible to use symbolic names (symbols) in programs, to easily address system variables, or to create aliases for variables of all available data types. Symbols can be global or local. SYMBOL directive is used to declare symbolic names:
   Symbol led1 = PORTB.0
   led1 = 1
   Symbol adc_start = ADCSRA.ADSC
   adc_start = 1

Constants can be used in decimal number system with no special marks, in hexadecimal number system with leading 0x or leading $ notation (or with H at the end) and in binary system with leading % mark (or with B at the end). ASCII value of a character can be expressed in string format (e.g. "A"). Keywords True and False are also available for Bit type constants. For example:
   Dim x As Bit
   Dim y As Byte
   x = True
   y = 0x55
   y = %01010101
   y = "Y"

Constants can be assigned to symbolic names using CONST directive. Constants can be global or local. One example:
   Dim x As Single
   Dim y As Word
   Const pi = 3.14159
   Const highval = 1023
   x = pi
   y = highval

Any integer variable can be used as a pointer to internal data SRAM memory when it is used as an argument of POINTER function. The value contained in the variable that is used as a pointer should be in the appropriate range. Here is one example:
   Dim x As Word
   Dim y As Byte
   Dim k As Long
   x = 0x070
   y = Pointer(x)  'The content of SRAM location $070 will be loaded to variable y
   y = y + 0x55
   x = x - 1
   Pointer(x) = y  'Modified y value will be stored to SRAM location $06F
   x = 0x077
   k = 0x12345678
   Pointer(x) = k  'Four SRAM locations $077-$07A will be loaded with the 4-byte value contained in variable k

It is possible to use comments in basic source programs. The comments must begin with single quote symbol (') and may be placed anywhere in the program.
Comment sign '//' is an alternative for the standard single quote sign.

Lines of assembler source code may be placed anywhere in basic source program and must begin with ASM: prefix. If labels are used, no space should be left between the ASM: prefix and the label. For example:
   ASM:        NOP
   ASM:LABEL1: LDI R16,0xFF

Symbolic names of all variables, symbols and constants (global and local) can be used as the arguments of the assembler instructions. The compiler will replace that symbolic name with the proper variable address or constant value:
   Dim varname As Byte
   varname = 0
   ASM:        LDI R16,0xFF
   ASM:        STS VARNAME,R16

If large amount of assembler code should be used, it can be loaded from an external assembler file and included to the current program by using IncludeASM directive. Its only argument is a string containing the path to the external .ASM file. This can be the full path or only the file name, if the external file is located in the same folder as the current basic program file. During the compilation process the external assembler code will be appended to the current program at its end, and not at the position of the directive. Multiple files can be included with separate IncludeASM directives. External assembler files should not contain ASM: prefix used for inline assembler code. It is also strongly suggested not to use ORG directives in the external assembler code. For example:
   IncludeASM "test.asm"
   IncludeASM "d:\example\test2.asm"

• Mathematical and logical operations

Five arithmetic operations (+, -, *, /, MOD) are available for all integer data types. MOD operation is not applicable for Single data type variables. The compiler is able to compile all possible complex arithmetic expressions, including those containing math functions and user-defined functions. For example:
   Dim i As Word
   Dim j As Word
   Dim x As Word
   i = 123
   j = i * 234
   x = 2
   x = (j * x - 12345) / (i + x)

Square root of an integer number (0-65535 range) can be calculated using SQR function:
   Dim x As Word
   x = 3600
   x = Sqr(x)

There are five basic single precision mathematical functions (SQRT, LOG, EXP, SIN, COS) that can be used with Single data type variables. LOG will compute the natural logarithm of a real number. All math functions can also be used in complex math expressions. For example:
   Dim x As Single
   x = 2
   x = Sqrt(x)

Also, there are three more advanced single precision mathematical functions (ARCSIN, ARCTAN, POWER). Arcsin and Arctan functions expect one input single data type expression argument whose absolute value is less than or equal 1. They are calculated by using their infinite series representation. Normally first 4 terms of the series will be taken into account. If ARCUS_PRECISION parameter is set to 2 by the #define directive, 7 terms of the series will be calculated leading to the greater precision of the result. That will also take some more program memory.
Power function performs the exponentiation of a single data type expression that is the first argument of the function (the base). The second argument (the exponent) is expected to be a positive integer or a single data type numeric expression. Exponentiation operation with the single data type exponent will take more program memory.
Here is one example:
   #define ARCUS_PRECISION = 2
   Dim s_var As Single
   s_var = 0.4
   s_var = Arctan(s_var)
   s_var = Power(3.333, 5)

For Bit data type variables four logical operations are available. It is possible to make only one logical operation in one single statement. Logical operations are also available for other variable types. For example:
Example 1:
   Dim i As Bit
   Dim j As Bit
   Dim x As Bit
   x = Not i
   x = i And j
   x = i Or j
   x = i Xor j

Example 2:
   Dim x As Word
   Dim y As Word
   x = x Or y
   PORTB = PORTD And %11110000

SHIFTLEFT and SHIFTRIGHT functions can be used to shift bit-level representation of a variable left and right. The first argument is input variable and the second argument is number of shifts to be performed. Here are two examples:
Example 1:
   DDRB = 0xff
   PORTB = %00000011

   goleft:
      WaitMs 250
      PORTB = ShiftLeft(PORTB, 1)
      If PORTB = %11000000 Then Goto goright
   Goto goleft

   goright:
      WaitMs 250
      PORTB = ShiftRight(PORTB, 1)
      If PORTB = %00000011 Then Goto goleft
   Goto goright

Example 2:
   DDRB = 0xff
   PORTB = %00000001

   goleft:
      WaitMs 250
      PORTB = ShiftLeft(PORTB, 1)
      If PORTB.7 Then Goto goright
   Goto goleft

   goright:
      WaitMs 250
      PORTB = ShiftRight(PORTB, 1)
      If PORTB.0 Then Goto goleft
   Goto goright

There are three statements that are used for bit manipulation - HIGH, LOW and TOGGLE. If the argument of these statements is a bit in one of the PORT registers, then the same bit in the corresponding DDR register is automatically set, configuring the affected pin as an output pin. Some examples:
   High PORTB.0
   Low ADCSRA.ADEN
   Toggle PB1

• Standard Basic language elements

Unconditional jumps are performed by GOTO statement. It uses line label name as argument. Line labels can be global or local. Line labels must be followed by colon mark ':'. Here is one example:
   Dim x As Word
   x = 0
   loop: x = x + 1
   Goto loop

Four standard BASIC structures are supported: FOR-TO-STEP-NEXT, WHILE-WEND, IF-THEN-ELSE-ENDIF and SELECT CASE-CASE-ENDSELECT. Here are several examples:
Example 1:
   Dim x As Byte
   DDRB = 255
   x = 255
   While x > 0
      PORTB = x
      x = x - 1
      WaitMs 100
   Wend
   PORTB = x

Example 2:
   DDRB.0 = 1
   loop:
      If PINA.0 Then
         PORTB.0 = 1
      Else
         PORTB.0 = 0
      Endif
   Goto loop

Example 3:
   Dim x As Word
   DDRB = 255
   For x = 0 To 10000 Step 10
      PORTB = x.LB
   Next x

Example 4:
   Dim i As Byte
   Dim j As Byte
   Dim x As Byte
   j = 255
   x = 2
   DDRB = 255
   For i = j To 0 Step -x
      PORTB = i
   Next i

Example 5:
   Dim x As Byte
   loop:
      Select Case x
      Case 255
         x = 1
      Case <= 127
         x = x + 1
      Case Else
         x = 255
      EndSelect
   Goto loop

For statement will accept all numeric data types for the running variable. Exit For statement provides a way to exit a For-Next loop. It transfers control to the statement following the Next statement.

After IF-THEN statement in the same line can be placed almost every other possible statement and then ENDIF is not used. There are no limits for the number of nested statements of any kind. Six standard comparison operators are available: =, <>, >, >=, <, <=. Multiple comma-separated conditions can be used with CASE statements, also.
Also, the compiler is able to evaluate complex expressions (both math/arithmetics and string expressions) on both sides of the comparison operator in While and If-Then statements.

If there is a need to insert an infinite loop in basic program, that can be done with HALT statement.

• Complex expressions evaluation

Complex expressions evaluation engine can evaluate all possible and meaningful expressions containing any number of supported operators, operands, functions and parentheses.

The expression evaluation engine will accept both Basic and C-inspired syntax for the operators.
Here is the list of all supported operators with their alternative forms:
- Arithmetic operators: +, -, *, /, % (Mod), ++ (post or pre-increment), -- (post or pre-decrement), unary +, unary -
- Assignment operators: =, +=, -=, *=, /=
- Comparison operators: == (=), != (<>), <, <=, >, >=
- Logical operators: unary ! (Not), && (And), || (Or)
- Bitwise operators: unary ~ (Not), & (And), | (Or), ^ (Xor), << (ShiftLeft), >> (ShiftRight)

The dot operator (.) is available for addressing individual bits, bytes and words in variables. There is also # unary operator (ascii print) used to get the decimal string representation of the operand.

On low level, implemented expression evaluation engine is C-language based, following C standards for operator precedence.
In assignment statements, the engine will initially consider Not, And and Or as bitwise operators.
In condition expressions of While and If-Then statements, Not, And and Or will be initially considered as logical operators.
This information may only be important from an operator precedence perspective. Actually, the expression evaluation engine will not generate errors related to the distinction between logical and bitwise operators. The operation performed is determined by the type of the operands.

Complex expressions are generally accepted everywhere. Complex expressions can be used as direct arguments when calling procedures and functions in the code, and in all statements where comma-separated list of arguments is accepted, like Lcdout, Serout, I2CWrite, GLcdwrite, etc., including the statements from the libraries like UART_Write.
While statement will accept all possible complex expressions that evaluate to Bit data type. The same is valid for If-Then statement. Case statement can also contain complex expressions.
For statement will accept all possible complex expressions for the initial expression, for the To argument, and for the optional Step argument as well.

Illustrative lines of code:
   Lcdout #func1(i * 2 + 1), 32, #a1++, 32, #--a2
   Case 2, 3, >= 10 + j
   If l1 = CLong w1 * 1000 Then i = 0
   For i = j + 1 To k * 2 + 1 Step 2

The compiler will accept lines containing the expression without the assignment operator. The expression will just be evaluated if possible, and if not, the Syntax error message will be displayed. So, it is possible to have lines of code like these ones:
   i++
   #(j + k)
   x > y

Along with the standard basic For-Next loops, it is possible to use the C-like CFor-CNext loops. Init expression, condition expression and loop expression should be separated by the semicolon ';' symbol. In addition, the init and loop expressions can contain multiple statements separated by commas.
For example:
   Dim i As Byte
   Dim j As Byte
   Dim k As Byte
   k = 0
   CFor (i = 1; i < 7; i++)
      k++
   CNext
   k = 0
   CFor (i = 5, j = 10; i + j < 20; i++, j++)
      k++
   CNext

'#' (ascii print) symbol is now treated as a unary operator returning string data type, so it can be used in complex expressions like these ones:
   s1 = #(a1 + a2 * a3)
   s1 = "abcd" + #a2 + s2 + s3

Unary operators are treated as functions. The opposite is also true. So, the evaluation engine will accept the following formats, also:
   y = Sin x
   x = j Power k

Complex expressions evaluation engine accepts all meaningful combinations of dot operator extensions (like .LB) and bit indexes with dot operator. The dot operator for extensions and bit indexes can be used with array members and all other applicable expressions. It is treated as a binary operator with the highest binary operator precedence.
For example:
   Dim i As Byte
   Dim xlng(8) As Long
   For i = 1 To 8
      xlng(i - 1).LW.LB.i = 1
      xlng(i - 1).LW.HB.i = 1
      xlng(i - 1).HW.LB.i = 1
      xlng(i - 1).HW.HB.i = 1
   Next i

If needed, temporary system variables will be declared and used during the expression evaluation. Optimal management of temporary variables has been achieved by the evaluation engine.

Data type promotion

Numeric data types are ordered from the lowest to the highest in the following sequence: Byte, Word, Long, Single. Particular arithmetic or logical operation is performed on the higher data type level of the operands (variables and constants).
In case when only arithmetic operators remain until the end of the evaluation, the evaluation engine will promote the data type of temporary variables to the result variable data type. So, one can freely write:
   Dim w1 As Word
   Dim l1 As Long
   Dim s1 As Single
'w1 / 1000 will be calculated on the single data type level; desired precision will be achieved
   s1 += w1 / 1000 + 3.14
   s1 = w1 / 1000 + 3.14
'w1 * 1000 will be calculated on the long data type level; desired precision will be achieved
   l1 = w1 * 1000 + 500

However, that will not happen in case of comparison operators, like in the example:
   Dim w1 As Word
   Dim l1 As Long
   Dim s1 As Single
'left-hand side will be evaluated to single; right-hand side to word; desired precision will be probably lost
   While s1 - 1 = w1 / 1000
   Wend
'left-hand side will be evaluated to long; right-hand side to word; desired precision will be probably lost
   While l1 - 1 = w1 * 1000
   Wend

For similar situations, one can make use of the data type conversion functions (unary operators): CByte, CWord, CLong, CShort, CInteger, CLongInt, CSingle.
This is the updated example:
   While s1 + 1 = CSingle w1 / 1000
   Wend
   While l1 + 1 = CLong w1 * 1000
   Wend

• Subroutines

Structured basic programs can be written with subroutines. When using subroutines, the main routine must be ended with END statement, and subroutines must be placed after END statement in program. END statement is compiled as an infinite loop. Subroutines should be declared with SUB statement followed by the subroutine name. All variables declared in a subroutine have local scope, so they don't need to have unique names. Subroutines must be ended with END SUB statement. They can be conditionally exited with EXIT SUB statement. Calls to subroutines are implemented with GOSUB statement.
For backward compatibility, global scope line labels will still be recognized as subroutine declarations, GOSUB statements will also accept the line label names as arguments and RETURN statement can be used for the final return from a subroutine. Users need to take care that the program structure is consistent when using local scope line label as GOSUB argument (the compiler will accept that, however an applicable warning will be generated).
Here are two examples:
Example 1:
   Dim x1 As Byte
   Dim x2 As Byte
   Dim x3 As Byte
   For x1 = 0 To 10
      Gosub calculate_x2x3
   Next x1
   End

   Sub calculate_x2x3
   x2 = 100 + x1
   If x1 > 5 Then Exit Sub
   x3 = x2
   End Sub

Example 2:
   Symbol adc_start = ADCSRA.ADSC
   Symbol display = PORTB
   DDRB = %11111111
   ADMUX = 0x01  'Select ADC1 input channel
   ADCSRB = 0x10  'Left adjust ADC result
   ADCSRA.ADEN = 1
   main:
      Gosub getadcresult
      display = ADCH
   Goto main
   End

   getadcresult:
   High adc_start
   While adc_start
   Wend
   Return

• Microcontroller related language elements

Microcontroller ports and pins can be configured as inputs or outputs by assigning proper values to DDRx registers or their bits. That task can also be accomplished by a CONFIGPIN statement. Its syntax is apparent from the following examples:
   ConfigPin PORTB = Output
   ConfigPin PA0 = Output
   ConfigPin PORTC.3 = Input
   ConfigPin PD = Input

The clock frequency of the target device can be specified by setting the CLOCK_FREQUENCY parameter (the value is expressed in MHz) with the #define directive (alias: Define). This parameter should be setup at the beginning of the basic program. For example:
   #define CLOCK_FREQUENCY = 20

The full list of all available parameters for the #define directive, along with their default values and allowed ranges of values, can be found in the last topic of this document.

• Special Basic language elements

WAITMS and WAITUS statements can be used to force program to wait for the specified number of milliseconds or microseconds. It is also possible to use variable argument of Byte or Word data type. These routines use Clock Frequency parameter that can be changed from the Options menu. WAITUS routine has minimal delay and step that also depend on the Clock Frequency parameter.
   Dim x As Word
   x = 100
   WaitMs x
   WaitUs 50

Important Note: When writing programs for real AVR devices you will most likely use delay intervals that are comparable to 1 second or 1000 milliseconds. Many examples in this help file also use such 'real-time' intervals. But, if you want to simulate those programs you have to be very patient to see something to happen, even on very powerful PCs available today. For simulation of 'WaitMs 1000' statement on 4MHz you have to wait the simulator to simulate 4000000 instructions and it will take considerable amount of time even if 'extremely fast' simulation rate is selected. So, just for the purpose of simulation you should recompile your programs with adjusted delay intervals, that should not exceed 1-10ms. But, be sure to recompile your program with original delays before you download it to a real device. There is an easy way to change arguments of all WAITMS statements in a large basic program with a value in the range 0-10 for simulation purposes. With one line of code setting parameter SIMULATION_WAITMS_VALUE with #define directive, the arguments of all WAITMS statements in the program will be ignored and the specified value will be used instead during compiling. Omitting that line (say, with the comment sign) will cancel its effect and the compiled code will be ready again for the real hardware.

It is possible to insert breakpoints for the simulator directly in basic programs using BREAK statement. It is compiled as BREAK assembler instruction and the simulator will interpret this opcode as a breakpoint and switch the simulation rate to Step By Step.

LOOKUP function can be used to select one from the list of Byte constants, based on the value in the index variable, that is supplied as the last separated argument of the function. The first constant in the list has index value 0. The selected Byte constant will be loaded into the result variable of the function. If the value in the index variable goes beyond the number of constants in the list, the result variable will not be affected by the function. Here is one small example for a 7-segment LED display:
   Dim digit As Byte
   Dim mask As Byte
   DDRB = %11111111
   loop:
      For digit = 0 To 9
         mask = LookUp(0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f), digit
         PORTB = mask
         WaitMs 1000
      Next digit
   Goto loop

If all constants in the list (or part of them) are ASCII values, then shorter form of the list can be created by using string arguments. For example:
   mask = LookUp("ABCDEFGHIJK"), index

FREQOUT statement can be used to generate a train of pulses (sound tone) on the specified pin with constant frequency and specified duration. It has three arguments. The first argument is the pin that the tone will be generated on. The statement will setup the pin as an output pin. The second argument specify the tone frequency and it must be a constant in the range 1-10000Hz. The third argument defines the tone duration and it also must be a numeric constant in the range 1-10000ms. Choosing higher tone frequencies with low microcontroller clock frequency used may result in somewhat inaccurate frequency of the generated tones. FREQOUT statement can be alternatively used in 'variable mode' with Word data type variables instead of constants for the last two arguments. In this mode of usage the second argument is supposed to hold the half-period of the tone (in microseconds) and the third argument must hold the total number of pulses that will be generated. The following code will generate one second long tone on PB0 pin with 600Hz frequency:
   FreqOut PORTB.0, 600, 1000

• Structured language support (procedures and functions)

Procedures can be declared with PROC statement. They can contain up to 5 arguments (comma-separated list) and all available data types can be used for argument variables. Argument variables are declared locally, so they do not need to have unique names in relation to the rest of user basic program, that makes very easy to re-use once written procedures in other basic programs. Procedures can be conditionally exited with EXIT statement. They must be ended with END PROC statement and must be placed after END statement in program. Calls to procedures are implemented with CALL statement. The passed arguments can be variables, numeric constants or complex numeric expressions. For example:
   Dim x As Byte
   DDRB = 255
   For x = 0 To 255
      Call portb_display(x)
      WaitMs 100
   Next x
   End

   Proc portb_display(arg1 As Byte)
   PORTB = arg1
   End Proc

All facts stated for procedures are valid for functions, also. Functions can be declared with FUNCTION statement. They can contain up to 5 arguments and argument variables are declared locally. Functions can be exited with EXIT statement and must be ended with END FUNCTION. The value returned by a function should be assigned to the function name in the function code, or provided as argument of ReturnValue statement. The name of the function is declared as a global variable, so if the function is called with CALL statement, after its execution the function variable will contain the result. Standard way of function calls in assignment statements can be used, also. One simple example:
   Dim x As Byte
   Dim y As Word
   For x = 0 To 255
      y = square(x) + 1
   Next x
   End

   Function square(arg1 As Word) As Word
   square = arg1 * arg1
   'or ReturnValue arg1 * arg1
   End Function

The default mechanism is to pass an argument to a procedure by value. Either no prefix or ByVal prefix can be used for that argument. The procedure will use the argument value and will not change the input value of the calling variable. There are two passing mechanisms for passing arguments to procedures by reference - ByRef and ByRefOut. When ByRef prefix is used to pass a variable argument to a procedure, the procedure will use the input variable value, but it can also change the value of the variable during the procedure execution. The calling variable will be exposed to change. When ByRefOut prefix is used for the procedure argument, the input value of the calling variable will not be passed to the procedure at all. The procedure will only return an output value to the calling code through that argument. So, ByRefOut mechanism should be used when the procedure has a genuine need to output only a value to the calling code, and in that way the result will be a more optimized code compared to the use of the standard ByRef mechanism. ByRef and ByRefOut can be used for function arguments as well.
One test example:
   Dim in_only As Byte
   Dim inc_me As Byte
   Dim add_inc_me_and_in_only As Byte

   in_only = 5
   inc_me = 10
   Call testbyref(in_only, inc_me, add_inc_me_and_in_only)
   'after this call
   'inc_me = 11
   'add_inc_me_and_in_only = 16
   End

   Proc testbyref(arg1 As Byte, ByRef arg2 As Byte, ByRefOut arg3 As Byte)
      arg2 = arg2 + 1
      arg3 = arg1 + arg2
   End Proc

Procedures (and functions) can also be called without the Call statement. In that case, the procedure name should be followed by the comma-separated list of arguments.
The lines of code with the same effect:
   Call portb_display(x)
   portb_display x

Basic source code from an external file can be included to the current program by using INCLUDE directive. Its only argument is a string containing the path to the external .BAS file. This can be the full path or only the file name, if the external file is located in the same folder as the current basic program file. During the compilation process the external basic source will be appended to the current program. Multiple files can be included with separate INCLUDE directives. To maintain the overall basic code structure, it is strongly suggested that the external file contains global declarations, subroutines, procedures and functions, only. Here is one very simple example for the demonstration:
main.bas:
   Dim i As Word
   Dim j As Word

   Include "inc1.bas"
   Include "inc2.bas"

   For i = 1 To 10
      j = func1(i, 100)
      Call proc1(j)
   Next i
   End

inc1.bas:
   Dim total As Word

   Proc proc1(i As Word)
   total = total + i
   End Proc

inc2.bas:
   Function func1(i As Word, j As Word) As Word
   func1 = i + j
   End Function

• Using internal EEPROM data memory

Access to internal EEPROM data memory can be programmed using EEPROM_Read and EEPROM_Write statements (aliases: Read, Write). These statements are implemented in OshonSoft basic library files (see Library support section). The first argument is the address of a byte in EEPROM memory and can be an arbitrary expression. The second argument is byte type data that is read or written (for EEPROM_Read statement it must be a variable argument; for EEPROM_Write statement an arbitrary expression can be used). It is suggested to keep interrupts disabled during the execution of EEPROM_Write statement.
   Dim x As Byte
   Dim y As Byte
   x = 10
   EEPROM_Read x, y
   EEPROM_Write 11, y

• Using internal A/D converter module

ADC_Read statement (alias: Adcin) is available as a support for internal A/D converter. This statement is implemented in OshonSoft basic library files (see Library support section). It has two arguments. The first argument is ADC channel number that can be an arbitrary expression and the second argument is a variable that will be used to store the result of A/D conversion. It is not only possible to convert single-ended inputs with ADC_Read statement, but also differential inputs with various gain selections. For that purpose the appropriate value for MUX bits (ADMUX register) should be used as ADC channel number argument of ADC_Read statement. ADC_Read statement uses values of three parameters ADC_Clk, ADC_Ref and ADC_Sample_uS (aliases: ADC_CLOCK, ADC_VREF, ADC_SAMPLEUS). Their default values and allowed ranges are defined in ADC Module library and this information is shown in Libraries info panel. The default parameter value can be changed using #define directive. ADC_Clk parameter determines the choice for ADC input clock prescaler selection. ADC_Ref parameter defines the ADC voltage reference selection. Its value is used to setup REFS bits in ADMUX register. ADC_Sample_uS parameter sets the desired ADC acquisition time in microseconds. ADC_Read result must be word type variable (refer to Lib info panel). Here is one example:
   Dim v(5) As Byte
   Dim vm As Word
   Dim i As Byte

   #define ADC_Clk = 3
   #define ADC_Ref = 0
   #define ADC_Sample_uS = 50
   DDRA = 0x00
   DDRB = 0xff

   For i = 0 To 4
      ADC_Read 0, v(i)
   Next i

   vm = 0
   For i = 0 To 4
      vm = vm + v(i)
   Next i
   vm = vm / 5

   PORTB = vm.LB

• Using interrupts

Interrupt routines should be placed as all other subroutines after the END statement. A separate routine should be implemented for every interrupt source used. The list of available interrupt sources for the selected AVR device with their official names can be viewed by selecting Show System Interrupt Names command from the Options menu of the basic compiler window. Interrupt routine should begin with ON INTERRUPT keyword followed by the official name of its interrupt source and it should end with RESUME statement. ENABLE and DISABLE statements followed by an interrupt source name can be used in the program to control the status of its enable flag. If used with no arguments they will control the status of the global interrupt enable flag (I-flag) in the SREG register. RESUME statement will automatically set the I-flag and enable new interrupts. It is always a good programming practice to keep interrupt routines as small as possible. One example:
   Dim x As Byte
   x = 255
   DDRB = 0xff
   PORTB = x
   DDRD.2 = 0  'configure INT0 pin as an input pin
   MCUCR.ISC00 = 1
   MCUCR.ISC01 = 1  'INT0 rising edge generates an interrupt
   Enable INT0
   Enable
   End

   On Interrupt INT0
      x = x - 1
      PORTB = x
   Resume

• String data type related basic elements

All declared string variables will reserve fixed number of bytes in microcontroller memory. That number is set by the #define directive and STRING_MAX_LENGTH parameter. The allowed range for the parameter is 8-100; the default value is 16. The string variables will take one extra memory byte containing zero value to define the end of string (string termination byte).
   #define STRING_MAX_LENGTH = 8

It is also possible to specify the length of each declared string variable by adding [length] suffix to the variable name:
   Dim string_var[3] As String
   string_var = "ABC"
This feature can be used in cases when available memory is critical, and when it is certain that a string variable will not hold string longer than the specified length during the program execution.

Special string pointer (StrPtr) data type is available for advanced strings related programming. String pointers are Word type variables holding the address of a string variable. String pointer value can be assigned and dynamically changed with SetStrPtr statement. String pointers can be used everythere along with String variables and constants. ByRef String arguments of procedures and functions are implemented using string pointers
minimizing the required SRAM memory. One example:
   Dim s1 As String
   Dim p1 As StrPtr
   s1 = "aa"
   SetStrPtr p1 = s1
   p1 = p1 + "bb"

String constants should begin and end with the double quotation marks. There are four symbolic string constants available: Qt (or """") for the double quotation mark (ASCII code 34), CrLf for the carriage return - line feed sequence (ASCII codes 13-10), Cr for the carriage return (ASCII code 13) and Lf for the line feed character (ASCII code 10). User-defined names for the string constants with the Const directive can also be used.
   Dim string_var As String
   string_var = Qt + "abcde" + """" + CrLf

Decimal string representation of any numeric data type variable (Byte, Word, Long and Single) is assigned to a string variable using the # prefix. For Single data type variables, SINGLE_DECIMAL_PLACES parameter value is used in this operation. This parameter is used to define the number of decimal places taken after the decimal point in decimal representation of Single data type variables. Valid parameter values are in the range 1-6. The default value is 3.
For example:
   #define SINGLE_DECIMAL_PLACES = 2
   Dim string_var As String
   Dim string_var2 As String
   Dim pi As Single
   Dim i As Word
   i = 12345
   string_var = "Number: " + #i
   pi = 3.14159
   string_var2 = #pi

The following note is applicable to Lcdout, GLcdwrite, UART_Write, Serout and SeroutInv statements. If '#' sign is used before the name of a variable that is an argument of those statements (valid for Byte, Word, Long and Single data type variables), then its decimal string representation is sent to the device (ASCII characters), without the need of using any string variables.

The Len() function returns the numeric value representing the number of characters in a string variable or constant, that is the only argument of the function, and stores it in a Byte or Word variable:
   Dim i As Word
   i = Len("abcde")
Variable i will contain the number 5.

The Asc() function returns the numeric value representing the ASCII character code corresponding to the first letter in a string variable or constant, that is the only argument of the function, and stores it in a Byte or Word variable:
   Dim i As Word
   i = Asc("ABC")
Variable i will contain the number 65.

The Chr() function returns an one-char long string containing the character that is associated with the ASCII character code provided as the only argument of the function.
For example:
   Dim string_var As String
   Dim i As Word
   i = Asc("A")
   string_var = Chr(i + 1)
Variable string_var will contain the string "B".

The LeftStr() function returns a string containing the specified number of characters from the left side of an input string. The first argument of the function is an input string expression from which the leftmost characters are returned. The second argument is a numeric expression indicating how many characters to return. If 0, a zero-length string ("") is returned. If greater than or equal to the number of characters in the input string, the entire string is returned.
The RightStr() function returns a string containing the specified number of characters from the right side of an input string. The first argument of the function is an input string expression from which the rightmost characters are returned. The second argument is a numeric expression indicating how many characters to return. If 0, a zero-length string ("") is returned. If greater than or equal to the number of characters in the input string, the entire string is returned.
The MidStr() function returns a string containing the specified number of characters from an input string. The first argument of the function is an input string expression from which the characters are returned. The second argument is a numeric expression specifying character position in the input string at which the part to be taken begins (the first character of the string is at position 1). The third argument is a numeric expression indicating the number of characters to return. The returned part of the string will not go beyond the end of the input string.
For example:
   Dim string_var As String
   Dim string_var1 As String
   Dim string_var2 As String
   Dim string_var3 As String
   string_var = "123456789"
   string_var1 = LeftStr(string_var, 2)
   string_var2 = MidStr(string_var, 3, 3)
   string_var3 = RightStr(string_var, 4)
Variable string_var1 will contain the string "12".
Variable string_var2 will contain the string "345".
Variable string_var3 will contain the string "6789".

The LTrim() function returns a string containing a copy of an input string argument without leading spaces.
The RTrim() function returns a string containing a copy of an input string argument without trailing spaces.
   Dim string_var As String
   string_var = "   abc   "
   string_var = RTrim(LTrim(string_var))
Variable string_var will contain the string "abc".

The LTrimChr() function strips characters from the beginning of an input string (the first function argument).
The RTrimChr() function strips characters from the end of an input string (the first function argument).
The character to be stripped is specified by the ASCII character code as the second argument of the function.
   string_var = RTrimChr("aaaAAAaaa", "a")

The FillStr() function returns a string containing a repeating character string of the length specified. The first argument of the function is the ASCII character code used to build the return string. The second argument is a numeric expression specifying the length of the returned string.
For example:
   Dim string_var As String
   Dim i As Byte
   i = 3
   string_var = FillStr("a", i) + FillStr(65, i)
Variable string_var will contain the string "aaaAAA".

The InStr() function returns a number (stores it in a Byte or Word variable) specifying the position of the first occurrence of the ASCII character code (the second argument of the function) within an input string expression provided as the first argument of the function. The first character of the string is at position 1. If the ASCII character code has not been found in the input string, the function will return 0.
   Dim string_var As String
   Dim i As Word
   string_var = "123456ABCD"
   i = InStr(string_var, Asc("A"))
Variable i will contain the number 7.

The InStrRev() function returns a number specifying the position of the last occurrence of a character (specified by the ASCII character code as the second argument of the function) within an input string expression (the first argument of the function).
   byte_var = InStrRev("cccbbbaaa", "c")
Variable byte_var will contain the number 3.

The ReverseStr() function returns the reversed string from an input string expression (the only argument of the function).
   string_var = ReverseStr("12345")
Variable string_var will contain the string "54321".

The CountChr() function returns a number specifying the number of occurrences of a character (specified by the ASCII character code as the second argument of the function) within an input string expression (the first argument of the function).
For example:
   string_var = "ababababa"
   byte_var = CountChr(string_var, "a")
Variable byte_var will contain the number 5.

The ReplaceChr() function returns a string in which the specified character has been replaced with another character. The first argument is the string expression containing the character to replace, the second argument is the character being searched for, and the third argument is the replacement character.
   string_var = ReplaceChr("222111222", "1", "3")

The LCase() function returns an input string argument converted to lowercase. Only uppercase letters are converted to lowercase; all lowercase letters and non-letter characters remain unchanged.
The UCase() function returns an input string argument converted to uppercase. Only lowercase letters are converted to uppercase; all uppercase letters and non-letter characters remain unchanged.
For example:
   string_var = "aba123aza"
   string_var = UCase(string_var)
   string_var = LCase("ABa123aZA")

The LShiftStr() and RShiftStr() functions can be used to shift the characters in the input string expression (the first argument) to the left or to the right. The second argument of the function is the character (specified by the ASCII character code) that will be put on the last character position for the LShiftStr() function, or on the first character position for the RShiftStr() function, so the length of the input string will not be changed.

The LRotateStr() and RRotateStr() functions can be used to rotate the characters in the input string expression (that is the only function argument) to the left or to the right.
Here is one example:
   string_var = "123456789"
   string_var = LShiftStr(string_var, "a")
   string_var = LShiftStr(string_var, "b")
   string_var = RRotateStr(string_var)
Variable string_var will contain the string "b3456789a".

The HexStr() function returns a 4-chars long string representing the hexadecimal value of a number provided as the only argument of the function. The argument is expected to be in the Word data type range (2-byte numbers, 0-65535, 0000-FFFF).
   word_var = 43811
   string_var = HexStr(word_var)
Variable string_var will contain the string "AB23".

The DecFromHex() function returns a Word variable representing the decimal value of a number provided in the form of a hexadecimal string as the only argument of the function.
   word_var = DecFromHex("FFFF")
Variable word_var will contain the number 65535.

There are four functions available that can be used to convert a string variable into a numeric data type variable, one for each numeric variable data type.
All four functions expect one input string argument containing decimal string representation of a numeric variable.
StrValB() function returns a Byte data type variable, StrValW() returns a Word variable, StrValL() returns Long, and StrValS() returns a Single data type variable.
Here is one example:
   #define STRING_MAX_LENGTH = 10
   Dim string_var As String
   Dim b_var As Byte
   Dim w_var As Word
   Dim l_var As Long
   Dim s_var As Single
   b_var = StrValB("13")
   string_var = "54321"
   w_var = StrValW(string_var)
   l_var = StrValL("1234567890")
   string_var = "-123.56"
   s_var = StrValS(string_var)

The compiler is able to compile all possible complex expressions containing string constants, variables and functions.
For example:
   Dim string_var As String
   Dim string_var2 As String
   string_var = "123456789"
   string_var2 = LeftStr(string_var, Len(string_var) - 6) + MidStr(string_var, 4, 6) + Chr(Asc(RightStr(string_var, 1)) - 9)
After the execution of this example, string_var2 variable will contain the string "1234567890".

Two standard comparison operators are available for string data type in conditional statements: =, <>.
   Dim string_var As String
   Dim i As Word
   i = 1
   string_var = "abcd"
   If LeftStr("abcde", 4) <> string_var Then i = 2 Else i = 3
Variable i will contain the number 3 after the execution of the example.

The compiler is also able to evaluate complex string expressions in conditional statements.
One example (ASCII code for digit 1 is 49):
   Dim st_var As String
   Dim i As Word
   i = 1
   st_var = "123456"
   If LeftStr(st_var, 1) + MidStr(st_var, 2, 2) + RightStr(st_var, 3) = Chr(48 + Len("a")) + "23456" Then i = 2

For experienced users, it is worth noting that a string variable can actually be treated as a byte array, if needed.
For example:
   string_var = "1234567"
   string_var(3) = "a"
After the execution of this example, string_var variable will contain the string "123a567".

• Modbus firmware implementation

Modbus master/slave firmware can be implemented in the basic compiler. This is an advanced feature, requiring somewhat advanced experience in using the compiler, however it is designed to be as simple as possible.
The Modbus solution consists of the Modbus basic compiler elements and the Modbus Simulation Device tool available for the Simulator.

Modbus master implementation
The Modbus protocol is a master/slave protocol. The master sends a request in the form of series of bytes that end with the two-byte Modbus CRC-16 checksum. The first byte in the request is the slave address, in the range 1-247. The second byte is the function code. In this software release two function codes are supported, 0x03 that reads the values in the so-called slave 16-bit holding registers, and 0x10 function code used to write the values to those slave holding registers. Standard holding register addresses are in the range 40001-49999. However, standard practice is that they are addressed with 40001 offset, meaning that 40001 register has 0 address in the Modbus request. After the addressed slave has received the request, it should confirm its consistence by calculating the checksum, and then send the formated response back to the master, that also ends with the two-byte Modbus CRC-16 checksum corresponding to the bytes contained in the slave response.

The Modbus firmware is started by the ModbusInit statement that should be placed at the beginning of the basic program. It will reserve the necessary number of system memory locations, including the locations for the request and response Modbus buffers. The number of reserved locations is determined by the value of the MODBUS_REG_NUM parameter that specifies the maximal number of holding registers that will be read/written in one Modbus request. The parameter value is set by the #define directive. The allowed range for the parameter is 1-16; the default value is 1.

The ModbusInit statement requires that the system 'modbus_init' subroutine is present in the program, where the code for initializing the master/slave communication should be placed.

ModbusWrite and ModbusRead statements are used to initiate Modbus protocol communication to write/read the values in the slave holding registers. The first argument of both statements is the target slave address and the second argument is the address of the first holding register location that will be accessed. For the ModbusWrite statement then follows the list up to MODBUS_REG_NUM number of Word data type constants and variables, the values that will be written to the consecutive slave 16-bit holding register locations.
For the ModbusRead statement then follows the list up to MODBUS_REG_NUM number of the Word data type variables, where the values that will be read from the consecutive slave holding register locations will be stored.

ModbusService statement is the most important statement related to Modbus firmware implementation. It requires that the system 'modbus_service' subroutine is present in the program, that will be called on every ModbusService statement execution. The statement should be executed as often as possible in the basic program, because the system 'modbus_service' subroutine is the 'heart' of the Modbus firmware implementation.
That is the place where the appropriate action should be performed, based on the value of the Modbus system variable 'mb_status'. There are the following system names for the 'mb_status' bits available: 'mb_request' (bit 0) - specifying that sending the Modbus request is in progress, 'mb_response' (bit 1) - specifying that receiving the Modbus response is in progress, 'mb_completed' (bit 2) - set when all the bytes from the Modbus response has been received, 'mb_simgotbyte' (bit 3) - set by MODBUS_SIM_GET_BYTE statement when Modbus Simulation Device tool updates the 'mb_byte' system variable (this bit should be cleared in the firmware), and 'mb_crcerror' (bit 7) - set when the received response has incorrect checksum.

ModbusGetNextByte and ModbusPutNextByte statements are supposed to be used in the system 'modbus_service' subroutine. ModbusGetNextByte statement will put the next byte value from the Modbus request buffer to the 'mb_byte' system variable, the value that should be sent to the slave.
ModbusPutNextByte statement will put the next received byte from the slave, expected to be in the 'mb_byte' system variable, to the Modbus response buffer.

There are two special statements MODBUS_SIM_SEND_BYTE and MODBUS_SIM_GET_BYTE that serves exclusively for the communication of the Modbus firmware and the Modbus Simulation Device simulation tool. Both of them are compiled as two simple NOP instructions, but the simulation tool will recognize them as the moments when it should interfere with the values of two basic Modbus system variables - 'mb_status' and 'mb_byte', and in that way interfere with the firmware, making the automatic simulation possible. To test the following example the Slave Address in the simulation tool should be set to 5, and the Starting Address for the displayed holding registers should be set to 40100. It is also suggested to make the Watch Variables window opened during the simulation.

Here is one of the simplest examples for the Modbus simulation:
   #define MODBUS_REG_NUM = 2
   Dim phase As Byte
   Dim var1 As Word
   Dim var2 As Word
   ModbusInit
   phase = 0
   loop1:
      If mb_status = 0 And phase = 0 Then
         ModbusWrite 5, 101, 0x1022, 0x1033
      Endif
      If mb_status = 0 And phase = 1 Then
         ModbusRead 5, 101, var1, var2
      Endif
      ModbusService
   Goto loop1
   End

   modbus_init:
   mb_status = 0
   Return

   modbus_service:
   If mb_status.mb_request = True Then
      ModbusGetNextByte
      'mb_byte system variable is ready to be sent to the slave device
      MODBUS_SIM_SEND_BYTE
   Endif
   If mb_status.mb_response = True Then
      MODBUS_SIM_GET_BYTE
      If mb_status.mb_simgotbyte = 1 Then
         mb_status.mb_simgotbyte = 0
         'mb_byte system variable has the byte received from the slave device
         ModbusPutNextByte
      Endif
   Endif
   If mb_status.mb_completed = True Then
      If mb_status.mb_crcerror = False Then
         mb_status = 0
         phase = phase + 1
      Endif
   Endif
   Return

And here is one of the simplest examples for the real ATmega32 chip communicating with the slave device using the the hardware UART module:
   #define CLOCK_FREQUENCY = 8
   #define MODBUS_REG_NUM = 2
   Dim phase As Byte
   Dim var1 As Word
   Dim var2 As Word
   ModbusInit
   phase = 0
   loop1:
      If mb_status = 0 And phase = 0 Then
         ModbusWrite 0x05, 101, 0x1022, 0x1033
      Endif
      If mb_status = 0 And phase = 1 Then
         ModbusRead 0x05, 101, var1, var2
      Endif
      ModbusService
   Goto loop1
   End

   modbus_init:
   'initializes the hardware UART
   UART_Init 9600
   Return

   modbus_service:
   If mb_status.mb_request = True Then
      If UCSRA.UDRE = 1 Then
         ModbusGetNextByte
         'mb_byte system variable is ready to be sent to the slave device
         UDR = mb_byte
      Endif
   Endif
   If mb_status.mb_response = True Then
      If UCSRA.RXC = 1 Then
         mb_byte = UDR
         'mb_byte system variable has the byte received from the slave device
         ModbusPutNextByte
      Endif
   Endif
   If mb_status.mb_completed = True Then
      If mb_status.mb_crcerror = False Then
         mb_status = 0
         phase = phase + 1
      Endif
   Endif
   Return

Modbus experienced users info
Here is the information that might be useful for more experienced users, because it reveals some of the low-level Modbus implementation elements.

There is a number of system variables that are set after every ModbusWrite and ModbusRead statements. 'mb_buffer' byte array refer to Modbus request buffer, 'mb_slaveaddress' and 'mb_function' byte variables refer to the 'mb_buffer(0)' and 'mb_buffer(1)' request buffer terms. 'mb_bufflen' system variable is modified by every call to ModbusGetNextByte statement; it contains the number of bytes in the request buffer waiting to be sent. 'mb_buffer2' byte array refer to Modbus response buffer. 'mb_bufflen2' system variable is modified by every call to ModbusPutNextByte statement; it contains the number of bytes waiting to be received to the response buffer.

Modbus slave implementation
The Modbus slave firmware is also started by the ModbusInit statement that should be placed at the beginning of the basic program. Everything stated in the Modbus master implementation subsection applies for the slave firmware implementation, also. To specify that the slave firmware is being implemented, MODBUS_SLAVE_MODE parameter value should be set to 1 by the #define directive. Its default value is 0, referring to the master firmware implementation.

ModbusSlavePrepare statement will prepare the Modbus buffer to receive a request from the master device in the system 'modbus_service' subroutine by setting the 'mb_request' system bit (bit 0) in the 'mb_status' system variable. The bytes received from the master are expected to be in the 'mb_byte' system variable, and they are put into the Modbus request buffer by the ModbusSlavePutByte statement. After the successful request has been received the 'mb_prepare' (bit 4) 'mb_status' system bit will be set, so that the slave can prepare and send its response. 'mb_regnum' system variable will contain the number of registers value extracted from the received Modbus request.
There are the following system names for the 'mb_status' bits available for the slave firmware implementation: 'mb_request' (bit 0) - specifying that receiving the Modbus request from the master is in progress, 'mb_response' (bit 1) - specifying that sending the Modbus response to the master is in progress, 'mb_completed' (bit 2) - set when all the bytes from the Modbus response has been sent, 'mb_simgotbyte' (bit 3) - set by MODBUS_SIM_GET_BYTE statement when Modbus Simulation Device tool updates the 'mb_byte' system variable (this bit should be cleared in the firmware), 'mb_prepare' (bit 4) - specifying that a successful request has been received from the master, 'mb_funcerror' (bit 6) - set when the received request has unsupported function code, and 'mb_crcerror' (bit 7) - set when the received request has incorrect checksum.
As with master firmware implementation, two function codes are supported, 0x03 ('mb_read' system symbolic name) that reads the values in the slave holding registers, and 0x10 ('mb_write' system symbolic name) function code used to write the values to those slave holding registers.

ModbusPrepareResponse statement will call the system 'modbus_prepare_response' subroutine that has to be present in the firmware program. It is the place where the slave's response should be prepared.

During the preparation of the response, ModbusGetAddress statement can be used to extract the starting holding register location from the Modbus request, and for write requests ModbusGetData statement can be used to extract the register data to be written. Arguments are supposed to be Word data type variables.

For Modbus write requests to the slave, ModbusMakeWriteResponse statement will prepare the appropriate response. It will also reset the 'mb_prepare' flag and set the 'mb_response' flag in the 'mb_status' system variable.

For Modbus read requests to the slave, ModbusMakeReadResponse statement will prepare the appropriate response. It expects to be followed by the 'mb_regnum' number of the Word data type arguments that will be included in the response. It will also reset the 'mb_prepare' flag and set the 'mb_response' flag in the 'mb_status' system variable.

ModbusSlaveGetByte statement is supposed to be used in the system 'modbus_service' subroutine. ModbusSlaveGetByte statement will put the next byte value from the Modbus response buffer to the 'mb_byte' system variable, the value that should be sent to the master. After all the bytes from the prepared response have been sent to the master 'mb_response' flag will be reset and the 'mb_completed' flag will be set in the 'mb_status' system variable.

Here is one of the simplest examples for the Modbus slave simulation by the Modbus Simulation Device tool:
   #define MODBUS_REG_NUM = 2
   #define MODBUS_SLAVE_MODE = 1
   Const my_slave_address = 5
   Dim addr As Word
   Dim var1 As Word
   Dim var2 As Word
   ModbusInit
   loop1:
      If mb_status = 0 Then
         ModbusSlavePrepare
      Endif
      ModbusService
   Goto loop1
   End

   modbus_init:
   mb_status = 0
   Return

   modbus_service:
   If mb_status.mb_request = True Then
      MODBUS_SIM_GET_BYTE
      If mb_status.mb_simgotbyte = 1 Then
         mb_status.mb_simgotbyte = 0
         'mb_byte system variable has the byte received from the master device
         ModbusSlavePutByte
      Endif
   Endif
   If mb_status.mb_prepare = True Then
      ModbusPrepareResponse
   Endif
   If mb_status.mb_response = True Then
      ModbusSlaveGetByte
      'mb_byte system variable is ready to be sent to the master device
      MODBUS_SIM_SEND_BYTE
   Endif
   If mb_status.mb_completed = True Then
      mb_status = 0
   Endif
   If mb_status.mb_crcerror = True Then
      mb_status = 0
   Endif
   If mb_status.mb_funcerror = True Then
      mb_status = 0
   Endif
   Return

   modbus_prepare_response:
   If mb_slaveaddress = my_slave_address Then
      If mb_function = mb_read Then
         ModbusGetAddress addr
         ModbusMakeReadResponse var1, var2
      Endif
      If mb_function = mb_write Then
         ModbusGetAddress addr
         'mb_regnum system variable has the number of holding register values received from the master device
         ModbusGetData var1, var2
         ModbusMakeWriteResponse
         var1 = var1 + 1
         var2 = var2 + 1
      Endif
   Endif
   Return

• Interfacing MMC/SD/SDSC/SDHC cards implementation

The MMC/SD/SDSC/SDHC cards interface is implemented by the software master SPI feature of the basic compiler, so it is needed to correctly setup the SPI related parameters by the #define directive to match the hardware interface, prior to using the SD card related basic statements. The interface supports all the card types available on the market.

The card must be initialized first by using the SDCardInit statement. Right after the execution of this statement, it is possible and needed to check the status of its execution by querying the value of the system variable 'sd_status'. There are the following system names for the 'sd_status' bits available: 'sd_error' (bit 0) - specifying that the SDCardInit statement could not to successfully initialize the card, 'sd_mmc' (bit 1) - set when the statement has detected an MMC card, 'sd_sd' (bit 2) - set when a standard (version 1) SD card has been detected, 'sd_sdsc' (bit 3) - set when a standard capacity (version 2, SDSC) SD card has been detected, and 'sd_sdhc' (bit 4) - set when a high or extended capacity (SDHC or SDXC) SD card has been detected and successfully initialized.

All cards are initialized to work with 512-byte block length. All the card memory capacity can be viewed as a sequence of the 512-byte long blocks. Each block has its address, and the address of the first block of the card memory is 0. In this implementation, all operations are performed on the block level, and only one block can be accessed during one card operation.

Writing data to a block is initialized with the SDCardWriteStart statement. Its only argument is the target block address supplied as a variable or a constant, that can be in the up to Long data type range, and should not go beyond the number of available 512-byte memory blocks in the card.

Bytes are written one by one to the target block by the SDCardWriteByte statement. Its only argument is a Byte variable or constant to be written to the current location in the target block. The statement can have up to 32 comma-separated arguments. If the argument is out of Byte data type range, the lowest byte will be written only. Any attempts to write more than 512 bytes will be ignored.

Block writing process should be finished by the SDCardWriteFinish statement. If less than 512 bytes are previously supplied by the SDCardWriteByte statement, SDCardWriteFinish statement will fill in the rest of block locations with the value specified by the SDCARD_DEFAULT_WRITE parameter. Its default value (0xFF) can be changed by the #define directive and must be in the Byte data type range.

If any errors have occurred during the block writing process, 'sd_rw_error' system bit (bit 7) will be set in the 'sd_status' system variable.

Reading data from a card block is implemented in a similar way to the block writing process.

Reading data from a block is initialized with the SDCardReadStart statement. Its only argument is the target block address. Bytes are read one by one from the target block by the SDCardReadByte statement. Its only argument is a Byte variable where the value read from the current location of the target block will be stored. The statement can have up to 32 comma-separated arguments. An attempt to read more than 512 bytes will return the value 0. Block reading process should be finished by the SDCardReadFinish statement.

If any errors have occurred during the block reading process, 'sd_rw_error' system bit (bit 7) will be set in the 'sd_status' system variable.

Here is one of the simplest examples that still demonstrates all the functionality of the MMC/SD/SDSC/SDHC cards interface implementation:
   #define CLOCK_FREQUENCY = 20
   #define SDCARD_DEFAULT_WRITE = 0x00

   #define LCD_LINES = 4
   #define LCD_CHARS = 20
   #define LCD_BITS = 4
   #define LCD_DREG = PORTB
   #define LCD_DBIT = 4
   #define LCD_EREG = PORTB
   #define LCD_EBIT = 3
   #define LCD_RSREG = PORTB
   #define LCD_RSBIT = 2

   #define SPI_CS_REG = PORTC
   #define SPI_CS_BIT = 2
   #define SPI_SCK_REG = PORTC
   #define SPI_SCK_BIT = 3
   #define SPI_SDI_REG = PORTC
   #define SPI_SDI_BIT = 4
   #define SPI_SDO_REG = PORTC
   #define SPI_SDO_BIT = 5

   Dim block As Byte
   Dim num As Word
   Dim var1 As Byte
   Dim var2 As Byte
   Dim var3 As Byte
   Dim var4 As Byte
   Dim var5 As Byte
   Dim var6 As Byte
   Dim var7 As Byte
   Dim var8 As Byte

   Lcdinit LcdCurBlink

   WaitMs 2000
   Lcdcmdout LcdClear
   Lcdout "SD CARDS TEST!"
   Lcdcmdout LcdCurOff

   SDCardInit

   block = 1
   do_again:

   WaitMs 2000
   Lcdcmdout LcdClear
   If sd_status.sd_error = 1 Then Lcdout "Unknown card!"
   If sd_status.sd_mmc = 1 Then Lcdout "MMC card!"
   If sd_status.sd_sd = 1 Then Lcdout "SD card!"
   If sd_status.sd_sdsc = 1 Then Lcdout "SDSC card!"
   If sd_status.sd_sdhc = 1 Then Lcdout "SDHC card!"

   WaitMs 2000
   Lcdcmdout LcdLine2Clear
   SDCardWriteStart block
   For num = 0 To 511
      SDCardWriteByte num
   Next num
   SDCardWriteFinish
   If sd_status.sd_rw_error = 1 Then Lcdout "Write error!"
   If sd_status.sd_rw_error = 0 Then Lcdout "Block written!"

   WaitMs 2000
   SDCardReadStart block
   For num = 0 To 63
      SDCardReadByte var1
      SDCardReadByte var2
      SDCardReadByte var3
      SDCardReadByte var4
      SDCardReadByte var5
      SDCardReadByte var6
      SDCardReadByte var7
      SDCardReadByte var8
      Lcdcmdout LcdLine3Clear
      Lcdout #var1, ",", #var2, ",", #var3, ",", #var4
      Lcdcmdout LcdLine4Clear
      Lcdout #var5, ",", #var6, ",", #var7, ",", #var8
      WaitMs 250
   Next num
   SDCardReadFinish
   Lcdcmdout LcdLine4Clear
   Lcdcmdout LcdLine3Clear
   If sd_status.sd_rw_error = 1 Then Lcdout "Read error!"
   If sd_status.sd_rw_error = 0 Then Lcdout "Block read!"

   WaitMs 2000
   If block = 1 Then
      block = 2
      Goto do_again
   Endif
   End

FAT16 File System Support
For the MMC/SD/SDSC/SDHC cards formatted with FAT16 file system, the functionality described below is available.

Before any file access, FAT16 file system on the card must be initialized first with the SDCardFAT16Init statement. If any errors have occurred during the file system initialization process, 'sd_fat16_error' system bit (bit 0) will be set in the 'sd_fat16_status' system variable.

To open existing file in the root folder on the card for reading SDCardFAT16FileOpen statement should be used. Its only argument is a string constant containing the file name. 8.3 file name format is expected. After the execution of this statement, it is possible to check the status of its execution by querying the value of the system variable 'sd_fat16_status'. If the file was not found in the card root folder the 'sd_fat16_notfound' system bit (bit 1) will be set. If the file was found and successfully opened for reading, the 'sd_fat16_opened' system bit (bit 2) will be set in the 'sd_fat16_status' variable, and another Long data type system variable - 'sd_fat16_filelen' will be updated with the actual length of the opened file, expressed in bytes.

The file data are read with the consecutive calls to the SDCardFAT16FileRead statement. Reading process starts from the beginning of the file. One call to the statement will read up to 32 bytes from the file and store them in the system Byte data type array 'sd_fat16_buff'. 'sd_fat16_bytes_read' system variable will be updated with the number of bytes actually read from the file, and the 'sd_fat16_filelen' variable will be decremented by that value. It will now contain the number of bytes that have not yet been read from the file. After all the bytes from the file have been read, both 'sd_fat16_filelen' and 'sd_fat16_bytes_read' variable will contain zero values, and the 'sd_fat16_opened' system bit will be reset.

Here is one example:
   Dim cnt As Byte
   Dim file_data(32) As Byte

   #define SPI_CS_REG = PORTC
   #define SPI_CS_BIT = 2
   #define SPI_SCK_REG = PORTC
   #define SPI_SCK_BIT = 3
   #define SPI_SDI_REG = PORTC
   #define SPI_SDI_BIT = 4
   #define SPI_SDO_REG = PORTC
   #define SPI_SDO_BIT = 5

   Lcdinit LcdCurBlink
   SDCardInit

   SDCardFAT16Init
   Lcdcmdout LcdClear
   If sd_fat16_status.sd_fat16_error = 0 Then
      Lcdout "FAT16 initialized!"
   Endif

   SDCardFAT16FileOpen "test.txt"
   Lcdcmdout LcdClear
   If sd_fat16_status.sd_fat16_notfound = 1 Then
      Lcdout "File not found!"
   Endif
   If sd_fat16_status.sd_fat16_opened = 1 Then
      Lcdout "File test.txt found!"
      Lcdcmdout LcdLine2Clear
      Lcdout "filelen: ", #sd_fat16_filelen
      Gosub read_file
   Endif
   End

   read_file:
   SDCardFAT16FileRead
   Lcdcmdout LcdClear
   Lcdout "bytes_read: ", #sd_fat16_bytes_read
   Lcdcmdout LcdLine2Clear
   Lcdout "filelen: ", #sd_fat16_filelen
   For cnt = 0 To 31
      file_data(cnt) = sd_fat16_buff(cnt)
   Next cnt
   If sd_fat16_bytes_read = 0 Then Return
   Goto read_file
   Return

There are three statements that can be used to create a new file in the root folder on the FAT16 formatted memory card and to write data to it. The file is created and opened for writing by the SDCardFAT16FileCreate statement. Its only argument is a string constant containing the file name. 8.3 file name format is expected. If the operation is successfully completed, the 'sd_fat16_wr_opened' system bit (bit 4) will be set in the 'sd_fat16_status' system variable. Data is written to the file by the SDCardFAT16FileWrite statement. One statement can contain up to 32 comma-separated arguments. The arguments can be ASCII bytes (variables and constants) and string variables and constants. The writing operation should be completed by the SDCardFAT16FileClose statement. It will also reset the 'sd_fat16_wr_opened' system bit.

To start writing data to an existing file from the root folder SDCardFAT16FileAppend should be used. Its only argument is the file name. If the file has been found, it is opened for writing and the 'sd_fat16_wr_opened' system bit is set. If the file has not been found, the 'sd_fat16_notfound' system bit will be set.

To check the existence of a file in the root folder SDCardFAT16FileExists statement should be used. Its only argument is the file name. If the file has been found, the 'sd_fat16_found' system bit (bit 3) will be set in the 'sd_fat16_status' system variable after the statement execution. If the file has not been found, the 'sd_fat16_notfound' system bit will be set.

To delete a file in the root folder SDCardFAT16FileDelete statement should be used. Its only argument is the file name. If the file has been found and deleted, the 'sd_fat16_deleted' system bit (bit 5) will be set in the 'sd_fat16_status' system variable after the statement execution. If the file has not been found, the 'sd_fat16_notfound' system bit will be set.

The file names from the the root folder can be read by using SDCardFAT16Dir statement. The statement can contain up to 32 comma-separated arguments - string variables, where 8.3 file format names (without the dot sign) will be stored. If the number of file entries is less than the number of the statement arguments, the 'sd_fat16_notfound' system bit will be set.

There are however some limitations. To use these statements the AVR device must have at least 1024 SRAM locations. Also, only the first 32 sectors of the FAT memory card area can be used. In practice, this means that the card should not contain a lot of files on it.

Here is one simple example:
   Dim cnt As Byte

   #define SPI_CS_REG = PORTC
   #define SPI_CS_BIT = 2
   #define SPI_SCK_REG = PORTC
   #define SPI_SCK_BIT = 3
   #define SPI_SDI_REG = PORTC
   #define SPI_SDI_BIT = 4
   #define SPI_SDO_REG = PORTC
   #define SPI_SDO_BIT = 5

   Lcdinit LcdCurBlink
   SDCardInit

   SDCardFAT16Init
   Lcdcmdout LcdClear
   If sd_fat16_status.sd_fat16_error = 0 Then
      Lcdout "FAT16 initialized!"
   Endif

   WaitMs 2000
   SDCardFAT16FileCreate "oshon.txt"
   Lcdcmdout LcdClear
   If sd_fat16_status.sd_fat16_wr_opened = 1 Then
      Lcdout "oshon.txt created!"
   Endif

   For cnt = 1 To 200
      SDCardFAT16FileWrite "file oshon.txt", CrLf
   Next cnt

   SDCardFAT16FileClose
   Lcdcmdout LcdLine2Clear
   If sd_fat16_status.sd_fat16_wr_opened = 0 Then
      Lcdout "oshon.txt written!"
   Endif

FAT32 File System Support
For the MMC/SD/SDSC/SDHC cards formatted with FAT32 file system, the functionality described below is available.

Before any file access, FAT32 file system on the card must be initialized first with the SDCardFAT32Init statement. If any errors have occurred during the file system initialization process, 'sd_fat32_error' system bit (bit 0) will be set in the 'sd_fat32_status' system variable.

To open existing file in the root folder on the card for reading SDCardFAT32FileOpen statement should be used. Its only argument is a string constant containing the file name. 8.3 file name format is expected. After the execution of this statement, it is possible to check the status of its execution by querying the value of the system variable 'sd_fat32_status'. If the file was not found in the card root folder the 'sd_fat32_notfound' system bit (bit 1) will be set. If the file was found and successfully opened for reading, the 'sd_fat32_opened' system bit (bit 2) will be set in the 'sd_fat32_status' variable, and another Long data type system variable - 'sd_fat32_filelen' will be updated with the actual length of the opened file, expressed in bytes.

The file data are read with the consecutive calls to the SDCardFAT32FileRead statement. Reading process starts from the beginning of the file. One call to the statement will read up to 32 bytes from the file and store them in the system Byte data type array 'sd_fat32_buff'. 'sd_fat32_bytes_read' system variable will be updated with the number of bytes actually read from the file, and the 'sd_fat32_filelen' variable will be decremented by that value. It will now contain the number of bytes that have not yet been read from the file. After all the bytes from the file have been read, both 'sd_fat32_filelen' and 'sd_fat32_bytes_read' variable will contain zero values, and the 'sd_fat32_opened' system bit will be reset.

Here is one example:
   Dim cnt As Byte
   Dim file_data(32) As Byte

   #define SPI_CS_REG = PORTC
   #define SPI_CS_BIT = 2
   #define SPI_SCK_REG = PORTC
   #define SPI_SCK_BIT = 3
   #define SPI_SDI_REG = PORTC
   #define SPI_SDI_BIT = 4
   #define SPI_SDO_REG = PORTC
   #define SPI_SDO_BIT = 5

   Lcdinit LcdCurBlink
   SDCardInit

   SDCardFAT32Init
   Lcdcmdout LcdClear
   If sd_fat32_status.sd_fat32_error = 0 Then
      Lcdout "FAT32 initialized!"
   Endif

   SDCardFAT32FileOpen "test.txt"
   Lcdcmdout LcdClear
   If sd_fat32_status.sd_fat32_notfound = 1 Then
      Lcdout "File not found!"
   Endif
   If sd_fat32_status.sd_fat32_opened = 1 Then
      Lcdout "File test.txt found!"
      Lcdcmdout LcdLine2Clear
      Lcdout "filelen: ", #sd_fat32_filelen
      Gosub read_file
   Endif
   End

   read_file:
   SDCardFAT32FileRead
   Lcdcmdout LcdClear
   Lcdout "bytes_read: ", #sd_fat32_bytes_read
   Lcdcmdout LcdLine2Clear
   Lcdout "filelen: ", #sd_fat32_filelen
   For cnt = 0 To 31
      file_data(cnt) = sd_fat32_buff(cnt)
   Next cnt
   If sd_fat32_bytes_read = 0 Then Return
   Goto read_file
   Return

There are three statements that can be used to create a new file in the root folder on the FAT32 formatted memory card and to write data to it. The file is created and opened for writing by the SDCardFAT32FileCreate statement. Its only argument is a string constant containing the file name. 8.3 file name format is expected. If the operation is successfully completed, the 'sd_fat32_wr_opened' system bit (bit 4) will be set in the 'sd_fat32_status' system variable. Data is written to the file by the SDCardFAT32FileWrite statement. One statement can contain up to 32 comma-separated arguments. The arguments can be ASCII bytes (variables and constants) and string variables and constants. The writing operation should be completed by the SDCardFAT32FileClose statement. It will also reset the 'sd_fat32_wr_opened' system bit.

To start writing data to an existing file from the root folder SDCardFAT32FileAppend should be used. Its only argument is the file name. If the file has been found, it is opened for writing and the 'sd_fat32_wr_opened' system bit is set. If the file has not been found, the 'sd_fat32_notfound' system bit will be set.

To check the existence of a file in the root folder SDCardFAT32FileExists statement should be used. Its only argument is the file name. If the file has been found, the 'sd_fat32_found' system bit (bit 3) will be set in the 'sd_fat32_status' system variable after the statement execution. If the file has not been found, the 'sd_fat32_notfound' system bit will be set.

To delete a file in the root folder SDCardFAT32FileDelete statement should be used. Its only argument is the file name. If the file has been found and deleted, the 'sd_fat32_deleted' system bit (bit 5) will be set in the 'sd_fat32_status' system variable after the statement execution. If the file has not been found, the 'sd_fat32_notfound' system bit will be set.

The file names from the the root folder can be read by using SDCardFAT32Dir statement. The statement can contain up to 32 comma-separated arguments - string variables, where 8.3 file format names (without the dot sign) will be stored. If the number of file entries is less than the number of the statement arguments, the 'sd_fat32_notfound' system bit will be set.

There are however some limitations. To use these statements the AVR device must have at least 1024 SRAM locations. Also, only the first 64 sectors of the FAT memory card area can be used. In practice, this means that the card should not contain a lot of files on it.

Here is one simple example:
   Dim cnt As Byte

   #define SPI_CS_REG = PORTC
   #define SPI_CS_BIT = 2
   #define SPI_SCK_REG = PORTC
   #define SPI_SCK_BIT = 3
   #define SPI_SDI_REG = PORTC
   #define SPI_SDI_BIT = 4
   #define SPI_SDO_REG = PORTC
   #define SPI_SDO_BIT = 5

   Lcdinit LcdCurBlink
   SDCardInit

   SDCardFAT32Init
   Lcdcmdout LcdClear
   If sd_fat32_status.sd_fat32_error = 0 Then
      Lcdout "FAT32 initialized!"
   Endif

   WaitMs 2000
   SDCardFAT32FileCreate "oshon.txt"
   Lcdcmdout LcdClear
   If sd_fat32_status.sd_fat32_wr_opened = 1 Then
      Lcdout "oshon.txt created!"
   Endif

   For cnt = 1 To 200
      SDCardFAT32FileWrite "file oshon.txt", CrLf
   Next cnt

   SDCardFAT32FileClose
   Lcdcmdout LcdLine2Clear
   If sd_fat32_status.sd_fat32_wr_opened = 0 Then
      Lcdout "oshon.txt written!"
   Endif

• Serial communication using internal hardware UART

The support for the hardware UART modules is available in the basic compiler. UART_Init, UART_Write, UART_Read and UART_Get statements can be used with AVR devices that feature internal hardware UART module (UART0). These statements are implemented in OshonSoft basic library files (see Library support section). UART_Init statement (aliases: UART0_Init, Hseropen) sets up the hardware UART. Its only argument is baud rate (constant argument). All baud rates in the range 100-1000000 will be accepted, but it is suggested to use the standard baud rates like: 1200, 2400, 4800, 9600, 14400, 19200, 28800, 31250, 38400, 56000, 57600, 115200.

UART_Write statement (aliases: UART0_Write, Hserout) is used for serial transmission. UART_Write statement will accept comma-separated list of arguments. Arbitrary complex expressions of all types will be accepted as arguments. One can use string variables, string constants (including symbolic and user-defined), numeric variables (variables are passed byte by byte starting from the lowest byte) and byte-type numeric constants. It is worth noting that decimal string representation of all numeric type variables (ASCII characters) can be passed to the statement by using the # prefix before the variable name.

UART_Read statement (aliases: UART0_Read, Hserin) can be used to store values received on serial port. The statement will accept multiple comma-separated arguments. All numeric data type variables are accepted. This statement will wait until the required number of bytes is received on serial port. Variables are filled in byte by byte starting from the lowest byte.

UART_Get statement (aliases: UART0_Get, Hserget) can have one argument that must be a variable. If there is a byte value waiting in the receive buffer it will be loaded into the variable, otherwise 0 value will be loaded. UART0_Data_Ready system variable from the library would be set to 1 if there was data waiting (default value: 0).

UART1_Init, UART1_Write, UART1_Read, UART1_Get and UART1_Data_Ready language elements from the UART library are available for the devices with the UART1 module.

UART2_Init, UART2_Write, UART2_Read, UART2_Get and UART2_Data_Ready language elements from the library are available for the devices with the UART2 module.

UART3_Init, UART3_Write, UART3_Read, UART3_Get and UART3_Data_Ready language elements are available for the devices with the UART3 module.

Here are some examples:
Example 1:
   Dim i As Byte
   UART_Init 38400
   WaitMs 1000
   For i = 20 To 0 Step -1
      UART_Write "Number: ", #i, CrLf
      WaitMs 500
   Next i

Example 2:
   Dim i As Byte
   UART_Init 19200
   loop:
      UART_Read i
      UART_Write "Number: ", #i, CrLf
   Goto loop

Example 3:
   Dim i As Byte
   UART_Init 19200
   loop:
      UART_Get i
      If UART0_Data_Ready > 0 Then
         UART_Write "Number: ", #i, CrLf
         WaitMs 50
      Endif
   Goto loop

• Software UART implementation

On all supported AVR devices software serial communication can be implemented with SEROUT and SERIN statements. The first argument of both statements must be one of the microcontroller's pins, and the second argument is baud rate. All baud rates in the range 100-200000 will be accepted, but it is suggested to use the standard baud rates like: 300, 600, 1200, 2400, 4800, 9600, 14400, 19200. Using higher baud rates with low clock frequency could cause inaccurate timing and framing errors. Small adjustments (1-5%) could possibly fix the problem.
For SEROUT statement then follows the list of arguments to be sent to serial port. One can use string variables and constants, numeric variables and byte numeric constants. If '#' sign is used before the name of a variable then its decimal string representation is sent to the serial port. SEROUT statement uses SEROUT_DELAYUS parameter that can be set by #define directive and has default value of 1000 microseconds. This defines the delay interval before a character is actually sent to the port and it is used to increase the reliability of software SEROUT routine.
For SERIN statement then follows the list of numeric data type variables to be loaded with the values received on serial port. This statement will wait until the required number of bytes is received on serial port. For serial interface with inverted logic levels there are SERININV and SEROUTINV statements available.
Here are some examples:
Example 1:
   #define SEROUT_DELAYUS = 5000
   Serout PORTD.1, 1200, "Hello world!", CrLf

Example 2:
   Dim i As Byte
   loop:
      Serin PORTD.0, 9600, i
      Serout PORTD.1, 9600, "Number: ", #i, CrLf
   Goto loop

SERIN and SERININV statements will wait indefinitely until the required number of bytes is received on software serial port. SERIN_TIMEOUT_REG and SERIN_TIMEOUT_BIT parameters for the #define directive provide a means to implement a custom timeout feature for these statements. When the predefined bit (symbolic bit names can be used here) in any of the available device registers becomes set, the software UART input statement will be interrupted. So, it is possible to use one of the device Timer modules, and program it to overflow after the desired timeout has been reached. In that case, the Timer overflow bit should be assigned to the timeout parameters. For example:
   #define SERIN_TIMEOUT_REG = TIFR
   #define SERIN_TIMEOUT_BIT = TOV0

• Interfacing character LCDs

Basic compiler also features the support for LCD modules based on HD44780 or compatible controller chip. Prior to using LCD related statements, user should set up LCD interface using #define directives. Here is the list of available parameters:
LCD_BITS - defines the number of data interface lines (allowed values are 4 and 8; default is 4)
LCD_DREG - defines the port where data lines are connected to (default is PORTB)
LCD_DBIT - defines the position of data lines for 4-bit interface (0 or 4; default is 4), ignored for 8-bit interface
LCD_RSREG - defines the port where RS line is connected to (default is PORTB)
LCD_RSBIT - defines the pin where RS line is connected to (default is 3)
LCD_EREG - defines the port where E line is connected to (default is PORTB)
LCD_EBIT - defines the pin where E line is connected to (default is 2)
LCD_RWREG - defines the port where R/W line is connected to (set to 0 if not used; 0 is default)
LCD_RWBIT - defines the pin where R/W line is connected to (set to 0 if not used; 0 is default)
LCD_COMMANDUS - defines the delay after LCDCMDOUT statement (default value is 5000)
LCD_DATAUS - defines the delay after LCDOUT statement (default value is 100)
LCD_INITMS - defines the delay for LCDINIT statement (default value is 100)

The last three parameters should be set to low values when using integrated LCD module simulator. If R/W line is connected to microcontroller and parameter LCD_READ_BUSY_FLAG is set to 1 using #define directive, then these delay parameters will be ignored by compiler and correct timing will be implemented by reading the status of the busy flag in the LCD.

LCDINIT statement should be placed in the program before any of LCDOUT (used for sending data) and LCDCMDOUT (used for sending commands) statements. Numeric constant argument of LCDINIT is used to define the cursor type: 0 = no cursor (default), 1 = blink, 2 = underline, 3 = blink + underline.
LCDOUT and LCDCMDOUT statements may have multiple arguments separated by ','. One can use string variables and constants, and byte variables and constants (representing ASCII character codes) as arguments of the LCDOUT statement. If '#' sign is used before the name of a variable then its decimal string representation is sent to the LCD module.
Constants and variables can be used as arguments of LCDCMDOUT statement and the following keywords are also available: LcdClear, LcdHome (LcdLine1Home), LcdLine2Home, LcdDisplayOn, LcdDisplayOff, LcdCurOff, LcdCurBlink, LcdCurUnderline, LcdCurBlinkUnderline, LcdLeft, LcdRight, LcdShiftLeft, LcdShiftRight, LcdLine1Clear, LcdLine2Clear, LcdLine1Pos() and LcdLine2Pos().
Argument of LcdLine1Pos() and LcdLine2Pos() can be a number in the range (1-40) or a variable. The value contained in that variable should be in the same range. LcdDisplayOn and LcdDisplayOff will turn the cursor off. Cursor related symbolic commands can be used as arguments of LCDINIT. Here are some examples:
Example 1:
   #define LCD_BITS = 8
   #define LCD_DREG = PORTB
   #define LCD_DBIT = 0
   #define LCD_RSREG = PORTD
   #define LCD_RSBIT = 1
   #define LCD_EREG = PORTD
   #define LCD_EBIT = 3
   #define LCD_RWREG = PORTD
   #define LCD_RWBIT = 2

   Lcdinit LcdCurBlink
   loop:
      Lcdout "Hello world!"
      WaitMs 1000
      Lcdcmdout LcdClear
      WaitMs 1000
   Goto loop

Example 2:
   #define LCD_BITS = 8
   #define LCD_DREG = PORTB
   #define LCD_DBIT = 0
   #define LCD_RSREG = PORTD
   #define LCD_RSBIT = 1
   #define LCD_EREG = PORTD
   #define LCD_EBIT = 3
   #define LCD_RWREG = PORTD
   #define LCD_RWBIT = 2

   Dim x As Word
   x = 65535
   Lcdinit 3
   WaitMs 1000
   loop:
      Lcdout "I am counting!"
      Lcdcmdout LcdLine2Home
      Lcdout #x
      x = x - 1
      WaitMs 250
      Lcdcmdout LcdClear
   Goto loop

LCD related statements will take control over DDR registers connected with pins used for LCD interface.

You can setup up to eight user-defined characters to be used on LCD. This can easily be done with LCDDEFCHAR statement. The first argument of this statement is char number and must be in the range 0-7. Next 8 arguments form 8-line char pattern (from the top to the bottom) and must be in the range 0-31 (5-bits wide). These 8 user characters are assigned to char codes 0-7 and 8-15 and can be displayed using LCDOUT statement. After LCDDEFCHAR statement the cursor will be in HOME position. For example:
   Lcddefchar 0, 10, 10, 10, 10, 10, 10, 10, 10
   Lcddefchar 1, %11111, %10101, %10101, %10101, %10101, %10101, %10101, %11111
   Lcdout 0, 1, "Hello!", 1, 0

For LCDs with four lines of characters additional symbolic arguments of LCDCMDOUT statement can be used: LcdLine3Home, LcdLine4Home, LcdLine3Clear, LcdLine4Clear, LcdLine3Pos() and LcdLine4Pos(). Argument of LcdLine3Pos() and LcdLine4Pos() can be a number in the range (1-40) or a variable. The value contained in that variable should be in the same range. Prior to using these language elements, correct values determining LCD type should be assigned to LCD_LINES and LCD_CHARS parameters using #define directives (the default values select 2x16 characters module).
   #define LCD_LINES = 4
   #define LCD_CHARS = 16
   #define LCD_BITS = 8
   #define LCD_DREG = PORTB
   #define LCD_DBIT = 0
   #define LCD_RSREG = PORTD
   #define LCD_RSBIT = 1
   #define LCD_EREG = PORTD
   #define LCD_EBIT = 3
   #define LCD_RWREG = PORTD
   #define LCD_RWBIT = 2

   Lcdinit 3
   loop:
      Lcdcmdout LcdClear
      Lcdcmdout LcdLine1Home
      Lcdout "This is line 1"
      Lcdcmdout LcdLine2Home
      Lcdout "This is line 2"
      Lcdcmdout LcdLine3Home
      Lcdout "This is line 3"
      Lcdcmdout LcdLine4Home
      Lcdout "This is line 4"
      WaitMs 1000
      Lcdcmdout LcdLine1Clear
      Lcdcmdout LcdLine2Clear
      Lcdcmdout LcdLine3Clear
      Lcdcmdout LcdLine4Clear
      Lcdcmdout LcdLine1Pos(1)
      Lcdout "Line 1"
      Lcdcmdout LcdLine2Pos(2)
      Lcdout "Line 2"
      Lcdcmdout LcdLine3Pos(3)
      Lcdout "Line 3"
      Lcdcmdout LcdLine4Pos(4)
      Lcdout "Line 4"
      WaitMs 1000
   Goto loop

• I2C communication with external I2C devices

I2C communication can be implemented in basic programs using I2CWRITE and I2CREAD statements. The first argument of both statements must be one of the microcontroller's pins that is connected to the SDA line of the external I2C device. The second argument of both statements must be one of the microcontroller's pins that is connected to the SCL line. The third argument of both statements must be a constant value or Byte variable called 'slave address'. Its format is described in the datasheet of the used device. For example, for EEPROMs from 24C family (with device address inputs connected to ground) the value 0xA0 should be used for slave address parameter. Both statements will take control over bit 0 of slave address during communication. The fourth argument of both statements must be a Byte or Word variable (this depends on the device used) that contains the address of the location that will be accessed. If a constant value is used for address parameter it must be in Byte value range. The last (fifth) argument of I2CWRITE statement is a Byte constant or variable that will be written to the specified address, and for I2CREAD statement it must be a Byte variable to store the value that will be read from the specified address. It is allowed to use more than one 'data' argument. For I2C devices that do not support data address argument there is short form of I2C statements (I2CWRITE1 and I2CREAD1) available where slave address argument is followed with one or more data arguments directly. For some I2C slave devices it is necessary to make a delay to make sure device is ready to respond to I2CREAD statement. For that purpose there is I2CREAD_DELAYUS parameter that can be set by #define directive and has default value of 0 microseconds. Also, for slower I2C devices, it might be necessary to use longer clock pulses. That can be done by setting I2CCLOCK_STRETCH parameter using #define directive. This parameter will set clock stretch factor. Its default value is 1. Here is one combined example with LCD module and 24C64 EEPROM (SDA connected to RC1; SCL connected to RC0):
Example 1:
   #define LCD_BITS = 8
   #define LCD_DREG = PORTB
   #define LCD_DBIT = 0
   #define LCD_RSREG = PORTD
   #define LCD_RSBIT = 1
   #define LCD_EREG = PORTD
   #define LCD_EBIT = 3
   #define LCD_RWREG = PORTD
   #define LCD_RWBIT = 2

   Dim addr As Word
   Dim data As Byte
   Symbol sda = PORTC.1
   Symbol scl = PORTC.0
   Lcdinit 3
   WaitMs 1000

   For addr = 0 To 31
      Lcdcmdout LcdClear
      data = 255 - addr
      I2CWrite sda, scl, 0xa0, addr, data
      Lcdout "Write To EEPROM"
      Lcdcmdout LcdLine2Home
      Lcdout "(", #addr, ") = ", #data
      WaitMs 1000
   Next addr

   For addr = 0 To 31
      Lcdcmdout LcdClear
      I2CRead sda, scl, 0xa0, addr, data
      Lcdout "Read From EEPROM"
      Lcdcmdout LcdLine2Home
      Lcdout "(", #addr, ") = ", #data
      WaitMs 1000
   Next addr

There is a set of low-level I2C communication statements available, if it is needed to have more control over I2C communication process. I2CPREPARE statement has two arguments that must be one of the microcontroller's pins. The first argument defines SDA line and second argument defines SCL line. This statement will prepare these lines for I2C communication. I2CSTART statement will generate start condition, and I2CSTOP statement will generate stop condition. One byte can be sent to the I2C slave using I2CSEND statement. After the statement is executed T bit in SREG register will hold the copy of the state on the SDA line during the acknowledge cycle. There are two statements that can be used to receive one byte from I2C slave. I2CRECA or I2CRECEIVEACK will generate acknowledge signal during acknowledge cycle after the byte is received. I2CRECN or I2CRECEIVENACK will not generate acknowledge signal during acknowledge cycle after the byte is received. One example:
Example 2:
   Dim addr As Word
   Dim data(31) As Byte

   Symbol sda = PORTC.1
   Symbol scl = PORTC.0
   addr = 0

   I2CPrepare sda, scl
   I2CStart
   I2CSend 0xa0
   I2CSend addr.HB
   I2CSend addr.LB
   I2CStop
   I2CStart
   I2CSend 0xa1
   For addr = 0 To 30
      I2CReceiveAck data(addr)
   Next addr
   I2CRecN data(31)
   I2CStop

• Support for software master SPI (Serial Peripheral Interface) communication

Prior to using SPI related statements, SPI interface should be set up using #define directives. There are eight available parameters to define the connection of SCK, SDI, SDO and (optionally) CS lines:
SPI_SCK_REG - defines the port where SCK line is connected to
SPI_SCK_BIT - defines the pin where SCK line is connected to
SPI_SDI_REG - defines the port where SDI line is connected to
SPI_SDI_BIT - defines the pin where SDI line is connected to
SPI_SDO_REG - defines the port where SDO line is connected to
SPI_SDO_BIT - defines the pin where SDO line is connected to
SPI_CS_REG - defines the port where CS line is connected to
SPI_CS_BIT - defines the pin where CS line is connected to

The assumed settings are active-high for Clock line and active-low for ChipSelect line. That can be changed by assigning the value 1 to SPICLOCK_INVERT and/or SPICS_INVERT parameters by #define directive. For slower SPI devices, it might be necessary to use longer clock pulses. The default clock stretch factor (0) can be changed by setting SPICLOCK_STRETCH parameter.

SPIPREPARE statement (no arguments) will prepare interface lines for SPI communication. SPICSON and SPICSOFF statements will enable/ disable the ChipSelect line of the interface. One byte can be sent to the SPI peripheral using SPISEND statement. To receive a byte from the peripheral SPIRECEIVE statement should be used. To send the specified number of bits there is SPISENDBITS statement available. Its first argument should be the number of bits to be sent [1-8] and the second argument is a byte variable or constant. Here is one example for using 25C040 SPI eeprom:
Example 1:
   #define SPI_CS_REG = PORTB
   #define SPI_CS_BIT = 4
   #define SPI_SCK_REG = PORTB
   #define SPI_SCK_BIT = 7
   #define SPI_SDI_REG = PORTB
   #define SPI_SDI_BIT = 6
   #define SPI_SDO_REG = PORTB
   #define SPI_SDO_BIT = 5
   SPIPrepare

   #define LCD_BITS = 8
   #define LCD_DREG = PORTD
   #define LCD_DBIT = 0
   #define LCD_RSREG = PORTC
   #define LCD_RSBIT = 1
   #define LCD_EREG = PORTC
   #define LCD_EBIT = 2
   #define LCD_RWREG = PORTC
   #define LCD_RWBIT = 3
   #define LCD_READ_BUSY_FLAG = 1
   Lcdinit

   Dim addr As Byte
   Dim data As Byte

   For addr = 0 To 10
      data = 200 - addr
      SPICSOn
      SPISend 0x06
      SPICSOff
      SPICSOn
      SPISend 0x02
      SPISend addr
      SPISend data
      SPICSOff
      Lcdcmdout LcdClear
      Lcdout "Write To EEPROM"
      Lcdcmdout LcdLine2Home
      Lcdout "(", #addr, ") = ", #data
      WaitMs 500
   Next addr

   For addr = 0 To 10
      SPICSOn
      SPISend 0x03
      SPISend addr
      SPIReceive data
      SPICSOff
      Lcdcmdout LcdClear
      Lcdout "Read From EEPROM"
      Lcdcmdout LcdLine2Home
      Lcdout "(", #addr, ") = ", #data
      WaitMs 500
   Next addr

Here is the same example written for 93C86 Microwire EEPROM:
Example 2:
   #define SPI_CS_REG = PORTB
   #define SPI_CS_BIT = 4
   #define SPICS_INVERT = 1
   #define SPI_SCK_REG = PORTB
   #define SPI_SCK_BIT = 7
   #define SPI_SDI_REG = PORTB
   #define SPI_SDI_BIT = 6
   #define SPI_SDO_REG = PORTB
   #define SPI_SDO_BIT = 5
   SPIPrepare

   #define LCD_BITS = 8
   #define LCD_DREG = PORTD
   #define LCD_DBIT = 0
   #define LCD_RSREG = PORTC
   #define LCD_RSBIT = 1
   #define LCD_EREG = PORTC
   #define LCD_EBIT = 2
   #define LCD_RWREG = PORTC
   #define LCD_RWBIT = 3
   #define LCD_READ_BUSY_FLAG = 1
   Lcdinit

   Dim addr As Byte
   Dim data As Byte

   SPICSOn
   SPISendBits 6, %100110
   SPISendBits 8, %00000000
   SPICSOff

   For addr = 0 To 10
      data = 200 - addr
      SPICSOn
      SPISendBits 6, %101000
      SPISendBits 8, addr
      SPISend data
      SPICSOff
      SPICSOn
      SPISend 0x00
      SPICSOff
      Lcdcmdout LcdClear
      Lcdout "Write To EEPROM"
      Lcdcmdout LcdLine2Home
      Lcdout "(", #addr, ") = ", #data
      WaitMs 500
   Next addr

   For addr = 0 To 10
      SPICSOn
      SPISendBits 6, %110000
      SPISendBits 8, addr
      SPIReceive data
      SPICSOff
      Lcdcmdout LcdClear
      Lcdout "Read From EEPROM"
      Lcdcmdout LcdLine2Home
      Lcdout "(", #addr, ") = ", #data
      WaitMs 500
   Next addr

• Interfacing graphical LCDs with 128x64 dot matrix

Interfacing graphical LCDs with dot matrix resolution 128x64 controlled by KS0108 or compatible chip is supported with the following list of Basic language elements: GLCDINIT, GLCDCLEAR, GLCDPSET, GLCDPRESET, GLCDPOSITION, GLCDWRITE, GLCDCLEAN, GLCDOUT, GLCDIN, GLCDCMDOUT. Prior to using Graphical LCDs related statements, user should set up the interface with the graphical LCD module using #define directives. Here is the list of available parameters:
GLCD_DREG - defines the port where data lines are connected to (it has to be a full 8-pins port)
GLCD_RSREG - defines the port where RS line is connected to
GLCD_RSBIT - defines the pin where RS line is connected to
GLCD_EREG - defines the port where E line is connected to
GLCD_EBIT - defines the pin where E line is connected to
GLCD_RWREG - defines the port where R/W line is connected to
GLCD_RWBIT - defines the pin where R/W line is connected to
GLCD_CS1REG - defines the port where CS1 line is connected to
GLCD_CS1BIT - defines the pin where CS1 line is connected to
GLCD_CS2REG - defines the port where CS2 line is connected to
GLCD_CS2BIT - defines the pin where CS2 line is connected to

GLCDINIT statement should be placed somewhere at the beginning of the basic program before any other graphical LCD related statements are used. Graphical LCD related statements will take control over DDR registers connected with pins used for LCD interface.

GLCDCLEAR statement will clear the whole display. It can be used with one optional constant argument in the range 0-255 that will be placed on every byte position on the display (128x64 graphical displays are internally divided in two 64x64 halves; both halves are divided in eight 64x8 horizontal pages; every page has its addressing number in the range 0-15; page in upper-left corner has number 0; page in lower-left corner has number 7; page in upper-right corner has number 8; page in lower-right corner has number 15; every page has 64 byte positions addressed with numbers in the range 0-63; every byte position has 8 bits; the uppermost bit is LSB and the lowermost bit is MSB). For example:
   WaitMs 2
   GLcdinit
   loop:
      GLcdclear 0xaa
      WaitMs 1000
      GLcdclear 0x55
      WaitMs 1000
   Goto loop

GLCDPSET and GLCDPRESET statements are used to turn on and turn off one of the dots on the graphical display. The first argument is the horizontal coordinate and it must be a byte data type variable or constant in the range 0-127. The second argument is the vertical coordinate and it must be a byte data type variable or constant in the range 0-63. The dot in the upper-left corner of the display is the origin with coordinates 0,0. For example:
   Dim i As Byte
   Dim j As Byte
   WaitMs 2
   GLcdinit
   For i = 0 To 127
   For j = 0 To 63
      GLcdpset i, j
   Next j
   Next i

GLCDCLEAN statement is used to clear a section of the page on the display. It has three arguments. The first argument is page address and it must be a byte data type variable or constant in the range 0-15. The second argument is the first byte position on the page that will be cleaned and it must be a byte data type variable or constant in the range 0-63. The third argument is the last byte position on the page that will be cleaned and it must be a byte data type variable or constant in the range 0-63. If the last two arguments are omitted the whole page will be cleared. For example:
   Dim i As Byte
   WaitMs 2
   GLcdinit
   GLcdclear 0xff
   For i = 0 To 15
      GLcdclean i
      WaitMs 500
   Next i

GLCDPOSITION statement is used to address a byte position on the display. It must be used before any of the GLCDWRITE, GLCDIN, GLCDOUT and GLCDCMDOUT statements. The first argument is page address and it must be a byte data type variable or constant in the range 0-15. The second argument is the target byte position on the page and it must be a byte data type variable or constant in the range 0-63. If the second argument is omitted, zero byte position is used.

GLCDWRITE statement is used to write text on the display. It will start writing from the current byte position on the display. It must be used carefully, because when the byte position (63) of the page is reached, the writing will continue from the byte position 0 staying on the same page. The width of every character written is 5 byte positions plus one clear byte position. After the statement is executed the current byte position will be at the end of the text written.
GLCDWRITE statement may have multiple arguments separated by ','. One can use string variables and constants, and byte constants (representing ASCII character codes) as its arguments. If '#' sign is used before the name of a variable then its decimal string representation is written. For example:
   Dim i As Byte
   WaitMs 2
   GLcdinit
   For i = 0 To 15
      GLcdposition i, 0
      GLcdwrite "Page: ", #i
      WaitMs 250
   Next i

GLCDOUT statement is used to write the value of the byte variable or constant at the current byte position on the display. The current byte position will be incremented by one. GLCDIN statement will read the value from the current byte position on the display and put it in the byte variable specified as its argument. GLCDCMDOUT statement is used to send low-level commands to the graphical LCD. Its argument can be a constant or byte data type variable. All these three statements can be used with multiple arguments separated by ','.

• Interfacing Radio Control (R/C) servos

For writing applications to interface R/C servos there are two statements available: SERVOIN and SERVOOUT. R/C servo is controlled by a train of pulses (15-20 pulses per second) whose length define the position of the servo arm. The valid length of pulses is in the range 1-2ms. These two statements have two arguments. The first argument of both statements is the microcontroller pin where the servo signal is received or transmitted. For SERVOIN statement that pin should be previously setup as an input pin and for SERVOOUT statement the pin should be setup for output. The second argument of SERVOIN statement must be a Byte variable where the length of the pulse will be saved. The pulses are measured in 10us units, so it is possible to measure pulses in the range 0.01-2.55ms. The value stored in the variable for normal servos should be in the range 100-200. The second argument of the SERVOOUT statement should be a Byte variable or constant that determines the length of the generated pulse. For proper operation of the target servo SERVOOUT statement should be executed 15-20 times during one second. Here is an example of the servo reverse operation:
   Dim length As Byte
   DDRB.0 = 0
   DDRB.1 = 1
   loop:
      ServoIn PORTB.0, length
      If length < 100 Then length = 100
      If length > 200 Then length = 200
      length = length - 100
      length = 100 - length
      length = length + 100
      ServoOut PORTB.1, length
   Goto loop

• Interfacing Stepper Motors

Prior to using stepper motor related statements, its connection and desired drive mode should be set up using #define directives. There are eight available parameters to define the connection of A, B, C and D coils:
STEP_A_REG - defines the port where A coil is connected to
STEP_A_BIT - defines the pin where A coil is connected to
STEP_B_REG - defines the port where B coil is connected to
STEP_B_BIT - defines the pin where B coil is connected to
STEP_C_REG - defines the port where C coil is connected to
STEP_C_BIT - defines the pin where C coil is connected to
STEP_D_REG - defines the port where D coil is connected to
STEP_D_BIT - defines the pin where D coil is connected to

Coils A and C are actually parts of one single coil with common connection. The same is valid for B and D coil connections. There is also STEP_MODE parameter used to define the drive mode. If it is set to 1 (default) the motor will be driven in full-step mode. The value 2 should be used for half-step mode. The first basic statement that should be used is STEPHOLD. It will configure used pins as outputs and also energize A and B coils to fix the rotor in its initial position. For moving rotor in clockwise and counterclockwise directions there are STEPCW and STEPCCW statements available. Their first argument is the number of rotor steps that will be performed and it can be Byte data type constant or variable. The second argument defines the delay between consecutive steps expressed in microseconds by a Byte or Word data type variable or constant. If using STEPCW statement results in rotor movement in counterclockwise direction then connection settings for B and D coils should be exchanged. Here are two examples (the second example uses delays suitable for simulation in the simulator):
Example 1:
   #define STEP_A_REG = PORTB
   #define STEP_A_BIT = 7
   #define STEP_B_REG = PORTB
   #define STEP_B_BIT = 6
   #define STEP_C_REG = PORTB
   #define STEP_C_BIT = 5
   #define STEP_D_REG = PORTB
   #define STEP_D_BIT = 4
   #define STEP_MODE = 2

   WaitMs 1000
   StepHold
   WaitMs 1000

   Dim an0 As Word

   loop:
      ADC_Read 0, an0
      an0 = an0 * 60
      an0 = an0 + 2000
      StepCW 1, an0
   Goto loop

Example 2:
   #define STEP_A_REG = PORTB
   #define STEP_A_BIT = 7
   #define STEP_B_REG = PORTB
   #define STEP_B_BIT = 6
   #define STEP_C_REG = PORTB
   #define STEP_C_BIT = 5
   #define STEP_D_REG = PORTB
   #define STEP_D_BIT = 4
   #define STEP_MODE = 2

   WaitUs 300
   StepHold
   WaitUs 1000

   loop:
      StepCCW 16, 300
      WaitUs 1000
      StepCW 24, 300
      WaitUs 1000
   Goto loop

• Interfacing 1-WIRE devices

Prior to using 1-WIRE related statements, user should define the pin where the device is connected to using #define directives. Available parameters are 1WIRE_REG and 1WIRE_BIT. For example:
   #define 1WIRE_REG = PORTB
   #define 1WIRE_BIT = 0

Initialization sequence can be performed by 1WIREINIT statement. It can have an optional argument (Bit data type variable) that will be set to 0 if the presence of the device has been detected and set to 1 if there is no device on the line.

Individual bits (time slots) can be sent to and received from the device using 1WIRESENDBIT and 1WIREGETBIT statements. Both statements can have multiple arguments - comma-separated list of Bit data type variables (or Bit constants for 1WIRESENDBIT statement).

1WIRESENDBYTE and 1WIREGETBYTE statements can be used to send to and receive bytes from the device. Both statements can have multiple arguments - comma-separated list of Byte data type variables (or Byte constants for 1WIRESENDBYTE statement). Here is one example for measuring temperature using DS18S20 or DS18B20 device:
   Dim finish As Bit
   Dim temp_lsb As Byte
   Dim temp_msb As Byte

   1wireInit
   1wireSendByte 0xcc, 0x44
   WaitMs 1
   loop:
      1wireGetBit finish
   If finish = 0 Then Goto loop
   1wireInit
   1wireSendByte 0xcc, 0xbe
   1wireGetByte temp_lsb, temp_msb

This example can be very short by using two DS1820 specific high level basic statements, that can be used for both DS18S20 and DS18B20 devices. DS1820START statement will initiate a single temperature conversion. According to the device datasheets, the conversion will be completed in at most 750ms. After that period the measured value can be read by DS1820READT statement that requires two Byte data type variables as arguments. For the DS18S20 device, the first argument will contain the temperature value in 0.5 degrees centigrade units (for example, the value 100 represents the temperature of 50 degrees). The second argument will contain the value 0x00 if the temperature is positive and 0xFF value if it is negative. Here is a simple basic program:
   Dim temp_lsb As Byte
   Dim temp_msb As Byte

   DS1820Start
   WaitMs 1000
   DS1820ReadT temp_lsb, temp_msb

• OshonSoft PID Controller Library

With the PID Controller and PID Auto Tuning libraries it is possible to easily implement reliable PID controllers with the functional auto tuning feature.

Both PID_Init and PID_Compute statements require the current value of the process variable and it should be assigned to the PIDinput library variable. Also, the user basic program should insure that the library variable PIDtimeMS always contains the real time value in milliseconds.

The PID_Init statement should be called once, before starting regular calls to PID_Compute. PID_Init has one argument - the initial process variable setpoint value, that will be loaded to the PIDsetpoint library variable. Later, if needed, the setpoint can be changed using the PIDsetpoint variable.

Immediately after the PID_Init statement, one should use PID_SetTunings statement to set the initial (or desired) values for the PID tuning parameters. It has three arguments containing the new values for the proportional gain (Kp), the integral gain (Ki) and the derivative gain (Kd). PID_SetTunings statement can be used later again to change the tuning parameters.

The PID_Compute statement has no arguments. It should be called regularly by the user basic program. The actual PID computation will take place only if the PID_sampleTime interval has passed since the last computation. Otherwise, PID_Compute exits immediately. After every actual PID computation, the value in the PIDoutput control variable will be updated and the user basic program should 'forward' that value to the process being controlled.

Before using the PID Controller library statements, the library parameters should be properly set.

PID_outMin - This parameter defines the minimal value for the PIDoutput variable, that is evaluated according to the PID algorithm by the PID_Compute statement.
(allowed range: 0-4095; default: 0)
PID_outMax - Defines the maximal value for the PIDoutput variable.
(allowed range: 0-4095; default: 255)
PID_sampleTime - Sets the desired time interval in milliseconds between two consecutive PID computations.
(allowed range: 100-10000; default: 500)
PID_ctrlAction - The parameter determines the control action type, direct (1) or reverse (2). The process is direct acting if an increase in the output causes an increase in the input.
(allowed range: 1-2; default: 1)

When the PID_ctrlAction parameter is set to the reverse control action, the computed value in the PIDoutput control variable will be negative. The value of the PID_outMax parameter is expected to be added to the PIDoutput to get the proper value that can be used to control the process.

• OshonSoft PID Auto Tuning Library

The auto tuning implemented in the library is based on the so-called 'Relay method'. While the auto tuning operation is running, the output is switched between two values of the control variable (pidAToutputStart + pidAT_outStep) and (pidAToutputStart - pidAT_outStep). The values must be chosen so the process will cross the setpoint (PIDsetpoint) that is previously set by the PID_Init statement. That will result in the process variable oscillations around the setpoint. For actual processes, by choosing suitable values, dangerous oscillations can be avoided.

Before using the PID Auto Tuning library statements, the library parameters should be properly set.

pidAT_ctrlType - This parameter defines the choice for the tuning gains calculation at the end of the operation between the PI controller (proportional-integral controller) (value 0) and the full PID controller (value 1).
(allowed range: 0-1; default: 1)
pidAT_noiseBand - Sets the level of noise present in the process variable value. The PID output switching occurs when the process variable crosses the setpoint value. By using the correct pidAT_noiseBand value multiple (and false) crossings will be avoided. The result is the correct auto tuning operation.
(allowed range: 0-10; default: 0.5)
pidAT_inputMax - Defines the maximal value for the PIDinput process variable.
(allowed range: 0-4095; default: 100)
pidAT_outStep - This parameter will be used to change the initial control variable value (pidAToutputStart library variable) in both directions to create the desired oscillations.
(allowed range: 0-1000; default: 30)
pidAT_sampleTime - Sets the desired time interval in milliseconds between two actual consecutive PID_AutoTuningRunning statement executions.
(allowed range: 100-10000; default: 250)
pidAT_timeout - Defines the timeout interval (in seconds) that the PID_AutoTuningRunning statement must wait for the process variable to cross the setpoint value, before stopping the auto tuning operation with the error code 3.
(allowed range: 10-1800; default: 180)
pidAT_ctrlAction - The parameter determines the control action type, direct (1) or reverse (2). Generally, it should have the same value as the PID_ctrlAction parameter.
(allowed range: 1-2; default: 1)

Both PID_AutoTuningInit and PID_AutoTuningRunning statements require the current value of the process variable and it should be assigned to the PIDinput library variable. Also, the user basic program should insure that the library variable PIDtimeMS always contains the real time value in milliseconds.

The only argument of the PID_AutoTuningInit statement is the starting value for the PIDoutput control variable that will be stored in the library defined pidAToutputStart.

The PID_AutoTuningInit statement should be called once, before regular calls to the PID_AutoTuningRunning start.

Before using PID_AutoTuningInit, the PID_Init statement from the PID Controller library should be executed to preset the PIDsetpoint variable and perform the other needed setups.

The PID_AutoTuningRunning statement has no arguments. It should be called regularly by the user basic program. The actual statement execution will take place only if the pidAT_sampleTime interval has passed since the last execution. Otherwise, PID_AutoTuningRunning exits immediately. After every actual statement execution, the value in the PIDoutput control variable could be updated, so the user basic program should 'forward' that value to the process being controlled.

While the auto tuning operation is in progress, the pidATrunning library variable will have the value 1. Once the operation has been completed, the pidATcompleted variable will be set to 1. In case of an error that canceled the auto tuning process, pidATerror will get a positive value representing the error code.

If (pidAToutputStart + pidAT_outStep) or (pidAToutputStart - pidAT_outStep) values are beyond the limits defined by the PID_outMin and PID_outMax from the PID Controller library, the error codes in pidATerror will be 1 or 2. The error code 3 will be returned if it times out while waiting the process variable to cross the setpoint value.

When the pidATcompleted variable is finally set to 1 (the auto tuning has been completed), the calculated gains will be stored in the PID Auto Tuning Library variables pidATKp, pidATKi and pidATKd.

Here is one example:
   #define PID_outMin = 0
   #define PID_outMax = 255
   #define PID_sampleTime = 500
   #define PID_ctrlAction = 1

   #define pidAT_ctrlType = 1
   #define pidAT_noiseBand = 0.5
   #define pidAT_inputMax = 100
   #define pidAT_outStep = 80
   #define pidAT_sampleTime = 250
   #define pidAT_timeout = 10
   #define pidAT_ctrlAction = 1

   PID_Init 40  'value for PIDsetpoint
   PID_AutoTuningInit 100  'value for pidAToutputStart
   mainloop2:
      PID_AutoTuningRunning
      If pidATcompleted = 1 Then Goto mainloopexit2
      If pidATerror > 0 Then Goto mainloopexit
   Goto mainloop2
   mainloopexit2:

   PID_SetTunings pidATKp, pidATKi, pidATKd
   PIDsetpoint = 80
   mainloop:
      PID_Compute
      If PIDtimeMS > 61000 Then Goto mainloopexit
   Goto mainloop
   mainloopexit:

   End

• Advanced features

If STARTFROMZERO directive is used the compiler will start the program from zero flash program memory location (reset vector) and use the available program memory continuously. Interrupt routines if used should be implemented by using inline assembler code. This advanced feature can be used when developing bootloader applications, for example.

• #define directive parameters

Here is the list of all available parameters for the #define directive, along with their default values and allowed ranges of values:

CLOCK_FREQUENCY - (default value: selected in software, allowed range: 0.01-120MHz)

SIMULATION_WAITMS_VALUE - (default value: -1 (not used), allowed range: 0-10)
SINGLE_DECIMAL_PLACES - (default value: 3, allowed range: 1-6)
STRING_MAX_LENGTH - (default value: 16, allowed range: 8-100)
ARCUS_PRECISION - (default value: 1, allowed range: 1-2)

ADC_Sample_uS - (from ADC Module library, default value and allowed range shown in Libraries info panel)
ADC_Clk - (from ADC Module library, default value and allowed range shown in Libraries info panel)
ADC_Ref - (from ADC Module library, default value and allowed range shown in Libraries info panel)

LCD_BITS - (default value: 4, allowed values: 4,8)
LCD_DREG - (default value: PORTB, allowed values: available PORT registers for the selected device)
LCD_DBIT - (default value: 4, allowed values: 0,4)
LCD_RSREG - (default value: PORTB, allowed values: available PORT registers for the selected device)
LCD_RSBIT - (default value: 3, allowed range: 0-7)
LCD_EREG - (default value: PORTB, allowed values: available PORT registers for the selected device)
LCD_EBIT - (default value: 2, allowed range: 0-7)
LCD_RWREG - (default value: 0, allowed values: 0, or available PORT registers for the selected device)
LCD_RWBIT - (default value: 0, allowed range: 0-7)
LCD_COMMANDUS - (default value: 5000, allowed range: 100-65535)
LCD_DATAUS - (default value: 100, allowed range: 50-255)
LCD_INITMS - (default value: 100, allowed range: 2-65535)
LCD_READ_BUSY_FLAG - (default value: 0, allowed values: 0,1)
LCD_LINES - (default value: 2, allowed values: 1,2,4)
LCD_CHARS - (default value: 16, allowed values: 8,16,20,24,40)

GLCD_DREG - (default value: PORTB, allowed values: available PORT registers for the selected device)
GLCD_RSREG - (default value: PORTC, allowed values: available PORT registers for the selected device)
GLCD_RSBIT - (default value: 3, allowed range: 0-7)
GLCD_RWREG - (default value: PORTC, allowed values: available PORT registers for the selected device)
GLCD_RWBIT - (default value: 4, allowed range: 0-7)
GLCD_EREG - (default value: PORTC, allowed values: available PORT registers for the selected device)
GLCD_EBIT - (default value: 5, allowed range: 0-7)
GLCD_CS1REG - (default value: PORTC, allowed values: available PORT registers for the selected device)
GLCD_CS1BIT - (default value: 6, allowed range: 0-7)
GLCD_CS2REG - (default value: PORTC, allowed values: available PORT registers for the selected device)
GLCD_CS2BIT - (default value: 7, allowed range: 0-7)

SEROUT_DELAYUS - (default value: 1000, allowed range: 0-65535)
SERIN_TIMEOUT_REG - (default value: 0, allowed values: 0, or available registers for the selected device)
SERIN_TIMEOUT_BIT - (default value: 0, allowed range: 0-7)

I2CREAD_DELAYUS - (default value: 0, allowed range: 0-65535)
I2CCLOCK_STRETCH - (default value: 1, allowed range: 1-50)

1WIRE_REG - (default value: PORTB, allowed values: available PORT registers for the selected device)
1WIRE_BIT - (default value: 0, allowed range: 0-7)

SPI_CS_REG - (default value: 0, allowed values: 0, or available PORT registers for the selected device)
SPI_CS_BIT - (default value: 0, allowed range: 0-7)
SPI_SCK_REG - (default value: PORTB, allowed values: available PORT registers for the selected device)
SPI_SCK_BIT - (default value: 0, allowed range: 0-7)
SPI_SDI_REG - (default value: PORTB, allowed values: available PORT registers for the selected device)
SPI_SDI_BIT - (default value: 1, allowed range: 0-7)
SPI_SDO_REG - (default value: PORTB, allowed values: available PORT registers for the selected device)
SPI_SDO_BIT - (default value: 2, allowed range: 0-7)
SPICLOCK_STRETCH - (default value: 0, allowed range: 0-50)
SPICLOCK_INVERT - (default value: 0, allowed values: 0,1)
SPICS_INVERT - (default value: 0, allowed values: 0,1)

COUNT_MODE - (default value: 1, allowed values: 1,2)

STEP_A_REG - (default value: PORTB, allowed values: available PORT registers for the selected device)
STEP_A_BIT - (default value: 0, allowed range: 0-7)
STEP_B_REG - (default value: PORTB, allowed values: available PORT registers for the selected device)
STEP_B_BIT - (default value: 1, allowed range: 0-7)
STEP_C_REG - (default value: PORTB, allowed values: available PORT registers for the selected device)
STEP_C_BIT - (default value: 2, allowed range: 0-7)
STEP_D_REG - (default value: PORTB, allowed values: available PORT registers for the selected device)
STEP_D_BIT - (default value: 3, allowed range: 0-7)
STEP_MODE - (default value: 1, allowed values: 1,2)

MODBUS_REG_NUM - (default value: 1, allowed range: 1-16)
MODBUS_SLAVE_MODE - (default value: 0, allowed range: 0-1)

SDCARD_DEFAULT_WRITE - (default value: 255, allowed range: 0-255)
SDCARD_HARDWARE_SPI - (default value: 0, allowed range: 0-1)
SDCARD_WRITE_DELAY - (default value: 5, allowed range: 0-100)

• Library support
The library support is a new way for the basic statements implementation.
This is indeed an advanced feature, however that is a way that can be used by both the author and the users to extend the compiler language from the external library files.
The information is loaded by the compiler from the external textual .lib files stored in the OshonSoft application data
folder.
Library files can be edited by Notepad, or any other plain text editor.
OshonSoft .lib files are well documented with comments covering all the currently available features of the library support compiler engine.
Detailed description of all basic elements, statements and functions, that is loaded from the library files is displayed on the Libraries Info Panel.

oshonsoftavr.lib (advanced and concise library support engine overview):
//the concept is that one library group contains the implementation of one new language functionality - one or more related statements or functions
//library items contain different implementations of the same functionality for different groups of microcontrollers
//#lib_item_begin must be followed by #processor
//#statement_begin, #statement_type, #argument sequence must be fulfilled for proper library load

//#processor comma-separated list of processors, x can be used as a wild card character
//#processor can be used in multiple lines to quote all devices if needed
//#parameter is used to implement #define parameters needed for the statement implementation
//#parameter const, parameter_name, allowed_range, default_value
//#parameter symbol, parameter_name, type (pin, bit, byte, address of), system_bit or system_register
//'pin' type is used for the bits in the PORT registers
//'address of' type will implement a constant parameter
//#variable is used to declare global system variables
//#variable variable_name, type (byte, word, long, single, string)
//#statement_begin statement_name [argument1_name[, argument2_name[, ...]]]
//#statement_type type (procedure; inline; function, f_type (byte, word, long, single, string))
//#argument argument_name, type (const xx, byte, byte system xx, word, word system xx, long, long system xx, single, string), passing_type
//the default type of the system variables can not be changed with #argument
//passing_type (byval, byval allowed_constant_range, byref, byrefout) for statement_type procedure and inline
//passing_type (byval, byval allowed_constant_range) for statement_type function
//allowed_constant_range can contain arithmetic expressions in brackets, like 0-[EEPROM_Num-1]

//used to define parameters and statements that are not available or not applicable for the current item devices
//#parameter n/a, parameter_name
//#statement n/a, statement_name

//used to define alternative names for the parameter and statement names defined in the library group
//#alias_for <library_defined_element_name>, <new_alternative_name>
//used to define code variations among processors when only one or more register names in the code should be replaced with their alternatives
//#alternate_reg_name <register_name_used_in_code>, <alternate_register_name>

//code section can contain both inline assembler and basic language lines of code
//symbol and const parameters can be used directly in the assembler lines of code
//calculate[] or calc[] macro is available for double precision calculations
//calculate[] must be used to enter the parameter value in the basic code, will be replaced with the value of the parameter
//calculate[] can be used to perform one arithmetic operation, will be replaced with the value of the result
//calc[] arithmetic operators: +, -, *, /, % (modulus or remainder operator), \ (division returning integer result)
//calc[] macros can be nested
//reg_addr[] macro will be replaced with the register address; if not found, -1 is returned
//Clock_Freq in MHz is available as an argument
//Flash_Num and EEPROM_Num (total number of memory locations) are available as arguments
//int_val() and abs_val() functions are available for getting integer or absolute values of the argument
//int_val() and abs_val() can be used on one or both arguments in calc[] expression, and/or on the whole expression

//#if [], #endif macro is available
//#if comparison operators: ==,<>,<,>,<=,>=
//#if [] macro can be used without #endif when followed by one operation in the same line
//for example: #if [libtemp3 > 0.5] #math libtemp2 = calc[libtemp2 + 1]
//for example: #if [ADC_Ref > 3] sbr R30,0x10

//'const' statement argument type can be used to load the numeric value into one of the library engine temp variables libtemp0-libtemp99
//for const arguments only 'byval' and 'byval allowed_constant_range' passing types are allowed
//#math directive is used to assign numeric value or the result of calc[] expression to one libtempxx variable
//for example: #math libtemp8 = calc[calc[Clock_Freq * 1000000] / calc[calc[libtemp2 + 1] * 4]] //exact baud rate achieved
//#if macro also works with libtemp0-libtemp99 variables
//desired info can be inserted as a comment in the generated assembler source with the #echo directive
//#echo is followed by a string that can contain temp variable names to be replaced with their numeric values
//for example: #echo "exact baud rate achieved = libtemp8; bit period = libtemp7µs; baud rate error = libtemp6%"

//when calling 'procedure' type statements with one 'byval' byte argument declared, library engine will accept multiple comma-separated arguments
//for that purpose #statement_accept_multiple_arguments directive should be placed after #statement_type
//variables are passed byte by byte starting from the lowest byte; string constants can be used, along with symbolic constants Qt, CrLf, Cr, Lf
//decimal representation of a variable can be passed to the procedure by using the # prefix before the variable name
//#break_for_string_argument directive
//for string variables and decimal representations (argument in the form #variable_name)
//library engine will load XHL register (X-register) with address of zero-terminated string prepared in memory
//and call the procedure at position of the #break_for_string_argument directive in the procedure code
//when calling 'procedure' type statements with one 'byrefout' byte argument declared, library engine will also accept multiple comma-separated arguments
//variables are filled in byte by byte starting from the lowest byte

//be careful that basic statements like WaitUs also make use of the system registers and can alter those values

//special function registers are declared in basic code as byte variables; if needed, this byte variable type can be changed with #redim directive
//#redim register_name new_type (byte, word, long, single)

//list of available compiler system byte registers: R16, R17, R18, R19, R20, R21, R22, R23, R24, R25
//list of available compiler system byte registers: R26 (XL), R27 (XH), R28 (YL), R29 (YH), R30 (ZL), R31 (ZH)
//list of available compiler system byte registers: R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15
//list of available compiler system word registers: R1716, R1918, R2120, R2322, R2524
//list of available compiler system word registers: R2726 (XHL), R2928 (YHL), R3130 (ZHL)
//list of available compiler system word registers: R1R0, R3R2, R5R4, R7R6, R9R8, R1110, R1312, R1514
//list of available compiler system long registers: R19181716, R23222120, R27262524, R31302928
//the compiler holds zero value in R15 system register
//the compiler uses R16 system register as working register
//registers R16-R31 are much better covered by AVR instruction set