Skip to content

Commit 2eb9753

Browse files
authored
Merge pull request #1057 from pimoroni/patch-scd4x-calibration
SCD4X: Add calibration commands for #1056.
2 parents 0658719 + cfa3d1d commit 2eb9753

File tree

4 files changed

+90
-3
lines changed

4 files changed

+90
-3
lines changed

micropython/modules/breakout_scd41/README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,25 @@ while True:
3535
time.sleep(1.0)
3636
```
3737

38-
The `measure()` method will return a Tuple containing the CO2 reading, temperature in degrees C and humidity.
38+
The `measure()` method will return a Tuple containing the CO₂ reading, temperature in degrees C and humidity.
3939

40+
## Changing Calibration
41+
42+
By default the SCD41 will perform automatic self calibration, which could lead to drift in readings over time.
43+
44+
You can stop this with `breakout_scd41.set_automatic_self_calibration(False)`.
45+
46+
You can then use force recalibration with a known good CO₂ PPM baseline to calibrate your sensor:
47+
48+
```python
49+
correction_amount = breakout_scd41.scd41_perform_forced_recalibration(target_co2_concentration);
50+
```
51+
52+
`correction_amount` is the resulting correction in CO₂ PPM.
53+
54+
To successfully conduct an accurate forced recalibration, the following steps must be carried out:
55+
56+
1. Operate the SCD4x in a periodic measurement mode for > 3 minutes in an environment with homogenous and constant CO₂ concentration. (read: don't breathe)
57+
2. Stop periodic measurement.
58+
3. Wait 500 ms.
59+
4. Issue the perform_forced_recalibration command.

micropython/modules/breakout_scd41/breakout_scd41.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ static MP_DEFINE_CONST_FUN_OBJ_0(scd41_get_data_ready_obj, scd41_get_data_ready)
2020
static MP_DEFINE_CONST_FUN_OBJ_1(scd41_set_temperature_offset_obj, scd41_set_temperature_offset);
2121
static MP_DEFINE_CONST_FUN_OBJ_0(scd41_get_temperature_offset_obj, scd41_get_temperature_offset);
2222

23+
static MP_DEFINE_CONST_FUN_OBJ_1(scd41_perform_forced_recalibration_obj, scd41_perform_forced_recalibration);
24+
static MP_DEFINE_CONST_FUN_OBJ_1(scd41_set_automatic_self_calibration_obj, scd41_set_automatic_self_calibration);
25+
static MP_DEFINE_CONST_FUN_OBJ_0(scd41_get_automatic_self_calibration_obj, scd41_get_automatic_self_calibration);
26+
2327
static MP_DEFINE_CONST_FUN_OBJ_1(scd41_set_sensor_altitude_obj, scd41_set_sensor_altitude);
2428
static MP_DEFINE_CONST_FUN_OBJ_1(scd41_set_ambient_pressure_obj, scd41_set_ambient_pressure);
2529

@@ -39,6 +43,10 @@ static const mp_map_elem_t scd41_globals_table[] = {
3943
{ MP_ROM_QSTR(MP_QSTR_set_temperature_offset), MP_ROM_PTR(&scd41_set_temperature_offset_obj) },
4044
{ MP_ROM_QSTR(MP_QSTR_get_temperature_offset), MP_ROM_PTR(&scd41_get_temperature_offset_obj) },
4145

46+
{ MP_ROM_QSTR(MP_QSTR_perform_forced_recalibration), MP_ROM_PTR(&scd41_perform_forced_recalibration_obj) },
47+
{ MP_ROM_QSTR(MP_QSTR_set_automatic_self_calibration), MP_ROM_PTR(&scd41_set_automatic_self_calibration_obj) },
48+
{ MP_ROM_QSTR(MP_QSTR_get_automatic_self_calibration), MP_ROM_PTR(&scd41_get_automatic_self_calibration_obj) },
49+
4250
{ MP_ROM_QSTR(MP_QSTR_set_sensor_altitude), MP_ROM_PTR(&scd41_set_sensor_altitude_obj) },
4351
{ MP_ROM_QSTR(MP_QSTR_set_ambient_pressure), MP_ROM_PTR(&scd41_set_ambient_pressure_obj) },
4452
};

micropython/modules/breakout_scd41/breakout_scd41.cpp

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,66 @@ mp_obj_t scd41_get_data_ready() {
8888
mp_raise_msg(&mp_type_RuntimeError, READ_FAIL_MSG);
8989
return mp_const_none;
9090
}
91-
// The datasheet doesn't really say *which* bit might be 1 if data is ready...
92-
// so check if the least significant eleven bits are != 0
9391
return data_ready ? mp_const_true : mp_const_false;
9492
}
9593

94+
/*
95+
To successfully conduct an accurate forced recalibration, the following
96+
steps need to be carried out:
97+
1. Operate the SCD4x in a periodic measurement mode for > 3 minutes in an
98+
environment with homogenous and constant CO₂ concentration.
99+
2. Stop periodic measurement. Wait 500 ms.
100+
3. Subsequently issue the perform_forced_recalibration command and
101+
optionally read out the baseline correction. A return value of 0xffff
102+
indicates that the forced recalibration failed.
103+
*/
104+
mp_obj_t scd41_perform_forced_recalibration(mp_obj_t target_co2_concentration_in) {
105+
if(!scd41_initialised) {
106+
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);
107+
return mp_const_none;
108+
};
109+
uint16_t frc_correction;
110+
uint16_t target_co2_concentration = mp_obj_get_int(target_co2_concentration_in);
111+
int error = scd4x_perform_forced_recalibration(target_co2_concentration, &frc_correction);
112+
if(error) {
113+
mp_raise_msg(&mp_type_RuntimeError, FAIL_MSG);
114+
}
115+
// FRC correction value in CO₂ ppm or 0xFFFF if the
116+
// command failed. Convert value to CO₂ ppm with: value - 0x8000
117+
// The mp_const_none return and guardrails here are hopefully unecessary
118+
// since it'll throw an error
119+
return frc_correction == 0xffff ? mp_const_none : mp_obj_new_int(frc_correction - 0x8000);
120+
}
121+
122+
mp_obj_t scd41_set_automatic_self_calibration(mp_obj_t asc_enabled) {
123+
if(!scd41_initialised) {
124+
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);
125+
return mp_const_none;
126+
};
127+
int error = scd4x_set_automatic_self_calibration(asc_enabled == mp_const_true ? 1u : 0u);
128+
if(error) {
129+
mp_raise_msg(&mp_type_RuntimeError, FAIL_MSG);
130+
}
131+
132+
return mp_const_none;
133+
}
134+
135+
mp_obj_t scd41_get_automatic_self_calibration() {
136+
if(!scd41_initialised) {
137+
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);
138+
return mp_const_none;
139+
}
140+
141+
uint16_t asc_enabled;
142+
int error = scd4x_get_automatic_self_calibration(&asc_enabled);
143+
if(error) {
144+
mp_raise_msg(&mp_type_RuntimeError, FAIL_MSG);
145+
return mp_const_none;
146+
}
147+
148+
return asc_enabled ? mp_const_true : mp_const_false;
149+
}
150+
96151
mp_obj_t scd41_set_temperature_offset(mp_obj_t offset) {
97152
if(!scd41_initialised) {
98153
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);

micropython/modules/breakout_scd41/breakout_scd41.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ extern mp_obj_t scd41_set_temperature_offset(mp_obj_t offset);
1414
extern mp_obj_t scd41_get_temperature_offset();
1515
extern mp_obj_t scd41_set_sensor_altitude(mp_obj_t altitude);
1616
extern mp_obj_t scd41_set_ambient_pressure(mp_obj_t pressure);
17+
18+
extern mp_obj_t scd41_perform_forced_recalibration(mp_obj_t target_co2_concentration_in);
19+
extern mp_obj_t scd41_set_automatic_self_calibration(mp_obj_t asc_enabled);
20+
extern mp_obj_t scd41_get_automatic_self_calibration();

0 commit comments

Comments
 (0)