Permalink
Cannot retrieve contributors at this time
/* | |
* 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 Puck.js | |
* ---------------------------------------------------------------------------- | |
*/ | |
#include "jswrap_puck.h" | |
#include "jsinteractive.h" | |
#include "jsdevices.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 "ble_gap.h" | |
#include "jsflash.h" // for jsfRemoveCodeFromFlash | |
#include "jsi2c.h" // accelerometer/etc | |
#define MAG3110_ADDR 0x0E | |
#define LIS3MDL_ADDR MAG_ADDR | |
#define I2C_TIMEOUT 100000 | |
JshI2CInfo i2cMag; | |
JshI2CInfo i2cAccel; | |
JshI2CInfo i2cTemp; | |
bool isPuckV2 = false; | |
const Pin PUCK_IO_PINS[] = {1,2,4,6,7,8,23,24,28,29,30,31}; | |
#define IR_INPUT_PIN 25 // Puck v2 | |
#define IR_FET_PIN 27 // Puck v2 | |
#define FET_PIN 26 // Puck v2 | |
bool mag_enabled = false; //< Has the magnetometer been turned on? | |
int16_t mag_reading[3]; //< magnetometer xyz reading | |
bool accel_enabled = false; //< Has the accelerometer been turned on? | |
int16_t accel_reading[3]; | |
int16_t gyro_reading[3]; | |
JsVar *to_xyz(int16_t d[3], double scale) { | |
JsVar *obj = jsvNewObject(); | |
if (!obj) return 0; | |
jsvObjectSetChildAndUnLock(obj,"x",jsvNewFromFloat(d[0]*scale)); | |
jsvObjectSetChildAndUnLock(obj,"y",jsvNewFromFloat(d[1]*scale)); | |
jsvObjectSetChildAndUnLock(obj,"z",jsvNewFromFloat(d[2]*scale)); | |
return obj; | |
} | |
/// MAG3110 I2C implementation - write pin | |
void wr(int pin, bool state) { | |
if (state) { | |
nrf_gpio_pin_set(pin); nrf_gpio_cfg_output(pin); | |
nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_PULLUP); | |
} else { | |
nrf_gpio_pin_clear(pin); | |
nrf_gpio_cfg_output(pin); | |
} | |
} | |
/// MAG3110 I2C implementation - read pin | |
bool rd(int pin) { | |
return nrf_gpio_pin_read(pin); | |
} | |
/// MAG3110 I2C implementation - delay | |
void dly() { | |
volatile int i; | |
for (i=0;i<10;i++); | |
} | |
/// MAG3110 I2C implementation - show error | |
void err(const char *s) { | |
jsiConsolePrintf("I2C: %s\n", s); | |
} | |
/// MAG3110 I2C implementation has i2c started? | |
bool started = false; | |
/// MAG3110 I2C implementation - start bit | |
void i2c_start() { | |
if (started) { | |
// reset | |
wr(MAG_PIN_SDA, 1); | |
dly(); | |
wr(MAG_PIN_SCL, 1); | |
int timeout = I2C_TIMEOUT; | |
while (!rd(MAG_PIN_SCL) && --timeout); // clock stretch | |
if (!timeout) err("Timeout (start)"); | |
dly(); | |
} | |
if (!rd(MAG_PIN_SDA)) err("Arbitration (start)"); | |
wr(MAG_PIN_SDA, 0); | |
dly(); | |
wr(MAG_PIN_SCL, 0); | |
dly(); | |
started = true; | |
} | |
/// MAG3110 I2C implementation - stop bit | |
void i2c_stop() { | |
wr(MAG_PIN_SDA, 0); | |
dly(); | |
wr(MAG_PIN_SCL, 1); | |
int timeout = I2C_TIMEOUT; | |
while (!rd(MAG_PIN_SCL) && --timeout); // clock stretch | |
if (!timeout) err("Timeout (stop)"); | |
dly(); | |
wr(MAG_PIN_SDA, 1); | |
dly(); | |
if (!rd(MAG_PIN_SDA)) err("Arbitration (stop)"); | |
dly(); | |
started = false; | |
} | |
/// MAG3110 I2C implementation - write bit | |
void i2c_wr_bit(bool b) { | |
wr(MAG_PIN_SDA, b); | |
dly(); | |
wr(MAG_PIN_SCL, 1); | |
dly(); | |
int timeout = I2C_TIMEOUT; | |
while (!rd(MAG_PIN_SCL) && --timeout); // clock stretch | |
if (!timeout) err("Timeout (wr)"); | |
wr(MAG_PIN_SCL, 0); | |
wr(MAG_PIN_SDA, 1); // stop forcing SDA (needed?) | |
} | |
/// MAG3110 I2C implementation - read bit | |
bool i2c_rd_bit() { | |
wr(MAG_PIN_SDA, 1); // stop forcing SDA | |
dly(); | |
wr(MAG_PIN_SCL, 1); // stop forcing SDA | |
int timeout = I2C_TIMEOUT; | |
while (!rd(MAG_PIN_SCL) && --timeout); // clock stretch | |
if (!timeout) err("Timeout (rd)"); | |
dly(); | |
bool b = rd(MAG_PIN_SDA); | |
wr(MAG_PIN_SCL, 0); | |
return b; | |
} | |
/// MAG3110 I2C implementation - write byte, true on ack, false on nack | |
bool i2c_wr(uint8_t data) { | |
int i; | |
for (i=0;i<8;i++) { | |
i2c_wr_bit(data&128); | |
data <<= 1; | |
} | |
return !i2c_rd_bit(); | |
} | |
/// MAG3110 I2C implementation - read byte | |
uint8_t i2c_rd(bool nack) { | |
int i; | |
int data = 0; | |
for (i=0;i<8;i++) | |
data = (data<<1) | (i2c_rd_bit()?1:0); | |
i2c_wr_bit(nack); | |
return data; | |
} | |
void mag_pin_on() { | |
jshPinSetValue(MAG_PIN_PWR, 1); | |
jshPinSetValue(MAG_PIN_SCL, 1); | |
jshPinSetValue(MAG_PIN_SDA, 1); | |
jshPinSetState(MAG_PIN_PWR, JSHPINSTATE_GPIO_OUT); | |
jshPinSetState(MAG_PIN_SCL, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP); | |
jshPinSetState(MAG_PIN_SDA, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP); | |
if (!isPuckV2) { | |
// IRQ line on Puck.js is often 0 - don't pull up | |
jshPinSetState(MAG_PIN_INT, JSHPINSTATE_GPIO_IN); | |
} else { | |
// using DRDY for data | |
jshPinSetState(MAG_PIN_DRDY, JSHPINSTATE_GPIO_IN); | |
} | |
} | |
// Turn magnetometer on and configure | |
bool mag_on(int milliHz) { | |
//jsiConsolePrintf("mag_on\n"); | |
mag_pin_on(); | |
unsigned char buf[2]; | |
if (isPuckV2) { // LIS3MDL | |
jshDelayMicroseconds(10000); // takes ages to start up | |
bool lowPower = false; | |
int reg1 = 0x80; // temp sensor, low power | |
if (milliHz == 80000) reg1 = 7<<2; // 900uA | |
else if (milliHz == 40000) reg1 = 6<<2; // 550uA | |
else if (milliHz == 20000) reg1 = 5<<2; // 275uA | |
else if (milliHz == 10000) reg1 = 4<<2; // 137uA | |
else if (milliHz == 5000) reg1 = 3<<2; // 69uA | |
else if (milliHz == 2500) reg1 = 2<<2; // 34uA | |
else if (milliHz == 1250) reg1 = 1<<2; // 17uA | |
else if (milliHz <= 630) { /*if (milliHz == 630 || milliHz == 625)*/ | |
// We just go for the lowest power mode | |
reg1 = 0<<2; // 8uA | |
lowPower = true; | |
} | |
else return false; | |
buf[0] = 0x21; buf[1]=0x00; // CTRL_REG2 - full scale +-4 gauss | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 2, buf, true); | |
buf[0] = 0x20; buf[1]=reg1; // CTRL_REG1 | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 2, buf, true); | |
buf[0] = 0x23; buf[1]=0x02; // CTRL_REG4 - low power, LSb at higher address (to match MAG3110) | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 2, buf, true); | |
buf[0] = 0x22; buf[1]=lowPower ? 0x20 : 0x00; // CTRL_REG3 - normal or low power, continuous measurement | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 2, buf, true); | |
buf[0] = 0x24; buf[1]=0x40; // CTRL_REG5 - block data update | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 2, buf, true); | |
} else { // MAG3110 | |
jshDelayMicroseconds(2500); // 1.7ms from power on to ok | |
int reg1 = 0; | |
if (milliHz == 80000) reg1 |= (0x00)<<3; // 900uA | |
else if (milliHz == 40000) reg1 |= (0x04)<<3; // 550uA | |
else if (milliHz == 20000) reg1 |= (0x08)<<3; // 275uA | |
else if (milliHz == 10000) reg1 |= (0x0C)<<3; // 137uA | |
else if (milliHz == 5000) reg1 |= (0x10)<<3; // 69uA | |
else if (milliHz == 2500) reg1 |= (0x14)<<3; // 34uA | |
else if (milliHz == 1250) reg1 |= (0x18)<<3; // 17uA | |
else if (milliHz == 630) reg1 |= (0x1C)<<3; // 8uA | |
else if (milliHz == 310) reg1 |= (0x1D)<<3; // 8uA | |
else if (milliHz == 160) reg1 |= (0x1E)<<3; // 8uA | |
else if (milliHz == 80) reg1 |= (0x1F)<<3; // 8uA | |
else return false; | |
wr(MAG_PIN_SDA, 1); | |
wr(MAG_PIN_SCL, 1); | |
jshDelayMicroseconds(2000); // 1.7ms from power on to ok | |
i2c_start(); | |
i2c_wr(MAG3110_ADDR<<1); // CTRL_REG2, AUTO_MRST_EN | |
i2c_wr(0x11); | |
i2c_wr(0x80/*AUTO_MRST_EN*/ + 0x20/*RAW*/); | |
i2c_stop(); | |
i2c_start(); | |
i2c_wr(MAG3110_ADDR<<1); // CTRL_REG1, active mode 80 Hz ODR with OSR = 0 | |
i2c_wr(0x10); | |
reg1 |= 1; // Active bit | |
i2c_wr(reg1); | |
i2c_stop(); | |
} | |
return true; | |
} | |
// Wait for magnetometer IRQ line to be set | |
void mag_wait() { | |
int timeout; | |
if (isPuckV2) { | |
unsigned char buf[1]; | |
timeout = 400; | |
do { | |
buf[0] = 0x27; // STATUS_REG | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 1, buf, false); | |
jsi2cRead(&i2cMag, LIS3MDL_ADDR, 1, buf, true); | |
//jsiConsolePrintf("M %d\n", buf[0]); | |
} while (!(buf[0]&0x08) && --timeout); // ZYXDA | |
} else { | |
timeout = I2C_TIMEOUT*2; | |
while (!nrf_gpio_pin_read(MAG_PIN_INT) && --timeout); | |
} | |
if (!timeout) jsExceptionHere(JSET_INTERNALERROR, "Timeout (Magnetometer)"); | |
} | |
// Read a value | |
void mag_read() { | |
unsigned char buf[6]; | |
if (isPuckV2) { | |
buf[0] = 0x28; | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 1, buf, false); | |
jsi2cRead(&i2cMag, LIS3MDL_ADDR, 6, buf, true); | |
mag_reading[0] = (buf[0]<<8) | buf[1]; | |
mag_reading[1] = (buf[2]<<8) | buf[3]; | |
mag_reading[2] = (buf[4]<<8) | buf[5]; | |
} else { // Puck.js v1 | |
buf[0] = 1; | |
jsi2cWrite(&i2cMag, MAG3110_ADDR, 1, buf, false); | |
jsi2cRead(&i2cMag, MAG3110_ADDR, 6, buf, true); | |
i2c_start(); | |
i2c_wr(MAG3110_ADDR<<1); | |
i2c_wr(1); // OUT_X_MSB | |
i2c_start(); | |
i2c_wr(1|(MAG3110_ADDR<<1)); | |
mag_reading[0] = i2c_rd(false)<<8; | |
mag_reading[0] |= i2c_rd(false); | |
mag_reading[1] = i2c_rd(false)<<8; | |
mag_reading[1] |= i2c_rd(false); | |
mag_reading[2] = i2c_rd(false)<<8; | |
mag_reading[2] |= i2c_rd(true); | |
i2c_stop(); | |
} | |
} | |
// Get temperature, shifted right 8 bits | |
int mag_read_temp() { | |
unsigned char buf[2]; | |
if (isPuckV2) { | |
buf[0] = 0x2E; // TEMP_OUT_L | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 1, buf, false); | |
jsi2cRead(&i2cMag, LIS3MDL_ADDR, 2, buf, true); | |
int16_t t = buf[0] | (buf[1]<<8); | |
return (int)t; | |
} else { // Puck.js v1 | |
i2c_start(); | |
i2c_wr(MAG3110_ADDR<<1); | |
i2c_wr(15); // DIE_TEMP | |
i2c_start(); | |
i2c_wr(1|(MAG3110_ADDR<<1)); | |
int8_t temp = i2c_rd(true); | |
i2c_stop(); | |
return temp<<8; | |
} | |
} | |
// Turn magnetometer off | |
void mag_off() { | |
//jsiConsolePrintf("mag_off\n"); | |
nrf_gpio_cfg_default(MAG_PIN_SDA); | |
nrf_gpio_cfg_default(MAG_PIN_SCL); | |
nrf_gpio_cfg_default(MAG_PIN_INT); | |
nrf_gpio_cfg_default(MAG_PIN_DRDY); | |
nrf_gpio_pin_clear(MAG_PIN_PWR); | |
nrf_gpio_cfg_output(MAG_PIN_PWR); | |
} | |
bool accel_on(int milliHz) { | |
// CTRL1_XL / CTRL2_G | |
int reg = 0; | |
bool gyro = true; | |
if (milliHz<12500) { // 1.6Hz, no gyro | |
reg = 11<<4; | |
gyro = false; | |
} else if (milliHz==12500) reg=1<<4; // 12.5 Hz (low power) | |
else if (milliHz==26000) reg=2<<4; // 26 Hz (low power) | |
else if (milliHz==52000) reg=3<<4; // 52 Hz (low power) | |
else if (milliHz==104000) reg=4<<4; // 104 Hz (normal mode) | |
else if (milliHz==208000) reg=5<<4; // 208 Hz (normal mode) | |
else if (milliHz==416000) reg=6<<4; // 416 Hz (high performance) | |
else if (milliHz==833000) reg=7<<4; // 833 Hz (high performance) | |
else if (milliHz==1660000) reg=8<<4; // 1.66 kHz (high performance) | |
else return false; | |
jshPinSetState(ACCEL_PIN_INT, JSHPINSTATE_GPIO_IN); | |
#ifdef ACCEL_PIN_PWR | |
jshPinSetState(ACCEL_PIN_PWR, JSHPINSTATE_GPIO_OUT); | |
#endif | |
jshPinSetState(ACCEL_PIN_SCL, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP); | |
jshPinSetState(ACCEL_PIN_SDA, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP); | |
#ifdef ACCEL_PIN_PWR | |
jshPinSetValue(ACCEL_PIN_PWR, 1); | |
#endif | |
jshPinSetValue(ACCEL_PIN_SCL, 1); | |
jshPinSetValue(ACCEL_PIN_SDA, 1); | |
jshDelayMicroseconds(20000); // 20ms boot from app note | |
// LSM6DS3TR | |
unsigned char buf[2]; | |
buf[0] = 0x15; buf[1]=0x10; // CTRL6-C - XL_HM_MODE=1, low power accelerometer | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true); | |
buf[0] = 0x16; buf[1]=0x80; // CTRL6-C - G_HM_MODE=1, low power gyro | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true); | |
buf[0] = 0x18; buf[1]=0x38; // CTRL9_XL Acc X, Y, Z axes enabled | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true); | |
buf[0] = 0x10; buf[1]=reg | 0b00001011; // CTRL1_XL Accelerometer, +-4g, 50Hz AA filter | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true); | |
buf[0] = 0x11; buf[1]=gyro ? reg : 0; // CTRL2_G Gyro, 250 dps, no 125dps limit | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true); | |
buf[0] = 0x12; buf[1]=0x44; // CTRL3_C, BDU, irq active high, push pull, auto-inc | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true); | |
buf[0] = 0x0D; buf[1]=3; // INT1_CTRL - Gyro/accel data ready IRQ | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true); | |
return true; | |
} | |
// Read a value | |
void accel_read() { | |
unsigned char buf[12]; | |
buf[0] = 0x22; // OUTX_L_G | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 1, buf, false); | |
jsi2cRead(&i2cAccel, ACCEL_ADDR, 12, buf, true); | |
gyro_reading[0] = (buf[1]<<8) | buf[0]; | |
gyro_reading[1] = (buf[3]<<8) | buf[2]; | |
gyro_reading[2] = (buf[5]<<8) | buf[4]; | |
accel_reading[0] = (buf[7]<<8) | buf[6]; | |
accel_reading[1] = (buf[9]<<8) | buf[8]; | |
accel_reading[2] = (buf[11]<<8) | buf[10]; | |
} | |
void accel_wait() { | |
int timeout; | |
unsigned char buf[1]; | |
timeout = 400; | |
do { | |
buf[0] = 0x1E; // STATUS_REG | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 1, buf, false); | |
jsi2cRead(&i2cAccel, ACCEL_ADDR, 1, buf, true); | |
//jsiConsolePrintf("M %d\n", buf[0]); | |
} while (!(buf[0]&3) && --timeout); // ZYXDA | |
if (!timeout) jsExceptionHere(JSET_INTERNALERROR, "Timeout (Accelerometer)"); | |
} | |
// Turn accelerometer off | |
void accel_off() { | |
#ifdef ACCEL_PIN_PWR | |
nrf_gpio_cfg_input(ACCEL_PIN_SDA, NRF_GPIO_PIN_NOPULL); | |
nrf_gpio_cfg_input(ACCEL_PIN_SCL, NRF_GPIO_PIN_NOPULL); | |
nrf_gpio_cfg_input(ACCEL_PIN_INT, NRF_GPIO_PIN_NOPULL); | |
nrf_gpio_pin_clear(ACCEL_PIN_PWR); | |
nrf_gpio_cfg_output(ACCEL_PIN_PWR); | |
#else | |
unsigned char buf[2]; | |
buf[0] = 0x10; buf[1]=0; // CTRL1_XL - power down | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true); | |
buf[0] = 0x11; buf[1]=0; // CTRL2_G - power down | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true); | |
#endif | |
} | |
/*JSON{ | |
"type": "class", | |
"class" : "Puck", | |
"ifdef" : "PUCKJS" | |
} | |
Class containing [Puck.js's](http://www.puck-js.com) utility functions. | |
*/ | |
/*JSON{ | |
"type" : "variable", | |
"name" : "FET", | |
"generate_full" : "26", | |
"ifdef" : "PUCKJS", | |
"return" : ["pin",""] | |
} | |
On Puck.js V2 (not v1.0) this is the pin that controls the FET, for high-powered outputs. | |
*/ | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "mag", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_mag", | |
"return" : ["JsVar", "An Object `{x,y,z}` of magnetometer readings as integers" ] | |
} | |
Turn on the magnetometer, take a single reading, and then turn it off again. | |
An object of the form `{x,y,z}` is returned containing magnetometer readings. | |
Due to residual magnetism in the Puck and magnetometer itself, with | |
no magnetic field the Puck will not return `{x:0,y:0,z:0}`. | |
Instead, it's up to you to figure out what the 'zero value' is for your | |
Puck in your location and to then subtract that from the value returned. If | |
you're not trying to measure the Earth's magnetic field then it's a good idea | |
to just take a reading at startup and use that. | |
With the aerial at the top of the board, the `y` reading is vertical, `x` is | |
horizontal, and `z` is through the board. | |
Readings are in increments of 0.1 micro Tesla (uT). The Earth's magnetic field | |
varies from around 25-60 uT, so the reading will vary by 250 to 600 depending | |
on location. | |
*/ | |
JsVar *jswrap_puck_mag() { | |
/* If not enabled, turn on and read. If enabled, | |
* just pass out the last reading */ | |
if (!mag_enabled) { | |
mag_on(80000); | |
mag_wait(); | |
mag_read(); | |
mag_off(); | |
} | |
return to_xyz(mag_reading, 1); | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "magTemp", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_magTemp", | |
"return" : ["float", "Temperature in degrees C" ] | |
} | |
Turn on the magnetometer, take a single temperature reading from the MAG3110 chip, and then turn it off again. | |
(If the magnetometer is already on, this just returns the last reading obtained) | |
`E.getTemperature()` uses the microcontroller's temperature sensor, but this uses the magnetometer's. | |
The reading obtained is an integer (so no decimal places), but the sensitivity is factory trimmed. to 1°C, however the temperature | |
offset isn't - so absolute readings may still need calibrating. | |
*/ | |
JsVarFloat jswrap_puck_magTemp() { | |
JsVarInt t; | |
if (!mag_enabled) { | |
mag_on(80000); | |
mag_wait(); | |
t = mag_read_temp(); | |
mag_off(); | |
} else | |
t = mag_read_temp(); | |
return t / 256.0; | |
} | |
/*JSON{ | |
"type" : "event", | |
"class" : "Puck", | |
"name" : "mag", | |
"ifdef" : "PUCKJS" | |
} | |
Called after `Puck.magOn()` every time magnetometer data | |
is sampled. There is one argument which is an object | |
of the form `{x,y,z}` containing magnetometer readings | |
as integers (for more information see `Puck.mag()`). | |
Check out [the Puck.js page on the magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) | |
for more information. | |
*/ | |
/*JSON{ | |
"type" : "event", | |
"class" : "Puck", | |
"name" : "accel", | |
"ifdef" : "PUCKJS" | |
} | |
Only on Puck.js v2.0 | |
Called after `Puck.accelOn()` every time accelerometer data | |
is sampled. There is one argument which is an object | |
of the form `{acc:{x,y,z}, gyro:{x,y,z}}` containing the data. | |
The data is as it comes off the accelerometer and is not | |
scaled to 1g. For more information see `Puck.accel()` or | |
[the Puck.js page on the magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals). | |
*/ | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "magOn", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_magOn", | |
"params" : [ | |
["samplerate","float","The sample rate in Hz, or undefined"] | |
] | |
} | |
Turn the magnetometer on and start periodic sampling. Samples will then cause | |
a 'mag' event on 'Puck': | |
``` | |
Puck.magOn(); | |
Puck.on('mag', function(xyz) { | |
console.log(xyz); | |
// {x:..., y:..., z:...} | |
}); | |
// Turn events off with Puck.magOff(); | |
``` | |
This call will be ignored if the sampling is already on. | |
If given an argument, the sample rate is set (if not, it's at 0.63 Hz). | |
The sample rate must be one of the following (resulting in the given power consumption): | |
* 80 Hz - 900uA | |
* 40 Hz - 550uA | |
* 20 Hz - 275uA | |
* 10 Hz - 137uA | |
* 5 Hz - 69uA | |
* 2.5 Hz - 34uA | |
* 1.25 Hz - 17uA | |
* 0.63 Hz - 8uA | |
* 0.31 Hz - 8uA | |
* 0.16 Hz - 8uA | |
* 0.08 Hz - 8uA | |
When the battery level drops too low while sampling is turned on, | |
the magnetometer may stop sampling without warning, even while other | |
Puck functions continue uninterrupted. | |
Check out [the Puck.js page on the magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) | |
for more information. | |
*/ | |
void jswrap_puck_magOn(JsVarFloat hz) { | |
if (mag_enabled) { | |
jswrap_puck_magOff(); | |
// wait 1ms for power-off | |
jshDelayMicroseconds(1000); | |
} | |
int milliHz = (int)((hz*1000)+0.5); | |
if (milliHz<=0) milliHz=630; | |
if (!mag_on(milliHz)) { | |
jsExceptionHere(JSET_ERROR, "Invalid sample rate %f - must be 80, 40, 20, 10, 5, 2.5, 1.25, 0.63, 0.31, 0.16 or 0.08 Hz", hz); | |
} | |
if (isPuckV2) { | |
jshPinWatch(MAG_PIN_DRDY, true); | |
jshPinSetState(MAG_PIN_DRDY, JSHPINSTATE_GPIO_IN); | |
} else { | |
jshPinWatch(MAG_PIN_INT, true); | |
jshPinSetState(MAG_PIN_INT, JSHPINSTATE_GPIO_IN); | |
} | |
mag_enabled = true; | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "magOff", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_magOff" | |
} | |
Turn the magnetometer off | |
*/ | |
void jswrap_puck_magOff() { | |
if (mag_enabled) { | |
if (isPuckV2) { | |
jshPinWatch(MAG_PIN_DRDY, false); | |
} else { | |
jshPinWatch(MAG_PIN_INT, false); | |
} | |
mag_off(); | |
} | |
mag_enabled = false; | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "magWr", | |
"generate" : "jswrap_puck_magWr", | |
"params" : [ | |
["reg","int",""], | |
["data","int",""] | |
], | |
"ifdef" : "PUCKJS" | |
} | |
Writes a register on the LIS3MDL / MAX3110 Magnetometer. Can be used for configuring advanced functions. | |
Check out [the Puck.js page on the magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) | |
for more information and links to modules that use this function. | |
*/ | |
void jswrap_puck_magWr(JsVarInt reg, JsVarInt data) { | |
unsigned char buf[2]; | |
buf[0] = (unsigned char)reg; | |
buf[1] = (unsigned char)data; | |
jsi2cWrite(&i2cMag, isPuckV2 ? LIS3MDL_ADDR : MAG3110_ADDR, 2, buf, true); | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "magRd", | |
"generate" : "jswrap_puck_magRd", | |
"params" : [ | |
["reg","int",""] | |
], | |
"return" : ["int",""], | |
"ifdef" : "PUCKJS" | |
} | |
Reads a register from the LIS3MDL / MAX3110 Magnetometer. Can be used for configuring advanced functions. | |
Check out [the Puck.js page on the magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) | |
for more information and links to modules that use this function. | |
*/ | |
int jswrap_puck_magRd(JsVarInt reg) { | |
unsigned char buf[1]; | |
buf[0] = (unsigned char)reg; | |
int addr = isPuckV2 ? LIS3MDL_ADDR : MAG3110_ADDR; | |
jsi2cWrite(&i2cMag, addr, 1, buf, true); | |
jsi2cRead(&i2cMag, addr, 1, buf, true); | |
return buf[0]; | |
} | |
// Turn temp sensor on | |
void temp_on() { | |
jshPinSetValue(TEMP_PIN_PWR, 1); | |
jshPinSetValue(TEMP_PIN_SCL, 1); | |
jshPinSetValue(TEMP_PIN_SDA, 1); | |
jshPinSetState(TEMP_PIN_PWR, JSHPINSTATE_GPIO_OUT); | |
jshPinSetState(TEMP_PIN_SCL, JSHPINSTATE_GPIO_OUT); | |
jshPinSetState(TEMP_PIN_SDA, JSHPINSTATE_GPIO_OUT); | |
jshPinSetState(TEMP_PIN_SCL, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP); | |
jshPinSetState(TEMP_PIN_SDA, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP); | |
jshDelayMicroseconds(50000); // wait for startup and first reading... could this be faster? | |
} | |
// Turn temp sensor off | |
void temp_off() { | |
nrf_gpio_cfg_input(TEMP_PIN_SDA, NRF_GPIO_PIN_NOPULL); | |
nrf_gpio_cfg_input(TEMP_PIN_SCL, NRF_GPIO_PIN_NOPULL); | |
nrf_gpio_pin_clear(TEMP_PIN_PWR); | |
nrf_gpio_cfg_output(TEMP_PIN_PWR); | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "getTemperature", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_getTemperature", | |
"return" : ["float", "Temperature in degrees C" ] | |
} | |
On Puck.js v2.0 this will use the on-board PCT2075TP temperature sensor, but on Puck.js the less accurate on-chip Temperature sensor is used. | |
*/ | |
JsVarFloat jswrap_puck_getTemperature() { | |
if (isPuckV2) { | |
temp_on(); | |
unsigned char buf[2]; | |
// 'on' is the default | |
//buf[1] = 1; // CONF | |
//buf[0] = 0; // on | |
//jsi2cWrite(&i2cTemp,TEMP_ADDR, 1, buf, false); | |
buf[0] = 0; // TEMP | |
jsi2cWrite(&i2cTemp,TEMP_ADDR, 1, buf, false); | |
jsi2cRead(&i2cTemp, TEMP_ADDR, 2, buf, true); | |
int t = (buf[0]<<3) | (buf[1]>>5); | |
if (t&1024) t-=2048; // negative | |
temp_off(); | |
return t / 8.0; | |
} else { | |
return jshReadTemperature(); | |
} | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "accelOn", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_accelOn", | |
"params" : [ | |
["samplerate","float","The sample rate in Hz, or undefined"] | |
] | |
} | |
Accepted values are: | |
* 1.6 Hz (no Gyro) - 40uA (2v05 and later firmware) | |
* 12.5 Hz (with Gyro)- 350uA | |
* 26 Hz (with Gyro) - 450 uA | |
* 52 Hz (with Gyro) - 600 uA | |
* 104 Hz (with Gyro) - 900 uA | |
* 208 Hz (with Gyro) - 1500 uA | |
* 416 Hz (with Gyro) (not recommended) | |
* 833 Hz (with Gyro) (not recommended) | |
* 1660 Hz (with Gyro) (not recommended) | |
Once `Puck.accelOn()` is called, the `Puck.accel` event will be called each time data is received. `Puck.accelOff()` can be called to turn the accelerometer off. | |
For instance to light the red LED whenever Puck.js is face up: | |
``` | |
Puck.on('accel', function(a) { | |
digitalWrite(LED1, a.acc.z > 0); | |
}); | |
Puck.accelOn(); | |
``` | |
Check out [the Puck.js page on the accelerometer](http://www.espruino.com/Puck.js#on-board-peripherals) | |
for more information. | |
*/ | |
void jswrap_puck_accelOn(JsVarFloat hz) { | |
if (!isPuckV2) { | |
jsExceptionHere(JSET_ERROR, "Not available on Puck.js v1"); | |
return; | |
} | |
if (accel_enabled) { | |
jswrap_puck_accelOff(); | |
// wait 1ms for power-off | |
jshDelayMicroseconds(1000); | |
} | |
int milliHz = (int)((hz*1000)+0.5); | |
if (milliHz==0) milliHz=12500; | |
if (!accel_on(milliHz)) { | |
jsExceptionHere(JSET_ERROR, "Invalid sample rate %f - must be 1660, 833, 416, 208, 104, 52, 26, 12.5, 1.6 Hz", hz); | |
} | |
jshPinWatch(ACCEL_PIN_INT, true); | |
accel_enabled = true; | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "accelOff", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_accelOff" | |
} | |
Turn the accelerometer off after it has been turned on by `Puck.accelOn()`. | |
Check out [the Puck.js page on the accelerometer](http://www.espruino.com/Puck.js#on-board-peripherals) | |
for more information. | |
*/ | |
void jswrap_puck_accelOff() { | |
if (!isPuckV2) { | |
jsExceptionHere(JSET_ERROR, "Not available on Puck.js v1"); | |
return; | |
} | |
if (accel_enabled) { | |
jshPinWatch(ACCEL_PIN_INT, false); | |
accel_off(); | |
} | |
accel_enabled = false; | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "accel", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_accel", | |
"return" : ["JsVar", "An Object `{acc:{x,y,z}, gyro:{x,y,z}}` of accelerometer/gyro readings" ] | |
} | |
Turn on the accelerometer, take a single reading, and then turn it off again. | |
The values reported are the raw values from the chip. In normal configuration: | |
* accelerometer: full-scale (32768) is 4g, so you need to divide by 8192 to get correctly scaled values | |
* gyro: full-scale (32768) is 245 dps, so you need to divide by 134 to get correctly scaled values | |
If taking more than one reading, we'd suggest you use `Puck.accelOn()` and the `Puck.accel` event. | |
*/ | |
JsVar *jswrap_puck_accel() { | |
/* If not enabled, turn on and read. If enabled, | |
* just pass out the last reading */ | |
if (!accel_enabled) { | |
accel_on(1660000); | |
accel_wait(); | |
accel_read(); | |
accel_off(); | |
} | |
JsVar *o = jsvNewObject(); | |
jsvObjectSetChildAndUnLock(o,"acc",to_xyz(accel_reading, 1)); | |
jsvObjectSetChildAndUnLock(o,"gyro",to_xyz(gyro_reading, 1)); | |
return o; | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "accelWr", | |
"generate" : "jswrap_puck_accelWr", | |
"params" : [ | |
["reg","int",""], | |
["data","int",""] | |
], | |
"ifdef" : "PUCKJS" | |
} | |
Writes a register on the LSM6DS3TR-C Accelerometer. Can be used for configuring advanced functions. | |
Check out [the Puck.js page on the accelerometer](http://www.espruino.com/Puck.js#on-board-peripherals) | |
for more information and links to modules that use this function. | |
*/ | |
void jswrap_puck_accelWr(JsVarInt reg, JsVarInt data) { | |
if (!isPuckV2) { | |
jsExceptionHere(JSET_ERROR, "Not available on Puck.js v1"); | |
return; | |
} | |
unsigned char buf[2]; | |
buf[0] = (unsigned char)reg; | |
buf[1] = (unsigned char)data; | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true); | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "accelRd", | |
"generate" : "jswrap_puck_accelRd", | |
"params" : [ | |
["reg","int",""] | |
], | |
"return" : ["int",""], | |
"ifdef" : "PUCKJS" | |
} | |
Reads a register from the LSM6DS3TR-C Accelerometer. Can be used for configuring advanced functions. | |
Check out [the Puck.js page on the accelerometer](http://www.espruino.com/Puck.js#on-board-peripherals) | |
for more information and links to modules that use this function. | |
*/ | |
int jswrap_puck_accelRd(JsVarInt reg) { | |
if (!isPuckV2) { | |
jsExceptionHere(JSET_ERROR, "Not available on Puck.js v1"); | |
return -1; | |
} | |
unsigned char buf[1]; | |
buf[0] = (unsigned char)reg; | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 1, buf, false); | |
jsi2cRead(&i2cAccel, ACCEL_ADDR, 1, buf, true); | |
return buf[0]; | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "IR", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_IR", | |
"params" : [ | |
["data","JsVar","An array of pulse lengths, in milliseconds"], | |
["cathode","pin","(optional) pin to use for IR LED cathode - if not defined, the built-in IR LED is used"], | |
["anode","pin","(optional) pin to use for IR LED anode - if not defined, the built-in IR LED is used"] | |
] | |
} | |
Transmit the given set of IR pulses - data should be an array of pulse times | |
in milliseconds (as `[on, off, on, off, on, etc]`). | |
For example `Puck.IR(pulseTimes)` - see http://www.espruino.com/Puck.js+Infrared | |
for a full example. | |
You can also attach an external LED to Puck.js, in which case | |
you can just execute `Puck.IR(pulseTimes, led_cathode, led_anode)` | |
It is also possible to just supply a single pin for IR transmission | |
with `Puck.IR(pulseTimes, led_anode)` (on 2v05 and above). | |
*/ | |
Pin _jswrap_puck_IR_pin; | |
void _jswrap_puck_IR_on() { | |
jshPinAnalogOutput(_jswrap_puck_IR_pin, 0.1, 38000, 0); | |
} | |
void _jswrap_puck_IR_off() { | |
// normally we're doing single-pin IR with the cathode, so 1=off | |
bool polarity = 1; | |
// On Puck.js v2 we go through an (N)FET, which means 0=off. | |
if (isPuckV2 && (_jswrap_puck_IR_pin==IR_FET_PIN || _jswrap_puck_IR_pin==FET_PIN)) | |
polarity = 0; | |
jshPinOutput(_jswrap_puck_IR_pin, polarity); | |
} | |
// Called when we're done with the IR transmission | |
void _jswrap_puck_IR_done(JsSysTime t, void *data) { | |
uint32_t d = (uint32_t)data; | |
Pin cathode = d&255; | |
Pin anode = (d>>8)&255; | |
// set as input - so no signal | |
jshPinSetState(anode, JSHPINSTATE_GPIO_IN); | |
// this one also stops the PWM | |
jshPinSetState(cathode, JSHPINSTATE_GPIO_IN); | |
} | |
void jswrap_puck_IR(JsVar *data, Pin cathode, Pin anode) { | |
if (!jsvIsIterable(data)) { | |
jsExceptionHere(JSET_TYPEERROR, "Expecting an array, got %t", data); | |
return; | |
} | |
if (jshIsPinValid(anode) && !jshIsPinValid(cathode)) { | |
jsExceptionHere(JSET_TYPEERROR, "Invalid pin combination"); | |
return; | |
} | |
if (!(jshIsPinValid(cathode) || jshIsPinValid(anode))) { | |
if (isPuckV2) { | |
cathode = IR_FET_PIN; | |
} else { // Puck v1 | |
anode = IR_ANODE_PIN; | |
cathode = IR_CATHODE_PIN; | |
} | |
} | |
bool twoPin = jshIsPinValid(anode) && jshIsPinValid(cathode); | |
bool pulsePolarity = true; | |
if (twoPin) { | |
jshPinAnalogOutput(cathode, 0.75, 38000, 0); | |
jshPinSetValue(anode, pulsePolarity); | |
} else { | |
// Otherwise we're just doing stuff on a single pin | |
_jswrap_puck_IR_pin = cathode; | |
} | |
JsSysTime time = jshGetSystemTime(); | |
bool hasPulses = false; | |
JsvIterator it; | |
jsvIteratorNew(&it, data, JSIF_EVERY_ARRAY_ELEMENT); | |
while (jsvIteratorHasElement(&it)) { | |
JsVarFloat pulseTime = jsvIteratorGetFloatValue(&it); | |
if (twoPin) { | |
if (hasPulses) jstPinOutputAtTime(time, &anode, 1, pulsePolarity); | |
else jshPinSetState(anode, JSHPINSTATE_GPIO_OUT); | |
} else { | |
if (pulsePolarity) jstExecuteFn(_jswrap_puck_IR_on, NULL, time, 0); | |
else jstExecuteFn(_jswrap_puck_IR_off, NULL, time, 0); | |
} | |
hasPulses = true; | |
time += jshGetTimeFromMilliseconds(pulseTime); | |
pulsePolarity = !pulsePolarity; | |
jsvIteratorNext(&it); | |
} | |
jsvIteratorFree(&it); | |
if (hasPulses) { | |
if (twoPin) { | |
uint32_t d = cathode | anode<<8; | |
jstExecuteFn(_jswrap_puck_IR_done, (void*)d, time, 0); | |
} else { | |
jstExecuteFn(_jswrap_puck_IR_off, NULL, time, 0); | |
} | |
} | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "capSense", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_capSense", | |
"params" : [ | |
["tx","pin",""], | |
["rx","pin",""] | |
], | |
"return" : ["int", "Capacitive sense counter" ] | |
} | |
Capacitive sense - the higher the capacitance, the higher the number returned. | |
If called without arguments, a value depending on the capacitance of what is | |
attached to pin D11 will be returned. If you attach a length of wire to D11, | |
you'll be able to see a higher value returned when your hand is near the wire | |
than when it is away. | |
You can also supply pins to use yourself, however if you do this then | |
the TX pin must be connected to RX pin and sense plate via a roughly 1MOhm | |
resistor. | |
When not supplying pins, Puck.js uses an internal resistor between D12(tx) | |
and D11(rx). | |
*/ | |
int jswrap_puck_capSense(Pin tx, Pin rx) { | |
if (jshIsPinValid(tx) && jshIsPinValid(rx)) { | |
return (int)nrf_utils_cap_sense(tx, rx); | |
} | |
return (int)nrf_utils_cap_sense(CAPSENSE_TX_PIN, CAPSENSE_RX_PIN); | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "light", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_light", | |
"return" : ["float", "A light value from 0 to 1" ] | |
} | |
Return a light value based on the light the red LED is seeing. | |
**Note:** If called more than 5 times per second, the received light value | |
may not be accurate. | |
*/ | |
JsVarFloat jswrap_puck_light() { | |
// If pin state wasn't an analog input before, make it one now, | |
// read, and delay, just to make sure everything has time to settle | |
// before the 'real' reading | |
JshPinState s = jshPinGetState(LED1_PININDEX); | |
if (s != JSHPINSTATE_GPIO_IN) { | |
jshPinOutput(LED1_PININDEX,0);// discharge | |
jshPinAnalog(LED1_PININDEX);// analog | |
int delay = 5000; | |
// if we were using a peripheral it can take longer | |
// for everything to sort itself out | |
if ((s&JSHPINSTATE_MASK) == JSHPINSTATE_AF_OUT) | |
delay = 50000; | |
jshDelayMicroseconds(delay); | |
} | |
JsVarFloat v = jswrap_ble_getBattery(); | |
JsVarFloat f = jshPinAnalog(LED1_PININDEX)*v/(3*0.45); | |
if (f>1) f=1; | |
// turn the red LED back on if it was on before | |
if (s & JSHPINSTATE_PIN_IS_ON) | |
jshPinOutput(LED1_PININDEX, 1); | |
return f; | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"ifdef" : "PUCKJS", | |
"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_puck_getBattery() { | |
JsVarFloat v = jshReadVRef(); | |
int pc = (v-2.2)*100/0.6; | |
if (pc>100) pc=100; | |
if (pc<0) pc=0; | |
return pc; | |
} | |
static bool selftest_check_pin(Pin pin, char *err) { | |
unsigned int i; | |
char pinStr[4]; | |
pinStr[0]='-'; | |
itostr(pin,&pinStr[1],10); | |
bool ok = true; | |
jshPinSetState(pin, JSHPINSTATE_GPIO_OUT); | |
jshPinSetValue(pin, 1); | |
jshPinSetState(pin, JSHPINSTATE_GPIO_IN_PULLUP); | |
if (!jshPinGetValue(pin)) { | |
pinStr[0]='l'; | |
if (!err[0]) strcpy(err,pinStr); | |
jsiConsolePrintf("Pin %p forced low\n", pin); | |
ok = false; | |
} | |
for (i=0;i<sizeof(PUCK_IO_PINS)/sizeof(Pin);i++) | |
if (PUCK_IO_PINS[i]!=pin) | |
jshPinOutput(PUCK_IO_PINS[i], 0); | |
if (!jshPinGetValue(pin)) { | |
pinStr[0]='L'; | |
if (!err[0]) strcpy(err,pinStr); | |
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)) { | |
pinStr[0]='h'; | |
if (!err[0]) strcpy(err,pinStr); | |
jsiConsolePrintf("Pin %p forced high\n", pin); | |
ok = false; | |
} | |
for (i=0;i<sizeof(PUCK_IO_PINS)/sizeof(Pin);i++) | |
if (PUCK_IO_PINS[i]!=pin) | |
jshPinOutput(PUCK_IO_PINS[i], 1); | |
if (jshPinGetValue(pin)) { | |
pinStr[0]='H'; | |
if (!err[0]) strcpy(err,pinStr); | |
jsiConsolePrintf("Pin %p shorted high\n", pin); | |
ok = false; | |
} | |
jshPinSetState(pin, JSHPINSTATE_GPIO_IN); | |
return ok; | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Puck", | |
"name" : "selfTest", | |
"ifdef" : "PUCKJS", | |
"generate" : "jswrap_puck_selfTest", | |
"return" : ["bool", "True if the self-test passed" ] | |
} | |
Run a self-test, and return true for a pass. This checks for shorts | |
between pins, so your Puck shouldn't have anything connected to it. | |
**Note:** This self-test auto starts if you hold the button on your Puck | |
down while inserting the battery, leave it pressed for 3 seconds (while | |
the green LED is lit) and release it soon after all LEDs turn on. 5 | |
red blinks is a fail, 5 green is a pass. | |
If the self test fails, it'll set the Puck.js Bluetooth advertising name | |
to `Puck.js !ERR` where ERR is a 3 letter error code. | |
*/ | |
bool jswrap_puck_selfTest() { | |
unsigned int timeout, i; | |
JsVarFloat v; | |
bool ok = true; | |
char err[4] = {0,0,0,0}; | |
// light up all LEDs white | |
jshPinOutput(LED1_PININDEX, LED1_ONSTATE); | |
jshPinOutput(LED2_PININDEX, LED2_ONSTATE); | |
jshPinOutput(LED3_PININDEX, LED3_ONSTATE); | |
jshPinSetState(BTN1_PININDEX, BTN1_PINSTATE); | |
timeout = 2000; | |
while (jshPinGetValue(BTN1_PININDEX)==BTN1_ONSTATE && timeout--) | |
nrf_delay_ms(1); | |
if (jshPinGetValue(BTN1_PININDEX)==BTN1_ONSTATE) { | |
jsiConsolePrintf("Timeout waiting for button to be released.\n"); | |
if (!err[0]) strcpy(err,"BTN"); | |
ok = false; | |
} | |
nrf_delay_ms(100); | |
jshPinInput(LED1_PININDEX); | |
jshPinInput(LED2_PININDEX); | |
jshPinInput(LED3_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.2 || v>0.65) { | |
if (!err[0]) strcpy(err,"LD1"); | |
jsiConsolePrintf("LED1 pullup voltage out of range (%f) - disconnected?\n", v); | |
ok = false; | |
} | |
jshPinSetState(LED2_PININDEX, JSHPINSTATE_GPIO_IN_PULLUP); | |
nrf_delay_ms(1); | |
v = jshPinAnalog(LED2_PININDEX); | |
jshPinSetState(LED2_PININDEX, JSHPINSTATE_GPIO_IN); | |
if (v<0.55 || v>0.85) { | |
if (!err[0]) strcpy(err,"LD2"); | |
jsiConsolePrintf("LED2 pullup voltage out of range (%f) - disconnected?\n", v); | |
ok = false; | |
} | |
jshPinSetState(LED3_PININDEX, JSHPINSTATE_GPIO_IN_PULLUP); | |
nrf_delay_ms(1); | |
v = jshPinAnalog(LED3_PININDEX); | |
jshPinSetState(LED3_PININDEX, JSHPINSTATE_GPIO_IN); | |
if (v<0.55 || v>0.90) { | |
if (!err[0]) strcpy(err,"LD3"); | |
jsiConsolePrintf("LED3 pullup voltage out of range (%f) - disconnected?\n", v); | |
ok = false; | |
} | |
jshPinSetState(IR_ANODE_PIN, JSHPINSTATE_GPIO_IN_PULLDOWN); | |
jshPinSetState(IR_CATHODE_PIN, JSHPINSTATE_GPIO_OUT); | |
jshPinSetValue(IR_CATHODE_PIN, 1); | |
nrf_delay_ms(1); | |
if (jshPinGetValue(IR_ANODE_PIN)) { | |
if (!err[0]) strcpy(err,"IRs"); | |
jsiConsolePrintf("IR LED wrong way around/shorted?\n"); | |
ok = false; | |
} | |
if (isPuckV2) { | |
jshPinSetValue(IR_FET_PIN, 0); | |
jshPinSetState(IR_FET_PIN, JSHPINSTATE_GPIO_OUT); | |
jshPinSetState(IR_INPUT_PIN, JSHPINSTATE_GPIO_IN_PULLUP); | |
nrf_delay_ms(1); | |
if (!jshPinGetValue(IR_INPUT_PIN)) { | |
if (!err[0]) strcpy(err,"IRs"); | |
jsiConsolePrintf("IR LED short?\n"); | |
ok = false; | |
} | |
jshPinSetValue(IR_FET_PIN, 1); | |
nrf_delay_ms(1); | |
if (jshPinGetValue(IR_INPUT_PIN)) { | |
if (!err[0]) strcpy(err,"IRF"); | |
jsiConsolePrintf("IR FET disconnected?\n"); | |
ok = false; | |
} | |
jshPinSetState(IR_INPUT_PIN, JSHPINSTATE_GPIO_IN); | |
jshPinSetValue(IR_FET_PIN, 0); | |
} else { | |
jshPinSetState(IR_CATHODE_PIN, JSHPINSTATE_GPIO_IN_PULLDOWN); | |
jshPinSetState(IR_ANODE_PIN, JSHPINSTATE_GPIO_OUT); | |
jshPinSetValue(IR_ANODE_PIN, 1); | |
nrf_delay_ms(1); | |
if (!jshPinGetValue(IR_CATHODE_PIN)) { | |
if (!err[0]) strcpy(err,"IRd"); | |
jsiConsolePrintf("IR LED disconnected?\n"); | |
ok = false; | |
} | |
} | |
jshPinSetState(IR_ANODE_PIN, JSHPINSTATE_GPIO_IN); | |
jshPinSetState(IR_CATHODE_PIN, JSHPINSTATE_GPIO_IN); | |
mag_on(80000); | |
mag_wait(); | |
mag_read(); | |
mag_off(); | |
mag_enabled = false; | |
if (mag_reading[0]==-1 && mag_reading[1]==-1 && mag_reading[2]==-1) { | |
if (!err[0]) strcpy(err,"MAG"); | |
jsiConsolePrintf("Magnetometer not working?\n"); | |
ok = false; | |
} | |
if (isPuckV2) { | |
accel_on(1660000); | |
unsigned char buf[1]; | |
buf[0] = 0x0F; // WHOAMI | |
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 1, buf, false); | |
jsi2cRead(&i2cAccel, ACCEL_ADDR, 1, buf, true); | |
accel_off(); | |
if (buf[0]!=106) { | |
if (!err[0]) strcpy(err,"ACC"); | |
jsiConsolePrintf("Accelerometer WHOAMI failed\n"); | |
ok = false; | |
} | |
JsVarFloat t = jswrap_puck_getTemperature(); | |
if (t<0 || t>40) { | |
if (!err[0]) strcpy(err,"TMP"); | |
jsiConsolePrintf("Unexpected temperature\n"); | |
ok = false; | |
} | |
} | |
jshPinSetState(CAPSENSE_TX_PIN, JSHPINSTATE_GPIO_OUT); | |
jshPinSetState(CAPSENSE_RX_PIN, JSHPINSTATE_GPIO_IN); | |
jshPinSetValue(CAPSENSE_TX_PIN, 1); | |
nrf_delay_ms(1); | |
if (!jshPinGetValue(CAPSENSE_RX_PIN)) { | |
if (!err[0]) strcpy(err,"CPu"); | |
jsiConsolePrintf("Capsense resistor disconnected? (pullup)\n"); | |
ok = false; | |
} | |
jshPinSetValue(CAPSENSE_TX_PIN, 0); | |
nrf_delay_ms(1); | |
if (jshPinGetValue(CAPSENSE_RX_PIN)) { | |
if (!err[0]) strcpy(err,"CPd"); | |
jsiConsolePrintf("Capsense resistor disconnected? (pulldown)\n"); | |
ok = false; | |
} | |
jshPinSetState(CAPSENSE_TX_PIN, JSHPINSTATE_GPIO_IN); | |
ok &= selftest_check_pin(1,err); | |
ok &= selftest_check_pin(2,err); | |
if (!isPuckV2) { | |
ok &= selftest_check_pin(6,err); | |
ok &= selftest_check_pin(7,err); | |
ok &= selftest_check_pin(8,err); | |
} | |
ok &= selftest_check_pin(28,err); | |
ok &= selftest_check_pin(29,err); | |
ok &= selftest_check_pin(30,err); | |
ok &= selftest_check_pin(31,err); | |
for (i=0;i<sizeof(PUCK_IO_PINS)/sizeof(Pin);i++) | |
jshPinSetState(PUCK_IO_PINS[i], JSHPINSTATE_GPIO_IN); | |
if (err[0]) { | |
char deviceName[BLE_GAP_DEVNAME_MAX_LEN]; | |
strcpy(deviceName,"Puck.js !"); | |
strcat(deviceName,err); | |
ble_gap_conn_sec_mode_t sec_mode; | |
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode); | |
sd_ble_gap_device_name_set(&sec_mode, | |
(const uint8_t *)deviceName, | |
strlen(deviceName)); | |
jsiConsolePrintf("Error code %s\n",err); | |
} | |
return ok; | |
} | |
/*JSON{ | |
"type" : "init", | |
"generate" : "jswrap_puck_init" | |
}*/ | |
void jswrap_puck_init() { | |
// Set up I2C | |
jshI2CInitInfo(&i2cMag); | |
i2cMag.bitrate = 0x7FFFFFFF; // make it as fast as we can go | |
i2cMag.pinSDA = MAG_PIN_SDA; | |
i2cMag.pinSCL = MAG_PIN_SCL; | |
i2cMag.clockStretch = false; | |
jshI2CInitInfo(&i2cAccel); | |
i2cAccel.bitrate = 0x7FFFFFFF; // make it as fast as we can go | |
i2cAccel.pinSDA = ACCEL_PIN_SDA; | |
i2cAccel.pinSCL = ACCEL_PIN_SCL; | |
i2cAccel.clockStretch = false; | |
jshI2CInitInfo(&i2cTemp); | |
i2cTemp.bitrate = 0x7FFFFFFF; // make it as fast as we can go | |
i2cTemp.pinSDA = TEMP_PIN_SDA; | |
i2cTemp.pinSCL = TEMP_PIN_SCL; | |
i2cTemp.clockStretch = false; | |
accel_off(); | |
temp_off(); | |
mag_pin_on(); | |
// MAG3110 WHO_AM_I | |
unsigned char buf[2]; | |
buf[0] = 0x07; | |
jsi2cWrite(&i2cMag, MAG3110_ADDR, 1, buf, false); | |
jsi2cRead(&i2cMag, MAG3110_ADDR, 1, buf, true); | |
// clock stretch is off, so no need to trap exceptions | |
isPuckV2 = false; | |
//jsiConsolePrintf("M3110 %d\n", buf[0]); | |
if (buf[0]!=0xC4) { | |
// Not found, check for LIS3MDL - Puck.js v2 | |
nrf_delay_ms(1); // LIS3MDL takes longer to boot | |
buf[0] = 0x0F; | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 1, buf, false); | |
jsi2cRead(&i2cMag, LIS3MDL_ADDR, 1, buf, true); | |
//jsiConsolePrintf("LIS3 %d\n", buf[0]); | |
if (buf[0] == 0b00111101) { | |
// all good, MAG3110 - Puck.js v2 | |
isPuckV2 = true; | |
} else { | |
// ERROR - magnetometer not found! | |
} | |
} else { | |
// all good, MAG3110 - Puck.js v1 | |
} | |
// Power off | |
mag_off(); | |
// If Puck.js v2 ensure FETs are forced off | |
if (isPuckV2) { | |
jshPinSetValue(IR_FET_PIN, 0); | |
jshPinSetState(IR_FET_PIN, JSHPINSTATE_GPIO_OUT); | |
jshPinSetValue(FET_PIN, 0); | |
jshPinSetState(FET_PIN, JSHPINSTATE_GPIO_OUT); | |
} | |
/* 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; | |
if (firstStart && jshPinGetValue(BTN1_PININDEX) == BTN1_ONSTATE) { | |
firstStart = false; // don't do it during a software reset - only first hardware reset | |
bool result = jswrap_puck_selfTest(); | |
// green if good, red if bad | |
Pin indicator = result ? LED2_PININDEX : LED1_PININDEX; | |
int i; | |
for (i=0;i<5;i++) { | |
jshPinOutput(indicator, LED1_ONSTATE); | |
nrf_delay_ms(500); | |
jshPinOutput(indicator, !LED1_ONSTATE); | |
nrf_delay_ms(500); | |
} | |
jshPinInput(indicator); | |
// If the button is *still* pressed, remove all code from flash memory too! | |
if (jshPinGetValue(BTN1_PININDEX) == BTN1_ONSTATE) { | |
jsfRemoveCodeFromFlash(); | |
} | |
} | |
} | |
/*JSON{ | |
"type" : "kill", | |
"generate" : "jswrap_puck_kill" | |
}*/ | |
void jswrap_puck_kill() { | |
if (mag_enabled) { | |
mag_off(); | |
mag_enabled = false; | |
} | |
} | |
/*JSON{ | |
"type" : "idle", | |
"generate" : "jswrap_puck_idle" | |
}*/ | |
bool jswrap_puck_idle() { | |
bool busy = false; | |
/* jshPinWatch should mean that we wake up whenever a new | |
* magnetometer reading is ready */ | |
if (mag_enabled) { | |
if (isPuckV2) { | |
if (nrf_gpio_pin_read(MAG_PIN_DRDY)) { | |
/*jsiConsolePrintf("irq\n"); | |
mag_read(); | |
unsigned char buf[3]; | |
buf[0] = 0x32; buf[1]=0xFF;buf[1]=0x7F; // INT_THS_L,H - turn off IRQ by writing big values | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 2, buf, true); | |
buf[0] = 0x31; // read INT_SRC - latch IRQ pin | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 1, buf, true); | |
jsi2cRead(&i2cMag, LIS3MDL_ADDR, 1, buf, true); | |
jsiConsolePrintf("irq %d\n",nrf_gpio_pin_read(MAG_PIN_INT)); | |
buf[0] = 0x32; buf[1]=0;buf[1]=0; // INT_THS_L,H - turn on by writing 0 | |
jsi2cWrite(&i2cMag, LIS3MDL_ADDR, 2, buf, true); | |
jsiConsolePrintf("irq %d\n",nrf_gpio_pin_read(MAG_PIN_INT));*/ | |
mag_read(); | |
JsVar *xyz = to_xyz(mag_reading, 1); | |
JsVar *puck = jsvObjectGetChild(execInfo.root, "Puck", 0); | |
if (jsvHasChildren(puck)) | |
jsiQueueObjectCallbacks(puck, JS_EVENT_PREFIX"mag", &xyz, 1); | |
jsvUnLock2(puck, xyz); | |
busy = true; // don't sleep - handle this now | |
} | |
} else { // Puck.js v1 | |
if (nrf_gpio_pin_read(MAG_PIN_INT)) { | |
mag_read(); | |
JsVar *xyz = to_xyz(mag_reading, 1); | |
JsVar *puck = jsvObjectGetChild(execInfo.root, "Puck", 0); | |
if (jsvHasChildren(puck)) | |
jsiQueueObjectCallbacks(puck, JS_EVENT_PREFIX"mag", &xyz, 1); | |
jsvUnLock2(puck, xyz); | |
busy = true; // don't sleep - handle this now | |
} | |
} | |
} | |
if (accel_enabled && nrf_gpio_pin_read(ACCEL_PIN_INT)) { // accel_enabled only on isPuckV2 | |
accel_read(); | |
JsVar *d = jswrap_puck_accel(); | |
JsVar *puck = jsvObjectGetChild(execInfo.root, "Puck", 0); | |
if (jsvHasChildren(puck)) | |
jsiQueueObjectCallbacks(puck, JS_EVENT_PREFIX"accel", &d, 1); | |
jsvUnLock2(puck, d); | |
busy = true; | |
} | |
return busy; | |
} |