mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
239 lines
6.1 KiB
C
239 lines
6.1 KiB
C
/*
|
|
* Copyright (C) 2018 Koen Zandberg <koen@bergzand.net>
|
|
* Copyright (C) 2018 Beduino Master Projekt - University of Bremen
|
|
* Copyright (C) 2020 Bas Stottelaar <basstottelaar@gmail.com>
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup drivers_mhz19
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Device driver implementation for MH-Z19 CO2 sensor.
|
|
*
|
|
* @author Koen Zandberg <koen@bergzand.net>
|
|
* @author Christian Manal <manal@uni-bremen.de>
|
|
* @author Bas Stottelaar <basstottelaar@gmail.com>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include "mhz19.h"
|
|
#include "mhz19_params.h"
|
|
#include "ztimer.h"
|
|
#include "mutex.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
#ifdef MODULE_MHZ19_UART
|
|
#include "mhz19_internals.h"
|
|
|
|
/* Precalculated command sequences */
|
|
static const uint8_t value_read[] = {
|
|
MHZ19_READ_START,
|
|
MHZ19_READ_SENSOR_NUM,
|
|
MHZ19_CMD_GAS_CONCENTRATION,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x79 /* checksum */
|
|
};
|
|
|
|
static const uint8_t auto_calibration_on[] = {
|
|
MHZ19_READ_START,
|
|
MHZ19_READ_SENSOR_NUM,
|
|
MHZ19_CMD_AUTO_CALIBRATION,
|
|
0xA0, /* on */
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0xE6 /* checksum */
|
|
};
|
|
|
|
static const uint8_t auto_calibration_off[] = {
|
|
MHZ19_READ_START,
|
|
MHZ19_READ_SENSOR_NUM,
|
|
MHZ19_CMD_AUTO_CALIBRATION,
|
|
0x00, /* off */
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x86 /* checksum */
|
|
};
|
|
|
|
static const uint8_t calibrate_zero_point[] = {
|
|
MHZ19_READ_START,
|
|
MHZ19_READ_SENSOR_NUM,
|
|
MHZ19_CMD_CALIBRATE_ZERO,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x78 /* checksum */
|
|
};
|
|
|
|
static void _mhz19_rx_cb(void *arg, uint8_t byte)
|
|
{
|
|
mhz19_t *dev = arg;
|
|
|
|
/* Skip start byte and skip out of array bounds writes */
|
|
if ((dev->idx == 0 && byte == 0xff) || dev->idx >= MHZ19_BUF_SIZE) {
|
|
return;
|
|
}
|
|
/* Store byte and increment idx */
|
|
dev->rxmem[dev->idx++] = byte;
|
|
if (dev->idx == MHZ19_BUF_SIZE) {
|
|
mutex_unlock(&dev->sync);
|
|
}
|
|
}
|
|
|
|
int mhz19_init(mhz19_t *dev, const mhz19_params_t *params)
|
|
{
|
|
dev->params = params;
|
|
|
|
DEBUG("mhz19: initializing device %p on UART %i\n",
|
|
(void *)dev, dev->params->uart);
|
|
|
|
mutex_init(&dev->mutex);
|
|
mutex_init(&dev->sync);
|
|
|
|
dev->idx = 0;
|
|
|
|
/* Initialize UART interface */
|
|
if (uart_init(params->uart, MHZ19_UART_BAUDRATE, _mhz19_rx_cb, dev)) {
|
|
DEBUG("mhz19: UART initialization failed\n");
|
|
return MHZ19_ERR_INIT;
|
|
}
|
|
|
|
/* set auto calibration to a know value */
|
|
mhz19_set_auto_calibration(dev, params->auto_calibration);
|
|
|
|
DEBUG("mhz19: initialization complete\n");
|
|
return MHZ19_OK;
|
|
}
|
|
|
|
/*
|
|
* Do a raw send command to the sensor, without waiting for data.
|
|
*
|
|
* @param[in] dev The mhz19 device context
|
|
* @param[in] out the 9 bytes to transmit to the device
|
|
*/
|
|
static void mhz19_cmd(mhz19_t *dev, const uint8_t *in)
|
|
{
|
|
/* First lock, guarantees no concurrent access to the UART device */
|
|
mutex_lock(&dev->mutex);
|
|
|
|
/* send the command */
|
|
uart_write(dev->params->uart, in, MHZ19_BUF_SIZE + 1);
|
|
|
|
/* Add some delay after executing command */
|
|
ztimer_sleep(ZTIMER_MSEC, MHZ19_TIMEOUT_CMD);
|
|
|
|
/* Unlock concurrency guard mutex */
|
|
mutex_unlock(&dev->mutex);
|
|
}
|
|
|
|
/*
|
|
* Do a raw send/receive exchange to the sensor. As exchanges between the
|
|
* MH-Z19 and the host always consists of 9 bytes in each direction, the size
|
|
* of the input and output arrays is fixed at 9 bytes here. The returned bytes
|
|
* from the MH-Z19 appear in mhz19_t::rxmem
|
|
*
|
|
* @param[in] dev The mhz19 device context
|
|
* @param[in] out the 9 bytes to transmit to the device
|
|
*/
|
|
static void mhz19_xmit(mhz19_t *dev, const uint8_t *in)
|
|
{
|
|
/* Reset the buffer index to zero */
|
|
dev->idx = 0;
|
|
|
|
/* Lock the synchronization mutex */
|
|
mutex_lock(&dev->sync);
|
|
|
|
/* Send read command to the sensor */
|
|
uart_write(dev->params->uart, in, MHZ19_BUF_SIZE + 1);
|
|
|
|
/* By locking the same mutex another time, this thread blocks until
|
|
* the UART ISR received all bytes and unlocks the mutex. If that does not
|
|
* happen, then ztimer_mutex_lock_timeout unlocks the mutex after as well
|
|
* after the timeout expired.
|
|
*/
|
|
ztimer_mutex_lock_timeout(ZTIMER_MSEC, &dev->sync, MHZ19_TIMEOUT_CMD);
|
|
|
|
/* Unlock synchronization for next transmission */
|
|
mutex_unlock(&dev->sync);
|
|
}
|
|
|
|
int mhz19_get_ppm(mhz19_t *dev, int16_t *ppm)
|
|
{
|
|
int res = MHZ19_OK;
|
|
|
|
/* First lock, guarantees no concurrent access to the UART device */
|
|
mutex_lock(&dev->mutex);
|
|
|
|
DEBUG("mhz19: Starting measurement\n");
|
|
mhz19_xmit(dev, value_read);
|
|
|
|
DEBUG("mhz19: Checking buffer: %d\n", dev->idx);
|
|
/* MHZ19_BUF_SIZE indicates completely filled buffer */
|
|
if (dev->idx == MHZ19_BUF_SIZE) {
|
|
uint8_t checksum = 0;
|
|
/* MHZ19_BUF_SIZE - 1 to exclude the received checksum */
|
|
for (unsigned i = 0; i < MHZ19_BUF_SIZE - 1; i++) {
|
|
checksum -= dev->rxmem[i];
|
|
}
|
|
if (checksum == dev->rxmem[MHZ19_RX_POS_CHECKSUM]) {
|
|
*ppm = dev->rxmem[MHZ19_RX_POS_PPM_HIGH] << 8;
|
|
*ppm += dev->rxmem[MHZ19_RX_POS_PPM_LOW];
|
|
res = MHZ19_OK;
|
|
}
|
|
else {
|
|
/* Checksum mismatch */
|
|
DEBUG("mhz19: Checksum failed, calculated 0x%x != 0x%x\n", checksum,
|
|
dev->rxmem[MHZ19_RX_POS_CHECKSUM]);
|
|
res = MHZ19_ERR_CHECKSUM;
|
|
}
|
|
}
|
|
else {
|
|
DEBUG("mhz19: Timeout trying to retrieve measurement\n");
|
|
res = MHZ19_ERR_TIMEOUT;
|
|
}
|
|
|
|
/* Unlock concurrency guard mutex */
|
|
mutex_unlock(&dev->mutex);
|
|
|
|
return res;
|
|
}
|
|
|
|
void mhz19_set_auto_calibration(mhz19_t *dev, bool enable)
|
|
{
|
|
DEBUG("mhz19: setting autocalibration to %d\n", (int)enable);
|
|
|
|
if (enable) {
|
|
mhz19_cmd(dev, auto_calibration_on);
|
|
}
|
|
else {
|
|
mhz19_cmd(dev, auto_calibration_off);
|
|
}
|
|
}
|
|
|
|
void mhz19_calibrate_zero_point(mhz19_t *dev)
|
|
{
|
|
DEBUG("mhz19: calibrating zero point\n");
|
|
|
|
mhz19_cmd(dev, calibrate_zero_point);
|
|
}
|
|
#else
|
|
typedef int dont_be_pedantic;
|
|
#endif /* MODULE_MHZ19_UART */
|