一、向 Arduino 刷入 Firmata 固件
Firmata 是一種開放的類似于 MIDI 的協(xié)議。它可以將微控制器(microcontroller)變成“客戶端”,通過串口通信直接被“主機(jī)”電腦訪問和控制。
通過 Firmata 協(xié)議,可以在主機(jī)上使用 Ruby、Python、JavaScript 等多種語言與刷入了 Firmata 固件的 arduino 板“交流”。
首先安裝 firmata-party 工具:
$ npm install -g firmata-party
安裝成功后通過 USB 線將 Arduino 板連接至電腦,運(yùn)行以下命令:
$ firmata-party uno --debug
found uno on port /dev/tty.usbserial-14110
connected
reset complete.
flashing, please wait...
flash complete.
刷寫完畢后,可以通過 Firmata Test Program 進(jìn)行簡(jiǎn)單的測(cè)試。

二、使用 JavaScript 編程
Blink LED
可以使用如下命令開始一個(gè)新的項(xiàng)目:
$ 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;
// 設(shè)置 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);
});
});
運(yùn)行效果如下:
$ node blink_led2.js
Ready!
ON
OFF
ON
...
代碼執(zhí)行后 arduino 板子上 13 號(hào)引腳上接的 LED 開始以 1 秒的間隔閃爍,同時(shí)電腦的終端界面不停地輸出 ON 和 OFF 表示當(dāng)前 LED 的開關(guān)狀態(tài)。
Analog Input
Arduino 板子上的 A0 - A5 引腳可以讀取模擬輸入信號(hào)。
示例代碼如下:
// 加載依賴庫
var Board = require('firmata');
// 引腳定義
const LED = 5;
const POT = 0;
// 初始化變量
var ledOn = 0;
var delay = 0;
// map 函數(shù)。將初始的數(shù)值范圍轉(zhuǎn)變成需要的數(shù)值范圍
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() 函數(shù)
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 函數(shù)將 0-1023 的取值轉(zhuǎn)變成 400-1600,作為接在 5 號(hào)引腳上 LED 的閃爍頻率。
程序運(yùn)行后,調(diào)節(jié)可變電阻的旋鈕,LED 即以不同的時(shí)間間隔(0.4s - 1.6s)閃爍。
PWM (Pulse-Width Modulation)
PWM 即脈寬調(diào)制,其機(jī)制為:通過 MCU 內(nèi)部的計(jì)時(shí)器設(shè)置引腳的狀態(tài),使該引腳在一個(gè)周期內(nèi)的某段時(shí)間保持 HIGH 狀態(tài),在該周期內(nèi)的其余時(shí)間保持 LOW 狀態(tài)。
平均下來看,該引腳的輸出電壓即介于最低和最高輸出電壓之間(即 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() {
// 設(shè)置 LED 引腳為 PWM 模式
board.pinMode(LED, board.MODES.PWM);
// 每隔 30 ms 提高或降低 LED 亮度,循環(huán)執(zhí)行
function fadeLed() {
brightness += fadeAmount;
if (brightness ==0 || brightness == 255) {
fadeAmount = -fadeAmount;
}
board.analogWrite(LED, brightness);
setTimeout(fadeLed, 30);
}
fadeLed();
});
});
該程序執(zhí)行后,5 號(hào)引腳上的 LED 燈先是緩慢變亮,達(dá)到最亮以后再緩慢變暗。依照此規(guī)則循環(huán)。
附錄:JavaScript 的串口通信
即使 Arduino 板子未刷入 Firmata 固件,運(yùn)行的是普通的程序。
也可以利用 Node.js 的 serialport 庫和串口通信完成 Arduino 與主機(jī)的對(duì)話。
首先初始化項(xiàng)目并安裝依賴庫:
$ mkdir test2 && cd test2
$ npm init -y
$ npm install --save-dev serialport
掃描串口設(shè)備
$ 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 收取數(shù)據(jù)
首先通過 Arduino IDE 刷入以下 counter.ino 文件,作為發(fā)送端程序:
void setup() {
Serial.begin(9600);
}
int i=0;
void loop() {
Serial.println(i++);
delay(1000);
}
上述代碼以 1000 ms 的間隔生成從 0 開始不斷遞增的數(shù)字并發(fā)送至串口。
電腦端的接收程序 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)
運(yùn)行效果如下:
發(fā)送端不斷生成新的數(shù)字并發(fā)送至串口,接收端通過串口接收該數(shù)字后將其打印到終端輸出。
向 Arduino 發(fā)送數(shù)據(jù)
首先通過 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);
}
}
}
發(fā)送端的 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);
運(yùn)行效果如下:
電腦端發(fā)送程序運(yùn)行后($ node write_port.js),打開 Arduino IDE 的 Serial Monitor。
在命令行輸入任何一個(gè)數(shù)字并按回車進(jìn)行確認(rèn),Arduino 板子接收到該數(shù)字后,又將其打印到串口輸出(即 Serial Monitor 中顯示的內(nèi)容)
參考資料
Node.js for Embedded Systems: Using Web Technologies to Build Connected Devices 1st Edition
Node SerialPort