diff --git a/drivers/Kconfig b/drivers/Kconfig index 145f9f7d35..eead0d8c2e 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -42,6 +42,7 @@ rsource "ds3231/Kconfig" rsource "ds3234/Kconfig" rsource "edbg_eui/Kconfig" rsource "io1_xplained/Kconfig" +rsource "pcf857x/Kconfig" rsource "tps6274x/Kconfig" rsource "uart_half_duplex/Kconfig" rsource "usbdev_mock/Kconfig" diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index de294701c4..0940945c31 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -113,6 +113,10 @@ ifneq (,$(filter nrf24l01p_ng_%,$(USEMODULE))) USEMODULE += nrf24l01p_ng endif +ifneq (,$(filter pcf857%,$(USEMODULE))) + USEMODULE += pcf857x +endif + ifneq (,$(filter periph_ptp_timer periph_ptp_speed_adjustment,$(FEATURES_USED))) FEATURES_REQUIRED += periph_ptp endif diff --git a/drivers/include/pcf857x.h b/drivers/include/pcf857x.h new file mode 100644 index 0000000000..e93f0c89c1 --- /dev/null +++ b/drivers/include/pcf857x.h @@ -0,0 +1,600 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * 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. + */ + +/** + * @defgroup drivers_pcf857x PCF857X I2C I/O expanders + * @ingroup drivers_misc + * @ingroup drivers_saul + * @brief Device driver for Texas Instruments PCF857X I2C I/O expanders + * + * + * \section Device driver for Texas Instruments PCF857X I2C I/O expanders + * + * ## Overview + * + * Texas Instruments PCF857X I2C I/O expanders provide general purpose I/O + * extension via I2C bus. The driver supports the following PCF857X I2C I/O + * expander variants: + * + *
+ * Expander | Type | Pseudomodule to be used + * :--------|:------------------------|:----------------------- + * PCF8574 | 8-bit I2C I/O expander | `pcf8574` + * PCF8574A | 8-bit I2C I/O expander | `pcf8574a` + * PCF8575 | 16-bit I2C I/O expander | `pcf8575` + *

+ * + * For each of these PCF857X I2C I/O expanders variants, the driver defines + * a separate pseudomodule. Multiple PCF857X I2C I/O expanders and different + * variants can be used at the same time. Either the board definition or the + * application must specify used PCF857X I/O expander variants by a list of + * used pseudomodules. For example, to use a PCF8574A and a PCF8575 I/O + * expander in one application, the make command would be: + * + * USEMODULE="pcf8574a pcf8575" make -C tests/driver_pcf857x BOARD=... + * + * At least one PCF857X I2C I/O expander variant has to be specified. The + * driver module `pcf857x` is then enabled implicitly. + * + * @note While PCF8575 is working in I2C fast mode with up to 400 kHz clock + * frequency, PCF8574 and PCF8574A are only specified for I2C normal mode + * with up to 100 kHz clock frequency. However, they seem also to work at + * 400 kHz clock frequency. + * + * The driver interface is kept as compatible as possible with the peripheral + * GPIO interface. The only differences are that + * + * - functions have the prefix `pcf857x_` and + * - functions require an additional parameter, the pointer to the expander + * device of type #pcf857x_t. + * + * ## Defined pseudomodules + * + * The functionality of the driver is controlled by the use of pseudomodules. + * The following pseudomodules are defined: + *
+ * Pseudomoule | Functionality + * :--------------------|:--------------------- + * `pcf8574` | support of PCF8574 enabled + * `pcf8574a` | support of PCF8574A enabled + * `pcf8575` | support of PCF8575 enabled + * `pcf857x_irq` | support of interrupts enabled with medium event priority + * `pcf857x_irq_low` | support of interrupts enabled with low event priority + * `pcf857x_irq_medium` | support of interrupts enabled with medium event priority + * `pcf857x_irq_low` | support of interrupts enabled with high event priority + *

+ * + * @note At least one of the modules `pcf8574`, `pcf8574a` and `pcf8575` has to + * be used. + * + * ## Expander GPIOs + * + * The PCF857X expander devices provide a GPIO expansion over the I2C + * interface with either + * + * - 1 port with 8 quasi-parallel input/output (I/O) pins (PCF8574, PCF8574A) or + * - 2 ports with 8 quasi-parallel input/output (I/O) pins each (PCF8575). + * + * Each quasi-bidirectional expander I/O pin can be used as an input or output + * without the use of a data-direction control signal. Output pins are latched + * and have high-current drive capability for directly driving LEDs. + * The quasi-bidirectional expander I/O pins without direction control + * work as follows: + * + * - INPUT: + * Writing 1 to an expander pin configures the pin as an input, which is + * pulled up to HIGH by a very weak 100 μA pull-up. When reading the pin, + * its value then depends on the actual voltage. This corresponds to the + * behavior of the #GPIO_IN_PU mode. + * - OUTPUT: + * Writing 0 to an expander pin configures the pin as an output and + * actively drives the pin to LOW. This corresponds to the behavior of + * the #GPIO_OD_PU mode. + * + * @note Since the expander I/O pins are quasi-bidirectional without direction + * control, the only actively driven level is the output LOW. Therefore the + * driver physically supports only the modes #GPIO_IN_PU and #GPIO_OD_PU. + * The other logically identical modes #GPIO_IN, #GPIO_OUT and #GPIO_OD are + * emulated. Please keep this in mind when connecting these pins to other + * open-drain output pins that do not generate active signals. The #GPIO_IN_PD + * mode is not supported. + * + * After the initialization with the #pcf857x_init function, all + * expander I/O pins are in input mode and pulled-up to HIGH. + * + * The expander I/O pins can be addressed as GPIO pins using the following + * scheme: + * + *
+ * PCF857X pin label | Port | Pin | RIOT symbol | Remark + * ----------------- |:----:|:---:|:--------------------------|:----------------- + * P00 | 0 | 0 | `PCF857X_GPIO_PIN(0, 0)` | PCF8574, PCF8574A and PCF8575 + * P01 | 0 | 1 | `PCF857X_GPIO_PIN(0, 1)` | PCF8574, PCF8574A and PCF8575 + * ... | ... | ... | ... | ... + * P07 | 0 | 7 | `PCF857X_GPIO_PIN(0, 7)` | PCF8574, PCF8574A and PCF8575 + * P10 | 0 | 8 | `PCF857X_GPIO_PIN(0, 8)` | PCF8575 only + * P11 | 0 | 9 | `PCF857X_GPIO_PIN(0, 9)` | PCF8575 only + * ... | ... | ... | ... | ... + * P17 | 0 | 15 | `PCF857X_GPIO_PIN(0, 15)` | PCF8575 only + *
+ * + * ## Interrupts + * + * PCF857X expanders have an open-drain, low-active interrupt (INT) signal, + * which generates an interrupt by any rising or falling edge of the expander + * pins in the input mode. Using this expander interrupt signal, the following + * features become available: + * + * - An interrupt service function can be attached to an expander input pin with + * the #pcf857x_gpio_init_int function. This interrupt service function is + * then called on any rising and/or falling edge of the expander input pin. + * + * - In addition, the driver uses the interrupt on changes of an expander + * input pin to internally maintain the current state of all expander + * input pins. Using this internal current state of the expander input + * pins avoids reading all expander input pins via I2C every time the input + * value of a single expander GPIO pin is read with #pcf857x_gpio_read. + * + * Since interrupts are handled in the context of a separate event thread (see + * section [The Interrupt Context Problem](#pcf857x_interrupt_context_problem)) + * enabling interrupts requires more RAM. Therefore interrupts have to be + * explicitly enabled with the module `pcf857x_irq_`. + * `priority` can be one of `low`, `medium` or `highest`, which correspond to + * the priority of the event thread that processes the interrupts. If only the + * module `pcf857x_irq` is used without specifying the priority, the interrupt + * handling is enabled with a medium priority of the event thread. For more + * information on the priorities check the @ref sys_event module. + * + * Furthermore, the MCU GPIO pin to which the PCF857X `INT` signal is + * connected has to be defined by the default configuration parameter + * #PCF857X_PARAM_INT_PIN (pcf857x_params_t::int_pin) either in the + * configuration parameter file or at the command line, for example: + * + * CFLAGS="-DPCF857X_PARAM_INT_PIN=\(GPIO_PIN\(0,6\)\)" \ + * USEMODULE="pcf8575 pcf857x_irq_medium" make -C tests/driver_pcf857x BOARD=... + * + *
+ * @note If an output of the expander is connected to an input of the same + * expander, there is no interrupt triggered by the input when the + * output changes. Therefore, a write operation to an output with + * #pcf857x_gpio_write, #pcf857x_gpio_clear, #pcf857x_gpio_set or + * #pcf857x_gpio_toggle leads to an additional read-after-write operation, + * if interrupts are used.

+ * The use of interrupts therefore increases the read performance considerably, + * since I2C read operations are required only when the inputs change. But the + * write performance is reduced to the half. + * + * ## The Interrupt Context Problem {#pcf857x_interrupt_context_problem} + * + * Handling an interrupt of a PCF857x expander requires the driver to access + * the device directly via I2C. However, the mutex-based synchronization of + * I2C accesses does not work in the interrupt context. Therefore the ISR must + * not access the PCF857x expander device directly. Rather, the ISR must only + * indicate the occurrence of the interrupt which has to be handled + * asynchronously in thread context. + * + * For this purpose an event thread module is used when interrupts are + * enabled by the module `pcf857x_irq_`. The driver then + * handles the interrupts in the context of the event thread with given + * `priority`. For more information on the priorities check + * the @ref sys_event module. + * + * ## SAUL Capabilities + * + * The driver provides SAUL capabilities that are compatible to the SAUL + * capabilities of peripheral GPIOs. Each PCF857X expander I/O pin can be + * mapped directly to SAUL by defining an according entry in + * \c PCF857X_SAUL_GPIO_PARAMS. Please refer file + * `$RIOTBASE/drivers/pcf857x/include/pcf857x_params.h` for an example. + * + * @note Module `saul_gpio` has to be added to the + * project to enable SAUL capabilities of the PCF857X driver, e.g.: + * + * USEMODULE="pcf8575 saul_gpio" make -C tests/saul BOARD=... + * + * ## Using Multiple Devices + * + * It is possible to use multiple devices and different variants of PCF857X + * I/O expanders at the same time. Either the board definition or the + * application must specify used PCF857X I/O expander variants by a list of + * used pseudomodules. For example, to use a PCF8574A and a PCF8575 I/O + * expander in one application, the make command would be: + * + * USEMODULE="pcf8574a pcf8575" make -C tests/driver_pcf857x BOARD=... + * + * Furthermore, used devices have to be configured by defining the + * configuration parameter set `pcf857x_params` of type #pcf857x_params_t. + * The default configuration for one device is defined in + * `drivers/pcf857x/pcf857x_params.h`. Either the board definition or the + * application can override it by placing a file `pcf857x_params.h` in the + * board definition directory or the application directory `$APPDIR`. + * For example, the definition of the configuration parameter array for the + * two devices above could be: + * + * static const pcf857x_params_t pcf857x_params[] = { + * { + * .dev = I2C_DEV(0), + * .addr = 0, + * .exp = PCF857X_EXP_PCF8574A, + * .int_pin = GPIO_PIN(0,1), + * }, + * { + * .dev = I2C_DEV(0), + * .addr = 0, + * .exp = PCF857X_EXP_PCF8575, + * .int_pin = GPIO_PIN(0,2), + * }, + * }; + * + * @{ + * + * @author Gunar Schorcht + * @file + */ + +#ifndef PCF857X_H +#define PCF857X_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +#include "kernel_defines.h" +#include "periph/gpio.h" +#include "periph/i2c.h" + +#if IS_USED(MODULE_SAUL_GPIO) || DOXYGEN +#include "saul/periph.h" +#endif /* MODULE_SAUL_GPIO */ + +#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN +#include "event.h" +#endif /* MODULE_PCF857X_IRQ */ + +#if !IS_USED(MODULE_PCF8574) && !IS_USED(MODULE_PCF8574A) && !IS_USED(MODULE_PCF8575) +#error "Please provide a list of pcf857x variants used by the application (pcf8574, pcf8574a or pcf8575)" +#endif + +/** + * @name PCF857X I2C slave addresses + * + * PCF857X I2C slave addresses are defined as an offset to a base address, + * which depends on the expander used. The address offset is in the range + * of 0 to 7. + * @{ + */ +#define PCF8575_BASE_ADDR (0x20) /**< PCF8575 I2C slave base address. + Addresses are then in range from + 0x20 to 0x27 */ +#define PCF8574_BASE_ADDR (0x20) /**< PCF8574 I2C slave base address. + Addresses are then in range from + 0x20 to 0x27 */ +#define PCF8574A_BASE_ADDR (0x38) /**< PCF8574A I2C slave base address. + Addresses are then in range from + 0x38 to 0x3f */ +/** @} */ + +/** + * @name PCF857X I/O expander pin number + * @{ + */ +#define PCF8575_GPIO_PIN_NUM (16) /**< PCF8575 has 16 I/O pins */ +#define PCF8574_GPIO_PIN_NUM (8) /**< PCF8574 has 8 I/O pins */ +#define PCF8574A_GPIO_PIN_NUM (8) /**< PCF8574A has 8 I/O pins */ +/** @} */ + +/** conversion of (port x : pin y) to a pin number */ +#define PCF857X_GPIO_PIN(x,y) ((gpio_t)((x << 3) | y)) + +/** + * @name Module dependent definitions and declarations + * @{ + */ +#if IS_USED(MODULE_PCF8575) || DOXYGEN + +/** + * @brief Maximum number of GPIO pins + * + * Defines the maximum number of GPIO pins of all PCF857X I/O expanders + * used. If a PCF8575 is used, the maximum number is 16 I/O pins. + */ +#define PCF857X_GPIO_PIN_NUM (16) + +/** + * @brief Data type that can mask all expander pins + * + * If a PCF8575 is used, the 16 I/O pins have to be masked. + */ +typedef uint16_t pcf857x_data_t; + +#else /* MODULE_PCF8575 || DOXYGEN */ + +#define PCF857X_GPIO_PIN_NUM (8) /**< PCF8574, PCF8574 provide 8 I/O pins */ +typedef uint8_t pcf857x_data_t; /**< type that can mask all expander pins */ + +#endif /* MODULE_PCF8575 || DOXYGEN */ +/** @} */ + +/** Definition of PCF857X driver error codes */ +typedef enum { + PCF857X_OK, /**< success */ + PCF857X_ERROR_I2C, /**< I2C communication error */ + PCF857X_ERROR_INV_EXP, /**< invalid expander variant */ + PCF857X_ERROR_INV_MODE, /**< invalid pin mode */ + PCF857X_ERROR_INV_FLANK, /**< invalid interrupt flank */ + PCF857X_ERROR_INT_PIN, /**< interrupt pin initialization failed */ +} pcf857x_error_codes_t; + +/** + * @brief Definition of PCF857X expander variants + * + * It is used in configuration parameters to specify the PCF857X expander + * used by device. + * + * @note Expander variants known by the driver depend on enabled pseudomodules + * `pcf8574`, `pcf8574a` and `pcf8575`. + */ +typedef enum { +#if IS_USED(MODULE_PCF8574) || DOXYGEN + PCF857X_EXP_PCF8574, /**< PCF8574 8 bit I/O expander used */ +#endif +#if IS_USED(MODULE_PCF8574A) || DOXYGEN + PCF857X_EXP_PCF8574A, /**< PCF8574A 8 bit I/O expander */ +#endif +#if IS_USED(MODULE_PCF8575) || DOXYGEN + PCF857X_EXP_PCF8575, /**< PCF8575 16 bit I/O expander */ +#endif + PCF857X_EXP_MAX, +} pcf857x_exp_t; + +/** + * @brief PCF857X device initialization parameters + */ +typedef struct { + + i2c_t dev; /**< I2C device (default I2C_DEV(0)) */ + uint16_t addr; /**< I2C slave address offset to the PCF7857X base + address (default 0) */ + pcf857x_exp_t exp; /**< PCF857X expander variant used by the device + (default depends on used pseudomodules */ + +#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN + gpio_t int_pin; /**< MCU GPIO pin or #GPIO_UNDEF if not used (default). + Using interrupt pin has the advantage that inputs + have to be read from expander only if any input + value changes. + @note To use interrupts for expander inputs, this + pin has to be defined. */ +#endif /* MODULE_PCF857X_IRQ */ +} pcf857x_params_t; + +#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN +/** + * @brief IRQ event type + * + * Handling an interrupt of a PCF857x expander requires the driver to access + * the device directly via I2C. However, the mutex-based synchronization of + * I2C accesses does not work in the interrupt context. Therefore the ISR must + * not access the PCF857x expander device directly. Rather, the ISR must only + * indicate the occurrence of the interrupt which has to be handled + * asynchronously in the thread context. + * + * The type defines the data structure that is part of each device data + * structure to indicate that an interrupt of the device occurred. Since there + * is only one interrupt source, only one interrupt can be pending per device. + * Thus, only one object of this type per device is required. + */ +typedef struct { + event_t event; /**< inherited event data structure */ + void *dev; /**< PCF857X device reference */ +} pcf857x_irq_event_t; + +#endif /* MODULE_PCF857X_IRQ */ + +/** + * @brief PCF857X device data structure type + */ +typedef struct { + pcf857x_params_t params; /**< device initialization parameters */ + + uint8_t pin_num; /**< number of I/O pins, depends on used expander + variant */ + pcf857x_data_t modes; /**< expander pin modes */ + pcf857x_data_t in; /**< expander input pin values */ + pcf857x_data_t out; /**< expander output pin values */ + +#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN + gpio_isr_ctx_t isr[PCF857X_GPIO_PIN_NUM]; /**< ISR with arg for each expander pin */ + gpio_flank_t flank[PCF857X_GPIO_PIN_NUM]; /**< interrupt flank for each expander pin */ + bool enabled[PCF857X_GPIO_PIN_NUM]; /**< enabled flag for each expander pin */ + pcf857x_irq_event_t irq_event; /**< IRQ event object used for the device */ +#endif /* MODULE_PCF857X_IRQ */ + +} pcf857x_t; + +#if IS_USED(MODULE_SAUL_GPIO) || DOXYGEN +/** + * @brief PCF857X configuration structure for mapping expander pins to SAUL + * + * This data structure is an extension of the GPIO configuration structure for + * mapping GPIOs to SAUL. The only additional information required is a + * reference to the according PCF857X device. + * + * @note To use PCF857X with SAUL, module `saul_gpio` has to be added to the + * project. + */ +typedef struct { + unsigned int dev; /**< PCF857X device index */ + saul_gpio_params_t gpio; /**< GPIO configuration for mapping to SAUL */ +} pcf857x_saul_gpio_params_t; +#endif + +/** + * @brief Initialize the PCF857X I/O expander + * + * All expander pins are set to be input and are pulled up. + * + * @param[in] dev descriptor of PCF857X I/O expander device + * @param[in] params configuration parameters, see #pcf857x_params_t + * + * @pre If the interrupt handling is enabled by one of the modules + * `pcf857x_irq*`, the MCU GPIO pin for the interrupt signal + * has to be defined by the default configuration parameter + * #PCF857X_PARAM_INT_PIN (pcf857x_params_t::int_pin). + * + * @retval PCF857X_OK on success + * @retval PCF857X_ERROR_* a negative error code on error, + * see #pcf857x_error_codes_t + */ +int pcf857x_init(pcf857x_t *dev, const pcf857x_params_t *params); + +/** + * @brief Initialize a PCF857X pin + * + * @param[in] dev descriptor of PCF857X I/O expander device + * @param[in] pin pin to initialize, use PCF857X_GPIO_PIN(x,y) to specify + * @param[in] mode mode of the pin, see #gpio_t + * + * @note + * - Since the expander I/O pins are quasi-bidirectional without direction + * control, the only actively driven level is the output LOW. Therefore + * the driver physically supports only the modes #GPIO_IN_PU and + * #GPIO_OD_PU. The other logically identical modes #GPIO_IN, #GPIO_OUT + * and #GPIO_OD are emulated. For the #GPIO_IN_PU mode the function returns + * with #PCF857X_ERROR_INV_MODE. + * - After initialization in #GPIO_OUT mode the pin is actively driven LOW, + * after initialization in all other modes the pin is pulled-up to HIGH. + * + * @retval PCF857X_OK on success + * @retval PCF857X_ERROR_* a negative error code on error, + * see #pcf857x_error_codes_t + */ +int pcf857x_gpio_init(pcf857x_t *dev, gpio_t pin, gpio_mode_t mode); + +#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN +/** + * @brief Initialize a PCF857X pin for external interrupt usage + * + * The registered callback function will be called in interrupt context every + * time the defined flank(s) are detected. Therefore, it MUST NOT be blocking + * or time-consuming. + * + * The interrupt is activated automatically after the initialization. + * + * @pre The MCU GPIO pin for the interrupt signal has to be defined by the + * default configuration parameter #PCF857X_PARAM_INT_PIN + * (pcf857x_params_t::int_pin). + * + * @note + * - This function is only available if interrupt handling is enabled by one + * of the modules `pcf857x_irq*` + * - Since the expander I/O pins are quasi-bidirectional without direction + * control, the only actively driven level is the output LOW. Therefore + * the driver physically supports only the modes #GPIO_IN_PU and + * #GPIO_OD_PU. The other logically identical modes #GPIO_IN, #GPIO_OUT + * and #GPIO_OD are emulated. For the #GPIO_IN_PU mode the function returns + * with #PCF857X_ERROR_INV_MODE. + * - After initialization in #GPIO_OUT mode the pin is actively driven LOW, + * after initialization in all other modes the pin is pulled-up to HIGH. + * + * @param[in] dev descriptor of PCF857X I/O expander device + * @param[in] pin pin to initialize, use PCF857X_GPIO_PIN(x,y) to specify + * @param[in] mode mode of the pin, see #gpio_t + * @param[in] flank define the active flanks, see #gpio_flank_t + * @param[in] isr ISR that is called back from interrupt context + * @param[in] arg optional argument passed to the callback + * + * @retval PCF857X_OK on success + * @retval PCF857X_ERROR_* a negative error code on error, + * see #pcf857x_error_codes_t + */ +int pcf857x_gpio_init_int(pcf857x_t *dev, gpio_t pin, + gpio_mode_t mode, + gpio_flank_t flank, + gpio_cb_t isr, + void *arg); +#endif /* MODULE_PCF857X_IRQ || DOXYGEN */ + +/** + * @brief Get the value from PCF857X input pin + * + * @note If the PCF857X interrupt is used, the read operation does not perform + * an I2C read operation since the last input pin value is already read. + * + * @param[in] dev descriptor of PCF857X I/O expander device + * @param[in] pin pin to read, use PCF857X_GPIO_PIN(x,y) to specify + */ +int pcf857x_gpio_read(pcf857x_t *dev, gpio_t pin); + +/** + * @brief Write the value to PCF857X input pin + * + * @param[in] dev descriptor of PCF857X I/O expander device + * @param[in] pin pin to write, use PCF857X_GPIO_PIN(x,y) to specify + * @param[in] value value to write + */ +void pcf857x_gpio_write(pcf857x_t *dev, gpio_t pin, int value); + +/** + * @brief Clear the PCF857X output pin + * + * @param[in] dev descriptor of PCF857X I/O expander device + * @param[in] pin pin to clear, use PCF857X_GPIO_PIN(x,y) to specify + */ +void pcf857x_gpio_clear(pcf857x_t *dev, gpio_t pin); + +/** + * @brief Set the PCF857X output pin + * + * @param[in] dev descriptor of PCF857X I/O expander device + * @param[in] pin pin to set, use PCF857X_GPIO_PIN(x,y) to specify + */ +void pcf857x_gpio_set(pcf857x_t *dev, gpio_t pin); + +/** + * @brief Toggle the value of the PCF857X output pin + * + * @param[in] dev descriptor of PCF857X I/O expander device + * @param[in] pin pin to toggle, use PCF857X_GPIO_PIN(x,y) to specify + */ +void pcf857x_gpio_toggle(pcf857x_t *dev, gpio_t pin); + +#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN +/** + * @brief Enable pin interrupt + * + * @note This function is only available if interrupt handling is enabled + * by one of the modules `pcf857x_irq*` + * + * @param[in] dev descriptor of PCF857X I/O expander device + * @param[in] pin pin to enable the interrupt for + */ +void pcf857x_gpio_irq_enable(pcf857x_t *dev, gpio_t pin); + +/** + * @brief Disable pin interrupt + * + * @note This function is only available if interrupt handling is enabled + * by one of the modules `pcf857x_irq*` + * + * @param[in] dev descriptor of PCF857X I/O expander device + * @param[in] pin pin to enable the interrupt for + */ +void pcf857x_gpio_irq_disable(pcf857x_t *dev, gpio_t pin); +#endif /* MODULE_PCF857X_IRQ || DOXYGEN */ + +#ifdef __cplusplus +} +#endif + +#endif /* PCF857X_H */ +/** @} */ diff --git a/drivers/pcf857x/Kconfig b/drivers/pcf857x/Kconfig new file mode 100644 index 0000000000..f60a2ab858 --- /dev/null +++ b/drivers/pcf857x/Kconfig @@ -0,0 +1,65 @@ +# Copyright (c) 2021 Gunar Schorcht +# +# 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. +# + +menuconfig MODULE_PCF857X + bool "PCF857x Remote I/O Expander for I2C Bus" + depends on HAS_PERIPH_GPIO + depends on HAS_PERIPH_I2C + depends on TEST_KCONFIG + select MODULE_PERIPH_GPIO + select MODULE_PERIPH_I2C + help + Driver for Texas Instruments PCF857X I2C I/O expanders. + The driver supports the PCF8574, PCF8574A, and PCF8575 variants. + Select the variants used by your application. + +if MODULE_PCF857X + +config MODULE_PCF8574 + bool "PCF8574 Remote 8-Bit I/O is used" + +config MODULE_PCF8574A + bool "PCF8574A Remote 8-Bit I/O is used" + +config MODULE_PCF8575 + bool "PCF8575 Remote 16-Bit I/O is used" + default y + +config MODULE_PCF857X_IRQ + bool "Interrupt support for PCF857x I/O Expander pins" + depends on MODULE_PCF857X + depends on HAS_PERIPH_GPIO_IRQ + select MODULE_PERIPH_GPIO_IRQ + select MODULE_EVENT + select MODULE_EVENT_THREAD + help + To use the IRQs the MODULE_EVENT_THREAD symbol should be set. + +choice + bool "Thread priority" + depends on MODULE_PCF857X_IRQ + default MODULE_PCF857X_IRQ_MEDIUM + help + To process IRQs an event thread is used. The MODULE_EVENT_THREAD + symbol should be set. Choose a priority for the thread that + processes the IRQs. The default is medium priority. + +config MODULE_PCF857X_IRQ_LOW + bool "Low" + select MODULE_EVENT_THREAD_LOW + +config MODULE_PCF857X_IRQ_MEDIUM + bool "Medium" + select MODULE_EVENT_THREAD_MEDIUM + +config MODULE_PCF857X_IRQ_HIGHEST + bool "Highest" + select MODULE_EVENT_THREAD_HIGHEST + +endchoice + +endif # MODULE_PCF857X diff --git a/drivers/pcf857x/Makefile b/drivers/pcf857x/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/pcf857x/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/pcf857x/Makefile.dep b/drivers/pcf857x/Makefile.dep new file mode 100644 index 0000000000..090294112c --- /dev/null +++ b/drivers/pcf857x/Makefile.dep @@ -0,0 +1,19 @@ +FEATURES_REQUIRED += periph_gpio +FEATURES_REQUIRED += periph_i2c + +_PCF857X_IRQ_MODULE := $(filter pcf857x_irq_%,$(USEMODULE)) +ifneq (,$(_PCF857X_IRQ_MODULE)) + # pull in the correspondant event_thread_ module + USEMODULE += $(_PCF857X_IRQ_MODULE:pcf857x_irq_%=event_thread_%) + USEMODULE += pcf857x_irq +else + ifneq (,$(filter pcf857x_irq,$(USEMODULE))) + # pull in the pcf857x_irq_medium module as default if pcf857x_irq is used. + USEMODULE += pcf857x_irq_medium + endif +endif + +ifneq (,$(filter pcf857x_irq,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio_irq + USEMODULE += pcf857x +endif diff --git a/drivers/pcf857x/Makefile.include b/drivers/pcf857x/Makefile.include new file mode 100644 index 0000000000..bddc48997f --- /dev/null +++ b/drivers/pcf857x/Makefile.include @@ -0,0 +1,11 @@ +# include variants of PCF857X drivers as pseudo modules +PSEUDOMODULES += pcf8574 +PSEUDOMODULES += pcf8574a +PSEUDOMODULES += pcf8575 +PSEUDOMODULES += pcf857x_irq +PSEUDOMODULES += pcf857x_irq_low +PSEUDOMODULES += pcf857x_irq_medium +PSEUDOMODULES += pcf857x_irq_highest + +USEMODULE_INCLUDES_pcf857x := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_pcf857x) diff --git a/drivers/pcf857x/include/pcf857x_params.h b/drivers/pcf857x/include/pcf857x_params.h new file mode 100644 index 0000000000..b260cd0247 --- /dev/null +++ b/drivers/pcf857x/include/pcf857x_params.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * 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_pcf857x + * @brief Default configuration for Texas Instruments PCF857X I2C I/O expanders + * @author Gunar Schorcht + * @file + * @{ + */ + +#ifndef PCF857X_PARAMS_H +#define PCF857X_PARAMS_H + +#include "board.h" +#include "pcf857x.h" +#include "saul_reg.h" +#include "saul/periph.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Set default configuration parameters + * @{ + */ +#ifndef PCF857X_PARAM_DEV +/** device is I2C_DEV(0) */ +#define PCF857X_PARAM_DEV I2C_DEV(0) +#endif + +#ifndef PCF857X_PARAM_ADDR +/** I2C slave address offset is 0 */ +#define PCF857X_PARAM_ADDR (0) +#endif + +#ifndef PCF857X_PARAM_EXP +/** PCF857X expander variant used depends on enabled pseudomodules*/ +#if IS_USED(MODULE_PCF8575) || DOXYGEN +#define PCF857X_PARAM_EXP (PCF857X_EXP_PCF8575) +#elif IS_USED(MODULE_PCF8574) +#define PCF857X_PARAM_EXP (PCF857X_EXP_PCF8574) +#elif IS_USED(MODULE_PCF8574A) +#define PCF857X_PARAM_EXP (PCF857X_EXP_PCF8574A) +#endif +#endif /* PCF857X_PARAM_EXP */ + +#ifndef PCF857X_PARAM_INT_PIN +/** MCU interrupt pin */ +#define PCF857X_PARAM_INT_PIN (GPIO_UNDEF) +#endif + +#ifndef PCF857X_PARAMS +#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN +/** Default configuration parameter set */ +#define PCF857X_PARAMS { \ + .dev = PCF857X_PARAM_DEV, \ + .addr = PCF857X_PARAM_ADDR, \ + .exp = PCF857X_PARAM_EXP, \ + .int_pin = PCF857X_PARAM_INT_PIN, \ + }, +#else +#define PCF857X_PARAMS { \ + .dev = PCF857X_PARAM_DEV, \ + .addr = PCF857X_PARAM_ADDR, \ + .exp = PCF857X_PARAM_EXP, \ + }, +#endif +#endif /* PCF857X_PARAMS */ + +#ifndef PCF857X_SAUL_GPIO_PARAMS +/** Example for mapping expander pins to SAUL */ +#define PCF857X_SAUL_GPIO_PARAMS { \ + .dev = 0, \ + .gpio = { \ + .name = "P00 Output", \ + .pin = PCF857X_GPIO_PIN(0, 0), \ + .mode = GPIO_OUT, \ + .flags = SAUL_GPIO_INIT_CLEAR, \ + } \ + }, \ + { \ + .dev = 0, \ + .gpio = { \ + .name = "P01 Input", \ + .pin = PCF857X_GPIO_PIN(0, 1), \ + .mode = GPIO_IN, \ + .flags = 0, \ + } \ + }, +#endif +/**@}*/ + +/** + * @brief Allocate some memory to store the actual configuration + */ +static const pcf857x_params_t pcf857x_params[] = +{ + PCF857X_PARAMS +}; + +#if IS_USED(MODULE_SAUL_GPIO) || DOXYGEN +/** + * @brief Additional meta information to keep in the SAUL registry + */ +static const pcf857x_saul_gpio_params_t pcf857x_saul_gpio_params[] = +{ + PCF857X_SAUL_GPIO_PARAMS +}; +#endif /* MODULE_SAUL_GPIO || DOXYGEN */ + +#ifdef __cplusplus +} +#endif + +#endif /* PCF857X_PARAMS_H */ +/** @} */ diff --git a/drivers/pcf857x/pcf857x.c b/drivers/pcf857x/pcf857x.c new file mode 100644 index 0000000000..f7f4e8b224 --- /dev/null +++ b/drivers/pcf857x/pcf857x.c @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * 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_pcf857x + * @brief Device driver for Texas Instruments PCF857X I2C I/O expanders + * @author Gunar Schorcht + * @file + * @{ + */ + +#include +#include + +#include "pcf857x.h" + +#include "irq.h" +#include "log.h" +#include "thread.h" + +#if IS_USED(MODULE_PCF857X_IRQ) +#include "event/thread.h" +#endif + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#if ENABLE_DEBUG + +#define DEBUG_DEV(f, d, ...) \ + DEBUG("[pcf857x] %s i2c dev=%d addr=%02x: " f "\n", \ + __func__, d->params.dev, d->params.addr, ## __VA_ARGS__) + +#else /* ENABLE_DEBUG */ + +#define DEBUG_DEV(f, d, ...) + +#endif /* ENABLE_DEBUG */ + +#if IS_USED(MODULE_PCF857X_IRQ_LOW) +#define PCF857X_EVENT_PRIO EVENT_PRIO_LOWEST +#elif IS_USED(MODULE_PCF857X_IRQ_MEDIUM) +#define PCF857X_EVENT_PRIO EVENT_PRIO_MEDIUM +#elif IS_USED(MODULE_PCF857X_IRQ_HIGHEST) +#define PCF857X_EVENT_PRIO EVENT_PRIO_HIGHEST +#endif + +/** Forward declaration of functions for internal use */ + +static inline void _acquire(const pcf857x_t *dev); +static inline void _release(const pcf857x_t *dev); +static int _read(const pcf857x_t *dev, pcf857x_data_t *data); +static int _write(const pcf857x_t *dev, pcf857x_data_t data); + +#if IS_USED(MODULE_PCF857X_IRQ) + +/* interrutp service routine for IRQs */ +static void _irq_isr(void *arg); + +/* declaration of IRQ handler function */ +static void _irq_handler(event_t *event); + +/* internal update function */ +static void _update_state(pcf857x_t* dev); + +#endif /* MODULE_PCF857X_IRQ */ + +int pcf857x_init(pcf857x_t *dev, const pcf857x_params_t *params) +{ + /* some parameter sanity checks */ + assert(dev != NULL); + assert(params != NULL); + assert(params->exp < PCF857X_EXP_MAX); +#if IS_USED(MODULE_PCF857X_IRQ) + assert(gpio_is_valid(params->int_pin)); +#endif + + DEBUG_DEV("params=%p", dev, params); + + /* init device data structure */ + dev->params = *params; + + switch (params->exp) { +#if IS_USED(MODULE_PCF8574) + /**< PCF8574 8 bit I/O expander used */ + case PCF857X_EXP_PCF8574: dev->pin_num = PCF8574_GPIO_PIN_NUM; + dev->params.addr += PCF8574_BASE_ADDR; + break; +#endif +#if IS_USED(MODULE_PCF8574A) + /**< PCF8574A 8 bit I/O expander */ + case PCF857X_EXP_PCF8574A: dev->pin_num = PCF8574A_GPIO_PIN_NUM; + dev->params.addr += PCF8574A_BASE_ADDR; + break; +#endif +#if IS_USED(MODULE_PCF8575) + /**< PCF8575 16 bit I/O expander */ + case PCF857X_EXP_PCF8575: dev->pin_num = PCF8575_GPIO_PIN_NUM; + dev->params.addr += PCF8575_BASE_ADDR; + break; +#endif + default: return -PCF857X_ERROR_INV_EXP; + } + +#if IS_USED(MODULE_PCF857X_IRQ) + /* initialize the IRQ event object used for delaying interrupts */ + dev->irq_event.event.handler = _irq_handler; + dev->irq_event.dev = dev; + + for (unsigned i = 0; i < dev->pin_num; i++) { + dev->isr[i].cb = NULL; + dev->isr[i].arg = NULL; + dev->enabled[i] = false; + } + + /* initialize the interrupt pin */ + if (gpio_init_int(dev->params.int_pin, + GPIO_IN_PU, GPIO_FALLING, _irq_isr, (void*)dev)) { + return -PCF857X_ERROR_INT_PIN; + } +#endif /* MODULE_PCF857X_IRQ */ + + int res = PCF857X_OK; + + _acquire(dev); + + /* write 1 to all pins to switch them to INPUTS pulled up to HIGH */ + dev->out = ~0; + res |= _write(dev, dev->out); + + /* initial read all pins */ + res |= _read(dev, &dev->in); + + /* set all pin modes to INPUT and set internal output data to 1 (HIGH) */ + dev->modes = ~0; + + _release(dev); + + return PCF857X_OK; +} + +int pcf857x_gpio_init(pcf857x_t *dev, gpio_t pin, gpio_mode_t mode) +{ + /* some parameter sanity checks */ + assert(dev != NULL); + assert(pin < dev->pin_num); + + DEBUG_DEV("pin=%u mode=%u", dev, pin, mode); + + /* + * Since the LOW output is the only actively driven level possible with + * this expander, only in the case of GPIO_OUT we write a 0 to the pin + * to configure the pin as an output and actively drive it LOW. In all + * other modes, the pin is configured as an input and pulled-up to HIGH + * with the weak pull-up to emulate them. + */ + switch (mode) { + case GPIO_IN_PD: DEBUG_DEV("gpio mode GPIO_IN_PD not supported", dev, mode); + return -PCF857X_ERROR_INV_MODE; + case GPIO_OUT: dev->modes &= ~(1 << pin); /* set mode bit to 0 */ + dev->out &= ~(1 << pin); /* set output bit to 0 */ + break; + default: dev->modes |= (1 << pin); /* set mode bit to 1 */ + dev->out |= (1 << pin); /* set output bit to 1 */ + break; + } + + int res; + + /* write the mode */ + pcf857x_data_t data = dev->modes | dev->out; + _acquire(dev); + if ((res = _write(dev, data)) != PCF857X_OK) { + _release(dev); + return res; + } + +#if IS_USED(MODULE_PCF857X_IRQ) + /* reset the callback in case the port used external interrupts before */ + dev->isr[pin].cb = NULL; + dev->isr[pin].arg = NULL; + dev->enabled[pin] = false; + + /* + * If an output of the expander is connected to an input of the same + * expander, there is no interrupt triggered by the input when the + * output changes. + * Therefore, we have to read input pins after the write operation to + * update the input pin state in the device data structure and to trigger + * an ISR if necessary. + * + * @note _update_state releases the bus. + */ + _update_state(dev); +#else + /* read to update the internal input state */ + res = _read(dev, &dev->in); + _release(dev); +#endif + return res; +} + +#if IS_USED(MODULE_PCF857X_IRQ) +int pcf857x_gpio_init_int(pcf857x_t *dev, gpio_t pin, + gpio_mode_t mode, + gpio_flank_t flank, + gpio_cb_t isr, + void *arg) +{ + int res = PCF857X_OK; + + /* initialize the pin */ + if ((res = pcf857x_gpio_init(dev, pin, mode)) != PCF857X_OK) { + return res; + } + + switch (flank) { + case GPIO_FALLING: + case GPIO_RISING: + case GPIO_BOTH: dev->isr[pin].cb = isr; + dev->isr[pin].arg = arg; + dev->flank[pin] = flank; + dev->enabled[pin] = true; + break; + default: DEBUG_DEV("invalid flank %d for pin %d", dev, flank, pin); + return -PCF857X_ERROR_INV_FLANK; + } + + return PCF857X_OK; +} + +void pcf857x_gpio_irq_enable(pcf857x_t *dev, gpio_t pin) +{ + /* some parameter sanity checks */ + assert(dev != NULL); + assert(pin < dev->pin_num); + + DEBUG_DEV("pin=%u", dev, pin); + dev->enabled[pin] = true; +} + +void pcf857x_gpio_irq_disable(pcf857x_t *dev, gpio_t pin) +{ + /* some parameter sanity checks */ + assert(dev != NULL); + assert(pin < dev->pin_num); + + DEBUG_DEV("pin=%u", dev, pin); + dev->enabled[pin] = false; +} +#endif + +int pcf857x_gpio_read(pcf857x_t *dev, gpio_t pin) +{ + /* some parameter sanity checks */ + assert(dev != NULL); + assert(pin < dev->pin_num); + + DEBUG_DEV("pin=%u", dev, pin); + + /* + * If we use the interrupt, we always have an up-to-date input snapshot + * stored in the device data structure and which can be used directly. + * Otherwise we have to read the pins first. + */ +#if !IS_USED(MODULE_PCF857X_IRQ) + _acquire(dev); + _read(dev, &dev->in); + _release(dev); +#endif + return (dev->in &(1 << pin)) ? 1 : 0; +} + +void pcf857x_gpio_write(pcf857x_t *dev, gpio_t pin, int value) +{ + /* some parameter sanity checks */ + assert(dev != NULL); + assert(pin < dev->pin_num); + + DEBUG_DEV("pin=%u value=%d", dev, pin, value); + + /* set pin bit value */ + if (value) { + dev->out |= (1 << pin); + } + else { + dev->out &= ~(1 << pin); + } + + /* update pin values */ + pcf857x_data_t data = dev->modes | dev->out; + _acquire(dev); + _write(dev, data); +#if IS_USED(MODULE_PCF857X_IRQ) + /* + * If an output of the expander is connected to an input of the same + * expander, there is no interrupt triggered by the input when the + * output changes. + * Therefore, we have to read input pins after the write operation to + * update the input pin state in the device data structure and to trigger + * an ISR if necessary. + * + * @note _update_state releases the bus. + */ + _update_state(dev); +#else + _release(dev); +#endif +} + +void pcf857x_gpio_clear(pcf857x_t *dev, gpio_t pin) +{ + DEBUG_DEV("pin=%u", dev, pin); + return pcf857x_gpio_write(dev, pin, 0); +} + +void pcf857x_gpio_set(pcf857x_t *dev, gpio_t pin) +{ + DEBUG_DEV("pin=%u", dev, pin); + return pcf857x_gpio_write(dev, pin, 1); +} + +void pcf857x_gpio_toggle(pcf857x_t *dev, gpio_t pin) +{ + DEBUG_DEV("pin=%u", dev, pin); + return pcf857x_gpio_write(dev, pin, (dev->out & (1 << pin)) ? 0 : 1); +} + +/** Functions for internal use only */ + +#if IS_USED(MODULE_PCF857X_IRQ) + +/* interrupt service routine for IRQs */ +static void _irq_isr(void *arg) +{ + assert(arg != NULL); + + /* just indicate that an interrupt occurred and return */ + event_post(PCF857X_EVENT_PRIO, (event_t*)&((pcf857x_t*)arg)->irq_event); +} + +/* handle one IRQ event of device referenced by the event */ +static void _irq_handler(event_t* event) +{ + pcf857x_irq_event_t* irq_event = (pcf857x_irq_event_t*)event; + + assert(irq_event != NULL); + _acquire(irq_event->dev); + /* _update_state releases the bus */ + _update_state(irq_event->dev); +} + +/* + * @warning: It is expected that the I2C bus is already acquired when the + * function is called. However, it is released by this function + * before the function returns. + */ +static void _update_state(pcf857x_t* dev) +{ + assert(dev != NULL); + DEBUG_DEV("", dev); + + /* save old input values */ + pcf857x_data_t old_in = dev->in; + pcf857x_data_t new_in; + + /* read in new input values and release the bus */ + if (_read(dev, &dev->in)) { + _release(dev); + return; + } + _release(dev); + + new_in = dev->in; + + /* iterate over all pins to check whether ISR has to be called */ + for (unsigned i = 0; i < dev->pin_num; i++) { + pcf857x_data_t mask = 1 << i; + + /* + * if pin is input, interrupt is enabled, has an ISR registered + * and the input value changed + */ + if (((dev->modes & mask) != 0) && dev->enabled[i] && + (dev->isr[i].cb != NULL) && ((old_in ^ new_in) & mask)) { + /* check for the flank and the activated flank mode */ + if ((dev->flank[i] == GPIO_BOTH) || /* no matter what flank */ + ((new_in & mask) == 0 && /* falling flank */ + (dev->flank[i] == GPIO_FALLING)) || + ((new_in & mask) == mask && /* rising flank */ + (dev->flank[i] == GPIO_RISING))) { + + /* call the ISR */ + dev->isr[i].cb(dev->isr[i].arg); + } + } + } +} +#endif /* MODULE_PCF857X_IRQ */ + +static inline void _acquire(const pcf857x_t *dev) +{ + assert(dev != NULL); + i2c_acquire(dev->params.dev); +} + +static inline void _release(const pcf857x_t *dev) +{ + assert(dev != NULL); + i2c_release(dev->params.dev); +} + +static int _read(const pcf857x_t *dev, pcf857x_data_t *data) +{ + assert(dev != NULL); + assert(data != NULL); + + uint8_t bytes[2]; + size_t len = (dev->pin_num == 8) ? 1 : 2; + + int res = i2c_read_bytes(dev->params.dev, dev->params.addr, bytes, len, 0); + + if (res != 0) { + DEBUG_DEV("could not read data, reason %d (%s)", + dev, res, strerror(res * -1)); + return -PCF857X_ERROR_I2C; + } + + if (dev->pin_num == 8) { + *data = bytes[0]; + DEBUG_DEV("data=%02x", dev, *data); + } + else { + *data = (bytes[1] << 8) | bytes[0]; + DEBUG_DEV("data=%04x", dev, *data); + } + + return PCF857X_OK; +} + +static int _write(const pcf857x_t *dev, pcf857x_data_t data) +{ + assert(dev != NULL); + + uint8_t bytes[2]; + size_t len; + + if (dev->pin_num == 8) { + DEBUG_DEV("data=%02x", dev, data & 0xff); + + bytes[0] = data & 0xff; + len = 1; + } + else { + DEBUG_DEV("data=%04x", dev, data); + + bytes[0] = data & 0xff; + bytes[1] = data >> 8; + len = 2; + } + + int res = i2c_write_bytes(dev->params.dev, dev->params.addr, bytes, len, 0); + + if (res != 0) { + DEBUG_DEV("could not write data, reason %d (%s)", + dev, res, strerror(res * -1)); + return -PCF857X_ERROR_I2C; + } + + return PCF857X_OK; +} diff --git a/drivers/pcf857x/pcf857x_saul.c b/drivers/pcf857x/pcf857x_saul.c new file mode 100644 index 0000000000..84edc7f2a9 --- /dev/null +++ b/drivers/pcf857x/pcf857x_saul.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * 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_pcf857x + * @brief PCF857X adaption to the RIOT actuator/sensor interface + * @author Gunar Schorcht + * @file + */ +#if MODULE_SAUL_GPIO + +#include + +#include "saul.h" +#include "pcf857x.h" + +extern pcf857x_t pcf857x_devs[]; + +static int read(const void *dev, phydat_t *res) +{ + const pcf857x_saul_gpio_params_t *p = (const pcf857x_saul_gpio_params_t *)dev; + int inverted = (p->gpio.flags & SAUL_GPIO_INVERTED); + + res->val[0] = (pcf857x_gpio_read(&pcf857x_devs[p->dev], + p->gpio.pin)) ? !inverted : inverted; + res->unit = UNIT_BOOL; + res->scale = 0; + return 1; +} + +static int write(const void *dev, phydat_t *state) +{ + const pcf857x_saul_gpio_params_t *p = (const pcf857x_saul_gpio_params_t *)dev; + int inverted = (p->gpio.flags & SAUL_GPIO_INVERTED); + int value = (state->val[0] ? !inverted : inverted); + + pcf857x_gpio_write(&pcf857x_devs[p->dev], p->gpio.pin, value); + return 1; +} + +const saul_driver_t pcf857x_gpio_out_saul_driver = { + .read = read, + .write = write, + .type = SAUL_ACT_SWITCH +}; + +const saul_driver_t pcf857x_gpio_in_saul_driver = { + .read = read, + .write = saul_notsup, + .type = SAUL_SENSE_BTN +}; +#endif /* MODULE_SAUL_GPIO */ diff --git a/drivers/saul/init_devs/auto_init_pcf857x.c b/drivers/saul/init_devs/auto_init_pcf857x.c new file mode 100644 index 0000000000..9044e8fdd9 --- /dev/null +++ b/drivers/saul/init_devs/auto_init_pcf857x.c @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * 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_pcf857x + * @ingroup sys_auto_init_saul + * @brief Auto initialization of Texas Instruments PCF857X I2C I/O expanders + * @author Gunar Schorcht + * @file + * @{ + */ + +#if MODULE_PCF857X && MODULE_SAUL_GPIO + +#include "assert.h" +#include "log.h" + +#include "saul_reg.h" +#include "saul/periph.h" + +#include "pcf857x.h" +#include "pcf857x_params.h" + +/** + * @brief Number of configured PCF857X I/O expander devices + */ +#define PCF857X_NUM ARRAY_SIZE(pcf857x_params) + +/** + * @brief Number of configured SAUL PCF857X I/O pins + */ +#define PCF857X_SAUL_GPIO_NUMOF ARRAY_SIZE(pcf857x_saul_gpio_params) + +/** + * @brief Number of saul info + */ +#define PCF857X_INFO_NUM ARRAY_SIZE(pcf857x_saul_info) + +/** + * @brief Allocate the memory for the PCF857X I/O expander device descriptors + */ +pcf857x_t pcf857x_devs[PCF857X_NUM]; + +/** + * @brief Allocate the memory for PCF857X I/O expander SAUL registry entries + */ +static saul_reg_t pcf857x_saul_reg_entries[PCF857X_SAUL_GPIO_NUMOF]; + +/** + * @brief Reference the PCF857X I/O expander input mode driver struct + */ +extern saul_driver_t pcf857x_gpio_in_saul_driver; + +/** + * @brief Reference to the PCF857X I/O expander output mode driver struct + */ +extern saul_driver_t pcf857x_gpio_out_saul_driver; + +void auto_init_pcf857x(void) +{ + for (unsigned int i = 0; i < PCF857X_NUM; i++) { + LOG_DEBUG("[auto_init_saul] initializing PCF857X I/O expander dev #%u\n", i); + pcf857x_init(&pcf857x_devs[i], &pcf857x_params[i]); + } + + for (unsigned int i = 0; i < PCF857X_SAUL_GPIO_NUMOF; i++) { + const pcf857x_saul_gpio_params_t *p = &pcf857x_saul_gpio_params[i]; + + LOG_DEBUG("[auto_init_saul] initializing PCF857X GPIO #%u\n", i); + + /* check the PCF857X device index */ + assert(p->dev < PCF857X_NUM); + /* check the PCF857X device index */ + assert(p->gpio.pin < PCF857X_GPIO_PIN_NUM); + + pcf857x_saul_reg_entries[i].dev = (void *)p; + pcf857x_saul_reg_entries[i].name = p->gpio.name; + if ((p->gpio.mode == GPIO_IN) || + (p->gpio.mode == GPIO_IN_PD) || + (p->gpio.mode == GPIO_IN_PU)) { + pcf857x_saul_reg_entries[i].driver = &pcf857x_gpio_in_saul_driver; + } + else { + pcf857x_saul_reg_entries[i].driver = &pcf857x_gpio_out_saul_driver; + } + /* initialize the PCF857X pin */ + pcf857x_gpio_init(&pcf857x_devs[p->dev], p->gpio.pin, p->gpio.mode); + /* set initial pin PCF857X state if configured */ + if (p->gpio.flags & (SAUL_GPIO_INIT_CLEAR | SAUL_GPIO_INIT_SET)) { + phydat_t s; + s.val[0] = (p->gpio.flags & SAUL_GPIO_INIT_SET); + pcf857x_saul_reg_entries[i].driver->write(p, &s); + } + /* add to registry */ + saul_reg_add(&(pcf857x_saul_reg_entries[i])); + } +} +#else +typedef int dont_be_pedantic; +#endif /* MODULE_PCF857X && MODULE_SAUL_GPIO */ diff --git a/drivers/saul/init_devs/init.c b/drivers/saul/init_devs/init.c index ebacd494e8..e052c8410c 100644 --- a/drivers/saul/init_devs/init.c +++ b/drivers/saul/init_devs/init.c @@ -223,6 +223,10 @@ void saul_init_devs(void) extern void auto_init_pca9685(void); auto_init_pca9685(); } + if (IS_USED(MODULE_PCF857X)) { + extern void auto_init_pcf857x(void); + auto_init_pcf857x(); + } if (IS_USED(MODULE_PH_OEM)) { extern void auto_init_ph_oem(void); auto_init_ph_oem(); diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 753b1708f7..15bae104e2 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -174,6 +174,12 @@ NO_PSEUDOMODULES += netdev_ieee802154_submac # print ascii representation in function od_hex_dump() PSEUDOMODULES += od_string +# include variants of PCF857X drivers as pseudo modules +PSEUDOMODULES += pcf857x_irq +PSEUDOMODULES += pcf8574 +PSEUDOMODULES += pcf8574a +PSEUDOMODULES += pcf8575 + # add all pseudo random number generator variants as pseudomodules PSEUDOMODULES += prng_% diff --git a/tests/driver_pcf857x/Makefile b/tests/driver_pcf857x/Makefile new file mode 100644 index 0000000000..1e785fac83 --- /dev/null +++ b/tests/driver_pcf857x/Makefile @@ -0,0 +1,13 @@ +INCLUDES += -I$(APPDIR) + +include ../Makefile.tests_common + +ifeq (,$(filter pcf857%,$(USEMODULE))) + # default expander module + USEMODULE += pcf8575 +endif + +USEMODULE += shell +USEMODULE += benchmark + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_pcf857x/Makefile.ci b/tests/driver_pcf857x/Makefile.ci new file mode 100644 index 0000000000..1152ca53bc --- /dev/null +++ b/tests/driver_pcf857x/Makefile.ci @@ -0,0 +1,8 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + # diff --git a/tests/driver_pcf857x/README.md b/tests/driver_pcf857x/README.md new file mode 100644 index 0000000000..39f7f7e6c8 --- /dev/null +++ b/tests/driver_pcf857x/README.md @@ -0,0 +1,70 @@ +# Texas Instruments PCF857X I2C I/O expanders test application + +## Overview + +This test appliation demonstrates the usage of the PCF857X driver interface +and can be used to test each PCF857X expander I/O pin with shell commands. + +## Compilation + +To use the test application, compile it with one or more of the pseudomodules +`pcf8574`, `pcf8574a` or `pcf8575` to enable the driver for your +expander modules. Please check the default configuration parameters in +`$(RIOTBASE)/drivers/pcf857x/include/pcf857x_params.h` and modify them +if necessary. Alternatively, a modified version of this file could be +placed in the directory of this test application to override it. +``` +USEMODULE=pcf8575 make -C tests/driver_pcf857x BOARD=... +``` +**Please note:** When no pseudomodule is given, `pcf8575` is used by default. + +To use external interrupts with the expander I/O pins, the PCF857X +low-active open-drain interrupt signal has to be enabled. Add module +`pcf857x_irq` for this purpose and define the MCU interrupt pin by +parameter `PCF857X_PARAM_INT_PIN`, e.g. +``` +CFLAGS="-DPCF857X_PARAM_INT_PIN=\(GPIO_PIN\(0,6\)\)" \ +USEMODULE="pcf8575 pcf857x_irq" make -C tests/driver_pcf857x BOARD=... +``` +**Please note:** Since interrupts are handled in the context of a separate +event thread, enabling interrupts requires more RAM. + +## Usage + +The test allows to use commands as known from GPIO test application for +PCF857X expanders: +``` +> help +Command Description +--------------------------------------- +init_out init as output (push-pull mode) +init_in init as input w/o pull resistor +init_in_pu init as input with pull-up +init_od init as output (open-drain without pull resistor) +init_od_pu init as output (open-drain with pull-up) +init_int init as external INT w/o pull resistor +enable_int enable or disable gpio interrupt +read read pin status +set set pin to HIGH +clear set pin to LOW +toggle toggle pin +bench run a set of predefined benchmarks + +``` +The number of the first PCF857X expander port used by the test application +is defined by the macro `PCF857X_PORT_0`, which is 16 by default. This value +can be overridden during compilation, e.g.: +``` +CFLAGS="-DPCF857X_PORT_0=8" \ +USEMODULE=pcf8575 make -C tests/driver_pcf857x BOARD=... +``` +Using the port number defined by `PCF857X_PORT_0` and the following port +numbers, you can apply the command to the PCF857X expander ports. For +example, the following command initializes I/O pin 7 of the first PCF857X +expander: +``` +init_out 16 7 +``` +Commands with port numbers less than `PCF857X_PORT_0` refer to GPIO +peripheral ports. Thus, both the I/O pins of the PCF857X expanders as well +as the GPIO peripheral pins of the MCU can be addressed by all commands. diff --git a/tests/driver_pcf857x/app.config.test b/tests/driver_pcf857x/app.config.test new file mode 100644 index 0000000000..6366f1bfcf --- /dev/null +++ b/tests/driver_pcf857x/app.config.test @@ -0,0 +1,7 @@ +# this file enables modules defined in Kconfig. Do not use this file for +# application configuration. This is only needed during migration. +CONFIG_MODULE_PCF857X=y + +CONFIG_MODULE_SHELL=y +CONFIG_MODULE_BENCHMARK=y +CONFIG_ZTIMER_USEC=y diff --git a/tests/driver_pcf857x/main.c b/tests/driver_pcf857x/main.c new file mode 100644 index 0000000000..89a6c12610 --- /dev/null +++ b/tests/driver_pcf857x/main.c @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * 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 tests + * @brief Test application for Texas Instruments PCF857X I2C I/O expanders + * @author Gunar Schorcht + * @file + * + * ## Overview + * + * This test appliation demonstrates the usage of the PCF857X driver interface + * and can be used to test each PCF857X expander I/O pin with shell commands. + * + * The application bases on the test application for GPIO peripheral drivers + * which is under following copyright: + * + * Copyright (C) 2014,2017 Freie Universität Berlin + * @author Hauke Petersen + * + * ## Compilation + * + * To use the test application, compile it with one or more of the pseudomodules + * `pcf8574`, `pcf8574a` or `pcf8575` to enable the driver for your + * expander modules. Please check the default configuration parameters in + * `$(RIOTBASE)/drivers/pcf857x/include/pcf857x_params.h` and modify them + * if necessary. Alternatively, a modified version of this file could be + * placed in the directory of this test application to override it. + * ``` + * USEMODULE=pcf8575 make -C tests/driver_pcf857x BOARD=... + * ``` + * @note When no pseudomodule is given, `pcf8575` is used by default. + * + * To use external interrupts with the expander I/O pins, the PCF857X + * low-active open-drain interrupt signal has to be enabled. Add module + * `pcf857x_irq` for this purpose and define the MCU interrupt pin by + * parameter `PCF857X_PARAM_INT_PIN`, e.g. + * ``` + * CFLAGS="-DPCF857X_PARAM_INT_PIN=\(GPIO_PIN\(0,6\)\)" \ + * USEMODULE="pcf8575 pcf857x_irq" make -C tests/driver_pcf857x BOARD=... + * ``` + * @note Since interrupts are handled in the context of a separate event thread, + * enabling interrupts requires more RAM. + * + * ## Usage + * + * The test allows to use commands as known from GPIO test application for + * PCF857X expanders: + * ``` + * > help + * Command Description + * --------------------------------------- + * init_out init as output (push-pull mode) + * init_in init as input w/o pull resistor + * init_in_pu init as input with pull-up + * init_od init as output (open-drain without pull resistor) + * init_od_pu init as output (open-drain with pull-up) + * init_int init as external INT w/o pull resistor + * enable_int enable or disable gpio interrupt + * read read pin status + * set set pin to HIGH + * clear set pin to LOW + * toggle toggle pin + * bench run a set of predefined benchmarks + * ``` + * The number of the first PCF857X expander port used by the test application + * is defined by the macro `PCF857X_PORT_0`, which is 16 by default. This value + * can be overridden during compilation, e.g.: + * ``` + * CFLAGS="-DPCF857X_PORT_0=8" \ + * USEMODULE=pcf8575 make -C tests/driver_pcf857x BOARD=... + * ``` + * Using the port number defined by `PCF857X_PORT_0` and the following port + * numbers, you can apply the command to the PCF857X expander ports. For + * example, the following command initializes I/O pin 7 of the first PCF857X + * expander: + * ``` + * init_out 16 7 + * ``` + * Commands with port numbers less than `PCF857X_PORT_0` refer to GPIO + * peripheral ports. Thus, both the I/O pins of the PCF857X expanders as well + * as the GPIO peripheral pins of the MCU can be addressed by all commands. + */ + +#include +#include + +#include "pcf857x.h" +#include "pcf857x_params.h" + +#include "irq.h" +#include "shell.h" +#include "benchmark.h" + +#define BENCH_RUNS_DEFAULT (100UL * 100) + +/* Number of configured PCF857X I/O expander devices */ +#define PCF857X_NUM ARRAY_SIZE(pcf857x_params) + +/* Port number of the first PCF857X I/O expander device */ +#ifndef PCF857X_PORT_0 +#define PCF857X_PORT_0 (16) +#endif + +/* PCF857X devices allocation */ +pcf857x_t pcf857x_dev[PCF857X_NUM]; + +#ifdef MODULE_PCF857X_IRQ +static void cb(void *arg) +{ + printf("INT: external interrupt from pin %i\n", (int)arg); +} +#endif + +static int init_pin(int argc, char **argv, gpio_mode_t mode) +{ + if (argc < 3) { + printf("usage: %s \n", argv[0]); + return 1; + } + + int po = atoi(argv[1]); + int pi = atoi(argv[2]); + + if (po < PCF857X_PORT_0) { + gpio_init(GPIO_PIN(po, pi), mode); + } + else if (pcf857x_gpio_init(&pcf857x_dev[po - PCF857X_PORT_0], + pi, mode) < 0) { + printf("error: init PCF857X pin (dev %i, pin %02i) failed\n", po, pi); + return 1; + } + + return 0; +} + +static int init_out(int argc, char **argv) +{ + return init_pin(argc, argv, GPIO_OUT); +} + +static int init_in(int argc, char **argv) +{ + return init_pin(argc, argv, GPIO_IN); +} + +static int init_in_pu(int argc, char **argv) +{ + return init_pin(argc, argv, GPIO_IN_PU); +} + +static int init_od(int argc, char **argv) +{ + return init_pin(argc, argv, GPIO_OD); +} + +static int init_od_pu(int argc, char **argv) +{ + return init_pin(argc, argv, GPIO_OD_PU); +} + +#ifdef MODULE_PCF857X_IRQ +static int init_int(int argc, char **argv) +{ + gpio_mode_t mode = GPIO_IN; + gpio_flank_t flank; + int fl; + + if (argc < 4) { + printf("usage: %s \n", argv[0]); + puts("\tflank:\n" + "\t0: falling\n" + "\t1: rising\n" + "\t2: both\n"); + return 1; + } + + int po = atoi(argv[1]); + int pi = atoi(argv[2]); + + fl = atoi(argv[3]); + switch (fl) { + case 0: + flank = GPIO_FALLING; + break; + case 1: + flank = GPIO_RISING; + break; + case 2: + flank = GPIO_BOTH; + break; + default: + puts("error: invalid value for active flank"); + return 1; + } + + if (po < PCF857X_PORT_0) { + gpio_init_int(GPIO_PIN(po, pi), mode, flank, cb, (void *)pi); + } + else if (pcf857x_gpio_init_int(&pcf857x_dev[po - PCF857X_PORT_0], pi, + mode, flank, cb, (void *)pi) < 0) { + printf("error: init_int PCF857X pin (dev %i, pin %02i) failed\n", + po, pi); + return 1; + } + + return 0; +} + +static int enable_int(int argc, char **argv) +{ + int status; + + if (argc < 4) { + printf("usage: %s \n", argv[0]); + puts("\tstatus:\n" + "\t0: disable\n" + "\t1: enable\n"); + return 1; + } + + int po = atoi(argv[1]); + int pi = atoi(argv[2]); + + status = atoi(argv[3]); + + switch (status) { + case 0: + puts("disabling GPIO interrupt"); + if (po < PCF857X_PORT_0) { + gpio_irq_disable(GPIO_PIN(po, pi)); + } + else { + pcf857x_gpio_irq_disable(&pcf857x_dev[po - PCF857X_PORT_0], pi); + } + break; + case 1: + puts("enabling GPIO interrupt"); + if (po < PCF857X_PORT_0) { + gpio_irq_enable(GPIO_PIN(po, pi)); + } + else { + pcf857x_gpio_irq_enable(&pcf857x_dev[po - PCF857X_PORT_0], pi); + } + break; + default: + puts("error: invalid status"); + return 1; + } + + return 0; +} +#endif /* MODULE_PCF857X_IRQ */ + +static int read(int argc, char **argv) +{ + if (argc < 3) { + printf("usage: %s \n", argv[0]); + return 1; + } + + int po = atoi(argv[1]); + int pi = atoi(argv[2]); + + if (po < PCF857X_PORT_0) { + if (gpio_read(GPIO_PIN(po, pi))) { + printf("GPIO pin (port %i, pin %02i) is HIGH\n", po, pi); + } + else { + printf("GPIO pin (port %i, pin %02i) is LOW\n", po, pi); + } } + else { + if (pcf857x_gpio_read(&pcf857x_dev[po - PCF857X_PORT_0], pi)) { + printf("PCF857X pin (dev %i, pin %02i) is HIGH\n", po, pi); + } + else { + printf("PCF857X pin (dev %i, pin %02i) is LOW\n", po, pi); + } + } + return 0; +} + +static int set(int argc, char **argv) +{ + if (argc < 3) { + printf("usage: %s \n", argv[0]); + return 1; + } + + int po = atoi(argv[1]); + int pi = atoi(argv[2]); + + if (po < PCF857X_PORT_0) { + gpio_set(GPIO_PIN(po, pi)); + } + else { + pcf857x_gpio_set(&pcf857x_dev[po - PCF857X_PORT_0], pi); + } + return 0; +} + +static int clear(int argc, char **argv) +{ + if (argc < 3) { + printf("usage: %s \n", argv[0]); + return 1; + } + + int po = atoi(argv[1]); + int pi = atoi(argv[2]); + + if (po < PCF857X_PORT_0) { + gpio_clear(GPIO_PIN(po, pi)); + } + else { + pcf857x_gpio_clear(&pcf857x_dev[po - PCF857X_PORT_0], pi); + } + return 0; +} + +static int toggle(int argc, char **argv) +{ + if (argc < 3) { + printf("usage: %s \n", argv[0]); + return 1; + } + + int po = atoi(argv[1]); + int pi = atoi(argv[2]); + + if (po < PCF857X_PORT_0) { + gpio_toggle(GPIO_PIN(po, pi)); + } + else { + pcf857x_gpio_toggle(&pcf857x_dev[po - PCF857X_PORT_0], pi); + } + return 0; +} + +static int bench(int argc, char **argv) +{ + if (argc < 3) { + printf("usage: %s [# of runs]\n", argv[0]); + return 1; + } + + int po = atoi(argv[1]); + int pi = atoi(argv[2]); + + unsigned long runs = BENCH_RUNS_DEFAULT; + if (argc > 3) { + runs = (unsigned long)atol(argv[3]); + } + + puts("\nGPIO driver run-time performance benchmark\n"); + + if (po < PCF857X_PORT_0) { + BENCHMARK_FUNC("nop loop", runs, __asm__ volatile("nop")); + gpio_init(GPIO_PIN(po, pi), GPIO_OUT); + BENCHMARK_FUNC("gpio_set", runs, gpio_set(GPIO_PIN(po, pi))); + BENCHMARK_FUNC("gpio_clear", runs, gpio_clear(GPIO_PIN(po, pi))); + BENCHMARK_FUNC("gpio_toggle", runs, gpio_toggle(GPIO_PIN(po, pi))); + gpio_init(GPIO_PIN(po, pi), GPIO_IN); + BENCHMARK_FUNC("gpio_read", runs, (void)gpio_read(GPIO_PIN(po, pi))); + gpio_init(GPIO_PIN(po, pi), GPIO_OUT); + BENCHMARK_FUNC("gpio_write", runs, gpio_write(GPIO_PIN(po, pi), 1)); + } + else { + pcf857x_t* dev = &pcf857x_dev[po - PCF857X_PORT_0]; + BENCHMARK_FUNC("nop loop", runs, __asm__ volatile("nop")); + pcf857x_gpio_init(dev, pi, GPIO_OUT); + BENCHMARK_FUNC("gpio_set", runs, pcf857x_gpio_set(dev, pi)); + BENCHMARK_FUNC("gpio_clear", runs, pcf857x_gpio_clear(dev, pi)); + BENCHMARK_FUNC("gpio_toggle", runs, pcf857x_gpio_toggle(dev, pi)); + pcf857x_gpio_init(dev, pi, GPIO_IN); + BENCHMARK_FUNC("gpio_read", runs, (void)pcf857x_gpio_read(dev, pi)); + pcf857x_gpio_init(dev, pi, GPIO_OUT); + BENCHMARK_FUNC("gpio_write", runs, pcf857x_gpio_write(dev, pi, 1)); + puts("\n --- DONE ---"); + } + return 0; +} + +static const shell_command_t shell_commands[] = { + { "init_out", "init as output (push-pull mode)", init_out }, + { "init_in", "init as input w/o pull resistor", init_in }, + { "init_in_pu", "init as input with pull-up", init_in_pu }, + { "init_od", "init as output (open-drain without pull resistor)", init_od }, + { "init_od_pu", "init as output (open-drain with pull-up)", init_od_pu }, +#ifdef MODULE_PCF857X_IRQ + { "init_int", "init as external INT w/o pull resistor", init_int }, + { "enable_int", "enable or disable gpio interrupt", enable_int }, +#endif + { "read", "read pin status", read }, + { "set", "set pin to HIGH", set }, + { "clear", "set pin to LOW", clear }, + { "toggle", "toggle pin", toggle }, + { "bench", "run a set of predefined benchmarks", bench }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + puts("PCF857X I/O expander GPIO peripheral driver test\n"); + puts("Initializing PCF857X"); + + /* initialize configured PCF857X devices */ + for (unsigned i = 0; i < PCF857X_NUM; i++) { + if (pcf857x_init(&pcf857x_dev[i], &pcf857x_params[i]) != PCF857X_OK) { + puts("[Failed]"); + return 1; + } + } + puts("[OK]\n"); + + printf("In this test, pins are specified by integer port and pin numbers.\n" + "PCF8574 has 8 I/O pins labeled P00...P07.\n" + "PCF8575 has 16 I/O pins labeled P00...P07 and P10...P17\n" + "Use port %d and pin 0...15 in all commands to access them.\n\n" + "NOTE: make sure the values you use exist! The\n" + " behavior for not existing ports/pins is not defined!\n", + PCF857X_PORT_0); + + /* start the shell */ + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + return 0; +}