Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion examples/3prog1/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ endif

AM_CFLAGS = -std=c11 -flto
AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/modules
LDADD = $(top_builddir)/src/libesp32basic.a $(top_builddir)/modules/libesp32modules.a
LDADD = $(top_builddir)/modules/libesp32modules.a $(top_builddir)/src/libesp32basic.a

bin_PROGRAMS = \
prog.elf


if WITH_BINARIES
CLEANFILES = \
prog.bin
Expand Down
6 changes: 6 additions & 0 deletions examples/3prog1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ A simple pattern is being written to the OLED display from left to right.
* I2C_SLAVE#1: BME280 temperature/pressure/humidity sensor
* I2C_SLAVE#2: BH1750FVI light sensor
* I2C_SLAVE#3: 32x128 OLED display
* I2C_SLAVE#4: SHT40 Temp/hum sensor
* [NODE0 .. NODE4]: 5 nodes (maybe on breadboard) as connection nodes of multiple wires.

#### Connections
Expand Down Expand Up @@ -44,4 +45,9 @@ OLED.GND -- NODE4
OLED.VCC -- NODE3
OLED.SCL -- NODE1
OLED.SDA -- NODE2

SHT40.GND -- NODE4
SHT40.VIN -- NODE3
SHT40.SCL -- NODE1
SHT40.SDA -- NODE2
```
88 changes: 78 additions & 10 deletions examples/3prog1/prog.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "typeaux.h"
#include "bme280.h"
#include "bh1750.h"
#include "sht4x.h"
#include "ssd1306.h"
#include "utils/i2cutils.h"
#include "utils/uartutils.h"
Expand All @@ -39,6 +40,7 @@
#define OLED_PERIOD_MS 100U
#define BH1750_PERIOD_MS 1333U
#define BME280_PERIOD_MS 5200U
#define SHT40_PERIOD_MS 4800U
#define LOG_PERIOD_MS 4000U
#define INC_PERIOD_MS 1900U
#define I2CSCAN_PERIOD_MS 8600U
Expand All @@ -62,6 +64,9 @@
#define BME280_I2C_CH I2C1
#define BME280_I2C_SLAVEADDR 0x76

#define SHT40_I2C_CH I2C1
#define SHT40_I2C_SLAVEADDR 0x44

// #3: Sizes
#define UART0_TXSIZE 1U
#define I2CSCAN_PRINT_PER_ROW 8
Expand Down Expand Up @@ -117,6 +122,8 @@ static void _bh1750_cycle(uint64_t u64tckNow);
static void _bme280_init(SBme280StateDesc *psState, SI2cIfaceCfg *psIface);
static void _bme280_print_result(uint64_t u64tckNow, const SBme280TPH *psRes, uint32_t u32TFine);
static void _bme280_cycle(uint64_t u64tckNow);
static void _sht40_print_result(uint64_t u64tckNow, SSht40StateDesc *psState);
static void _sht40_cycle(uint64_t u64tckNow);
static void _log_cycle(uint64_t u64tckNow);
static void _inc_cycle(uint64_t u64tckNow);
static void _uartctrl_cycle(uint64_t u64tckNow);
Expand Down Expand Up @@ -205,12 +212,7 @@ static void _init_drivers() {

static void _init_uart() {
gpsUART0->CLKDIV.raw = UART_HZ2CLKDIV(UART_FREQ_HZ, APB_FREQ_HZ);

Reg rUartMemConf = gpsUART0->MEM_CONF;
rUartMemConf &= ~(0xf << 7);
rUartMemConf |= UART0_TXSIZE << 7;

gpsUART0->MEM_CONF = rUartMemConf;
uart_set_memconf_xsize(gpsUART0, true, UART0_TXSIZE);
}

// TODO: make it an ISR and attach to I2C INT
Expand Down Expand Up @@ -384,6 +386,71 @@ static void _bme280_cycle(uint64_t u64tckNow) {
}
}

// Section SHT40

static void _sht40_print_result(uint64_t u64tckNow, SSht40StateDesc *psState) {
_uart_print_header(gpsUART0, u64tckNow, "SHT40");
uart_printf(gpsUART0, " cmd: #%u, raw: %02X %02X %02X %02X %02X %02X,",
psState->eCommand,
psState->au8RxBuffer[0], psState->au8RxBuffer[1], psState->au8RxBuffer[2],
psState->au8RxBuffer[3], psState->au8RxBuffer[4], psState->au8RxBuffer[5]);
if (psState->eCommand != SHT4X_CMD_SERIAL) {
int32_t i32TempM = sht4x_get_temp(psState);
int32_t i32HumM = sht4x_get_hum(psState);
uart_printf(gpsUART0, " Temp: %d.%03d, Hum: %d.%03d\r\n", i32TempM / 1000, i32TempM % 1000, i32HumM / 1000, i32HumM % 1000);
} else {
uint32_t u32Serial = sht4x_get_serial(psState);
uart_printf(gpsUART0, " Serial number: %08X\r\n", u32Serial);
}
// check crc
if (!sht4x_check_crc(psState, true)) {
uart_printf(gpsUART0, " 1st CRC8 does not match (Temp)\r\n");
}
if (!sht4x_check_crc(psState, true)) {
uart_printf(gpsUART0, " 2nd CRC8 does not match (Hum)\r\n");
}
}

static void _sht40_cycle(uint64_t u64tckNow) {
static ESht4xCommand eCommand = SHT4X_CMD_MEAS_H;
static uint64_t u64tckNext = MS2TICKS(SHT40_PERIOD_MS);
static bool bFirstRun = true;
static SSht40StateDesc sState;

if (bFirstRun) {
sState = sht40_init_descriptor((SI2cIfaceCfg){SHT40_I2C_CH, SHT40_I2C_SLAVEADDR, _i2c_to_lock(SHT40_I2C_CH)});
bFirstRun = false;
}

uint32_t u32msWait = 0;

if (u64tckNext <= u64tckNow) {
if (sState.eState == SHT4X_STATE_READY || sState.eState == SHT4X_STATE_ERROR) {
if (sState.eState == SHT4X_STATE_ERROR) {
_uart_print_header(gpsUART0, u64tckNow, "SHT40");
uart_printf(gpsUART0, " ERROR!");
} else {
if (sState.eCommand != SHT4X_CMD_RESET) {
_sht40_print_result(u64tckNow, &sState);
}
}
u32msWait = SHT40_PERIOD_MS;
++eCommand;
if (SHT4X_CMD_RESET < eCommand) { // here we skip SHT4X_CMD_HEAT* commands
eCommand = SHT4X_CMD_MEAS_H;
}
sState.eState = SHT4X_STATE_IDLE;
} else if (sState.eState == SHT4X_STATE_IDLE) {
sht40_set_command(&sState, eCommand);
}

if (sht40_needs_rxtx(&sState)) {
u32msWait = sht4x_rxtx_cycle(&sState);
}
u64tckNext += MS2TICKS(u32msWait);
}
}

// Section BH1750FVI

static void _bh1750_init(SBh1750StateDesc *psState, SI2cIfaceCfg *psIface) {
Expand All @@ -408,7 +475,7 @@ static void _bh1750_print_result(uint64_t u64tckTimestamp, const SBh1750StateDes
_uart_print_header(gpsUART0, u64tckTimestamp, "BH1750");
uart_printf(gpsUART0, " mode: %s, result: %d.%03d lx (raw: %u), mtime: %u ms (raw: %u)\r\n",
acBh1750MResName[eMRes],
u32mLx/1000, u32mLx%1000, u16Result,
u32mLx / 1000, u32mLx % 1000, u16Result,
u32hmsMTime / 2, u8MTime);
}

Expand Down Expand Up @@ -568,7 +635,7 @@ static void _uartctrl_cycle(uint64_t u64tckNow) {
if (0 == u8WaitForArgs) { // all the required number of arguments arrived
switch (cCommand) {
case 'w': // put a byte into lockmgr RX buffer
uint8_t u8ArgValue= char_to_hex8(acArg[0]) | (char_to_hex8(acArg[1]) << 4);
uint8_t u8ArgValue = char_to_hex8(acArg[0]) | (char_to_hex8(acArg[1]) << 4);
ELockmgrResource eRes = _i2c_to_lock(OLED_I2C_CH);
bool bLocked = lockmgr_is_locked(eRes);
if (bLocked) {
Expand Down Expand Up @@ -610,7 +677,7 @@ static void _uartctrl_cycle(uint64_t u64tckNow) {
}
uart_printf(gpsUART0, "\r\n");
}
break;
break;
case 'r':
{
_uart_print_header(gpsUART0, u64tckNow, "CTRL");
Expand All @@ -624,7 +691,7 @@ static void _uartctrl_cycle(uint64_t u64tckNow) {
lockmgr_free_lock(eRes);
}
}
break;
break;
case 'w':
cCommand = 'w';
u8WaitForArgs = 2;
Expand Down Expand Up @@ -675,5 +742,6 @@ void prog_cycle_pro(uint64_t u64tckNow) {
_i2cscan_cycle(u64tckNow);
_bh1750_cycle(u64tckNow);
_bme280_cycle(u64tckNow);
_sht40_cycle(u64tckNow);
_uartctrl_cycle(u64tckNow);
}
4 changes: 2 additions & 2 deletions modules/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ libesp32modules_a_AR=$(AR) rcs

lib_LIBRARIES = libesp32modules.a

include_HEADERS = bh1750.h bme280.h dht22.h ssd1306.h tm1637.h ws2812.h
include_HEADERS = bh1750.h bme280.h dht22.h sht4x.h ssd1306.h tm1637.h ws2812.h
nodist_include_HEADERS =

libesp32modules_a_SOURCES = bh1750.c bme280.c dht22.c ssd1306.c tm1637.c ws2812.c
libesp32modules_a_SOURCES = bh1750.c bme280.c dht22.c sht4x.c ssd1306.c tm1637.c ws2812.c
nodist_libesp32modules_a_SOURCES =

CLEANFILES =
Expand Down
187 changes: 187 additions & 0 deletions modules/sht4x.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Copyright 2024 - 2025 SZIGETI János
*
* This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3.
* See LICENSE or <https://www.gnu.org/licenses/> for full license details.
*/

#include "i2c.h"
#include "sht4x.h"
#include "utils/crc.h"

// ============== Defines ==============

// ============== Local types ==============

// ============== Local data ==============
static const uint8_t gau8Command[] = {
0xFD, 0xF6, 0xE0,
0x89, 0x94,
0x39, 0x32,
0x2F, 0x24,
0x1E, 0x15
};

static const uint32_t gau32msWait[] = {
9, 5, 2,
1, 1,
1100, 110,
1100, 110,
1100, 110
};
// ============== Internal function declarations ==============
static inline bool _command_has_data_response(ESht4xCommand eCommand);
static inline uint16_t _pu8_to_u16(const uint8_t *pu8Data);

// ============== Implementation ==============
// -------------- Internal functions --------------
/**
* Tells whether a given command produces data to be read or not.
* @param eCommand Given command.
* @return The command produces data to be read.
*/
static inline bool _command_has_data_response(ESht4xCommand eCommand) {
return eCommand != SHT4X_CMD_RESET;
}

/**
* Converts the first two bytes of a (big endian) data stream to uint16_t.
* @param pu8Data Input data stream with at least 2 bytes.
* @return The first two bytes converted into uint16_t type.
*/
static inline uint16_t _pu8_to_u16(const uint8_t *pu8Data) {
return (pu8Data[0] << 8) | (pu8Data[1] & 0xFF);
}

// -------------- Interface functions --------------
/**
* Get raw temperature data.
* Valid only in SHT4X_STATE_READY communication state
* and for SHT4X_CMD_MEAS_* or SHT4X_CMD_HEAT* commands.
* @param psState Current state.
* @return Raw temperature data.
*/
uint16_t sht4x_get_temp_raw(const SSht40StateDesc *psState) {
return _pu8_to_u16(psState->au8RxBuffer);
}

/**
* Get raw humidity data.
* Valid only in SHT4X_STATE_READY communication state
* and for SHT4X_CMD_MEAS_* or SHT4X_CMD_HEAT* commands.
* @param psState Current state.
* @return Raw humidity data.
*/
uint16_t sht4x_get_hum_raw(const SSht40StateDesc *psState) {
return _pu8_to_u16(psState->au8RxBuffer + 3);
}

/**
* Get temperature value in 0.001°C resolution.
* Valid only in SHT4X_STATE_READY communication state
* and for SHT4X_CMD_MEAS_* or SHT4X_CMD_HEAT* commands.
* @param psState Current state.
* @return Temperature data in 0.001°C resolution.
*/
int32_t sht4x_get_temp(const SSht40StateDesc *psState) {
return ((sht4x_get_temp_raw(psState) * 2734) >> 10) - 45000;
}

/**
* Get relative humidity data in 0.001% resolution.
* Valid only in SHT4X_STATE_READY communication state
* and for SHT4X_CMD_MEAS_* or SHT4X_CMD_HEAT* commands.
* @param psState Current state.
* @return Humidity data in 0.001% resolution.
*/
int32_t sht4x_get_hum(const SSht40StateDesc *psState) {
return ((sht4x_get_hum_raw(psState) * 1953) >> 10) - 6000;
}

/**
* Get serial number.
* Valid only in SHT4X_STATE_READY communication state
* and for SHT4X_CMD_SERIAL command.
* @param psState
* @return Serial number.
*/
uint32_t sht4x_get_serial(const SSht40StateDesc *psState) {
return (_pu8_to_u16(psState->au8RxBuffer) << 16) | _pu8_to_u16(psState->au8RxBuffer + 3);
}

/**
* SHT4x devices send data with CRC checksum.
* The 6 byte long responses are in format:
* (1st data byte, 2nd data byte, checksum of the first data byte pair,
* 3rd data byte, 4th data byte, checksum of the second data byte pair).
* This function checks whether the a checksum is OK.
* Valid only in SHT4X_STATE_READY communication state
* and for any command except for SHT4X_CMD_RESET (reset does not produces any data response).
* @param psState Current state.
* @param bFirst True check the CRC of the first data byte pair, false: check the CRC of the second data pair.
* @return CRC byte is OK.
*/
bool sht4x_check_crc(const SSht40StateDesc *psState, bool bFirst) {
const uint8_t *pu8Data = psState->au8RxBuffer + (bFirst ? 0 : 3);
uint8_t u8CrcRecv = psState->au8RxBuffer[bFirst ? 2 : 5];

uint8_t u8CrcCalc = crc8(SHT40_CRC_POLY, SHT40_CRC_INIT, pu8Data, 2);
return (u8CrcRecv == u8CrcCalc);
}

/**
* This function is responsible for I2C communicating to SHT4x device.
* It must be called multiple times once the a command is issued, until
* SHT4X_STATE_READY or SHT4X_STATE_ERROR is reached.
* It returns a hint saying when the next calling should be done (in ms).
* @param psState Current state.
* @return After how many ms the function should be called again.
*/
uint32_t sht4x_rxtx_cycle(SSht40StateDesc *psState) {
uint32_t u32msWait = 0U;

// recv side
if (psState->eState == SHT4X_STATE_RD_RECV || psState->eState == SHT4X_STATE_WR_RECV) {
AsyncResultEntry *psEntry = lockmgr_get_entry(psState->u32LockLabel);
if (psEntry->bReady) {
bool bErr = (0 < (psEntry->u32IntSt & I2C_INT_MASK_ERR));
if (!bErr) {
if (_command_has_data_response(psState->eCommand)) {
++psState->eState;
} else {
psState->eState = SHT4X_STATE_READY;
}
} else {
// maybe repeat corresponding SEND
psState->eState = SHT4X_STATE_ERROR;
}
lockmgr_release_entry(psState->u32LockLabel);
} else {
// I2C is still not ready.. maybe wait more time
u32msWait = 1;
}
}

// send side
if (psState->eState == SHT4X_STATE_RD_SEND || psState->eState == SHT4X_STATE_WR_SEND) {
if (lockmgr_acquire_lock(psState->sIface.eLck, &psState->u32LockLabel)) {
switch (psState->eState) {
case SHT4X_STATE_WR_SEND:
i2c_write(psState->sIface.eBus, psState->sIface.u8SlaveAddr, 1, &gau8Command[psState->eCommand]);
u32msWait = gau32msWait[psState->eCommand];
break;
case SHT4X_STATE_RD_SEND: // read data
AsyncResultEntry *psEntry = lockmgr_get_entry(psState->u32LockLabel);
psEntry->pu8ReceiveBuffer = psState->au8RxBuffer;
psEntry->u8RxLen = 6;
i2c_read(psState->sIface.eBus, psState->sIface.u8SlaveAddr, 6);
break;
default:
// this branch should not be reached
}
++psState->eState;
}
}

return u32msWait;
}
Loading