mirror of
https://github.com/google/pebble
synced 2026-10-16 08:14:11 +02:00
Import of the watch repository from Pebble
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/debounced_button.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/button.h"
|
||||
#include "drivers/exti.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/timer.h"
|
||||
#include "kernel/events.h"
|
||||
#include "kernel/util/stop.h"
|
||||
#include "system/bootbits.h"
|
||||
#include "system/reset.h"
|
||||
#include "util/bitset.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include "projdefs.h"
|
||||
|
||||
// We want TIM4 to run at 32KHz
|
||||
static const uint32_t TIMER_FREQUENCY_HZ = 32000;
|
||||
// Sample the buttons every 2ms to debounce
|
||||
static const uint32_t TIMER_PERIOD_TICKS = 64;
|
||||
// A button must be stable for 20 samples (40ms) to be accepted.
|
||||
static const uint32_t NUM_DEBOUNCE_SAMPLES = 20;
|
||||
|
||||
#define RESET_BUTTONS ((1 << BUTTON_ID_SELECT) | (1 << BUTTON_ID_BACK))
|
||||
|
||||
#define DEBOUNCE_SAMPLES_PER_SECOND (TIMER_FREQUENCY_HZ / TIMER_PERIOD_TICKS)
|
||||
|
||||
// This reset-buttons-held timeout must be lower than the PMIC's back-button-reset timeout,
|
||||
// which is ~8-11s. The spacing between these timeouts should be large enough to avoid accidentally
|
||||
// shutting down the device when a customer is attempting to reset. Therefore the FW's
|
||||
// reset-buttons-held timeout is set to 5 seconds:
|
||||
#define RESET_THRESHOLD_SAMPLES (5 * DEBOUNCE_SAMPLES_PER_SECOND)
|
||||
|
||||
|
||||
static void initialize_button_timer(void) {
|
||||
periph_config_enable(TIM4, RCC_APB1Periph_TIM4);
|
||||
|
||||
NVIC_InitTypeDef NVIC_InitStructure;
|
||||
/* Enable the TIM4 gloabal Interrupt */
|
||||
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
|
||||
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
|
||||
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0b;
|
||||
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
|
||||
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
|
||||
NVIC_Init(&NVIC_InitStructure);
|
||||
|
||||
TIM_TimeBaseInitTypeDef tim_config;
|
||||
TIM_TimeBaseStructInit(&tim_config);
|
||||
tim_config.TIM_Period = TIMER_PERIOD_TICKS;
|
||||
// The timer is on ABP1 which is clocked by PCLK1
|
||||
TimerConfig tc = {
|
||||
.peripheral = TIM4,
|
||||
.config_clock = RCC_APB1Periph_TIM4,
|
||||
};
|
||||
tim_config.TIM_Prescaler = timer_find_prescaler(&tc,
|
||||
TIMER_FREQUENCY_HZ);
|
||||
tim_config.TIM_CounterMode = TIM_CounterMode_Up;
|
||||
TIM_TimeBaseInit(TIM4, &tim_config);
|
||||
|
||||
periph_config_disable(TIM4, RCC_APB1Periph_TIM4);
|
||||
}
|
||||
|
||||
static bool prv_check_timer_enabled(void) {
|
||||
// We're only enabled if all the configuration is correct.
|
||||
|
||||
return (TIM4->CR1 & TIM_CR1_CEN) && // TIM_Cmd
|
||||
(TIM4->DIER & TIM_IT_Update) && // TIM_ITConfig
|
||||
(RCC->APB1ENR & RCC_APB1Periph_TIM4); // RCC_APB1PeriphClockCmd
|
||||
}
|
||||
|
||||
static void disable_button_timer(void) {
|
||||
if (prv_check_timer_enabled()) {
|
||||
TIM_Cmd(TIM4, DISABLE);
|
||||
TIM_ITConfig(TIM4, TIM_IT_Update, DISABLE);
|
||||
periph_config_disable(TIM4, RCC_APB1Periph_TIM4);
|
||||
|
||||
// Allow us to enter stop mode
|
||||
stop_mode_enable(InhibitorButton);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_enable_button_timer(void) {
|
||||
// Don't let the timer interrupt us while we're mucking with it.
|
||||
__disable_irq();
|
||||
if (!prv_check_timer_enabled()) {
|
||||
periph_config_enable(TIM4, RCC_APB1Periph_TIM4);
|
||||
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
|
||||
TIM_Cmd(TIM4, ENABLE);
|
||||
|
||||
// Prevent us from entering stop mode (and disabling the clock timer)
|
||||
stop_mode_disable(InhibitorButton);
|
||||
}
|
||||
__enable_irq();
|
||||
}
|
||||
|
||||
static void prv_button_interrupt_handler(bool *should_context_switch) {
|
||||
prv_enable_button_timer();
|
||||
}
|
||||
|
||||
static void clear_stuck_button(ButtonId button_id) {
|
||||
__disable_irq();
|
||||
|
||||
const uint32_t button_counter_register = RTC_ReadBackupRegister(STUCK_BUTTON_REGISTER);
|
||||
if (button_counter_register != 0) {
|
||||
// Create bitmask with all 1s, except on the counter byte for this button_id in button_counter_register. AND to mask out:
|
||||
const uint32_t updated_button_counter_register = button_counter_register & ~(0xff << (button_id << 3));
|
||||
if (button_counter_register != updated_button_counter_register) {
|
||||
RTC_WriteBackupRegister(STUCK_BUTTON_REGISTER, updated_button_counter_register);
|
||||
}
|
||||
}
|
||||
|
||||
__enable_irq();
|
||||
}
|
||||
|
||||
void debounced_button_init(void) {
|
||||
button_init();
|
||||
|
||||
#if defined(BOARD_SNOWY_BB2) || defined(BOARD_SPALDING_BB2)
|
||||
// Snowy BB2s have a capacitor that results in a really slow rise time (~0.4ms). Sleep for
|
||||
// at least 1 ms to prevent fake button events
|
||||
psleep(2);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < NUM_BUTTONS; ++i) {
|
||||
const ExtiConfig config = BOARD_CONFIG_BUTTON.buttons[i].exti;
|
||||
exti_configure_pin(config, ExtiTrigger_RisingFalling, prv_button_interrupt_handler);
|
||||
exti_enable(config);
|
||||
}
|
||||
|
||||
initialize_button_timer();
|
||||
|
||||
// If someone is holding down a button, we need to start up the timer immediately ourselves as
|
||||
// we won't get a button down interrupt to start it.
|
||||
if (button_get_state_bits() != 0) {
|
||||
prv_enable_button_timer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Interrupt Service Routines
|
||||
///////////////////////////////////////////////////////////
|
||||
void TIM4_IRQHandler(void) {
|
||||
// This array holds the number of samples we have for the button being in a different state than
|
||||
// the current debounced state of the button.
|
||||
static uint32_t s_button_timers[] = {0, 0, 0, 0};
|
||||
// A bitset of the current states of the buttons after the debouncing is done.
|
||||
static uint32_t s_debounced_button_state = 0;
|
||||
|
||||
// Should we tell the scheduler to attempt to context switch after this function has completed?
|
||||
bool should_context_switch = pdFALSE;
|
||||
// Should we power down this interrupt timer once we're done here or should we leave it on?
|
||||
bool can_power_down_tim4 = true;
|
||||
|
||||
// We handle all 4 buttons every time this interrupt is fired.
|
||||
for (int i = 0; i < NUM_BUTTONS; ++i) {
|
||||
// What stable state is the button in, according to the debouncing algorithm?
|
||||
bool debounced_button_state = bitset32_get(&s_debounced_button_state, i);
|
||||
// What is the current physical state of the button?
|
||||
bool is_pressed = button_is_pressed(i);
|
||||
|
||||
if (is_pressed == debounced_button_state) {
|
||||
// If the state is not changing, skip this button.
|
||||
s_button_timers[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Leave the timer running so we can track this button that's changing state.
|
||||
can_power_down_tim4 = false;
|
||||
|
||||
s_button_timers[i] += 1;
|
||||
|
||||
// If the button has been in a stable state that's different than the debounced state for enough
|
||||
// samples, change the debounced state to the stable state and generate an event.
|
||||
if (s_button_timers[i] == NUM_DEBOUNCE_SAMPLES) {
|
||||
s_button_timers[i] = 0;
|
||||
|
||||
bitset32_update(&s_debounced_button_state, i, is_pressed);
|
||||
|
||||
if (!is_pressed) {
|
||||
// A button has been released. Make sure we weren't tracking this as a stuck button.
|
||||
clear_stuck_button(i);
|
||||
}
|
||||
|
||||
PebbleEvent e = {
|
||||
.type = (is_pressed) ? PEBBLE_BUTTON_DOWN_EVENT : PEBBLE_BUTTON_UP_EVENT,
|
||||
.button.button_id = i
|
||||
};
|
||||
should_context_switch = event_put_isr(&e);
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(MANUFACTURING_FW)
|
||||
// Now that s_debounced_button_state is updated, check to see if the user is holding down the reset
|
||||
// combination.
|
||||
static uint32_t s_hard_reset_timer = 0;
|
||||
if ((s_debounced_button_state & RESET_BUTTONS) == RESET_BUTTONS) {
|
||||
s_hard_reset_timer += 1;
|
||||
can_power_down_tim4 = false;
|
||||
|
||||
if (s_hard_reset_timer > RESET_THRESHOLD_SAMPLES) {
|
||||
__disable_irq();
|
||||
|
||||
// If the UP button is held at the moment the timeout is hit, set the force-PRF bootbit:
|
||||
const bool force_prf = (s_debounced_button_state & (1 << BUTTON_ID_UP));
|
||||
if (force_prf) {
|
||||
boot_bit_set(BOOT_BIT_FORCE_PRF);
|
||||
}
|
||||
|
||||
RebootReason reason = {
|
||||
.code = force_prf ? RebootReasonCode_PrfResetButtonsHeld :
|
||||
RebootReasonCode_ResetButtonsHeld
|
||||
};
|
||||
reboot_reason_set(&reason);
|
||||
|
||||
// Don't use system_reset here. This back door absolutely must work. Just hard reset.
|
||||
system_hard_reset();
|
||||
}
|
||||
} else {
|
||||
s_hard_reset_timer = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (can_power_down_tim4) {
|
||||
__disable_irq();
|
||||
disable_button_timer();
|
||||
__enable_irq();
|
||||
}
|
||||
|
||||
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
|
||||
|
||||
portEND_SWITCHING_ISR(should_context_switch);
|
||||
}
|
||||
|
||||
// Serial commands
|
||||
///////////////////////////////////////////////////////////
|
||||
void command_put_raw_button_event(const char* button_index, const char* is_button_down_event) {
|
||||
PebbleEvent e;
|
||||
int is_down = atoi(is_button_down_event);
|
||||
int button = atoi(button_index);
|
||||
|
||||
if ((button < 0 || button > NUM_BUTTONS) || (is_down != 1 && is_down != 0)) {
|
||||
return;
|
||||
}
|
||||
e.type = (is_down) ? PEBBLE_BUTTON_DOWN_EVENT : PEBBLE_BUTTON_UP_EVENT;
|
||||
e.button.button_id = button;
|
||||
event_put(&e);
|
||||
}
|
||||
Reference in New Issue
Block a user