Skip to content

Instantly share code, notes, and snippets.

@CLCL
Last active September 1, 2015 16:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CLCL/381b40691dad0afda2ad to your computer and use it in GitHub Desktop.
Save CLCL/381b40691dad0afda2ad to your computer and use it in GitHub Desktop.
akibaLEDピカリ館の単色LEDマトリクスパネルP10をRaspberry Piのnode.jsで光らせて文字をスクロールする
{
"name": "picari-banner-gorinpikku.js",
"private": true,
"scripts": { "start": "node picari-banner-gorinpikku.js" },
"dependencies": {
"canvas": ">=1.2.7",
"onoff" : ">=1.0.2",
"pi-spi": ">=1.0.0"
}
}
'use strict';
// picari-banner-gorinpikku.js:
// akibaLEDピカリ館の単色LEDマトリクスパネルP10を
// Raspberry Piのnode.jsで光らせて文字をスクロールする
// 配線については http://cl.hatenablog.com/entry/picari-ledmatrix-raspberrypi 参照
// Usage:
// (Raspberry PiにRaspbian Wheezyをインストールして、piユーザでログイン後)
// sudo apt-get update
// sudo apt-get -y install libcairo2-dev libpango1.0-dev libjpeg8-dev libgif-dev
// curl -LO http://node-arm.herokuapp.com/node_latest_armhf.deb
// sudo dpkg -i node_latest_armhf.deb
// git clone https://gist.github.com/381b40691dad0afda2ad.git picari
// cd $_
// curl -LO http://jaist.dl.osdn.jp/mix-mplus-ipa/63545/migu-1m-20150712.zip
// unzip migu-1m-20150712.zip
// ln -s migu-1m-20150712/migu-1m-regular.ttf .
// npm install onoff pi-spi canvas
// sudo node picari-banner-gorinpikku.js
//
// #Use libs/Global Objects
//
var SPI = require('pi-spi');
var Gpio = require('onoff').Gpio;
var Canvas = require('canvas');
// SPI初期化
var spi = SPI.initialize('/dev/spidev0.0');
spi.clockSpeed(7812500); // 7.8MHz(15.6MHzだと取りこぼしがみられた)
//spi.bitOrder(SPI.order.LSB_FIRST); // パネル仕様ではLSBだがpi-spiでエラーになるので
//
// #define
//
// 信号値定義
var HIGH = 1;
var LOW = 0;
// 信号ピン定義
var OE = 22; // GPIO22(15pin):Output Enable -> OE
var DYNA = 24; // GPIO24(18pin):Dynamic Lighting A(LSB) -> A
var DYNB = 23; // GPIO23(16pin):Dynamin Lighting B(MSB) -> B
var LATCH = 25; // GPIO25(22pin):74HC595 LATCH -> LAT
// MOSI(19pin)[変更不可]:SPI DATA OUTPUT -> /DAT
// SCLK(23pin)[変更不可]:SPI CLOCK -> CLK
// VDP駆動インターバル定義
var WAIT = 0; // 0: 410Hz, 1: 250Hz
// パネル横カラム設定(横8ドット分を1カラム、パネル1枚=4カラム)
//var COLUMN = 4; // パネル1枚は4カラム(32dot)
var COLUMN = 8; // パネル2枚は8カラム(64dot)
//var COLUMN = 12; // パネル3枚は12カラム(96dot)
// デバッグ用
var USE_SPI = 1;
var USE_CONSOLE = 0;
//
// # Define Objects
//
// VRAM オブジェクト パネルの画素分のBuffer4つとそれを取り扱うメソッド
//
var VRAM = function() {
this.buffers = [
new Buffer(COLUMN * 4),
new Buffer(COLUMN * 4),
new Buffer(COLUMN * 4),
new Buffer(COLUMN * 4)
];
};
VRAM.prototype = {
clean: function() { // buffersをクリアする
this.buffers[0].fill(255);
this.buffers[1].fill(255);
this.buffers[2].fill(255);
this.buffers[3].fill(255);
},
setBuffers: function(buffers) { // ビットパターンをbuffersにセットする
for ( var i = 0; i < buffers.length; i++) {
this.buffers[i] = buffers[i];
}
},
copy: function(target) { // buffersの内容を別のbuffersにコピー(転送)する
this.buffers[0].copy(target.buffers[0]);
this.buffers[1].copy(target.buffers[1]);
this.buffers[2].copy(target.buffers[2]);
this.buffers[3].copy(target.buffers[3]);
},
getLength: function() {
return this.buffers[0].length;
},
getBufferRow: function(r) {
return this.buffers[r];
}
};
// VDP オブジェクト VRAMオブジェクトの内容でLEDマトリクスパネルを駆動する
//
var VDP = function(f) {
this.workVram = new VRAM();
this.vram = new VRAM();
this.gpio = {
oe : new Gpio(f.OE , 'out'),
a : new Gpio(f.A , 'out'),
b : new Gpio(f.B , 'out'),
lat : new Gpio(f.LAT, 'out')
}
this._init();
};
VDP.prototype = {
// VRAMの参照渡し
setVram: function(vram) {
this.vram = vram;
},
on : function() { // LEDパネル点灯
this._loop(0);
},
off: function() { // LEDパネル消灯
this.gpio.oe .writeSync(LOW); // 消灯
this.gpio.oe .unexport();
this.gpio.a .unexport();
this.gpio.b .unexport();
this.gpio.lat.unexport();
},
_init: function() { // イニシャライズ
this.gpio.oe .writeSync(LOW); // 消灯
this.gpio.lat.writeSync(LOW); // ラッチ
},
_loop: function(i) { // VDP内部ループ処理 i: 表示ライン(0~3)
var self = this;
i %= 4;
// テアリング防止のため第0ライン表示時に画素を固定する
if (i == 0) {
self.vram.copy(self.workVram);
}
// 画面表示
if (USE_CONSOLE) {
if (i == 3) {
console.log('\n');
// コンソールにLEDマトリクス点灯パターンを表示する
var l = self.vram.getLength(); // 32dotの場合16になる
for (var y = 0; y < 16; y++ ) {
var str = '';
for (var x = 0; x < l / 4; x++) {
var s = 255 - self.vram.getBufferRow(y % 4)[ x * 4 + (Math.floor(3 - Math.floor(y / 4)))];
str += ('0000000' + s.toString(2)).slice(-8);
}
console.log(str);
}
}
}
// SPIデータ転送
if (USE_SPI) {
spi.write(self.workVram.getBufferRow(i), function(e) {
if (e) console.error(e);
// LEDパネル制御線の操作
self.gpio.oe .writeSync(LOW); // パネル消灯
self.gpio.a .write(i & 1); // ダイナミック点灯桁指定(LOW)
self.gpio.b .write(i >> 1 & 1); // ダイナミック点灯桁指定(HIGH)
self.gpio.lat.write(HIGH); // ラッチ解除
self.gpio.oe .write(HIGH); // パネル点灯
self.gpio.lat.write(LOW); // ラッチ
});
}
var timerId1 = setTimeout(function() {self._loop(++i);}, WAIT); // 0: 410Hz, 1: 250Hz
}
};
//
// グローバル関数
//
function getImage(str) { // 文字列を入力してCanvasコンテキスト画像オブジェクトを得る
// HTML5-Canvas描画
var Image = Canvas.Image;
var canvas = new Canvas(str.length * 16, 16);
var ctx = canvas.getContext('2d');
ctx.antialias = 'none';
ctx.textBaseline = 'top';
//ctx.font = '16px Sans-serif';
var Font = Canvas.Font;
var myFont = new Font('MyFont', 'migu-1m-regular.ttf');
ctx.font = '16px MyFont';
ctx.fillText(str, 0, 0);
ctx.stroke();
var te = ctx.measureText(str);
// Canvas内のピクセル走査
//console.log(te.width);
var imageData = ctx.getImageData(0, 0, te.width, 16);
return imageData;
}
function getPattern(ctx, COLUMN, sx) { // ctxの画像を入力してビットパターンの配列を得る
// HTML5-Canvas描画
// Canvas内のピクセル走査
var pattern = [];
for (var y = 0; y < 16; y++ ) {
for (var x = 0; x < COLUMN * 8; x++) {
// RGBa
var l = ctx.width;
pattern.push(ctx.data[((sx + x) + y * l) * 4 + 3] > 0 ? 1 : 0);
}
}
return pattern;
}
function convPatternToBuffer(pattern, COLUMN) { // ビットパターンの配列を入力してBuffer[4]を得る
var buffers = [];
//console.log(pattern.length);
for (var i = 0; i < 4; i++) {
var buf = new Buffer(COLUMN * 4); // LEDパネル1面分(画素512bitのうち1回の駆動での画素128bit=16byte)
buf.fill(255); // bufferクリア
for (var j = 0; j < COLUMN; j++) {
// 配列からbufferに転記するループ
for (var k = 0; k < 4; k++) {
var byte = 255;
var pos = i * COLUMN
+ j
+ ( (3 - k) * 4 ) * COLUMN;
for (var l = 0; l < 8; l++) {
byte = byte - (pattern[pos * 8 + l] << (7 -l));
}
buf.writeUInt8(byte, j * 4 + k);
}
}
buffers.push(buf);
}
return buffers;
}
//
// Main Routine
//
var main = function() {
// VRAM準備
var vram = new VRAM(); // このVRAMオブジェクトの参照は書き換えないよう
vram.clean();
// VDP動作開始
var vdp = new VDP({ A: DYNA, OE : OE, B: DYNB, LAT: LATCH });
// vramをVDPに登録
vdp.setVram(vram) //参照渡しでVDPに登録されるのでvramはいわばDMAみたいに扱う
// VDP表示開始
vdp.on(); // 以降割り込みタイマーでVDPがvramの中身を描画しつづける
// 文字パターン準備(コスト高いので使いまわす)
//var img = getImage('漢字'); // ctxで'漢字'と書かれたオブジェクトが帰る
var img = getImage('    これが噂の!ゴリンピック    '); // ctx
// VRAM移動操作タイマ
var cnt = 0; // 文字の位置を指定するカウンタ
var timerId2 = setInterval( function() {
var pattern = getPattern(img, COLUMN, cnt);
var buffers = convPatternToBuffer(pattern, COLUMN);
// vramの参照を書き換えないようにsetBuffersメソッドを使う
vram.setBuffers(buffers);
cnt++; cnt %= 256;
},
33
);
process.on('SIGINT', function() {
vdp.off();
console.log('\nexit.');
process.exit(0);
});
};
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment