CPB Modbus TCP Technical Manual

Programming with NanoJ

NanoJ is a programming language similar to C or C++. NanoJ is integrated in the Plug & Drive Studio 3 software. You can find further information in document Plug & Drive Studio 3: User Manual at us.nanotec.com.

NanoJ program

A NanoJ program makes a protected runtime environment available within the firmware. Here, the user can create his own processes. These can then trigger functions in the controller by, for example, reading or writing entries in the object dictionary.

Through the use of protective mechanisms, a NanoJ program is prevented from crashing the firmware. In the worst case, the execution is interrupted with an error code stored in the object dictionary.

If the NanoJ program was loaded on the controller, it is automatically executed after the controller is switched on or restarted, as long as you do not set bit 0 in object 2300h to "0".

Available computing time

A NanoJ program receives computing time cyclically in a 1 ms clock (see following figure). Because computing time is lost through interrupts and system functions of the firmware, only approx. 30% – 50% of computing time is available to the user program (depending on control mode and application). In this time, the user program must run through the cycle and either complete the cycle or yield the computing time by calling the yield() function. In the former case, the user program is restarted with the start of the next 1 ms cycle; the latter results in the program being continued on the next 1 ms cycle with the command that follows the yield() function.

If the NanoJ program needs more time than was allotted, it is ended and an error code set in the object dictionary.

Tip: When developing user programs, the runtime behavior must be carefully examined, especially for more time-intensive tasks. For example, it is therefore recommended that tables be used instead of calculating a sine value using a sin function.
Note:

If the NanoJ program does not yield the computing time after too long a time, it is ended by the operating system. In this case, the number 4 is entered in the statusword for object 2301h; in the error register for object 2302h, the number 5 (timeout) is noted, see 2301h NanoJ Status and 2302h NanoJ Error Code.

To keep the NanoJ program from stopping, you can activate AutoYield mode by writing value "5" in 2300h. In AutoYield mode, however, the NanoJ program is no longer real-time capable and no longer runs every 1 ms.

Protected runtime environment

Using process-specific properties, a so-called protected runtime environment is generated. A user program in the protected runtime environment is only able to access specially allocated memory areas and system resources. For example, an attempt to directly write to a processor IO register is acknowledged with an MPU Fault and the user program terminated with the corresponding error code in the object dictionary.

NanoJ program – communication possibilities

A NanoJ program has a number of possibilities for communicating with the controller:

  • Read and write OD values using PDO mapping
  • Directly read and write OD values via NanoJ functions
  • Call other NanoJ functions (e.g., write debug output)

The OD values of the user program are made available in the form of variables via PDO mapping. Before a user program receives the 1 ms time slot, the firmware transfers the values from the object dictionary to the variables of the user program. As soon as the user program receives computing time, it can manipulate these variables as regular C variables. At the end of the time slot, the new values are then automatically copied by the firmware back to the respective OD entries.

To optimize the performance, three types of mapping are defined: input, output, and input/output (In, Out, InOut).

  • Input mappings can only be read; they are not transferred back to the object dictionary.
  • Output mappings can only be written.
  • Input/output mappings, on the other hand, can both be read and written.

The set mappings can be read and checked via the GUI for objects 2310h, 2320h, and 2330h. Up to 16 entries are allowed for each mapping.

Whether a variable is stored in the input, output or data range is controlled in Plug & Drive Studio via the specification of the linker section.

NanoJ inputs and NanoJ outputs

To communicate with the NanoJ program via the respective interface, you can use the following objects:

  • 2400h NanoJ Inputs: Array with thirty-two S32 values for passing values to the NanoJ program
  • 2410h NanoJ Init Parameters: Array with thirty-two S32 values. This object can be stored, unlike 2400h.
  • 2500h NanoJ Outputs: Array with thirty-two S32 values, where the NanoJ program can store values that can be read out via the fieldbus

Executing a NanoJ program

When executing a cycle, the NanoJ program essentially consists of the following three steps with respect to the PDO mapping:

  1. Read values from the object dictionary and copy them to the input and output areas
  2. Execute a user program
  3. Copy values from the output and input areas back to the object dictionary

The configuration of the copy processes is based on the CANopen standard.

In addition, values of the object dictionary can be accessed via NanoJ functions. This is generally slower; mappings are therefore to be preferred. The number of mappings is limited (16 entries each in In/Out/InOut).

Tip: Nanotec recommends: Map OD entries that are used and changed frequently and use NanoJ function to access OD entries that are used less frequently.

A list of available NanoJ functions can be found in chapter NanoJ functions in the NanoJ program.

Tip: Nanotec recommends accessing a given OD value either by mapping or using a NanoJ function with od_write(). If both are used simultaneously, the NanoJ function has no effect.

NanoJ program – OD entries

The NanoJ program is controlled and configured in object range 2300h to 2330h (see 2300h NanoJ Control).

OD-Index Name and description
2300h 2300h NanoJ Control
2301h 2301h NanoJ Status
2302h 2302h NanoJ Error Code
2310h 2310h NanoJ Input Data Selection
2320h 2320h NanoJ Output Data Selection
2330h 2330h NanoJ In/output Data Selection

Example:

To start the TEST1.USR user program, the following sequence can, for example, be used:

  • Check entry 2302h for error code.
  • If no error:

    Start the NanoJ program by writing object 2300h, bit 0 = "1" or by restarting the controller.

    Note: It can take up to 200 ms for the NanoJ program to start.

  • Check entry 2302h for error code and object 2301h, bit 0 = "1".

To stop a running program: write entry 2300h with bit 0 value = "0".

Structure of a NanoJ program

A user program consists of at least two instructions:

  • the preprocessor instruction #include "wrapper.h"
  • the void user(){} function

The code to be executed can be stored in the void user() function.

Note: The file names of the user programs must not be longer than eight characters plus three characters in the suffix; file name main.cpp is permissible, file name aLongFileName.cpp is not permissible.
Note: In NanoJ programs, global variables may only be initialized within functions. It then follows:
  • No new operator
  • No constructors
  • No initialization of global variables outside of functions

Examples:

The global variable is to be initialized within the void user() function:

unsigned int i; 
void user(){
 i = 1;
 i += 1; 
} 

The following assignment results in an error during compilation:

unsigned int i = 1;
 void user() {
 i += 1; 
} 

NanoJ program example

The example shows the programming of a square wave signal in object 2500h:01h.

// file main.cpp
map S32 outputReg1 as inout 0x2500:1
#include "wrapper.h"

// user program
void user()
{
  U16 counter = 0;
  while( 1 ) 
  {
    ++counter; 

    if( counter < 100 )
     InOut.outputReg1 = 0;
    else if( counter < 200 )
      InOut.outputReg1 = 1;
    else
      counter = 0;

    // yield() 5 times (delay 5ms)
    for(U08 i = 0; i < 5; ++i )
      yield();
  }
}// eof

You can find other examples at us.nanotec.com.

Mapping in the NanoJ program

With this method, a variable in the NanoJ program is linked directly with an entry in the object dictionary. The creation of the mapping must be located at the start of the file here, even before the #include "wrapper.h" instruction.

Tip: Nanotec recommends:
  • Use mapping if you need to access an object in the object dictionary frequently, e. g., controlword 6040h or statusword 6041h.
  • The od_write() and od_read() functions are better suited for accessing objects a single time, see Accessing the object dictionary.

Declaration of the mapping

The declaration of the mapping is structured as follows:

map <TYPE> <NAME> as <input|output|inout> <INDEX>:<SUBINDEX>

Where:

  • <TYPE> 

    The data type of the variable; U32, U16, U08, S32, S16 or S08.

  • <NAME>

    The name of the variable as it is used in the user program.

  • <input|output|inout> 

    The read and write permission of a variable: a variable can be declared as an input, output or inout. This defines whether a variable is readable (input), writable (output) or both (inout) and the structure by means of which it must be addressed in the program.

  • <INDEX>:<SUBINDEX> 

    Index and subindex of the object to be mapped in the object dictionary.

Each declared variable is addressed in the user program via one of the three structures: In, Out or InOut depending on the defined write and read direction.

Note: A comment is only permitted above the respective mapping declaration in the code, not on the same line.

Example of mapping

Example of a mapping and the corresponding variable accesses:

// 6040h:00h is UNSIGNED16 
map U16 controlWord as output 0x6040:00
// 6041h:00h is UNSIGNED16
map U16 statusWord as input 0x6041:00

// 6060h:00h is SIGNED08 (INTEGER8)
map S08 modeOfOperation as inout 0x6060:00

#include "wrapper.h"

void user()
{
  [...]
  Out.controlWord = 1;
  U16 tmpVar = In.statusword;
  InOut.modeOfOperation = tmpVar;
  [...]
}

Possible error at od_write()

A possible source of errors is a write access with the od_write() function (see NanoJ functions in the NanoJ program) of an object in the object dictionary that was simultaneously created as mapping. The code listed in the following is incorrect:

map U16 controlWord as output 0x6040:00
#include " wrapper.h"
void user()
{
 [...]
  Out.controlWord = 1;
  [...]
  od_write(0x6040, 0x00, 5 ); // der Wert wird durch das Mapping überschrieben
  [...]
}

The line with the od_write(0x6040, 0x00, 5 ); command has no effect. As described in the introduction, all mappings are copied to the object dictionary at the end of each millisecond.

This results in the following sequence:

  1. The od_write function writes the value 5 in object 6040h:00h.
  2. At the end of the 1 ms cycle, the mapping is written that also specifies object 6040h:00h, however, with the value 1.
  3. From the perspective of the user, the od_write command thus serves no purpose.

NanoJ functions in the NanoJ program

With NanoJ functions, it is possible to call up functions integrated in the firmware directly from a user program. Code can only be directly executed in the protected area of the protected execution environment and is realized via so-called Cortex Supervisor Calls (Svc Calls). Here, an interrupt is triggered when the function is called, thereby giving the firmware the possibility to temporarily permit code execution outside of the protected execution environment. Developers of user programs do not need to worry about this mechanism – for them, the NanoJ functions can be called up like normal C functions. Only the wrapper.h file needs to be integrated as usual.

Accessing the object dictionary

void od_write (U32 index, U32 subindex, U32 value)

This function writes the transferred value to the specified location in the object dictionary.

index Index of the object to be written in the object dictionary
subindex Subindex of the object to be written in the object dictionary
value Value to be written
Note: It is highly recommended that the processor time be passed on with yield() after calling a od_write(). The value is immediately written to the OD. For the firmware to be able to trigger actions that are dependent on this, however, it must receive computing time. This, in turn, means that the user program must either be ended or interrupted with yield().

U32 od_read (U32 index, U32 subindex)

This function reads the value at the specified location in the object dictionary and returns it.

index Index of the object to be read in the object dictionary
subindex Subindex of the object to be read in the object dictionary
Output value Content of the OD entry
Note: Active waiting for a value in the object dictionary should always be associated with a yield().

Example

while (od_read(2400,2) != 0) // wait until 2400:2 is set
{ yield(); }

Process control

void yield() 

This function returns the processor time to the operating system. In the next time slot, the program continues at the location after the call.

void sleep (U32 ms)

This function returns the processor time to the operating system for the specified number of milliseconds. The user program is then continued at the location after the call.

ms Time to be waited in milliseconds

Debug output

The following functions output a value in the debug console. They differ with respect to the data type of the parameter to be passed.

bool VmmDebugOutputString (const char *outstring)
bool VmmDebugOutputInt (const U32 val)
bool VmmDebugOutputByte (const U08 val)
bool VmmDebugOutputHalfWord (const U16 val)
bool VmmDebugOutputWord (const U32 val) 
bool VmmDebugOutputFloat (const float val) 
Note:

The debug outputs are first written to a separate area of the object dictionary and read from there by the Plug & Drive Studio.

This OD entry has index 2600h and is 64 characters long, see 2600h NanoJ Debug Output. Subindex 00 always contains the number of characters already written.

If the buffer is full, VmmDebugOutputxxx() initially fails; execution of the user program ceases and it stops at the location of the debug output. Only after the GUI has read the buffer and after subindex 00 has been reset does the program continue and VmmDebugOutputxxx() returns to the user program.

Note: Debug outputs may therefore only be used during the test phase when developing a user program.
Note: Do not use the debug output if AutoYield mode is activated (see Available computing time).

Restrictions and possible problems

Restrictions and possible problems when working with NanoJ are listed below:

Restriction/problem Measure
If an object is mapped, e. g., 0x6040, the object is reset to its previous value every 1 ms. This makes it impossible to control this object via the fieldbus or the Plug & Drive Studio. Instead use od_read / od_write to access the object.
If an object was mapped as output and the value of the object was never defined before starting the NanoJ program, the value of this object may be random. Initialize the values of the mapped objects in your NanoJ program to ensure that it behaves deterministically.
The array initialization must not be used with more than 16 entries. Use constant array instead.
Too many local variables and arrays within functions may result in a stack overflow. Declare the variables globally. Memory requirements are monitored already during compilation; errors do not occur at runtime.
Functions that are too deeply nested may result in a stack overflow. Observe a maximum nesting depth of 2.
float must not be used with comparison operators. Use int instead.
double must not be used.
If a NanoJ program restarts the controller (either directly with an explicit restart or indirectly, e. g., through the use of the Reset function), the controller may fall into a restart loop that can be exited only with difficulty if at all.
math or cmath cannot be included.
▶   next

Contents