#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "driver/gpio.h" #include "driver/i2c.h" #include "esp_log.h" #include "sdkconfig.h" #include "lux.h" #include "mqtt.h" #include "config.h" // Code from: // https://github.com/espressif/esp-idf/blob/release/v4.4/examples/peripherals/i2c/i2c_simple/main/i2c_simple_main.c #ifdef ENABLE_LUX_SENSOR static const char *TAG = "LUX"; #include bool _tsl2561AutoGain = true; tsl2561IntegrationTime_t _tsl2561IntegrationTime = TSL2561_INTEGRATIONTIME_101MS; tsl2561Gain_t _tsl2561Gain = TSL2561_GAIN_1X; #define I2C_MASTER_SCL_IO I2C_MASTER_SCL /*!< GPIO number used for I2C master clock */ #define I2C_MASTER_SDA_IO I2C_MASTER_SDA /*!< GPIO number used for I2C master data */ #define I2C_MASTER_NUM 0 /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */ #define I2C_MASTER_FREQ_HZ 400000 /*!< I2C master clock frequency */ #define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */ #define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */ #define I2C_MASTER_TIMEOUT_MS 1000 /** TSL2561 I2C Registers */ enum { TSL2561_REGISTER_CONTROL = 0x00, // Control/power register TSL2561_REGISTER_TIMING = 0x01, // Set integration time register TSL2561_REGISTER_THRESHHOLDL_LOW = 0x02, // Interrupt low threshold low-byte TSL2561_REGISTER_THRESHHOLDL_HIGH = 0x03, // Interrupt low threshold high-byte TSL2561_REGISTER_THRESHHOLDH_LOW = 0x04, // Interrupt high threshold low-byte TSL2561_REGISTER_THRESHHOLDH_HIGH = 0x05, // Interrupt high threshold high-byte TSL2561_REGISTER_INTERRUPT = 0x06, // Interrupt settings TSL2561_REGISTER_CRC = 0x08, // Factory use only TSL2561_REGISTER_ID = 0x0A, // TSL2561 identification setting TSL2561_REGISTER_CHAN0_LOW = 0x0C, // Light data channel 0, low byte TSL2561_REGISTER_CHAN0_HIGH = 0x0D, // Light data channel 0, high byte TSL2561_REGISTER_CHAN1_LOW = 0x0E, // Light data channel 1, low byte TSL2561_REGISTER_CHAN1_HIGH = 0x0F // Light data channel 1, high byte }; static esp_err_t i2c_master_init(void) { int i2c_master_port = I2C_MASTER_NUM; i2c_config_t conf = { .mode = I2C_MODE_MASTER, .sda_io_num = I2C_MASTER_SDA_IO, .scl_io_num = I2C_MASTER_SCL_IO, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = I2C_MASTER_FREQ_HZ, }; i2c_param_config(i2c_master_port, &conf); return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0); } static esp_err_t tsl2561_register_read(uint8_t reg_addr, uint8_t *data, size_t len) { return i2c_master_write_read_device(I2C_MASTER_NUM, CONFIG_TSL2561_I2C_ADDRESS, ®_addr, 1, data, len, I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS); } void write8(uint8_t reg, uint8_t value) { uint8_t data[2]; data[0] = reg; data[1] = value; i2c_master_write_to_device(I2C_MASTER_NUM, CONFIG_TSL2561_I2C_ADDRESS, data, 2, I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS); } uint16_t read16(uint8_t reg) { uint8_t data[2]; tsl2561_register_read(reg, data, 2); return (data[1] << 8) | data[0]; } uint8_t read8(uint8_t reg) { uint8_t data; tsl2561_register_read(reg, &data, 1); return data; } void setIntegrationTime(tsl2561IntegrationTime_t time) { /* Enable the device by setting the control bit to 0x03 */ /* Update the timing register */ write8(TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, time | _tsl2561Gain); /* Update value placeholders */ _tsl2561IntegrationTime = time; } void setGain(tsl2561Gain_t gain) { /* Update the timing register */ write8(TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, _tsl2561IntegrationTime | gain); /* Update value placeholders */ _tsl2561Gain = gain; } void enableAutoRange(bool enable) { _tsl2561AutoGain = enable ? true : false; } void enable(void) { /* Enable the device by setting the control bit to 0x03 */ write8(TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWERON); } void delay(uint32_t ms) { vTaskDelay(ms / portTICK_RATE_MS); } void getData (uint16_t *broadband, uint16_t *ir) { /* Wait x ms for ADC to complete */ switch (_tsl2561IntegrationTime) { case TSL2561_INTEGRATIONTIME_13MS: delay(TSL2561_DELAY_INTTIME_13MS); // KTOWN: Was 14ms break; case TSL2561_INTEGRATIONTIME_101MS: delay(TSL2561_DELAY_INTTIME_101MS); // KTOWN: Was 102ms break; default: delay(TSL2561_DELAY_INTTIME_402MS); // KTOWN: Was 403ms break; } /* Reads a two byte value from channel 0 (visible + infrared) */ *broadband = read16(TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN0_LOW); /* Reads a two byte value from channel 1 (infrared) */ *ir = read16(TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN1_LOW); } void getLuminosity (uint16_t *broadband, uint16_t *ir) { bool valid = false; /* If Auto gain disabled get a single reading and continue */ if(!_tsl2561AutoGain) { getData (broadband, ir); return; } /* Read data until we find a valid range */ bool _agcCheck = false; do { uint16_t _b, _ir; uint16_t _hi, _lo; tsl2561IntegrationTime_t _it = _tsl2561IntegrationTime; /* Get the hi/low threshold for the current integration time */ switch(_it) { case TSL2561_INTEGRATIONTIME_13MS: _hi = TSL2561_AGC_THI_13MS; _lo = TSL2561_AGC_TLO_13MS; break; case TSL2561_INTEGRATIONTIME_101MS: _hi = TSL2561_AGC_THI_101MS; _lo = TSL2561_AGC_TLO_101MS; break; default: _hi = TSL2561_AGC_THI_402MS; _lo = TSL2561_AGC_TLO_402MS; break; } getData(&_b, &_ir); /* Run an auto-gain check if we haven't already done so ... */ if (!_agcCheck) { if ((_b < _lo) && (_tsl2561Gain == TSL2561_GAIN_1X)) { /* Increase the gain and try again */ setGain(TSL2561_GAIN_16X); /* Drop the previous conversion results */ getData(&_b, &_ir); /* Set a flag to indicate we've adjusted the gain */ _agcCheck = true; } else if ((_b > _hi) && (_tsl2561Gain == TSL2561_GAIN_16X)) { /* Drop gain to 1x and try again */ setGain(TSL2561_GAIN_1X); /* Drop the previous conversion results */ getData(&_b, &_ir); /* Set a flag to indicate we've adjusted the gain */ _agcCheck = true; } else { /* Nothing to look at here, keep moving .... Reading is either valid, or we're already at the chips limits */ *broadband = _b; *ir = _ir; valid = true; } } else { /* If we've already adjusted the gain once, just return the new results. This avoids endless loops where a value is at one extreme pre-gain, and the the other extreme post-gain */ *broadband = _b; *ir = _ir; valid = true; } } while (!valid); } uint32_t calculateLux(uint16_t broadband, uint16_t ir) { unsigned long chScale; unsigned long channel1; unsigned long channel0; /* Make sure the sensor isn't saturated! */ uint16_t clipThreshold; switch (_tsl2561IntegrationTime) { case TSL2561_INTEGRATIONTIME_13MS: clipThreshold = TSL2561_CLIPPING_13MS; break; case TSL2561_INTEGRATIONTIME_101MS: clipThreshold = TSL2561_CLIPPING_101MS; break; default: clipThreshold = TSL2561_CLIPPING_402MS; break; } /* Return 65536 lux if the sensor is saturated */ if ((broadband > clipThreshold) || (ir > clipThreshold)) { return 65536; } /* Get the correct scale depending on the intergration time */ switch (_tsl2561IntegrationTime) { case TSL2561_INTEGRATIONTIME_13MS: chScale = TSL2561_LUX_CHSCALE_TINT0; break; case TSL2561_INTEGRATIONTIME_101MS: chScale = TSL2561_LUX_CHSCALE_TINT1; break; default: /* No scaling ... integration time = 402ms */ chScale = (1 << TSL2561_LUX_CHSCALE); break; } /* Scale for gain (1x or 16x) */ if (!_tsl2561Gain) chScale = chScale << 4; /* Scale the channel values */ channel0 = (broadband * chScale) >> TSL2561_LUX_CHSCALE; channel1 = (ir * chScale) >> TSL2561_LUX_CHSCALE; /* Find the ratio of the channel values (Channel1/Channel0) */ unsigned long ratio1 = 0; if (channel0 != 0) ratio1 = (channel1 << (TSL2561_LUX_RATIOSCALE+1)) / channel0; /* round the ratio value */ unsigned long ratio = (ratio1 + 1) >> 1; unsigned int b, m; #ifdef TSL2561_PACKAGE_CS if ((ratio >= 0) && (ratio <= TSL2561_LUX_K1C)) {b=TSL2561_LUX_B1C; m=TSL2561_LUX_M1C;} else if (ratio <= TSL2561_LUX_K2C) {b=TSL2561_LUX_B2C; m=TSL2561_LUX_M2C;} else if (ratio <= TSL2561_LUX_K3C) {b=TSL2561_LUX_B3C; m=TSL2561_LUX_M3C;} else if (ratio <= TSL2561_LUX_K4C) {b=TSL2561_LUX_B4C; m=TSL2561_LUX_M4C;} else if (ratio <= TSL2561_LUX_K5C) {b=TSL2561_LUX_B5C; m=TSL2561_LUX_M5C;} else if (ratio <= TSL2561_LUX_K6C) {b=TSL2561_LUX_B6C; m=TSL2561_LUX_M6C;} else if (ratio <= TSL2561_LUX_K7C) {b=TSL2561_LUX_B7C; m=TSL2561_LUX_M7C;} else if (ratio > TSL2561_LUX_K8C) {b=TSL2561_LUX_B8C; m=TSL2561_LUX_M8C;} #else if ((ratio >= 0) && (ratio <= TSL2561_LUX_K1T)) {b=TSL2561_LUX_B1T; m=TSL2561_LUX_M1T;} else if (ratio <= TSL2561_LUX_K2T) {b=TSL2561_LUX_B2T; m=TSL2561_LUX_M2T;} else if (ratio <= TSL2561_LUX_K3T) {b=TSL2561_LUX_B3T; m=TSL2561_LUX_M3T;} else if (ratio <= TSL2561_LUX_K4T) {b=TSL2561_LUX_B4T; m=TSL2561_LUX_M4T;} else if (ratio <= TSL2561_LUX_K5T) {b=TSL2561_LUX_B5T; m=TSL2561_LUX_M5T;} else if (ratio <= TSL2561_LUX_K6T) {b=TSL2561_LUX_B6T; m=TSL2561_LUX_M6T;} else if (ratio <= TSL2561_LUX_K7T) {b=TSL2561_LUX_B7T; m=TSL2561_LUX_M7T;} else if (ratio > TSL2561_LUX_K8T) {b=TSL2561_LUX_B8T; m=TSL2561_LUX_M8T;} #endif unsigned long temp; temp = ((channel0 * b) - (channel1 * m)); /* Do not allow negative lux value */ if (temp < 0) temp = 0; /* Round lsb (2^(LUX_SCALE-1)) */ temp += (1 << (TSL2561_LUX_LUXSCALE-1)); /* Strip off fractional portion */ uint32_t lux = temp >> TSL2561_LUX_LUXSCALE; /* Signal I2C had no errors */ return lux; } uint32_t readLuxSensor(void) { uint16_t broadband; uint16_t ir; getLuminosity(&broadband, &ir); uint32_t lux = calculateLux(broadband, ir); if( lux > 65000 ) lux=65000; // Set a maximum value return lux; } static void tsl2561_task(void * pvParameter) { uint8_t data[2]; static uint32_t totalLux = 0; static uint16_t loopCnt = 0; TickType_t vLastWakeTime = xTaskGetTickCount(); ESP_ERROR_CHECK(i2c_master_init()); ESP_LOGI(TAG, "I2C initialized successfully"); delay(200); enable(); delay(200); /* Read the TSL2561_REGISTER_ID register, on power up the register should have the value 0x05 The returned value consists of: Bit 7:4 = Part number Bit 3:0 = Revision number 0x50 = TSL2561T/FN/CL Rev:0 */ uint8_t id = read8(TSL2561_COMMAND_BIT | TSL2561_REGISTER_ID); ESP_LOGI(TAG, "WHO_AM_I = %02X", id); setIntegrationTime(TSL2561_INTEGRATIONTIME_101MS); setGain(TSL2561_GAIN_1X); // Do an initial delay to make the different tasks to work out of sync to each other (not send all data at same time) vTaskDelayUntil( &vLastWakeTime, 5000 / portTICK_PERIOD_MS ); while (1) { uint32_t lux = readLuxSensor(); totalLux += lux; loopCnt++; ESP_LOGI(TAG, "Lux: %u", lux); // Once every minute, calc mean lux and send to server. if( loopCnt == 6 ) { char mqtt_s[50]; char value_s[10]; totalLux /= 6; sprintf(mqtt_s,"garage/lux"); sprintf(value_s,"%u",totalLux); ESP_LOGI(TAG, "Topic:%s Data:%s", mqtt_s, value_s); sendMQTTMessage(mqtt_s, value_s); totalLux = 0; loopCnt = 0; } vTaskDelayUntil( &vLastWakeTime, 10000 / portTICK_PERIOD_MS ); } } void init_tsl2561() { xTaskCreate(&tsl2561_task, "tsl2561_task", 2048, NULL, 5, NULL); } #endif