Skip to content

Commit 4e18244

Browse files
committed
Add new example: physical button debouncing with a help of time::TimerHost timer.
1 parent fb28c5a commit 4e18244

9 files changed

Lines changed: 2115 additions & 0 deletions

File tree

build/example/debounce/example.cpp

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* SuperTinyKernel(TM) RTOS: Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3+
*
4+
* Source: https://github.com/SuperTinyKernel-RTOS
5+
*
6+
* Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7+
* License: MIT License, see LICENSE for a full text.
8+
*
9+
* ----------------------------------------------------------------------------
10+
* Button Debounce Demo - STM32F407G-DISC1
11+
* ----------------------------------------------------------------------------
12+
*
13+
* Hardware:
14+
* * USER button - PA0 (active-HIGH, external pull-down on board)
15+
* * LEDs - PD12 (GREEN), PD13 (ORANGE), PD14 (RED), PD15 (BLUE)
16+
*
17+
* Behavior:
18+
* 1. Pressing USER button (PA0) triggers EXTI0_IRQHandler on every rising
19+
* edge - including contact-bounce spikes. A DebounceTimer (one-shot,
20+
* DEBOUNCE_MS ms) is armed/re-armed on every edge via TimerHost::Restart().
21+
* Only when the button stays high for the full DEBOUNCE_MS ms without another
22+
* edge does OnExpired() fire and the confirmed press be acted upon.
23+
* 2. On a confirmed press next LED is lit exclusively.
24+
*
25+
* Debounce strategy - re-arming one-shot via Restart():
26+
*
27+
* EXTI edge arrives -> Restart(DebounceTimer, DEBOUNCE_MS ms, one-shot)
28+
* |
29+
* bounce edge -> Restart(...) resets the DEBOUNCE_MS ms window
30+
* |
31+
* DEBOUNCE_MS ms of silence -> OnExpired() - confirmed press
32+
*
33+
* TimerHost::Restart() is used (not StartOrReset) because the debounce
34+
* window must always restart from the latest edge, even when a previous
35+
* one-shot is already in-flight. Restart() is atomic: the timer cannot
36+
* fire between the implicit stop and re-start, eliminating the race that
37+
* would exist with a Stop() + Start() sequence.
38+
*
39+
* The ISR only calls g_Timers.Restart() - it never touches LED state
40+
* directly, keeping ISR work minimal and hardware-register access out of
41+
* interrupt context.
42+
*/
43+
44+
#include <stk.h>
45+
#include <time/stk_time.h>
46+
#include "example.h"
47+
48+
using namespace bsp;
49+
50+
enum { TASK_STACK_SIZE = 256 };
51+
52+
// Debounce window: button must be stable for this long after the last edge
53+
// before the press is considered confirmed. 200 ms covers the vast majority
54+
// of mechanical switches (typical bounce < 20 ms). The lower value makes
55+
// button press more sensitive/responsive but prone to bouncing effect.
56+
enum { DEBOUNCE_MS = 200 };
57+
58+
// ----------------------------------------------------------------------------
59+
// Board helpers - GPIO / button init (STM32F407G-DISC1)
60+
// ----------------------------------------------------------------------------
61+
62+
namespace Board
63+
{
64+
static void ButtonInit()
65+
{
66+
// Enable GPIOA clock.
67+
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
68+
__DSB();
69+
70+
// PA0: input, no pull (board has external pull-down).
71+
GPIOA->MODER &= ~(3U << 0);
72+
GPIOA->PUPDR &= ~(3U << 0);
73+
74+
// Route PA0 to EXTI0.
75+
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
76+
__DSB();
77+
SYSCFG->EXTICR[0] = (SYSCFG->EXTICR[0] & ~SYSCFG_EXTICR1_EXTI0)
78+
| SYSCFG_EXTICR1_EXTI0_PA;
79+
80+
// EXTI0: rising-edge only, unmask.
81+
EXTI->RTSR |= (1U << 0);
82+
EXTI->FTSR &= ~(1U << 0);
83+
EXTI->IMR |= (1U << 0);
84+
85+
// Priority below SysTick so ISR can safely call TimerHost::Restart().
86+
NVIC_SetPriority(EXTI0_IRQn, 8);
87+
NVIC_EnableIRQ(EXTI0_IRQn);
88+
}
89+
} // namespace Board
90+
91+
// ----------------------------------------------------------------------------
92+
// Debounce timer callback - confirmed button press, fires DEBOUNCE_MS ms after
93+
// the last rising edge on PA0 with no further edges in between.
94+
// ----------------------------------------------------------------------------
95+
96+
struct DebounceTimer : public stk::time::TimerHost::Timer
97+
{
98+
uint8_t m_led;
99+
100+
DebounceTimer() : m_led(0)
101+
{}
102+
103+
void OnExpired(stk::time::TimerHost * /*host*/) override
104+
{
105+
// Confirmed press: next LED as acknowledgement.
106+
Led::SwitchOnExclusive(static_cast<Led::Id>(m_led));
107+
m_led = (m_led + 1) % LED_MAX;
108+
}
109+
};
110+
111+
// ----------------------------------------------------------------------------
112+
// Static timer instances - must outlive the kernel
113+
// ----------------------------------------------------------------------------
114+
115+
static stk::time::TimerHost g_Timers;
116+
static DebounceTimer g_DebounceTimer;
117+
118+
// ----------------------------------------------------------------------------
119+
// EXTI0 ISR - USER button on PA0
120+
//
121+
// Re-arms the debounce one-shot on every rising edge. Restart() is
122+
// ISR-safe: it posts a single command to the TimerHost tick task and
123+
// returns immediately without blocking.
124+
// ----------------------------------------------------------------------------
125+
126+
extern "C" void EXTI0_IRQHandler()
127+
{
128+
// Acknowledge the interrupt first to allow re-triggering.
129+
EXTI->PR = (1U << 0);
130+
131+
// Check if button is pressed.
132+
bool pressed = ((GPIOA->IDR & (1U << 0)) != 0U);
133+
134+
// Re-arm (or arm for the first time) the debounce window.
135+
// Restart() atomically cancels any in-flight one-shot and schedules a
136+
// fresh DEBOUNCE_MS ms one-shot, so every bounce (on-off-on...) simply
137+
// resets the window.
138+
if (pressed)
139+
g_Timers.Restart(g_DebounceTimer, stk::GetTicksFromMs(DEBOUNCE_MS), 0);
140+
}
141+
142+
// ---------------------------------------------------------------------------
143+
// RunExample
144+
// ---------------------------------------------------------------------------
145+
146+
void RunExample()
147+
{
148+
Led::InitAll(false);
149+
Board::ButtonInit();
150+
151+
static stk::Kernel<stk::KERNEL_STATIC | stk::KERNEL_SYNC | (STK_TICKLESS_IDLE ? stk::KERNEL_TICKLESS : 0),
152+
stk::time::TimerHost::TASK_COUNT, stk::SwitchStrategyRR, stk::PlatformDefault> g_Kernel;
153+
154+
g_Kernel.Initialize();
155+
g_Timers.Initialize(&g_Kernel, stk::ACCESS_PRIVILEGED);
156+
157+
g_Kernel.Start();
158+
STK_ASSERT(false); // Kernel in KERNEL_STATIC mode never exits
159+
}

build/example/debounce/example.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* SuperTinyKernel(TM) RTOS: Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3+
*
4+
* Source: https://github.com/SuperTinyKernel-RTOS
5+
*
6+
* Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7+
* License: MIT License, see LICENSE for a full text.
8+
*/
9+
10+
#ifndef EXAMPLE_H_
11+
#define EXAMPLE_H_
12+
13+
#include <assert.h>
14+
#include "../driver/led.h"
15+
16+
#ifdef __cplusplus
17+
#define STK_EXTERN extern "C"
18+
#else
19+
#define STK_EXTERN extern
20+
#endif
21+
22+
STK_EXTERN void RunExample();
23+
24+
#endif /* EXAMPLE_H_ */

0 commit comments

Comments
 (0)