1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-25 22:43:50 +01:00

drivers/mcp23x17: add support for MCP23x17 I/O expanders

This commit is contained in:
Gunar Schorcht 2021-12-05 17:34:24 +01:00
parent 3d5582d563
commit f8585d81c7
9 changed files with 1673 additions and 0 deletions

View File

@ -120,6 +120,10 @@ ifneq (,$(filter mrf24j40m%,$(USEMODULE)))
USEMODULE += mrf24j40
endif
ifneq (,$(filter mcp23%17 mcp23x17_%,$(USEMODULE)))
USEMODULE += mcp23x17
endif
ifneq (,$(filter mtd_%,$(USEMODULE)))
USEMODULE += mtd
endif

687
drivers/include/mcp23x17.h Normal file
View File

@ -0,0 +1,687 @@
/*
* 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.
*/
#pragma once
/**
* @defgroup drivers_mcp23x17 MCP23x17 I/O Expander
* @ingroup drivers_misc
* @ingroup drivers_saul
* @brief Device driver for Microchip MCP23x17 I/O expanders
*
* \section mcp23x17_driver Device driver for Microchip MCP23x17 I/O expanders
*
* ## Overview
*
* Microchip MCP23x17 I/O expanders provide general purpose I/O extension over
* I2C or SPI. The driver supports the following MCP23x17 I/O expander and
* interface variants:
*
* <center>
* Expander | Type | Interface | Pseudomodule to be used
* :--------|:--------------------|:----------|:----------------------------
* MCP23017 | 16-bit I/O expander | I2C | `mcp23017` or `mcp23x17_i2c`
* MCP23S17 | 16-bit I/O expander | SPI | `mcp23s17` or `mcp23x17_spi`
* </center>
*
* For each of the MCP23x17 interface variant, the driver defines a separate
* pseudomodule. Multiple MCP23x17 I/O expanders and different interface
* variants can be used simultaneously. The application has to specify used
* MCP23x17 I/O expander or interface variants as a list of used pseudomodules.
* For example, to use a MCP23017 with I2C interface and a MCP23S17 with SPI
* interface at the same time, the make command would be:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* USEMODULE="mcp23x17_i2c mcp23x17_spi" BOARD=... make -C tests/driver_mcp23x17
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* At least one MCP23x17 I/O expander or interface variant has to be specified.
* The driver module `mcp23x17` is then enabled implicitly.
*
* ## Driver Interface
*
* The driver interface is kept as compatible as possible with the peripheral
* GPIO interface. The only differences are that
*
* - functions have the prefix `mcp23x17_` and
* - functions require an additional parameter, the pointer to the expander
* device of type #mcp23x17_t.
*
* ## Defined pseudomodules
*
* The functionality of the driver is controlled by the use of pseudomodules.
* The following pseudomodules are defined:
* <center>
* Pseudomoule | Functionality
* :---------------------|:---------------------
* `mcp23017` | support of MCP23017 with I2C interface
* `mcp23x17_i2c` | support of MCP23017 with I2C interface
* `mcp23s17` | support of MCP23S17 with SPI interface
* `mcp23x17_spi` | support of MCP23S17 with SPI interface
* `mcp23x17_irq` | support of interrupts enabled with medium event priority
* `mcp23x17_irq_medium` | support of interrupts enabled with medium event priority
* `mcp23x17_irq_highest`| support of interrupts enabled with highest event priority
* `mcp23x17_reset` | support of hardware reset (RESET pin is used)
* </center><br>
*
* @note
* - At least one of the modules `mcp23017`, `mcp23x17_i2c`, `mcp23s17` or
* `mcp23x17_spi` has to be used.
* - Module `mcp23017` enables the `mcp23x17_i2c` module automatically.
* - Module `mcp23s17` enables the `mcp23x17_spi` module automatically.
* - Module `mcp23x17_irq` enables the `mcp23x17_irq_medium` module
* automatically if no other ``mcp23x17_irq_*` module is enabled.
*
* ## Expander GPIOs
*
* The MCP23x17 expander devices provide 16 bidirectional input/output (I/O)
* pins. These pins are arranged in two ports A and B with 8 pins each.
* Each expander I/O pin can be used as input or output. Weak pull-up resistors
* can be enabled for input pins. Output pins are latched.
*
* The driver supports the following GPIO modes:
*
* <center>
* GPIO mode | Remarks
* :---------- |:--------
* #GPIO_IN | supported by the MCP27x17 expander device
* #GPIO_IN_PU | supported by the MCP27x17 expander device
* #GPIO_IN_PD | not supported
* #GPIO_OUT | supported by the MCP27x17 expander device
* #GPIO_OD | emulated by the driver
* #GPIO_OD_PU | emulated by the driver
* </center><br>
*
* After the initialization with function #mcp23x17_init, all
* MCP23x17 expander I/O pins are in #GPIO_IN mode.
*
* The MCP23x17 expander I/O pins for each device can be addressed either
* consecutively in the range of 0 ... 15 or by using the macro
* #MCP23X17_GPIO_PIN with the tuple (port, pin) according to the
* following scheme:
*
* <center>
* MCP23x17 pin label | Expander Port | Expander Pin | RIOT symbol
* ------------------ |:-------------:|:------------:|:-------------------------
* GPA0 | 0 | 0 | `MCP23X17_GPIO_PIN(0, 0)`
* GPA1 | 0 | 1 | `MCP23X17_GPIO_PIN(0, 1)`
* ... | ... | ... | ...
* GPA7 | 0 | 7 | `MCP23X17_GPIO_PIN(0, 7)`
* GPB0 | 1 | 0 | `MCP23X17_GPIO_PIN(1, 0)`
* GPB1 | 1 | 1 | `MCP23X17_GPIO_PIN(1, 1)`
* ... | ... | ... | ...
* GPB7 | 1 | 7 | `MCP23X17_GPIO_PIN(1, 7)`
* </center>
*
* ## Expander Interfaces
*
* MCP23x17 I/O expanders can be connected either via I2C or via SPI. The
* interface of the respective device is defined by the configuration
* parameter mcp23x17_if_params_t::type, which can be either
* #MCP23X17_SPI or #MCP23X17_I2C. To use these interface types, the
* corresponding modules `mcp23x17_spi` and/or `mcp23x17_i2c` have to be
* enabled.
*
* Each MCP23x17 device requires an address which is configured via the
* hardware address pins A0 ... A2 of the MCP23x17 device. The address is in
* the range from 0 to 7 and is used internally by the driver as an offset
* to the base address #MCP23X17_BASE_ADDR to derive the full device address.
*
* @note The driver uses hardware addressing with MCP23x17 pins A0 ... A2
* also for MCP23x17 SPI devices. This allows the use of up to eight SPI
* devices with the same CS signal.
*
* In addition to the type and the address of the interface, a number of
* interface specific parameters have to be configured for each device:
*
* <center>
* |Interface | Parameter | Parameter |
* |:--------:|:-------------------|:---------------------------------------|
* | SPI | Device Identifier | \ref mcp23x17_spi_params_t::dev "mcp23x17_if_params_t::if_params.spi.dev" |
* | SPI | Chip Select GPIO | \ref mcp23x17_spi_params_t::cs "mcp23x17_if_params_t::if_params.spi.cs" |
* | SPI | Clock Rate | \ref mcp23x17_spi_params_t::clk "mcp23x17_if_params_t::if_params.spi.clk" |
* | SPI | Address Offset | \ref mcp23x17_params_t::addr "mcp23x17_params_t::addr" |
* | | | |
* | I2C | Device Identifier | \ref mcp23x17_i2c_params_t::dev "mcp23x17_if_params_t::if_params.i2c.dev" |
* | I2C | Address Offset | \ref mcp23x17_params_t::addr "mcp23x17_params_t::addr" |
* </center>
*
* ## Hardware Reset
*
* MCP23x17 I/O expanders have a low-active `RESET` pin. If module
* `mcp23x17_reset` is used, a hardware reset is executed when the expander
* device is initialized with function #mcp23x17_init. Otherwise, only power
* on reset configuration is restored.
*
* If the hardware reset is used by enabling module `mcp23x17_reset`, the
* configuration parameter mcp23x17_params_t::reset_pin has to define the
* MCU GPIO pin that is connected to the `RESET` pin of MCP23x17 devices.
*
* ## Interrupts
*
* MCP23x17 expanders have two interrupt pins `INTA` and `INTB`, one for each
* port. These interrupt pins are internally connected (mirrored) by the driver
* so that an interrupt on either port will cause both pins to activate. Thus,
* interrupts on either port can be handled using a single interrupt pin
* connected to the MCU.
*
* The configuration parameter mcp23x17_params_t::int_pin is used to
* configure the MCU pin that is connected to one of the interrupt pins
* `INTA` or `INTB`. Each MCP23x17 expander device must use its own GPIO pin
* for its combined `INTA`/`INTB` signal.
*
* An interrupt callback function can be attached to an expander input pin with
* the #mcp23x17_gpio_init_int function. This interrupt callback function is
* then called on any rising and/or falling edge of the expander input pin.
*
* @note The interrupt callback function is called in the thread context,
* so there are no restrictions on execution time or bus access.
*
* To be able to handle interrupts in thread context, a separate event
* thread is used, see section [The Interrupt Context Problem]
* (#mcp23x17_interrupt_context_problem).
* Therefore, enabling interrupts requires more RAM and interrupts have to
* be explicitly enabled with the module `mcp23x17_irq_<priority>`.
* `priority` can be one `medium` or `highest`, which correspond
* to the priority of the event thread that processes the interrupts.
* For more information on the priorities check @ref sys_event module.
*
* @note Interrupt support can also be enabled by using module `mxp23x17_irq`
* without specifying the priority. In this case, module `mcp23x17_irq_medium`
* is enabled automatically and the interrupt support is enabled with a
* medium priority of the event thread.
*
* Furthermore, the GPIO pin to which the combined MCP23x17 `INTA`/`INTB` signal
* is connected has to be defined by the parameter mcp23x17_params_t::int_pin.
* The default hardware configuration as defined in `mcp23x17_params.h`
* defines by
*
* - #MCP23X17_PARAM_I2C_INT the GPIO pin for the interrupt signal of the
* default MCP23017 device, and
* - #MCP23X17_PARAM_SPI_INT he GPIO pin for the interrupt signal of the
* default MCP23S17 device.
*
* For more information about the default hardware configuration, see section
* [Default Hardware Configuration](#mcp23x17_default_hardware_configuration).
*
* This default configuration could be overridden at make command line,
* for example to use a MCP23017 with I2C interface and interrupt support with
* high priority:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* CFLAGS="-DMCP23X17_PARAM_I2C_INT=GPIO_PIN\(0,6\)" \
* USEMODULE="mcp23x17_i2c mcp23x17_irq_highest" BOARD=... make -C tests/driver_mcp23x17
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ## The Interrupt Context Problem {#mcp23x17_interrupt_context_problem}
*
* Handling an interrupt of a MCP23x17 expander requires the driver to access
* the device directly via I2Cor SPI. However, the mutex-based synchronization
* of I2C and SPI accesses do not work in the interrupt context. Therefore the
* ISR must not access the MCP23x17 expander device directly. Rather, the ISR
* must only indicate the occurrence of the interrupt which has to be handled
* asynchronously in the thread context.
*
* For this purpose an event thread module is used when interrupts are
* enabled by the module `mcp23x17_irq_<priority>`. 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 MCU GPIOs. Each MCP23x17 expander I/O pin can
* be mapped directly to SAUL by defining an according entry in
* #MCP23X17_SAUL_GPIO_PARAMS. Please refer file `mcp23x17_params.h` for
* an example.
*
* @note Module `saul_gpio` has to be added to the
* project to enable SAUL capabilities of the MCP23x17 driver, e.g.,
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* USEMODULE="mcp23x17_i2c saul_gpio" BOARD=... make -C tests/saul
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ## Using Multiple Devices
*
* It is possible to use multiple devices and different interface variants of
* MCP23x17 I/O expanders simultaneously. The application has to specify used
* interface variants by a list of pseudomodules. For example, to use MCP23017
* and MCP23S17 I/O expanders simultaneously, the make command would be:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* USEMODULE="mcp23x17_i2c mcp23x17_spi" BOARD=... make -C tests/driver_mcp23x17
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Furthermore, used devices have to be configured by defining the hardware
* configuration parameter array `mcp23x17_params` of type #mcp23x17_params_t.
* A default hardware configuration for one device of each interface variant is
* already defined in `mcp23x17_params.h`.
*
* The application can override it by placing a `mcp23x17_params.h` *
* in the application directory `$(APPDIR)`. For example, the definition
* of the hardware configuration parameter array for two devices with SPI
* interface and two devices with I2C interface could be:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
* static const mcp23x17_params_t mcp23x17_params[] = {
* {
* .addr = 0,
* .int_pin = GPIO_PIN(0,1),
* .reset_pin = GPIO_UNDEF,
* .if_params.type = MCP23X17_SPI,
* .if_params.spi.dev = SPI_DEV(0),
* .if_params.spi.cs = GPIO_PIN(0,0),
* .if_params.spi.clk = SPI_CLK_10MHZ,
* },
* {
* .addr = 1,
* .int_pin = GPIO_PIN(0,2),
* .reset_pin = GPIO_UNDEF,
* .if_params.type = MCP23X17_SPI,
* .if_params.spi.dev = SPI_DEV(0),
* .if_params.spi.cs = GPIO_PIN(0,0),
* .if_params.spi.clk = SPI_CLK_10MHZ,
* },
* {
* .addr = 0,
* .int_pin = GPIO_PIN(0,3),
* .reset_pin = GPIO_UNDEF,
* .if_params.type = MCP23X17_I2C,
* .if_params.i2c.dev = I2C_DEV(0),
* },
* {
* .addr = 1,
* .int_pin = GPIO_PIN(0,4),
* .reset_pin = GPIO_UNDEF,
* .if_params.type = MCP23X17_I2C,
* .if_params.i2c.dev = SPI_DEV(0),
* }
* };
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ## Default Hardware Configuration {#mcp23x17_default_hardware_configuration}
*
* The default hardware configuration is defined in file `mcp23x17_params.h`
* using the following defines:
*
* <center>
* | Hardware configuration | Driver name | Default Value |
* |:------------------------------|:--------------------------|:--------------|
* | SPI Address Offset | #MCP23X17_PARAM_SPI_ADDR | 0 |
* | SPI Device Identifier | #MCP23X17_PARAM_SPI_DEV | SPI_DEV(0) |
* | SPI Clock rRate | #MCP23X17_PARAM_SPI_CLK | SPI_CLK_10MHZ |
* | SPI Chip Select GPIO | #MCP23X17_PARAM_SPI_CS | GPIO_PIN(0,0) |
* | SPI Device `INTA`/`INTB` GPIO | #MCP23X17_PARAM_SPI_INT | GPIO_PIN(0,1) |
* | | | |
* | I2C Address Offset | #MCP23X17_PARAM_I2C_ADDR | 0 |
* | I2C Device Identifier | #MCP23X17_PARAM_I2C_DEV | I2C_DEV(0) |
* | I2C Device `INTA`/`INTB` GPIO | #MCP23X17_PARAM_I2C_INT | GPIO_PIN(0,2) |
* | | | |
* | `RESET` GPIO for all devices | #MCP23X17_PARAM_RESET_PIN | GPIO_UNDEF |
* </center><br>
*
* These default hardware configuration parameters can be overridden either by
* the board definition or by defining them in the `CFLAGS` variable in the make
* command, for example:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* CFLAGS="-DMCP23X17_PARAM_I2C_ADDR=2 -DMCP23X17_PARAM_RESET_PIN=GPIO_PIN\(0,7\)" \
* USEMODULE='mcp23x17_i2c mcp23x17_reset' BOARD=... make -C tests/driver_mcp23x17
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* @author Gunar Schorcht <gunar@schorcht.net>
*
* @{
* @file
* @brief Device driver interface for Microchip MCP23x17 I/O expanders
*/
#ifdef __cplusplus
extern "C" {
#endif
#include "periph/gpio.h"
#include "periph/i2c.h"
#include "periph/spi.h"
#if IS_USED(MODULE_SAUL_GPIO) || DOXYGEN
#include "saul/periph.h"
#endif /* MODULE_SAUL_GPIO */
#if !IS_USED(MODULE_MCP23X17_I2C) && !IS_USED(MODULE_MCP23X17_SPI)
#error "Please provide the MCP23x17 variants used by the application."
#error "At least one variant has to be specified (mcp23017 and/or mcp23s17)."
#endif
#if IS_USED(MODULE_MCP23X17_IRQ) || DOXYGEN
#include "event.h"
#endif /* MODULE_MCP23X17_IRQ */
/**
* @brief MCP23x17 device base address
*
* The address of a MCP23x17 device, both for devices with I2C interface and
* for devices with SPI interface, is defined as the offset to a base address.
* The address is in the range from 0 to 7 and is defined for the respective
* MCP23x17 device by its hardware address pins A0 ... A2.
*
* @note The base address MCP23X17_BASE_ADDR is for internal use only. In the
* device parameters only the offset to the base address is used as address.
*/
#define MCP23X17_BASE_ADDR (0x20)
/**
* @brief MCP23x17 has 16 I/O pins
*/
#define MCP23X17_GPIO_PIN_NUM (16)
/**
* @brief Conversion of (port x : pin y) to a pin number
*
* MCP23x17 expanders have 16 pins arranged in 2 ports with 8 pins each.
* #MCP23X17_GPIO_PIN can either be used
*
* - #MCP23X17_GPIO_PIN(0, 0...15) or
* - #MCP23X17_GPIO_PIN(0, 0...7) and MCP23X17_GPIO_PIN(1, 0...7)
*
* to address the 16 expander pins.
*/
#define MCP23X17_GPIO_PIN(port, pin) ((gpio_t)((port << 3) | pin))
/**
* @brief Named MCP23x17 driver error codes
*/
typedef enum {
MCP23X17_OK, /**< success */
MCP23X17_ERROR_I2C, /**< I2C communication error */
MCP23X17_ERROR_SPI, /**< SPI communication error */
MCP23X17_ERROR_NO_DEV, /**< no MCP23x17 I/O expander device */
MCP23X17_ERROR_INV_MODE, /**< invalid pin mode */
MCP23X17_ERROR_INV_FLANK, /**< invalid interrupt flank */
MCP23X17_ERROR_GPIO, /**< GPIO pin error */
MCP23X17_ERROR_INT_PIN, /**< `INTA`/`INTB` pin error */
MCP23X17_ERROR_RESET_PIN, /**< `RESET` pin error */
} mcp23x17_error_codes_t;
/**
* @brief MCP23x17 interface types
*/
typedef enum {
#if IS_USED(MODULE_MCP23X17_I2C) || DOXYGEN
MCP23X17_I2C, /**< I2C interface used */
#endif
#if IS_USED(MODULE_MCP23X17_SPI) || DOXYGEN
MCP23X17_SPI, /**< SPI interface used */
#endif
} mcp23x17_if_t;
/**
* @brief MCP23017 I2C parameters
*/
#if IS_USED(MODULE_MCP23X17_I2C) || DOXYGEN
typedef struct {
i2c_t dev; /**< I2C device used */
} mcp23x17_i2c_params_t;
#endif
/**
* @brief MCP23S17 SPI parameters
*/
#if IS_USED(MODULE_MCP23X17_SPI) || DOXYGEN
typedef struct {
spi_t dev; /**< SPI device used */
spi_clk_t clk; /**< SPI clock speed */
gpio_t cs; /**< SPI chip Select pin */
} mcp23x17_spi_params_t;
#endif
/**
* @brief MCP23x17 Hardware interface parameters union
*/
typedef struct {
mcp23x17_if_t type; /**< I2C/SPI interface type selector */
union {
#if IS_USED(MODULE_MCP23X17_I2C) || DOXYGEN
mcp23x17_i2c_params_t i2c; /**< I2C specific interface parameters */
#endif
#if IS_USED(MODULE_MCP23X17_SPI) || DOXYGEN
mcp23x17_spi_params_t spi; /**< SPI specific interface parameters */
#endif
};
} mcp23x17_if_params_t;
/**
* @brief Struct containing the peripheral configuration
*/
typedef struct {
/**
* @brief MCP2317 device address.
*
* The MCP2317 device address is the address configured via the hardware
* address pins A0 ... A2 of the MCP23x17 device. It is in the range
* from 0 to 7. The address is used internally by the driver as an offset
* to the base address MCP23X17_BASE_ADDR to derive the complete device
* address.
*
* @note The driver uses hardware addressing with MCP23x17
* pins A0 ... A2 also for MCP23x17 SPI devices. The use of hardware
* addressing also for SPI devices allows the use of up to eight
* SPI devices with the same CS signal.
*/
uint8_t addr;
gpio_t int_pin; /**< GPIO pin used for combined `INTA`/`INTB` signal.
Each device must use its own GPIO pin for its
combined `INTA`/`INTB` signal. */
gpio_t reset_pin; /**< GPIO pin used for `RESET` signal */
mcp23x17_if_params_t if_params; /**< specific I2C/SPI interface parameters */
} mcp23x17_params_t;
#if IS_USED(MODULE_MCP23X17_IRQ) || DOXYGEN
/**
* @brief IRQ event type
*
* Handling an interrupt of a MCP23x17 expander requires direct access to the
* device by the driver over I2C/SPI within the ISR. However, the mutex
* based synchronization of I2C/SPI accesses does not work in the interrupt
* context. Accessing I2C/SPI within an ISR could therefore interfere with an
* existing I2C/SPI access. Therefore, the ISR must not access the MCP23x17
* expander device. Rather, the ISR has only to indicate the occurrence
* of the interrupt. The interrupt is then handled asynchronously by a thread.
*
* The type defines the data structure which is part of each device data
* structure to indicate that an interrupt for 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; /**< Super event data structure */
void *dev; /**< MCP23x17 device reference */
} mcp23x17_irq_event_t;
#endif /* MODULE_MCP23X17_IRQ */
/**
* @brief Device descriptor for MCP23x17 I/O expanders
*/
typedef struct {
mcp23x17_params_t params; /**< Device initialization parameters */
uint16_t od_pins; /**< Pins defined as GPIO_OD or GPIO_OD_PU */
#if IS_USED(MODULE_MCP23X17_IRQ) || DOXYGEN
gpio_isr_ctx_t isr[MCP23X17_GPIO_PIN_NUM]; /**< ISR with arg for each expander pin */
gpio_flank_t flank[MCP23X17_GPIO_PIN_NUM]; /**< interrupt flank for each expander pin */
mcp23x17_irq_event_t irq_event; /**< IRQ event object used for the device */
#endif /* MODULE_MCP23X17_IRQ */
} mcp23x17_t;
#if IS_USED(MODULE_SAUL_GPIO) || DOXYGEN
/**
* @brief MCP23x17 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 MCP23x17 device.
*
* @note To use MCP23x17 with SAUL, module `saul_gpio` has to be added to the
* project.
*/
typedef struct {
uint8_t dev; /**< MCP23x17 device index */
saul_gpio_params_t gpio; /**< GPIO configuration for mapping to SAUL */
} mcp23x17_saul_gpio_params_t;
#endif
/**
* @brief Initialize the MCP23x17 I/O expander
*
* All expander pins are set to be input and are pulled up.
*
* @param[in] dev Descriptor of MCP23x17 I/O expander device
* @param[in] params Configuration parameters, see #mcp23x17_params_t
*
* @retval MCP23X17_OK on success
* @retval MCP23X17_ERROR_* on error, a negative error code,
* see #mcp23x17_error_codes_t
*/
int mcp23x17_init(mcp23x17_t *dev, const mcp23x17_params_t *params);
/**
* @brief Initialize a MCP23x17 pin
*
* @param[in] dev Descriptor of MCP23x17 I/O expander device
* @param[in] pin Pin to initialize, use MCP23X17_GPIO_PIN(x,y) to specify
* @param[in] mode Mode of the pin, see #gpio_t
*
* @retval MCP23X17_OK on success
* @retval MCP23X17_ERROR_* on error, a negative error code,
* see #mcp23x17_error_codes_t
*/
int mcp23x17_gpio_init(mcp23x17_t *dev, gpio_t pin, gpio_mode_t mode);
#if IS_USED(MODULE_MCP23X17_IRQ) || DOXYGEN
/**
* @brief Initialize a MCP23x17 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.
*
* @note
* - Module `mcp23x17_irq` has to be added to the project to enable this
* function.
* - The GPIO pin connected to the MCP23x17 combined `INTA`/`INTB` signal has
* to be defined by parameter mcp23x17_params_t::int_pin.
*
* @param[in] dev Descriptor of MCP23x17 I/O expander device
* @param[in] pin pin to initialize, use MCP23X17_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 MCP23X17_OK on success
* @retval MCP23X17_ERROR_* on error, a negative error code,
* see #mcp23x17_error_codes_t
*/
int mcp23x17_gpio_init_int(mcp23x17_t *dev, gpio_t pin,
gpio_mode_t mode,
gpio_flank_t flank,
gpio_cb_t isr,
void *arg);
#endif /* MODULE_MCP23X17_IRQ || DOXYGEN */
/**
* @brief Get the value from MCP23x17 input pin
*
* @param[in] dev Descriptor of MCP23x17 I/O expander device
* @param[in] pin pin to read, use MCP23X17_GPIO_PIN(x,y) to specify
*
* @retval 0 on LOW signal
* @retval 1 on HIGH signal
* @retval MCP23X17_ERROR_* on error, a negative error code,
* see #mcp23x17_error_codes_t
*/
int mcp23x17_gpio_read(mcp23x17_t *dev, gpio_t pin);
/**
* @brief Write the value to MCP23x17 input pin
*
* @param[in] dev Descriptor of MCP23x17 I/O expander device
* @param[in] pin pin to write, use MCP23X17_GPIO_PIN(x,y) to specify
* @param[in] value value to write
*/
void mcp23x17_gpio_write(mcp23x17_t *dev, gpio_t pin, int value);
/**
* @brief Clear the MCP23x17 output pin
*
* @param[in] dev Descriptor of MCP23x17 I/O expander device
* @param[in] pin pin to clear, use MCP23X17_GPIO_PIN(x,y) to specify
*/
void mcp23x17_gpio_clear(mcp23x17_t *dev, gpio_t pin);
/**
* @brief Set the MCP23x17 output pin
*
* @param[in] dev Descriptor of MCP23x17 I/O expander device
* @param[in] pin pin to set, use MCP23X17_GPIO_PIN(x,y) to specify
*/
void mcp23x17_gpio_set(mcp23x17_t *dev, gpio_t pin);
/**
* @brief Toggle the value of the MCP23x17 output pin
*
* @param[in] dev Descriptor of MCP23x17 I/O expander device
* @param[in] pin pin to toggle, use MCP23X17_GPIO_PIN(x,y) to specify
*/
void mcp23x17_gpio_toggle(mcp23x17_t *dev, gpio_t pin);
#if IS_USED(MODULE_MCP23X17_IRQ) || DOXYGEN
/**
* @brief Enable pin interrupt
*
* @note
* - Module `mcp23x17_irq` has to be added to the project to enable this
* function.
* - The GPIO pin connected to the MCP23x17 combined `INTA`/`INTB` signal has
* to be defined by parameter mcp23x17_params_t::int_pin.
*
* @param[in] dev Descriptor of MCP23x17 I/O expander device
* @param[in] pin pin to enable the interrupt for
*/
void mcp23x17_gpio_irq_enable(mcp23x17_t *dev, gpio_t pin);
/**
* @brief Disable pin interrupt
*
* @note
* - Module `mcp23x17_irq` has to be added to the project to enable this
* function.
* - The GPIO pin connected to the MCP23x17 combined `INTA`/`INTB` signal has
* to be defined by parameter mcp23x17_params_t::int_pin.
*
* @param[in] dev Descriptor of MCP23x17 I/O expander device
* @param[in] pin pin to enable the interrupt for
*/
void mcp23x17_gpio_irq_disable(mcp23x17_t *dev, gpio_t pin);
#endif /* MODULE_MCP23X17_IRQ || DOXYGEN */
#ifdef __cplusplus
}
#endif
/** @} */

View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,38 @@
FEATURES_REQUIRED += periph_gpio
ifneq (,$(filter mcp23s17,$(USEMODULE)))
USEMODULE += mcp23x17_spi
endif
ifneq (,$(filter mcp23017,$(USEMODULE)))
USEMODULE += mcp23x17_i2c
endif
ifneq (,$(filter mcp23x17_i2c,$(USEMODULE)))
FEATURES_REQUIRED += periph_i2c
endif
ifneq (,$(filter mcp23x17_spi,$(USEMODULE)))
FEATURES_REQUIRED += periph_spi
endif
_MCP23X17_IRQ_MODULE := $(filter mcp23x17_irq_%,$(USEMODULE))
ifneq (,$(_MCP23X17_IRQ_MODULE))
# pull in the correspondant event_thread_<priority> module
USEMODULE += $(_MCP23X17_IRQ_MODULE:mcp23x17_irq_%=event_thread_%)
USEMODULE += mcp23x17_irq
else
ifneq (,$(filter mcp23x17_irq,$(USEMODULE)))
# pull in the event_thread_medium module by default if mcp23x17_irq is used.
USEMODULE += mcp23x17_irq_medium
endif
endif
ifneq (,$(filter mcp23x17_irq,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio_irq
endif
ifneq (,$(filter mcp23x17_reset,$(USEMODULE)))
USEMODULE += ztimer
USEMODULE += ztimer_msec
endif

View File

@ -0,0 +1,12 @@
# variants of MCP27x17
PSEUDOMODULES += mcp23017
PSEUDOMODULES += mcp23s17
PSEUDOMODULES += mcp23x17_i2c
PSEUDOMODULES += mcp23x17_spi
PSEUDOMODULES += mcp23x17_irq
PSEUDOMODULES += mcp23x17_irq_medium
PSEUDOMODULES += mcp23x17_irq_highest
PSEUDOMODULES += mcp23x17_reset
USEMODULE_INCLUDES_mcp23x17 := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_mcp23x17)

View File

@ -0,0 +1,172 @@
/*
* 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.
*/
#pragma once
/**
* @ingroup drivers_mcp23x17
* @{
*
* @file
* @brief Default configuration for Microchip MCP23x17 I/O expanders
*
* @author Gunar Schorcht <gunar@schorcht.net>
*/
#include "board.h"
#include "mcp23x17.h"
#include "saul_reg.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name Set default hardware configuration parameters
* @{
*/
#if IS_USED(MODULE_MCP23X17_SPI) || DOXYGEN
#ifndef MCP23X17_PARAM_SPI_ADDR
/**
* Default hardware address, if the SPI interface is used.
* The use of hardware addressing also for SPI devices allows the use of
* up to eight SPI devices with the same CS signal.
*/
#define MCP23X17_PARAM_SPI_ADDR (0)
#endif
#ifndef MCP23X17_PARAM_SPI_DEV
/** Default SPI device, if the SPI interface is used */
#define MCP23X17_PARAM_SPI_DEV (SPI_DEV(0))
#endif
#ifndef MCP23X17_PARAM_SPI_CLK
/** Default SPI clock frequency, if the SPI interface is used */
#define MCP23X17_PARAM_SPI_CLK (SPI_CLK_10MHZ)
#endif
#ifndef MCP23X17_PARAM_SPI_CS
/** Default SPI CS signal, if the SPI interface is used */
#define MCP23X17_PARAM_SPI_CS (GPIO_PIN(0, 0))
#endif
#ifndef MCP23X17_PARAM_SPI_INT
/** Default MCU pin for INTA and INTB signal of the default SPI device */
#define MCP23X17_PARAM_SPI_INT (GPIO_PIN(0, 1))
#endif
#endif /* MODULE_MCP23X17_SPI || DOXYGEN */
#if IS_USED(MODULE_MCP23X17_I2C) || DOXYGEN
#ifndef MCP23X17_PARAM_I2C_ADDR
/** Default hardware address, if the I2C interface is used */
#define MCP23X17_PARAM_I2C_ADDR (0)
#endif
#ifndef MCP23X17_PARAM_I2C_DEV
/** Default I2C device, if the I2C interface is used */
#define MCP23X17_PARAM_I2C_DEV (I2C_DEV(0))
#endif
#ifndef MCP23X17_PARAM_I2C_INT
/** Default MCU pin for INTA and INTB signal of the default I2C device */
#define MCP23X17_PARAM_I2C_INT (GPIO_PIN(0, 2))
#endif
#endif /* MODULE_MCP23X17_I2C || DOXYGEN */
#ifndef MCP23X17_PARAM_RESET_PIN
/** MCU pin for RESET signal is undefinded by default */
#define MCP23X17_PARAM_RESET_PIN (GPIO_UNDEF)
#endif
#if IS_USED(MODULE_MCP23X17_SPI) || DOXYGEN
#ifndef MCP23X17_SPI_PARAMS
/** Default device parameters, if SPI interface is used */
#define MCP23X17_SPI_PARAMS { \
.addr = MCP23X17_PARAM_SPI_ADDR, \
.int_pin = MCP23X17_PARAM_SPI_INT, \
.reset_pin = MCP23X17_PARAM_RESET_PIN, \
.if_params.type = MCP23X17_SPI, \
.if_params.spi.dev = MCP23X17_PARAM_SPI_DEV, \
.if_params.spi.cs = MCP23X17_PARAM_SPI_CS, \
.if_params.spi.clk = MCP23X17_PARAM_SPI_CLK, \
}
#endif /* MCP23X17_SPI_PARAMS */
#endif /* MODULE_MCP23X17_SPI || DOXYGEN */
#if IS_USED(MODULE_MCP23X17_I2C) || DOXYGEN
#ifndef MCP23X17_I2C_PARAMS
/** Default device parameters, if I2C interface is used */
#define MCP23X17_I2C_PARAMS { \
.addr = MCP23X17_PARAM_I2C_ADDR, \
.int_pin = MCP23X17_PARAM_I2C_INT, \
.reset_pin = MCP23X17_PARAM_RESET_PIN, \
.if_params.type = MCP23X17_I2C, \
.if_params.i2c.dev = MCP23X17_PARAM_I2C_DEV, \
}
#endif /* MCP23X17_I2C_PARAMS */
#endif /* MODULE_MCP23X17_I2C || DOXYGEN */
#if IS_USED(MODULE_SAUL_GPIO) || DOXYGEN
#ifndef MCP23X17_SAUL_GPIO_PARAMS
/** Example for mapping expander pins to SAUL */
#define MCP23X17_SAUL_GPIO_PARAMS { \
.dev = 0, \
.gpio = { \
.name = "PA0 Input", \
.pin = MCP23X17_GPIO_PIN(0, 0), \
.mode = GPIO_IN, \
.flags = 0, \
} \
}, \
{ \
.dev = 0, \
.gpio = { \
.name = "PB5 Output", \
.pin = MCP23X17_GPIO_PIN(1, 5), \
.mode = GPIO_OUT, \
.flags = SAUL_GPIO_INIT_CLEAR, \
} \
},
#endif /* MCP23X17_SAUL_GPIO_PARAMS */
#endif /* MODULE_SAUL_GPIO || DOXYGEN */
/**@}*/
/**
* @brief Allocation of MCP23x17 configuration
*/
static const mcp23x17_params_t mcp23x17_params[] =
{
#if IS_USED(MODULE_MCP23X17_SPI) || DOXYGEN
MCP23X17_SPI_PARAMS,
#endif
#if IS_USED(MODULE_MCP23X17_I2C) || DOXYGEN
MCP23X17_I2C_PARAMS,
#endif
};
#if IS_USED(MODULE_SAUL_GPIO) || DOXYGEN
/**
* @brief Additional meta information to keep in the SAUL registry
*/
static const mcp23x17_saul_gpio_params_t mcp23x17_saul_gpio_params[] =
{
MCP23X17_SAUL_GPIO_PARAMS
};
#endif /* MODULE_SAUL_GPIO || DOXYGEN */
#ifdef __cplusplus
}
#endif
/** @} */

View File

@ -0,0 +1,80 @@
/*
* 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.
*/
#pragma once
/**
* @ingroup drivers_mcp23x17
* @brief Register definitions for Microchip MCP23x17 I/O expanders
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
* @{
*/
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name Register addresses (IOCON.BANK = 0)
* @{
*/
#define MCP23X17_REG_IODIR (0x00) /**< I/O direction register pair */
#define MCP23X17_REG_IODIRA (0x00) /**< I/O direction register port A */
#define MCP23X17_REG_IODIRB (0x01) /**< I/O direction register port B */
#define MCP23X17_REG_IPOL (0x02) /**< Input polarity port register pair */
#define MCP23X17_REG_IPOLA (0x02) /**< Input polarity port register port A */
#define MCP23X17_REG_IPOLB (0x03) /**< Input polarity port register port B */
#define MCP23X17_REG_GPINTEN (0x04) /**< Interupt-on-change enabled register pair */
#define MCP23X17_REG_GPINTENA (0x04) /**< Interupt-on-change enabled register port A */
#define MCP23X17_REG_GPINTENB (0x05) /**< Interupt-on-change enabled register port B */
#define MCP23X17_REG_DEFVAL (0x06) /**< Default value register pair */
#define MCP23X17_REG_DEFVALA (0x06) /**< Default value register port A */
#define MCP23X17_REG_DEFVALB (0x07) /**< Default value register port B */
#define MCP23X17_REG_INTCON (0x08) /**< Interupt-on-change config register pair */
#define MCP23X17_REG_INTCONA (0x08) /**< Interupt-on-change config register port A */
#define MCP23X17_REG_INTCONB (0x09) /**< Interupt-on-change config register port B */
#define MCP23X17_REG_IOCON (0x0A) /**< I/O expander config register pair */
#define MCP23X17_REG_IOCONA (0x0A) /**< I/O expander config register port A */
#define MCP23X17_REG_IOCONB (0x0B) /**< I/O expander config register port B */
#define MCP23X17_REG_GPPU (0x0C) /**< GPIO pull-up resistor register pair */
#define MCP23X17_REG_GPPUA (0x0C) /**< GPIO pull-up resistor register port A */
#define MCP23X17_REG_GPPUB (0x0D) /**< GPIO pull-up resistor register port B */
#define MCP23X17_REG_INTF (0x0E) /**< Interrupt flag register pair */
#define MCP23X17_REG_INTFA (0x0E) /**< Interrupt flag register port A */
#define MCP23X17_REG_INTFB (0x0F) /**< Interrupt flag register port B */
#define MCP23X17_REG_INTCAP (0x10) /**< Interrupt capture register pair */
#define MCP23X17_REG_INTCAPA (0x10) /**< Interrupt capture register port A */
#define MCP23X17_REG_INTCAPB (0x11) /**< Interrupt capture register port B */
#define MCP23X17_REG_GPIO (0x12) /**< GPIO port register pair */
#define MCP23X17_REG_GPIOA (0x12) /**< GPIO port register port A */
#define MCP23X17_REG_GPIOB (0x13) /**< GPIO port register port B */
#define MCP23X17_REG_OLAT (0x14) /**< Output latch register pair */
#define MCP23X17_REG_OLATA (0x14) /**< Output latch register port A */
#define MCP23X17_REG_OLATB (0x15) /**< Output latch register port B */
/** @} */
/**
* @name Register structure definitions
* @{
*/
/* IOCONA/IOCONB registers */
#define MCP23X17_IOCON_BANK (0x80) /**< Registered separated into different banks */
#define MCP23X17_IOCON_MIRROR (0x40) /**< INT pins internally connected */
#define MCP23X17_IOCON_SEQOP (0x20) /**< Sequential option disabled */
#define MCP23X17_IOCON_DISSLW (0x10) /**< Slew rate control for SDA disabled */
#define MCP23X17_IOCON_HAEN (0x08) /**< Hardware address enabled */
#define MCP23X17_IOCON_ODR (0x04) /**< Open-drain output (overrides INTPOL) */
#define MCP23X17_IOCON_INTPOL (0x02) /**< INT signals are active high */
/** @} */
#ifdef __cplusplus
}
#endif
/** @} */

622
drivers/mcp23x17/mcp23x17.c Normal file
View File

@ -0,0 +1,622 @@
/*
* 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.
*/
/**
* @ingroup drivers_mcp23x17
* @brief Device driver implementation for Microchip MCP23x17 I/O expanders
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*/
#include <stdint.h>
#include "assert.h"
#include "periph/i2c.h"
#include "ztimer.h"
#include "mcp23x17.h"
#include "mcp23x17_regs.h"
#if IS_USED(MODULE_MCP23X17_IRQ)
#include "event/thread.h"
#endif
#define ENABLE_DEBUG 0
#include "debug.h"
#if ENABLE_DEBUG
#include <string.h>
#define DEBUG_DEV(m, d, ...) \
DEBUG("[mcp23x17] %s dev=%" PRIxPTR " addr=%02x: " m "\n", \
__func__, (unsigned int)d, d->params.addr, ## __VA_ARGS__)
#else /* ENABLE_DEBUG */
#define DEBUG_DEV(f, d, ...)
#endif /* ENABLE_DEBUG */
#define _ADDR (MCP23X17_BASE_ADDR + dev->params.addr)
#if IS_USED(MODULE_MCP23X17_SPI)
#define _SPI_DEV (dev->params.if_params.spi.dev)
#define _SPI_CS (dev->params.if_params.spi.cs)
#define _SPI_CLK (dev->params.if_params.spi.clk)
#endif
#if IS_USED(MODULE_MCP23X17_I2C)
#define _I2C_DEV (dev->params.if_params.i2c.dev)
#endif
#if IS_USED(MODULE_MCP23X17_IRQ)
#if IS_USED(MODULE_MCP23X17_IRQ_MEDIUM)
#define MCP23X17_EVENT_PRIO EVENT_PRIO_MEDIUM
#elif IS_USED(MODULE_MCP23X17_IRQ_HIGHEST)
#define MCP23X17_EVENT_PRIO EVENT_PRIO_HIGHEST
#endif
/* interrupt service routine for IRQs */
static void _irq_isr(void *arg);
/* declaration of IRQ handler function */
static void _irq_handler(event_t *event);
#endif /* MODULE_MCP23X17_IRQ */
/* forward declarations of internal functions */
static void _acquire(const mcp23x17_t *dev);
static void _release(const mcp23x17_t *dev);
static int _read(const mcp23x17_t *dev, uint8_t reg, uint8_t *data, size_t len);
static int _write(const mcp23x17_t *dev, uint8_t reg, const uint8_t *data, size_t len);
static int _update_pin(const mcp23x17_t *dev, uint8_t reg, gpio_t pin, int value);
/* static power on reset configuration */
static const uint8_t _reset_conf[] =
{
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
int mcp23x17_init(mcp23x17_t *dev, const mcp23x17_params_t* params)
{
assert(dev);
assert(params);
dev->params = *params;
dev->od_pins = 0;
DEBUG_DEV("params %p", dev, params);
int res = MCP23X17_OK;
#if IS_USED(MODULE_MCP23X17_SPI)
if (params->if_params.type == MCP23X17_SPI) {
/* CS pin has to be defined and has to be initialized */
assert(gpio_is_valid(_SPI_CS));
if (spi_init_cs(_SPI_DEV, _SPI_CS) != SPI_OK) {
DEBUG_DEV("CS pin defined but could not be initialized\n", dev);
return -MCP23X17_ERROR_NO_DEV;
}
}
#endif
#if IS_USED(MODULE_MCP23X17_RESET)
/* GPIO pin for the RESET signal has to be define and initialized */
assert(gpio_is_valid(params->reset_pin));
/* initialize the low active RESET pin */
if (gpio_init(params->reset_pin, GPIO_OUT)) {
DEBUG_DEV("RESET pin defined but could not be initialized", dev);
return -MCP23X17_ERROR_RESET_PIN;
}
/* hardware reset impuls for at least 10 us is required, we use 1 ms */
gpio_clear(params->reset_pin);
ztimer_sleep(ZTIMER_MSEC, 1);
gpio_set(params->reset_pin);
#endif
#if IS_USED(MODULE_MCP23X17_IRQ)
/* GPIO pin for combined interrupt signal INTA/INTB has to be defined */
assert(gpio_is_valid(params->int_pin));
/* 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 < MCP23X17_GPIO_PIN_NUM; i++) {
dev->isr[i].cb = NULL;
dev->isr[i].arg = NULL;
}
/* GPIO for interrupt signal has to be initialized */
if (gpio_init_int(dev->params.int_pin, GPIO_IN, GPIO_FALLING,
_irq_isr, (void*)dev)) {
DEBUG_DEV("INT pin defined but could not be initialized", dev);
return -MCP23X17_ERROR_INT_PIN;
}
#endif /* MODULE_MCP23X17_IRQ */
_acquire(dev);
/*
* After power on reset or hardware reset, the default BANK mode is 0
* i.e., the A/B registers are paired and in the same bank with
* subsequent ascending addresses.
* Since the BANK mode is never changed, we can rely on the addressing
* scheme as define in mcp23x17_regs.h.
*/
uint8_t iocon; /* configuration register is the same for port A and B */
/* read the configuration registers to see whether device is reachable */
if (_read(dev, MCP23X17_REG_IOCONA, &iocon, 1) ||
_read(dev, MCP23X17_REG_IOCONB, &iocon, 1)) {
DEBUG_DEV("error reading IOCON registers", dev);
_release(dev);
return -MCP23X17_ERROR_NO_DEV;
}
/*
* After power on reset or hardware reset, the configuration of GPIOs is:
*
* - GPIO pins are defined as inputs
* - GPIO registers reflects the same logic state of the input pin
* - GPIO pin value is compared against previous pin value for
* interrupt-on-change
* - GPIO input pins are disabled for interrupts on change
* - GPIO pull-ups are disabled
* - GPIO default output values are 0
*
* If hardware reset is not used, we have to restore this configuration
* after system reboots
*/
res |= _write(dev, MCP23X17_REG_IODIR, _reset_conf, ARRAY_SIZE(_reset_conf));
#if IS_USED(MODULE_MCP23X17_IRQ)
/* INT is configured as push/pull and is active low */
iocon &= ~MCP23X17_IOCON_ODR;
iocon &= ~MCP23X17_IOCON_INTPOL;
/*
* Since we use only one pin for INTA and INTB signal, we have to use
* the MIRROR mode, i.e., an interrupt on either port will cause both
* interrupt pins to activate.
*/
iocon |= MCP23X17_IOCON_MIRROR;
/*
* Reset all interrupt-on-change control bits to 0, that is, pin values
* are compared against the previous value for interrupt-on-change.
* Disable all interrupts.
*
* Since this corresponds to the power on reset configuration written
* before, we have not execute it here.
*/
#if 0
uint8_t zero = 0;
res |= _write(dev, MCP23X17_REG_INTCONA, &zero, 1);
res |= _write(dev, MCP23X17_REG_INTCONB, &zero, 1);
res |= _write(dev, MCP23X17_REG_GPINTENA, &zero, 1);
res |= _write(dev, MCP23X17_REG_GPINTENB, &zero, 1);
#endif
#endif /* MODULE_MCP23X17_IRQ */
iocon &= ~MCP23X17_IOCON_SEQOP; /* sequential operation mode enabled */
iocon &= ~MCP23X17_IOCON_DISSLW; /* slew rate control enabled */
iocon |= MCP23X17_IOCON_HAEN; /* hardware addressing enabled */
/* write back configuration registers */
res |= _write(dev, MCP23X17_REG_IOCONA, &iocon, 1);
res |= _write(dev, MCP23X17_REG_IOCONB, &iocon, 1);
_release(dev);
return res;
}
int mcp23x17_gpio_init(mcp23x17_t *dev, gpio_t pin, gpio_mode_t mode)
{
assert(dev);
assert(pin < MCP23X17_GPIO_PIN_NUM);
DEBUG_DEV("pin %u, mode %d", dev, pin, mode);
uint8_t port = pin >> 3;
uint8_t pin_bit = 1 << (pin & 0x07);
uint8_t iodir;
uint8_t gppu;
uint8_t gpinten;
int res;
_acquire(dev);
/* read the I/O direction configuration and GPIO pull-up register */
if ((res = _read(dev, MCP23X17_REG_IODIR + port, &iodir, 1)) ||
(res = _read(dev, MCP23X17_REG_GPPU + port, &gppu, 1)) ||
(res = _read(dev, MCP23X17_REG_GPINTEN + port, &gpinten, 1))) {
DEBUG_DEV("error reading IODIR, GPPU or GPINTEN register", dev);
_release(dev);
return res;
}
/* set default configuration first */
iodir |= pin_bit; /* input pin */
gppu &= ~pin_bit; /* no pull-up */
gpinten &= ~pin_bit; /* interrupt disabled */
dev->od_pins &= ~(1 << pin); /* open-drain flag not set */
/* override only non default settings */
switch (mode) {
case GPIO_OUT: iodir &= ~pin_bit; /* change direction to output */
break;
case GPIO_OD_PU: gppu |= pin_bit; /* enable pull-up */
/* intentionally falls through */
case GPIO_OD: dev->od_pins |= (1 << pin); /* set open-drain flag */
_update_pin(dev, MCP23X17_REG_GPIO, pin, 0); /* clear pin */
break;
case GPIO_IN_PU: gppu |= pin_bit; /* enable pull-up */
/* intentionally falls through */
case GPIO_IN: break;
default: DEBUG_DEV("invalid pin mode", dev);
_release(dev);
return -MCP23X17_ERROR_INV_MODE;
}
/* write back the I/O direction configuration and GPIO pull-up register */
if ((res = _write(dev, MCP23X17_REG_GPINTEN + port, &gpinten, 1)) ||
(res = _write(dev, MCP23X17_REG_IODIR + port, &iodir, 1)) ||
(res = _write(dev, MCP23X17_REG_GPPU + port, &gppu, 1))) {
DEBUG_DEV("error writing IODIR, GPPU or GPINTEN register", dev);
_release(dev);
return res;
}
_release(dev);
return MCP23X17_OK;
}
int mcp23x17_gpio_read(mcp23x17_t *dev, gpio_t pin)
{
assert(dev);
assert(pin < MCP23X17_GPIO_PIN_NUM);
DEBUG_DEV("pin %u", dev, pin);
uint8_t port = pin >> 3;
pin -= (port << 3);
uint8_t gpio;
/* read the GPIO port register */
_acquire(dev);
int res = _read(dev, MCP23X17_REG_GPIO + port, &gpio, 1);
_release(dev);
if (res) {
DEBUG_DEV("error reading GPIO register", dev);
return res;
}
return (gpio & (1 << pin)) ? 1 : 0;
}
void mcp23x17_gpio_write(mcp23x17_t *dev, gpio_t pin, int value)
{
assert(dev);
assert(pin < MCP23X17_GPIO_PIN_NUM);
DEBUG_DEV("pin %u, value %d", dev, pin, value);
_acquire(dev);
/* check whether it is an emulated OD pin */
if (dev->od_pins & (1 << pin)) {
if (value) {
/* simply set direction to input */
_update_pin(dev, MCP23X17_REG_IODIR, pin, 1);
}
else {
/* set direction to output, was cleared during initialization */
_update_pin(dev, MCP23X17_REG_IODIR, pin, 0);
}
}
else {
_update_pin(dev, MCP23X17_REG_GPIO, pin, value);
}
_release(dev);
}
void mcp23x17_gpio_set(mcp23x17_t *dev, gpio_t pin)
{
mcp23x17_gpio_write(dev, pin, 1);
}
void mcp23x17_gpio_clear(mcp23x17_t *dev, gpio_t pin)
{
mcp23x17_gpio_write(dev, pin, 0);
}
void mcp23x17_gpio_toggle(mcp23x17_t *dev, gpio_t pin)
{
DEBUG_DEV("pin %u", dev, pin);
mcp23x17_gpio_write(dev, pin, mcp23x17_gpio_read(dev, pin) ? 0 : 1);
}
#if IS_USED(MODULE_MCP23X17_IRQ)
int mcp23x17_gpio_init_int(mcp23x17_t *dev, gpio_t pin,
gpio_mode_t mode,
gpio_flank_t flank,
gpio_cb_t isr,
void *arg)
{
assert(dev);
assert(pin < MCP23X17_GPIO_PIN_NUM);
assert(isr != NULL);
DEBUG_DEV("pin %u, mode %d, flank %d, isr %p, arg %p",
dev, pin, mode, flank, isr, arg);
/* initialize the pin */
int res = mcp23x17_gpio_init(dev, pin, mode);
if (res != MCP23X17_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;
break;
default: DEBUG_DEV("invalid flank %d for pin %d", dev, flank, pin);
return -MCP23X17_ERROR_INV_FLANK;
}
/* enable the interrupt */
mcp23x17_gpio_irq_enable(dev, pin);
return MCP23X17_OK;
}
void mcp23x17_gpio_irq_enable(mcp23x17_t *dev, gpio_t pin)
{
/* some parameter sanity checks */
assert(dev != NULL);
assert(pin < MCP23X17_GPIO_PIN_NUM);
DEBUG_DEV("pin %u", dev, pin);
_acquire(dev);
/* delete pending interrupts */
uint8_t regs[4];
/* read the GPIO port register */
if (_read(dev, MCP23X17_REG_INTF, regs, ARRAY_SIZE(regs))) {
DEBUG_DEV("error reading INTF and INTCAP registers", dev);
_release(dev);
return;
}
_update_pin(dev, MCP23X17_REG_GPINTEN, pin, 1);
_release(dev);
}
void mcp23x17_gpio_irq_disable(mcp23x17_t *dev, gpio_t pin)
{
/* some parameter sanity checks */
assert(dev != NULL);
assert(pin < MCP23X17_GPIO_PIN_NUM);
DEBUG_DEV("pin %u", dev, pin);
_acquire(dev);
_update_pin(dev, MCP23X17_REG_GPINTEN, pin, 0);
_release(dev);
}
/* interrupt service routine for IRQs */
static void _irq_isr(void *arg)
{
/* some parameter sanity checks */
assert(arg != NULL);
/* just indicate that an interrupt occurred and return */
event_post(MCP23X17_EVENT_PRIO, (event_t*)&((mcp23x17_t*)arg)->irq_event);
}
/* handle one IRQ event of device referenced by the event */
static void _irq_handler(event_t* event)
{
mcp23x17_irq_event_t* irq_event = (mcp23x17_irq_event_t*)event;
assert(irq_event != NULL);
assert(irq_event->dev);
mcp23x17_t *dev = irq_event->dev;
DEBUG_DEV("event %p", dev, event);
uint8_t regs[4];
/* read the GPIO port register */
_acquire(dev);
if (_read(dev, MCP23X17_REG_INTF, regs, ARRAY_SIZE(regs))) {
DEBUG_DEV("error reading INTF and INTCAP registers", dev);
_release(dev);
return;
}
_release(dev);
/* iterate over all pins to check whether ISR has to be called */
for (unsigned i = 0; i < MCP23X17_GPIO_PIN_NUM; i++) {
uint8_t port = i >> 3;
uint8_t pin_bit = 1 << (i & 0x07);
/* test whether interrupt flag is set and cb is defined for the pin */
if ((regs[port] & pin_bit) && dev->isr[i].cb != NULL) {
/* check the flank and the activated flank mode */
if (dev->flank[i] == GPIO_BOTH || /* no matter what flank */
((regs[2 + port] & pin_bit) == 0 && /* new value is 0 -> falling flank */
(dev->flank[i] == GPIO_FALLING)) ||
((regs[2 + port] & pin_bit) && /* new value is 1 -> rising flank */
(dev->flank[i] == GPIO_RISING))) {
/* call the ISR */
dev->isr[i].cb(dev->isr[i].arg);
}
}
}
}
#endif /* MODULE_MCP23X17_IRQ */
/* internal functions */
static void _acquire(const mcp23x17_t *dev)
{
#if IS_USED(MODULE_MCP23X17_SPI)
if (dev->params.if_params.type == MCP23X17_SPI) {
spi_acquire(_SPI_DEV, _SPI_CS, SPI_MODE_0, _SPI_CLK);
}
#endif
#if IS_USED(MODULE_MCP23X17_I2C)
if (dev->params.if_params.type == MCP23X17_I2C) {
i2c_acquire(_I2C_DEV);
}
#endif
}
static void _release(const mcp23x17_t *dev)
{
#if IS_USED(MODULE_MCP23X17_SPI)
if (dev->params.if_params.type == MCP23X17_SPI) {
spi_release(_SPI_DEV);
}
#endif
#if IS_USED(MODULE_MCP23X17_I2C)
if (dev->params.if_params.type == MCP23X17_I2C) {
i2c_release(_I2C_DEV);
}
#endif
}
static int _read(const mcp23x17_t *dev, uint8_t reg, uint8_t *data, size_t len)
{
DEBUG_DEV("reg=%02x data=%p len=%d", dev, reg, data, len);
int res = MCP23X17_OK;
#if IS_USED(MODULE_MCP23X17_SPI)
if (dev->params.if_params.type == MCP23X17_SPI) {
spi_transfer_byte(_SPI_DEV, _SPI_CS, true, (_ADDR << 1) | 1);
spi_transfer_byte(_SPI_DEV, _SPI_CS, true, reg);
spi_transfer_bytes(_SPI_DEV, _SPI_CS, false, NULL, data, len);
}
#endif
#if IS_USED(MODULE_MCP23X17_I2C)
if (dev->params.if_params.type == MCP23X17_I2C) {
int res = i2c_read_regs(_I2C_DEV, _ADDR, reg, data, len, 0);
if (res) {
DEBUG_DEV("I2C read error: %d (%s)", dev, res, strerror(res * -1));
return -MCP23X17_ERROR_I2C;
}
else {
return MCP23X17_OK;
}
}
#endif
if (ENABLE_DEBUG) {
printf("[mcp23x17] %s dev=%" PRIxPTR " addr=%02x reg=%02x read: ",
__func__, (uintptr_t)dev, dev->params.addr, reg);
for (uint8_t i = 1; i < len + 1; i++) {
printf("%02x ", data[i]);
}
printf("\n");
}
return res;
}
static int _write(const mcp23x17_t *dev, uint8_t reg,
const uint8_t *data, size_t len)
{
DEBUG_DEV("reg=%02x data=%p len=%d", dev, reg, data, len);
if (ENABLE_DEBUG) {
printf("[mcp23x17] %s dev=%" PRIxPTR " addr=%02x reg=%02x write: ",
__func__, (uintptr_t)dev, dev->params.addr, reg);
for (uint8_t i = 1; i < len + 1; i++) {
printf("%02x ", data[i]);
}
printf("\n");
}
#if IS_USED(MODULE_MCP23X17_SPI)
if (dev->params.if_params.type == MCP23X17_SPI) {
spi_transfer_byte(_SPI_DEV, _SPI_CS, true, (_ADDR << 1));
spi_transfer_byte(_SPI_DEV, _SPI_CS, true, reg);
spi_transfer_bytes(_SPI_DEV, _SPI_CS, false, data, NULL, len);
return MCP23X17_OK;
}
#endif
#if IS_USED(MODULE_MCP23X17_I2C)
if (dev->params.if_params.type == MCP23X17_I2C) {
int res = i2c_write_regs(_I2C_DEV, _ADDR, reg, data, len, 0);
if (res) {
DEBUG_DEV("I2C write error: %d (%s)", dev, res, strerror(res * -1));
return -MCP23X17_ERROR_I2C;
}
else {
return MCP23X17_OK;
}
}
#endif
return -MCP23X17_ERROR_NO_DEV;
}
static int _update_pin(const mcp23x17_t *dev, uint8_t reg, gpio_t pin, int value)
{
/* some parameter sanity checks */
assert(dev != NULL);
assert(pin < MCP23X17_GPIO_PIN_NUM);
DEBUG_DEV("pin %d, value %d, reg %02x", dev, pin, value, reg);
uint8_t data;
uint8_t port = pin >> 3;
pin -= (port << 3);
/* read the register */
int res = _read(dev, reg + port, &data, 1);
if (value) {
data |= (1 << pin);
}
else {
data &= ~(1 << pin);
}
/* write back the register */
res |= _write(dev, reg + port, &data, 1);
return res;
}

View File

@ -0,0 +1,57 @@
/*
* 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.
*/
/**
* @ingroup drivers_mcp23x17
* @brief MCP23x17 adaption to the RIOT actuator/sensor interface
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*/
#include <string.h>
#include "saul.h"
#include "mcp23x17.h"
#if IS_USED(MODULE_SAUL_GPIO)
extern mcp23x17_t mcp23x17_devs[];
static int _read(const void *dev, phydat_t *res)
{
const mcp23x17_saul_gpio_params_t *p = (const mcp23x17_saul_gpio_params_t *)dev;
int inverted = (p->gpio.flags & SAUL_GPIO_INVERTED);
res->val[0] = (mcp23x17_gpio_read(&mcp23x17_devs[p->dev],
p->gpio.pin)) ? !inverted : inverted;
res->unit = UNIT_BOOL;
res->scale = 0;
return 1;
}
static int _write(const void *dev, const phydat_t *state)
{
const mcp23x17_saul_gpio_params_t *p = (const mcp23x17_saul_gpio_params_t *)dev;
int inverted = (p->gpio.flags & SAUL_GPIO_INVERTED);
int value = (state->val[0] ? !inverted : inverted);
mcp23x17_gpio_write(&mcp23x17_devs[p->dev], p->gpio.pin, value);
return 1;
}
const saul_driver_t mcp23x17_gpio_out_saul_driver = {
.read = _read,
.write = _write,
.type = SAUL_ACT_SWITCH
};
const saul_driver_t mcp23x17_gpio_in_saul_driver = {
.read = _read,
.write = saul_write_notsup,
.type = SAUL_SENSE_BTN
};
#endif /* MODULE_SAUL_GPIO */