使用 JavaScript 对 Arduino 编程

一、向 Arduino 刷入 Firmata 固件

Firmata 是一种开放的类似于 MIDI 的协议。它可以将微控制器(microcontroller)变成“客户端”,通过串口通信直接被“主机”电脑访问和控制。
通过 Firmata 协议,可以在主机上使用 Ruby、Python、JavaScript 等多种语言与刷入了 Firmata 固件的 arduino 板“交流”。

首先安装 firmata-party 工具:
$ npm install -g firmata-party
安装成功后通过 USB 线将 Arduino 板连接至电脑,运行以下命令:

$  firmata-party uno --debug
found uno on port /dev/tty.usbserial-14110
connected
reset complete.
flashing, please wait...
flash complete.

刷写完毕后,可以通过 Firmata Test Program 进行简单的测试。

Firmata Test

二、使用 JavaScript 编程

Blink LED

可以使用如下命令开始一个新的项目:

$ mkdir test && cd test
$ npm init -y
$ npm install --save firmata

编辑源代码文件(blink_led.js)用来控制 LED 闪烁:

// 加载依赖库
var Board = require('firmata');

// 连接初始化
Board.requestPort(function(error, port) {
    if (error) {
        console.log(error);
        return;
    }

    var board = new Board(port.comName);

    // 等待连接
    board.on("ready",function() {
        console.log("Ready!");
        var ledOn = true;

        // 设置 pin 13 为输出引脚
        board.pinMode(13, board.MODES.OUTPUT);

        // 使 LED 闪烁
        setInterval(function() {
            if (ledOn) {
                console.log('ON');
                board.digitalWrite(13, board.HIGH);
            } else {
                console.log('OFF');
                board.digitalWrite(13, board.LOW);
            }
            ledOn = !ledOn;
        }, 1000);
    });
});

运行效果如下:

$ node blink_led2.js
Ready!
ON
OFF
ON
...

代码执行后 arduino 板子上 13 号引脚上接的 LED 开始以 1 秒的间隔闪烁,同时电脑的终端界面不停地输出 ON 和 OFF 表示当前 LED 的开关状态。

Analog Input

Arduino 板子上的 A0 - A5 引脚可以读取模拟输入信号。
示例代码如下:

// 加载依赖库
var Board = require('firmata');

// 引脚定义
const LED = 5;
const POT = 0;

// 初始化变量
var ledOn = 0;
var delay = 0;

// map 函数。将初始的数值范围转变成需要的数值范围
function map(x, in_min, in_max, out_min, out_max)
{
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

// 连接初始化
Board.requestPort(function(error, port) {
    if (error) {
        console.log(error);
        return;
    }

    var board = new Board(port.comName);

    // 等待连接
    board.on("ready", function() {

        // 控制 LED 闪烁的 blink() 函数
        function blink() {
            board.digitalWrite(LED, ledOn);
            ledOn = !ledOn;
            setTimeout(blink, delay);
        }

        // 读取并更新可变电阻提供的模拟输入
        board.analogRead(0, function(d) {
            delay = map(d, 0, 1023, 400, 1600);
        });

        blink();
    });
});

线路连接图如下:


线路连接图

即通过 Arduino 板 A0 引脚可以读取模拟输入的功能(连接可变电阻的中间引脚),获取可变电阻的取值(0-1023)。
再通过程序中的 map 函数将 0-1023 的取值转变成 400-1600,作为接在 5 号引脚上 LED 的闪烁频率。

程序运行后,调节可变电阻的旋钮,LED 即以不同的时间间隔(0.4s - 1.6s)闪烁。

PWM (Pulse-Width Modulation)

PWM 即脉宽调制,其机制为:通过 MCU 内部的计时器设置引脚的状态,使该引脚在一个周期内的某段时间保持 HIGH 状态,在该周期内的其余时间保持 LOW 状态。
平均下来看,该引脚的输出电压即介于最低和最高输出电压之间(即 0~5 V 之间)。所以一般可以近似地作为模拟电压输出。

// 加载依赖库
var Board = require('firmata');

// 变量定义
const LED = 5;
var brightness = 0;
var fadeAmount = 5;

// 连接初始化
Board.requestPort(function(error, port) {
    if (error) {
        console.log(error);
        return;
    }

    var board = new Board(port.comName);

    // 等待连接
    board.on("ready", function() {

        // 设置 LED 引脚为 PWM 模式
        board.pinMode(LED, board.MODES.PWM);

        // 每隔 30 ms 提高或降低 LED 亮度,循环执行
        function fadeLed() {
            brightness += fadeAmount;
            if (brightness ==0 || brightness == 255) {
                fadeAmount = -fadeAmount;
            }
            board.analogWrite(LED, brightness);
            setTimeout(fadeLed, 30);
        }
        fadeLed();
    });
});

该程序执行后,5 号引脚上的 LED 灯先是缓慢变亮,达到最亮以后再缓慢变暗。依照此规则循环。

附录:JavaScript 的串口通信

即使 Arduino 板子未刷入 Firmata 固件,运行的是普通的程序。
也可以利用 Node.js 的 serialport 库和串口通信完成 Arduino 与主机的对话。

首先初始化项目并安装依赖库:

$ mkdir test2 && cd test2
$ npm init -y
$ npm install --save-dev serialport
扫描串口设备
$ cat list_ports.js
var serialPort = require("serialport");
serialPort.list(function (error, ports) {
    ports.forEach(function(port) {
        console.log(port.comName);
    });
});
$ node list_ports.js
/dev/tty.Bluetooth-Incoming-Port
/dev/tty.usbserial-14110
从 Arduino 收取数据

首先通过 Arduino IDE 刷入以下 counter.ino 文件,作为发送端程序:

void setup() {
  Serial.begin(9600);
}

int i=0;
void loop() {
  Serial.println(i++);
  delay(1000);
}

上述代码以 1000 ms 的间隔生成从 0 开始不断递增的数字并发送至串口。

电脑端的接收程序 read_port.js 代码如下:

const SerialPort = require('serialport')
const Readline = require('@serialport/parser-readline')
const port = new SerialPort('/dev/cu.usbserial-14110')

const parser = port.pipe(new Readline({ delimiter: '\n' }))
parser.on('data', console.log)

运行效果如下:
read_port

发送端不断生成新的数字并发送至串口,接收端通过串口接收该数字后将其打印到终端输出。

向 Arduino 发送数据

首先通过 Arduino IDE 刷入以下 receiver.ino 文件,作为接收端程序:

void setup() {
  Serial.begin(9600);
}

void loop() {
  int incoming = 0;
  if (Serial.available() > 0) {
    incoming = Serial.parseInt();

    if (Serial.read() == '\n') {
      Serial.println(incoming);
    }
  }
}

发送端的 write_port.js 程序代码如下:

var SerialPort = require('serialport');
var Stream = require('stream');
var modem = 'cu.usbserial-14110';
var ws = new Stream();
ws.writable = true;

ws.write = function(data) {
    serialPort.write(data);
};

ws.end = function(buf) {
    console.log('bye');
}

var serialPort = new SerialPort('/dev/' + modem);

process.stdin.pipe(ws);

运行效果如下:
write_port

电脑端发送程序运行后($ node write_port.js),打开 Arduino IDE 的 Serial Monitor
在命令行输入任何一个数字并按回车进行确认,Arduino 板子接收到该数字后,又将其打印到串口输出(即 Serial Monitor 中显示的内容)

参考资料

Node.js for Embedded Systems: Using Web Technologies to Build Connected Devices 1st Edition
Node SerialPort

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容