/* * Copyright (c) 2017, George Oikonomou - http://www.spd.gr * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ /*---------------------------------------------------------------------------*/ /** * \addtogroup button_hal * @{ * * \file * Platform-independent button driver. */ /*---------------------------------------------------------------------------*/ #include "contiki.h" #include "sys/process.h" #include "sys/ctimer.h" #include "sys/critical.h" #include "dev/gpio-hal.h" #include "dev/button-hal.h" #include #include #include /*---------------------------------------------------------------------------*/ PROCESS(button_hal_process, "Button HAL process"); /*---------------------------------------------------------------------------*/ process_event_t button_hal_press_event; process_event_t button_hal_release_event; process_event_t button_hal_periodic_event; /*---------------------------------------------------------------------------*/ /* A mask of all pins that have changed state since the last process poll */ static volatile gpio_hal_pin_mask_t pmask; /*---------------------------------------------------------------------------*/ extern button_hal_button_t *button_hal_buttons[]; /*---------------------------------------------------------------------------*/ /* Common handler for all handler events, and register it with the GPIO HAL */ static gpio_hal_event_handler_t button_event_handler; /*---------------------------------------------------------------------------*/ static void duration_exceeded_callback(void *btn) { button_hal_button_t *button = (button_hal_button_t *)btn; button->press_duration_seconds++; ctimer_set(&button->duration_ctimer, CLOCK_SECOND, duration_exceeded_callback, button); process_post(PROCESS_BROADCAST, button_hal_periodic_event, button); } /*---------------------------------------------------------------------------*/ static void debounce_handler(void *btn) { button_hal_button_t *button; int expired; uint8_t button_state; button = (button_hal_button_t *)btn; /* * A new debounce may have been triggered after expiration of the previous * one but before we got called. */ if(!ctimer_expired(&button->debounce_ctimer)) { return; } expired = ctimer_expired(&button->duration_ctimer); button_state = button_hal_get_state(button); /* * A debounce timer expired. Inspect the button's state. If the button's * state is the same as it was before, then we ignore this as noise. */ if(button_state == BUTTON_HAL_STATE_PRESSED && expired) { /* * Button is pressed and no tick counter running. Treat as new press. * Include the debounce duration in the first periodic, so that the * callback will happen 1 second after the button press, not 1 second * after the end of the debounce. Notify process about the press event. */ button->press_duration_seconds = 0; ctimer_set(&button->duration_ctimer, CLOCK_SECOND - BUTTON_HAL_DEBOUNCE_DURATION, duration_exceeded_callback, button); process_post(PROCESS_BROADCAST, button_hal_press_event, button); } else if(button_state == BUTTON_HAL_STATE_RELEASED && expired == 0) { /* * Button is released and there is a duration_ctimer running. Treat this * as a new release and notify processes. */ ctimer_stop(&button->duration_ctimer); process_post(PROCESS_BROADCAST, button_hal_release_event, button); } } /*---------------------------------------------------------------------------*/ static void press_release_handler(gpio_hal_pin_mask_t pin_mask) { pmask |= pin_mask; process_poll(&button_hal_process); } /*---------------------------------------------------------------------------*/ button_hal_button_t * button_hal_get_by_id(uint8_t unique_id) { button_hal_button_t **button; for(button = button_hal_buttons; *button != NULL; button++) { if((*button)->unique_id == unique_id) { return *button; } } return NULL; } /*---------------------------------------------------------------------------*/ button_hal_button_t * button_hal_get_by_index(uint8_t index) { if(index >= button_hal_button_count) { return NULL; } return button_hal_buttons[index]; } /*---------------------------------------------------------------------------*/ uint8_t button_hal_get_state(button_hal_button_t *button) { uint8_t pin_state = gpio_hal_arch_read_pin(button->pin); if((pin_state == 0 && button->negative_logic == true) || (pin_state == 1 && button->negative_logic == false)) { return BUTTON_HAL_STATE_PRESSED; } return BUTTON_HAL_STATE_RELEASED; } /*---------------------------------------------------------------------------*/ void button_hal_init() { button_hal_button_t **button; gpio_hal_pin_cfg_t cfg; button_hal_press_event = process_alloc_event(); button_hal_release_event = process_alloc_event(); button_hal_periodic_event = process_alloc_event(); button_event_handler.pin_mask = 0; button_event_handler.handler = press_release_handler; for(button = button_hal_buttons; *button != NULL; button++) { cfg = GPIO_HAL_PIN_CFG_EDGE_BOTH | GPIO_HAL_PIN_CFG_INT_ENABLE | (*button)->pull; gpio_hal_arch_pin_set_input((*button)->pin); gpio_hal_arch_pin_cfg_set((*button)->pin, cfg); gpio_hal_arch_interrupt_enable((*button)->pin); button_event_handler.pin_mask |= gpio_hal_pin_to_mask((*button)->pin); } process_start(&button_hal_process, NULL); gpio_hal_register_handler(&button_event_handler); } /*---------------------------------------------------------------------------*/ PROCESS_THREAD(button_hal_process, ev, data) { int_master_status_t status; gpio_hal_pin_mask_t pins; button_hal_button_t **button; PROCESS_BEGIN(); while(1) { PROCESS_YIELD_UNTIL(ev == PROCESS_EVENT_POLL); status = critical_enter(); pins = pmask; pmask = 0; critical_exit(status); for(button = button_hal_buttons; *button != NULL; button++) { if(gpio_hal_pin_to_mask((*button)->pin) & pins) { /* Ignore all button presses/releases during its debounce */ if(ctimer_expired(&(*button)->debounce_ctimer)) { /* * Here we merely set a debounce timer. At the end of the debounce we * will inspect the button's state and we will take action only if it * has changed. * * This is to prevent erroneous edge detections due to interference. */ ctimer_set(&(*button)->debounce_ctimer, BUTTON_HAL_DEBOUNCE_DURATION, debounce_handler, *button); } } } } PROCESS_END(); } /*---------------------------------------------------------------------------*/ /** @} */