2018-11-22 18:50:29 +01:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2018 HAW-Hamburg
|
|
|
|
*
|
|
|
|
* 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_sds011
|
|
|
|
* @{
|
|
|
|
*
|
|
|
|
* @file
|
|
|
|
* @brief SDS011 Laser Dust Sensor driver implementation
|
|
|
|
*
|
|
|
|
* @author Michel Rottleuthner <michel.rottleuthner@haw-hamburg.de>
|
|
|
|
*
|
|
|
|
* @}
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "assert.h"
|
|
|
|
#include "sds011.h"
|
|
|
|
#include "periph/uart.h"
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief UART receive callback
|
|
|
|
*
|
|
|
|
* @param[in] arg Context value previously handed to the uart_init call
|
|
|
|
* @param[in] data single byte received over UART
|
|
|
|
*/
|
|
|
|
static void _rx_cb(void *arg, uint8_t data)
|
|
|
|
{
|
|
|
|
sds011_t *dev = (sds011_t*)arg;
|
|
|
|
|
|
|
|
/* frame MUST start with HEAD byte and the buffer must be cleared
|
|
|
|
before writing to it again */
|
|
|
|
if (((dev->pos == 0) && (data != SDS011_FRAME_HEAD)) ||
|
|
|
|
(dev->pos == SDS011_FRAME_RECV_LEN)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev->rx_mem[dev->pos] = data;
|
|
|
|
|
|
|
|
if ((dev->pos >= SDS011_DB1_IDX) &&
|
|
|
|
(dev->pos < SDS011_FRAME_RECV_CSUM_IDX)) {
|
|
|
|
dev->checksum += data;
|
|
|
|
}
|
|
|
|
else if ((dev->pos == SDS011_FRAME_RECV_LEN - 1) &&
|
|
|
|
(dev->rx_mem[SDS011_FRAME_HEAD_IDX] == SDS011_FRAME_HEAD) &&
|
|
|
|
(dev->rx_mem[SDS011_FRAME_RECV_TAIL_IDX] == SDS011_FRAME_TAIL)) {
|
|
|
|
|
|
|
|
dev->checksum &= SDS011_FRAME_CSUM_MSK;
|
|
|
|
if (dev->rx_mem[SDS011_FRAME_RECV_CSUM_IDX] == dev->checksum) {
|
|
|
|
|
|
|
|
if ((dev->cb != NULL) &&
|
|
|
|
(dev->rx_mem[SDS011_CMDID_IDX] == SDS011_RCMDID_DATA)) {
|
|
|
|
sds011_data_t measure;
|
|
|
|
measure.pm_2_5 = dev->rx_mem[SDS011_DB1_IDX] |
|
|
|
|
(dev->rx_mem[SDS011_DB2_IDX] << 8);
|
|
|
|
measure.pm_10 = dev->rx_mem[SDS011_DB3_IDX] |
|
|
|
|
(dev->rx_mem[SDS011_DB4_IDX] << 8);
|
|
|
|
dev->cb(&measure, dev->cbctx);
|
|
|
|
dev->pos = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
dev->pos = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev->checksum = 0;
|
|
|
|
|
|
|
|
/* unlock the mutex for the calling function */
|
|
|
|
mutex_unlock(&dev->cb_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
dev->pos++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief send command and wait for first replied message
|
|
|
|
*
|
|
|
|
* @param[in] dev SDS011 device the command is sent to
|
|
|
|
* @param[in] data_bytes data bytes to send within the command
|
|
|
|
* @param[in] len number of data bytes
|
|
|
|
* @param[out] recv_frm pointer where the received frame will be stored
|
|
|
|
* must at least provide SDS011_FRAME_RECV_LEN bytes
|
|
|
|
*/
|
|
|
|
int _send_recv_cmd(sds011_t *dev, uint8_t *data_bytes, size_t len, uint8_t *recv_frm)
|
|
|
|
{
|
|
|
|
uint8_t cmd[SDS011_FRAME_SEND_LEN] = {0};
|
|
|
|
int checksum = 0;
|
|
|
|
int res = SDS011_ERROR;
|
|
|
|
|
|
|
|
cmd[SDS011_FRAME_HEAD_IDX] = SDS011_FRAME_HEAD;
|
|
|
|
cmd[SDS011_CMDID_IDX] = SDS011_CMDID_QUERY;
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < len; i++) {
|
|
|
|
cmd[SDS011_DB1_IDX + i] = data_bytes[i];
|
|
|
|
checksum += data_bytes[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd[SDS011_DEVID1_IDX] = (dev->params.dev_id >> 8) & 0xFF;
|
|
|
|
checksum += cmd[SDS011_DEVID1_IDX];
|
|
|
|
cmd[SDS011_DEVID2_IDX] = dev->params.dev_id & 0xFF;
|
|
|
|
checksum += cmd[SDS011_DEVID2_IDX];
|
|
|
|
|
|
|
|
cmd[SDS011_FRAME_SEND_LEN - 2] = checksum & SDS011_FRAME_CSUM_MSK;
|
|
|
|
cmd[SDS011_FRAME_SEND_TAIL_IDX] = SDS011_FRAME_TAIL;
|
|
|
|
|
|
|
|
mutex_lock(&dev->dev_lock);
|
|
|
|
|
|
|
|
dev->pos = 0;
|
|
|
|
dev->checksum = 0;
|
|
|
|
|
|
|
|
mutex_lock(&dev->cb_lock);
|
|
|
|
|
|
|
|
/* if no active reporting callback is registered, UART must be enabled first */
|
|
|
|
if((dev->cb == NULL) &&
|
|
|
|
(uart_init(dev->params.uart, SDS011_UART_BAUDRATE, _rx_cb, dev) != 0)) {
|
|
|
|
mutex_unlock(&dev->cb_lock);
|
|
|
|
mutex_unlock(&dev->dev_lock);
|
|
|
|
return SDS011_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
uart_write(dev->params.uart, cmd, SDS011_FRAME_SEND_LEN);
|
|
|
|
|
|
|
|
/* wait for the isr callback to unlock the mutex */
|
|
|
|
mutex_lock(&dev->cb_lock);
|
|
|
|
|
|
|
|
/* only copy data when checksum was valid */
|
|
|
|
if (dev->pos != 0) {
|
|
|
|
memcpy(recv_frm, dev->rx_mem, SDS011_FRAME_RECV_LEN);
|
|
|
|
/* mark the recv buffer as free */
|
|
|
|
dev->pos = 0;
|
|
|
|
dev->checksum = 0;
|
|
|
|
|
|
|
|
/* check if we received a valid response for the cmd sent*/
|
|
|
|
if(((recv_frm[SDS011_CMDID_IDX] == SDS011_RCMDID_REPLY) &&
|
|
|
|
(cmd[SDS011_DB1_IDX] == recv_frm[SDS011_DB1_IDX]))
|
|
|
|
|| ((recv_frm[SDS011_CMDID_IDX] == SDS011_RCMDID_DATA)
|
|
|
|
&& (cmd[SDS011_DB1_IDX] == SDS011_CMD_DB1_QUERY_DATA))) {
|
|
|
|
res = SDS011_OK;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
res = SDS011_INVALID_RESPONSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
res = SDS011_INVALID_CHKSUM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* reset mutex state */
|
|
|
|
mutex_unlock(&dev->cb_lock);
|
|
|
|
|
|
|
|
/* if no active reporting callback is registered, UART can be disabled */
|
|
|
|
if((dev->cb == NULL) &&
|
|
|
|
(uart_init(dev->params.uart, SDS011_UART_BAUDRATE, NULL, NULL) != 0)) {
|
|
|
|
res = SDS011_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* release device */
|
|
|
|
mutex_unlock(&dev->dev_lock);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief shorthand to get a single byte property with _send_recv_cmd
|
|
|
|
*
|
|
|
|
* @param[in] dev SDS011 device the command is sent to
|
|
|
|
* @param[in] data_bytes data bytes to send within the command
|
|
|
|
* @param[in] len number of data bytes
|
|
|
|
* @param[out] p pointer for storing single data byte of the reply
|
|
|
|
* @param[out] p_idx index of data byte we want to read
|
|
|
|
*/
|
|
|
|
static int _get_property(sds011_t *dev, uint8_t *data_bytes, size_t len,
|
|
|
|
uint8_t *p, uint8_t p_idx)
|
|
|
|
{
|
|
|
|
uint8_t recv[SDS011_FRAME_RECV_LEN];
|
|
|
|
int res = _send_recv_cmd(dev, data_bytes, len, recv);
|
|
|
|
|
|
|
|
if (res == SDS011_OK) {
|
|
|
|
*p = recv[p_idx];
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sds011_init(sds011_t *dev, const sds011_params_t *params)
|
|
|
|
{
|
|
|
|
assert((dev != NULL) && (params != NULL) && (params->uart < UART_NUMOF));
|
|
|
|
|
2020-01-17 12:45:13 +01:00
|
|
|
if ((gpio_is_valid(params->pwr_pin)) &&
|
2018-11-22 18:50:29 +01:00
|
|
|
(gpio_init(params->pwr_pin, GPIO_OUT) != 0)) {
|
|
|
|
return SDS011_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(&dev->params, params, sizeof(sds011_params_t));
|
|
|
|
|
|
|
|
mutex_init(&dev->dev_lock);
|
|
|
|
mutex_init(&dev->cb_lock);
|
|
|
|
|
|
|
|
dev->cb = NULL;
|
|
|
|
|
|
|
|
sds011_power_on(dev);
|
|
|
|
|
|
|
|
return SDS011_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sds011_register_callback(sds011_t *dev, sds011_callback_t cb, void *ctx)
|
|
|
|
{
|
|
|
|
assert(dev != NULL);
|
|
|
|
mutex_lock(&dev->dev_lock);
|
|
|
|
dev->cbctx = ctx;
|
|
|
|
dev->cb = cb;
|
|
|
|
|
|
|
|
/* either register un unregister the uart callback */
|
|
|
|
if (uart_init(dev->params.uart, SDS011_UART_BAUDRATE,
|
|
|
|
cb == NULL ? NULL : _rx_cb,
|
|
|
|
cb == NULL ? NULL : dev) != 0) {
|
|
|
|
mutex_unlock(&dev->dev_lock);
|
|
|
|
return SDS011_ERROR;
|
|
|
|
}
|
|
|
|
mutex_unlock(&dev->dev_lock);
|
|
|
|
return SDS011_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sds011_power_on(const sds011_t *dev)
|
|
|
|
{
|
|
|
|
assert(dev != NULL);
|
2020-01-17 12:45:13 +01:00
|
|
|
if(gpio_is_valid(dev->params.pwr_pin)) {
|
2018-11-22 18:50:29 +01:00
|
|
|
gpio_write(dev->params.pwr_pin, dev->params.pwr_ah);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sds011_power_off(const sds011_t *dev)
|
|
|
|
{
|
|
|
|
assert(dev != NULL);
|
2020-01-17 12:45:13 +01:00
|
|
|
if(gpio_is_valid(dev->params.pwr_pin)) {
|
2018-11-22 18:50:29 +01:00
|
|
|
gpio_write(dev->params.pwr_pin, !dev->params.pwr_ah);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int sds011_get_reporting_mode(sds011_t *dev, sds011_reporting_mode_t *mode)
|
|
|
|
{
|
|
|
|
assert(dev != NULL);
|
|
|
|
uint8_t cmd[] = {SDS011_CMD_DB1_SET_DR_MODE, SDS011_CMD_OPT_QUERY};
|
|
|
|
uint8_t prop = 0;
|
|
|
|
int res = _get_property(dev, cmd, sizeof(cmd), &prop, SDS011_DB3_IDX);
|
|
|
|
*mode = ((prop == 0) ? SDS011_RMODE_ACTIVE : SDS011_RMODE_QUERY);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sds011_set_reporting_mode(sds011_t *dev, sds011_reporting_mode_t mode)
|
|
|
|
{
|
|
|
|
assert(dev != NULL);
|
|
|
|
uint8_t cmd[] = {SDS011_CMD_DB1_SET_DR_MODE, SDS011_CMD_OPT_SET, mode};
|
|
|
|
|
|
|
|
uint8_t recv[SDS011_FRAME_RECV_LEN];
|
|
|
|
int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
|
|
|
|
|
|
|
|
if (res == SDS011_OK) {
|
|
|
|
if ((recv[SDS011_DB2_IDX] == SDS011_CMD_OPT_SET) &&
|
|
|
|
(recv[SDS011_DB3_IDX] == mode)) {
|
|
|
|
return SDS011_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SDS011_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sds011_read(sds011_t *dev, sds011_data_t *data)
|
|
|
|
{
|
|
|
|
assert((dev != NULL) && (data != NULL));
|
|
|
|
uint8_t cmd[] = {SDS011_CMD_DB1_QUERY_DATA};
|
|
|
|
|
|
|
|
uint8_t recv[SDS011_FRAME_RECV_LEN];
|
|
|
|
int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
|
|
|
|
|
|
|
|
if (res == SDS011_OK) {
|
|
|
|
data->pm_2_5 = recv[SDS011_DB1_IDX] | (recv[SDS011_DB2_IDX] << 8);
|
|
|
|
data->pm_10 = recv[SDS011_DB3_IDX] | (recv[SDS011_DB4_IDX] << 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sds011_set_dev_id(sds011_t *dev, uint16_t sens_dev_id)
|
|
|
|
{
|
|
|
|
assert(dev != NULL);
|
|
|
|
uint8_t cmd[13] = {0};
|
|
|
|
cmd[0] = SDS011_CMD_DB1_SET_DEV_ID;
|
|
|
|
cmd[11] = (sens_dev_id >> 8) & 0xFF;
|
|
|
|
cmd[12] = sens_dev_id & 0xFF;
|
|
|
|
|
|
|
|
uint8_t recv[SDS011_FRAME_RECV_LEN];
|
|
|
|
int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
|
|
|
|
|
|
|
|
if ((res == SDS011_OK) &&
|
|
|
|
(recv[SDS011_DB5_IDX] == cmd[11]) &&
|
|
|
|
(recv[SDS011_DB6_IDX] == cmd[12])) {
|
|
|
|
return SDS011_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SDS011_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sds011_get_working_mode(sds011_t *dev, sds011_working_mode_t *mode)
|
|
|
|
{
|
|
|
|
assert(dev != NULL);
|
|
|
|
uint8_t cmd[] = {SDS011_CMD_DB1_SET_SLEEP_WORK, SDS011_CMD_OPT_QUERY};
|
|
|
|
uint8_t prop = 0;
|
|
|
|
int res = _get_property(dev, cmd, sizeof(cmd), &prop, SDS011_DB3_IDX);
|
|
|
|
*mode = ((prop == 0) ? SDS011_WMODE_SLEEP : SDS011_WMODE_WORK);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sds011_set_working_mode(sds011_t *dev, sds011_working_mode_t mode)
|
|
|
|
{
|
|
|
|
assert(dev != NULL);
|
|
|
|
uint8_t cmd[] = {SDS011_CMD_DB1_SET_SLEEP_WORK, SDS011_CMD_OPT_SET, mode};
|
|
|
|
|
|
|
|
uint8_t recv[SDS011_FRAME_RECV_LEN];
|
|
|
|
int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
|
|
|
|
|
|
|
|
if (res == SDS011_OK) {
|
|
|
|
if ((recv[SDS011_DB2_IDX] == SDS011_CMD_OPT_SET) &&
|
|
|
|
(recv[SDS011_DB3_IDX] == mode)) {
|
|
|
|
return SDS011_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SDS011_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sds011_get_working_period(sds011_t *dev, uint8_t *minutes)
|
|
|
|
{
|
|
|
|
assert(dev != NULL);
|
|
|
|
uint8_t cmd[] = {SDS011_CMD_DB1_SET_WORK_PERIOD, SDS011_CMD_OPT_QUERY};
|
|
|
|
return _get_property(dev, cmd, sizeof(cmd), minutes, SDS011_DB3_IDX);
|
|
|
|
}
|
|
|
|
|
|
|
|
int sds011_set_working_period(sds011_t *dev, uint8_t minutes)
|
|
|
|
{
|
|
|
|
assert(dev != NULL);
|
|
|
|
uint8_t cmd[] = {SDS011_CMD_DB1_SET_WORK_PERIOD, SDS011_CMD_OPT_SET, minutes};
|
|
|
|
|
|
|
|
uint8_t recv[SDS011_FRAME_RECV_LEN];
|
|
|
|
int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
|
|
|
|
|
|
|
|
if (res == SDS011_OK) {
|
|
|
|
if ((recv[SDS011_DB2_IDX] == SDS011_CMD_OPT_SET) &&
|
|
|
|
(recv[SDS011_DB3_IDX] == minutes)) {
|
|
|
|
return SDS011_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SDS011_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sds011_get_fw_version(sds011_t *dev, uint8_t *year, uint8_t *mon, uint8_t *day)
|
|
|
|
{
|
|
|
|
assert((dev != NULL) && (year != NULL) && (mon != NULL) && (day != NULL));
|
|
|
|
uint8_t cmd[] = {SDS011_CMD_DB1_CHECK_FIRMWARE};
|
|
|
|
|
|
|
|
uint8_t recv[SDS011_FRAME_RECV_LEN];
|
|
|
|
int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
|
|
|
|
|
|
|
|
if (res == SDS011_OK) {
|
|
|
|
*year = recv[SDS011_DB2_IDX];
|
|
|
|
*mon = recv[SDS011_DB3_IDX];
|
|
|
|
*day = recv[SDS011_DB4_IDX];
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|