使用 NanoJ 编程
NanoJ 是一种类似 C 或 C++ 的编程语言。NanoJ 集成在 Plug & Drive Studio 软件中。如需了解更多信息,请参阅网站 cn.nanotec.com 上的文档 Plug & Drive Studio:快速入门指南。
NanoJ 程序
NanoJ 程序在固件内形成受保护的运行环境。用户可以在其中创建自己的进程。随后,这些进程可以通过在对象目录中读写条目等操作触发控制器中的函数。
NanoJ 程序可通过保护机制避免固件崩溃。在最坏的情况下,执行中断时会将故障代码存储在对象目录中。
如果 NanoJ 程序已加载到控制器,则控制器打开或重启后将自动执行此程序。
可用计算时间
NanoJ 程序以 1 ms 脉冲的周期循环接收计算时间(见下图)。由于计算时间在固件中断和系统函数中有所损耗,仅有约 30% – 50% 的计算时间可用于用户程序(取决于工作模式和应用)。在此时间内,用户程序必须运行此循环,并完成循环或通过调用 yield()
函数生成计算时间。在前一种情况下,用户程序将在下一个 1 ms 循环开始时重启;后者则会使程序使用 yield()
函数的相关命令继续进行下一个 1 ms 循环。
如果 NanoJ 程序需要的时间多于分配时间,则程序将结束,并将故障代码存储到对象目录。
sin
函数计算正弦值。4
;而在对象 2302h 的错误寄存器中标注数字 5
(超时),参见2301h NanoJ Status和2302h NanoJ Error Code。沙盒
可使用处理器特定功能生成所谓的沙盒。在沙盒中使用时,用户程序只能访问专门分配的存储区和系统资源。例如,通过 MPU 故障确认直接写入处理器 IO 寄存器的尝试,用户程序终止,并将相应的故障代码存储到对象目录。
NanoJ 程序 – 通信可能性
NanoJ 程序可能通过多种方式与控制器通信:
- 使用 PDO 映射读取和写入 OD 值
- 使用系统调用直接读取和写入 OD 值
- 调用其他系统调用(如写入调试输出)
通过 PDO 映射以变量形式提供用户程序的 OD 值。用户程序收到 1 ms 时间片之前,固件将这些值从对象目录传送到用户程序的变量中。用户程序收到计算时间后,可立即将这些变量作为常规 C 变量进行操作。在时间片结束时,固件会自动将新值复制回相应的 OD 条目。
为了优化性能,定义了三种映射类型:输入、输出以及输入/输出(In、Out、InOut)。
- 输入映射只能被读取;不会被传送回对象目录。
- 输出映射只能被写入。
- 而输入/输出映射则可被读取和写入。
可通过 GUI 读取和检查对象 2310h、2320h 和 2330h 的设定映射。每个映射最多可以包含 16 个条目。
在 NanoJEasy 中通过链接器部分的规范控制将变量存储在输入、输出或数据范围中。
执行 NanoJ 程序
执行循环时,NanoJ 程序基本包含以下三个有关 PDO 映射的步骤:
- 从对象目录读取值并将其复制到输入和输出区
- 执行用户程序
- 将数据从输出和输入区复制回对象目录
复制过程的配置基于 CANopen 标准。
此外,还可通过系统调用访问对象目录的值。这通常比较慢;因此首选映射。映射数量是有限的(In/Out/InOut 中各 16 个条目)。
在NanoJ 程序中的系统调用一章中列出了可用的系统调用。
od_write()
访问给定的 OD 值。如果两种方式同时使用,则系统调用无效。NanoJ 程序 – OD 条目
在对象范围 2300h 至 2330h 中控制和配置 NanoJ 程序(参见2300h NanoJ Control)。
OD 索引 | 名称和说明 |
---|---|
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 |
示例:
例如,可通过以下顺序选择和启动 TEST1.USR 用户程序:
- 检查条目 2302h 中是否有故障代码。
-
如果无错误:
通过写入对象 2300h,位 0 =“1”,启动 NanoJ 程序
注: NanoJ 程序启动最多可能需要 200 ms。 - 检查条目 2302h 中是否有故障代码以及对象 2301h,位 0 =“1”。
要停止正在运行的程序:将位 0 值 =“0”写入条目 2300h。
NanoJ 程序的结构
用户程序至少包含两条指令:
- 预处理器指令
#include "wrapper.h"
void user(){}
函数
要执行的代码可存储在 void user()
函数中。
main.cpp
允许使用,文件名 aLongFileName.cpp
则不允许使用。- 没有
新
运算符 - 没有构造函数
- 不会在代码外对全局变量进行初始化
示例:
在 void user()
函数中初始化全局变量:
unsigned int i;
void user(){
i = 1;
i += 1;
}
以下分配不正确:
unsigned int i = 1;
void user() {
i += 1;
}
NanoJ 程序示例
此示例显示了在对象 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
如需查看其他示例,请访问:cn.nanotec.com。
NanoJ 程序中的映射
通过此方法,可直接将 NanoJ 程序中的变量与对象目录中的条目相关联。映射的创建必须位于文件开头,甚至在 #include "wrapper.h"
指令之前。可以在映射上方添加注释。
- 如果需要频繁访问对象目录中的对象,如控制字 6040h 或状态字 6041h,请使用映射。
od_write()
和od_read()
函数更适合单次访问对象,参见访问对象目录。
映射声明
映射声明的结构如下:
map <TYPE> <NAME> as <input|output|inout> <INDEX>:<SUBINDEX>
其中:
-
<TYPE>
变量的数据类型;U32、U16、U08、S32、S16 或 S08。
<NAME>
在用户程序中使用的变量名称。
-
<input|output|inout>
变量的读写权限:可将变量声明为
输入、输出
或inout
。这定义了变量是否可读(输入
)、可写(输出
)或可读写 (inout
),同时定义了在程序中对其进行寻址时必须使用的结构。 -
<INDEX>:<SUBINDEX>
要在对象目录中映射的对象的索引和子索引。
根据定义的写入和读取方向,在用户程序中通过以下三种结构之一对每个声明的变量进行寻址:In、Out 或 InOut。
映射示例
映射示例以及相应的变量访问:
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;
[...]
}
od_write()
中可能存在错误
错误可能来自使用对象目录中对象的 od_write()
函数(参见NanoJ 程序中的系统调用)进行的写入访问,此函数同时创建为映射。以下代码不正确:
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
[...]
}
od_write(0x6040, 0x00, 5
);
命令所在的行不起作用。如简介中所述,在每一毫秒结束时,会将所有映射复制到对象目录。
这将导致以下结果:
od_write
函数将值5
写入对象 6040h:00h。- 在 1 ms 循环结束时,映射也指定了对象 6040h:00h,但写入值
1
。 - 因此,从用户的角度来看,
od_write
命令没有任何用途。
NanoJ 程序中的系统调用
借助系统调用,可以直接从用户程序调用固件中集成的函数。由于只能在沙盒的受保护区域直接执行代码,因此通过所谓的 Cortex-Supervisor-Call(Svc 调用)实现此操作。调用函数时会触发中断。因此固件可能会临时允许在沙盒以外执行代码。用户程序的开发人员不必担心此机制,因为对于这些函数,可以像正常 C 函数一样调用系统调用。只有 wrapper.h 文件仍需像往常一样进行集成。
访问对象目录
无效 od_write(U32 索引、U32 子索引、U32 值)
此函数将传送的值写入对象目录中的指定位置。
索引 | 要写入对象目录中的对象的索引 |
子索引 | 要写入对象目录中的对象的子索引 |
值 | 要写入的值 |
od_write()
后随 yield()
一同传递处理器时间。立即将该值写入 OD。但是,要使固件能够触发依赖于此的操作,则必须接收计算时间。这反过来又意味着用户程序必须通过 yield()
结束或中断。U32 od_read(U32 索引、U32 子索引)
此函数读取对象目录指定位置的值,并返回该值。
索引 | 要在对象目录中读取的对象的索引 |
子索引 | 要在对象目录中读取的对象的子索引 |
输出值 | OD 条目的内容 |
yield()
相关联。示例
while (od_read(2400,2) != 0) // wait until 2400:2 is set
{ yield(); }
过程控制
void yield()
此函数将处理器时间返回操作系统。在下一个时间片中,程序将在调用后的位置继续执行。
void sleep (U32 ms)
此函数将处理器时间按指定的毫秒数返回操作系统。然后用户程序在调用后的位置继续执行。
ms | 等待时间,单位为毫秒 |