Skip to content

Instantly share code, notes, and snippets.

@geekscape
Last active July 31, 2016 19:33
Show Gist options
  • Save geekscape/8e7e949c7e610c46ebfebad50c9ed9de to your computer and use it in GitHub Desktop.
Save geekscape/8e7e949c7e610c46ebfebad50c9ed9de to your computer and use it in GitHub Desktop.
Example NodeBots (JavaScript) code for MakeBlock mBot LED Matrix
/*
* Titan Micro Electronics TM1640: 16 x 8 LED driver datasheet (Chinese)
* https://dl.dropboxusercontent.com/u/8663580/TM1640.pdf
*
* TODO: Turn this code into a module and use it in "nodebots.js".
* TODO: Make all functions available to the REPL.
*/
var five = require('johnny-five');
var board = five.Board();
// Define Data Command Parameters
var MODE_ADDRESS_AUTO_ADD_1 = 0x40;
var MODE_PERMANENT_ADDRESS = 0x44;
var WIDTH = 16;
var HEIGHT = 8;
board.on('ready', function() {
var pin_clock = five.Pin({
pin: 14,
mode: this.io.MODES.OUTPUT,
});
var pin_data = five.Pin({
pin: 15,
mode: this.io.MODES.OUTPUT,
});
console.log('Board ready');
var screen = {
pin_clock: pin_clock, pin_data: pin_data, matrix: new Buffer(16)
};
write_byte(pin_clock, pin_data, MODE_ADDRESS_AUTO_ADD_1); // Set mode
write_byte(pin_clock, pin_data, 0x8c); // Set brightness ?
clear_screen(screen);
if (true) {
draw_character(screen, 1, 0, 'G', state);
draw_character(screen, 7, 0, 'o', state);
draw_character(screen, 13, 0, '!', state);
write_screen(screen);
}
else {
screen.matrix = new Buffer([ // Checkerboard
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55
]);
write_screen(screen);
}
clear_screen(screen);
var state = false;
setInterval(function() {
state = ! state;
if (true) { // Face ?
draw_rectangle(screen, 0, 0, WIDTH, HEIGHT, state);
draw_circle(screen, 5, 3, 1, state);
draw_circle(screen, 10, 3, 1, state);
draw_hline(screen, 7, 5, 2, state);
}
else { // X marks the spot !
draw_line(screen, 0, 0, WIDTH - 1, HEIGHT - 1, state);
draw_line(screen, 0, HEIGHT - 1, WIDTH - 1, 0, state);
}
// invert_screen(screen);
write_screen(screen);
}.bind(this), 1000);
this.repl.inject({
test: function() {
console.log('test');
}
});
});
function write_byte(pin_clock, pin_data, buffer) {
pin_clock.high(); pin_data.low();
for (var bit = 0; bit < 8; bit ++) {
pin_clock.low();
pin_data.write(buffer & 0x01);
pin_clock.high();
buffer = buffer >> 1;
}
pin_clock.low(); pin_data.low();
pin_clock.high(); pin_data.high();
}
function write_bytes_to_address(pin_clock, pin_data, address, buffer) {
address = address | 0xc0;
pin_clock.high(); pin_data.low();
for (var bit = 0; bit < 8; bit ++) {
pin_clock.low();
pin_data.write(address & 0x01);
pin_clock.high();
address = address >> 1;
}
for (var byte = 0; byte < buffer.length; byte ++) {
var data = buffer[byte];
for (bit = 0; bit < 8; bit ++) {
pin_clock.low();
pin_data.write(data & 0x01);
pin_clock.high();
data = data >> 1;
}
}
pin_clock.low(); pin_data.low();
pin_clock.high(); pin_data.high();
}
function write_screen(screen) {
write_bytes_to_address(screen.pin_clock, screen.pin_data, 0, screen.matrix);
}
function clear_screen(screen, value) { // value = 0xff for all LEDS on
if (typeof(value) === 'undefined') value = 0;
screen.matrix.fill(value);
write_screen(screen);
}
function invert_screen(screen) {
for (var index = 0; index < screen.matrix.length; index ++) {
screen.matrix[index] = screen.matrix[index] ^ 0xff;
}
}
function draw_point(screen, column, row, value) {
var bit = (typeof(value) === 'undefined') ? 1 : value;
var mask = 0xff ^ (1 << row);
screen.matrix[column] = screen.matrix[column] & mask | (bit << row);
// TODO: This should work and would be much more efficient !
//write_bytes_to_address(
// screen.pin_clock, screen.pin_data, column, new Buffer(screen.matrix[column])
//);
}
function draw_hline(screen, column, row, length, value) {
for (var index = column; index < column + length; index ++) {
draw_point(screen, index, row, value);
}
// TODO: More efficient to write_bytes_to_address() for only changed columns !
}
function draw_vline(screen, column, row, length, value) {
var bit = (typeof(value) === 'undefined') ? 1 : value;
var byte = (Math.pow(2, length) - 1) << row;
var mask = 0xff ^ byte;
if (bit === false) byte = 0;
screen.matrix[column] = screen.matrix[column] & mask | byte;
// TODO: This should work and would be much more efficient !
//write_bytes_to_address(
// screen.pin_clock, screen.pin_data, column, new Buffer(screen.matrix[column])
//);
}
// Bresenham's line algorithm
function draw_line(screen, column0, row0, column1, row1, value) {
var column_delta = Math.abs(column1 - column0);
var row_delta = Math.abs(row1 - row0);
var column_increment = column0 < column1 ? 1 : -1;
var row_increment = row0 < row1 ? 1 : -1;
var error = (column_delta > row_delta ? column_delta : -row_delta) / 2;
while (true) {
draw_point(screen, column0, row0, value);
if (column0 === column1 && row0 === row1) break;
var error2 = error;
if (error2 > -column_delta) {
error -= row_delta;
column0 += column_increment;
}
if (error2 < row_delta) {
error += column_delta;
row0 += row_increment;
}
}
}
function draw_rectangle(screen, row, column, width, height, value) {
draw_hline(screen, column, row, width, value);
draw_hline(screen, column, row + height - 1, width, value);
draw_vline(screen, column, row, height, value);
draw_vline(screen, column + width - 1, row, height, value);
}
function fill_rectangle(screen, column, row, width, height, value) {
for (var index = 0; index < width; index ++) {
draw_vline(screen, column + index, row, height, value);
}
}
// Bresenham's line algorithm extended for circles
function draw_circle(screen, column, row, radius, value) {
var x = radius, y = 0;
var radiusError = 1 - x;
while (x >= y) {
draw_point(screen, -y + column, -x + row, value);
draw_point(screen, y + column, -x + row, value);
draw_point(screen, -x + column, -y + row, value);
draw_point(screen, x + column, -y + row, value);
draw_point(screen, -x + column, y + row, value);
draw_point(screen, x + column, y + row, value);
draw_point(screen, -y + column, x + row, value);
draw_point(screen, y + column, x + row, value);
y++;
if (radiusError < 0) {
radiusError += 2 * y + 1;
}
else {
x --;
radiusError += 2 * (y - x + 1);
}
}
}
function fill_circle(screen, column, row, radius, value) {
var x = radius, y = 0;
var radiusError = 1 - x;
while (x >= y) {
draw_line(screen, -y + column, -x + row, y + column, -x + row, value);
draw_line(screen, -x + column, -y + row, x + column, -y + row, value);
draw_line(screen, -x + column, y + row, x + column, y + row, value);
draw_line(screen, -y + column, x + row, y + column, x + row, value);
y++;
if (radiusError < 0) {
radiusError += 2 * y + 1;
}
else {
x --;
radiusError+= 2 * (y - x + 1);
}
}
}
// Thanks Suz (@noopkat) ... https://github.com/noopkat/oled-font-5x7 !
var font = require('./vendor/oled-font-5x7/oled-font-5x7');
function draw_character(screen, column, row, character, value) {
var lookup = index = font.lookup.indexOf(character) * 5;
var fontData = font.fontData.slice(lookup, lookup + 5);
for (var index = 0; index < fontData.length; index ++) {
screen.matrix[column + index] = fontData[index] << 1;
}
}
@geekscape
Copy link
Author

Now includes fill_circle() and draw_character()

Note: You will need to ...
mkdir vendor
cd vendor
git clone https://github.com/noopkat/oled-font-5x

@ajfisher
Copy link

Brilliant - will look at how we get this back into the main side of J5 as well in LED matrix

@geekscape
Copy link
Author

The prototype code in this Gist has been made into a proper NodeBot driver and moved to https://github.com/geekscape/nodebot_ai ... @ajfisher let's continue the discussion over there (as GitHub issues ?).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment