mirror of
https://github.com/espruino/Espruino.git
synced 2025-12-08 19:06:15 +00:00
338 lines
11 KiB
JavaScript
338 lines
11 KiB
JavaScript
/* Copyright (c) 2018 Gordon Williams, Pur3 Ltd. See the file LICENSE for copying permission. */
|
|
/* Nordic Thingy Support Library */
|
|
|
|
|
|
var MPU_PWR_CTRL = V8;
|
|
var MPU_INT = D6;
|
|
var MIC_PWR_CTRL = V9;
|
|
var MIC_DOUT = D25;
|
|
var MIC_CLK = D26;
|
|
var CCS_PWR_CTRL = V10;
|
|
var CCS_RST = V11;
|
|
var CCS_WAKE = V12;
|
|
var CCS_INT = D22;
|
|
var LIS_INT = D12;
|
|
var SENSE_LEDR = V13;
|
|
var SENSE_LEDG = V14;
|
|
var SENSE_LEDB = V15;
|
|
var SENSE_LEDS = [SENSE_LEDR,SENSE_LEDG,SENSE_LEDB];
|
|
var LPS_INT = D23;
|
|
var HTS_INT = D24;
|
|
var BH_INT = D31;
|
|
var BATTERY_CHARGE = D17;
|
|
var BATTERY_VOLTAGE = D28;
|
|
var BATTERY_MONITOR = V4;
|
|
var SPEAKER = D27;
|
|
var SPK_PWR_CTRL= D29;
|
|
|
|
var i2c = new I2C();
|
|
i2c.setup({sda:7,scl:8,bitrate:400000});
|
|
exports.I2C = i2c;
|
|
var i2ce = new I2C();
|
|
i2ce.setup({sda:14,scl:15,bitrate:400000});
|
|
exports.I2CE = i2ce;
|
|
|
|
// ------------------------------------------------------------------------------------------- LIS2DH12
|
|
// Get repeated callbacks with {x,y,z}. Call with no argument to disable
|
|
exports.onAcceleration = function(callback) {
|
|
if (callback) {
|
|
if (!this.accel) this.accel = require("LIS2DH12").connectI2C(i2ce/*, { int:LIS_INT } - not used */);
|
|
this.accel.callback = callback;
|
|
this.accel.setPowerMode("low");
|
|
} else {
|
|
if (this.accel) this.accel.setPowerMode("powerdown");
|
|
this.accel = undefined;
|
|
}
|
|
};
|
|
// Get one callback with a new acceleration value ({x,y,z})
|
|
exports.getAcceleration = function(callback) {
|
|
if (!this.accel) {
|
|
require("LIS2DH12").connectI2C(i2ce/*, { int:LIS_INT } - not used */).readXYZ(callback);
|
|
} else {
|
|
this.accel.readXYZ(callback);
|
|
}
|
|
}
|
|
// ------------------------------------------------------------------------------------------- MPU9250
|
|
// Get repeated callbacks with {accel,gyro,mag} from the MPU. Call with no argument to disable
|
|
exports.onMPU = function(callback) {
|
|
if (callback) {
|
|
if (!this.mpu) {
|
|
MPU_PWR_CTRL.set(); // MPU on
|
|
this.mpu = require("MPU9250").connectI2C(i2c);
|
|
this.mpu.samplerate = 10; // Hz
|
|
this.mpu.callback = callback;
|
|
this.mpu.watch = setWatch(function(){
|
|
this.mpu.callback(this.mpu.read());
|
|
}.bind(this),MPU_INT,{repeat:1,edge:"rising"});
|
|
setTimeout(this.mpu.initMPU9250.bind(this.mpu), 10);
|
|
} else {
|
|
this.mpu.callback = callback;
|
|
}
|
|
} else {
|
|
if (this.mpu)
|
|
clearWatch(this.mpu.watch);
|
|
MPU_PWR_CTRL.reset(); // MPU off
|
|
this.mpu = undefined;
|
|
}
|
|
};
|
|
// Get one callback with a {accel,gyro,mag} value from the MPU
|
|
exports.getMPU = function(callback) {
|
|
if (!this.mpu) {
|
|
this.onMPU(function(d) {
|
|
this.onMPU();
|
|
callback(d);
|
|
}.bind(this));
|
|
} else {
|
|
callback(this.mpu.read());
|
|
}
|
|
}
|
|
// ------------------------------------------------------------------------------------------- LPS22HB
|
|
// Get repeated callbacks with {pressure,temperature}. Call with no argument to disable
|
|
exports.onPressure = function(callback) {
|
|
if (callback) {
|
|
if (!this.pressure) {
|
|
this.pressure = require("LPS22HB").connectI2C(i2c, {int:LPS_INT});
|
|
this.pressure.on('data',function(d) { this.pressureCallback(d); }.bind(this));
|
|
}
|
|
this.pressureCallback = callback;
|
|
} else {
|
|
if (this.pressure) this.pressure.stop();
|
|
this.pressure = undefined;
|
|
this.pressureCallback = undefined;
|
|
}
|
|
};
|
|
// Get one callback with a new {pressure,temperature} value
|
|
exports.getPressure = function(callback) {
|
|
if (!this.pressure) {
|
|
var p = require("LPS22HB").connectI2C(i2c, {int:LPS_INT});
|
|
p.read(function(d) {
|
|
p.stop();
|
|
callback(d);
|
|
});
|
|
} else {
|
|
this.pressure.read(callback);
|
|
}
|
|
}
|
|
// ------------------------------------------------------------------------------------------- HTS221
|
|
// Get repeated callbacks with {humidity,temperature}. Call with no argument to disable
|
|
exports.onHumidity = function(callback) {
|
|
if (callback) {
|
|
if (!this.humidity) {
|
|
this.humidity = require("HTS221").connect(i2c, {int:HTS_INT});
|
|
this.humidity.on('data',function(d) { this.humidityCallback(d); }.bind(this));
|
|
}
|
|
this.humidityCallback = callback;
|
|
} else {
|
|
if (this.humidity) this.humidity.stop();
|
|
this.humidity = undefined;
|
|
this.humidityCallback = undefined;
|
|
}
|
|
};
|
|
// Get one callback with a new {humidity,temperature} value
|
|
exports.getHumidity = function(callback) {
|
|
if (!this.humidity) {
|
|
this.onHumidity(function(d) {
|
|
this.onHumidity();
|
|
callback(d);
|
|
}.bind(this));
|
|
} else {
|
|
this.humidity.read(callback);
|
|
}
|
|
}
|
|
// ------------------------------------------------------------------------------------------- HTS221
|
|
// Get repeated callbacks with air quality `{eC02,TVOC}`. Call with no argument to disable
|
|
exports.onGas = function(callback) {
|
|
if (callback) {
|
|
if (!this.gas) {
|
|
CCS_PWR_CTRL.set(); // CCS on
|
|
CCS_RST.set(); // no reset
|
|
CCS_WAKE.reset(); // wake
|
|
this.gas = require("CCS811").connectI2C(i2c, { int : CCS_INT });
|
|
this.gas.on('data',function(d) { this.gasCallback(d); }.bind(this));
|
|
}
|
|
this.gasCallback = callback;
|
|
} else {
|
|
if (this.gas) {
|
|
this.gas.stop();
|
|
CCS_RST.reset(); // reset (so it doesn't power it via RST pin)
|
|
CCS_PWR_CTRL.reset(); // CCS off
|
|
}
|
|
this.gas = undefined;
|
|
this.gasCallback = undefined;
|
|
}
|
|
};
|
|
/* Get one callback with a new air quality value `{eC02,TVOC}`. This may not be useful
|
|
as the sensor takes a while to warm up and produce useful values */
|
|
exports.getGas = function(callback) {
|
|
if (!this.gas) {
|
|
this.onGas(function(d) {
|
|
this.onGas();
|
|
callback(d);
|
|
}.bind(this));
|
|
} else {
|
|
callback(this.gas.get());
|
|
}
|
|
}
|
|
// ------------------------------------------------------------------------------------------- HTS221
|
|
// Get repeated callbacks with color `{r,g,b,c}`. Call with no argument to disable
|
|
exports.onColor = function(callback) {
|
|
if (callback) {
|
|
if (!this.color) {
|
|
digitalWrite(SENSE_LEDS,0); // all LEDs on
|
|
this.color = require("BH1745").connectI2C(i2c/*, {int : BH_INT}*/);
|
|
this.colorInt = setInterval(function() {
|
|
this.colorCallback(this.color.read());
|
|
}.bind(this), 200);
|
|
}
|
|
this.colorCallback = callback;
|
|
} else {
|
|
if (this.color) {
|
|
clearInterval(this.colorInt);
|
|
this.color.stop();
|
|
digitalWrite(SENSE_LEDS,7); // all LEDs off
|
|
}
|
|
this.color = undefined;
|
|
this.colorInt = undefined;
|
|
this.colorCallback = undefined;
|
|
}
|
|
};
|
|
// Get one callback with a new color value `{r,g,b,c}`
|
|
exports.getColor = function(callback) {
|
|
if (!this.color) {
|
|
this.onColor(function(d) {
|
|
this.onColor();
|
|
callback(d);
|
|
}.bind(this));
|
|
} else {
|
|
callback(this.color.read());
|
|
}
|
|
}
|
|
// ------------------------------------------------------------------------------------------- Battery
|
|
// Returns the state of the battery (immediately, or via callback) as { charging : bool, voltage : number }
|
|
exports.getBattery = function(callback) {
|
|
BATTERY_MONITOR.set();
|
|
var result = {
|
|
charging : BATTERY_CHARGE.read(),
|
|
voltage : E.getAnalogVRef()*analogRead(BATTERY_VOLTAGE)*1500/180
|
|
};
|
|
BATTERY_MONITOR.reset();
|
|
if (callback) callback(result);
|
|
return result;
|
|
}
|
|
// ------------------------------------------------------------------------------------------- Speaker
|
|
// Play a sound, supply a string/uint8array/arraybuffer, samples per second, and a callback to use when done
|
|
// This can play up to 3 sounds at a time (assuming ~4000 samples per second)
|
|
exports.sound = function(waveform, pitch, callback) {
|
|
if (!this.sounds) this.sounds=0;
|
|
if (this.sounds>2) throw new Error("Too many sounds playing at once");
|
|
var w = new Waveform(waveform.length);
|
|
w.buffer.set(waveform);
|
|
w.on("finish", function(buf) {
|
|
this.sounds--;
|
|
if (!this.sounds) {
|
|
SPK_PWR_CTRL.reset();
|
|
digitalWrite(SPEAKER,0);
|
|
}
|
|
if (callback) callback();
|
|
}.bind(this));
|
|
if (!this.sounds) {
|
|
analogWrite(SPEAKER, 0.5, {freq:40000});
|
|
SPK_PWR_CTRL.set();
|
|
}
|
|
this.sounds++;
|
|
w.startOutput(SPEAKER, pitch);
|
|
};
|
|
// Make a simple beep noise. frequency in Hz, length in milliseconds. Both are optional.
|
|
exports.beep = function(freq, length) {
|
|
length = (length>0)?length:250;
|
|
freq = (freq>0)?freq:500;
|
|
analogWrite(SPEAKER, 0.5, {freq:freq});
|
|
SPK_PWR_CTRL.set();
|
|
if (this.beepTimeout) clearTimeout(this.beepTimeout);
|
|
this.beepTimeout = setTimeout(function() {
|
|
delete this.beepTimeout;
|
|
SPK_PWR_CTRL.reset();
|
|
digitalWrite(SPEAKER,0);
|
|
}.bind(this), length);
|
|
};
|
|
// Record audio for the given number of samples, at 8192 Hz 8 bit.
|
|
// This can then be fed into Thingy.sound(waveform, 8192). RAM is scarce, so realistically 1 sec is a maximum.
|
|
exports.record = function(samples, callback) {
|
|
var gain = 0x48; // gain ( 0 -> 0x50, 0x28 default )
|
|
var buf = new ArrayBuffer(2049); // 2x 1k byte sample buffers for DMA (+1 for byte shift)
|
|
var bufAddr = E.getAddressOf(buf,true);
|
|
if (!bufAddr) throw new Error("Unable to create a buffer");
|
|
var result = new Uint8Array(samples);
|
|
var resultIdx = 0;
|
|
var p = 0; // 1 or 0 (which buffer)
|
|
MIC_PWR_CTRL.set();
|
|
MIC_CLK.mode("output");
|
|
MIC_DOUT.mode("input");
|
|
poke32(0x4001D504,0x08400000); // 1.032MHz clock default
|
|
poke32(0x4001D508,1); // mono, left on falling edge
|
|
poke32(0x4001D518,gain); // gain left
|
|
poke32(0x4001D51C,gain); // gain right
|
|
poke32(0x4001D540,MIC_CLK.getInfo().num); // CLK
|
|
poke32(0x4001D544,MIC_DOUT.getInfo().num); // DIN
|
|
poke32(0x4001D560,bufAddr); // PTR
|
|
poke32(0x4001D564,512); // MAXCNT
|
|
poke32(0x4001D500,1); // enable PDM
|
|
poke8(0x4001D100,0); // event start
|
|
poke8(0x4001D104,0); // event stop
|
|
poke8(0x4001D108,0); // event end
|
|
poke8(0x4001D000,1); // START TASK
|
|
poke32(0x4001D560,bufAddr+1024); // second pointer
|
|
// 16kHz output 16 bit, 32kByte/sec = will be done in 30ms
|
|
// Poll more often so we're more likely to catch it (no way to hook IRQs yet)
|
|
var i = setInterval(function() {
|
|
if (peek8(0x4001D108)) { // end event
|
|
poke8(0x4001D108,0); // clear end
|
|
poke32(0x4001D560,bufAddr+p*1024); // push new address in
|
|
// quick copy, drop every other sample, 16->8 bits
|
|
result.set(new Uint32Array(buf,1+(p*1024),256),resultIdx);
|
|
// advance and stop if required
|
|
p=1-p;
|
|
resultIdx+=256;
|
|
if (resultIdx>=result.length) stop();
|
|
}
|
|
},5);
|
|
function stop() {
|
|
clearInterval(i); // stop polling
|
|
poke8(0x4001D004,1); // STOP TASK
|
|
poke8(0x4001D500,0); // disable PDM
|
|
poke32(0x4001D540,0xFFFFFFFF); // disconnect CLK
|
|
poke32(0x4001D544,0xFFFFFFFF); // disconnect DIN
|
|
MIC_PWR_CTRL.reset(); // mic off
|
|
// Now we have some time, add 128 to each item
|
|
E.mapInPlace(result,result,function(x){return x+128;});
|
|
// call the callback
|
|
if (callback) setTimeout(callback,0,result);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
// Reinitialise any hardware that might have been set up before being saved
|
|
E.on('init',function() {
|
|
if (exports.accel && exports.accel.callback) {
|
|
var c = exports.accel.callback;
|
|
exports.accel = undefined;
|
|
exports.onAcceleration(c);
|
|
}
|
|
if (exports.pressureCallback) {
|
|
exports.pressure = undefined;
|
|
exports.onPressure(exports.pressureCallback);
|
|
}
|
|
if (exports.humidityCallback) {
|
|
exports.humidity = undefined;
|
|
exports.onHumidity(exports.humidityCallback);
|
|
}
|
|
if (exports.gasCallback) {
|
|
exports.gas = undefined;
|
|
exports.onGas(exports.gasCallback);
|
|
}
|
|
if (exports.colorCallback) {
|
|
exports.color = undefined;
|
|
exports.onColor(exports.colorCallback);
|
|
}
|
|
});
|