Skip to content

Instantly share code, notes, and snippets.

@pearlchen
Last active November 8, 2015 04:37
Show Gist options
  • Save pearlchen/31ac996f8688a01cc5b6 to your computer and use it in GitHub Desktop.
Save pearlchen/31ac996f8688a01cc5b6 to your computer and use it in GitHub Desktop.
Scroll text across your Grove LCD screen using JavaScript on the Intel Edison or Intel Galileo. Video: https://twitter.com/PearlChen/status/588890387260903424. And check it out within a larger project: https://github.com/pearlchen/iot-smart-desk-clock
/* global setInterval: false, setTimeout: false, clearInterval: false, clearTimeout:false, module: false, console: false */
/**
* Module exports.
*/
module.exports = LcdTextHelper;
/**
* LcdTextHelper constructor.
*
* @param {Jhd1313m1} UPM I2C LCD instance
* @api public
*/
function LcdTextHelper(lcd) {
var TOTAL_LCD_ROWS = 2;
var TOTAL_LCD_COLUMNS = 16;
var KEEP_TEXT_ON_SCREEN_DELAY = 2000; // in ms
var TEXT_UPDATE_INTERVAL = 550; // in ms, should be less than TEXT_DRAW_INTERVAL
var TEXT_DRAW_INTERVAL = 500; // in ms, should be more than TEXT_UPDATE_INTERVAL
var REPEATING_TEXT_PADDING = " "; // whitespace between looping message when text is scrolling
var BLANK_ROW_TEXT = " "; // exactly 16 space characters
var lcdRows = []; //var EXAMPLE_ROW_OBJECT = { displayText: BLANK_ROW_TEXT, updateDisplayTextIntervalId: 0 };
var startScrollingTimeoutId = 0;
var drawIntervalId = 0;
/**
* Takes array of message strings to display.
* Later uses string lengths to set text on LCD with or without scrolling.
*
* @param {string[]} messages - Each string in the array corresponds to a row on the LCD.
* @api public
*/
function set(messages){
reset();
messages = messages || []; // default, empty array
if (messages.length > TOTAL_LCD_ROWS) {
console.log("This LCD screen only supports %s rows of text but you tried to give me %s", TOTAL_LCD_ROWS, messages.length);
messages.length = TOTAL_LCD_ROWS; // truncate array, if needed
}
for (var row=0; row<messages.length; row++) {
var msg = messages[row];
if (msg.length < TOTAL_LCD_COLUMNS) {
msg = msg + BLANK_ROW_TEXT.slice(msg.length); // fill in any remaining gaps with spaces
} else if (msg.length > TOTAL_LCD_COLUMNS) {
msg = msg + REPEATING_TEXT_PADDING; // add padding for looping text
}
// create a new slot in lcdRows to save the display string
// (and later save any scrolling interval timer IDs)
lcdRows[row] = {displayText: msg};
}
// immediately draw text to LCD then, after a slight startup delay, start scrolling effect
draw();
startScrollingTimeoutId = setTimeout(startScrolling, KEEP_TEXT_ON_SCREEN_DELAY);
}
/**
* Checks length of display text strings and, if long enough for scrolling, will:
* - Add a timer interval for each row to update the string value to give illusion of scrolling.
* - Add a timer interval to re-draw all strings to the LCD.
*
* @api private
*/
function startScrolling() {
var totalScrolling = 0;
for (var r=0; r<lcdRows.length; r++) {
if (lcdRows[r].displayText.length > TOTAL_LCD_COLUMNS) {
lcdRows[r].updateDisplayTextIntervalId = setInterval(updateScrollingRow, TEXT_UPDATE_INTERVAL, r);
totalScrolling++;
}
}
if (totalScrolling > 0) {
drawIntervalId = setInterval(draw, TEXT_DRAW_INTERVAL);
}
}
/**
* Takes the first character of the display string and moves it to the end
* of the string to give the illusion of scrolling.
*
* @api private
*/
function updateScrollingRow(row){
var currentText = lcdRows[row].displayText;
var shiftedText = currentText.slice(1) + currentText.slice(0, 1);
lcdRows[row].displayText = shiftedText;
}
/**
* Draws all display strings to the LCD
*
* @api private
*/
function draw() {
//console.log("----------------");
for (var r=0; r<lcdRows.length; r++) {
var text = lcdRows[r].displayText.slice(0, TOTAL_LCD_COLUMNS);
//console.log(text);
lcd.setCursor(r, 0);
lcd.write(text);
}
}
/**
* Clear LCD screen and clear any timer events that might try to write to the screen
*
* @api public
*/
function reset() {
// write blank rows instead of lcd.clear()
// which takes too long to execute and messes up drawn text sometimes
lcd.setCursor(0, 0);
lcd.write(BLANK_ROW_TEXT);
lcd.setCursor(1, 0);
lcd.write(BLANK_ROW_TEXT);
clearInterval(drawIntervalId);
clearTimeout(startScrollingTimeoutId);
for (var r=0; r<lcdRows.length; r++) {
var rowObject = lcdRows[r];
clearInterval(rowObject.updateDisplayTextIntervalId);
}
lcdRows.length = 0;
}
/**
* Return public methods for module.exports.
*/
return {
set: set,
reset: reset
};
}
/* global require: false, setInterval: false, setTimeout: false, clearInterval: false, clearTimeout:false, module: false, console: false */
/**
* Module dependencies.
*/
var mraa = require('mraa');
/*
If you get any mraa missing errors, run this on your board:
$ echo "src mraa-upm http://iotdk.intel.com/repos/1.1/intelgalactic" > /etc/opkg/mraa-upm.conf
$ opkg update
$ opkg install libmraa0
*/
var jsUpmI2cLcd = require('jsupm_i2clcd');
/*
If you get any upm missing errors, run this on your board:
$ opkg install upm
*/
var LcdTextHelper = require('./lcd_text_helper');
/*
Download lcd_text_helper.js from https://gist.github.com/pearlchen/31ac996f8688a01cc5b6
*/
/**
* Initialization.
*/
// Initialize the LCD.
// The 1st param is the BUS ID:
// Intel Edison: Use 6
// Intel Galileo Gen 2: Use 6 (TODO: unconfirmed)
// Intel Galileo Gen 1: Use 0
var lcd = new jsUpmI2cLcd.Jhd1313m1(6, 0x3E, 0x62);
var lcdText = new LcdTextHelper(lcd);
/**
* Try it out with some test messages.
*/
var LCD_MESSAGE_VERY_LONG = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvxyz"; // >16 characters long (48 chars)
var LCD_MESSAGE_LONG = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // >16 characters long (24 chars)
var LCD_MESSAGE_16CHAR = "1234567890ABCDEF"; // ==16 characters long
var LCD_MESSAGE_SHORT = "1234567890"; // <16 characters long (10 chars)
var LCD_MESSAGE_VERY_SHORT = "ABC"; // <16 characters long (3 chars)
lcdText.set([
" Hello,",
" World!"
]);
setTimeout(function(){
lcdText.set([
LCD_MESSAGE_SHORT,
LCD_MESSAGE_VERY_SHORT
]);
}, 5000);
setTimeout(function(){
lcdText.set([
LCD_MESSAGE_LONG,
LCD_MESSAGE_VERY_LONG
]);
}, 10000);
@pearlchen
Copy link
Author

Some quick notes about my implementation:

  • performance-wise, if you want to update the text on the LCD screen when there is already text displayed, it's visually much smoother/faster to just immediately overwrite with new text than to call lcd.clear() first
  • If the text you're using to overwrite is shorter than what's already there, you'll get ghost characters, hence that's why there's BLANK_ROW_TEXT and msg = msg + BLANK_ROW_TEXT.slice(msg.length);

@pearlchen
Copy link
Author

Updated:

  • Breaking changes: removed init(). Pass in lcd instance using the constructor, e.g. var lcdText = new LcdTextHelper(lcd);
  • performance improvements
  • minor bug fixes
  • added JSDocs comments
  • passes jsLint

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