【Duktape】移植到单片机

Duktape 是一个可嵌入的 Javascript引擎,专注于可移植性和紧凑的占用空间。

Duktape 很容易集成到 C/C++ 项目中:添加 duktape.c、duktape.h 和 duk_config.h 三个文件到您的程序中,并使用 Duktape API 从 C 代码调用 ECMAScript 函数,反之亦然。

安装依赖

1
2
sudo apt update
sudo apt install -y wget make gcc

下载源代码并解压

官网下载地址

  1. 下载

    1
    wget https://duktape.org/duktape-2.6.0.tar.xz
  2. 解压

    1
    tar -xvJf duktape-2.6.0.tar.xz

编译运行命令行版本

  1. 编译

    1
    2
    cd duktape-2.6.0/
    make -f Makefile.cmdline
  2. 创建程序文件 fib.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function fib(n) {
    if (n == 0) { return 0; }
    if (n == 1) { return 1; }
    return fib(n-1) + fib(n-2);
    }

    function test() {
    var res = [];
    for (i = 0; i < 20; i++) {
    res.push(fib(i));
    }
    print(res.join(' '));
    }

    test();
  3. 运行脚本

    1
    ./duk fib.js

    应该输出了以下数据

    1
    0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181

生成移植用的程序

  1. 安装 python2 运行环境

    不太幸运,需要使用 python2 来运行脚本,包管理都已经不提供 pip2 了,只能从其他地方下载。

    1
    2
    3
    4
    5
    sudo apt install -y python2
    curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
    sudo python2 get-pip.py

    sudo python2 -m pip install PyYAML
  2. 创建配置文件 my_config.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
        ```

    3. 生成自检程序

    ```bash
    rm -rf /tmp/out

    # 一般问题检查
    python2 tools/configure.py --output-directory /tmp/out -DDUK_USE_ASSERTIONS -DDUK_USE_SELF_TESTS
    ls /tmp/out

    # 带有超时检查函数(可用于中断应用程序)
    python2 tools/configure.py --output-directory /tmp/out --fixup-line='extern duk_bool_t duk_exec_timeout_check(void*);' --fixup-line='extern duk_double_t duk_get_now_date(void);' -DDUK_USE_INTERRUPT_COUNTER -DDUK_USE_EXEC_TIMEOUT_CHECK=duk_exec_timeout_check

    # sudo mv /tmp/out /mnt/c/Users/szc31/Downloads/duk_out
  3. 将 /tmp/out 里的 duktape.c、duktape.h 和 duk_config.h 添加到单片机的工程(GCC)中,然后在 main.c 中包含 duktape.h 头文件,修改单片机堆栈大小 >2k 以下是调用方式:

    需要单片机的工程标准库中有 malloc、realloc、free 函数。

    1
    2
    3
    duk_context *ctx = duk_create_heap_default();
    duk_eval_string(ctx, "a = 0;");
    duk_destroy_heap(ctx);
  4. 修复嵌入式设备中无法使用的时间问题和中断时间的使用

    1. 打开duk_config.h

    2. 找到#elif defined(DUK_F_UNIX)下面的那个#else

    3. 修改#else里面的内容

      1. #define DUK_USE_DATE_NOW_TIME#define DUK_USE_DATE_TZO_GMTIME注释掉
      2. 并在#undef DUK_USE_DATE_PRS_STRPTIME上面添加#undef DUK_USE_DATE_NOW_TIME#undef DUK_USE_DATE_TZO_GMTIME
      3. 注释掉#include <time.h>,这个头文件在本示例里用不到了
    4. 找到#error no provider for DUK_USE_DATE_GET_NOW()语句将其注释掉,并在其下面添加以下程序

      1
      #define DUK_USE_DATE_GET_NOW(ctx) duk_get_now_date()
    5. 找到#error no provider for DUK_USE_DATE_GET_LOCAL_TZOFFSET()语句将其注释掉,并在其下面添加以下程序

      1
      2
      // 默认时区偏移(秒)
      #define DUK_USE_DATE_GET_LOCAL_TZOFFSET(d) (8 * 3600)
    6. 在另外的源文件中,提供外部时间处理函数和周期检查函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      #include "duktape.h"

      duk_double_t duk_get_now_date(void)
      {
      // 这里先返回一个固定时间测试 2021-08-04 09:33:24
      return -1628040804062.0;
      }

      duk_bool_t duk_exec_timeout_check(void *udata) {
      // 一般在执行好多指令后才会执行一次这个函数
      // 因为作者写死了,所以只能去 duktape.c里找
      // DUK_HTHREAD_INTCTR_DEFAULT 宏定义
      // 修改后面的值可以提高本函数的调用频率

      // 超时返回1,将触发异常
      // 正常返回0
      return 0;
      }

添加 require 功能

这个 require 实际上是一个从磁盘上加载模块的功能,类似c语言的 include,不过是在运行时进行的。

在 duktape 中注册模块实际上是通过调用 Duktape.modSearch 来实现的,指定该属性的值到c语言中的加载函数即可实现模块的加载功能。

从 duktape-2.6.0\extras\module-duktape 中将 duk_module_duktape.c 和 duk_module_duktape.h 放到工程下。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include "duk_module_duktape.h"

duk_ret_t duk_my_mod_search(duk_context *ctx)
{
/*
* index 0: id (string)
* index 1: require (object)
* index 2: exports (object)
* index 3: module (object)
*
* 读取成功返回1,读取失败返回-1
*/
const char *id = duk_require_string(ctx, 0);
printf("mod_search_id: %s\n", id);

const char *src = 0;

if (strcmp(id, "sys") == 0) {
// 这边直接读取了个程序中写死的字符串,可用文件读取函数代替
src = duk_mod_sys;
} else {
return -1;
}

duk_push_lstring(ctx, src, strlen(src));
return 1;

}

// 用来注册的函数
void duk_modSearch_register(duk_context *ctx)
{
// https://github.com/svaarala/duktape/blob/master/extras/module-duktape/README.rst
duk_module_duktape_init(ctx);

duk_get_global_string(ctx, "Duktape");
duk_push_c_function(ctx, duk_my_mod_search, 4 /*nargs*/);
duk_put_prop_string(ctx, -2, "modSearch");
duk_pop(ctx);
}

sys模块程序如下:

1
2
3
4
5
var text = 'Hello world!';

exports.hello = function () {
return text;
};

示例调用如下:

1
2
const sys = require('sys');
println('sys.hello = ' + sys.hello());

【Duktape】移植到单片机

https://biteax.com/60e97aa8.html

作者

石志超

发布于

2022-01-19

更新于

2023-09-27

许可协议