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:
parent
3d5582d563
commit
f8585d81c7
@ -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
687
drivers/include/mcp23x17.h
Normal 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
|
||||
|
||||
/** @} */
|
||||
1
drivers/mcp23x17/Makefile
Normal file
1
drivers/mcp23x17/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
||||
38
drivers/mcp23x17/Makefile.dep
Normal file
38
drivers/mcp23x17/Makefile.dep
Normal 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
|
||||
12
drivers/mcp23x17/Makefile.include
Normal file
12
drivers/mcp23x17/Makefile.include
Normal 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)
|
||||
172
drivers/mcp23x17/include/mcp23x17_params.h
Normal file
172
drivers/mcp23x17/include/mcp23x17_params.h
Normal 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
|
||||
|
||||
/** @} */
|
||||
80
drivers/mcp23x17/include/mcp23x17_regs.h
Normal file
80
drivers/mcp23x17/include/mcp23x17_regs.h
Normal 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
622
drivers/mcp23x17/mcp23x17.c
Normal 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;
|
||||
}
|
||||
57
drivers/mcp23x17/mcp23x17_saul.c
Normal file
57
drivers/mcp23x17/mcp23x17_saul.c
Normal 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 */
|
||||
Loading…
x
Reference in New Issue
Block a user