Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/iio/chemical/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 6 kB image not shown  

Quelle  scd30_serial.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * Sensirion SCD30 carbon dioxide sensor serial driver
 *
 * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
 */

#include <linux/crc16.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/iio/iio.h>
#include <linux/jiffies.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/serdev.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/unaligned.h>

#include "scd30.h"

#define SCD30_SERDEV_ADDR 0x61
#define SCD30_SERDEV_WRITE 0x06
#define SCD30_SERDEV_READ 0x03
#define SCD30_SERDEV_MAX_BUF_SIZE 17
#define SCD30_SERDEV_RX_HEADER_SIZE 3
#define SCD30_SERDEV_CRC_SIZE 2
#define SCD30_SERDEV_TIMEOUT msecs_to_jiffies(200)

struct scd30_serdev_priv {
 struct completion meas_ready;
 char *buf;
 int num_expected;
 int num;
};

static u16 scd30_serdev_cmd_lookup_tbl[] = {
 [CMD_START_MEAS] = 0x0036,
 [CMD_STOP_MEAS] = 0x0037,
 [CMD_MEAS_INTERVAL] = 0x0025,
 [CMD_MEAS_READY] = 0x0027,
 [CMD_READ_MEAS] = 0x0028,
 [CMD_ASC] = 0x003a,
 [CMD_FRC] = 0x0039,
 [CMD_TEMP_OFFSET] = 0x003b,
 [CMD_FW_VERSION] = 0x0020,
 [CMD_RESET] = 0x0034,
};

static u16 scd30_serdev_calc_crc(const char *buf, int size)
{
 return crc16(0xffff, buf, size);
}

static int scd30_serdev_xfer(struct scd30_state *state, char *txbuf, int txsize,
        char *rxbuf, int rxsize)
{
 struct serdev_device *serdev = to_serdev_device(state->dev);
 struct scd30_serdev_priv *priv = state->priv;
 int ret;

 priv->buf = rxbuf;
 priv->num_expected = rxsize;
 priv->num = 0;

 ret = serdev_device_write(serdev, txbuf, txsize, SCD30_SERDEV_TIMEOUT);
 if (ret < 0)
  return ret;
 if (ret != txsize)
  return -EIO;

 ret = wait_for_completion_interruptible_timeout(&priv->meas_ready, SCD30_SERDEV_TIMEOUT);
 if (ret < 0)
  return ret;
 if (!ret)
  return -ETIMEDOUT;

 return 0;
}

static int scd30_serdev_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg,
    void *response, int size)
{
 /*
 * Communication over serial line is based on modbus protocol (or rather
 * its variation called modbus over serial to be precise). Upon
 * receiving a request device should reply with response.
 *
 * Frame below represents a request message. Each field takes
 * exactly one byte.
 *
 * +------+------+-----+-----+-------+-------+-----+-----+
 * | dev  | op   | reg | reg | byte1 | byte0 | crc | crc |
 * | addr | code | msb | lsb |       |       | lsb | msb |
 * +------+------+-----+-----+-------+-------+-----+-----+
 *
 * The message device replies with depends on the 'op code' field from
 * the request. In case it was set to SCD30_SERDEV_WRITE sensor should
 * reply with unchanged request. Otherwise 'op code' was set to
 * SCD30_SERDEV_READ and response looks like the one below. As with
 * request, each field takes one byte.
 *
 * +------+------+--------+-------+-----+-------+-----+-----+
 * | dev  | op   | num of | byte0 | ... | byteN | crc | crc |
 * | addr | code | bytes  |       |     |       | lsb | msb |
 * +------+------+--------+-------+-----+-------+-----+-----+
 */

 char txbuf[SCD30_SERDEV_MAX_BUF_SIZE] = { SCD30_SERDEV_ADDR },
      rxbuf[SCD30_SERDEV_MAX_BUF_SIZE];
 int ret, rxsize, txsize = 2;
 char *rsp = response;
 u16 crc;

 put_unaligned_be16(scd30_serdev_cmd_lookup_tbl[cmd], txbuf + txsize);
 txsize += 2;

 if (rsp) {
  txbuf[1] = SCD30_SERDEV_READ;
  if (cmd == CMD_READ_MEAS)
   /* number of u16 words to read */
   put_unaligned_be16(size / 2, txbuf + txsize);
  else
   put_unaligned_be16(0x0001, txbuf + txsize);
  txsize += 2;
  crc = scd30_serdev_calc_crc(txbuf, txsize);
  put_unaligned_le16(crc, txbuf + txsize);
  txsize += 2;
  rxsize = SCD30_SERDEV_RX_HEADER_SIZE + size + SCD30_SERDEV_CRC_SIZE;
 } else {
  if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET))
   arg = 0x0001;

  txbuf[1] = SCD30_SERDEV_WRITE;
  put_unaligned_be16(arg, txbuf + txsize);
  txsize += 2;
  crc = scd30_serdev_calc_crc(txbuf, txsize);
  put_unaligned_le16(crc, txbuf + txsize);
  txsize += 2;
  rxsize = txsize;
 }

 ret = scd30_serdev_xfer(state, txbuf, txsize, rxbuf, rxsize);
 if (ret)
  return ret;

 switch (txbuf[1]) {
 case SCD30_SERDEV_WRITE:
  if (memcmp(txbuf, rxbuf, txsize)) {
   dev_err(state->dev, "wrong message received\n");
   return -EIO;
  }
  break;
 case SCD30_SERDEV_READ:
  if (rxbuf[2] != (rxsize - SCD30_SERDEV_RX_HEADER_SIZE - SCD30_SERDEV_CRC_SIZE)) {
   dev_err(state->dev, "received data size does not match header\n");
   return -EIO;
  }

  rxsize -= SCD30_SERDEV_CRC_SIZE;
  crc = get_unaligned_le16(rxbuf + rxsize);
  if (crc != scd30_serdev_calc_crc(rxbuf, rxsize)) {
   dev_err(state->dev, "data integrity check failed\n");
   return -EIO;
  }

  rxsize -= SCD30_SERDEV_RX_HEADER_SIZE;
  memcpy(rsp, rxbuf + SCD30_SERDEV_RX_HEADER_SIZE, rxsize);
  break;
 default:
  dev_err(state->dev, "received unknown op code\n");
  return -EIO;
 }

 return 0;
}

static size_t scd30_serdev_receive_buf(struct serdev_device *serdev,
           const u8 *buf, size_t size)
{
 struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev);
 struct scd30_serdev_priv *priv;
 struct scd30_state *state;
 size_t num;

 if (!indio_dev)
  return 0;

 state = iio_priv(indio_dev);
 priv = state->priv;

 /* just in case sensor puts some unexpected bytes on the bus */
 if (!priv->buf)
  return 0;

 if (priv->num + size >= priv->num_expected)
  num = priv->num_expected - priv->num;
 else
  num = size;

 memcpy(priv->buf + priv->num, buf, num);
 priv->num += num;

 if (priv->num == priv->num_expected) {
  priv->buf = NULL;
  complete(&priv->meas_ready);
 }

 return num;
}

static const struct serdev_device_ops scd30_serdev_ops = {
 .receive_buf = scd30_serdev_receive_buf,
 .write_wakeup = serdev_device_write_wakeup,
};

static int scd30_serdev_probe(struct serdev_device *serdev)
{
 struct device *dev = &serdev->dev;
 struct scd30_serdev_priv *priv;
 int irq, ret;

 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 if (!priv)
  return -ENOMEM;

 init_completion(&priv->meas_ready);
 serdev_device_set_client_ops(serdev, &scd30_serdev_ops);

 ret = devm_serdev_device_open(dev, serdev);
 if (ret)
  return ret;

 serdev_device_set_baudrate(serdev, 19200);
 serdev_device_set_flow_control(serdev, false);

 ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
 if (ret)
  return ret;

 irq = fwnode_irq_get(dev_fwnode(dev), 0);

 return scd30_probe(dev, irq, KBUILD_MODNAME, priv, scd30_serdev_command);
}

static const struct of_device_id scd30_serdev_of_match[] = {
 { .compatible = "sensirion,scd30" },
 { }
};
MODULE_DEVICE_TABLE(of, scd30_serdev_of_match);

static struct serdev_device_driver scd30_serdev_driver = {
 .driver = {
  .name = KBUILD_MODNAME,
  .of_match_table = scd30_serdev_of_match,
  .pm = pm_sleep_ptr(&scd30_pm_ops),
 },
 .probe = scd30_serdev_probe,
};
module_serdev_device_driver(scd30_serdev_driver);

MODULE_AUTHOR("Tomasz Duszynski ");
MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("IIO_SCD30");

Messung V0.5
C=91 H=92 G=91

¤ Dauer der Verarbeitung: 0.10 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.