small changes and many new sensors

This commit is contained in:
2023-10-30 14:30:02 +01:00
parent 07917fe5ba
commit aad07c1b0a
21 changed files with 1642 additions and 297 deletions

View File

@@ -5,7 +5,11 @@
#include "../../Debug.h"
#ifndef usleep
void usleep(int val) {
vTaskDelay(val / 1000 / portTICK_PERIOD_MS);
}
#endif
/**
* base-class for Waveshare SPI E-Ink displays
@@ -16,11 +20,11 @@ template <int PIN_CS, int PIN_BUSY, int PIN_RST, int PIN_DC, typename SPI> class
static constexpr const char* NAME = "E-Ink";
SPI& spi;
public:
Waveshare(SPI& spi) : spi(spi) {
debugMod4(NAME, "ctor() CS:%d BUSY:%d RST:%d DC:%d", PIN_CS, PIN_BUSY, PIN_RST, PIN_DC);
Log::addInfo(NAME, "ctor() CS:%d BUSY:%d RST:%d DC:%d", PIN_CS, PIN_BUSY, PIN_RST, PIN_DC);
MyGPIO::setInput(PIN_BUSY);
MyGPIO::setOutput(PIN_RST);
MyGPIO::setOutput(PIN_DC);
@@ -30,7 +34,7 @@ public:
protected:
void reset() {
debugMod(NAME, "reset()");
Log::addInfo(NAME, "reset()");
MyGPIO::clear(PIN_RST);
usleep(200*1000);
MyGPIO::set(PIN_RST);
@@ -54,9 +58,9 @@ protected:
}
void waitUntilIdle() {
debugMod(NAME, "waitUntilIdle()");
Log::addInfo(NAME, "waitUntilIdle()");
while(MyGPIO::get(PIN_BUSY) == 0) {usleep(200*1000);}
debugMod(NAME, "OK");
Log::addInfo(NAME, "OK");
}
void lut(uint8_t c, uint8_t l, const uint8_t* p) {

View File

@@ -1,10 +1,52 @@
#include "Waveshare.h"
// www.waveshare.com/wiki/4.2inch_e-Paper_Module_(B)
// https://www.waveshare.com/wiki/4.2inch_e-Paper_Module_(B)_Manual#Overview
template <int PIN_CS, int PIN_BUSY, int PIN_RST, int PIN_DC, typename SPI> class Waveshare_4_2 : public Waveshare<PIN_CS, PIN_BUSY, PIN_RST, PIN_DC, SPI> {
static constexpr const char* NAME = "E-Ink 4.2\"";
static constexpr const uint8_t PANEL_SETTING = 0x00;
static constexpr const uint8_t POWER_SETTING = 0x01;
static constexpr const uint8_t POWER_OFF = 0x02;
static constexpr const uint8_t POWER_OFF_SEQUENCE_SETTING = 0x03;
static constexpr const uint8_t POWER_ON = 0x04;
static constexpr const uint8_t POWER_ON_MEASURE = 0x05;
static constexpr const uint8_t BOOSTER_SOFT_START = 0x06;
static constexpr const uint8_t DEEP_SLEEP = 0x07;
static constexpr const uint8_t DATA_START_TRANSMISSION_1 = 0x10;
static constexpr const uint8_t DATA_STOP = 0x11;
static constexpr const uint8_t DISPLAY_REFRESH = 0x12;
static constexpr const uint8_t DATA_START_TRANSMISSION_2 = 0x13;
static constexpr const uint8_t LUT_FOR_VCOM = 0x20;
static constexpr const uint8_t LUT_WHITE_TO_WHITE = 0x21;
static constexpr const uint8_t LUT_BLACK_TO_WHITE = 0x22;
static constexpr const uint8_t LUT_WHITE_TO_BLACK = 0x23;
static constexpr const uint8_t LUT_BLACK_TO_BLACK = 0x24;
static constexpr const uint8_t PLL_CONTROL = 0x30;
static constexpr const uint8_t TEMPERATURE_SENSOR_COMMAND = 0x40;
static constexpr const uint8_t TEMPERATURE_SENSOR_SELECTION = 0x41;
static constexpr const uint8_t TEMPERATURE_SENSOR_WRITE = 0x42;
static constexpr const uint8_t TEMPERATURE_SENSOR_READ = 0x43;
static constexpr const uint8_t VCOM_AND_DATA_INTERVAL_SETTING = 0x50;
static constexpr const uint8_t LOW_POWER_DETECTION = 0x51;
static constexpr const uint8_t TCON_SETTING = 0x60;
static constexpr const uint8_t RESOLUTION_SETTING = 0x61;
static constexpr const uint8_t GSST_SETTING = 0x65;
static constexpr const uint8_t GET_STATUS = 0x71;
static constexpr const uint8_t AUTO_MEASUREMENT_VCOM = 0x80;
static constexpr const uint8_t READ_VCOM_VALUE = 0x81;
static constexpr const uint8_t VCM_DC_SETTING = 0x82;
static constexpr const uint8_t PARTIAL_WINDOW = 0x90;
static constexpr const uint8_t PARTIAL_IN = 0x91;
static constexpr const uint8_t PARTIAL_OUT = 0x92;
static constexpr const uint8_t PROGRAM_MODE = 0xA0;
static constexpr const uint8_t ACTIVE_PROGRAMMING = 0xA1;
static constexpr const uint8_t READ_OTP = 0xA2;
static constexpr const uint8_t POWER_SAVING = 0xE3;
public:
enum class Mode {
@@ -42,53 +84,55 @@ public:
}
void enableWindow() {
this->sendCommand(0x91);
this->sendCommand(PARTIAL_IN);
}
void disableWindow() {
this->sendCommand(0x92);
this->sendCommand(PARTIAL_OUT);
}
void init(Mode mode) {
debugMod(NAME, "init()");
Log::addInfo(NAME, "init()");
this->reset();
this->send3(0x06, 0x17, 0x17, 0x17); // BOOSTER_SOFT_START
this->sendCommand(0x04); // POWER_ON
this->send3(BOOSTER_SOFT_START, 0x17, 0x17, 0x17);
this->sendCommand(POWER_ON);
this->waitUntilIdle();
uint8_t cfg = 0x0F;
if (mode == Mode::BLACK_WHITE) {cfg |= (1<<4);}
this->send1(0x00, cfg); // PANEL_SETTING
this->send1(0x50, 0xF7); // VCOM_AND_DATA_INTERVAL_SETTING
this->send1(PANEL_SETTING, cfg);
this->send1(VCOM_AND_DATA_INTERVAL_SETTING, 0xF7);
//this->sendCommand(0x10);//DATA_START_TRANSMISSION_1
//usleep(10*1000);
debugMod(NAME, "init() complete");
Log::addInfo(NAME, "init() complete");
}
/** load black data, bit-set = black pixel, nullptr input = clear all */
void loadBlack(const uint8_t* black) {
this->sendCommand(0x10);
this->sendCommand(DATA_START_TRANSMISSION_1);
if (black) {
for (int i = 0; i < 400*300/8; ++i) {this->sendData(~reverse(black[i]));}
} else {
for (int i = 0; i < 400*300/8; ++i) {this->sendData(~0x00);}
for (int i = 0; i < 400*300/8; ++i) {this->sendData(0);} // 0 = pixel not set (blank)
}
this->sendCommand(0x11);
this->sendCommand(DATA_STOP);
}
/** load black data, bit-set = red pixel, nullptr input = clear all */
void loadRed(const uint8_t* red) {
this->sendCommand(0x13);
this->sendCommand(DATA_START_TRANSMISSION_2);
if (red) {
for (int i = 0; i < 400*300/8; ++i) {this->sendData(~reverse(red[i]));}
} else {
for (int i = 0; i < 400*300/8; ++i) {this->sendData(~0x00);}
for (int i = 0; i < 400*300/8; ++i) {this->sendData(0);} // 0 = pixel not set (blank)
}
this->sendCommand(0x11);
this->sendCommand(DATA_STOP);
}
static uint8_t reverse(uint8_t a) {
@@ -101,19 +145,19 @@ public:
void show() {
debugMod(NAME, "refresh");
this->sendCommand(0x12);//DISPLAY_REFRESH
Log::addInfo(NAME, "refresh");
this->sendCommand(DISPLAY_REFRESH);
usleep(100*1000);
this->waitUntilIdle();
debugMod(NAME, "refresh done");
Log::addInfo(NAME, "refresh done");
debugMod(NAME, "sleep");
this->send1(0x50, 0x17);//VCOM_AND_DATA_INTERVAL_SETTING
this->send1(0x82, 0x00);//VCM_DC_SETTING_REGISTER, to solve Vcom drop
this->send4(0x01, 0x02, 0x00, 0x00, 0x00);//POWER_SETTING
Log::addInfo(NAME, "sleep");
this->send1(VCOM_AND_DATA_INTERVAL_SETTING, 0x17);
this->send1(VCM_DC_SETTING, 0x00); //to solve Vcom drop
this->send4(POWER_SETTING, 0x02, 0x00, 0x00, 0x00);
this->waitUntilIdle();
this->sendCommand(0x02);//POWER_OFF
debugMod(NAME, "sleep done");
this->sendCommand(POWER_OFF);
Log::addInfo(NAME, "sleep done");
}

147
ext/lcd/SH1106.h Normal file
View File

@@ -0,0 +1,147 @@
#pragma once
#include "../../io/SoftI2C.h"
// https://www.displayfuture.com/Display/datasheet/controller/SH1106.pdf
// Note: the SH1106 is almost the same as SSD1306,
// except for some addressing modes, when writing the whole display at once
// the device address is also the same
template <typename I2C, uint8_t w, uint8_t h> class SH1106 {
private:
static constexpr uint8_t ADDR7 = 0b0111100;
static constexpr uint8_t CMD_COL_ADDR_LO = 0x00;
static constexpr uint8_t CMD_COL_ADDR_HI = 0x10;
static constexpr uint8_t CMD_PUMP_VOLTAGE = 0x30;
static constexpr uint8_t CMD_START_LINE = 0x40;
static constexpr uint8_t CMD_DISPLAY_OFF = 0xAE | 0;
static constexpr uint8_t CMD_DISPLAY_ON = 0xAE | 1;
static constexpr uint8_t CMD_PAGE_ADDR = 0xB0;
private:
bool inited = false;
I2C& i2c;
public:
SH1106(I2C& i2c) : i2c(i2c) {
}
bool isPresent() {
return i2c.query(ADDR7);
}
/** checks if LCD is present and initializes it once / when it was gone */
bool isPresentAndInit() {
bool present = isPresent();
if (!present) {inited = false;}
if ( present && !inited) {initOnce();}
return present;
}
void initOnce() {
if (inited) {return;}
init();
inited = true;
}
void flush(const uint8_t* data) {
for (uint8_t y = 0; y < h/8; ++y) {
sendCommand(CMD_COL_ADDR_LO | 0);
sendCommand(CMD_COL_ADDR_HI | 0);
sendCommand(CMD_PAGE_ADDR | y);
startDataTransfer();
for (uint8_t x = 0; x < w; ++x) {
writeData(*data); ++data;
}
stopDataTransfer();
}
}
private:
inline void startDataTransfer() {
i2c.startWrite(ADDR7);
i2c.writeByteAndCheck(0x40);
}
inline void writeData(uint8_t val) {
i2c.writeByteAndCheck(val);
}
inline void stopDataTransfer() {
i2c.stop();
}
void init() {
sendCommand(CMD_DISPLAY_OFF);
// Init
sendCommand(CMD_DISPLAY_ON);
}
private:
void sendCommand(uint8_t cmd) {
bool ok;
ok = i2c.startWrite(ADDR7);
if (!ok) {os_printf("failed start write\n");}
ok = i2c.writeByteAndCheck(0x00); // command
if (!ok) {os_printf("failed command mode\n");}
ok = i2c.writeByteAndCheck(cmd);
if (!ok) {os_printf("failed command\n");}
i2c.stop();
}
void sendCommand(uint8_t cmd, uint8_t v1) {
sendCommand(cmd);
sendCommand(v1);
}
void sendCommand(uint8_t cmd, uint8_t v1, uint8_t v2) {
sendCommand(cmd);
sendCommand(v1);
sendCommand(v2);
}
void sendCommand2(uint8_t cmd, uint8_t v1) {
i2c.startWrite(ADDR7);
i2c.writeByteAndCheck(0x00);
i2c.writeByteAndCheck(cmd);
//i2c.writeByteAndCheck(0x00);
i2c.writeByteAndCheck(v1);
i2c.stop();
}
void sendCommand2(uint8_t cmd, uint8_t v1, uint8_t v2) {
i2c.startWrite(ADDR7);
i2c.writeByteAndCheck(0x00);
i2c.writeByteAndCheck(cmd);
//i2c.writeByteAndCheck(0x00);
i2c.writeByteAndCheck(v1);
//i2c.writeByteAndCheck(0x00);
i2c.writeByteAndCheck(v2);
i2c.stop();
}
};

View File

@@ -3,6 +3,9 @@
#include "../../io/SoftI2C.h"
// NOTE: this is almost the same as SH1106
// https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
//#define SSD1306_128_64 1
//#define SSD1306_64_48 1 // https://github.com/mcauser/Adafruit_SSD1306/blob/esp8266-64x48/Adafruit_SSD1306.cpp
@@ -88,6 +91,14 @@ public:
bool isPresent() {
return i2c.query(ADDR7);
}
/** checks if LCD is present and initializes it once / when it was gone */
bool isPresentAndInit() {
bool present = isPresent();
if (!present) {inited = false;}
if ( present && !inited) {initOnce();}
return present;
}
void initOnce() {
if (inited) {return;}
@@ -98,20 +109,29 @@ public:
void flush(const uint8_t* data) {
sendCommand(SSD1306_COLUMNADDR);
// special handling for 64x48 oled shield
#if SSD1306_LCDWIDTH == 64 && SSD1306_LCDHEIGHT == 48
sendCommand(SSD1306_COLUMNADDR);
sendCommand(32);
sendCommand(32 + SSD1306_LCDWIDTH - 1);
#else
sendCommand(0); // Column start address (0 = reset)
sendCommand(SSD1306_LCDWIDTH-1); // Column end address (127 = reset)
sendCommand(SSD1306_COLUMNADDR, 0, SSD1306_LCDWIDTH-1);
//sendCommand(SSD1306_COLUMNADDR);
//sendCommand(0); // Column start address (0 = reset)
//sendCommand(SSD1306_LCDWIDTH-1); // Column end address (127 = reset)
#endif
sendCommand(SSD1306_PAGEADDR);
sendCommand(0); // Page start address (0 = reset)
sendCommand( (SSD1306_LCDHEIGHT/8)-1 ); // Page end address
sendCommand(SSD1306_PAGEADDR, 0, (SSD1306_LCDHEIGHT/8)-1);
//sendCommand(SSD1306_PAGEADDR);
//sendCommand(0); // Page start address (0 = reset)
//sendCommand( (SSD1306_LCDHEIGHT/8)-1 ); // Page end address
// #if SSD1306_LCDHEIGHT == 64
// sendCommand(7); // Page end address
// #endif
@@ -147,7 +167,8 @@ public:
bool ok = i2c.writeByteAndCheck(0x40);
if (!ok) {os_printf("failed write data\n");}
for (uint16_t i=0; i < (SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++) {
//for (uint16_t i=0; i < (SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++) {
for (uint16_t i=0; i < 16; i++) {
i2c.writeByteAndCheck(data[i]);
}
@@ -163,25 +184,32 @@ private:
uint8_t vccstate = 0;
sendCommand(SSD1306_DISPLAYOFF); // 0xAE
sendCommand(SSD1306_SETDISPLAYCLOCKDIV); // 0xD5
sendCommand(0x80); // the suggested ratio 0x80
sendCommand(SSD1306_SETMULTIPLEX); // 0xA8
sendCommand(SSD1306_SETMULTIPLEX); // 0xA8: multiplexer (31 == 32MUX, 63 == 64MUX)
sendCommand(SSD1306_LCDHEIGHT - 1);
sendCommand(SSD1306_SETDISPLAYOFFSET); // 0xD3
sendCommand(SSD1306_SETDISPLAYOFFSET); // 0xD3: vertical shift 0-63
sendCommand(0x0); // no offset
sendCommand(SSD1306_SETSTARTLINE | 0x0); // line #0
sendCommand(SSD1306_CHARGEPUMP); // 0x8D
if (vccstate == SSD1306_EXTERNALVCC) {
sendCommand(0x10);
} else {
sendCommand(0x14);
}
sendCommand(SSD1306_MEMORYMODE); // 0x20
sendCommand(0x00); // 0x0 act like ks0108
sendCommand(SSD1306_SEGREMAP | 0x1);
//sendCommand(SSD1306_MEMORYMODE); // 0x20: memory mode: hor/ver/page
//sendCommand(0x02); // 0x0 act like ks0108
sendCommand(SSD1306_MEMORYMODE, 0x00); // 0x20: memory mode: hor/ver/page
sendCommand(SSD1306_SEGREMAP | 0x0);
//sendCommand(SSD1306_COMSCANINC); // rotate 0
sendCommand(SSD1306_COMSCANDEC); // rotate 180?
// OLD
@@ -244,6 +272,8 @@ private:
private:
void sendCommand(uint8_t cmd) {
bool ok;
ok = i2c.startWrite(ADDR7);
@@ -254,6 +284,37 @@ private:
if (!ok) {os_printf("failed command\n");}
i2c.stop();
}
void sendCommand(uint8_t cmd, uint8_t v1) {
bool ok;
ok = i2c.startWrite(ADDR7);
if (!ok) {os_printf("failed start write\n");}
ok = i2c.writeByteAndCheck(0x00); // command
if (!ok) {os_printf("failed command mode\n");}
ok = i2c.writeByteAndCheck(cmd);
if (!ok) {os_printf("failed command\n");}
ok = i2c.writeByteAndCheck(0x00); // command
ok = i2c.writeByteAndCheck(v1);
if (!ok) {os_printf("failed command\n");}
i2c.stop();
}
void sendCommand(uint8_t cmd, uint8_t v1, uint8_t v2) {
bool ok;
ok = i2c.startWrite(ADDR7);
if (!ok) {os_printf("failed start write\n");}
ok = i2c.writeByteAndCheck(0x00); // command
if (!ok) {os_printf("failed command mode\n");}
ok = i2c.writeByteAndCheck(cmd);
if (!ok) {os_printf("failed command\n");}
ok = i2c.writeByteAndCheck(0x00); // command
ok = i2c.writeByteAndCheck(v1);
if (!ok) {os_printf("failed command\n");}
ok = i2c.writeByteAndCheck(0x00); // command
ok = i2c.writeByteAndCheck(v2);
if (!ok) {os_printf("failed command\n");}
i2c.stop();
}
};

324
ext/lcd/SSD1306.h.testing Normal file
View File

@@ -0,0 +1,324 @@
#ifndef LCD_SSD1306
#define LCD_SSD1306
#include "../../io/SoftI2C.h"
// https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
//#define SSD1306_128_64 1
//#define SSD1306_64_48 1 // https://github.com/mcauser/Adafruit_SSD1306/blob/esp8266-64x48/Adafruit_SSD1306.cpp
#if defined SSD1306_128_64
#define SSD1306_LCDWIDTH 128
#define SSD1306_LCDHEIGHT 64
#elif defined SSD1306_128_32
#define SSD1306_LCDWIDTH 128
#define SSD1306_LCDHEIGHT 32
#elif defined SSD1306_96_16
#define SSD1306_LCDWIDTH 96
#define SSD1306_LCDHEIGHT 16
#elif defined SSD1306_64_48
#define SSD1306_LCDWIDTH 64
#define SSD1306_LCDHEIGHT 48
#else
#error "SSD1306 display size not defined"
#endif
#define SSD1306_SETCONTRAST 0x81
#define SSD1306_DISPLAYALLON_RESUME 0xA4
#define SSD1306_DISPLAYALLON 0xA5
#define SSD1306_NORMALDISPLAY 0xA6
#define SSD1306_INVERTDISPLAY 0xA7
#define SSD1306_DISPLAYOFF 0xAE
#define SSD1306_DISPLAYON 0xAF
#define SSD1306_SETDISPLAYOFFSET 0xD3
#define SSD1306_SETCOMPINS 0xDA
#define SSD1306_SETVCOMDETECT 0xDB
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
#define SSD1306_SETPRECHARGE 0xD9
#define SSD1306_SETMULTIPLEX 0xA8
#define SSD1306_SETLOWCOLUMN 0x00
#define SSD1306_SETHIGHCOLUMN 0x10
#define SSD1306_SETSTARTLINE 0x40
#define SSD1306_MEMORYMODE 0x20
#define SSD1306_COLUMNADDR 0x21
#define SSD1306_PAGEADDR 0x22
#define SSD1306_COMSCANINC 0xC0
#define SSD1306_COMSCANDEC 0xC8
#define SSD1306_SEGREMAP 0xA0
#define SSD1306_CHARGEPUMP 0x8D
#define SSD1306_EXTERNALVCC 0x1
#define SSD1306_SWITCHCAPVCC 0x2
// Scrolling #defines
#define SSD1306_ACTIVATE_SCROLL 0x2F
#define SSD1306_DEACTIVATE_SCROLL 0x2E
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A
template <typename I2C> class SSD1306 {
private:
static constexpr uint8_t ADDR7 = 0b0111100;
bool inited = false;
I2C& i2c;
public:
SSD1306(I2C& i2c) : i2c(i2c) {
}
bool isPresent() {
return i2c.query(ADDR7);
}
/** checks if LCD is present and initializes it once / when it was gone */
bool isPresentAndInit() {
bool present = isPresent();
if (!present) {inited = false;}
if ( present && !inited) {initOnce();}
return present;
}
void initOnce() {
if (inited) {return;}
init();
inited = true;
}
void flush(const uint8_t* data) {
sendCommand(0xC0);
sendCommand2(0x20, 0b11111100);
// special handling for 64x48 oled shield
#if SSD1306_LCDWIDTH == 64 && SSD1306_LCDHEIGHT == 48
sendCommand(SSD1306_COLUMNADDR);
sendCommand(32);
sendCommand(32 + SSD1306_LCDWIDTH - 1);
#else
//sendCommand2(SSD1306_COLUMNADDR, 0, SSD1306_LCDWIDTH-1);
sendCommand(SSD1306_COLUMNADDR);
sendCommand(0); // Column start address (0 = reset)
sendCommand(SSD1306_LCDWIDTH-1); // Column end address (127 = reset)
#endif
//sendCommand2(SSD1306_PAGEADDR, 0, (SSD1306_LCDHEIGHT/8)-1);
sendCommand(SSD1306_PAGEADDR);
sendCommand(0); // Page start address (0 = reset)
sendCommand( (SSD1306_LCDHEIGHT/8)-1 ); // Page end address
sendCommand(0x00 | 0x00);
sendCommand(0x10 | 0x00);
sendCommand(0xB0 | 0x02);
// #if SSD1306_LCDHEIGHT == 64
// sendCommand(7); // Page end address
// #endif
// #if SSD1306_LCDHEIGHT == 48
// sendCommand(3); // Page end address
// #endif
// #if SSD1306_LCDHEIGHT == 32
// sendCommand(3); // Page end address
// #endif
// #if SSD1306_LCDHEIGHT == 16
// sendCommand(1); // Page end address
// #endif
// for (uint16_t i=0; i<(SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++) {
// // send a bunch of data in one xmission
// i2c::startWrite(ADDR7);
// bool ok = i2c::writeByteAndCheck(0x40);
// if (!ok) {os_printf("failed line\n");}
// for (uint8_t x = 0; x < 16; ++x) {
// i2c::writeByteAndCheck(data[i]);
// ++i;
// }
// i--;
// i2c::stop();
// }
i2c.startWrite(ADDR7);
bool ok = i2c.writeByteAndCheck(0x40);
if (!ok) {os_printf("failed write data\n");}
//for (uint16_t i=0; i < (SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++) {
for (uint16_t i=0; i < 16; i++) {
i2c.writeByteAndCheck(data[i]);
usleep(1000);
}
i2c.stop();
}
private:
void init() {
uint8_t vccstate = 0;
sendCommand(SSD1306_DISPLAYOFF); // 0xAE
sendCommand(SSD1306_SETDISPLAYCLOCKDIV, 0x80); // 0xD5, the suggested ratio 0x80
sendCommand(SSD1306_SETMULTIPLEX, SSD1306_LCDHEIGHT-1); // 0xA8: multiplexer (31 == 32MUX, 63 == 64MUX)
sendCommand(SSD1306_SETDISPLAYOFFSET, 0x00); // 0xD3: vertical shift 0-63, no offset
sendCommand(SSD1306_SETSTARTLINE | 0x0); // line #0
if (vccstate == SSD1306_EXTERNALVCC) {
sendCommand(SSD1306_CHARGEPUMP, 0x10); // 0x8D
} else {
sendCommand(SSD1306_CHARGEPUMP, 0x14); // 0x8D
}
sendCommand(SSD1306_MEMORYMODE, 0x00); // 0x20: memory mode(hor/ver/page) -> hor
sendCommand(SSD1306_SEGREMAP | 0x0);
//sendCommand(SSD1306_COMSCANINC); // rotate 0
sendCommand(SSD1306_COMSCANDEC); // rotate 180?
// OLD
// #if defined SSD1306_128_32
// sendCommand(SSD1306_SETCOMPINS); // 0xDA
// sendCommand(0x02);
// sendCommand(SSD1306_SETCONTRAST); // 0x81
// sendCommand(0x8F);
// #elif defined SSD1306_128_64
// sendCommand(SSD1306_SETCOMPINS); // 0xDA
// sendCommand(0x12);
// sendCommand(SSD1306_SETCONTRAST); // 0x81
// if (vccstate == SSD1306_EXTERNALVCC)
// { sendCommand(0x9F); }
// else
// { sendCommand(0xCF); }
// #elif defined SSD1306_96_16
// sendCommand(SSD1306_SETCOMPINS); // 0xDA
// sendCommand(0x2); //ada x12
// sendCommand(SSD1306_SETCONTRAST); // 0x81
// if (vccstate == SSD1306_EXTERNALVCC)
// { sendCommand(0x10); }
// else
// { sendCommand(0xAF); }
// #endif
// NEW
sendCommand(SSD1306_SETCOMPINS, 0x12); // 0xDA
if (vccstate == SSD1306_EXTERNALVCC) {
sendCommand(0x9F);
} else {
sendCommand(0xCF);
}
if (vccstate == SSD1306_EXTERNALVCC) {
sendCommand(SSD1306_SETPRECHARGE, 0x22); // 0xd9
} else {
sendCommand(SSD1306_SETPRECHARGE, 0xF1); // 0xd9
}
sendCommand(SSD1306_SETVCOMDETECT, 0x40); // 0xDB
sendCommand(SSD1306_DISPLAYALLON_RESUME); // 0xA4
sendCommand(SSD1306_NORMALDISPLAY); // 0xA6
sendCommand(SSD1306_DEACTIVATE_SCROLL);
sendCommand(SSD1306_DISPLAYON); //--turn on oled panel
}
private:
void sendCommand(uint8_t cmd) {
bool ok;
ok = i2c.startWrite(ADDR7);
if (!ok) {os_printf("failed start write\n");}
ok = i2c.writeByteAndCheck(0x00); // command
if (!ok) {os_printf("failed command mode\n");}
ok = i2c.writeByteAndCheck(cmd);
if (!ok) {os_printf("failed command\n");}
i2c.stop();
}
void sendCommand(uint8_t cmd, uint8_t v1) {
sendCommand(cmd);
sendCommand(v1);
}
void sendCommand(uint8_t cmd, uint8_t v1, uint8_t v2) {
sendCommand(cmd);
sendCommand(v1);
sendCommand(v2);
}
void sendCommand2(uint8_t cmd, uint8_t v1) {
i2c.startWrite(ADDR7);
i2c.writeByteAndCheck(0x00);
i2c.writeByteAndCheck(cmd);
//i2c.writeByteAndCheck(0x00);
i2c.writeByteAndCheck(v1);
i2c.stop();
}
void sendCommand2(uint8_t cmd, uint8_t v1, uint8_t v2) {
i2c.startWrite(ADDR7);
i2c.writeByteAndCheck(0x00);
i2c.writeByteAndCheck(cmd);
//i2c.writeByteAndCheck(0x00);
i2c.writeByteAndCheck(v1);
//i2c.writeByteAndCheck(0x00);
i2c.writeByteAndCheck(v2);
i2c.stop();
}
};
#endif

98
ext/sens/AHT2x.h Normal file
View File

@@ -0,0 +1,98 @@
#pragma once
/**
* humidity and temperature sensor
* https://asairsensors.com/wp-content/uploads/2021/09/Data-Sheet-AHT21-Humidity-and-Temperature-Sensor-ASAIR-V1.0.03.pdf
*/
template <typename I2C> class AHT2x {
private:
I2C& i2c;
static constexpr const uint8_t ADDR = 0x38;
static constexpr const char* NAME = "AHT2x";
static constexpr const uint8_t BIT_BUSY = 0x80;
static constexpr const uint8_t BIT_CALIBRATED = 0x08;
public:
struct Result {
float temp;
float humi;
};
public:
AHT2x(I2C& i2c) : i2c(i2c) {
}
void init() {
softReset();
int ok = calibrate();
printf("aht calib: %d\n", ok);
}
/** is the device present on the bus? */
bool isPresent() {
return i2c.query(ADDR);
}
void softReset() {
uint8_t cmd = 0xBA;
i2c.writeRaw(ADDR, 1, &cmd);
DELAY_MS(100);
}
bool calibrate() {
uint8_t cmdCalibrate[3] = {0xBE, 0x08, 0x00};
i2c.writeRaw(ADDR, 3, cmdCalibrate);
waitUntilDone();
return readStatus() & BIT_CALIBRATED;
}
void waitUntilDone() {
for (uint8_t i = 0; i < 200; ++i) {
printf(".");
if (!(readStatus() & BIT_BUSY)) {break;}
DELAY_MS(1);
}
printf("x\n");
}
uint8_t readStatus() {
uint8_t status = 0;
i2c.readRaw(ADDR, 1, &status);
return status;
}
Result measure() {
// trigger measurement
uint8_t cmdMeasure[3] = {0xAC, 0x33, 0x00};
i2c.writeRaw(ADDR, 3, cmdMeasure);
// fetch result, also waits until the busy-bit is cleared
uint8_t raw[6];
for(uint8_t i = 0; i < 50; ++i) {
DELAY_MS(10);
i2c.readRaw(ADDR, 6, raw);
bool busy = (raw[0] & BIT_BUSY);
if (!busy) {break;}
}
// calculate
Result res;
uint32_t _humi = ((raw[1] << 16) | (raw[2] << 8) | (raw[3] << 0)) >> 4;
res.humi = _humi * 100 / 1048576.0f;
uint32_t _temp = ((raw[3] & 0x0F) << 16) | (raw[4] << 8) | (raw[5] << 0);
res.temp = (_temp * 200 / 1048576.0f) - 50;
return res;
}
};

View File

@@ -4,15 +4,29 @@
#include "../../Platforms.h"
#include "../../Debug.h"
// https://www.mouser.com/datasheet/2/783/BST-BME280-DS002-1509607.pdf
template <typename I2C> class BME280 {
I2C& i2c;
static constexpr const char* NAME = "BME280";
static constexpr uint8_t ADDR7 = 0b1110110;
static constexpr uint8_t ADDR7_1 = 0b1110110; // 0x76
static constexpr uint8_t ADDR7_2 = 0b1110111; // 0x77
static constexpr uint8_t MODE_SLEEP = 0b00;
static constexpr uint8_t MODE_FORCED = 0b01; // manual sampling
static constexpr uint8_t MODE_NORMAL = 0b11; // periodic background sampling
uint8_t ADDR7;
static constexpr uint8_t REG_CTRL1 = 0xF2; // humidity
static constexpr uint8_t REG_CTRL2 = 0xF4; // temp, pressure, mode
static constexpr uint8_t REG_CONFIG = 0xF5;
static constexpr uint8_t REG_STATUS = 0xF3;
static constexpr uint8_t REG_PRESSURE = 0xF7;
static constexpr uint8_t REG_TEMPERATURE= 0xFA;
@@ -22,9 +36,6 @@ template <typename I2C> class BME280 {
static constexpr uint8_t REG_DIG_T2 = 0x8A;
static constexpr uint8_t REG_DIG_T3 = 0x8C;
public:
bool started = false;
/** internal sensor calibration values */
@@ -52,24 +63,141 @@ public:
int8_t dig_H6 = 0;
} cal;
public:
struct Result {
float temp;
float humi;
float pres;
};
enum class Interval : uint8_t {
INT_500_US = 0b000,
INT_62_MS = 0b001,
INT_125_MS = 0b010,
INT_250_MS = 0b011,
INT_500_MS = 0b100,
INT_1000_MS = 0b101,
INT_10_MS = 0b110,
INT_20_MS = 0b111,
};
enum class Oversample : uint8_t {
X1 = 0b001,
X2 = 0b010,
X4 = 0b011,
X8 = 0b100,
X16 = 0b101,
};
private:
struct Config {
Oversample temp = Oversample::X2;
Oversample pres = Oversample::X16;
Oversample humi = Oversample::X2;
uint8_t mode = MODE_NORMAL;
Interval interval = Interval::INT_500_MS;
uint8_t fir = 0b000; // fir filter disabled
} cfg;
public:
I2C& i2c;
BME280(I2C& i2c) : i2c(i2c) {
BME280(I2C& i2c, uint8_t addrOffset = 0) : i2c(i2c), ADDR7(ADDR7_1 + addrOffset) {
}
bool isPresent() {
return i2c.query(ADDR7);
}
void init() {
readCalib();
}
void setSampling(Oversample temp, Oversample pres, Oversample humi) {
cfg.temp = temp;
cfg.pres = pres;
cfg.humi = humi;
}
uint8_t getStatus() {
uint8_t res[1];
i2c.readReg(ADDR7, REG_STATUS, 1, res);
return 0;
}
/** start periodic background measurement (tends to sensor-self-heating??) */
void measurePeriodic(Interval ival) {
cfg.mode = MODE_NORMAL;
cfg.interval = ival;
commitConfig();
}
/** measure only once, takes some time before NEW results are present (seems more stable in terms of temperature) */
void measureOnce() {
cfg.mode = MODE_FORCED;
commitConfig();
}
/** get the most recent readings */
Result getAll() {
Result res;
res.temp = getTemperature();
res.humi = getHumidity();
res.pres = getPressure();
return res;
}
/** get most recent pressure reading (hPa) */
float getPressure() {
uint8_t res[3];
i2c.readReg(ADDR7, REG_PRESSURE, 3, res);
//os_printf("res: %d - %d - %d \n", res[0], res[1], res[2]);
const uint32_t tmp = ((res[0] << 16) | (res[1] << 8) | (res[2] << 0)) >> 4;
const uint32_t pres = BME280_compensate_P_int64(tmp);
const float presF = pres / 256.0f / 100.0f; // convert from Q24.8 to float and from Pa to hPa
//const uint32_t p0 = pres / 256;
//const uint32_t p1 = (uint32_t) presF;
//const uint32_t p2 = (presF - p1) * 100000;
//Log::addInfo((NAME, "[pres] ADC: %d -> %d Pa | %d.%d hPa", tmp, p0, p1,p2);
return presF;
}
/** get most recent temperature reading */
float getTemperature() {
uint8_t res[3];
i2c.readReg(ADDR7, REG_TEMPERATURE, 3, res);
const uint32_t tmp = ((res[0] << 16) | (res[1] << 8) | (res[2] << 0)) >> 4;
const int32_t temp = BME280_compensate_T_int32(tmp);
const float tempF = temp / 100.0f;
return tempF;
}
/** get the most recent humidity reading */
float getHumidity() {
uint8_t res[2];
i2c.readReg(ADDR7, REG_HUMIDITY, 2, res);
//os_printf("res: %d - %d \n", res[0], res[1]);
const uint32_t tmp = (res[0] << 8) | (res[1] << 0);
const int32_t humi = bme280_compensate_H_int32(tmp);
const float humiF = humi / 1024.0f;
//const uint16_t h0 = humi / 1024;
//const uint16_t h1 = (uint16_t) humiF;
//const uint16_t h2 = (humiF - humi) * 10000;
//Log::addInfo((NAME, "[humi] ADC: %d -> %d -> %d.%d %%", tmp, h0, h1,h2);
return humiF;
}
private:
void readCalib() {
debugMod(NAME, "readCalib()");
Log::addInfo(NAME, "readCalib()");
// read all 24 calibration bytes for temperature and pressure
uint8_t b1[24];
@@ -93,137 +221,33 @@ private:
i2c.readReg(ADDR7, 0xA1, 1, &cal.dig_H1);
i2c.readReg(ADDR7, 0xE1, 7, b1);
cal.dig_H2 = (b1[1] << 8) | b1[0];
cal.dig_H3 = b1[3];
cal.dig_H4 = (b1[3] << 4) | (b1[4] & 0b000001111);
cal.dig_H5 = (b1[5] << 4) | ((b1[4] & 0b111100000) >> 4);
cal.dig_H3 = b1[2];
cal.dig_H4 = (b1[3] << 4) | (b1[4] & 0b00001111);
cal.dig_H5 = (b1[5] << 4) | ((b1[4] & 0b11110000) >> 4);
cal.dig_H6 = (b1[6]);
//os_printf("calib temp: %d %d %d\n", cal.dig_T1, cal.dig_T2, cal.dig_T3);
//os_printf("calib pres: %d %d %d %d %d %d %d %d %d\n", cal.dig_P1, cal.dig_P2, cal.dig_P3, cal.dig_P4, cal.dig_P5, cal.dig_P6, cal.dig_P7, cal.dig_P8, cal.dig_P9);
//os_printf("calib humi: %d %d %d %d %d %d\n", cal.dig_H1, cal.dig_H2, cal.dig_H3, cal.dig_H4, cal.dig_H5, cal.dig_H6);
//debugMod3(NAME, "calTemp: %d %d %d", cal.dig_T1, cal.dig_T2, cal.dig_T3);
//debugMod9(NAME, "calPres: %d %d %d %d %d %d %d %d %d", cal.dig_P1, cal.dig_P2, cal.dig_P3, cal.dig_P4, cal.dig_P5, cal.dig_P6, cal.dig_P7, cal.dig_P8, cal.dig_P9);
//debugMod6(NAME, "calHumi: %d %d %d %d %d %d", cal.dig_H1, cal.dig_H2, cal.dig_H3, cal.dig_H4, cal.dig_H5, cal.dig_H6);
Log::addInfo(NAME, "calTemp: %d %d %d", cal.dig_T1, cal.dig_T2, cal.dig_T3);
Log::addInfo(NAME, "calPres: %d %d %d %d %d %d %d %d %d", cal.dig_P1, cal.dig_P2, cal.dig_P3, cal.dig_P4, cal.dig_P5, cal.dig_P6, cal.dig_P7, cal.dig_P8, cal.dig_P9);
Log::addInfo(NAME, "calHumi: %d %d %d %d %d %d", cal.dig_H1, cal.dig_H2, cal.dig_H3, cal.dig_H4, cal.dig_H5, cal.dig_H6);
}
void start() {
debugMod(NAME, "start()");
const uint8_t cfgHumi = 0b101; // 16x oversampling
const uint8_t cfgPres = 0b101; // 16x oversampling
const uint8_t cfgTemp = 0b101; // 16x oversampling
const uint8_t cfgMode = 0b11;
const uint8_t cfg1 = (cfgHumi << 1);
const uint8_t cfg2 = (cfgTemp << 5) | (cfgPres << 2) | (cfgMode << 0);
i2c.writeReg(ADDR7, REG_CTRL1, 1, &cfg1);
i2c.writeReg(ADDR7, REG_CTRL2, 1, &cfg2);
void commitConfig() {
const uint8_t ctrl1 = (uint8_t(cfg.humi) << 0);
const uint8_t ctrl2 = (uint8_t(cfg.temp) << 5) | (uint8_t(cfg.pres) << 2) | (uint8_t(cfg.mode) << 0);
const uint8_t conf = (uint8_t(cfg.interval) << 5) | (cfg.fir << 2);
i2c.writeReg8(ADDR7, REG_CTRL1, MODE_SLEEP);
i2c.writeReg8(ADDR7, REG_CTRL1, ctrl1);
i2c.writeReg8(ADDR7, REG_CTRL2, ctrl2);
i2c.writeReg8(ADDR7, REG_CONFIG, conf);
}
public:
void startOnce() {
if (started) {return;}
debugMod(NAME, "startOnce()");
readCalib();
start();
started = true;
}
uint8_t getStatus() {
uint8_t res[1];
i2c.readReg(ADDR7, REG_STATUS, 1, res);
//os_printf("Status: %d \n", res[0]);
return 0;
}
/** get current pressure in hPa */
float getPressure() {
uint8_t res[3];
i2c.readReg(ADDR7, REG_PRESSURE, 3, res);
//os_printf("res: %d - %d - %d \n", res[0], res[1], res[2]);
const uint32_t tmp = ((res[0] << 16) | (res[1] << 8) | (res[2] << 0)) >> 4;
const uint32_t pres = BME280_compensate_P_int64(tmp);
const float presF = pres / 256.0f / 100.0f; // convert from Q24.8 to float and from Pa to hPa
//const uint32_t p0 = pres / 256;
//const uint32_t p1 = (uint32_t) presF;
//const uint32_t p2 = (presF - p1) * 100000;
//debugMod4(NAME, "[pres] ADC: %d -> %d Pa | %d.%d hPa", tmp, p0, p1,p2);
return presF;
}
float getTemperature() {
uint8_t res[3];
i2c.readReg(ADDR7, REG_TEMPERATURE, 3, res);
//os_printf("res: %d - %d - %d \n", res[0], res[1], res[2]);
const uint32_t tmp = ((res[0] << 16) | (res[1] << 8) | (res[2] << 0)) >> 4;
const int32_t temp = BME280_compensate_T_int32(tmp);
const float tempF = temp / 100.0f;
//debugMod2(NAME, "[temp] ADC: %d -> %d", tmp, temp);
return tempF;
}
float getHumidity() {
uint8_t res[2];
i2c.readReg(ADDR7, REG_HUMIDITY, 2, res);
//os_printf("res: %d - %d \n", res[0], res[1]);
const uint32_t tmp = (res[0] << 8) | (res[1] << 0);
const int32_t humi = bme280_compensate_H_int32(tmp);
const float humiF = humi / 1024.0f;
//const uint16_t h0 = humi / 1024;
//const uint16_t h1 = (uint16_t) humiF;
//const uint16_t h2 = (humiF - humi) * 10000;
//debugMod4(NAME, "[humi] ADC: %d -> %d -> %d.%d %%", tmp, h0, h1,h2);
return humiF;
}
/*
bool readRegister(const uint8_t addr, uint8_t* dst, const uint8_t len) {
bool ok;
// address the slave in write mode and select the first register to read
ok = i2c.startWrite(ADDR7);
if (!ok) {printf("failed start write\n"); return false;}
ok = i2c.writeByteAndCheck(addr);
if (!ok) {printf("failed to select register %d\n", addr); return false;}
//i2c::stop();
// address the slave in read mode and read [len] registers
ok = i2c.startRead(ADDR7);
if (!ok) {printf("failed start read\n"); return 0;}
i2c.readBytes(dst, len);
// done
i2c.stop();
return true;
}
bool writeRegister(const uint8_t addr, const uint8_t* src, const uint8_t len) {
bool ok;
// address the slave in write mode and select the first register to read
ok = i2c.startWrite(ADDR7);
if (!ok) {printf("failed start write\n"); return false;}
ok = i2c.writeByteAndCheck(addr);
if (!ok) {printf("failed to select register %d\n", addr); return false;}
ok = i2c.writeBytesAndCheck(src, len);
if (!ok) {printf("failed to write register contents \n"); return false;}
// done
i2c.stop();
return true;
}
*/
private:
/** conversions from ADC values to real-world values. from Bosch BMP280 manual! */
using BME280_S32_t = int32_t;
@@ -251,7 +275,7 @@ private:
var2 = var2 + ((var1*(BME280_S64_t)cal.dig_P5)<<17);
var2 = var2 + (((BME280_S64_t)cal.dig_P4)<<35);
var1 = ((var1 * var1 * (BME280_S64_t)cal.dig_P3)>>8) + ((var1 * (BME280_S64_t)cal.dig_P2)<<12);
var1 = (((((BME280_S64_t)1)<<47)+var1))*((BME280_S64_t)cal.dig_P1)>>33;
var1 = (((((BME280_S64_t)1l)<<47)+var1))*((BME280_S64_t)cal.dig_P1)>>33;
if (var1 == 0) {return 0;} // avoid exception caused by division by zero
p = 1048576-adc_P;
p = (((p<<31)-var2)*3125)/var1;

74
ext/sens/CMPS10.h Normal file
View File

@@ -0,0 +1,74 @@
#pragma once
#include "../../Platforms.h"
#include "../../Debug.h"
/**
* CMPS10 3-axis magnetometer/accelerometer module
* https://www.robot-electronics.co.uk/htm/cmps10i2c.htm
*/
template <typename I2C> class CMPS10 {
I2C& i2c;
static constexpr const char* NAME = "CMPS10";
static constexpr uint8_t ADDR7 = 0xC0>>1;
static constexpr uint8_t REG_MAG_X = 10; //10+11;
static constexpr uint8_t REG_MAG_Y = 12; //12+13;
static constexpr uint8_t REG_MAG_Z = 14; //14+15;
static constexpr uint8_t REG_ACC_X = 16; //16+17;
static constexpr uint8_t REG_ACC_Y = 18; //18+19;
static constexpr uint8_t REG_ACC_Z = 20; //20+21;
public:
struct Magnetometer {
int16_t x;
int16_t y;
int16_t z;
};
struct Acceleromter {
int16_t x;
int16_t y;
int16_t z;
};
public:
CMPS10(I2C& i2c, uint8_t addrOffset = 0) : i2c(i2c){
}
bool isPresent() {
return i2c.query(ADDR7);
}
Magnetometer getMagnetometer() {
uint8_t res[6];
i2c.readReg(ADDR7, REG_MAG_X, 6, res);
Magnetometer mag;
mag.x = ((res[0] << 8) | (res[1] << 0));
mag.y = ((res[2] << 8) | (res[3] << 0));
mag.z = ((res[4] << 8) | (res[5] << 0));
return mag;
}
Acceleromter getAcceleromter() {
uint8_t res[6];
i2c.readReg(ADDR7, REG_ACC_X, 6, res);
Acceleromter acc;
acc.x = ((res[0] << 8) | (res[1] << 0));
acc.y = ((res[2] << 8) | (res[3] << 0));
acc.z = ((res[4] << 8) | (res[5] << 0));
return acc;
}
};

146
ext/sens/ENS160.h Normal file
View File

@@ -0,0 +1,146 @@
#pragma once
/**
* a digital multi-gas sensor for indoor air quality monitoring
* https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-7-ENS160-Datasheet.pdf
*/
template <typename I2C> class ENS160 {
private:
I2C& i2c;
static constexpr const uint8_t ADDR1 = 0x52;
static constexpr const uint8_t ADDR2 = 0x53;
uint8_t ADDR;
static constexpr const char* NAME = "ENS160";
static constexpr const uint8_t REG_PART_ID = 0x00;
static constexpr const uint8_t REG_OPMODE = 0x10;
static constexpr const uint8_t REG_TEMP_IN = 0x13;
static constexpr const uint8_t REG_RH_IN = 0x15;
static constexpr const uint8_t REG_DEVICE_STATUS = 0x20;
static constexpr const uint8_t REG_DATA_AQI = 0x21;
static constexpr const uint8_t REG_DATA_TVOC = 0x22;
static constexpr const uint8_t REG_DATA_ECO2 = 0x24;
public:
enum class Mode : uint8_t {
DEEP_SLEEP = 0x00,
IDLE = 0x01,
STANDARD = 0x02,
RESET = 0xF0,
};
union Status {
uint8_t raw;
struct {
uint8_t newgpr : 1;
uint8_t newdat : 1;
uint8_t valid : 2;
uint8_t dummy : 2;
uint8_t stater : 1;
uint8_t statas : 1;
} __attribute__((__packed__));
bool isNormal() const {return valid == 0;}
bool isWarmUp() const {return valid == 1;}
bool isStartUp() const {return valid == 2;}
bool isInvalid() const {return valid == 3;}
} __attribute__((__packed__));
union Result {
uint8_t raw[6];
struct {
Status status;
uint8_t aqi;
uint16_t tvoc;
uint16_t eco2;
} __attribute__((__packed__));
} __attribute__((__packed__));
public:
ENS160(I2C& i2c, uint8_t addr = ADDR1) : i2c(i2c), ADDR(addr) {
}
/** always 0x01 0x60 */
uint16_t getID() {
uint16_t res = 0;
i2c.readReg(ADDR, 0x00, 2, (uint8_t*)&res);
return res;
}
/** switch the mode of operation */
void setMode(Mode m) {
i2c.writeReg8(ADDR, REG_OPMODE, (uint8_t)m);
DELAY_MS(250); // not mentioned in the data-sheet but important?
}
/** set the current ambient temperature - for compensation */
void setTemp(float celsius) {
float kelvin = celsius + 273.15;
uint16_t tmp = (uint16_t) (kelvin * 64);
i2c.writeReg(ADDR, REG_TEMP_IN, 2, (uint8_t*)&tmp);
}
/** set the current ambient humidity (in %) - for compensation */
void setHumi(uint8_t humi) {
uint16_t tmp = humi * 512;
i2c.writeReg(ADDR, REG_RH_IN, 2, (uint8_t*)&tmp);
}
/** status + aqi + tvoc + eco2 */
Result getAll() {
Result res;
memset(res.raw, 0, 6);
i2c.readReg(ADDR, REG_DEVICE_STATUS, 6, res.raw);
res.status = getStatus();
//res.aqi = getAQI();
//res.tvoc = getTVOC();
//res.eco2 = getECO2();
return res;
}
Status getStatus() {
Status res;
res.raw = i2c.readReg8(ADDR, REG_DEVICE_STATUS);
return res;
}
uint8_t getAQI() {
uint8_t tmp = 0;
i2c.readReg(ADDR, REG_DATA_AQI, 1, &tmp);
return tmp & 0b111;
}
uint16_t getTVOC() {
uint16_t tmp = 0;
i2c.readReg(ADDR, REG_DATA_TVOC, 2, (uint8_t*)&tmp);
return tmp;
}
uint16_t getECO2() {
uint16_t tmp = 0;
i2c.readReg(ADDR, REG_DATA_ECO2, 2, (uint8_t*)&tmp);
return tmp;
}
/** is the device present on the bus? */
bool isPresent() {
return i2c.query(ADDR);
}
};

101
ext/sens/HTU2x.h Normal file
View File

@@ -0,0 +1,101 @@
#pragma once
/**
* humidity and temperature sensor
* https://www.ttieurope.com/content/dam/tti-europe/manufacturers/te-connectivity/resources/ENG_DS_HPC199_6_A6.pdf
*
* this sensor seems to be a bit stubborn.. requiring active polling until it is finished
* just waiting some time does not seem to work
*
*/
template <typename I2C> class HTU2x {
private:
I2C& i2c;
static constexpr const uint8_t ADDR = 0x40;
static constexpr const char* NAME = "HTU2x";
static constexpr const uint8_t CMD_QUERY_TEMP = 0xF3;
static constexpr const uint8_t CMD_QUERY_HUMI = 0xF5;
static constexpr const uint8_t CMD_READ_USER_REG = 0xE7;
static constexpr const uint8_t CMD_SOFT_RESET = 0xFE;
public:
HTU2x(I2C& i2c) : i2c(i2c) {
}
/** is the device present on the bus? */
bool isPresent() {
return i2c.query(ADDR);
}
struct Result {
float temp;
float humi;
float humiComp;
};
/** trigger a single measurement */
Result singleMeasure() {
uint8_t tmp[3]; // 2 bytes + checksum
Result res;
sendCMD(CMD_QUERY_TEMP);
if (waitForStart()) {
i2c.readBytes(tmp, 3);
i2c.stop();
uint16_t t = ((tmp[0]<<8)|(tmp[1]<<0)) & 0xFFFC;
//printf("a: %d\n", t);
res.temp = -46.85f + 175.72 * t / float(1<<16);
}
sendCMD(CMD_QUERY_HUMI);
if (waitForStart()) {
i2c.readBytes(tmp, 3);
i2c.stop();
uint16_t h = ((tmp[0]<<8)|(tmp[1]<<0)) & 0xFFFC;
//printf("b: %d\n", h);
res.humi = -6 + 125 * h / float(1<<16);
res.humiComp = res.humi + (-0.15 * (25 - res.temp));
}
return res;
}
private:
bool waitForStart() {
for (int i = 0; i < 1024; ++i) {
if (i2c.startRead(ADDR)) {return true;}
}
return false;
}
void sendCMD(uint8_t cmd) {
i2c.writeRaw(ADDR, 1, &cmd);
}
void readUserRegister() {
i2c.writeRaw(ADDR, 1, &CMD_SOFT_RESET);
vTaskDelay(100 / portTICK_PERIOD_MS);
uint8_t val = 0xaa;
i2c.readReg(ADDR, CMD_READ_USER_REG, 1, &val);
printf("user: %d\n", val);
}
};

123
ext/sens/LIS3MDL.h Normal file
View File

@@ -0,0 +1,123 @@
#pragma once
#include "../../Platforms.h"
#include "../../Debug.h"
/**
* LIS3MDL 3-axis magnetometer module
* https://www.st.com/resource/en/datasheet/lis3mdl.pdf
*/
template <typename I2C> class LIS3MDL {
I2C& i2c;
static constexpr const char* NAME = "LIS3MDL";
static constexpr uint8_t ADDR7 = 0b0011100;
static constexpr uint8_t CTRL_REG1 = 0x20;
static constexpr uint8_t CTRL_REG2 = 0x21;
static constexpr uint8_t CTRL_REG3 = 0x22;
static constexpr uint8_t CTRL_REG4 = 0x23;
static constexpr uint8_t CTRL_REG5 = 0x24;
static constexpr uint8_t REG_X = 0x28; //0x28(L= + 0x29(H)
static constexpr uint8_t REG_Y = 0x2A; //0x2A(L) + 0x2B(H)
static constexpr uint8_t REG_Z = 0x2C; //0x2C(L) + 0x2D(H)
public:
struct Magnetometer {
int16_t x;
int16_t y;
int16_t z;
};
struct Acceleromter {
int16_t x;
int16_t y;
int16_t z;
};
enum class Resolution : uint8_t {
GAUSS_4 = 0b00,
GAUSS_8 = 0b01,
GAUSS_12 = 0b10,
GAUSS_16 = 0b11,
};
enum class AxisMode : uint8_t {
LOW_POWER = 0b00,
MEDIUM_PERFORMANCE = 0b01,
HIGH_PERFORMANCE = 0b10,
ULTRA_HIGH_PERFORMANCE = 0b11,
};
enum class SamplingRate : uint8_t {
HZ_0_625,
HZ_1_25,
HZ_2_5,
HZ_5,
HZ_10,
HZ_20,
HZ_40,
HZ_80,
};
enum class OperationMode : uint8_t {
CONTINUOUS = 0b00,
SINGLE = 0b01,
OFF = 0b11,
};
public:
LIS3MDL(I2C& i2c, uint8_t addrOffset = 0) : i2c(i2c){
}
bool isPresent() {
return i2c.query(ADDR7);
}
void setResolution(Resolution res) {
getAndSet(CTRL_REG2, 0b01100000, (uint8_t)res << 5);
}
void setAxisMode(AxisMode mode) {
getAndSet(CTRL_REG1, 0b01100000, (uint8_t)mode << 5); // x and y
getAndSet(CTRL_REG4, 0b00001100, (uint8_t)mode << 2); // z
}
void setSamplingRate(SamplingRate rate) {
getAndSet(CTRL_REG1, 0b00011100, (uint8_t)rate << 2);
}
void setOperationMode(OperationMode mode) {
getAndSet(CTRL_REG3, 0b00000011, (uint8_t)mode << 0);
}
Magnetometer getMagnetometer() {
uint8_t res[6];
i2c.readReg(ADDR7, REG_X, 6, res);
Magnetometer mag;
mag.x = ((res[0] << 0) | (res[1] << 8));
mag.y = ((res[2] << 0) | (res[3] << 8));
mag.z = ((res[4] << 0) | (res[5] << 8));
return mag;
}
void getAndSet(uint8_t reg, uint8_t setMask, uint8_t setVal) {
uint8_t tmp = i2c.readReg8(ADDR7, reg);
tmp = (tmp & ~setMask) | setVal;
i2c.writeReg8(ADDR7, reg, tmp);
}
};

109
ext/sens/SGP30.h Normal file
View File

@@ -0,0 +1,109 @@
#pragma once
/**
* air quality sensor
* https://sensirion.com/media/documents/984E0DD5/61644B8B/Sensirion_Gas_Sensors_Datasheet_SGP30.pdf
*/
template <typename I2C> class SGP30 {
private:
I2C& i2c;
static constexpr const uint8_t ADDR = 0x58;
static constexpr const char* NAME = "SGP30";
static constexpr const uint16_t CMD_IAQ_INIT = 0x2003;
static constexpr const uint16_t CMD_IAQ_MEASURE = 0x2008;
static constexpr const uint16_t CMD_GET_SERIAL = 0x3682;
public:
SGP30(I2C& i2c) : i2c(i2c) {
}
void init() {
sendCommand(CMD_IAQ_INIT);
}
/** is the device present on the bus? */
bool isPresent() {
return i2c.query(ADDR);
}
struct Result {
uint16_t co2e;
uint16_t tvoc;
};
/** should be called ~ every second */
Result measure() {
sendCommand(CMD_IAQ_MEASURE);
vTaskDelay(100 / portTICK_PERIOD_MS);
uint8_t tmp[6];
i2c.readRaw(ADDR, 6, tmp);
Result res;
res.co2e = (tmp[0]<<8) | (tmp[1]<<0);
res.tvoc = (tmp[3]<<8) | (tmp[4]<<0);
return res;
}
void getSerial() {
struct Serial {
uint8_t v1[2];
uint8_t crc1;
uint8_t v2[2];
uint8_t crc2;
uint8_t v3[2];
uint8_t crc3;
} serial;
sendCommand(CMD_GET_SERIAL);
uint8_t tmp[9];
i2c.readRaw(ADDR, 9, &serial);
/*
uint8_t a = calcCRC(tmp+0, 2);
uint8_t b = calcCRC(tmp+3, 2);
uint8_t c = calcCRC(tmp+6, 2);
printf("%d %d %d %d\n", tmp[0], tmp[1], tmp[2], a);
printf("%d %d %d %d\n", tmp[3], tmp[4], tmp[5], b);
printf("%d %d %d %d\n", tmp[6], tmp[7], tmp[8], c);
*/
}
private:
void sendCommand(uint16_t cmd) {
uint8_t tmp[2];
tmp[0] = cmd >> 8;
tmp[1] = cmd >> 0;
i2c.writeRaw(ADDR, 2, tmp);
}
static uint8_t calcCRC(const uint8_t* data, uint8_t len) {
uint8_t crc = 0xff;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if ((crc & 0x80) != 0) {
crc = (uint8_t)((crc << 1) ^ 0x31);
} else {
crc <<= 1;
}
}
}
return crc;
}
};

93
ext/sens/SHT3x.h Normal file
View File

@@ -0,0 +1,93 @@
#pragma once
/**
* humidity and temperature sensor
* https://www.mouser.com/datasheet/2/682/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital-971521.pdf
*
* sensor seems to be really good! and very easy to use!
*
*/
template <typename I2C> class SHT3x {
private:
I2C& i2c;
static constexpr const uint8_t ADDR = 0x44;
static constexpr const char* NAME = "SHT3x";
public:
SHT3x(I2C& i2c) : i2c(i2c) {
}
/** is the device present on the bus? */
bool isPresent() {
return i2c.query(ADDR);
}
struct Result {
float temp;
float humi;
};
/** trigger a single measurement */
Result singleMeasure() {
uint16_t cmd = 0x2400; // high quality, no clock stretching
sendCommand(cmd);
vTaskDelay(100 / portTICK_PERIOD_MS);
return readResult();
}
void startPeriodicMeasure() {
uint16_t cmd = 0x2130; // high quality, 1 measurement per second
sendCommand(cmd);
}
Result getLastResult() {
return readResult();
}
private:
Result readResult() {
uint8_t tmp[6];
i2c.readRaw(ADDR, 6, (uint8_t*)&tmp);
Result res;
res.temp = -45 + 175 * ((tmp[0]<<8) | (tmp[1]<<0)) / float((1<<16)-1);
res.humi = 100 * ((tmp[3]<<8) | (tmp[4]<<0)) / float((1<<16)-1);
return res;
}
void sendCommand(uint16_t cmd) {
uint8_t tmp[2];
tmp[0] = cmd >> 8;
tmp[1] = cmd >> 0;
i2c.writeRaw(ADDR, 2, tmp);
}
static uint8_t calcCRC(const uint8_t* data, uint8_t len) {
uint8_t crc = 0xff;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if ((crc & 0x80) != 0) {
crc = (uint8_t)((crc << 1) ^ 0x31);
} else {
crc <<= 1;
}
}
}
return crc;
}
};