Skip to content

Instantly share code, notes, and snippets.

@smittytone
Created January 23, 2020 21:18
Show Gist options
  • Save smittytone/f32970ab10e9ee0f528ea9c2d1862df7 to your computer and use it in GitHub Desktop.
Save smittytone/f32970ab10e9ee0f528ea9c2d1862df7 to your computer and use it in GitHub Desktop.
Squirrel driver for TSL2561 light-level sensor (I2C)
class TSL2561 {
static version = "1.0.0";
// TSL2561 register values
static TSL2561_COMMAND_BIT = "\x80"; // Command register. Bit 7 must be 1
static TSL2561_CONTROL_POWERON = "\x03"; // Power on setting
static TSL2561_CONTROL_POWEROFF = "\x00"; // Power off setting
static TSL2561_REGISTER_TIMING = "\x81"; // Access timing register
static TSL2561_REGISTER_ADC0_LSB = "\xAC"; // LSB of sensor's two-byte ADC value
static TSL2561_REGISTER_ADC1_LSB = "\xAE"; // LSB of sensor's two-byte ADC value
static TSL2561_REGISTER_THRESHLOW_LSB = "\xA2"; // LSB of sensor's interrupt lower threshold
static TSL2561_REGISTER_THRESHHI_LSB = "\xA4"; // LSB of sensor's interrupt upper threshold
static TSL2561_CONTROL_INTERRUPT = "\x86"; // LSB of sensor's interrupt control
// Lux calculation values
static LUX_SCALE = 14; // Scale by 2^14
static RATIO_SCALE = 9; // Scale ratio by 2^9
// Class properties
_i2c = null;
_i2cAddr = null;
_interruptPin = null;
_interruptFlag = null;
_debugFlag = false;
_gain = 0;
_integ = 0;
constructor(i2cBus = null, i2cAddress = 0x39, debug = false) {
if (i2cBus == null) {
server.error("I2C bus undefined");
return null;
}
if (i2cAddress < 0x00 || i2cAddress > 0xFF) {
server.error("I2C address out of range");
return null;
}
_i2c = i2cBus;
_i2cAddr = i2cAddress << 1;
_debugFlag = debug;
}
function init(gain = 1, integration = 0) {
// Set command focus to the control register
_i2c.write(_i2cAddr, TSL2561_COMMAND_BIT);
// Send power up command
_i2c.write(_i2cAddr, TSL2561_CONTROL_POWERON);
// Check the power is on: this will return string '3' or '51' if it is
if (_debugFlag) {
local result = _i2c.read(_i2cAddr, TSL2561_CONTROL_POWERON, 1);
server.log("Power check: "+ result);
}
// Issue command: write (0x80) to the timing register (0x01)
// ie. TSL2561_REGISTER_TIMING = 0x81
_i2c.write(_i2cAddr, TSL2561_REGISTER_TIMING);
// Set gain to low by writing a byte to the timing register
// bit 4 to the gain: 1 or 0 (gain high or low)
// bits 1,0 to the integration timing: 0,0 or 1,0 (timing 13.7ms or 101ms)
if (gain < 0 || gain > 1) gain = 0;
if (integration < 0 || integration > 2) integration = 0;
_gain = gain;
_integ = integration;
// Move gain value to bit 4 and add integration value (bits 0,1)
gain = gain << 4;
gain = gain + integration;
// Write the gain + integration timing data
_i2c.write(_i2cAddr, gain.tochar());
if (_debugFlag) server.log("TSL2561 sensor ready");
}
function getLux() {
// Set command focus to ADC 0
_i2c.write(_i2cAddr, TSL2561_REGISTER_ADC0_LSB);
local lumo0 = readSensor0();
if (_debugFlag) server.log("Light level: " + lumo0);
_i2c.write(_i2cAddr, TSL2561_REGISTER_ADC1_LSB);
local lumo1 = readSensor1();
if (_debugFlag) server.log("IR level: " + lumo1);
local lux = _calculateLux(lumo0, lumo1);
if (_debugFlag) server.log("Lux: " + lux);
return lux;
}
function getVisible() {
// Convenience function for readSensor0()
return readSensor0();
}
function getInfrared () {
// Convenience function for readSensor1()
return readSensor1();
}
function readSensor0() {
local word = _i2c.read(_i2cAddr, TSL2561_REGISTER_ADC0_LSB, 2);
local lumo = (word[1] << 8) + word[0];
return lumo;
}
function readSensor1() {
local word = _i2c.read(_i2cAddr, TSL2561_REGISTER_ADC1_LSB, 2);
local lumo = (word[1] << 8) + word[0];
return lumo;
}
function setInterrupt(lower = 0, upper = 0xFFFF, intPin = null, callback = null) {
if (intPin == null) return;
if (lower < 0) lower = 0;
if (upper > 0xFFFF) upper = 0xFFFF;
if (lower > upper) {
local a = upper;
upper = lower;
lower = a;
}
// Configure interrupt pin
_interruptPin = intPin;
intPin.configure(DIGITAL_IN , callback.bindenv(this));
// Write lower and upper threshold values
_i2c.write(_i2cAddr, TSL2561_REGISTER_THRESHLOW_LSB);
_i2c.write(_i2cAddr, (lower & 0xFF).tochar() + (lower >> 8).tochar());
_i2c.write(_i2cAddr, TSL2561_REGISTER_THRESHHI_LSB);
_i2c.write(_i2cAddr, (upper & 0xFF).tochar() + (upper >> 8).tochar());
// Activate the interrupt
_i2c.write(_i2cAddr, TSL2561_CONTROL_INTERRUPT);
_i2c.write(_i2cAddr, "\x25");
_interruptFlag = true;
if (_debugFlag) server.log("TSL2561 interrupt set");
}
function clearInterrupt() {
if (_interruptFlag == false) return;
_i2c.write(_i2cAddr, TSL2561_CONTROL_INTERRUPT);
_i2c.write(_i2cAddr, "\x00");
_interruptFlag = false;
_interruptPin.configure(DIGITAL_OUT, 0);
if (_debugFlag) server.log("TSL2561 interrupt cleared");
}
/* ------ Private Functions ------ */
function _calculateLux(ch0, ch1) {
// Calculate the luminosity based on ADC Channel 0 (visible + IR) and
// Channel 1 (IR) values. Returns the luminosity.
local chScale = 0x0400;
if (_integ == 0) {
// 13.7ms
chScale = 0x7517;
} else if (_integ == 1) {
// 101ms
chScale = 0x0fe7;
}
if (_gain == 0) chScale = chScale << 4;
local channel0 = (ch0 * chScale) >> 10;
local channel1 = (ch1 * chScale) >> 10;
local ratio1 = 0;
if (channel0 != 0) ratio1 = (channel1 << (RATIO_SCALE + 1)) / channel0;
local ratio = (ratio1 + 1) >> 1;
local b = 0x0000;
local m = 0x0000;
if ((ratio >= 0) && (ratio <= 0x0043))
{b=0x0204; m=0x01ad;}
else if (ratio <= 0x0085)
{b=0x0228; m=0x02c1;}
else if (ratio <= 0x00c8)
{b=0x0253; m=0x0363;}
else if (ratio <= 0x010a)
{b=0x0282; m=0x03df;}
else if (ratio <= 0x014d)
{b=0x0177; m=0x01dd;}
else if (ratio <= 0x019a)
{b=0x0101; m=0x0127;}
else if (ratio <= 0x029a)
{b=0x0037; m=0x002b;}
else if (ratio > 0x029a)
{b=0x0000; m=0x0000;}
local temp = ((channel0 * b) - (channel1 * m));
if (temp < 0) temp = 0;
temp += (1 << (LUX_SCALE - 1));
return (temp >> LUX_SCALE);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment