kopia lustrzana https://github.com/pimoroni/pimoroni-pico
Add SCD41 MicroPython bindings
rodzic
0eeada72d7
commit
6d6803612a
|
@ -0,0 +1,15 @@
|
|||
import time
|
||||
|
||||
import pimoroni_i2c
|
||||
import breakout_scd41
|
||||
|
||||
i2c = pimoroni_i2c.PimoroniI2C(4, 5)
|
||||
|
||||
breakout_scd41.init(i2c)
|
||||
breakout_scd41.start()
|
||||
|
||||
while True:
|
||||
if breakout_scd41.ready():
|
||||
co2, temperature, humidity = breakout_scd41.measure()
|
||||
print(co2, temperature, humidity)
|
||||
time.sleep(1.0)
|
|
@ -0,0 +1,39 @@
|
|||
# SCD41 CO2 Sensor Driver <!-- omit in toc -->
|
||||
|
||||
## Getting Started
|
||||
|
||||
Construct a new PimoroniI2C instance for your specific board. Breakout Garden uses pins 4 & 5 and Pico Explorer uses pins 20 & 21.
|
||||
|
||||
Since SCD41 has a fixed I2C address and the Sensirion SCD4x library is used under the hood, it's wrapped up as a module for Python.
|
||||
|
||||
Import the `breakout_scd41` and call `init` to set up I2C:
|
||||
|
||||
```python
|
||||
import time
|
||||
|
||||
import pimoroni_i2c
|
||||
import breakout_scd41
|
||||
|
||||
i2c = pimoroni_i2c.PimoroniI2C(4, 5)
|
||||
|
||||
breakout_scd41.init(i2c)
|
||||
```
|
||||
|
||||
## Taking Measurements
|
||||
|
||||
Before taking a measurement you must start periodic measurement by calling `start()`.
|
||||
|
||||
Poll on `ready()` and use `measure()` to read the result when it's `True`:
|
||||
|
||||
```python
|
||||
breakout_scd41.start()
|
||||
|
||||
while True:
|
||||
if breakout_scd41.ready():
|
||||
co2, temperature, humidity = breakout_scd41.measure()
|
||||
print(co2, temperature, humidity)
|
||||
time.sleep(1.0)
|
||||
```
|
||||
|
||||
The `measure()` method will return a Tuple containing the CO2 reading, temperature in degrees C and humidity.
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#include "breakout_scd41.h"
|
||||
|
||||
/***** Constants *****/
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SCD41 Module
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/***** Module Functions *****/
|
||||
// Init, optionally (though you really should supply it) accepts a PimoroniI2C instance
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(scd41_init_obj, 0, scd41_init);
|
||||
|
||||
// Start/Stop measurement, no args (module-level, so no "self")
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_0(scd41_start_periodic_measurement_obj, scd41_start_periodic_measurement);
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_0(scd41_stop_periodic_measurement_obj, scd41_stop_periodic_measurement);
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_0(scd41_get_data_ready_obj, scd41_get_data_ready);
|
||||
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(scd41_set_temperature_offset_obj, scd41_set_temperature_offset);
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_0(scd41_get_temperature_offset_obj, scd41_get_temperature_offset);
|
||||
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(scd41_set_sensor_altitude_obj, scd41_set_sensor_altitude);
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(scd41_set_ambient_pressure_obj, scd41_set_ambient_pressure);
|
||||
|
||||
// No args here, either, we're home free!
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_0(scd41_read_measurement_obj, scd41_read_measurement);
|
||||
|
||||
/***** Globals Table *****/
|
||||
STATIC const mp_map_elem_t scd41_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_breakout_scd41) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&scd41_init_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&scd41_start_periodic_measurement_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&scd41_stop_periodic_measurement_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_measure), MP_ROM_PTR(&scd41_read_measurement_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_ready), MP_ROM_PTR(&scd41_get_data_ready_obj) },
|
||||
|
||||
{ MP_ROM_QSTR(MP_QSTR_set_temperature_offset), MP_ROM_PTR(&scd41_set_temperature_offset_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_get_temperature_offset), MP_ROM_PTR(&scd41_get_temperature_offset_obj) },
|
||||
|
||||
{ MP_ROM_QSTR(MP_QSTR_set_sensor_altitude), MP_ROM_PTR(&scd41_set_sensor_altitude_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_set_ambient_pressure), MP_ROM_PTR(&scd41_set_ambient_pressure_obj) },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(mp_module_scd41_globals, scd41_globals_table);
|
||||
|
||||
/***** Module Definition *****/
|
||||
const mp_obj_module_t scd41_user_cmodule = {
|
||||
.base = { &mp_type_module },
|
||||
.globals = (mp_obj_dict_t*)&mp_module_scd41_globals,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
MP_REGISTER_MODULE(MP_QSTR_breakout_scd41, scd41_user_cmodule, MODULE_BREAKOUT_SCD41_ENABLED);
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
@ -0,0 +1,177 @@
|
|||
#include "hardware/spi.h"
|
||||
#include "hardware/sync.h"
|
||||
#include "pico/binary_info.h"
|
||||
|
||||
#include "scd4x_i2c.h"
|
||||
#include "sensirion_common.h"
|
||||
#include "sensirion_i2c_hal.h"
|
||||
#include "common/pimoroni_i2c.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
bool scd41_initialised = false;
|
||||
|
||||
|
||||
extern "C" {
|
||||
#include "breakout_scd41.h"
|
||||
#include "pimoroni_i2c.h"
|
||||
|
||||
/***** I2C Struct *****/
|
||||
typedef struct _PimoroniI2C_obj_t {
|
||||
mp_obj_base_t base;
|
||||
I2C *i2c;
|
||||
} _PimoroniI2C_obj_t;
|
||||
|
||||
#define NOT_INITIALISED_MSG "SCD41: Not initialised. Call scd41.init(<i2c instance>) first."
|
||||
#define READ_FAIL_MSG "SCD41: Reading failed."
|
||||
#define FAIL_MSG "SCD41: Error."
|
||||
#define SAMPLE_FAIL_MSG "SCD41: Read invalid sample."
|
||||
|
||||
mp_obj_t scd41_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
|
||||
enum { ARG_i2c };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_i2c, MP_ARG_OBJ, {.u_obj = nullptr} }
|
||||
};
|
||||
|
||||
// Parse args.
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
// Perform the I2C type checking incantations
|
||||
if(!MP_OBJ_IS_TYPE(args[ARG_i2c].u_obj, &PimoroniI2C_type)) {
|
||||
mp_raise_ValueError(MP_ERROR_TEXT("SCD41: Bad i2C object"));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
_PimoroniI2C_obj_t *i2c = (_PimoroniI2C_obj_t *)MP_OBJ_TO_PTR(args[ARG_i2c].u_obj);
|
||||
|
||||
sensirion_i2c_hal_init(i2c->i2c);
|
||||
scd41_initialised = true;
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t scd41_stop_periodic_measurement() {
|
||||
if(!scd41_initialised) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
scd4x_stop_periodic_measurement();
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t scd41_start_periodic_measurement() {
|
||||
if(!scd41_initialised) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
int error = scd4x_start_periodic_measurement();
|
||||
if(error) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, FAIL_MSG);
|
||||
}
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t scd41_get_data_ready() {
|
||||
if(!scd41_initialised) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
uint16_t data_ready = 0;
|
||||
int error = scd4x_get_data_ready_status(&data_ready);
|
||||
if(error) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, READ_FAIL_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
// The datasheet doesn't really say *which* bit might be 1 if data is ready...
|
||||
// so check if the least significant eleven bits are != 0
|
||||
return (data_ready & 0x7ff) ? mp_const_true : mp_const_false;
|
||||
}
|
||||
|
||||
mp_obj_t scd41_set_temperature_offset(mp_obj_t offset) {
|
||||
if(!scd41_initialised) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
float o = mp_obj_get_float(offset);
|
||||
int error = scd4x_set_temperature_offset(o);
|
||||
if(error) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, FAIL_MSG);
|
||||
}
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t scd41_get_temperature_offset() {
|
||||
if(!scd41_initialised) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
int32_t t_offset;
|
||||
int error = scd4x_get_temperature_offset(&t_offset);
|
||||
if(error) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, FAIL_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
return mp_obj_new_int(t_offset);
|
||||
}
|
||||
|
||||
mp_obj_t scd41_set_sensor_altitude(mp_obj_t altitude) {
|
||||
if(!scd41_initialised) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
int a = mp_obj_get_int(altitude);
|
||||
int error = scd4x_set_sensor_altitude(a);
|
||||
if(error) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, FAIL_MSG);
|
||||
}
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t scd41_set_ambient_pressure(mp_obj_t pressure) {
|
||||
if(!scd41_initialised) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
int p = mp_obj_get_int(pressure);
|
||||
int error = scd4x_set_ambient_pressure(p);
|
||||
if(error) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, FAIL_MSG);
|
||||
}
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
||||
mp_obj_t scd41_read_measurement() {
|
||||
uint16_t co2;
|
||||
int32_t temperature;
|
||||
int32_t humidity;
|
||||
if(!scd41_initialised) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
int error = scd4x_read_measurement(&co2, &temperature, &humidity);
|
||||
if(error) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, READ_FAIL_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
if(co2 == 0) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, SAMPLE_FAIL_MSG);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t tuple[3];
|
||||
tuple[0] = mp_obj_new_float(co2);
|
||||
tuple[1] = mp_obj_new_float(temperature / 1000.0f);
|
||||
tuple[2] = mp_obj_new_float(humidity / 1000.0f);
|
||||
return mp_obj_new_tuple(3, tuple);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// Include MicroPython API.
|
||||
#include "py/runtime.h"
|
||||
#include "py/objstr.h"
|
||||
|
||||
// Declare the functions we'll make available in Python
|
||||
extern mp_obj_t scd41_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
|
||||
extern mp_obj_t scd41_start_periodic_measurement();
|
||||
extern mp_obj_t scd41_stop_periodic_measurement();
|
||||
extern mp_obj_t scd41_read_measurement();
|
||||
extern mp_obj_t scd41_get_data_ready();
|
||||
|
||||
extern mp_obj_t scd41_set_temperature_offset(mp_obj_t offset);
|
||||
extern mp_obj_t scd41_get_temperature_offset();
|
||||
extern mp_obj_t scd41_set_sensor_altitude(mp_obj_t altitude);
|
||||
extern mp_obj_t scd41_set_ambient_pressure(mp_obj_t pressure);
|
|
@ -0,0 +1,32 @@
|
|||
set(MOD_NAME breakout_scd41)
|
||||
set(DRIVER ${CMAKE_CURRENT_LIST_DIR}/../../../drivers/scd4x)
|
||||
string(TOUPPER ${MOD_NAME} MOD_NAME_UPPER)
|
||||
add_library(usermod_${MOD_NAME} INTERFACE)
|
||||
|
||||
target_sources(usermod_${MOD_NAME} INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.cpp
|
||||
${DRIVER}/src/scd4x_i2c.c
|
||||
${DRIVER}/src/sensirion_common.c
|
||||
${DRIVER}/src/sensirion_i2c.c
|
||||
${DRIVER}/i2c_hal.cpp
|
||||
${DRIVER}/scd4x.cpp
|
||||
)
|
||||
|
||||
target_include_directories(usermod_${MOD_NAME} INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
${DRIVER}
|
||||
${DRIVER}/src
|
||||
)
|
||||
|
||||
target_compile_definitions(usermod_${MOD_NAME} INTERFACE
|
||||
-DMODULE_${MOD_NAME_UPPER}_ENABLED=1
|
||||
)
|
||||
|
||||
target_link_libraries(usermod INTERFACE usermod_${MOD_NAME})
|
||||
|
||||
set_source_files_properties(
|
||||
${CMAKE_CURRENT_LIST_DIR}/breakout_scd41.c
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
"-Wno-discarded-qualifiers -Wno-implicit-int"
|
||||
)
|
|
@ -28,6 +28,7 @@ include(breakout_bme68x/micropython)
|
|||
include(breakout_bme280/micropython)
|
||||
include(breakout_bmp280/micropython)
|
||||
include(breakout_icp10125/micropython)
|
||||
include(breakout_scd41/micropython)
|
||||
|
||||
include(pico_scroll/micropython)
|
||||
include(pico_rgb_keypad/micropython)
|
||||
|
|
Ładowanie…
Reference in New Issue