CL3-E CANopen/USB Technical Manual

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.

Note
If the NanoJ program should not return the computing time for an excessive time, it is ended by the operating system. In this case, the number "4" is entered in the status word at object 2301h of the VMM; the number "5" (timeout) is noted in the VMM error register at object 2302h.

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:

  1. Read values from the object directory and copy them into the Inputs and Outputs areas.
  2. Execute the user program.
  3. 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:

  1. Unpack "NanoJEasyV2.zip" into a directory of your choice.

  2. Launch the program with the file "NanoJEasy.exe".

Programming of user programs

User program structure

A user program consists of at least two instructions:

  1. The #include "wrapper.h" preprocessor instruction
  2. 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

  1. 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

  2. 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 od_write() and od_read() functions are better suited for single access to objects (see the "Access to the object directory" section).

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:

map U16 controlWord as output 0x6040:00
map U08 statusWord as input 0x6041:00
map U08 modeOfOperation as inout 0x6060:00 

#include "wrapper.h"
void user() { 
  [...] 
  Out.controlWord = 1; 
  U08 tmpVar = In.statusword; 
  InOut.modeOfOperation = tmpVar; [...] 
}

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:

map U16 controlWord as output 0x6040:00 
#include " wrapper.h" 
void user() {
  [...]
  Out.controlWord = 1; 
  [...] 
  // the value is overwritten by the mapping 
  od_write(0x6040, 0x00, 5 ); 
  [...]
}

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:

  • The function od_write writes the value "5" in object 6040h:00h.

  • At the end of the 1-ms cycle, the mapping is written that also specifies object 6040h:00h , though with the value "1".

  • This means - from the user's perspective - the od_write command is without effect.

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 a od_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 with yield().

  • 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.

▶   next

Contents