diff --git a/examples/3prog1/Makefile.am b/examples/3prog1/Makefile.am
index 6cdbe82..528888d 100644
--- a/examples/3prog1/Makefile.am
+++ b/examples/3prog1/Makefile.am
@@ -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
diff --git a/examples/3prog1/README.md b/examples/3prog1/README.md
index 511c036..b397699 100644
--- a/examples/3prog1/README.md
+++ b/examples/3prog1/README.md
@@ -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
@@ -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
```
diff --git a/examples/3prog1/prog.c b/examples/3prog1/prog.c
index f4279c1..9788eda 100644
--- a/examples/3prog1/prog.c
+++ b/examples/3prog1/prog.c
@@ -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"
@@ -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
@@ -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
@@ -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);
@@ -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
@@ -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) {
@@ -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);
}
@@ -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) {
@@ -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");
@@ -624,7 +691,7 @@ static void _uartctrl_cycle(uint64_t u64tckNow) {
lockmgr_free_lock(eRes);
}
}
- break;
+ break;
case 'w':
cCommand = 'w';
u8WaitForArgs = 2;
@@ -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);
}
diff --git a/modules/Makefile.am b/modules/Makefile.am
index 99e8ffc..b0b6e1b 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -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 =
diff --git a/modules/sht4x.c b/modules/sht4x.c
new file mode 100644
index 0000000..ce97b52
--- /dev/null
+++ b/modules/sht4x.c
@@ -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 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;
+}
diff --git a/modules/sht4x.h b/modules/sht4x.h
new file mode 100644
index 0000000..32cdcdb
--- /dev/null
+++ b/modules/sht4x.h
@@ -0,0 +1,110 @@
+/*
+ * 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 for full license details.
+ */
+#ifndef SHT4X_H
+#define SHT4X_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#include
+#include "lockmgr.h"
+#include "utils/i2ciface.h"
+
+ // ============== Defines ==============
+#define SHT40_CRC_POLY 0x31
+#define SHT40_CRC_INIT 0xFF
+
+ // ============== Types ==============
+
+ /**
+ * List of accepted SHT4X commands (documented in manual).
+ */
+ typedef enum {
+ SHT4X_CMD_MEAS_H = 0,
+ SHT4X_CMD_MEAS_M = 1,
+ SHT4X_CMD_MEAS_L,
+ SHT4X_CMD_SERIAL,
+ SHT4X_CMD_RESET,
+ SHT4X_CMD_HEAT200MW_1S,
+ SHT4X_CMD_HEAT200MW_01S,
+ SHT4X_CMD_HEAT110MW_1S,
+ SHT4X_CMD_HEAT110MW_01S,
+ SHT4X_CMD_HEAT20MW_1S,
+ SHT4X_CMD_HEAT20MW_01S
+ } ESht4xCommand;
+
+ /**
+ * Set of host -- device (ESP32 -- SHT40) communication states.
+ */
+ typedef enum {
+ SHT4X_STATE_IDLE = 0, ///< Idle state, nothing to be done.
+ SHT4X_STATE_WR_SEND = 1, ///< WRITE command to be sent (host to device).
+ SHT4X_STATE_WR_RECV = 2, ///< Waiting for write command to be finished (receive signal (not busy) from peripheral controller)
+ SHT4X_STATE_RD_SEND = 3, ///< READ command to be sent (host to device).
+ SHT4X_STATE_RD_RECV = 4, ///< Waiting for read command to be finished (receive signal (not busy) from peripheral controller)
+ SHT4X_STATE_READY = 5, ///< Communication done, result data arrived.
+ SHT4X_STATE_ERROR = 6 ///< Something went wrong (probably I2C interrupt register shows error).
+ } ESht4xCommState;
+
+ /**
+ * State descriptor of SHT4x connection.
+ */
+ typedef struct {
+ ESht4xCommState eState; ///< Current communication state
+ ESht4xCommand eCommand; ///< Current SHT4x command.
+ uint32_t u32LockLabel; ///< Label used in LockManager.
+ uint8_t au8RxBuffer[6]; ///< Receive buffer.
+ SI2cIfaceCfg sIface; /// I2C communication interface.
+ } SSht40StateDesc;
+
+ // ============== Global values / References ==============
+
+ // ============== Inline interface functions ==============
+
+ static inline SSht40StateDesc sht40_init_descriptor(SI2cIfaceCfg sIface) {
+ return (SSht40StateDesc){.eState = SHT4X_STATE_IDLE, .sIface = sIface};
+ }
+
+ /**
+ * In idle state sets the SHT4X command that will be sent to the device (by calling sht4x_comm_cycle_ms()).
+ * @param psState
+ * @param eCommand
+ * @return
+ */
+ static inline bool sht40_set_command(SSht40StateDesc *psState, ESht4xCommand eCommand) {
+ if (psState->eState != SHT4X_STATE_IDLE) return false;
+ psState->eCommand = eCommand;
+ psState->eState = SHT4X_STATE_WR_SEND;
+ return true;
+ }
+
+ /**
+ * Tells whether in current state I2C interaction (i.e., calling sht4x_comm_cycle_ms()) is needed
+ * @param psState Current state
+ * @return true: sht4x_comm_cycle_ms() should be called.
+ */
+ static inline bool sht40_needs_rxtx(const SSht40StateDesc *psState) {
+ return SHT4X_STATE_WR_SEND <= psState->eState && psState->eState <= SHT4X_STATE_RD_RECV;
+ }
+
+ // ============== Interface functions ==============
+ uint16_t sht4x_get_temp_raw(const SSht40StateDesc *psState);
+ uint16_t sht4x_get_hum_raw(const SSht40StateDesc *psState);
+ int32_t sht4x_get_temp(const SSht40StateDesc *psState);
+ int32_t sht4x_get_hum(const SSht40StateDesc *psState);
+ uint32_t sht4x_get_serial(const SSht40StateDesc *psState);
+ bool sht4x_check_crc(const SSht40StateDesc *psState, bool bFirst);
+
+ uint32_t sht4x_rxtx_cycle(SSht40StateDesc *psState);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SHT4X_H */
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 47db425..9f8f543 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -7,10 +7,10 @@ lib_LIBRARIES = libesp32basic.a
include_HEADERS = dport.h esp32types.h esp_attr.h gpio.h i2c.h \
iomux.h lockmgr.h main.h pidctrl.h print.h rmt.h romfunctions.h rtc.h timg.h \
typeaux.h uart.h xtutils.h \
- utils/i2cutils.h utils/i2ciface.h utils/rmtutils.h utils/uartutils.h utils/generators.h
+ utils/crc.h utils/i2cutils.h utils/i2ciface.h utils/rmtutils.h utils/uartutils.h utils/generators.h
nodist_include_HEADERS =
-libesp32basic_a_SOURCES = i2c.c lockmgr.c main.c rmt.c timg.c uart.c utils/i2cutils.c utils/rmtutils.c utils/uartutils.c utils/generators.c
+libesp32basic_a_SOURCES = i2c.c lockmgr.c main.c rmt.c timg.c uart.c utils/crc.c utils/i2cutils.c utils/rmtutils.c utils/uartutils.c utils/generators.c
nodist_libesp32basic_a_SOURCES =
CLEANFILES =
diff --git a/src/utils/crc.c b/src/utils/crc.c
new file mode 100644
index 0000000..e2b2186
--- /dev/null
+++ b/src/utils/crc.c
@@ -0,0 +1,44 @@
+/*
+ * 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 for full license details.
+ */
+
+#include "crc.h"
+
+/**
+ * Calculates CRC8 value of a single byte.
+ * @param u8Poly CRC polynomial.
+ * @param u8Input Input value.
+ * @return CRC8 of u8Input.
+ */
+uint8_t crc8byte(uint8_t u8Poly, uint8_t u8Input) {
+ static const uint32_t u32PadMask = 0xFF;
+ uint32_t u32Input = u8Input << 8;
+ uint32_t u32Divisor = u8Poly | 0x100;
+ while (u32Divisor < u32Input) u32Divisor <<= 1;
+ while (u32PadMask < u32Input) {
+ if ((u32Input ^ u32Divisor) < u32Input) {
+ u32Input ^= u32Divisor;
+ }
+ u32Divisor >>= 1;
+ }
+ return (uint8_t) u32Input;
+}
+
+/**
+ * Calculates the CRC8 value of a bytestream.
+ * @param u8Poly CRC polynomial.
+ * @param u8Init Inital CRC value
+ * @param pu8Message Input bytestream.
+ * @param szMessageLen Length of the input.
+ * @return CRC8 of the bytestream.
+ */
+uint8_t crc8(uint8_t u8Poly, uint8_t u8Init, const uint8_t *pu8Message, size_t szMessageLen) {
+ uint8_t u8Crc = u8Init;
+ for (size_t i = 0; i < szMessageLen; ++i) {
+ u8Crc = crc8byte(u8Poly, u8Crc ^ pu8Message[i]);
+ }
+ return u8Crc;
+}
diff --git a/src/utils/crc.h b/src/utils/crc.h
new file mode 100644
index 0000000..3f13fae
--- /dev/null
+++ b/src/utils/crc.h
@@ -0,0 +1,26 @@
+/*
+ * 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 for full license details.
+ */
+
+#ifndef CRC_H
+#define CRC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#include
+
+ uint8_t crc8byte(uint8_t u8Poly, uint8_t u8Input);
+ uint8_t crc8(uint8_t u8Poly, uint8_t u8Init, const uint8_t *pu8Message, size_t szMessageLen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CRC_H */
+