mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
c2c2cc8592
Since https://github.com/RIOT-OS/RIOT/pull/20935 gpio_write() uses a `bool` instead of an `int`. This does the same treatment for `gpio_read()`. This does indeed add an instruction to `gpio_read()` implementations. However, users caring about an instruction more are better served with `gpio_ll_read()` anyway. And `gpio_read() == 1` is often seen in newcomer's code, which would now work as expected.
457 lines
12 KiB
C
457 lines
12 KiB
C
/*
|
|
* Copyright (C) 2015 HAW Hamburg
|
|
* 2016 INRIA
|
|
* 2023 Hugues Larrive
|
|
* 2023 Gerson Fernando Budke
|
|
|
|
*
|
|
* 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 cpu_atmega_common
|
|
* @ingroup drivers_periph_gpio
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Low-level GPIO driver implementation for ATmega family
|
|
*
|
|
* @author René Herthel <rene-herthel@outlook.de>
|
|
* @author Francisco Acosta <francisco.acosta@inria.fr>
|
|
* @author Laurent Navet <laurent.navet@gmail.com>
|
|
* @author Robert Hartung <hartung@ibr.cs.tu-bs.de>
|
|
* @author Torben Petersen <petersen@ibr.cs.tu-bs.de>
|
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
|
* @author Hugues Larrive <hugues.larrive@pm.me>
|
|
* @author Gerson Fernando Budke <nandojve@gmail.com>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <avr/interrupt.h>
|
|
|
|
#include "cpu.h"
|
|
#include "irq.h"
|
|
#include "board.h"
|
|
#include "periph/gpio.h"
|
|
#include "periph_conf.h"
|
|
#include "periph_cpu.h"
|
|
#include "atmega_gpio.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
#ifdef MODULE_PERIPH_GPIO_IRQ
|
|
|
|
static gpio_isr_ctx_t config[GPIO_EXT_INT_NUMOF];
|
|
|
|
/* Detects amount of possible PCINTs */
|
|
#if defined(MODULE_ATMEGA_PCINT0) || defined(MODULE_ATMEGA_PCINT1) || \
|
|
defined(MODULE_ATMEGA_PCINT2) || defined(MODULE_ATMEGA_PCINT3)
|
|
#include "atmega_pcint.h"
|
|
|
|
#define ENABLE_PCINT
|
|
|
|
/* Check which pcints should be enabled */
|
|
#if defined(MODULE_ATMEGA_PCINT0) && !defined(ATMEGA_PCINT_MAP_PCINT0)
|
|
# error "Either mapping for pin change interrupt bank 0 is missing or not supported by the MCU"
|
|
#endif
|
|
|
|
#if defined(MODULE_ATMEGA_PCINT1) && !defined(ATMEGA_PCINT_MAP_PCINT1)
|
|
# error "Either mapping for pin change interrupt bank 1 is missing or not supported by the MCU"
|
|
#endif
|
|
|
|
#if defined(MODULE_ATMEGA_PCINT2) && !defined(ATMEGA_PCINT_MAP_PCINT2)
|
|
# error "Either mapping for pin change interrupt bank 2 is missing or not supported by the MCU"
|
|
#endif
|
|
|
|
#if defined(MODULE_ATMEGA_PCINT3) && !defined(ATMEGA_PCINT_MAP_PCINT3)
|
|
# error "Either mapping for pin change interrupt bank 3 is missing or not supported by the MCU"
|
|
#endif
|
|
|
|
/**
|
|
* @brief Use anonymous enum as for addressing the @ref pcint_state
|
|
*/
|
|
enum {
|
|
#ifdef MODULE_ATMEGA_PCINT0
|
|
PCINT0_IDX, /**< Index of PCINT0, if used */
|
|
#endif /* MODULE_ATMEGA_PCINT0 */
|
|
#ifdef MODULE_ATMEGA_PCINT1
|
|
PCINT1_IDX, /**< Index of PCINT1, if used */
|
|
#endif /* MODULE_ATMEGA_PCINT1 */
|
|
#ifdef MODULE_ATMEGA_PCINT2
|
|
PCINT2_IDX, /**< Index of PCINT2, if used */
|
|
#endif /* MODULE_ATMEGA_PCINT2 */
|
|
#ifdef MODULE_ATMEGA_PCINT3
|
|
PCINT3_IDX, /**< Index of PCINT3, if used */
|
|
#endif /* MODULE_ATMEGA_PCINT3 */
|
|
PCINT_NUM_BANKS /**< Number of PCINT banks used */
|
|
};
|
|
|
|
/**
|
|
* @brief stores the last pcint state of each port
|
|
*/
|
|
static uint8_t pcint_state[PCINT_NUM_BANKS];
|
|
|
|
/**
|
|
* @brief stores all cb and args for all defined pcint.
|
|
*/
|
|
typedef struct {
|
|
gpio_flank_t flank; /**< type of interrupt the flank should be triggered on */
|
|
gpio_cb_t cb; /**< interrupt callback */
|
|
void *arg; /**< optional argument */
|
|
} gpio_isr_ctx_pcint_t;
|
|
|
|
/**
|
|
* @brief
|
|
*/
|
|
static const gpio_t pcint_mapping[] = {
|
|
#ifdef MODULE_ATMEGA_PCINT0
|
|
ATMEGA_PCINT_MAP_PCINT0,
|
|
#endif /* PCINT0_IDX */
|
|
#ifdef MODULE_ATMEGA_PCINT1
|
|
ATMEGA_PCINT_MAP_PCINT1,
|
|
#endif /* PCINT1_IDX */
|
|
#ifdef MODULE_ATMEGA_PCINT2
|
|
ATMEGA_PCINT_MAP_PCINT2,
|
|
#endif /* PCINT2_IDX */
|
|
#ifdef MODULE_ATMEGA_PCINT3
|
|
ATMEGA_PCINT_MAP_PCINT3,
|
|
#endif /* PCINT3_IDX */
|
|
};
|
|
/**
|
|
* @brief
|
|
*/
|
|
static gpio_isr_ctx_pcint_t pcint_config[8 * PCINT_NUM_BANKS];
|
|
#endif /* MODULE_ATMEGA_PCINTn */
|
|
|
|
#endif /* MODULE_PERIPH_GPIO_IRQ */
|
|
|
|
int gpio_init(gpio_t pin, gpio_mode_t mode)
|
|
{
|
|
uint8_t pin_mask = (1 << atmega_pin_num(pin));
|
|
|
|
switch (mode) {
|
|
case GPIO_OUT:
|
|
_SFR_MEM8(atmega_ddr_addr(pin)) |= pin_mask;
|
|
break;
|
|
case GPIO_IN:
|
|
_SFR_MEM8(atmega_ddr_addr(pin)) &= ~pin_mask;
|
|
_SFR_MEM8(atmega_port_addr(pin)) &= ~pin_mask;
|
|
break;
|
|
case GPIO_IN_PU:
|
|
_SFR_MEM8(atmega_ddr_addr(pin)) &= ~pin_mask;
|
|
_SFR_MEM8(atmega_port_addr(pin)) |= pin_mask;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool gpio_read(gpio_t pin)
|
|
{
|
|
return (_SFR_MEM8(atmega_pin_addr(pin)) & (1 << atmega_pin_num(pin)));
|
|
}
|
|
|
|
void gpio_set(gpio_t pin)
|
|
{
|
|
_SFR_MEM8(atmega_port_addr(pin)) |= (1 << atmega_pin_num(pin));
|
|
}
|
|
|
|
void gpio_clear(gpio_t pin)
|
|
{
|
|
_SFR_MEM8(atmega_port_addr(pin)) &= ~(1 << atmega_pin_num(pin));
|
|
}
|
|
|
|
void gpio_toggle(gpio_t pin)
|
|
{
|
|
if (gpio_read(pin)) {
|
|
gpio_clear(pin);
|
|
}
|
|
else {
|
|
gpio_set(pin);
|
|
}
|
|
}
|
|
|
|
void gpio_write(gpio_t pin, bool value)
|
|
{
|
|
if (value) {
|
|
gpio_set(pin);
|
|
}
|
|
else {
|
|
gpio_clear(pin);
|
|
}
|
|
}
|
|
|
|
#ifdef MODULE_PERIPH_GPIO_IRQ
|
|
static inline int8_t _int_num(gpio_t pin)
|
|
{
|
|
uint8_t num;
|
|
const gpio_t ext_ints[GPIO_EXT_INT_NUMOF] = CPU_ATMEGA_EXT_INTS;
|
|
|
|
/* find pin in ext_ints array to get the interrupt number */
|
|
for (num = 0; num < GPIO_EXT_INT_NUMOF; num++) {
|
|
if (pin == ext_ints[num]) {
|
|
return num;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
#ifdef ENABLE_PCINT
|
|
static inline int pcint_init_int(gpio_t pin, gpio_mode_t mode,
|
|
gpio_flank_t flank,
|
|
gpio_cb_t cb, void *arg)
|
|
{
|
|
int8_t offset = -1;
|
|
uint8_t pin_num = atmega_pin_num(pin);
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(pcint_mapping); i++) {
|
|
if (pin != GPIO_UNDEF && pin == pcint_mapping[i]) {
|
|
offset = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if pcint was not found: return -1 */
|
|
if (offset < 0) {
|
|
return offset;
|
|
}
|
|
|
|
uint8_t bank = offset / 8;
|
|
uint8_t bank_idx = offset % 8;
|
|
DEBUG("PCINT enabled for bank %u offset %u\n",
|
|
(unsigned)bank, (unsigned)offset);
|
|
|
|
/* save configuration for pin change interrupt */
|
|
pcint_config[offset].flank = flank;
|
|
pcint_config[offset].arg = arg;
|
|
pcint_config[offset].cb = cb;
|
|
|
|
/* init gpio */
|
|
gpio_init(pin, mode);
|
|
/* configure pcint */
|
|
cli();
|
|
switch (bank) {
|
|
#ifdef MODULE_ATMEGA_PCINT0
|
|
case PCINT0_IDX:
|
|
PCMSK0 |= (1 << bank_idx);
|
|
PCICR |= (1 << PCIE0);
|
|
break;
|
|
#endif /* MODULE_ATMEGA_PCINT0 */
|
|
#ifdef MODULE_ATMEGA_PCINT1
|
|
case PCINT1_IDX:
|
|
PCMSK1 |= (1 << bank_idx);
|
|
PCICR |= (1 << PCIE1);
|
|
break;
|
|
#endif /* MODULE_ATMEGA_PCINT1 */
|
|
#ifdef MODULE_ATMEGA_PCINT2
|
|
case PCINT2_IDX:
|
|
PCMSK2 |= (1 << bank_idx);
|
|
PCICR |= (1 << PCIE2);
|
|
break;
|
|
#endif /* MODULE_ATMEGA_PCINT2 */
|
|
#ifdef MODULE_ATMEGA_PCINT3
|
|
case PCINT3_IDX:
|
|
PCMSK3 |= (1 << bank_idx);
|
|
PCICR |= (1 << PCIE3);
|
|
break;
|
|
#endif /* MODULE_ATMEGA_PCINT3 */
|
|
default:
|
|
return -1;
|
|
break;
|
|
}
|
|
/* As ports are mixed in a bank (e.g. PCINT0), we can only save a single bit here! */
|
|
uint8_t port_value = (_SFR_MEM8(atmega_pin_addr( pin )));
|
|
uint8_t pin_mask = (1 << pin_num);
|
|
uint8_t pin_value = ((port_value & pin_mask) != 0);
|
|
if (pin_value) {
|
|
pcint_state[bank] |= pin_mask;
|
|
}
|
|
else {
|
|
pcint_state[bank] &= ~pin_mask;
|
|
}
|
|
sei();
|
|
return 0;
|
|
}
|
|
#endif /* ENABLE_PCINT */
|
|
|
|
int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank,
|
|
gpio_cb_t cb, void *arg)
|
|
{
|
|
int8_t int_num = _int_num(pin);
|
|
|
|
/* mode not supported */
|
|
if ((mode != GPIO_IN) && (mode != GPIO_IN_PU)) {
|
|
return -1;
|
|
}
|
|
|
|
/* not a valid interrupt pin. Set as pcint instead if pcints are enabled */
|
|
if (int_num < 0) {
|
|
#ifdef ENABLE_PCINT
|
|
/* If pin change interrupts are enabled, enable mask and interrupt */
|
|
return pcint_init_int(pin, mode, flank, cb, arg);
|
|
#else
|
|
return -1;
|
|
#endif /* ENABLE_PCINT */
|
|
}
|
|
|
|
/* flank not supported */
|
|
if (flank > GPIO_RISING) {
|
|
return -1;
|
|
}
|
|
|
|
gpio_init(pin, mode);
|
|
|
|
/* clear global interrupt flag */
|
|
cli();
|
|
|
|
/* enable interrupt number int_num */
|
|
#if defined(EIFR) && defined(EIMSK)
|
|
EIFR |= (1 << int_num);
|
|
EIMSK |= (1 << int_num);
|
|
#elif defined(GIFR) && defined(GICR)
|
|
GIFR |= (1 << (INTF0 + int_num));
|
|
GICR |= (1 << (INT0 + int_num));
|
|
#endif
|
|
|
|
/* apply flank to interrupt number int_num */
|
|
#if defined(EICRB)
|
|
if (int_num >= 4) {
|
|
EICRB &= ~(0x3 << ((int_num % 4) * 2));
|
|
EICRB |= (flank << ((int_num % 4) * 2));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
#if defined(EICRA)
|
|
EICRA &= ~(0x3 << (int_num * 2));
|
|
EICRA |= (flank << (int_num * 2));
|
|
#elif defined(MCUCR)
|
|
MCUCR &= ~(0x3 << (int_num * 2));
|
|
MCUCR |= (flank << (int_num * 2));
|
|
#endif
|
|
}
|
|
|
|
/* set callback */
|
|
config[int_num].cb = cb;
|
|
config[int_num].arg = arg;
|
|
|
|
/* set global interrupt flag */
|
|
sei();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void gpio_irq_enable(gpio_t pin)
|
|
{
|
|
#if defined(EIFR) && defined(EIMSK)
|
|
EIFR |= (1 << _int_num(pin));
|
|
EIMSK |= (1 << _int_num(pin));
|
|
#elif defined(GIFR) && defined(GICR)
|
|
GIFR |= (1 << (INTF0 + _int_num(pin)));
|
|
GICR |= (1 << (INT0 + _int_num(pin)));
|
|
#endif
|
|
}
|
|
|
|
void gpio_irq_disable(gpio_t pin)
|
|
{
|
|
#if defined(EIMSK)
|
|
EIMSK &= ~(1 << _int_num(pin));
|
|
#elif defined(GICR)
|
|
GICR &= ~(1 << (INT0 + _int_num(pin)));
|
|
#endif
|
|
}
|
|
|
|
static inline void irq_handler(uint8_t int_num)
|
|
{
|
|
config[int_num].cb(config[int_num].arg);
|
|
}
|
|
|
|
#ifdef ENABLE_PCINT
|
|
/* inline function that is used by the PCINT ISR */
|
|
static inline void pcint_handler(uint8_t bank, uint8_t enabled_pcints)
|
|
{
|
|
/* Find right item */
|
|
uint8_t idx = 0;
|
|
|
|
while (enabled_pcints > 0) {
|
|
/* check if this pin is enabled & has changed */
|
|
if (enabled_pcints & 0x1) {
|
|
/* get pin from mapping (assumes 8 entries per bank!) */
|
|
gpio_t pin = pcint_mapping[bank * 8 + idx];
|
|
/* re-construct mask from pin */
|
|
uint8_t pin_mask = (1 << (atmega_pin_num(pin)));
|
|
uint8_t idx_mask = (1 << idx);
|
|
uint8_t port_value = (_SFR_MEM8(atmega_pin_addr( pin )));
|
|
uint8_t pin_value = ((port_value & pin_mask) != 0);
|
|
uint8_t old_state = ((pcint_state[bank] & idx_mask) != 0);
|
|
gpio_isr_ctx_pcint_t *conf = &pcint_config[bank * 8 + idx];
|
|
if (old_state != pin_value) {
|
|
pcint_state[bank] ^= idx_mask;
|
|
if ((conf->flank == GPIO_BOTH ||
|
|
(pin_value && conf->flank == GPIO_RISING) ||
|
|
(!pin_value && conf->flank == GPIO_FALLING))) {
|
|
/* execute callback routine */
|
|
conf->cb(conf->arg);
|
|
}
|
|
}
|
|
}
|
|
enabled_pcints = enabled_pcints >> 1;
|
|
idx++;
|
|
}
|
|
}
|
|
#ifdef MODULE_ATMEGA_PCINT0
|
|
AVR8_ISR(PCINT0_vect, pcint_handler, PCINT0_IDX, PCMSK0);
|
|
#endif /* MODULE_ATMEGA_PCINT0 */
|
|
|
|
#ifdef MODULE_ATMEGA_PCINT1
|
|
AVR8_ISR(PCINT1_vect, pcint_handler, PCINT1_IDX, PCMSK1);
|
|
#endif /* MODULE_ATMEGA_PCINT1 */
|
|
|
|
#ifdef MODULE_ATMEGA_PCINT2
|
|
AVR8_ISR(PCINT2_vect, pcint_handler, PCINT2_IDX, PCMSK2);
|
|
#endif /* MODULE_ATMEGA_PCINT2 */
|
|
|
|
#ifdef MODULE_ATMEGA_PCINT3
|
|
AVR8_ISR(PCINT3_vect, pcint_handler, PCINT3_IDX, PCMSK3);
|
|
#endif /* MODULE_ATMEGA_PCINT3 */
|
|
|
|
#endif /* ENABLE_PCINT */
|
|
|
|
AVR8_ISR(INT0_vect, irq_handler, 0); /**< predefined interrupt pin */
|
|
AVR8_ISR(INT1_vect, irq_handler, 1); /**< predefined interrupt pin */
|
|
|
|
#if defined(INT2_vect)
|
|
AVR8_ISR(INT2_vect, irq_handler, 2); /**< predefined interrupt pin */
|
|
#endif
|
|
|
|
#if defined(INT3_vect)
|
|
AVR8_ISR(INT3_vect, irq_handler, 3); /**< predefined interrupt pin */
|
|
#endif
|
|
|
|
#if defined(INT4_vect)
|
|
AVR8_ISR(INT4_vect, irq_handler, 4); /**< predefined interrupt pin */
|
|
#endif
|
|
|
|
#if defined(INT5_vect)
|
|
AVR8_ISR(INT5_vect, irq_handler, 5); /**< predefined interrupt pin */
|
|
#endif
|
|
|
|
#if defined(INT6_vect)
|
|
AVR8_ISR(INT6_vect, irq_handler, 6); /**< predefined interrupt pin */
|
|
#endif
|
|
|
|
#if defined(INT7_vect)
|
|
AVR8_ISR(INT7_vect, irq_handler, 7); /**< predefined interrupt pin */
|
|
#endif
|
|
|
|
#endif /* MODULE_PERIPH_GPIO_IRQ */
|