Programming with NanoJ
Introduction
The VMM (Virtual Machine Monitor) is a protected execution environment within the firmware. The user can load his or her own programs ("User Program") in this environment . These can trigger functions in the motor controller, for example by reading or writing entries in the object directory.
The use of protective mechanisms makes it impossible for the user programs to cause the actual firmware to crash. In the worst case, the user program alone is aborted with an error code stored in the object directory.
Once loaded on the controller, the user program will start autocratically after power up or restart of the controller.
Available computing time
A user program receives computing time in a 1-ms cycle (see also the following diagram). Because the
firmware loses computing time due to interrupts and system functions, only about 30% - 50% of this
time is available to the user program (depending on the operating mode and application case). During
this time, the user program must have completed its operations and must either have closed or have
yielded the computing time with the yield()
function. In the first case, the user
program is started again when the next 1-ms cycle begins; in the second case, the program is
continued at the command following the yield()
in the next 1-ms cycle.
If the system detects that the user program requires more than the time assigned to it, it is closed
and an error code is entered in the object directory. When developing user programs, therefore, the
runtime behavior of the program must be carefully checked, especially in the case of time-intensive
tasks. Therefore, it is advisable to use tables, instead of calculating a sinus value from a
sin
function.
Interaction of the user program with the motor controller
Communication options
A user program has numerous options for communicating with the motor controller:
- Reading and writing of object dictionary values per PDO mapping
- Direct reading and writing of object dictionary values via system calls
- Calling up of other system calls (e.g. write debug output)
Via a PDO mapping, object dictionary values in the form of variables are made available to the user program. Before a user program receives its 1-ms time slot, the firmware transfer the values for this from the object dictionary to the variables of the user program. When the user program now receives computing time, it can manipulate these variables like the usual C variables. At the end of the time slot, the new values are automatically copied into the respective object dictionary entries by the firmware.
To optimize the performance, 3 types of mappings are defined: Input, output and input/output (In, Out, InOut). Input mappings can only be read and are not transferred back into the OD. Output mappings can only be written. Input/Output Mappings, on the other hand, permit reading and writing.
The set mappings can be read out and checked via the web interface at objects 2310h, 2320h, and 2330h. For each mapping, a maximum of 16 entries is allowed.
The specification of the linker section is used to control in NanoJ Easy whether a variable is stored the under input, output, or data range.
Execution of a VMM cycle
In summary, the procedure for the execution of a VMM cycle with respect to the PDO mapping consists of the following three steps:
- Read values from the object directory and copy them into the Inputs and Outputs areas.
- Execute the user program.
- Copy values from the Outputs and Inputs areas back to the object directory.
The configuration of the copy procedures is in line with the CANopen standard.
In addition, it is also possible to access system calls via the object directory. In general, this is considerably slower and therefore mappings should be given preference. However, the number of mappings is limited (16 entries each in In/Out/InOut). Therefore, it is advisable to map frequently-used and changed object dictionary values and to access less frequently used object dictionary entries by system call. A list of available system calls can be found in the "System calls" section.
Note |
---|
It is strongly advised to access one single object dictionary value either by mapping or
system call with od_write() . If both are used at the same
time, system call will not have any effect. |
Object dictionary entries for controlling and configuring the VMM
Object dictionary entries
The VMM is controlled and configured by means of object dictionary entries in the object range 2300h to 2330h.
object dictionary Index | Name |
---|---|
2300h | NanoJ Control |
2301h | NanoJ Status |
2302h | NanoJ Error Code |
2310h | NanoJ Input Data Selection |
2320h | NanoJ Output Data Selection |
2330h | NanoJ In/output Data Selection |
Example
To select and start the "TEST1.USR" user program, the following sequence can be used for instance:
- Rename the file "
TEST1.USR
" to "vmmcode.usr
". - Copy the file "
vmmcode.usr
" via USB to the controller. - Start the NanoJ program with setting the object 2300h, Bit 0 to "1". or restart the controller.
- Check the object 2302h if an error has occurred and check the objects 2301h, Bit 0 to be set to "1" (vmm is actually running).
Note |
---|
Due to limitations in the USB implementation, after a restart of the
controller the file "vmmcode.usr " will get set to
a size of 16kB and the creation date is reset to 13.03.2012. |
To stop a running program: Write the bit 0-value = "0" to the entry 2300h.
NanoJ Easy V2
Installation and use
Introduction
As an alternative to NanoIP, a user program can also be programmed, uploaded, and controlled with the NanoJ Easy V2 software.
Installation
Proceed as follows for installation:
-
Unpack "NanoJEasyV2.zip" into a directory of your choice.
-
Launch the program with the file "NanoJEasy.exe".
Programming of user programs
User program structure
A user program consists of at least two instructions:
- The
#include "wrapper.h"
preprocessor instruction - The
void user(){}
function
The code to be executed can then be stored in the
void user()
function.
The file names of the user programs must not be longer than eight characters and contains three characters in the extension; for example, "main.cpp" is admissible while "alongerfilename.cpp" is not.
Example
Programming a square wave signal in the object 2500h:01h
-
Copy the following text to the NanoJ Easy editor and store this file under the name "main.cpp".
// 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
-
When the program has been properly translated:
Structure of a mapping
Introduction
This method can be used to directly link a variable in the NanoJ program with an entry in the object directory. The mapping must be created at the beginning of the file - before the
#include "wrapper.h"
instruction. Only a comment above the mapping is allowed.
Tip |
Use mapping if you frequently need access to an object in the object directory, such as the control word 6040h or status word 6041h. The |
Declaration of the mapping
The declaration of the mapping is structured as follows:
map <TYPE> <NAME> as <input|output|inout> <INDEX>:<SUBINDEX>
The following applies:
-
<TYPE>
The data type of the variable, i.e. U32, U16, U08, S32, S16 or S08.
-
<NAME>
The name of the variable that is later used in the user program.
-
<input|output|inout>
The write and read authorization of a variable: A variable can either be declared as input, output, or inout. This defines whether a variable is readable (input), writable (output) or both (inout) and the structure by which it needs to be addressed in the program.
-
<INDEX>:<SUBINDEX>
Index and sub-index of the object being mapped in the object directory.
Every 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.
Example of a mapping
Example of a mapping and the associated variable access methods:
|
Potential error source
A potential source of error is a write access by means of the
od_write()
function on an object in the object directory that was also created as a mapping. The code shown below is
faulty:
|
The line with the command
od_write(0x6040, 0x00, 5 );
is without effect. As described in the introduction, all mappings are copied into the object directory at the end of each millisecond.
The following procedure is therefore derived:
System calls
Introduction
With system calls, it is possible to call up functions integrated in the firmware directly in a user program. Because a direct code execution is only possible in the protected area of the sandbox, this is implemented via so-called Cortex-Supervisor-Calls (Svc Calls). An interrupt is triggered when the function is called and the firmware thus has the possibility of temporarily allowing a code execution outside of the sandbox. Developers of user programs do not need to worry about this mechanism. For them, the system calls can be called up like normal C functions. Only the "wrapper.h" file must be integrated as usual.
Access to the object directory
-
void od_write(U32 index, U32 sub-index, U32 value)
This function writes the transferred value to the specified point in the object directory.
index Index of the object being written in the object directory sub-index Sub-index of the object being written in the object directory value Value to be written Note It is strongly advised, to generate processor time with yield()
after aod_write()
has been called up. The value is immediately written to the OD. However, to enable the firmware to trigger dependent actions, it must receive computing time and therefore the user program must have been ended or stopped withyield()
. -
U32 od_read(U32 index, U32 sub-index)
This function reads the value at the specified point in the object directory and returns it.
index Index of the object being read in the object directory sub-index Sub-index of the object being read in the object directory Return value Content of the object dictionary entry Note Active waiting for a value in the object directory 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 process time to the operating system. The program is resumed in the next time slot at the same location.
-
void sleep(U32 ms)
This function returns the process time to the operating system for the specified number of milliseconds. The user program is then continued at the location following the call.
ms Wait time in milliseconds
Debug output
The following functions output a value in the debug console. They differ only in the data type of the parameter being output.
- 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 are read out from there by the web interface. This object dictionary entry has the index 2600h and is 64 characters long. The sub-index 0 always contains the number of characters already written. |
If the buffer is full, VmmDebugOutputxxx()
initially fails; execution of the user program is
discontinued and it stops at the location of the debug output. The program is not resumed until
the web interface has read out the buffer and reset the sub-index 0;
VmmDebugOutputxxx()
returns to the user program.
Debug outputs therefore may only be used during the test phase in the development of a user program.