Justina (short for 'Just an Interpreter for Arduino') was developed and built around a few objectives.
- On top of the list: simplicity for the user. Justina is a structured symbolic language, and it’s easy to learn.
- Equally important: Justina was built with Arduino in mind - more specifically, 32-bit Arduino’s: boards with a SAMD processor (like the nano 33 IoT), the nano ESP32 and the nano RP2040.
- High quality and complete documentation: the Justina user manual completely covers functionality, including more technical aspects like integration with c++, system callbacks, examples and so on.
Justina does not impose any requirements or restrictions related to hardware (pin assignments, interrupts, timers,... - it does not use any), nor does it need to have any knowledge about it for proper operation. The Justina syntax has been kept as simple as possible. A program consists of statements. A statement either consists of
- a single expression (always yielding a result).
- a command, starting with a keyword, optionally followed by a list of arguments (expressions). Such a statement is called a command, because it ‘does’ something without actually calculating a result. Arguments are separated by comma's; the argument list is not surrounded by parentheses.
Because Justina is an interpreted language, a Justina program is not compiled into machine language but it is parsed into tokens before execution. Parsing is a fast process, which makes Justina the ideal tool for quick prototyping . Once it is installed as an Arduino library, call Justina from within an Arduino c++ program and you will have the Justina interpreter ready to receive commands, evaluate expressions and execute Justina programs. You can enter statements directly in the command line of the Arduino IDE (the Serial monitor by default, a Terminal app (e.g., YAT), a TCP IP client,...) and they will immediately get executed, without any programming.
Excited to get hands-on with Justina? Feel free to jump ahead to the section ‘Getting started with Justina: a simple setup guide’ for immediate practical experience. However, don’t miss out on the valuable insights in the preceding sections—they lay the groundwork for a smooth and informed journey with Justina.
A basic example, without programmingWe will first set the console display width for calculation results to 40 characters wide (by default, it's set to 64) and set the angle mode to degrees. We'll then define Arduino pin 17 as an output and write a HIGH value to the pin. Finally, we'll calculate the cosine of 60°. This is all done by typing the next 3 lines in the command line of the IDE Serial Monitor (each time followed by ENTER):
dispWidth 40; angleMode DEGREES;
pinMode( 17, OUTPUT); digitalWrite(17, HIGH);
cos(60);
The result will look like this:
Statements you type are echoed after the Justina prompt (“Justina>“), providing a history of what has been entered. Multiple statements can be entered on a single line, separated by semicolons. The result of the last expression entered in the command line is printed on the next line. In this example: both digitalWrite and digitalRead are functions, digitalWrite returning the value written to the pin (1 is the value of predefined constant HIGH) and digitalRead reading back that same value from the pin.
A few highlights- More than 250 built-in functions, commands and operators, 70+ predefined symbolic constants.
- More than 30 functions directly targeting Arduino IO ports and memory, including some new.
- Extended operator set includes relational, logical, bitwise operators, compound assignment operators, pre- and postfix increment operators.
- Integrated TCP/IP connection setup / maintenance function library.
- Two angle modes: radians and degrees.
- Scalar and array variables.
- Floating-point, integer and string data types.
- Perform integer arithmetic and bitwise operations in decimal or hexadecimal number format.
- Display settings define how to display calculation results: output width, number of digits / decimals to display, alignment, base (decimal, hex), …
- Input and output: Justina reads data from / writes data to multiple input and output devices (connected via Serial, TCP IP, SPI, I2C...). You can even switch the console from the default (typically Serial) to another input or output device (for instance, switch console output to an OLED screen).
- With an SD card breakout board connected via SPI, Justina creates, reads and writes SD card files...
- In Justina, input and output commands work with argument lists: for instance, with only one statement, you can read a properly formatted text line from a terminal or an SD card file and parse its contents into a series of variables.
- Write program functions with both mandatory and optional parameters that accept scalar and array arguments. When calling a function, variables (including arrays) are passed by reference, while constants and the results of expressions are passed by value.
- Variables or constants declared within a program can be global (accessible throughout the Justina program), local (accessible only within a Justina function), or static (accessible within one Justina function, with the value preserved between calls).
- Variables not declared within a program but by a user from the command line are called user variables (or user constants).
- Programs have access to user variables, and users have access to global program variables from the command line. User variables preserve their values when a program is cleared or another program is loaded.
- Parsing and execution errors are clearly indicated, with error numbers identifying the nature of the error.
- Error trapping, if enabled, ensures that an error will not terminate a program; instead, the error can be handled in code (either in the procedure where the error occurred or in a ‘caller’ procedure). It’s even possible to trap an error in the command line.
- If Justina program files are available on an SD card (assuming an SD card board is connected), they can be directly read and parsed from there. Alternatively, your computer can send Justina program files to an Arduino, provided you use a Terminal app that can send files (the Arduino IDE does not provide that functionality; however, a very good—and free—choice is YAT Terminal).
You can use any text editor to write and edit your programs. But you might consider using Notepad++ as text editor, because a specific 'User Defined Language' (UDL) file for Justina is available to provide Justina syntax highlighting.
When a program is stopped (either by execution of the ‘stop’ command, by user intervention or by an active breakpoint) debug mode is entered. You can then single step the program, execute statements until the end of a loop, a next breakpoint…
Breakpoints can be activated based on a trigger expression or a hit count. You can also include a list of ‘view expressions’ for each breakpoint, and Justina will automatically trace specific variables or even expressions, letting you watch their values change as you single step through the program or a breakpoint is hit.
While a procedure is stopped in debug mode, you can also manually review the procedure’s local and static variable contents or view the call stack.
If enabled, system callbacks allow the Arduino program to perform periodic housekeeping tasks beyond the control of Justina (e.g., maintaining a TCP connection, producing a beep when an error is encountered, aborting, or stopping a Justina program...). For that purpose, a set of system flags passes information back and forth between the main Arduino program and Justina at regular intervals (without the need for interrupts).
Time-critical user routines, functions targeting specific hardware and functions extending Justina functionality in general can be written in C++, given an alias and 'registered' (using a standard mechanism), informing Justina about the minimum and maximum number of arguments and the return type.
Once registered, these C++ functions can be called just like any other Justina function, with the same syntax, using the alias as function name and passing scalar or array variables as arguments.
A number of C++ example files are provided in the Arduino library folder 'examples'. These examples demonstrate topics such as calling Justina within your C++ sketch; using system callbacks to regularly execute specific tasks in the background (e.g., TCP/IP connection maintenance).
Other examples demonstrate how to write Justina user C++ functions, how to put them in a Justina user C++ 'library' file and how to add additional IO channels, such as OLED displays or a TCP/IP connection, next to Serial.
Justina language example files (Justina programs) are available in Arduino library folder 'extras/Justina_language_examples'. These text files obey the 8.3 file format, to make them compatible with the (optional) Arduino SD card file system. Also, they all have the '.jus' extension: opening these files in Notepad++ will automatically invoke Justina language highlighting (if the Justina language extension is installed).
Examples include an 'auto start' program, a recursive method to calculate factorials, setting up extra IO channels, reading and writing SD card files and more.
One example involves setting up an Arduino to function as a web server. When a browser establishes a connection to this server, it is served a webpage that features a fully operational scientific calculator (see figure below).
Start by installing the Justina library, named ‘ Justina interpreter ’, from the Arduino library manager. In the Arduino IDE:
- select 'Tools -> Manage Libraries' and look for 'Justina interpreter' (filter the library list by "Justina"). Click 'Install', to install library 'Justina interpreter'.
- select the correct board and port. Justina has been tested with the Arduino nano 33 IoT, the Arduino nano RP2040 and the Arduino nano ESP32. My preference: the nano ESP32 board. Compilation is relatively fast (much faster than with the nano RP2040 board - something you will get to appreciate if you have multiple test-debug-correct-compile cycles) and it has a large memory (larger than the 32K RAM of the nano 33 IoT).
Now let's immediately try a small Arduino program. It will simply call Justina and stay there (until we tell it to return to the calling Arduino program).
#include "Justina.h"
Justina justina; // create Justina_interpreter object with default values
// -------------------------------
// * Arduino setup() routine *
// -------------------------------
void setup() {
Serial.begin(115200);
delay(5000);
justina.begin(); // run interpreter (control will stay there until you quit) Justina)
}
// ------------------------------
// * Arduino loop() routine *
// ------------------------------
void loop() {
// empty loop()
}
No need to copy this program: it is provided as one of the Justina library examples.
- In the Arduino IDE, select 'File -> Examples -> (scroll down to section 'Examples from Custom Libraries') Justina_interpreter -> Justina_easy'
A sketch 'Justina_easy.ino' will open. It contains the same code as pictured above (and a lot of comments, too).
- Compile and upload your sketch.
When done, your Serial Monitor will show:
That's it!
Control is now within Justina (and will stay there until you execute the 'quit' command).
Your first steps with JustinaTo begin your journey with Justina, we will type a few statements in the command line of the Serial monitor and check out the results.
To begin, type the following command and press ENTER :
var subTotal = 0, total = 0.;
Command var is a non-executable command: it defines 2 variables and initialises them both to to zero. This initialization is done during parsing. But there's a difference: the data type of 'subTotal' is set to integer, whereas the data type of 'total' is set to floating point (values containing a decimal point or an exponent are considered floating point numbers). Arguments are always separated by a comma. The statement is terminated by an (optional) semicolon.
Now type
sin(PI / 2); total = 5 + 7 + (subTotal = 6 + 2);
This time, the line entered contains 2 statements (both being simple expressions, not commands), separated by a (mandatory) semicolon.
If statements entered in the command line contain at least one simple expression (not a command), then Justina will print the result of the last expression to the Serial Monitor (command statements do not produce a result). This is very useful if you want to use Justina as a scientific calculator .
In this example, the last (second) expression contains a sub-expression between parentheses. The result of this sub-expression will first be assigned to variable 'subTotal', which will now store integer value '8'. Then, this value will be added to the other values of the main expression and the result will be assigned to variable 'total' (note that this will change the data type of variable 'total' to integer). Finally, this result ('20') will be printed right-aligned within the set display width (specific commands are available to change these settings).
Because in this example, the result of the first expression is not assigned to a variable, its result will be lost.
Let's rearrange the second statement into 2 separate statements: type
subTotal = 6 + 2; total = subTotal + 5 + 7;
Obviously, this leads to the same result as previous example.
To create a constant (a variable initialized during parsing with a value that can not be changed afterwards), use the const keyword instead of var.
Example: type
const logoFile = "/justina/images/jus_logo.jpg";
Constant variable logoFile will be initialized as a string, containing the path to an SD card file (supposing an SD card board is connected).
Let's create one more variable: an array. Type
var thisIsAnArray(3, 2, 10) = 0.
This defines a 60 element floating point array, initialized to zero. In contrast to scalar variables, the data type of an array (or any of its element) is fixed. But numeric values assigned to a numeric array will be converted to the data type of the array.
To list all currently defined variables, type
listVars;
Justina will print a list of all user variables with data type, current value or array element count etc.)
Now, type the following two lines
var i = 0;
for i = 1, 100; if i == 50; printLine CONSOLE, "We will stop halfway: current value=",
i; break; end; end;
This ' immediate mode program ' contains a number of control statements: they control the order in which other statements are executed.
- The for and outer end keywords define a control structure that executes the statements within it multiple times, controlled by variable 'i' (a 'loop').
- The if and inner end form a control structure containing a test clause. If the test results is 'true', the inner statements are executed.
- The break statement exits the for...end control structure.
Command printLine CONSOLE, "We will stop halfway: current value = ", i; printsa message to the CONSOLE when value '50' is reached (this command will typically be used from inside a 'real' program).
Argument 'CONSOLE' (which is optional) directs output to the Serial Monitor (because by default, the Serial Monitor is set as the console).
Using the same command, you could print to additional I/O devices, if defined (a TCP/IP terminal, an SPI or I2C OLED display etc.).
The printLine command considers the output device it is printing to, as a terminal. Printing is left-aligned.
Because in this last example, the statements entered do not contain any simple expression (only command statements), a 'last result' is not printed.
If you executed all these examples, the Serial Monitor output should look like this:
Exploring Justina further can be both rewarding and enjoyable. The library comes with a number of instructive examples, from easy to more advanced. The user manual is a very helpful resource, offering detailed guidance on Justina’s commands and functions. When you’re ready, you might find the sections on extending I/O, SD cards, programming, and creating your own Justina library extensions particularly useful. You might even discover a new passion for tinkering with Justina.
Comments