Skip to content
On this page

凹语言再次点亮 Arduino nano 33


凹语言是国内首个面向WebAssembly设计通用编程语言,也是目前被 CNCF基金会wasm全景图 收录的的唯一一个来自中国的开源编程语言项目。

凹语言最初在2022年底增加了对Arduino-wasm平台的支持,后来在2023年底因为聚焦MVP开发临时去掉了Arduino目标的支持。最近在下个发布的v0.17.0版本中将恢复对Arduino nano 33开发版的支持,相对于之前的版本现在的凹语言特性更加丰富。

1. Arduino-wasm 是什么

Wasm3 是一个高性能的 WebAssembly 解释器,而 Arduino-wasm 则是 Wasm3 针对 Arduino 的定制版本。Wasm3 最小的硬件依赖是 ~64Kb Flash 和 ~10Kb RAM。

Github 仓库:https://github.com/wasm3/wasm3-arduino

下面是 Wasm3 运行在 iOS 的截图:

Arduino-wasm 则是运行在 Arduino Nano 33 等开发板上的 Wasm3。

2. Arduino Nano 33 开发板介绍

Arduino Nano 33,是 Arduino Nano 的高配版本,是一款基于 nRF52840 SoC ARM 32 位处理器的微型开发板。其中 Arduino Nano BLE Sense 其主控芯片集成了蓝牙低功耗(BLE)。NANO 33 BLE 不仅保留了与经典款 NANO 同样的尺寸与管脚,且在此基础上配有多种高性能传感器等,当然最重要的是满足了 Arduino-wasm 的最低硬件要求。

目前(2024年底),淘宝的价格大约在200元以内。

3. 编写 Arduino 的闪灯例子

首先用wa init生成一个arduino例子:

$ wa init -arduino
$ cd hello/
$ tree
.
├── README.
├── src
│   └── main.wa
└── wa.mod

其中 main.wa 内容如下:

wa
// 版权 @2024 arduino 作者。保留所有权利。

import "syscall/arduino"

global LED = arduino.GetPinLED()

func init {
	arduino.PinMode(LED, 1)
	arduino.Print("凹语言(Wa)/Arduino is running ...\n")
	arduino.Print("https://wa-lang.org\n")

	for {
		arduino.DigitalWrite(LED, arduino.HIGH)
		arduino.Delay(100)
		arduino.DigitalWrite(LED, arduino.LOW)
		arduino.Delay(900)
	}
}

代码逻辑比较简单,只是换成了凹语言来写。我们直接使用了 syscall/arduino 包来使用 Arduino 的功能。

4. syscall/arduino 包介绍

让我们看看 syscall/arduino 包的代码:

wa
// 版权 @2022 凹语言 作者。保留所有权利。

const (
	LOW  :i32 = 0
	HIGH :i32 = 1

	INPUT        :i32 = 0
	OUTPUT       :i32 = 1
	INPUT_PULLUP :i32 = 2
)

#wa:import arduino millis
func Millis() => i32

#wa:import arduino delay
func Delay(ms: i32)

#wa:import arduino pinMode
func PinMode(pin, mode: i32)

#wa:import arduino digitalWrite
func DigitalWrite(pin, value: i32)

#wa:import arduino getPinLED
func GetPinLED() => i32

#wa:import arduino print
func PrintRawString(ptr: i32, len: i32)

func Print(s: string) {
	print(s)
}

func Println(s: string) {
	println(s)
}

主要是将常用的函数通过 WASM 方式导入到了代码空间,大部分函数并不在凹语言中实现。

5. 编译到 Arduino-wasm 平台

可以通过wa build命令构建程序:

$ wa build
$ tree
.
├── README.md
├── output
│   ├── arduino
│   │   ├── app.wasm.h
│   │   └── arduino.ino
│   ├── hello.wasm
│   └── hello.wat
├── src
│   └── main.wa
└── wa.mod

output目录存放编译的结果,output/arduino/arduino.ino是Arduino工程文件,output/arduino/app.wasm.houtput/hello.wasm文件对应的数据数组。

c
// Auto Generate by Wa language. See https://wa-lang.org

unsigned int app_wasm_len = 4608;

unsigned char app_wasm[] = {
	0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x37,
	...
	0x03, 0x24, 0x74, 0x30, 0x17, 0x00, 0x18, 0x00,
};

然后 Arduino 工程就可以通过 #include "app.wasm.h" 方式引用这个 WASM 程序,最终和 Wasm3 一起编译。

6. Arduino 平台胶水代码初探

最终还需要一个 arduino.ino 代码(其实就是针对 Arduino 简化的 C++ 程序)。代码结构如下:

c
#include <wasm3.h>
#include <m3_env.h>

// 定义 WASM 和 本地栈大小
#define WASM_STACK_SLOTS  1024
#define NATIVE_STACK_SIZE (32*1024)

// WASM 最大内存限制, 一般不得超过 64KB 大小
#define WASM_MEMORY_LIMIT (32*1024)

// 导入 凹语言 生成的 WASM 文件对应的二进制头文件
#include "app.wasm.h"

开头保护 Wasm3 和 WASM 程序对应的头文件,同时定义以下栈的大小。

然后看看代码主体结构:

cpp
// 执行 WASM 的函数
void wasm_task(void*) { ... }

// setup 作为 main 函数用户
void setup() {
	// 串口初始化
	Serial.begin(115200);
	delay(100);

	// 等待串口初始化完成, 必须是 USB 串口
	while(!Serial) {}

	// 阻塞执行 wasm 程序, 不会返回
	wasm_task(NULL);
}

// 该函数不会被执行
// 定义该函数只是为了确保 Arduino 编译通过
void loop() {
	delay(100);
}

Arduino 的常规代码只有 setup 和 loop 两个函数。不过这里只用到了 setup 函数。在 setup 函数中首先初始化串口(方便打印调试信息),最后调用 wasm_task 执行凹语言写的亮灯代码,其中会加载 WASM 模块并执行。wasm_task 看起来是一个比较复杂的程序,不过核心逻辑和普通的 WASM 执行流程类似,细节可以下次文章再展开。

总体来说,以上这些胶水代码是相对固定的。后面会自动生成全部这些代码,同时去掉对外部其他工具的依赖。目标是生成的 Arduino 工程文件可以直接打开构建。

7. Arduino 构建 & 执行

如果是第一次使用 Arduino Nano 33 开发板,打开 IDE 后会提示安装必要的工具。然后需要在库管理菜单手动安装 Wasm3 包:

然后编译后上传的效果:

执行的效果,除了可以看到 LED 闪烁,串口还可以看到输出信息:

一切正常!

8. 总结展望

目前流行 Arduino 单片机的配置还是比较低的,可能难以运行 WASM 程序。不过可以乐观估计 Arduino Nano 33 将会很快普及。而且,Wasm3 不仅仅可以支持 Arduino,还可以支持树莓派 Pico(淘宝价格30元)。因此,从长远看 WASM 是一个兼具灵活性和性价比的可选方案。