Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…

/* | |
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers | |
* | |
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk> | |
* | |
* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* ---------------------------------------------------------------------------- | |
* This file is designed to be parsed during the build process | |
* | |
* Contains JavaScript interface for Pixl.js (http://www.espruino.com/Pixl.js) | |
* ---------------------------------------------------------------------------- | |
*/ | |
#include <jswrap_pixljs.h> | |
#include "jsinteractive.h" | |
#include "jsdevices.h" | |
#include "jsnative.h" | |
#include "jshardware.h" | |
#include "jsdevices.h" | |
#include "jspin.h" | |
#include "jstimer.h" | |
#include "jswrap_bluetooth.h" | |
#include "nrf_gpio.h" | |
#include "nrf_delay.h" | |
#include "nrf5x_utils.h" | |
#include "jsflash.h" // for jsfRemoveCodeFromFlash | |
#include "bluetooth.h" // for self-test | |
#include "jswrap_graphics.h" | |
#include "lcd_arraybuffer.h" | |
const Pin PIXL_IO_PINS[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}; | |
/*JSON{ | |
"type": "class", | |
"class" : "Pixl" | |
} | |
Class containing utility functions for [Pixl.js](http://www.espruino.com/Pixl.js) | |
*/ | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Pixl", | |
"name" : "getBatteryPercentage", | |
"generate" : "jswrap_espruino_getBattery", | |
"return" : ["int", "A percentage between 0 and 100" ] | |
} | |
DEPRECATED - Please use `E.getBattery()` instead. | |
Return an approximate battery percentage remaining based on | |
a normal CR2032 battery (2.8 - 2.2v) | |
*/ | |
JsVarInt jswrap_pixljs_getBattery() { | |
JsVarFloat v = jshReadVRef(); | |
int pc = (v-2.2)*100/0.6; | |
if (pc>100) pc=100; | |
if (pc<0) pc=0; | |
return pc; | |
} | |
/*JSON{ | |
"type" : "variable", | |
"name" : "SDA", | |
"generate_full" : "4", | |
"ifdef" : "PIXLJS", | |
"return" : ["pin",""] | |
} | |
The pin marked SDA on the Arduino pin footprint. This is connected directly to pin A4. | |
*/ | |
/*JSON{ | |
"type" : "variable", | |
"name" : "SCL", | |
"generate_full" : "5", | |
"ifdef" : "PIXLJS", | |
"return" : ["pin",""] | |
} | |
The pin marked SDA on the Arduino pin footprint. This is connected directly to pin A5. | |
*/ | |
void lcd_wr(int data) { | |
int bit; | |
for (bit=7;bit>=0;bit--) { | |
// TODO: we could push this faster by accessing IO directly | |
jshPinSetValue(LCD_SPI_MOSI, (data>>bit)&1 ); | |
jshPinSetValue(LCD_SPI_SCK, 1 ); | |
jshPinSetValue(LCD_SPI_SCK, 0 ); | |
} | |
} | |
void lcd_flip_gfx(JsGraphics *gfx) { | |
if (gfx->data.modMinX > gfx->data.modMaxX) return; // nothing to do! | |
JsVar *buf = jsvObjectGetChild(gfx->graphicsVar,"buffer",0); | |
if (!buf) return; | |
JSV_GET_AS_CHAR_ARRAY(bPtr, bLen, buf); | |
if (!bPtr || bLen<128*8) return; | |
int xcoord = gfx->data.modMinX&~7; | |
int xlen = gfx->data.modMaxX+1-xcoord; | |
jshPinSetValue(LCD_SPI_CS,0); | |
for (int y=0;y<8;y++) { | |
// skip any lines that don't need updating | |
int ycoord = y*8; | |
if (ycoord > gfx->data.modMaxY || | |
ycoord+7 < gfx->data.modMinY) continue; | |
// Send only what we need | |
jshPinSetValue(LCD_SPI_DC,0); | |
lcd_wr(0xB0|y/* page */); | |
lcd_wr(0x00|(xcoord&15)/* x lower*/); | |
lcd_wr(0x10|(xcoord>>4)/* x upper*/); | |
jshPinSetValue(LCD_SPI_DC,1); | |
char *px = &bPtr[y*128 + (xcoord>>3)]; | |
for (int x=0;x<xlen;x++) { | |
int bit = 128>>(x&7); | |
lcd_wr( | |
((px[ 0]&bit)?1:0) | | |
((px[ 16]&bit)?2:0) | | |
((px[ 32]&bit)?4:0) | | |
((px[ 48]&bit)?8:0) | | |
((px[ 64]&bit)?16:0) | | |
((px[ 80]&bit)?32:0) | | |
((px[ 96]&bit)?64:0) | | |
((px[112]&bit)?128:0)); | |
if ((x&7) == 7) px++; | |
} | |
} | |
jshPinSetValue(LCD_SPI_CS,1); | |
jsvUnLock(buf); | |
// Reset modified-ness | |
gfx->data.modMaxX = -32768; | |
gfx->data.modMaxY = -32768; | |
gfx->data.modMinX = 32767; | |
gfx->data.modMinY = 32767; | |
} | |
/// Send buffer contents to the screen. Usually only the modified data will be output, but if all=true then the whole screen contents is sent | |
void lcd_flip(JsVar *parent, bool all) { | |
JsGraphics gfx; | |
if (!graphicsGetFromVar(&gfx, parent)) return; | |
if (all) { | |
gfx.data.modMinX = 0; | |
gfx.data.modMinY = 0; | |
gfx.data.modMaxX = 127; | |
gfx.data.modMaxY = 63; | |
} | |
lcd_flip_gfx(&gfx); | |
graphicsSetVar(&gfx); | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Pixl", | |
"name" : "setContrast", | |
"generate" : "jswrap_pixljs_setContrast", | |
"params" : [ | |
["c","float","Contrast between 0 and 1"] | |
] | |
} | |
Set the LCD's contrast | |
*/ | |
void jswrap_pixljs_setContrast(JsVarFloat c) { | |
if (c<0) c=0; | |
if (c>1) c=1; | |
jshPinSetValue(LCD_SPI_CS,0); | |
jshPinSetValue(LCD_SPI_DC,0); | |
lcd_wr(0x81); | |
lcd_wr((int)(63*c)); | |
//lcd_wr(0x20|div); div = 0..7 | |
jshPinSetValue(LCD_SPI_CS,1); | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Pixl", | |
"name" : "setLCDPower", | |
"generate" : "jswrap_pixljs_setLCDPower", | |
"params" : [ | |
["isOn","bool","True if the LCD should be on, false if not"] | |
] | |
} | |
This function can be used to turn Pixl.js's LCD off or on. | |
* With the LCD off, Pixl.js draws around 0.1mA | |
* With the LCD on, Pixl.js draws around 0.25mA | |
*/ | |
void jswrap_pixljs_setLCDPower(bool isOn) { | |
jshPinSetValue(LCD_SPI_CS,0); | |
jshPinSetValue(LCD_SPI_DC,0); | |
if (isOn) { | |
lcd_wr(0xA4); // cancel pixel on | |
lcd_wr(0xAF); // display on | |
} else { | |
lcd_wr(0xAE); // display off | |
lcd_wr(0xA5); // all pixels on | |
} | |
jshPinSetValue(LCD_SPI_CS,1); | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Pixl", | |
"name" : "lcdw", | |
"generate" : "jswrap_pixljs_lcdw", | |
"params" : [ | |
["c","int",""] | |
] | |
} | |
Writes a command directly to the ST7567 LCD controller | |
*/ | |
void jswrap_pixljs_lcdw(JsVarInt c) { | |
jshPinSetValue(LCD_SPI_CS,0); | |
jshPinSetValue(LCD_SPI_DC,0); | |
lcd_wr(c); | |
jshPinSetValue(LCD_SPI_CS,1); | |
} | |
static bool selftest_check_pin(Pin pin) { | |
unsigned int i; | |
bool ok = true; | |
jshPinSetState(pin, JSHPINSTATE_GPIO_OUT); | |
jshPinSetValue(pin, 1); | |
jshPinSetState(pin, JSHPINSTATE_GPIO_IN_PULLUP); | |
if (!jshPinGetValue(pin)) { | |
jsiConsolePrintf("Pin %p forced low\n", pin); | |
ok = false; | |
} | |
for (i=0;i<sizeof(PIXL_IO_PINS)/sizeof(Pin);i++) | |
if (PIXL_IO_PINS[i]!=pin) | |
jshPinOutput(PIXL_IO_PINS[i], 0); | |
if (!jshPinGetValue(pin)) { | |
jsiConsolePrintf("Pin %p shorted low\n", pin); | |
ok = false; | |
} | |
jshPinSetState(pin, JSHPINSTATE_GPIO_OUT); | |
jshPinSetValue(pin, 0); | |
jshPinSetState(pin, JSHPINSTATE_GPIO_IN_PULLDOWN); | |
if (jshPinGetValue(pin)) { | |
jsiConsolePrintf("Pin %p forced high\n", pin); | |
ok = false; | |
} | |
for (i=0;i<sizeof(PIXL_IO_PINS)/sizeof(Pin);i++) | |
if (PIXL_IO_PINS[i]!=pin) | |
jshPinOutput(PIXL_IO_PINS[i], 1); | |
if (jshPinGetValue(pin)) { | |
jsiConsolePrintf("Pin %p shorted high\n", pin); | |
ok = false; | |
} | |
jshPinSetState(pin, JSHPINSTATE_GPIO_IN); | |
return ok; | |
} | |
static bool pixl_selfTest() { | |
unsigned int timeout, i; | |
JsVarFloat v; | |
bool ok = true; | |
// light up all LEDs white | |
jshPinOutput(LED1_PININDEX, LED1_ONSTATE); | |
jshPinSetState(BTN1_PININDEX, BTN1_PINSTATE); | |
v = jshReadVRef(); | |
if (v<3.2 || v>3.4) { | |
jsiConsolePrintf("VCC out of range 3.2-3.4 (%f)\n", v); | |
ok = false; | |
} | |
if (jshPinGetValue(BTN1_PININDEX)==BTN1_ONSTATE) | |
jsiConsolePrintf("Release BTN1\n"); | |
if (jshPinGetValue(BTN4_PININDEX)==BTN4_ONSTATE) | |
jsiConsolePrintf("Release BTN4\n"); | |
timeout = 2000; | |
while ((jshPinGetValue(BTN1_PININDEX)==BTN1_ONSTATE || | |
jshPinGetValue(BTN4_PININDEX)==BTN4_ONSTATE) && timeout--) | |
nrf_delay_ms(1); | |
if (jshPinGetValue(BTN1_PININDEX)==BTN1_ONSTATE) { | |
jsiConsolePrintf("BTN1 stuck down\n"); | |
ok = false; | |
} | |
if (jshPinGetValue(BTN2_PININDEX)==BTN1_ONSTATE) { | |
jsiConsolePrintf("BTN2 stuck down\n"); | |
ok = false; | |
} | |
if (jshPinGetValue(BTN3_PININDEX)==BTN1_ONSTATE) { | |
jsiConsolePrintf("BTN3 stuck down\n"); | |
ok = false; | |
} | |
if (jshPinGetValue(BTN4_PININDEX)==BTN1_ONSTATE) { | |
jsiConsolePrintf("BTN4 stuck down\n"); | |
ok = false; | |
} | |
nrf_delay_ms(100); | |
jshPinInput(LED1_PININDEX); | |
nrf_delay_ms(500); | |
jshPinSetState(LED1_PININDEX, JSHPINSTATE_GPIO_IN_PULLUP); | |
nrf_delay_ms(1); | |
v = jshPinAnalog(LED1_PININDEX); | |
jshPinSetState(LED1_PININDEX, JSHPINSTATE_GPIO_IN); | |
if (v<0.3 || v>0.65) { | |
jsiConsolePrintf("Backlight OOR (%f)\n", v); | |
ok = false; | |
} | |
for (i=0;i<sizeof(PIXL_IO_PINS)/sizeof(Pin);i++) { | |
if (!selftest_check_pin(PIXL_IO_PINS[i])) ok = false; | |
} | |
for (i=0;i<sizeof(PIXL_IO_PINS)/sizeof(Pin);i++) | |
jshPinSetState(PIXL_IO_PINS[i], JSHPINSTATE_GPIO_IN); | |
if (jshHasEvents()) { | |
jsiConsolePrintf("Have events - no BLE test\n"); | |
} else { | |
uint32_t err_code; | |
err_code = jsble_set_scanning(true, false); | |
jsble_check_error(err_code); | |
int timeout = 20; | |
while (timeout-- && !jshHasEvents()) { | |
nrf_delay_ms(100); | |
} | |
err_code = jsble_set_scanning(false, false); | |
jsble_check_error(err_code); | |
if (!jshHasEvents()) { | |
jsiConsolePrintf("No BLE adverts found in 2s\n"); | |
ok = false; | |
} | |
} | |
return ok; | |
} | |
/*JSON{ | |
"type" : "init", | |
"generate" : "jswrap_pixljs_init" | |
}*/ | |
void jswrap_pixljs_init() { | |
// LCD Init 1 | |
jshPinOutput(LCD_SPI_CS,0); | |
jshPinOutput(LCD_SPI_DC,0); | |
jshPinOutput(LCD_SPI_SCK,0); | |
jshPinOutput(LCD_SPI_MOSI,0); | |
jshPinOutput(LCD_SPI_RST,0); | |
// Create backing graphics for LCD | |
JsVar *graphics = jspNewObject(0, "Graphics"); | |
if (!graphics) return; // low memory | |
JsGraphics gfx; | |
graphicsStructInit(&gfx,128,64,1); | |
gfx.data.type = JSGRAPHICSTYPE_ARRAYBUFFER; | |
gfx.data.flags = JSGRAPHICSFLAGS_ARRAYBUFFER_MSB; | |
gfx.graphicsVar = graphics; | |
lcdInit_ArrayBuffer(&gfx); | |
graphicsSetVar(&gfx); | |
jsvObjectSetChild(execInfo.root, "g", graphics); | |
jsvObjectSetChild(execInfo.hiddenRoot, JS_GRAPHICS_VAR, graphics); | |
graphicsGetFromVar(&gfx, graphics); | |
// Set initial image | |
const unsigned char PIXLJS_IMG[] = { | |
251, 239, 135, 192, 0, 0, 31, 0, 0, 0, 125, 247, 195, 224, 0, 0, 15, 128, 0, | |
0, 62, 251, 225, 240, 0, 0, 7, 192, 0, 0, 31, 125, 240, 248, 0, 0, 3, 224, 0, | |
0, 15, 190, 248, 124, 0, 0, 1, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, | |
224, 62, 0, 15, 128, 248, 124, 0, 0, 1, 240, 31, 0, 7, 192, 124, 62, 0, 0, 0, | |
248, 15, 128, 3, 224, 62, 31, 0, 0, 0, 124, 7, 192, 1, 240, 31, 15, 128, 0, | |
0, 62, 3, 224, 0, 248, 15, 135, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, | |
190, 248, 124, 0, 248, 1, 240, 0, 0, 7, 223, 124, 62, 0, 124, 0, 248, 1, 128, | |
3, 239, 190, 31, 0, 62, 0, 124, 1, 224, 1, 247, 223, 15, 128, 31, 0, 62, 0, | |
240, 0, 251, 239, 135, 192, 15, 128, 31, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
0, 62, 0, 1, 240, 248, 15, 135, 223, 30, 31, 223, 0, 0, 248, 124, 7, 195, 239, | |
143, 31, 255, 128, 0, 124, 62, 3, 225, 247, 199, 158, 55, 192, 0, 62, 31, 1, | |
240, 251, 227, 199, 131, 224, 0, 31, 15, 128, 248, 125, 241, 227, 240, 0, 0, | |
0, 0, 0, 0, 0, 0, 240, 254, 0, 0, 0, 0, 0, 0, 0, 0, 120, 31, 128, 0, 0, 0, 0, | |
0, 0, 12, 60, 3, 192, 0, 0, 0, 0, 0, 0, 15, 30, 49, 224, 0, 0, 0, 0, 0, 0, 7, | |
143, 63, 240, 0, 0, 0, 0, 0, 0, 1, 135, 143, 224, 0, 0, 0, 0, 0, 0, 0, 3, 192, | |
0, 0, 0, 0, 0, 0, 0, 0, 1, 224, 0, 0, 0, 0, 0, 0, 0, 0, 7, 224, 0, 0, 0, 0, 0, | |
0, 0, 0, 3, 240, 0, 0, 0, 0, 0, 0, 0, 0, 1, 240, 0, 63 | |
}; | |
// Create 'flip' fn | |
JsVar *fn; | |
fn = jsvNewNativeFunction((void (*)(void))lcd_flip, JSWAT_VOID|JSWAT_THIS_ARG|(JSWAT_BOOL << (JSWAT_BITS*1))); | |
jsvObjectSetChildAndUnLock(graphics,"flip",fn); | |
// LCD init 2 | |
jshDelayMicroseconds(10000); | |
jshPinSetValue(LCD_SPI_RST,1); | |
jshDelayMicroseconds(10000); | |
const unsigned char LCD_SPI_INIT_DATA[] = { | |
//0xE2, // soft reset | |
0xA2, // bias 1/9 | |
//0xA3, // bias 1/7 | |
0xC8, // reverse scan dir | |
0x81, // contrast control (next byte) | |
35, // actual contrast (0..63) | |
0x25, // regulation resistor ratio (0..7) | |
0x2F, // control power circuits - last 3 bits = VB/VR/VF | |
0xF8, 1, // Set boost level to 5x - draws maybe 0.04mA more, but much better blacks | |
0xA0, // start at column 128 | |
0xAF // disp on | |
}; | |
for (unsigned int i=0;i<sizeof(LCD_SPI_INIT_DATA);i++) | |
lcd_wr(LCD_SPI_INIT_DATA[i]); | |
jshPinSetValue(LCD_SPI_CS,1); | |
/* If the button is pressed during reset, perform a self test. | |
* With bootloader this means apply power while holding button for >3 secs */ | |
static bool firstStart = true; | |
JsVar *splashScreen = jsfReadFile(jsfNameFromString(".splash")); | |
if (jsvIsString(splashScreen)) { | |
if (jsvGetStringLength(splashScreen)) { | |
graphicsSetVar(&gfx); | |
jsvUnLock(jswrap_graphics_drawImage(graphics, splashScreen,0,0,0)); | |
graphicsGetFromVar(&gfx, graphics); | |
} | |
} else { | |
if (firstStart) { | |
// animate logo in | |
for (int i=128;i>24;i-=4) { | |
lcd_flip_gfx(&gfx); | |
graphicsClear(&gfx); | |
graphicsDrawImage1bpp(&gfx,i,15,81,34,PIXLJS_IMG); | |
} | |
} else { | |
// if a standard reset, just display logo | |
graphicsClear(&gfx); | |
graphicsDrawImage1bpp(&gfx,24,15,81,34,PIXLJS_IMG); | |
} | |
jswrap_graphics_drawCString(&gfx,28,39,JS_VERSION); | |
// Write MAC address in bottom right | |
JsVar *addr = jswrap_ble_getAddress(); | |
char buf[20]; | |
jsvGetString(addr, buf, sizeof(buf)); | |
jsvUnLock(addr); | |
jswrap_graphics_drawCString(&gfx,127-strlen(buf)*4,59,buf); | |
} | |
lcd_flip_gfx(&gfx); | |
jsvUnLock(splashScreen); | |
if (firstStart && (jshPinGetValue(BTN1_PININDEX) == BTN1_ONSTATE || jshPinGetValue(BTN4_PININDEX) == BTN4_ONSTATE)) { | |
// don't do it during a software reset - only first hardware reset | |
jsiConsolePrintf("SELF TEST\n"); | |
if (pixl_selfTest()) jsiConsolePrintf("Test passed!\n"); | |
} | |
// If the button is *still* pressed, remove all code from flash memory too! | |
if (firstStart && jshPinGetValue(BTN1_PININDEX) == BTN1_ONSTATE) { | |
jsfRemoveCodeFromFlash(); | |
jsiConsolePrintf("Removed saved code from Flash\n"); | |
} | |
graphicsSetVar(&gfx); | |
firstStart = false; | |
jsvUnLock(graphics); | |
} | |
/*JSON{ | |
"type" : "kill", | |
"generate" : "jswrap_pixljs_kill" | |
}*/ | |
void jswrap_pixljs_kill() { | |
} | |
/*JSON{ | |
"type" : "idle", | |
"generate" : "jswrap_pixljs_idle" | |
}*/ | |
bool jswrap_pixljs_idle() { | |
return false; | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Pixl", | |
"name" : "menu", | |
"generate" : "gen_jswrap_E_showMenu", | |
"params" : [ | |
["menu","JsVar","An object containing name->function mappings to to be used in a menu"] | |
], | |
"return" : ["JsVar", "A menu object with `draw`, `move` and `select` functions" ] | |
} | |
Display a menu on Pixl.js's screen, and set up the buttons to navigate through it. | |
DEPRECATED: Use `E.showMenu` | |
*/ | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "E", | |
"name" : "showMenu", | |
"generate_js" : "libs/js/pixljs/E_showMenu.min.js", | |
"params" : [ | |
["menu","JsVar","An object containing name->function mappings to to be used in a menu"] | |
], | |
"return" : ["JsVar", "A menu object with `draw`, `move` and `select` functions" ] | |
} | |
Display a menu on Pixl.js's screen, and set up the buttons to navigate through it. | |
Supply an object containing menu items. When an item is selected, the | |
function it references will be executed. For example: | |
``` | |
var boolean = false; | |
var number = 50; | |
// First menu | |
var mainmenu = { | |
"" : { "title" : "-- Main Menu --" }, | |
"Backlight On" : function() { LED1.set(); }, | |
"Backlight Off" : function() { LED1.reset(); }, | |
"Submenu" : function() { E.showMenu(submenu); }, | |
"A Boolean" : { | |
value : boolean, | |
format : v => v?"On":"Off", | |
onchange : v => { boolean=v; } | |
}, | |
"A Number" : { | |
value : number, | |
min:0,max:100,step:10, | |
onchange : v => { number=v; } | |
}, | |
"Exit" : function() { E.showMenu(); }, | |
}; | |
// Submenu | |
var submenu = { | |
"" : { "title" : "-- SubMenu --" }, | |
"One" : undefined, // do nothing | |
"Two" : undefined, // do nothing | |
"< Back" : function() { E.showMenu(mainmenu); }, | |
}; | |
// Actually display the menu | |
Pixl.menu(mainmenu); | |
``` | |
See http://www.espruino.com/graphical_menu for more detailed information. | |
*/ | |