diff --git a/CMakeLists.txt b/CMakeLists.txt index dbb19ec..4cb6ca1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,9 +21,4 @@ zephyr_library_sources_ifdef(CONFIG_USE_SEVEN_SEGMENT_DISPLAY src/sevensegment.c zephyr_library_sources_ifdef(CONFIG_USE_READY_LED src/ready_led.c) zephyr_library_sources_ifdef(CONFIG_USE_METEOROLOGY_SENSORS src/meteorology.c) zephyr_library_sources_ifdef(CONFIG_USE_LOWPOWER src/low_power.c) -zephyr_library_sources_ifdef(CONFIG_MFRC522 src/mfrc522.c) -zephyr_library_sources_ifdef(CONFIG_ST25DVXXK src/st25dvxxk.c) -zephyr_library_sources_ifdef(CONFIG_PN532 src/pn532.c) -zephyr_library_sources_ifdef(CONFIG_USE_MFRC522 src/mfrc522.c) -zephyr_library_sources_ifdef(CONFIG_ST25DVXXK src/st25dvxxk.c) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md new file mode 100644 index 0000000..19decc1 --- /dev/null +++ b/DOCUMENTATION.md @@ -0,0 +1,61 @@ +# Sensors + +This directory contains drivers, helpers and example code for sensors and +actuators used in Zephyr-based projects. + +## Quick usage + +Add the sensors library to your application's CMakeLists.txt: + +```cmake +add_subdirectory(sensors) +target_link_libraries(app PRIVATE sensors) +``` + +Enable component configuration in your project's `prj.conf` (examples): + +``` +# Enable GPIO LED helpers +CONFIG_USE_GPIO_LED=y +# Enable motion sensor helper +CONFIG_USE_MOTION_SENSOR=y +``` + +## Patches + +The `patches/` directory contains patch files required for specific boards +or Zephyr modules. Patch paths use the format `module/NN_description.patch`. +Apply with `patch -p1 < path/to/patch` from a Zephyr source tree when needed. + +## Documentation + +API and developer documentation is in the `doc/` directory and can be +generated with Doxygen (run `doxygen` from the `doc/` folder). + +## Public headers and short notes + +The `sensors/include` directory exposes small helper APIs used by apps: + +- `gpio_led.h` — GPIO-driven LED helpers (`gpio_led_init()`, + `gpio_led_set_led()`); enabled with `CONFIG_USE_GPIO_LED`. +- `ready_led.h` — higher-level ready/status LED helpers used on some + boards. +- `button.h` — button state and callback registration (`button_init()`). +- `motion_sensor.h` — motion sensor init and callback (`motion_sensor_init()`); + enabled with `CONFIG_USE_MOTION_SENSOR`. +- `stripled.h`, `pwmled.h`, `sevensegment.h` — LED strip, PWM LED and + 7-segment helpers. +- `rfid.h`, `pn532.h`, `mfrc522.h`, `st25dvxxk.h` — RFID / NFC related drivers. + +## Examples and boards + +Board overlays and configuration live under `boards/` and `zephyr/`. +See the board-specific `.overlay` files for GPIO assignments and device-tree +settings used by drivers in this directory. + +## Next steps + +- Add per-header API notes to the headers in `sensors/include` (short + descriptions are present in header comments). +- If you want, I can run a repo-wide search to list all callers of the + public APIs and update examples. diff --git a/Kconfig b/Kconfig index 82a077f..c754bdf 100644 --- a/Kconfig +++ b/Kconfig @@ -200,61 +200,6 @@ config USE_METEOROLOGY_SENSORS help Use meteorology sensors (BMP280, BME280 etc) -config MFRC522 - bool "Use mfrc522 rfid chip" - select SPI - default n - help - Use the mfrc522 rfid chip - -config MFRC522_INIT_PRIORITY - int "Init priority" - depends on MFRC522 - default 90 - help - Device driver initialization priority. - - - - # ST25DVXXK NFC/RFID tag configuration options - -config ST25DVXXK - bool "ST25DVXXK NFC/RFID tag driver" - default n -config USE_MFRC522 - bool "Use mfrc522 rfid chip" - default n - help - Use the mfrc522 rfid chip -# ST25DVXXK NFC/RFID tag configuration options - -config ST25DVXXK - bool "ST25DVXXK NFC/RFID tag driver" - default y - select I2C - help - Enable driver for ST25DVXXK NFC/RFID tag. - -config ST25DVXXK_INIT_PRIORITY - int "Init priority" - depends on ST25DVXXK - default 90 - help - Device driver initialization priority. - -config PN532 - bool "PN532 NFC driver" - default n - help - Enable device driver for PN532 NFC/RFID device - -config PN532_INIT_PRIORITY - int "Init priority for the PN532" - depends on PN532 - default 90 - help - Device driver initialization priority - module = SENSORS module-str = "Sensors" module-help = Enables logging for the sensors module diff --git a/doc/Doxyfile b/doc/Doxyfile index fc992a4..931977b 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -944,8 +944,20 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = \ - ../src/ready_led.c ../src/sensor_log.c ../src/button.c \ - ../include/ready_led.h ../include/button.h + ../src/adc.c ../include/adc.h \ + ../src/button.c ../include/button.h \ + ../sr/gpio_led.c ../include/gpio_led.h\ + ../src/ky40.c ../incude/ky40.h \ + ../src/low_power.c ../include/low_power.h \ + ../src/meteorology.c ../include/meteorology.h \ + ../src/motion_sensor.c ../include/motion_sensor.h \ + ../src/pn532.c ../include/on532.h \ + ../src/pwmled.c ../include/pwmled.h \ + ../src/ready_led.c ../include/ready_led.h \ + ../src/sensor_log.c ../include/sensor_logging.h \ + ../src/sevensegment.c ../include/sevensegment.h \ + ../src/st25dvxxk.c ../include/st25dvxxk.h \ + ../src/stripled.c ../include/stripled.h # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -2433,8 +2445,25 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = CONFIG_USE_READY_LED \ - CONFIG_USE_BUTTON +PREDEFINED = \ + CONFIG_USE_ADC \ + CONFIG_USE_ADC_DEBOUNCE \ + CONFIG_USE_BUTTON \ + CONFIG_USE_READY_LED \ + CONFIG_USE_READY_LED_CONTROLLER \ + CONFIG_USE_READY_LED_GPIO \ + CONFIG_USE_READY_LED_PICO_W \ + CONFIG_USE_READY_LED_PWM \ + CONFIG_USE_READY_LED_STRIP \ + CONFIG_USE_GPIO_LED \ + CONFIG_USE_KY40 \ + CONFIG_USE_LOWPOWER \ + CONFIG_USE_METEOROLOGY_SENSORS \ + CONFIG_USE_MOTION_SENSOR \ + CONFIG_USE_PWM_LED \ + CONFIG_MOTION_SENSOR_DEBOUNCE \ + CONFIG_USE_FOUR_ELEMENT_SEVEN_SEGMENT_DISPLAY \ + CONFIG_USE_STRIP_LED \ # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/include/adc.h b/include/adc.h new file mode 100644 index 0000000..d0614b3 --- /dev/null +++ b/include/adc.h @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Beechwoods Software, Inc brad@beechwoods.com + * All Rights Reserved + * SPDX-License-Identifier: Apache 2.0 + */ + +#pragma once + +/** + * @file adc.h + * @brief ADC sensor interface + * + * Provides a small API to initialize ADC channels configured through + * devicetree and to read sensor levels. + */ + +/** + * @brief + * Initialize the adc + */ +int bws_adc_init(); + +/** + * @brief get the number of sensors + * @return The number of sensors + */ +int bws_adc_get_num_sensors(); +/** + * @brief get the level of a sensor + * @param num The sensor number + * @return The value of the sensor + */ +int bws_adc_get_sensor(int num); + +#ifdef CONFIG_USE_ADC_DEBOUNCE +/** + * @brief dump the debounce data for a sensor + * @param num The sensor number + */ +void bws_adc_dump_debounce(int num); +#endif diff --git a/include/gpio_led.h b/include/gpio_led.h new file mode 100644 index 0000000..c8dd257 --- /dev/null +++ b/include/gpio_led.h @@ -0,0 +1,71 @@ +/* + * Copyright 2023 Beechwoods Software Inc. + * All Rights Reserved. + * SPDX-License-Identifier: Apache 2.0 +*/ + +#pragma once + +#include + +/** + * @file gpio_led.h + * @brief GPIO-based LED helper API + * + * Provides initialization and simple control primitives for LEDs + * driven by GPIO lines. This header is conditionally compiled when + * `CONFIG_USE_GPIO_LED` is enabled. + */ + +/** + * @def GPIO_ZONE_SIZE + * @brief Number of entries in a color zone array. + */ +#define GPIO_ZONE_SIZE 6 + +/** + * @typedef gpio_color_zone_t + * @brief Array of integers representing different color zone thresholds. + * + * The array contains GPIO_ZONE_SIZE integers describing color thresholds + * or levels. The first element (index 0) is the highest legal value and + * the last element (index GPIO_ZONE_SIZE - 1) is the lowest legal value. + */ +typedef int gpio_color_zone_t[GPIO_ZONE_SIZE]; + +#ifdef CONFIG_USE_GPIO_LED + +/** + * @brief Post-initialization hook for the GPIO LED subsystem. + * + * Should be called after system boot or when bringing the LED subsystem + * up from a suspended state. Typically used to restore LED state. + */ +void gpio_led_post(void); + +/** + * @brief Initialize the GPIO-based LED subsystem. + * + * Performs any hardware configuration required to manage LEDs via GPIO. + * + * @return 0 on success, negative error code on failure. + */ +int gpio_led_init(void); + +/** + * @brief Return number of LEDs managed by the GPIO LED driver. + * + * @return Number of LEDs (>=0) or a negative error code on failure. + */ +int gpio_led_get_num_leds(void); + +/** + * @brief Set the state of a GPIO-controlled LED. + * + * @param led Index of the LED (0-based). + * @param value true to turn the LED on, false to turn it off. + * @return 0 on success, negative error code on failure. + */ +int gpio_led_set_led(int led, bool value); + +#endif /* CONFIG_USE_GPIO_LED */ diff --git a/include/ky40.h b/include/ky40.h new file mode 100644 index 0000000..96c5f52 --- /dev/null +++ b/include/ky40.h @@ -0,0 +1,27 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + * SPDX-License-Identifier: Apache 2.0 + */ + + + +#pragma once + +#include + +/** + * @file ky40.h + * @brief KY-040 rotary encoder helper + * + * Thin wrapper around Zephyr input events for handling the KY-040 + * rotary encoder and registering rotation/click callbacks. + */ + +typedef const struct device * const ky40_device_t; + +typedef void (*ky40_event_callback)(struct input_event *, int); + +int ky40_init(ky40_event_callback func); +int ky40_get_rotation(); +void ky40_set_callback(ky40_event_callback func, int index); diff --git a/include/low_power.h b/include/low_power.h new file mode 100644 index 0000000..7fd237f --- /dev/null +++ b/include/low_power.h @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Beechwoods Software Inc. + * All Rights Reserved. + * SPDX-License-Identifier: Apache 2.0 +*/ + +#pragma once + + +/** + * @file low_power.h + * @brief Low power / sleep helper API + * + * Provides a thin API to put the system into light or deep sleep modes. + */ + +typedef enum sleep_type { + LIGHT_SLEEP, + DEEP_SLEEP +}sleep_type_t; + +/** + * @brief Initialize low-power facilities + * @return 0 on success, negative errno on failure + */ +int low_power_init(); + +/** + * @brief Set the system sleep mode + * @param sleep_type The sleep mode to enter + * @return 0 on success, negative errno on failure + */ +int low_power_set(sleep_type_t sleep_type); + diff --git a/include/meteorology.h b/include/meteorology.h new file mode 100644 index 0000000..91cf840 --- /dev/null +++ b/include/meteorology.h @@ -0,0 +1,60 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + * SPDX-License-Identifier: Apache 2.0 + */ + + +#pragma once + +/** + * @file meteorology.h + * @brief Temperature, humidity and pressure sensor abstraction + * + * Provides helpers to initialize environmental sensors and read + * temperature, humidity and pressure values. + */ + + +/** + * @brief Initialize the environmental sensor subsystem. + * + * This function performs any hardware initialization and configuration + * required to read temperature, humidity and pressure from the + * available environmental sensor. It should be called once during + * application startup. + * + * @return 0 on success, negative errno-style error code on failure. + */ +int meteorology_init(void); + +/** + * @brief Get the current temperature. + * + * The returned value is in degrees Celsius (°C). If the sensor cannot + * provide a value the function returns NaN. + * + * @return Temperature in °C, or NaN on error. + */ +double meteorology_get_temperature(void); + +/** + * @brief Get the current relative humidity. + * + * The returned value is in percent relative humidity (%RH). If the + * sensor cannot provide a value the function returns NaN. + * + * @return Relative humidity in %RH, or NaN on error. + */ +double meteorology_get_humidity(void); + +/** + * @brief Get the current barometric pressure. + * + * The returned value is in Pascals (Pa). If the sensor cannot provide a + * value the function returns NaN. + * + * @return Pressure in Pa, or NaN on error. + */ +double meteorology_get_pressure(void); + diff --git a/include/motion_sensor.h b/include/motion_sensor.h new file mode 100644 index 0000000..a31f56d --- /dev/null +++ b/include/motion_sensor.h @@ -0,0 +1,53 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + * SPDX-License-Identifier: Apache 2.0 + */ + + +#pragma once + +#ifdef CONFIG_USE_MOTION_SENSOR +#/** + * @file motion_sensor.h + * @brief Motion sensor helper API + * + * Provides a tiny API to initialize a motion sensor and register a + * callback invoked when motion is detected. The implementation may use + * board devicetree bindings and GPIO interrupts; initialization should + * validate device readiness and configure the interrupt handler. + */ + +/** + * @brief Motion sensor callback type + * + * The callback is invoked with no arguments when motion is detected. + * Implementations should keep the handler lightweight and defer heavy + * work to a work queue if needed. + */ +typedef void (*motion_sensor_handler_t)(void); + +/** + * @brief Initialize the motion sensor subsystem + * + * Configures device tree bindings and interrupt handlers required to + * detect motion. The function is a no-op if `CONFIG_USE_MOTION_SENSOR` + * is not enabled at build time. + * + * @param handler Optional callback to invoke when motion is detected. + * May be NULL; call `set_motion_sensor_callback` to + * change the handler after initialization. + * @return 0 on success, negative errno on failure + */ +int motion_sensor_init(motion_sensor_handler_t handler); + +/** + * @brief Set or change the motion sensor callback + * + * This function updates the callback invoked on motion events. + * Passing NULL will disable the callback. + * + * @param handler Callback function or NULL + */ +void set_motion_sensor_callback(motion_sensor_handler_t handler); +#endif // CONFIG_USE_MOTION_SENSOR diff --git a/include/pwmled.h b/include/pwmled.h new file mode 100644 index 0000000..7b40b38 --- /dev/null +++ b/include/pwmled.h @@ -0,0 +1,33 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + * SPDX-License-Identifier: Apache 2.0 + */ + +#pragma once + +#include + +/** + * @file pwmled.h + * @brief PWM driven LED helpers + * + * Control functions for LEDs driven by PWM channels (set period, pulse, + * and simple helpers to map sensor values to LED output). + */ + +int pwmled_init(); +int pwmled_post(); +int pwmled_set_pulse(int led_num, uint32_t pulse); +uint32_t pwmled_get_pulse(int led_num); +int pwmled_set_period(int led_num, uint32_t period); +uint32_t pwmled_get_period(); +int pwmled_set(int led_num, uint32_t period, uint32_t pulse); +uint32_t pwmled_get_max_period(); +int pwmled_get_num_leds(); + +/** + * deprecated + */ +int pwmled_set_leds( int moisture_level); +void pwmled_set_rg_max(int maximum); diff --git a/include/rfid.h b/include/rfid.h new file mode 100644 index 0000000..0bb67d9 --- /dev/null +++ b/include/rfid.h @@ -0,0 +1,31 @@ + + +#pragma once + +/** + * @file rfid.h + * @brief RFID tag type definitions and driver API + * + * Bitflags for supported tag types and the small driver API used by + * board-level RFID drivers. + */ + +#define RFID_TAG_MIFARE_CLASSIC_EV1 BIT(0) +#define RFID_TAG_MIFARE_PLUS_EV1 BIT(1) +#define RFID_TAG_MIFARE_PLUS_X BIT(2) +#define RFID_TAG_MIFARE_PLUS_EV2 BIT(3) +#define RFID_TAG_MIFARE_ULTRALIGHT_C BIT(4) +#define RFID_TAG_MIFARE_ULTRALIGHT_EV1 BIT(5) +#define RFID_TAG_MIFARE_ULTRALIGHT_NANO BIT(6) +#define RFID_TAG_MIFARE_ULTRALIGHT_AES BIT(7) +#define RFID_TAG_MIFARE_DESFIR_EV1 BIT(8) +#define RFID_TAG_MIFARE_DESFIR_EV2 BIT(9) +#define RFID_TAG_MIFARE_DESFIR_EV3 BIT(10) +#define RFID_TAG_MIFARE_DUOX BIT(11) + +typedef bool (*rfid_poll_t)(const struct device * dev); + + +typedef struct rfid_driver_api { + rfid_poll_t poll; +} rfid_api_t; diff --git a/include/sensors_logging.h b/include/sensors_logging.h index 76c3ccc..da17cf1 100644 --- a/include/sensors_logging.h +++ b/include/sensors_logging.h @@ -5,3 +5,10 @@ */ #pragma once #define SENSORS_LOG_MODULE_NAME Sensors + +/** + * @file sensors_logging.h + * @brief Logging module name for sensors subsystem + * + * Centralized logging module name used by the sensors components. + */ diff --git a/include/sevensegment.h b/include/sevensegment.h new file mode 100644 index 0000000..63f264f --- /dev/null +++ b/include/sevensegment.h @@ -0,0 +1,24 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + * SPDX-License-Identifier: Apache 2.0 + */ + + +#pragma once + +/** + * @file sevensegment.h + * @brief Seven-segment display helpers + * + * Small API to initialize and display numbers / text on a seven-segment + * display module. + */ + +void sevensegment_post(); +int sevensegment_init(); +int sevensegment_set_int(int number); +int sevensegment_set_text(char * text); +int sevensegment_set_element(int element); +int sevensegment_set_segment(int segment, bool on); + diff --git a/include/stripled.h b/include/stripled.h new file mode 100644 index 0000000..8239072 --- /dev/null +++ b/include/stripled.h @@ -0,0 +1,74 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + * SPDX-License-Identifier: Apache 2.0 + */ + +#pragma once +#include + +/** + * @file stripled.h + * @brief LED strip (WS2812-style) helpers + * + * API for initializing and controlling an RGB LED strip using the + * Zephyr `led_strip` driver interface. + */ + +/** + * @brief Initialize the LED strip subsystem. + * + * Configures and probes the underlying Zephyr `led_strip` device used to + * drive WS2812-style RGB LEDs. Call this once during application startup. + * + * @return 0 on success, negative error code on failure. + */ +int strip_led_init(void); + +/** + * @brief Turn the entire strip on using the last set colors. + * + * @return 0 on success, negative error code on failure. + */ +int strip_led_on(void); + +/** + * @brief Turn the entire strip off (set all pixels to black). + * + * @return 0 on success, negative error code on failure. + */ +int strip_led_off(void); + +/** + * @brief Set a single pixel's color. + * + * The pixel index is 0-based. Color components are 0-255. + * The change is applied to the driver's internal buffer; call + * `strip_led_display()` to push the buffer to the strip. + * + * @param pixel 0-based pixel index + * @param red Red component (0-255) + * @param green Green component (0-255) + * @param blue Blue component (0-255) + * @return 0 on success, negative error code on failure. + */ +int strip_led_set_color(int pixel, uint8_t red, uint8_t green, uint8_t blue); + +/** + * @brief Get number of LEDs managed by the strip driver. + * + * @return Number of LEDs (>0) or negative error code on failure. + */ +int strip_led_num_leds(void); + +/** + * @brief Display an array of RGB pixels on the strip. + * + * Copies `num_pixels` entries from `pixels` to the strip and updates + * the LEDs. The `pixels` array must contain `struct led_rgb` entries. + * + * @param pixels Pointer to an array of `struct led_rgb` values. + * @param num_pixels Number of pixels in the array. + * @return 0 on success, negative error code on failure. + */ +int strip_led_display(struct led_rgb * pixels, size_t num_pixels); diff --git a/src/adc.c b/src/adc.c new file mode 100644 index 0000000..da2c125 --- /dev/null +++ b/src/adc.c @@ -0,0 +1,308 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + */ +# +/** + * @file adc.c + * @brief ADC sensor implementation + * + * Implementation of ADC sensor initialization, sampling and optional + * debounce handling for configured ADC channels. + */ + +#ifdef CONFIG_USE_ADC + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "sensors_logging.h" +LOG_MODULE_DECLARE( SENSORS_LOG_MODULE_NAME, CONFIG_SENSORS_LOG_LEVEL ); + +/* + * The analog to digtial converter is a sensor that converts the analog voltage on a pin + * to a digital number + * To configure the adc sensor, the boards/.overlay file should have something similar to this: + * #include + * #include + * #include + * &adc0 { + * status = "okay"; + * }; + * / { + * zephyr,user { + * io-channels = <&adc0 0>, <&adc0 3>, <&adc0 6>, <&adc0 7>; + * }; + * }; + * &adc0 { + * #address-cells = <1>; + * #size-cells = <0>; + * + * channel@0 { + * reg = <0>; + * zephyr,gain = "ADC_GAIN_1"; + * zephyr,reference = "ADC_REF_INTERNAL"; + * zephyr,acquisition-time = ; + * zephyr,resolution = <12>; + * }; + * channel@3 { + * reg = <3>; + * zephyr,gain = "ADC_GAIN_1"; + * zephyr,reference = "ADC_REF_INTERNAL"; + * zephyr,acquisition-time = ; + * zephyr,resolution = <12>; + * }; + * channel@6 { + * reg = <6>; + * zephyr,gain = "ADC_GAIN_1"; + * zephyr,reference = "ADC_REF_INTERNAL"; + * zephyr,acquisition-time = ; + * zephyr,resolution = <12>; + * }; + * channel@7 { + * reg = <7>; + * zephyr,gain = "ADC_GAIN_1"; + * zephyr,reference = "ADC_REF_INTERNAL"; + * zephyr,acquisition-time = ; + * zephyr,resolution = <12>; + * }; + * }; + * zephyr,user { + * io-channels = <&adc0 0 &adc0 3 &adc0 6 &adc0 7>; + * }; + * + * ADC is enabled via Kconfig. The application Kconfig file must contain this: + * config USE_ADC + * bool "configure adc gpio ports" + * default n + * help + * Enable the adc gpio ports + * + * The applications prj.conf or boards/.conf file should have these configurations + * CONFIG_ADC=y + * CONFIG_USE_ADC=y + */ + + +#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \ + !DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels) +#error "No suitable devicetree overlay specified adc io_channels" +#endif + +#define DT_SPEC_AND_COMMA(node_id, prop, idx) \ + ADC_DT_SPEC_GET_BY_IDX(node_id, idx), + +/* Data of ADC io-channels specified in devicetree. */ +static const struct adc_dt_spec adc_channels[] = { + DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, DT_SPEC_AND_COMMA) +}; +#define NUM_ADC_SENSORS ARRAY_SIZE(adc_channels) +#ifdef CONFIG_USE_ADC_DEBOUNCE +/*** + * @brief adc_debounce structure + * There is one adc_debounce structure per adc. + * The ring buffer size is confurable. + ***/ +typedef struct adc_debounce { + int sample[CONFIG_ADC_DEBOUNCE_SAMPLE_SIZE]; + int position; + struct k_mutex count_mutex; +} adc_debounce_t; + +adc_debounce_t adc_ring_buffer[NUM_ADC_SENSORS]; + +static struct k_thread debounce_thread[NUM_ADC_SENSORS]; + +K_THREAD_STACK_ARRAY_DEFINE(debounce_stacks, NUM_ADC_SENSORS, CONFIG_ADC_DEBOUNCE_STACK_SIZE); +void debounce_worker(void *p1, void *p2, void *p3); + +bool mDone = false; +#endif // CONFIG_USE_ADC_DEBOUNCE +/* + * @brief + * Returns the number of configured sensors. Sensors are configured in the device tree using + * the boards/.overlay + * @return number of configure adc ports + */ +int +bws_adc_get_num_sensors() +{ + return NUM_ADC_SENSORS; +} + +/* + * @brief + * Initialize the adc ports. If a port initialization fails no further ports are configured + * + * @return 0 on success, -1 or the negative error number on failure + */ +int +bws_adc_init() +{ + int err; +#ifdef CONFIG_USE_ADC_DEBOUNCE + char buf[8]; + k_tid_t tid; +#endif // CONFIG_USE_ADC_DEBOUNCE + /* Configure channels individually prior to sampling. */ + for (size_t i = 0U; i < NUM_ADC_SENSORS; i++) { + LOG_DBG("adc - %s, channel %d: ", + adc_channels[i].dev->name, + adc_channels[i].channel_id); + if (!device_is_ready(adc_channels[i].dev)) { + LOG_ERR("ADC controller device %s not ready\n", adc_channels[i].dev->name); + return -1; + } + + err = adc_channel_setup_dt(&adc_channels[i]); + if (err < 0) { + LOG_ERR("Could not setup channel #%d (%d)\n", i, err); + return err; + } +#ifdef CONFIG_USE_ADC_DEBOUNCE + k_mutex_init(&adc_ring_buffer[i].count_mutex); + tid = k_thread_create(&debounce_thread[i], debounce_stacks[i], CONFIG_ADC_DEBOUNCE_STACK_SIZE, debounce_worker, + INT_TO_POINTER(i), NULL, NULL, K_PRIO_PREEMPT(10), 0, + K_NO_WAIT); + snprintf(buf,sizeof(buf),"adc-%d", i); + k_thread_name_set(tid,buf); +#endif // CONFIG_USE_ADC_DEBOUNCE + } + return 0; +} + +void +bws_adc_fini() +{ +#ifdef CONFIG_USE_ADC_DEBOUNCE + mDone = true; +#endif +} + +/* + * @brief + * Retrieve the current value of the ADC port + * + * @param num - the port number (zero based) + * + * @return if Positve the reading on the adc port if negative the negative error number + */ +int +_bws_adc_get_sensor(int num, bool debug) +{ + + int err; + int rc = 0; + uint16_t buf; + struct adc_sequence sequence = { + .buffer = &buf, + /* buffer size in bytes, not number of samples */ + .buffer_size = sizeof(buf), + }; + + int32_t val_mv; + if(debug) { + LOG_DBG("- %s, channel %d: ", + adc_channels[num].dev->name, + adc_channels[num].channel_id); + } + (void)adc_sequence_init_dt(&adc_channels[num], &sequence); + + err = adc_read(adc_channels[num].dev, &sequence); + if (err < 0) { + LOG_ERR("Could not read (%d)\n", err); + return err; + } + + /* + * If using differential mode, the 16 bit value + * in the ADC sample buffer should be a signed 2's + * complement value. + */ + if (adc_channels[num].channel_cfg.differential) { + val_mv = (int32_t)((int16_t)buf); + } else { + val_mv = (int32_t)buf; + } + if(debug) { + LOG_DBG("%"PRIx32, val_mv); + } + err = adc_raw_to_millivolts_dt(&adc_channels[num], + &val_mv); + /* conversion to mV may not be supported, skip if not */ + if (err < 0) { + rc = err; + LOG_ERR(" (value in mV not available)\n"); + } else { + if(debug) { + LOG_DBG(" = %"PRIx32" mV\n", val_mv); + } + rc = val_mv; + } + + return rc; +} +int +bws_adc_get_sensor(int num) +{ + int retval; + +#ifdef CONFIG_USE_ADC_DEBOUNCE + int i; + retval = 0; + k_mutex_lock(&adc_ring_buffer[num].count_mutex, K_FOREVER); + + for(i=0; i < CONFIG_ADC_DEBOUNCE_SAMPLE_SIZE; i++) { + retval += adc_ring_buffer[num].sample[i]; + } + + k_mutex_unlock(&adc_ring_buffer[num].count_mutex); + retval /= CONFIG_ADC_DEBOUNCE_SAMPLE_SIZE; +#else + retval = _bws_adc_get_sensor(num, true); +#endif + return retval; +} +#ifdef CONFIG_USE_ADC_DEBOUNCE +void debounce_worker(void *p1, void *p2, void *p3) +{ + int adc_num = (int)p1; + adc_debounce_t *ring_buffer = &adc_ring_buffer[adc_num]; + while(!mDone) { + k_mutex_lock(&ring_buffer->count_mutex, K_FOREVER); + ring_buffer->sample[ring_buffer->position++] = _bws_adc_get_sensor(adc_num,false); + if(ring_buffer->position > CONFIG_ADC_DEBOUNCE_SAMPLE_SIZE) { + ring_buffer->position = 0; + } + k_mutex_unlock(&adc_ring_buffer[adc_num].count_mutex); + k_sleep(K_MSEC(CONFIG_ADC_DEBOUNCE_CADENCE)); + } +} + +void bws_adc_dump_debounce(int num) +{ + adc_debounce_t *ring_buffer = &adc_ring_buffer[num]; + if(num > (NUM_ADC_SENSORS -1)) { + LOG_ERR("Invalid adc sensor number %d", num); + return; + } + LOG_WRN("Dumping adc %d", num); + for( int i = 0 ; i < CONFIG_ADC_DEBOUNCE_SAMPLE_SIZE; i+=5) { + LOG_WRN("%d %d %d %d %d", ring_buffer->sample[i], ring_buffer->sample[i+1], + ring_buffer->sample[i+2], ring_buffer->sample[i+3], ring_buffer->sample[i+4]); + } +} + +#endif // CONFIG_USE_ADC_DEBOUNCE + + +#endif // CONFIG_USE_ADC diff --git a/src/gpio_led.c b/src/gpio_led.c new file mode 100644 index 0000000..777a1fb --- /dev/null +++ b/src/gpio_led.c @@ -0,0 +1,199 @@ +/* + Copyright 2023 Beechwoods Software Inc. + All Rights Reserved +*/ + +#ifdef CONFIG_USE_GPIO_LED + +/** + * @file gpio_led.c + * @brief GPIO LED driver implementation + * + * Implements initialization and simple control helpers for LEDs wired + * to GPIO pins using devicetree `gpio-leds` bindings. + */ + +#include +#include +#include +#include + +#include "gpio_led.h" + +#include +#include "sensors_logging.h" +LOG_MODULE_DECLARE( SENSORS_LOG_MODULE_NAME, CONFIG_SENSORS_LOG_LEVEL); + +/* + * The gpio_leds are LEDS attached to GPIO pins + * To configure the GPIO LEDS the boards/.overlay file should contain something like this: + * / { + * leds { + * compatible = "gpio-leds"; + * red_leds: red_leds { + * gpios = <&gpio0 5 GPIO_ACTIVE_HIGH> ; + * }; + * yellow_leds: yellow_leds { + * gpios = <&gpio0 18 GPIO_ACTIVE_HIGH> ; + * }; + * green_leds:green_leds { + * gpios = <&gpio0 19 GPIO_ACTIVE_HIGH> ; + * }; + * }; + * }; + * + * The applications Kconfig file must contain the following: + * config USE_GPIO_LED + * bool "Use GPIO LEDS" + * default n + * help + * Enable the use of GPIO LEDs + * + * The applications prj.conf or boards//conf needs to contain the following to enable GPIO LEDS + * CONFIG_USE_GPIO_LED=y + */ + +#define LED_OFF 0 +#define LED_ON 1 + + +static const struct gpio_dt_spec gpioleds[] = { + DT_FOREACH_PROP_ELEM_SEP(DT_NODELABEL(gpio_leds), gpios, GPIO_DT_SPEC_GET_BY_IDX, (,)) +}; + +#define NUM_GPIO_LEDS (sizeof(gpioleds)/sizeof(struct gpio_dt_spec)) + +#if 0 +static const struct gpio_dt_spec leds_red = + GPIO_DT_SPEC_GET(DT_NODELABEL(red_leds),gpios); +static const struct gpio_dt_spec leds_yellow = + GPIO_DT_SPEC_GET(DT_NODELABEL(yellow_leds),gpios); +static const struct gpio_dt_spec leds_green = + GPIO_DT_SPEC_GET(DT_NODELABEL(green_leds),gpios); + // GPIO_DT_SPEC_GET(RED_NODE, gpios); + +static gpio_led_t leds; +#endif + +int gpio_led_get_num_leds() +{ + return NUM_GPIO_LEDS; +} + +int gpio_led_set_led(int led, bool value) +{ + int rc = -1; + rc = gpio_pin_set_dt(&gpioleds[led], value); + return rc; +} + +void gpio_led_post() { + int i; + int rc; + LOG_INF("gpio led post"); + for(i=0; i < NUM_GPIO_LEDS; i++) { + if((rc = gpio_led_set_led(i, true)) < 0) { + LOG_ERR("gpio %d post set failed %d", i, rc); + } + } + k_msleep(1000); + + for(i=0; i < NUM_GPIO_LEDS; i++) { + if((rc = gpio_led_set_led(i, false)) < 0) { + LOG_ERR("gpio %d post clear failed %d", i, rc); + } + } +#if 0 + if(gpio_pin_set_dt(&leds_red, 1) < 0) { + LOG_ERR("red led failed"); + } + if(gpio_pin_set_dt(&leds_yellow, 1) < 0) { + LOG_ERR("red yellow failed"); + } + if(gpio_pin_set_dt(&leds_green, 1) < 0) { + LOG_ERR("red green failed"); + } + + k_msleep(1000); + + if(gpio_pin_set_dt(&leds_red, 0) < 0) { + LOG_ERR("red led failed"); + } + if(gpio_pin_set_dt(&leds_yellow, 0) < 0) { + LOG_ERR("yellow led failed"); + } + if(gpio_pin_set_dt(&leds_green, 0) < 0) { + LOG_ERR("green led failed"); + } +#endif +} + +int +gpio_led_init() +{ + int i; + for(i= 0; i< NUM_GPIO_LEDS; i++) { + LOG_INF("Configuring LED %d on pin %d", i, gpioleds[i].pin); + if(!device_is_ready(gpioleds[i].port)) { + LOG_ERR("gpio LED %d not ready", i); + return -1; + } + + if(gpio_pin_configure_dt(&gpioleds[i], GPIO_OUTPUT_ACTIVE) < 0) { + LOG_ERR("gpio LED %d configure failed", i); + return -1; + } + } + return 0; +} + +static gpio_color_zone_t mGpio_color_zone; +void +gpio_led_set_color_zone(gpio_color_zone_t zone) +{ + int i; + for(i = 0; i < GPIO_ZONE_SIZE; i++) { + mGpio_color_zone[i] = zone[1]; + } +} + +#if 0 +int +gpio_led_set_leds( int moisture_level) +{ + + LOG_INF("setting moisture to %d",moisture_level); + if (moisture_level >= mGpio_color_zone[4]) + { + gpio_pin_set_dt(leds.leds_green, LED_OFF); + gpio_pin_set_dt(leds.leds_yellow, LED_OFF); + gpio_pin_set_dt(leds.leds_red, LED_ON); + } + else if (moisture_level >= mGpio_color_zone[3]) + { + gpio_pin_set_dt(leds.leds_green, LED_OFF); + gpio_pin_set_dt(leds.leds_yellow, LED_ON); + gpio_pin_set_dt(leds.leds_red, LED_ON); + } + else if (moisture_level >= mGpio_color_zone[2] ) + { + gpio_pin_set_dt(leds.leds_green, LED_OFF); + gpio_pin_set_dt(leds.leds_yellow, LED_ON); + gpio_pin_set_dt(leds.leds_red, LED_OFF); + } + else if (moisture_level >= mGpio_color_zone[1] ) + { + gpio_pin_set_dt(leds.leds_green, LED_ON); + gpio_pin_set_dt(leds.leds_yellow, LED_ON); + gpio_pin_set_dt(leds.leds_red, LED_OFF); + } + else + { + gpio_pin_set_dt(leds.leds_green, LED_ON); + gpio_pin_set_dt(leds.leds_yellow, LED_OFF); + gpio_pin_set_dt(leds.leds_red, LED_OFF); + } + return 0; +} +#endif +#endif // CONFIG_GPIO_LED diff --git a/src/ky40.c b/src/ky40.c new file mode 100644 index 0000000..9ebe0b1 --- /dev/null +++ b/src/ky40.c @@ -0,0 +1,155 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + */ +#ifdef CONFIG_USE_KY40 + + +#include +#include +#include +# +/** + * @file ky40.c + * @brief KY-040 rotary encoder integration + * + * Handles Zephyr input events from a KY-040 rotary encoder and exposes + * a callback interface used by higher-level application code. + */ +#include +#include +#include "ky40.h" + +#include "sensors_logging.h" +#include +LOG_MODULE_DECLARE( SENSORS_LOG_MODULE_NAME, CONFIG_SENSORS_LOG_LEVEL ); + +/* + dials { + status = "okay"; + dial0:dial_0 { + compatible = "gpio-qdec"; + gpios = <&gpio0 25 0>, <&gpio0 26 0 >; + steps-per-period = <4>; + zephyr,axis = <0>; + sample-time-us = <2000>; + idle-timeout-ms = <200>; + }; + dial1:dial_1 { + compatible = gpio-qdec" + gpios = <&gpio0 16 0>, <&gpio0 17 0 >; + steps-per-period = <4>; + zephyr,axis = <0>; + sample-time-us = <2000>; + idle-timeout-ms = <200>; + }; + }; + buttons { + compatible = "gpio-keys"; + button0: button_0 { + gpios = < &gpio0 23 (GPIO_PULL_UP | GPIO_ACTIVE_LOW) >; + label = "button 0"; + }; + button1: button_1 { + gpios = < &gpio0 22 (GPIO_PULL_UP | GPIO_ACTIVE_LOW) >; + label = "button 1"; + }; + }; + zephyr,user { + dials = <&dial0 &dial1>; + }; + + +The configuration file should have the following +CONFIG_INPUT=y +CONFIG_INPUT_GPIO_QDEC=y +CONFIG_USER_KY40=y + */ + + + +#define ZEPHYR_USER zephyr_user +#define DT_DRV_COMPAT gpio_qdec + +#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \ + !DT_NODE_HAS_PROP(DT_PATH(zephyr_user), dials) +#error "No suitable devicetree overlay specified" +#endif +#define KY40_DT_SPEC_AND_COMMA(node_id, prop, idx) \ + DEVICE_DT_INST_GET(idx), + +static ky40_device_t dials[] = { + DT_FOREACH_PROP_ELEM(DT_PATH(ZEPHYR_USER), dials, KY40_DT_SPEC_AND_COMMA) +}; +static ky40_event_callback dial_callback = NULL; + +static void ky40_cb_handler(struct input_event *evt, int index) +{ + LOG_DBG("Got input event from %s", evt->dev->name); + LOG_DBG(" event type 0x%x code 0x%x value %d sync 0x%x", evt->type, evt->code, evt->value, evt->sync); + if(NULL != dial_callback) { + (*dial_callback)(evt, index); + } +} + + +#define KY40_DT_INIT(node_id, prop, index) \ + static void ky40_cb_handler_##index(struct input_event *evt, void * user_data) \ + { \ + ARG_UNUSED(user_data); \ + ky40_cb_handler(evt, index); \ + } \ + INPUT_CALLBACK_DEFINE(DEVICE_DT_INST_GET(index), ky40_cb_handler_##index, NULL); + + +DT_FOREACH_PROP_ELEM(DT_PATH(ZEPHYR_USER), dials, KY40_DT_INIT) + +#define NUM_DIALS ARRAY_SIZE(dials) + +// from input_gpio_qdec.c +struct gpio_qdec_data { + const struct device *dev; + struct k_timer sample_timer; + uint8_t prev_step; + int32_t acc; + struct k_work event_work; + struct k_work_delayable idle_work; + struct gpio_callback gpio_cb; + atomic_t polling; +}; + + +/* + * the KY-40 is a rotary encoder + * + */ +int ky40_init(ky40_event_callback func) +{ + for (size_t i = 0U; i < NUM_DIALS; i++) { + LOG_INF("Found %s API %p CONFIG %p ", dials[i]->name, dials[0]->api, dials[i]->config); + if (!device_is_ready(dials[i])) { + LOG_ERR("%s dial is not ready", dials[i]->name); + return -1; + } + + } + + dial_callback = func; + return NUM_DIALS; +} + +int ky40_get_rotation(int dev_num) +{ + if(dev_num >= NUM_DIALS) { + LOG_ERR("Invalid dial number %d", dev_num); + return -1; + } + struct gpio_qdec_data *data = dials[dev_num]->data; + + LOG_INF("Getting Rotation"); + LOG_INF("prev step 0x%x", data->prev_step); + LOG_INF("acc %d", data->acc); + + return data->acc; +} +#endif // CONFIG_USE_KY40 diff --git a/src/low_power.c b/src/low_power.c new file mode 100644 index 0000000..5a6c605 --- /dev/null +++ b/src/low_power.c @@ -0,0 +1,132 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + */ + +#ifdef CONFIG_USE_LOWPOWER + +#include +#/** + * @file low_power.c + * @brief Low power/sleep helper implementation + * + * Implements the small API used to configure and enter light or deep + * sleep modes on supported boards. + */ + +#include +#include +#include +#include + +#ifdef CONFIG_HAS_ESP_SLEEP +#include "esp_sleep.h" +#endif // CONFIG_HAS_ESP_SLEEP + +#include "low_power.h" +#include +LOG_MODULE_DECLARE( SENSORS_MODULE_NAME ); + +/* Most development boards have "boot" button attached to GPIO0. + * You can also change this to another pin. + */ +#define SW0_NODE DT_ALIAS(sw0) + +#if !DT_NODE_HAS_STATUS(SW0_NODE, okay) +#error "unsupported board: sw0 devicetree alias is not defined" +#endif // !DT_NODE_HAS_STATUS(SW0_NODE, okay) + +/* Add an extra delay when sleeping to make sure that light sleep + * is chosen. + */ +#define LIGHT_SLP_EXTRA_DELAY (50UL) + +static const struct gpio_dt_spec button = + GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0}); + +int +low_power_init() +{ + + LOG_DBG("Low power init"); + if (!device_is_ready(button.port)) { + LOG_ERR("Error: button device %s is not ready", button.port->name); + return -1; + } + + const int wakeup_level = (button.dt_flags & GPIO_ACTIVE_LOW) ? 0 : 1; + +#ifdef CONFIG_HAS_ESP_SLEEP + esp_gpio_wakeup_enable(button.pin, + wakeup_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL); + esp_sleep_enable_gpio_wakeup(); +#else +#error "low power not supported for board" +#endif // CONFIG_HAS_ESP_SLEEP + + /* Wait until GPIO goes high */ + if (gpio_pin_get_dt(&button) == wakeup_level) { + LOG_INF("Waiting for GPIO%d to go high...", button.pin); + do { + k_busy_wait(10000); + } while (gpio_pin_get_dt(&button) == wakeup_level); + } + + return 0; +} + +int +low_power_set(sleep_type_t sleep_type) +{ + if(sleep_type == DEEP_SLEEP) { + LOG_ERR("Deep sleep not supported"); + } + + + LOG_INF("Entering light sleep"); + /* To make sure the complete line is printed before entering sleep mode, + * need to wait until UART TX FIFO is empty + */ + k_busy_wait(10000); + + /* Get timestamp before entering sleep */ + int64_t t_before_ms = k_uptime_get(); + + /* Sleep triggers the idle thread, which makes the pm subsystem select some + * pre-defined power state. Light sleep is used here because there is enough + * time to consider it, energy-wise, worthy. + */ + k_sleep(K_USEC(DT_PROP(DT_NODELABEL(light_sleep), min_residency_us) + + LIGHT_SLP_EXTRA_DELAY)); + /* Execution continues here after wakeup */ + + /* Get timestamp after waking up from sleep */ + int64_t t_after_ms = k_uptime_get(); + + /* Determine wake up reason */ + const char *wakeup_reason; + +#ifdef CONFIG_HAS_ESP_SLEEP + switch (esp_sleep_get_wakeup_cause()) { + case ESP_SLEEP_WAKEUP_TIMER: + wakeup_reason = "timer"; + break; + case ESP_SLEEP_WAKEUP_GPIO: + wakeup_reason = "pin"; + break; + default: + wakeup_reason = "other"; + break; + } + +#else +#error "low power not supported for board" +#endif // CONFIG_HAS_ESP_SLEEP + + LOG_INF("Returned from light sleep, reason: %s, t=%lld ms, slept for %lld ms", + wakeup_reason, t_after_ms, (t_after_ms - t_before_ms)); + + return 0; + +} +#endif // CONFIG_USE_LOWPOWER diff --git a/src/meteorology.c b/src/meteorology.c new file mode 100644 index 0000000..689e917 --- /dev/null +++ b/src/meteorology.c @@ -0,0 +1,234 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + */ + +#ifdef CONFIG_USE_METEOROLOGY_SENSORS + +#include +#include +# +/** + * @file meteorology.c + * @brief Environmental sensors (temperature/humidity/pressure) + * + * Provides initialization and read helpers for environmental sensors + * used by the application. + */ +#include + +/** + * Meteorology.c - this file contains code to read data from atmospheric sensors (temperature, humidity, barametric pressure) + **/ + + + + +#include +LOG_MODULE_DECLARE( SENSORS_MODULE_NAME ); + +/* + * This file supports the bme280, bmp280, dht11 and dht22 + * + * This is a module for the BME280 barametric pressure sensor and the BME280 Humidity sensor + * The BMP280 is very similar to the BME280 so the BME280 driver will work + * The BMP280 supports both I2C and SPI + * To configure the sensors the boards/.overlay file should contain the following: + * For i2C + * &i2c0 { + * status = "okay"; + * pinctrl-0 = <&i2c0_default>; + * pinctrl-names = "default"; + * + * bme280@76 { + * compatible = "bosch,bme280"; + * status = "okay"; + * reg = <0x76>; + * }; + * }; + * For SPI + * + * This is a coniguration for an rpi_pico_w + * &pinctrl { + * pio0_spi0_default: pio0_spi0_default { + * group1 { + * pinmux = , , ; + * }; + * group2 { + * pinmux = ; + * input-enable; + * }; + * }; + * + * }; + * + * &pio0 { + * status = "okay"; + * + * pio0_spi0: pio0_spi0 { + * pinctrl-0 = <&pio0_spi0_default>; + * pinctrl-names = "default"; + * + * compatible = "raspberrypi,pico-spi-pio"; + * status = "okay"; + * }; + * }; + * + * &pio0_spi0 { + * status = "okay"; + * #address-cells = <1>; + * #size-cells = <0>; + * clocks = < &system_clk >; + * miso-gpios = <&gpio0 12 0>; + * cs-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; + * clk-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>; + * mosi-gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>; + * bme280@0 { + * compatible = "bosch,bme280"; + * reg = <0>; + * spi-max-frequency = <1000000>; + * }; + * }; + * + * + * + * To configure the sensor for the esp32 the boards/esp32.overlay should have something like : + * &spi3 { + * + * bme280@0 { + * compatible = "bosch,bme280"; + * reg = <0>; + * spi-max-frequency = <1000000>; + * }; + * }; + * + * The DHT22 and DHT11 temperature sensors provide both humidity and temperature readings + * + * To configure the sensors the boards/.overlay file should contain the following: + * If the sensor is a DHT11 the dht22 line should be removed + * / { + * dht22 { + * compatible = "aosong,dht"; + * status = "okay"; + * dio-gpios = <&gpio0 0 0>; + * dht22; + * }; + * }; + * + * The BMP388/BMP390L sensors provide pressure + * bmp388: bmp388@76 { + * compatible = "bosh, bmp388"; + * reg = <0x76> + * int-gpios=<&gpio0 2 GPIO_ACTIVE_LOW> + */ + +static const struct device *temp_dev; +static const struct device *humidity_dev; +static const struct device *pressure_dev; + +int meteorology_init() +{ + temp_dev = NULL; + humidity_dev = NULL; + pressure_dev = NULL; + + const struct device * dev = NULL; +#if defined(CONFIG_BME280) + dev = DEVICE_DT_GET_ANY(bosch_bme280); + temp_dev = dev; + humidity_dev = dev; + pressure_dev = dev; +#elif defined(CONFIG_DHT22) + dev = DEVICE_DT_GET_ONE(aosong_dht); + temp_dev = dev; + humidity_dev = dev; +#elif defined(CONFIG_BMP388) + dev = DEVICE_DT_GET_ANY(bosch_bmp388); + temp_dev = dev; + humidity_dev = dev; + pressure_dev = dev; +#elif defined (CONFIG_SHT4X) + dev = DEVICE_DT_GET_ANY(sensirion_sht4x); + temp_dev = dev; + humidity_dev = dev; +#elif defined(CONFIG_BME680) + dev = DEVICE_DT_GET_ONE(bosch_bme680); + temp_dev = dev; + humidity_dev = dev; + pressure_dev = dev; + +#else + #error "no sensors specified " +#endif + + if (dev == NULL) { + /* No such node, or the node does not have status "okay". */ + LOG_ERR("Error: no weather sensor device found."); + return -1; + } + + if (!device_is_ready(dev)) { + LOG_ERR("Error: Device \"%s\" is not ready; " + "check the driver initialization logs for errors.", + dev->name); + LOG_ERR("init %d init_res %d",dev->state->initialized, dev->state->init_res); + return -1; + } + + LOG_INF("Found device \"%s\"", dev->name); + return 0; +} + +static double meteorology_get_sensor(const struct device * dev, enum sensor_channel chan) +{ + double rc = -1.0; + struct sensor_value value; + int err; + do { + if(NULL == dev) { + LOG_ERR("No sensor device"); + break; + } + err = sensor_sample_fetch(dev); + if(0 > err) { + LOG_ERR("%s sample fetch failed %d",dev->name, err); + break; + } + err = sensor_channel_get(dev, chan, &value); + if(0 > err) { + LOG_ERR("%s channel %d get failed %d", dev->name, chan, err); + break; + } + rc = sensor_value_to_double(&value); + } while(0); + return rc; +} + +double meteorology_get_temperature() +{ + double rc = -1.0; + if(NULL != temp_dev) { + rc = meteorology_get_sensor(temp_dev, SENSOR_CHAN_AMBIENT_TEMP); + } + return rc; +} + +double meteorology_get_humidity() +{ + double rc = -1.0; + if(NULL != humidity_dev) { + rc = meteorology_get_sensor(humidity_dev, SENSOR_CHAN_HUMIDITY); + } + return rc; +} + +double meteorology_get_pressure() +{ + double rc = -1.0; + if(NULL != pressure_dev) { + rc = meteorology_get_sensor(pressure_dev, SENSOR_CHAN_PRESS); + } + return rc; +} + +#endif // CONFIG_USER_METEOROLOGY_SENSOR diff --git a/src/motion_sensor.c b/src/motion_sensor.c new file mode 100644 index 0000000..1e0c854 --- /dev/null +++ b/src/motion_sensor.c @@ -0,0 +1,144 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + */ +#ifdef CONFIG_USE_MOTION_SENSOR + + +#include +#include +#include + +#include +#include "motion_sensor.h" + +#include +#include "sensors_logging.h" +LOG_MODULE_DECLARE( SENSORS_LOG_MODULE_NAME, CONFIG_SENSORS_LOG_LEVEL ); + +/* + * This motion sensor supports the RCWL-0516 Microwave radar motion sensor and the AM312 infrared motion sensor + * to configure the sensor the boards/.overlay file shold have something like this + * / { + * motion_sensors { + * compatible = "gpio-keys"; + * motionsensor: motionsensor { + * gpios = < &gpio0 27 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH) >; + * label = "motion sensor"; + * }; + * }; + *}; + * for the esp32c3_devkitm the motion sensor is connected to GPIO 12 + * motion_sensors { + * compatible = "gpio-keys"; + * motionsensor: motionsensor { + * gpios = < &gpio0 12 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH) >; + * label = "motion sensor"; + * }; + * }; + * + * The motion sensor is configured via Kconfig. Your Kconfig file should contain this + * config USE_MOTION_SENSOR + * bool "Use the motion sensor" + * default n + * help + * Configure a motion sensor for your project + * The prj.conf file of your application must contain + * CONFIG_USE_MTION_SENSOR=y + * + * motion_sensor_init is called to initialize the motion sensor. the paramter handler is a + * pointer to a function to handle callbacks when motion is detected, it may be NULL + */ + +static const struct gpio_dt_spec motion_sensor = GPIO_DT_SPEC_GET(DT_NODELABEL(motionsensor), gpios); +static struct gpio_callback motion_cb_data; +static motion_sensor_handler_t motion_callback; + +static void motion_cooldown(struct k_work * work) +{ + if (NULL != motion_callback) { + motion_callback(); + } +} + +K_WORK_DELAYABLE_DEFINE(motion_work, motion_cooldown); +/** + * @brief GPIO interrupt handler - schedule debounce worker + * + * This is the GPIO callback invoked by Zephyr when the motion sensor + * GPIO asserts. It schedules a delayed work item (`motion_work`) which + * invokes the user-provided callback after a short debounce interval. + * + * @param dev GPIO device pointer + * @param cb Pointer to the gpio_callback structure + * @param pins Bitmask of pins that triggered the callback + */ +static void motion_sensed(const struct device * dev, + struct gpio_callback * cb, + uint32_t pins) +{ + /* LOG_DBG("motion_sensor pressed pin 0x%x", pins); */ + /* Debounce: schedule the work to run after 15 ms */ + k_work_reschedule(&motion_work, K_MSEC(15)); +} + + +/** + * @brief Initialize motion sensor + * + * Configures the motion sensor GPIO and interrupt, and registers the + * internal callback which will schedule the debounce worker. The + * optional `handler` is invoked after the debounce period when motion + * is confirmed. + * + * @param handler Optional user callback for motion events (may be NULL) + * @return 0 on success, negative errno on failure + */ +int motion_sensor_init(motion_sensor_handler_t handler) +{ + int rc = 0; + LOG_INF("configure motion sensor"); +#ifdef CONFIG_MOTION_SENSOR_DEBOUNCE +#endif /* CONFIG_MOTION_SENSOR_DEBOUNCE */ + motion_callback = handler; + do { + if (!device_is_ready(motion_sensor.port)) { + LOG_ERR("Motion sensor device not ready"); + rc = -ENODEV; + break; + } + if (0 != gpio_pin_configure_dt(&motion_sensor, GPIO_INPUT)) { + LOG_ERR("Motion sensor failed to configure"); + rc = -EIO; + break; + } + if (0 != gpio_pin_interrupt_configure_dt(&motion_sensor, GPIO_INT_EDGE_TO_ACTIVE)) { + LOG_ERR("Motion sensor failed to configure interrupts"); + rc = -EIO; + } + gpio_init_callback(&motion_cb_data, &motion_sensed, BIT(motion_sensor.pin)); + gpio_add_callback(motion_sensor.port, &motion_cb_data); + + LOG_INF("motion sensor inited"); + } while (0); + + return rc; +} +/** + * @brief Set or clear the motion sensor callback + * + * Update the user callback invoked when motion is detected. Passing + * `NULL` disables the callback. + * + * Note: the function name in the header is `set_motion_sensor_callback`; + * this implementation keeps the original (misspelled) symbol for + * compatibility with existing callers. + * + * @param handler Callback function or NULL + */ +void +set_moition_sensor_callback(motion_sensor_handler_t handler) +{ + motion_callback = handler; +} +#endif // CONFIG_USE_MOTION_SENSOR diff --git a/src/pwmled.c b/src/pwmled.c new file mode 100644 index 0000000..7e6d4ba --- /dev/null +++ b/src/pwmled.c @@ -0,0 +1,315 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + */ + +#ifdef CONFIG_USE_PWM_LED + +#include +#include +# +/** + * @file pwmled.c + * @brief PWM LED implementation + * + * Implements PWM-driven LED control: period/pulse configuration and + * convenience helpers exposed by `pwmled.h`. + */ +#include + +#include +#include "pwmled.h" + +#include +#include "sensors_logging.h" + +LOG_MODULE_DECLARE( SENSORS_LOG_MODULE_NAME, CONFIG_SENSORS_LOG_LEVEL ); + +/* + * + * / { + * pwms { + * compatible = "pwm-leds"; + * pwmleds: pwmleds { + * pwms = <&ledc0 0 PWM_HZ(100) PWM_POLARITY_NORMAL>, + * <&ledc0 1 PWM_HZ(100) PWM_POLARITY_NORMAL>, + * <&ledc0 2 PWM_HZ(100) PWM_POLARITY_NORMAL>; + * }; + * }; + * }; + * + * &pinctrl { + * ledc0_default: ledc0_default { + * group1 { + * pinmux = , + * , + * ; + * output-enable; + * }; + * }; + * }; + * &ledc0 { + * pinctrl-0 = <&ledc0_default>; + * pinctrl-names = "default"; + * status = "okay"; + * #address-cells = <1>; + * #size-cells = <0>; + * channle0@0 { + * reg = <0x0>; + * timer = <0>; + * }; + * channle0@1 { + * reg = <0x1>; + * timer = <0>; + * }; + * channle0@2 { + * reg = <0x2>; + * timer = <0>; + * }; + * }; + * + * The pwm LED is enabbled by Kconfig. The application Kconfig should have the following entry: + * config USE_PWM_LED + * bool "Use PWM leds" + * default n + * help + * Use PWM rgb LEDS + * + * To enable PWM LEDS the application prj.conf or boards/.conf should have the following line: + * CONFIG_USE_PWM_LED=y + */ + +#define ZEPHYR_USER zephyr_user +#define DT_DRV_COMPAT pwmleds +#define ZEPHYR_USER_NODE DT_PATH(ZEPHYR_USER) +#define LED_PWM_NODE_ID DT_PROP(ZEPHYR_USER_NODE,pwmleds) +#define LED_PWM_DEVICE_ID DT_PARENT(LED_PWM_NODE_ID) +//#define PWMLED_DT_SPEC(node_id) PWM_DT_SPEC_GET(node_id), +/* +#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \ + !DT_NODE_HAS_PROP(DT_PATH(zephyr_user), pwmleds) +#error "No suitable devicetree overlay specified for pwmleds" +#endif +*/ + +#define PWMLED_DT_SPEC_GET_BY_IDX(node_id, prop, idx) PWM_DT_SPEC_GET_BY_IDX(node_id, idx) + +static const struct pwm_dt_spec pwmleds[] = { + DT_FOREACH_PROP_ELEM_SEP(LED_PWM_NODE_ID, pwms, PWMLED_DT_SPEC_GET_BY_IDX, (,)) +}; +// DT_FOREACH_PROP_ELEM_SEP(DT_NODELABEL(pwmleds), pwms, PWMLED_DT_SPEC_GET_BY_IDX, (,)) + +//GPIO_DT_SPEC_GET_BY_IDX + +#define NUMLEDS (sizeof(pwmleds)/sizeof(struct pwm_dt_spec)) +#define MIN_PERIOD PWM_SEC(1U) / 128U +#define MAX_PERIOD PWM_SEC(1U) +typedef struct _pwmled_properties { + uint32_t pulse; + uint32_t period; + uint32_t max_period; +}pwmled_properties_t; + +pwmled_properties_t pwmled_properties[NUMLEDS]; + +static inline bool +pwmled_valid_led(int led_num) +{ + bool rc = false; + if((led_num >= 0) && (led_num < NUMLEDS)) { + rc = true; + } + return rc; +} + + +int +pwmled_get_num_leds() +{ + return NUMLEDS; +} + +static uint32_t _pwmled_get_max_period(int led_num) +{ + uint32_t max_period = 0; + if(pwmled_valid_led(led_num)) { + /* + * In case the default MAX_PERIOD value cannot be set for + * some PWM hardware, decrease its value until it can. + * + * Keep its value at least MIN_PERIOD * 4 to make sure + * the sample changes frequency at least once. + */ + LOG_DBG("Calibrating for channel %d...", pwmleds[led_num].channel); + max_period = MAX_PERIOD; + while (pwm_set_dt(&pwmleds[led_num], max_period, max_period / 2U)) { + max_period /= 2U; + if (max_period < (4U * MIN_PERIOD)) { + LOG_ERR("Error: PWM device does not support a period at least %lu", + 4U * MIN_PERIOD); + return 0; + } + } + + LOG_DBG("Done calibrating; maximum/minimum periods %u/%lu nsec", max_period, MIN_PERIOD); + } + return max_period; +} + +int +pwmled_init() +{ + int num_leds = NUMLEDS; + int rc = 0; + int i; + + LOG_DBG("configure %d pwmleds", num_leds); + + for(i = 0; i < num_leds; i++) { + LOG_INF("Configuring pwmled %d on channel %d", i, pwmleds[i].channel); + if(!device_is_ready(pwmleds[i].dev)) { + LOG_ERR("pwm led %d is not ready", i); + rc = -1; + } + pwmled_properties[i].period = pwmleds[i].period; + pwmled_properties[i].max_period = _pwmled_get_max_period(i); + pwmled_properties[i].pulse = 0; + + } + return rc; +} + + +int +pwmled_post() +{ + + return 0; +} + + + +int +pwmled_set_pulse(int led_num, uint32_t pulse) +{ + const struct pwm_dt_spec *ps; + int rc = -1; + if(pwmled_valid_led(led_num)) { + ps = &pwmleds[led_num]; + pwmled_properties[led_num].pulse = pulse; + rc = pwm_set(ps->dev, ps->channel, pwmled_properties[led_num].period, pulse, ps->flags); + } + return rc; +} + +int +pwmled_set_period(int led_num, uint32_t period) +{ + const struct pwm_dt_spec *ps; + int rc = -1; + if(pwmled_valid_led(led_num)) { + ps = &pwmleds[led_num]; + if(period < pwmled_properties[led_num].max_period) { + period = pwmled_properties[led_num].max_period; + } + pwmled_properties[led_num].period = period; + rc = pwm_set(ps->dev, ps->channel, period, pwmled_properties[led_num].pulse, ps->flags); + } + return rc; +} + +int pwmled_set(int led_num, uint32_t period, uint32_t pulse) +{ + const struct pwm_dt_spec *ps; + int rc = -1; + if(pwmled_valid_led(led_num)) { + ps = &pwmleds[led_num]; + if(period < pwmled_properties[led_num].max_period) { + period = pwmled_properties[led_num].max_period; + } + pwmled_properties[led_num].pulse = pulse; + LOG_DBG("Setting pwm %d period %d pulse %d flags 0x%x", led_num, period, pulse, ps->flags); + rc = pwm_set(ps->dev, ps->channel, period, pulse, ps->flags); + } + return rc; +} + + +uint32_t +pwmled_get_pulse(int led_num) +{ + uint32_t rc = 0; + if(pwmled_valid_led(led_num)) { + rc = pwmled_properties[led_num].pulse; + } + return rc; +} +uint32_t +pwmled_get_period(int led_num) +{ + uint32_t rc = 0; + if(pwmled_valid_led(led_num)) { + rc = pwmled_properties[led_num].period; + } + return rc; +} +uint32_t +pwmled_get_max_period(int led_num) +{ + uint32_t rc = 0; + if(pwmled_valid_led(led_num)) { + rc = pwmled_properties[led_num].max_period; + } + return rc; +} + + + +static int mRGmax; +/* + *@brief + * set the maximum value used in Red/Green operations + * + *@param maximum The maximum value for red/green operations + */ +void +pwmled_set_rg_max(int maximum) +{ + LOG_WRN("pwmled_set_rg_max deprecated"); + mRGmax = maximum; + LOG_DBG("mRGmax %d", mRGmax); +} + + +int +pwmled_set_leds( int moisture_level) +{ + int rc; + int red_level; + int green_level; + rc = 0; + LOG_WRN("pwmled_set_leds deprecated"); + red_level = moisture_level; + green_level = mRGmax - red_level ; + + LOG_DBG("level %d red %d green %d", moisture_level, red_level, green_level); +#if 0 + rc = pwm_set_pulse_dt(&red_pwm_led, red_level); + if(rc) { + LOG_ERR("setting red pwmled failed %d",rc); + } + rc = pwm_set_pulse_dt(&green_pwm_led, green_level); + if(rc) { + LOG_ERR("setting green pwm led failed %d",rc); + } + rc = pwm_set_pulse_dt(&blue_pwm_led, red_level); + if(rc) { + LOG_ERR("setting blue pwm led failed %d",rc); + } +#endif + return rc; +} + + + +#endif // CONFIG_USE_PWM_LED diff --git a/src/ready_led.c b/src/ready_led.c index 6d04110..183c398 100644 --- a/src/ready_led.c +++ b/src/ready_led.c @@ -20,10 +20,13 @@ LOG_MODULE_DECLARE(SENSORS_LOG_MODULE_NAME, CONFIG_SENSORS_LOG_LEVEL); #ifdef CONFIG_USE_READY_LED /** * @file ready_led.c + * @brief the ready LED implementation * @details * The ready led is a LED used to show status. It is useful when a device is running * without a console. It is useually the led that is soldered to the PCB of the microprocessor * + * Implements the ready LED abstraction used to indicate device status +cd * (blink rates, color when supported) for board-level feedback. * The ready_led sensor supports 5 types of LEDs * 1) gpio LEDs: these are single color LEDs driven by a GPIO line (CONFIG_USE_READY_LED_GPIO) * 2) LED strips: These are one or more LEDs driven by a GPIO line, they are capable of multiple colors (CONFIG_USE_READY_LED_STRIP) @@ -31,6 +34,7 @@ LOG_MODULE_DECLARE(SENSORS_LOG_MODULE_NAME, CONFIG_SENSORS_LOG_LEVEL); * 4) LED Driver: This is support for the Zephyr LED driver. It support gpio LEDS and some leds strips the underlying driver must support the LED controller device (CONFIG_USE_READY_LED_CONTROLLER) * 5) Raspberry PI Pico W: This is a big of a hack to support the onboard LED on the Raspberry PI Pico W (CONFIG_USE_READY_LED_PICO_W) * + * * For GPIO ready leds * To configure the ready_led, the boards/\.overlay should look something like this: * @code diff --git a/src/sensor_log.c b/src/sensor_log.c index fa2450f..6ae4deb 100644 --- a/src/sensor_log.c +++ b/src/sensor_log.c @@ -7,3 +7,12 @@ #include "sensors_logging.h" /** @brief register the log for the sensors module */ LOG_MODULE_REGISTER(SENSORS_LOG_MODULE_NAME, CONFIG_SENSORS_LOG_LEVEL); + +# +/** + * @file sensor_log.c + * @brief Sensor logging helper implementation + * + * Implements helpers used by sensor modules to log or dump diagnostic + * information during development. + */ diff --git a/src/sevensegment.c b/src/sevensegment.c new file mode 100644 index 0000000..cd7c8cc --- /dev/null +++ b/src/sevensegment.c @@ -0,0 +1,398 @@ +/* + * Copyright Beechwoods Software, Inc. 2023 brad@beechwoods.com + * All Rights Reserved + */ + +#ifdef CONFIG_USE_SEVEN_SEGMENT_DISPLAY + +#include +#include +# +/** + * @file sevensegment.c + * @brief Seven-segment display implementation + * + * Implements the routines declared in `sevensegment.h` for controlling + * a seven-segment display module. + */ +#include +#include +#include +#include + +#include +#include "sensors_logging.h" +LOG_MODULE_DECLARE( SENSORS_LOG_MODULE_NAME, CONFIG_SENSORS_LOG_LEVEL ); + +#define SEGMENT_A 0x01 +#define SEGMENT_B 0x02 +#define SEGMENT_C 0x04 +#define SEGMENT_D 0x08 +#define SEGMENT_E 0x10 +#define SEGMENT_F 0x20 +#define SEGMENT_G 0x40 + + +#define SEG_OFF 0 +#define SEG_ON 1 + + +/* + * The seven segment display is defined as follows + * 1 + * ____ + * | | + * 32| |2 + * | 64 | + * ____ + * | | + * 16| | 4 + * | 8 | + * ____ + */ +uint8_t numeric_segment_table[] = { + 0x3F, //0 + 0x06, //1 + 0x5B, //2 + 0x4F, //3 + 0x66, //4 + + 0x6D, //5 + 0x7C, //6 + 0x07, //7 + 0x7F, //8 + 0x67, //9 + 0x77, //A (10) + 0x7C, //b (11) + 0x39, //C (12) + 0x5E, //d (13) + 0x79, //E (14) + 0x71 //F (15) +}; + +typedef struct char_map { + char alpha; + uint8_t map; +}char_map_t; + +#define NUM_ALPHA 9 +char_map_t alpha_segment_table [] = { + { 'b', 0x7C }, + { 'u', 0x1C }, + { 't', 0x78 }, + { 'i', 0x30 }, + { 'm', 0x55 }, + { 'c', 0x58 }, + { 'y', 0x6E }, + { 'd', 0x5E }, + { 'r', 0x51 } +}; + +#define NUM_ELEMENTS 4 +char elements[NUM_ELEMENTS] = {0}; + +int sevensegment_set_text(char * text) +{ + memset(elements, '\0', NUM_ELEMENTS); + strncpy(elements, text, NUM_ELEMENTS); + for(int i = 0; i< 4; i++) { + if(elements[i] < 16) { + LOG_DBG("7S set to %d",elements[i]); + } else { + LOG_DBG("7S set to %c",elements[i]); + } + } + return 0; +} +static int _7s_cur_element = 0; + +int sevensegment_set_element(int element) +{ + if((element < 0) || (element >= NUM_ELEMENTS)) { + LOG_ERR("Bad element %d", element); + return -1; + } + _7s_cur_element = element; + return 0; +} + +int sevensegment_set_internal(int val); + +static const struct gpio_dt_spec segment_a = GPIO_DT_SPEC_GET(DT_NODELABEL(segment_a),gpios); +static const struct gpio_dt_spec segment_b = GPIO_DT_SPEC_GET(DT_NODELABEL(segment_b),gpios); +static const struct gpio_dt_spec segment_c = GPIO_DT_SPEC_GET(DT_NODELABEL(segment_c),gpios); +static const struct gpio_dt_spec segment_d = GPIO_DT_SPEC_GET(DT_NODELABEL(segment_d),gpios); +static const struct gpio_dt_spec segment_e = GPIO_DT_SPEC_GET(DT_NODELABEL(segment_e),gpios); +static const struct gpio_dt_spec segment_f = GPIO_DT_SPEC_GET(DT_NODELABEL(segment_f),gpios); +static const struct gpio_dt_spec segment_g = GPIO_DT_SPEC_GET(DT_NODELABEL(segment_g),gpios); +#ifdef CONFIG_USE_FOUR_ELEMENT_SEVEN_SEGMENT_DISPLAY +static const struct gpio_dt_spec cell_select_1 = GPIO_DT_SPEC_GET(DT_NODELABEL(cell_select_1),gpios); +static const struct gpio_dt_spec cell_select_2 = GPIO_DT_SPEC_GET(DT_NODELABEL(cell_select_2),gpios); +static const struct gpio_dt_spec cell_select_3 = GPIO_DT_SPEC_GET(DT_NODELABEL(cell_select_3),gpios); +static const struct gpio_dt_spec cell_select_4 = GPIO_DT_SPEC_GET(DT_NODELABEL(cell_select_4),gpios); + + +static const struct gpio_dt_spec * cell_selects[4] = { + &cell_select_1, + &cell_select_2, + &cell_select_3, + &cell_select_4 +}; +#endif + +void +sevensegment_post() +{ + LOG_INF("sevensegment post"); + if(gpio_pin_set_dt(&segment_a, 1) <0) { + LOG_ERR("segment_a failed"); + } + if(gpio_pin_set_dt(&segment_b, 1) <0) { + LOG_ERR("segment_b failed"); + } + if(gpio_pin_set_dt(&segment_c, 1) <0) { + LOG_ERR("segment_c failed"); + } + if(gpio_pin_set_dt(&segment_d, 1) <0) { + LOG_ERR("segment_d failed"); + } + if(gpio_pin_set_dt(&segment_e, 1) <0) { + LOG_ERR("segment_e failed"); + } + if(gpio_pin_set_dt(&segment_f, 1) <0) { + LOG_ERR("segment_f failed"); + } + if(gpio_pin_set_dt(&segment_g, 1) <0) { + LOG_ERR("segment_g failed"); + } + if(gpio_pin_set_dt(&segment_a, 0) <0) { + LOG_ERR("segment_a failed"); + } + if(gpio_pin_set_dt(&segment_b, 0) <0) { + LOG_ERR("segment_b failed"); + } + if(gpio_pin_set_dt(&segment_c, 0) <0) { + LOG_ERR("segment_c failed"); + } + if(gpio_pin_set_dt(&segment_d, 0) <0) { + LOG_ERR("segment_d failed"); + } + if(gpio_pin_set_dt(&segment_e, 0) <0) { + LOG_ERR("segment_e failed"); + } + if(gpio_pin_set_dt(&segment_f, 0) <0) { + LOG_ERR("segment_f failed"); + } + if(gpio_pin_set_dt(&segment_g, 0) <0) { + LOG_ERR("segment_g failed"); + } + +} + +int +sevensegment_init() +{ + int rc = 0; +#ifdef CONFIG_USE_FOUR_ELEMENT_SEVEN_SEGMENT_DISPLAY + LOG_INF("configure four element seven segment display\n"); + if(!device_is_ready(cell_select_1.port)) { + rc = -1; + } + if(!device_is_ready(cell_select_2.port)) { + rc = -1; + } + if(!device_is_ready(cell_select_3.port)) { + rc = -1; + } + if(!device_is_ready(cell_select_4.port)) { + rc = -1; + } + +#endif + LOG_INF("Configure seven segment display\n"); + if(!device_is_ready(segment_a.port)) { + rc = -1; + } + if(!device_is_ready(segment_b.port)) { + rc = -1; + } + if(!device_is_ready(segment_c.port)) { + rc = -1; + } + if(!device_is_ready(segment_d.port)) { + rc = -1; + } + if(!device_is_ready(segment_e.port)) { + rc = -1; + } + if(!device_is_ready(segment_f.port)) { + rc = -1; + } + if(!device_is_ready(segment_g.port)) { + rc = -1; + } + if(rc <0 ){ + return rc; + } +#ifdef CONFIG_USE_FOUR_ELEMENT_SEVEN_SEGMENT_DISPLAY + + if(gpio_pin_configure_dt(&cell_select_1,GPIO_OUTPUT_INACTIVE) < 0) { + rc = -1; + } + if(gpio_pin_configure_dt(&cell_select_2,GPIO_OUTPUT_INACTIVE) < 0) { + rc = -1; + } + if(gpio_pin_configure_dt(&cell_select_3,GPIO_OUTPUT_INACTIVE) < 0) { + rc = -1; + } + if(gpio_pin_configure_dt(&cell_select_4,GPIO_OUTPUT_INACTIVE) < 0) { + rc = -1; + } +#endif + if(gpio_pin_configure_dt(&segment_a,GPIO_OUTPUT_ACTIVE) < 0) { + rc = -1; + } + if(gpio_pin_configure_dt(&segment_b,GPIO_OUTPUT_ACTIVE) < 0) { + rc = -1; + } + if(gpio_pin_configure_dt(&segment_c,GPIO_OUTPUT_ACTIVE) < 0) { + rc = -1; + } + if(gpio_pin_configure_dt(&segment_d,GPIO_OUTPUT_ACTIVE) < 0) { + rc = -1; + } + if(gpio_pin_configure_dt(&segment_e,GPIO_OUTPUT_ACTIVE) < 0) { + rc = -1; + } + if(gpio_pin_configure_dt(&segment_f,GPIO_OUTPUT_ACTIVE) < 0) { + rc = -1; + } + if(gpio_pin_configure_dt(&segment_g,GPIO_OUTPUT_ACTIVE) < 0) { + rc = -1; + } + LOG_DBG("svn OK"); + return rc; + +} + +int +sevensegment_set_segment(int segment, bool on) +{ + int rc; + uint8_t val; + const struct gpio_dt_spec * pdt; + switch(segment) { + case 0: + case '0': + case 'a': + case 'A': + pdt= &segment_a; + val = SEGMENT_A; + break; + case 1: + case '1': + case 'b': + case 'B': + pdt = &segment_b; + val = SEGMENT_B; + break; + case 2: + case '2': + case 'c': + case 'C': + pdt = &segment_c; + val = SEGMENT_C; + break; + + default: + LOG_ERR("Bad segment descriptor 0x%x", segment); + return -1; + } + rc = gpio_pin_set_dt(pdt, val); + return rc; +} + +int +sevensegment_set_int(int number) +{ + uint8_t val; + if(number > 0x0F) { + return -1; + } + val = numeric_segment_table[number]; + elements[_7s_cur_element] = number; + LOG_DBG("svn %d val %x",number,val); + return(sevensegment_set_internal(val)); +} +int sevensegment_set_internal(int val) +{ + int rc = 0; + + rc = gpio_pin_set_dt(&segment_a, val & SEGMENT_A); + + rc |= gpio_pin_set_dt(&segment_b, val & SEGMENT_B); + + rc |= gpio_pin_set_dt(&segment_c, val & SEGMENT_C); + + rc |= gpio_pin_set_dt(&segment_d, val & SEGMENT_D); + + rc |= gpio_pin_set_dt(&segment_e, val & SEGMENT_E); + + rc |= gpio_pin_set_dt(&segment_f, val & SEGMENT_F); + + rc |= gpio_pin_set_dt(&segment_g, val & SEGMENT_G); + + return rc; +} + +#ifdef CONFIG_USE_FOUR_ELEMENT_SEVEN_SEGMENT_DISPLAY +/* size of stack area used by each thread */ +#define STACKSIZE 1024 +/* scheduling priority used by each thread */ +#define PRIORITY 7 +int sevensegment_get_alpha(char alpha) +{ + int i; + int val = 0; + for(i = 0; i < NUM_ALPHA; i++) { + if(alpha == alpha_segment_table[i].alpha) { + val = alpha_segment_table[i].map; + break; + } + } + return val; +} + +#define ELEMENT_SLEEP 1000 + +void scan(void) +{ + int i; + int val; + while(1) { + for( i = 0; i < 4;i++) { + + if (elements[i] < 16) { + val = numeric_segment_table[(int)elements[i]]; + } else { + val = sevensegment_get_alpha(elements[i]); + } + // LOG_DBG("7s %d 0x%x 0x%x", i, elements[i], val); + if(sevensegment_set_internal(val)) { + LOG_ERR("seting 7s disp failed"); + } + gpio_pin_set_dt(cell_selects[i], SEG_ON); + + k_msleep(ELEMENT_SLEEP); + gpio_pin_set_dt(cell_selects[i], SEG_OFF); + // k_msleep(ELEMENT_SLEEP); + } + } +} +K_THREAD_DEFINE(scan_id, STACKSIZE, scan, NULL, NULL, NULL, + PRIORITY, 0, 0); + +#endif //CONFIG_USE_FOUR_ELEMENT_SEVEN_SEGMENT_DISPLAY + +#endif // CONFIG_USE_SEVEN_SEGMENT_DISPLAY + diff --git a/src/stripled.c b/src/stripled.c new file mode 100644 index 0000000..41eb4e3 --- /dev/null +++ b/src/stripled.c @@ -0,0 +1,137 @@ +/* + * Copyright Beechwoods Software, Inc. 2025 brad@beechwoods.com + * All Rights Reserved + */ +#include +#include +#include +#include +#include "sensors_logging.h" +# +/** + * @file stripled.c + * @brief LED strip driver implementation + * + * Implements the high-level control functions for an addressable RGB + * LED strip (initialization, per-pixel color setting and display). + */ +LOG_MODULE_DECLARE( SENSORS_LOG_MODULE_NAME, CONFIG_SENSORS_LOG_LEVEL ); + +#ifdef CONFIG_USE_STRIP_LED +/* + * A strip LED is a set of one or more LEDS driven by a single GPIO. The strip LED uses I2C or SPI to + * send an array of 4 octet commands. Each element in the array corrosponds to one LED. + * / { + * zephyr,user { + * stripleds = <&led_strip> + * }; + * }; + * &spi2 { + * #address-cells = <1>; + * #size-cells = <0>; + * status = "okay"; + * pinctrl-0 = <&spim2_default>; + * pinctrl-names = "default"; + * + * line-idle-low; + * status = "okay"; + * + * led_strip: ws2812@0 { + * compatible = "worldsemi,ws2812-spi"; + * + * reg = <0>; + * spi-max-frequency = <6400000>; + * + * chain-length = <8>; + * spi-cpha; + * spi-one-frame = <0xf0>; + * spi-zero-frame = <0xc0>; + * color-mapping = ; + * }; + * }; + * &pinctrl { + * spim2_default: spim2_default { + * group2 { + * pinmux = ; + * output-low; + * }; + * }; + * }; + * + */ + +#define ZEPHYR_USER zephyr_user +#define LED_USER_NAME stipleds +#define ZEPHYR_USER_NODE DT_PATH(ZEPHYR_USER) + +#define LED_NODE_ID DT_PROP(ZEPHYR_USER_NODE, stripleds ) +#define LED_DEVICE_ID DT_PARENT(DT_PROP(ZEPHYR_USER_NODE, LED_USER_NAME )) +#if DT_NODE_HAS_PROP(LED_NODE_ID, chain_length) +#else +#error Unable to determine length of LED strip x LED_NODE_ID x +#endif + + +#define RGB(_r, _g, _b) { .r = (_r), .g = (_g), .b = (_b) } + +#define STRIP_NUM_PIXELS DT_PROP(LED_NODE_ID, chain_length) +static const struct device *const strip = DEVICE_DT_GET(LED_NODE_ID); + +static struct led_rgb pixels_off = RGB(0x00, 0x00, 0x00); +static struct led_rgb pixels_on = RGB(0xff, 0xff, 0xff) ; + +static struct led_rgb pixelbuffer[STRIP_NUM_PIXELS]; + + +int strip_led_init() +{ + if (!device_is_ready(strip)) { + LOG_ERR("LED strip deviec %s is not ready", strip->name); + return -1; + } + return 0; +} + +int strip_led_on() +{ + int i; + for(i = 0; i < STRIP_NUM_PIXELS; i++) { + pixelbuffer[i] = pixels_on; + } + return led_strip_update_rgb(strip, pixelbuffer, STRIP_NUM_PIXELS); +} + +int strip_led_off() +{ + int i; + for(i = 0; i < STRIP_NUM_PIXELS; i++) { + pixelbuffer[i] = pixels_off; + } + return led_strip_update_rgb(strip, pixelbuffer, STRIP_NUM_PIXELS); +} + +int strip_led_set_color(int pixel, uint8_t red, uint8_t green, uint8_t blue) +{ + pixels_on.r = red; + pixels_on.g = green; + pixels_on.b = blue; + + return 0; +} + +int strip_led_num_leds() +{ + return STRIP_NUM_PIXELS; +} + +int strip_led_display(struct led_rgb * pixels, size_t num_pixels) +{ + if((num_pixels > STRIP_NUM_PIXELS) || (num_pixels < 0)) { + return -EINVAL; + } + return led_strip_update_rgb(strip, pixels, num_pixels); +} + +#endif // CONFIG_USE_STRIP_LED