mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
6d61381d2a
The expandable GPIO API requires the comparison of structured GPIO types. This means that inline functions must be used instead of direct comparisons. For the migration process, drivers must first be changed so that they use the inline comparison functions.
385 lines
11 KiB
C
385 lines
11 KiB
C
/*
|
|
* 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));
|
|
|
|
if ((gpio_is_valid(params->pwr_pin)) &&
|
|
(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);
|
|
if(gpio_is_valid(dev->params.pwr_pin)) {
|
|
gpio_write(dev->params.pwr_pin, dev->params.pwr_ah);
|
|
}
|
|
}
|
|
|
|
void sds011_power_off(const sds011_t *dev)
|
|
{
|
|
assert(dev != NULL);
|
|
if(gpio_is_valid(dev->params.pwr_pin)) {
|
|
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;
|
|
}
|