| /* |
| * Copyright (c) 2023 SteadConnect |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Datasheet: |
| * https://wiki.dfrobot.com/A01NYUB%20Waterproof%20Ultrasonic%20Sensor%20SKU:%20SEN0313 |
| * |
| */ |
| |
| #define DT_DRV_COMPAT dfrobot_a01nyub |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/uart.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/drivers/sensor.h> |
| |
| LOG_MODULE_REGISTER(a01nyub_sensor, CONFIG_SENSOR_LOG_LEVEL); |
| |
| #define A01NYUB_BUF_LEN 4 |
| #define A01NYUB_CHECKSUM_IDX 3 |
| #define A01NYUB_HEADER 0xff |
| |
| const struct uart_config uart_cfg_a01nyub = { |
| .baudrate = 9600, |
| .parity = UART_CFG_PARITY_NONE, |
| .stop_bits = UART_CFG_STOP_BITS_1, |
| .data_bits = UART_CFG_DATA_BITS_8, |
| .flow_ctrl = UART_CFG_FLOW_CTRL_NONE |
| }; |
| |
| struct a01nyub_data { |
| /* Max data length is 16 bits */ |
| uint16_t data; |
| uint8_t xfer_bytes; |
| uint8_t rd_data[A01NYUB_BUF_LEN]; |
| }; |
| |
| struct a01nyub_cfg { |
| const struct device *uart_dev; |
| uart_irq_callback_user_data_t cb; |
| }; |
| |
| static void a01nyub_uart_flush(const struct device *uart_dev) |
| { |
| uint8_t c; |
| |
| while (uart_fifo_read(uart_dev, &c, 1) > 0) { |
| continue; |
| } |
| } |
| |
| static uint8_t a01nyub_checksum(const uint8_t *data) |
| { |
| uint16_t cs = 0; |
| |
| for (uint8_t i = 0; i < A01NYUB_BUF_LEN - 1; i++) { |
| cs += data[i]; |
| } |
| |
| return (uint8_t) (cs & 0x00FF); |
| } |
| |
| static inline int a01nyub_poll_data(const struct device *dev) |
| { |
| struct a01nyub_data *data = dev->data; |
| uint8_t checksum; |
| |
| checksum = a01nyub_checksum(data->rd_data); |
| if (checksum != data->rd_data[A01NYUB_CHECKSUM_IDX]) { |
| LOG_DBG("Checksum mismatch: calculated 0x%x != data checksum 0x%x", |
| checksum, |
| data->rd_data[A01NYUB_CHECKSUM_IDX]); |
| LOG_DBG("Data bytes: (%x,%x,%x,%x)", |
| data->rd_data[0], |
| data->rd_data[1], |
| data->rd_data[2], |
| data->rd_data[3]); |
| |
| return -EBADMSG; |
| } |
| |
| data->data = (data->rd_data[1]<<8) + data->rd_data[2]; |
| |
| return 0; |
| } |
| |
| static int a01nyub_channel_get(const struct device *dev, enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| struct a01nyub_data *data = dev->data; |
| |
| if (chan != SENSOR_CHAN_DISTANCE) { |
| return -ENOTSUP; |
| } |
| /* val1 is meters, val2 is microns. Both are int32_t |
| * data->data is in mm and units of uint16_t |
| */ |
| val->val1 = (uint32_t) (data->data / (uint16_t) 1000); |
| val->val2 = (uint32_t) ((data->data % 1000) * 1000); |
| return 0; |
| } |
| |
| static int a01nyub_sample_fetch(const struct device *dev, enum sensor_channel chan) |
| { |
| __ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL); |
| |
| if (chan == SENSOR_CHAN_DISTANCE || chan == SENSOR_CHAN_ALL) { |
| return a01nyub_poll_data(dev); |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static const struct sensor_driver_api a01nyub_api_funcs = { |
| .sample_fetch = a01nyub_sample_fetch, |
| .channel_get = a01nyub_channel_get, |
| }; |
| |
| static void a01nyub_uart_isr(const struct device *uart_dev, void *user_data) |
| { |
| const struct device *dev = user_data; |
| struct a01nyub_data *data = dev->data; |
| |
| if (uart_dev == NULL) { |
| LOG_DBG("UART device is NULL"); |
| return; |
| } |
| |
| if (!uart_irq_update(uart_dev)) { |
| LOG_DBG("Unable to start processing interrupts"); |
| return; |
| } |
| |
| if (uart_irq_rx_ready(uart_dev)) { |
| data->xfer_bytes += uart_fifo_read(uart_dev, &data->rd_data[data->xfer_bytes], |
| A01NYUB_BUF_LEN - data->xfer_bytes); |
| |
| /* The first byte should be A01NYUB_HEADER for a valid read. |
| * If we do not read A01NYUB_HEADER on what we think is the |
| * first byte, then reset the number of bytes read until we do |
| */ |
| if ((data->rd_data[0] != A01NYUB_HEADER) & (data->xfer_bytes == 1)) { |
| LOG_DBG("First byte not header! Resetting # of bytes read."); |
| data->xfer_bytes = 0; |
| } |
| |
| if (data->xfer_bytes == A01NYUB_BUF_LEN) { |
| LOG_DBG("Read (0x%x,0x%x,0x%x,0x%x)", |
| data->rd_data[0], |
| data->rd_data[1], |
| data->rd_data[2], |
| data->rd_data[3]); |
| a01nyub_uart_flush(uart_dev); |
| data->xfer_bytes = 0; |
| } |
| } |
| } |
| |
| static int a01nyub_init(const struct device *dev) |
| { |
| const struct a01nyub_cfg *cfg = dev->config; |
| int ret = 0; |
| |
| uart_irq_rx_disable(cfg->uart_dev); |
| uart_irq_tx_disable(cfg->uart_dev); |
| |
| a01nyub_uart_flush(cfg->uart_dev); |
| |
| LOG_DBG("Initializing A01NYUB driver"); |
| |
| ret = uart_configure(cfg->uart_dev, &uart_cfg_a01nyub); |
| if (ret == -ENOSYS) { |
| LOG_ERR("Unable to configure UART port"); |
| return -ENOSYS; |
| } |
| |
| ret = uart_irq_callback_user_data_set(cfg->uart_dev, cfg->cb, (void *)dev); |
| |
| if (ret < 0) { |
| if (ret == -ENOTSUP) { |
| LOG_ERR("Interrupt-driven UART API support not enabled"); |
| } else if (ret == -ENOSYS) { |
| LOG_ERR("UART device does not support interrupt-driven API"); |
| } else { |
| LOG_ERR("Error setting UART callback: %d", ret); |
| } |
| return ret; |
| } |
| |
| uart_irq_rx_enable(cfg->uart_dev); |
| |
| return ret; |
| } |
| |
| #define A01NYUB_INIT(inst) \ |
| \ |
| static struct a01nyub_data a01nyub_data_##inst; \ |
| \ |
| static const struct a01nyub_cfg a01nyub_cfg_##inst = { \ |
| .uart_dev = DEVICE_DT_GET(DT_INST_BUS(inst)), \ |
| .cb = a01nyub_uart_isr, \ |
| }; \ |
| \ |
| SENSOR_DEVICE_DT_INST_DEFINE(inst, a01nyub_init, NULL, \ |
| &a01nyub_data_##inst, &a01nyub_cfg_##inst, \ |
| POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &a01nyub_api_funcs); |
| |
| DT_INST_FOREACH_STATUS_OKAY(A01NYUB_INIT) |