起步走——驱动开发

让我们以开发套件中的大按键模块(Push Button Module)为例,介绍如何编写一个外设驱动。

前提条件

  • 如果还没有了解如何使用 Ruff 编写应用,可以阅读这里
  • 如果你希望在rap layout --visual 能够正确进行图形化展示,请在软件仓库上传并配置设备信息。参考这里了解如何在软件仓库添加设备。
  • 查看设备对应的硬件文档,确定接口类型。本文中的大按键模块,需要连接的是一个 GPIO 接口作为输入。

第一步:创建项目

在待创建项目的目录下,使用 rap 创建项目。注意,这里使用的初始化参数是 driver,它会生成一些驱动相关的配置文件。

rap init driver

根据提示,填写相应内容,一切顺利的话,一个新的目录就创建出来了,驱动项目就在其中。该命令还会生成 package.jsondriver.json

  • package.json 是软件包相关的配置信息
  • driver.json 是驱动相关的配置信息

第二步:声明接口信息

在这个例子里,大按键模块需要一个 GPIO 接口作为其输入,我们需要将这个信息声明在 driver.json 里。

{
"inputs": {
"gpio": {
"type": "gpio",
"args": {
"direction": "in",
"edge": "both"
}
}
}
}

inputs 下,我们声明了一个作为输入的对象。这个对象的名字叫做 gpio。在程序里,我们会用 gpio 引用这个变量,其类型( type )是 gpio。同时,我们还设置了这个 GPIO 的一些参数,比如,它是用作输入的,触发方式是上升沿和下降沿都可以触发。关于 GPIO 的参数,可以参考其 API 文档

第三步:编写代码

对于这个驱动,我们要提供几个行为:

  • 当按键按下时,产生一个 push 事件
  • 当按键松开时,产生一个 release 事件
  • 提供方法 isPushed,查询按键是否被按下

根据硬件的特性,当按键按下的时候,GPIO 接口的值为0,当按键松开时,GPIO 接口的值为1,我们编写如下代码:

var driver = require('ruff-driver');

var ButtonState = {
pushed: 0,
released: 1
};

module.exports = driver({
attach: function(inputs) {
var that = this;

this._gpio = inputs['gpio'];
this._currentState = ButtonState.released;

this._gpio.on('interrupt', function(state) {
if (that._currentState === state) {
return;
}

that._currentState = state;

if (state === ButtonState.pushed) {
that.emit('push');
} else {
that.emit('release');
}
});
},
exports: {
isPushed: function() {
return this._currentState === ButtonState.pushed;
}
}
});

更多驱动编程细节可以了解驱动编程模型

编外步骤:测试驱动

Ruff 提供了一个辅助程序库,我们只要提供一个模拟的接口(在这里是 GPIO ),就可以让测试在开发机上进行,保证了代码逻辑上的正确性。

var runner = require('ruff-driver-runner');
var assert = require('assert');
var path = require('path');
var driverPath = path.join(__dirname, '..');
var when = require('ruff-mock').when;

exports['test should emit push event'] = function(done) {
var run = false;
runner.run(driverPath, function(error, context) {
if (error) {
done(error);
return;
}

var button = context.device;
var gpio = context.inputs['gpio'];

button.on('push', function() {
done();
});

gpio.emit('interrupt', 0);
});
};

exports['test should return true when button is pushed'] = function(done) {
runner.run(driverPath, function(error, context) {
if (error) {
done(error);
return;
}

var button = context.device;
var gpio = context.inputs['gpio'];

gpio.emit('interrupt', 0);

assert(button.isPushed());
done()
});
};

require('test').run(exports);

(test/button-gpio-test.js)

这里用到的是几个框架:

  • 一个简单的测试框架( test ),符合 CommonJS 的 Unit Testing 规范
  • ruff-mock,是 Ruff 提供的一个通用 mock 框架。
  • ruff-driver-runner,是 Ruff 提供的驱动测试框架,所有的设备都已经由模拟对象替代,保证可以在开发机上执行。

运行如下命令执行测试:

ruff test/driver-test.js

关于驱动测试,更多细节参见驱动测试

第四步:硬件测试

驱动编写好之后,还必须把驱动部署到硬件上进行实际的测试。请参考起步走应用开发步骤,编写一个测试应用。

不同的是,我们需要安装本地驱动。

建议使用符号链接直接将驱动链接至项目 ruff_modules 文件夹内,避免反复添加。

rap device add -i <device-id> -l </path/to/driver>

最后,就是把我们的应用部署到硬件上进行测试。

扩展步骤:发布驱动

经过开发和硬件测试,驱动的开发已经完成。如果想将这个驱动分享给别人,可以把驱动发布到软件包仓库。如果还没有申请软件包仓库的账号,请这里申请账号。

注意 如果你希望在rap layout --visual 能够正确进行图形化展示,请在软件仓库上传并配置设备信息。

在命令行里运行如下命令,输入软件包仓库的账号信息:

rap add-user

在命令行里运行如下命令,发布驱动:

rap publish

一切顺利的话,你已经完成了一个驱动的开发,并将其发布出去。恭喜你,你已经成为了一个合格的硬件驱动开发工程师。