diff --git a/README.md b/README.md
index 2735adc..792688d 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,21 @@
# SCD4x Low-Power Calibration (LPC) Algorithm [in progress]
The SCD4x sensor has a built-in Automatic Self-Calibration (ASC) that is not available for ultra-low power applications that require power-down of the sensor between measurements.
-This C++ library attempts to emulate the behaviors of the ASC in single-shot mode, based on the ASC Application Note (p. 2-5,11-13). The key assumption is that the lowest measured co2 is no less than SCD4X_LPC_BASELINE_CO2_PPM.
-It also is designed to run asynchonously and delays in between function calls are up to the user to enforce. The HAL function sensirion_i2c_hal_sleep_usec must do nothing.
+This C++ library attempts to emulate the behaviors of the ASC in single-shot mode, based on the ASC Application Note (p. 2-5,11-13). The key assumption is that the lowest measured co2 is no less than SCD4X_LPC_BASELINE_CO2_PPM.
+Do **not** use this library (or the ASC) if this assumption does not hold for the application requirements.
+
+
+
*ASC Application Note (p. 5)*
+
+
+It also is designed to run asynchonously (from the sensor) and delays in between function calls are up to the user to enforce. This algorithm provides some safety by taking in a platform defined millisecond timer in the constructor and blocking function calls for the given sensor execution durations (specified in SCD4x datasheet p.8). The HAL function sensirion_i2c_hal_sleep_usec must be implemented as such:
+```
+void sensirion_i2c_hal_sleep_usec(uint32_t useconds) {
+ if(useconds > 1000) return;
+ platform_delay_us(useconds); // platform specific delay or RTC sleep
+}
+```
+The only function calls that are not handled by this method are the FRC (400ms) and self-test(10000ms, never called by this lib). These need to be manually modified by the user if maximum power savings are needed.
In general, this library has a FSM that goes through 3 states:
- First: First reading calibration
diff --git a/graph.png b/graph.png
new file mode 100644
index 0000000..17c0eb7
Binary files /dev/null and b/graph.png differ
diff --git a/scd4x_driver/.clang-format b/scd4x_driver/.clang-format
new file mode 100644
index 0000000..047f2ad
--- /dev/null
+++ b/scd4x_driver/.clang-format
@@ -0,0 +1,14 @@
+---
+Language: Cpp
+BasedOnStyle: LLVM
+IndentWidth: 4
+AlignAfterOpenBracket: Align
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+IndentCaseLabels: true
+SpacesBeforeTrailingComments: 2
+PointerAlignment: Left
+AlignEscapedNewlines: Left
+ForEachMacros: ['TEST_GROUP', 'TEST']
+...
diff --git a/scd4x_driver/scd4x_i2c.c b/scd4x_driver/scd4x_i2c.c
index 47b54de..0703e93 100644
--- a/scd4x_driver/scd4x_i2c.c
+++ b/scd4x_driver/scd4x_i2c.c
@@ -40,8 +40,9 @@
#include "sensirion_common.h"
#include "sensirion_i2c.h"
#include "sensirion_i2c_hal.h"
+#include "sl_sleeptimer.h"
-#define SCD4X_I2C_ADDRESS 98
+#define SCD4X_I2C_ADDRESS 0x62
int16_t scd4x_start_periodic_measurement() {
int16_t error;
@@ -73,7 +74,7 @@ int16_t scd4x_read_measurement_ticks(uint16_t* co2, uint16_t* temperature,
error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 6);
if (error) {
- return error;
+ return error;
}
*co2 = sensirion_common_bytes_to_uint16_t(&buffer[0]);
*temperature = sensirion_common_bytes_to_uint16_t(&buffer[2]);
@@ -234,7 +235,7 @@ int16_t scd4x_perform_forced_recalibration(uint16_t target_co2_concentration,
return error;
}
- sensirion_i2c_hal_sleep_usec(400000);
+ sl_sleeptimer_delay_millisecond(400);
error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2);
if (error) {
@@ -362,7 +363,7 @@ int16_t scd4x_perform_self_test(uint16_t* sensor_status) {
return error;
}
- sensirion_i2c_hal_sleep_usec(10000000);
+ sl_sleeptimer_delay_millisecond(10000);
error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2);
if (error) {
diff --git a/scd4x_driver/sensirion_i2c_hal.c b/scd4x_driver/sensirion_i2c_hal.c
index d63f815..d9387a7 100644
--- a/scd4x_driver/sensirion_i2c_hal.c
+++ b/scd4x_driver/sensirion_i2c_hal.c
@@ -32,7 +32,8 @@
#include "sensirion_i2c_hal.h"
#include "sensirion_common.h"
#include "sensirion_config.h"
-
+#include "sl_i2cspm.h"
+#include "sl_sleeptimer.h"
/*
* INSTRUCTIONS
* ============
@@ -84,8 +85,19 @@ void sensirion_i2c_hal_free(void) {
* @returns 0 on success, error code otherwise
*/
int8_t sensirion_i2c_hal_read(uint8_t address, uint8_t* data, uint16_t count) {
- /* TODO:IMPLEMENT */
- return NOT_IMPLEMENTED_ERROR;
+
+ I2C_TransferSeq_TypeDef i2cTransfer;
+ I2C_TransferReturn_TypeDef result;
+
+ // Initialize I2C transfer
+ i2cTransfer.addr = address << 1;
+ i2cTransfer.flags = I2C_FLAG_READ; // must write target address before reading
+ i2cTransfer.buf[0].data = data;
+ i2cTransfer.buf[0].len = count;
+
+ result = I2CSPM_Transfer(I2C0, &i2cTransfer);
+
+ return 0;
}
/**
@@ -101,8 +113,17 @@ int8_t sensirion_i2c_hal_read(uint8_t address, uint8_t* data, uint16_t count) {
*/
int8_t sensirion_i2c_hal_write(uint8_t address, const uint8_t* data,
uint16_t count) {
- /* TODO:IMPLEMENT */
- return NOT_IMPLEMENTED_ERROR;
+ I2C_TransferSeq_TypeDef i2cTransfer;
+ I2C_TransferReturn_TypeDef result;
+
+ // Initialize I2C transfer
+ i2cTransfer.addr = address << 1 ;
+ i2cTransfer.flags = I2C_FLAG_WRITE;
+ i2cTransfer.buf[0].data = data;
+ i2cTransfer.buf[0].len = count;
+
+ result = I2CSPM_Transfer(I2C0, &i2cTransfer);
+ return 0;
}
/**
@@ -114,5 +135,6 @@ int8_t sensirion_i2c_hal_write(uint8_t address, const uint8_t* data,
* @param useconds the sleep time in microseconds
*/
void sensirion_i2c_hal_sleep_usec(uint32_t useconds) {
- /* TODO:IMPLEMENT */
+ if(useconds > 1000) return;
+ sl_sleeptimer_delay_millisecond(useconds/1000);
}
diff --git a/scd4x_lpc.cpp b/scd4x_lpc.cpp
index f94564c..c41ea27 100644
--- a/scd4x_lpc.cpp
+++ b/scd4x_lpc.cpp
@@ -7,11 +7,12 @@
#include "scd4x_lpc.h"
#include "scd4x_i2c.h"
+
namespace LPC {
SCD4X::SCD4X(int16_t (*wu)(void), int16_t (*pd)(void), int16_t (*meas)(void),
int16_t (*drdy)(bool *ready), int16_t (*read)(uint16_t* co2, int32_t* temp, int32_t* hum),
- int16_t (*frc)(uint16_t target, uint16_t* corr), int16_t (*persist)(void))
+ int16_t (*frc)(uint16_t target, uint16_t* corr), int16_t (*persist)(void), uint32_t (*getMsTick)(void))
{
scd_fp.wu = wu;
scd_fp.pd = pd;
@@ -20,6 +21,7 @@ namespace LPC {
scd_fp.read = read;
scd_fp.frc = frc;
scd_fp.persist = persist;
+ scd_fp.getMsTick = getMsTick;
fsm.state = STATE_FIRST;
fsm.expend = false;
@@ -48,74 +50,82 @@ void SCD4X::get_last_frc(int32_t *offset, uint32_t *age, uint32_t *num)
}
lpc_ret SCD4X::powerOn(void) {
+ if(!_mutex_request_unlock()) return ERR_WAIT;
if (scd_fp.wu())
return _sig_scd_fail(true);
_sig_scd_fail(false);
+ _mutex_request_lock(SCD4X_LPC_CMD_WU_LOCK_DUR_MS);
return ERR_NONE;
}
lpc_ret SCD4X::powerOff(void) {
+ if(!_mutex_request_unlock()) return ERR_WAIT;
if (scd_fp.pd())
return _sig_scd_fail(true);
_sig_scd_fail(false);
+ _mutex_request_lock(SCD4X_LPC_CMD_PD_LOCK_DUR_MS);
return ERR_NONE;
}
lpc_ret SCD4X::discardMeasurement(void) {
+ if(!_mutex_request_unlock()) return ERR_WAIT;
if (scd_fp.meas())
return _sig_scd_fail(true);
_sig_scd_fail(false);
+ _mutex_request_lock(SCD4X_LPC_CMD_MEAS_LOCK_DUR_MS);
return ERR_NONE;
}
lpc_ret SCD4X::measure()
{
-
+ if(!_mutex_request_unlock()) return ERR_WAIT;
if (scd_fp.meas())
return _sig_scd_fail(true);
if (fsm.ttl <= 1)
return WARN_PRETRIG; // warn if yield fn should be passed
_sig_scd_fail(false);
+ _mutex_request_lock(SCD4X_LPC_CMD_MEAS_LOCK_DUR_MS);
return ERR_NONE;
}
- lpc_ret SCD4X::read(uint16_t *co2, int32_t *temp, int32_t *hum, void (*yield)(void))
+ lpc_ret SCD4X::read(uint16_t *co2, int32_t *temp, int32_t *hum)
{
bool tmp = false;
scd_fp.drdy(&tmp);
if(!tmp) return ERR_WAIT;
-
+ if(!_mutex_request_unlock()) return ERR_WAIT;
if(scd_fp.read(co2, temp, hum)) return _sig_scd_fail(true);
if(*co2 == 0) return _sig_scd_fail(true);
_sig_scd_fail(false);
if(*co2 < fsm.gl_min) fsm.gl_min = *co2;
prev_frc_age++;
- return _proc_fsm(*co2, yield);
+ return _proc_fsm(*co2);
}
- lpc_ret SCD4X::_proc_fsm(uint16_t co2, void (*yield)(void))
+ lpc_ret SCD4X::_proc_fsm(uint16_t co2)
{
if(fsm._cons_fail > SCD4X_LPC_SCD_FAIL_TOL_TH) return ERR_PANIC;
if (co2 < SCD4X_LPC_BASELINE_CO2_PPM) // called if co2 < 400
{
+
fsm.expend = true;
uint32_t offset = SCD4X_LPC_BASELINE_CO2_PPM - co2;
fsm.limit = offset > _sig_get_fsm_offset();
uint16_t ret = 0xFFFF;
scd_fp.frc(fsm.limit ?co2+_sig_get_fsm_offset() : SCD4X_LPC_BASELINE_CO2_PPM, &ret);
+ _mutex_request_lock(SCD4X_LPC_CMD_FRC_LOCK_DUR_MS);
frc_ctr++;
prev_frc_offset = ret - 0x8000U;
- if (yield != NULL)
- yield(); //yield to app function ptr
if (ret == 0xFFFF)
return ERR_LPC; // return on next calls, ttl will be -ve
}
if (--fsm.ttl <= 0) {
_sig_fsm_next();
+ uint16_t _gl_tmp = fsm.gl_min;
fsm.ttl = _sig_get_fsm_cnt();
fsm.gl_min = 0xFFFF;
@@ -125,16 +135,16 @@ lpc_ret SCD4X::measure()
} // do not frc again if already done
// called iff co2 >= 400 for the entire period
- uint32_t offset = fsm.gl_min - SCD4X_LPC_BASELINE_CO2_PPM;
+ uint32_t offset = _gl_tmp - SCD4X_LPC_BASELINE_CO2_PPM;
fsm.limit = offset > _sig_get_fsm_offset();
uint16_t ret = 0xFFFF;
scd_fp.frc(co2 - (fsm.limit ? _sig_get_fsm_offset() : offset),
&ret);
+
+ _mutex_request_lock(SCD4X_LPC_CMD_FRC_LOCK_DUR_MS);
frc_ctr++;
prev_frc_offset = ret - 0x8000U;
prev_frc_age = 0;
- if (yield != NULL)
- yield(); //yield to app function ptr
if (ret == 0xFFFF)
return ERR_LPC; // return on next calls, ttl will be -ve
}
@@ -143,6 +153,7 @@ lpc_ret SCD4X::measure()
}
void SCD4X::_sig_fsm_next(void) {
+
switch (fsm.state) {
case STATE_FIRST: {
fsm.state = STATE_INIT;
@@ -156,6 +167,8 @@ void SCD4X::_sig_fsm_next(void) {
fsm.state = STATE_STD;
break;
}
+
+
}
}
@@ -186,8 +199,40 @@ uint32_t SCD4X::_sig_get_fsm_cnt(void) {
lpc_ret SCD4X::_sig_scd_fail(bool trig)
{
- if(!trig) fsm._cons_fail = 0;
+ if (!trig) {
+ fsm._cons_fail = 0;
+ return ERR_NONE;
+ }
else fsm._cons_fail++;
+
return ERR_SCD;
}
+
+
+bool SCD4X::_mutex_request_lock(uint32_t dur) {
+ //CORE_DECLARE_IRQ_STATE;
+
+ //CORE_ENTER_ATOMIC();
+
+ if (_mutex.state)
+ return false;
+ if(dur > SCD4X_LPC_CMD_MAX_LOCK_DUR_MS) return false;
+ _mutex.lastTickMs = scd_fp.getMsTick();
+ _mutex.reqLockDurMs = dur;
+ return true;
+
+ //CORE_EXIT_ATOMIC();
+}
+
+bool SCD4X::_mutex_request_unlock(void) {
+ if(!_mutex.state) return true;
+ int64_t diff = scd_fp.getMsTick() - _mutex.lastTickMs;
+ if (diff < 0 || diff > _mutex.reqLockDurMs) {
+ _mutex.state = false;
+ return true;
+ }
+ else return false;
+
+}
+
}
diff --git a/scd4x_lpc.h b/scd4x_lpc.h
index ad0ccca..f4eeb6c 100644
--- a/scd4x_lpc.h
+++ b/scd4x_lpc.h
@@ -26,6 +26,15 @@ const uint32_t SCD4X_LPC_STD_MAX_CAL_OFFSET = 100;
const uint32_t SCD4X_LPC_SCD_FAIL_TOL_TH = 50;
+const uint32_t SCD4X_LPC_CMD_MAX_LOCK_DUR_MS = 10000;
+const uint32_t SCD4X_LPC_CMD_WU_LOCK_DUR_MS = 20;
+const uint32_t SCD4X_LPC_CMD_PD_LOCK_DUR_MS = 1;
+const uint32_t SCD4X_LPC_CMD_MEAS_LOCK_DUR_MS = 5000;
+const uint32_t SCD4X_LPC_CMD_DRDY_LOCK_DUR_MS = 0;
+const uint32_t SCD4X_LPC_CMD_READ_LOCK_DUR_MS = 0;
+const uint32_t SCD4X_LPC_CMD_FRC_LOCK_DUR_MS = 0;
+const uint32_t SCD4X_LPC_CMD_PERSIST_LOCK_DUR_MS = 800;
+
namespace LPC {
typedef enum {
@@ -37,7 +46,8 @@ namespace LPC {
public:
SCD4X(int16_t (*wu)(void), int16_t (*pd)(void), int16_t (*meas)(void),
int16_t (*drdy)(bool *ready), int16_t (*read)(uint16_t* co2, int32_t* temp, int32_t* hum),
- int16_t (*frc)(uint16_t target, uint16_t* corr), int16_t (*persist)(void));
+ int16_t (*frc)(uint16_t target, uint16_t* corr), int16_t (*persist)(void),
+ uint32_t (*getMsTick)(void));
lpc_ret powerOn(void);
lpc_ret powerOff(void);
@@ -59,19 +69,12 @@ namespace LPC {
* @brief This function is called at least 5000ms after measure() and returns
* sensor measurement data and performs FRC if applicable.
*
- * The yield() passed to this function should run for at
- * least 400ms and is recommended to be a low-power entry code that
- * preserves NVM.
- *
- * measure() will provide forewarning of FRC, iff co2 is above baseline
- * else the app must handle sporadic calls to yield()
- *
*
* @param co2,temp,hum pointers to store sensor data
* @param yield pointer to app function
* @retval lpc_ret error code
*/
- lpc_ret read(uint16_t *co2, int32_t *temp, int32_t *hum, void (*yield)(void) = NULL);
+ lpc_ret read(uint16_t *co2, int32_t *temp, int32_t *hum);
void get_last_frc(int32_t *offset, uint32_t *age, uint32_t *num);
@@ -88,6 +91,7 @@ namespace LPC {
int16_t (*read)(uint16_t* co2, int32_t* temp, int32_t* hum);
int16_t (*frc)(uint16_t target, uint16_t* corr);
int16_t (*persist)( void );
+ uint32_t (*getMsTick)(void);
} scd_fp;
typedef enum
@@ -107,7 +111,15 @@ namespace LPC {
uint32_t _cons_fail;
} fsm;
- lpc_ret _proc_fsm(uint16_t co2, void (*yield)(void));
+ struct {
+ bool state = false;
+ uint32_t lastTickMs = 0;
+ uint32_t reqLockDurMs = 0;
+ } _mutex;
+ bool _mutex_request_lock(uint32_t dur);
+ bool _mutex_request_unlock(void);
+
+ lpc_ret _proc_fsm(uint16_t co2);
void _sig_fsm_next(void);
uint32_t _sig_get_fsm_offset(void);
uint32_t _sig_get_fsm_cnt(void);