一、向 Arduino 刷入 Firmata 固件
Firmata 是一种开放的类似于 MIDI 的协议。它可以将微控制器(microcontroller)变成“客户端”,通过串口通信直接被“主机”电脑访问和控制。
通过 Firmata 协议,可以在主机上使用 Ruby、Python、JavaScript 等多种语言与刷入了 Firmata 固件的 arduino 板“交流”。
首先安装 firmata-party
工具:$ npm install -g firmata-party
安装成功后通过 USB 线将 Arduino 板连接至电脑,运行以下命令:1
2
3
4
5
6$ firmata-party uno --debug
found uno on port /dev/tty.usbserial-14110
connected
reset complete.
flashing, please wait...
flash complete.
刷写完毕后,可以通过 Firmata Test Program 进行简单的测试。
二、使用 JavaScript 编程
Blink LED
可以使用如下命令开始一个新的项目:1
2
3$ mkdir test && cd test
$ npm init -y
$ npm install --save firmata
编辑源代码文件(blink_led.js
)用来控制 LED 闪烁: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// 加载依赖库
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);
});
});
运行效果如下:1
2
3
4
5
6$ node blink_led2.js
Ready!
ON
OFF
ON
...
代码执行后 arduino 板子上 13 号引脚上接的 LED 开始以 1 秒的间隔闪烁,同时电脑的终端界面不停地输出 ON 和 OFF 表示当前 LED 的开关状态。
Analog Input
Arduino 板子上的 A0 - A5 引脚可以读取模拟输入信号。
示例代码如下: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
41
42
43
44// 加载依赖库
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 之间)。所以一般可以近似地作为模拟电压输出。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// 加载依赖库
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 与主机的对话。
首先初始化项目并安装依赖库:1
2
3$ mkdir test2 && cd test2
$ npm init -y
$ npm install --save-dev serialport
扫描串口设备
1 | $ cat list_ports.js |
从 Arduino 收取数据
首先通过 Arduino IDE 刷入以下 counter.ino
文件,作为发送端程序:1
2
3
4
5
6
7
8
9void setup() {
Serial.begin(9600);
}
int i=0;
void loop() {
Serial.println(i++);
delay(1000);
}
上述代码以 1000 ms 的间隔生成从 0 开始不断递增的数字并发送至串口。
电脑端的接收程序 read_port.js
代码如下:1
2
3
4
5
6const 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)
运行效果如下:
发送端不断生成新的数字并发送至串口,接收端通过串口接收该数字后将其打印到终端输出。
向 Arduino 发送数据
首先通过 Arduino IDE 刷入以下 receiver.ino
文件,作为接收端程序:1
2
3
4
5
6
7
8
9
10
11
12
13
14void 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
程序代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var 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);
运行效果如下:
电脑端发送程序运行后($ 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