Skip to content

Commit 49c2c29

Browse files
committed
ESPBufferManager integration
1 parent bbb4850 commit 49c2c29

8 files changed

Lines changed: 144 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The format follows Keep a Changelog and the project adheres to Semantic Versioni
88
### Changed
99
- Removed the library-provided inline `timer` instance. Sketches should now declare their own `ESPTimer` objects (global, static, or as class members) before calling `init()`, enabling multiple independent timer managers.
1010
- Added `clearTimeout(id)` for explicit timeout cancellation and kept `clearTimer(id)` as a backward-compatible alias.
11+
- Added `ESPTimerConfig::usePSRAMBuffers` and routed timer-owned persistent/transient vectors through `ESPBufferManager` with safe fallback to default heap.
1112

1213
### Fixed
1314
- Ensured per-second and per-minute countdown timers emit their final tick by rounding up remaining time.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Explore `examples/Basic/Basic.ino` for a complete sketch that demonstrates all t
6060
- `pause*` calls are idempotent and only transition `Running → Paused`. Use the matching `resume*` or `toggleRunStatus*` helpers to continue.
6161
- Each timer type owns its own FreeRTOS task. Tune `ESPTimerConfig` when you need larger stacks or different priorities.
6262
- IDs are unique per `ESPTimer` instance. Clearing a timer frees the ID; reusing stale IDs after `clear*` will fail.
63+
- `usePSRAMBuffers = true` is best-effort for timer-owned dynamic buffers. If PSRAM is unavailable, allocation falls back to normal heap automatically.
6364

6465
## API Reference
6566
- `void init(const ESPTimerConfig& cfg = {})` – allocate mutexes and spawn each timer task with the provided stack/priority/core settings.
@@ -77,6 +78,9 @@ Explore `examples/Basic/Basic.ino` for a complete sketch that demonstrates all t
7778
- Stack sizes (`stackSizeTimeout`, `stackSizeInterval`, `stackSizeSec`, `stackSizeMs`, `stackSizeMin`).
7879
- Priorities (`priorityTimeout`, …).
7980
- Core affinity (`core*`, `-1` = no pin).
81+
- Buffer policy (`usePSRAMBuffers`) for timer-owned vectors and callback dispatch staging buffers.
82+
83+
`usePSRAMBuffers` only affects allocations owned by ESPTimer. Callback captures (`std::function`) can still allocate outside this policy depending on capture size and STL behavior.
8084

8185
`ESPTimerStatus` reports `Invalid`, `Running`, `Paused`, `Stopped`, or `Completed`.
8286

library.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
],
2222
"frameworks": ["arduino"],
2323
"platforms": ["espressif32"],
24-
"dependencies": [],
24+
"dependencies": [
25+
{
26+
"name": "ESPBufferManager",
27+
"version": "https://github.com/ESPToolKit/esp-buffer-manager.git"
28+
}
29+
],
2530
"headers": ["ESPTimer.h"],
2631
"build": {
2732
"flags": [

library.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ category=HTTP
88
url=https://github.com/ESPToolKit/esp-timer
99
repository=https://github.com/ESPToolKit/esp-timer.git
1010
architectures=esp32
11-
depends=
11+
depends=ESPBufferManager
1212
license=MIT

src/esp_timer/timer.cpp

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ uint32_t ESPTimer::nextId() {
6161
void ESPTimer::init(const ESPTimerConfig& cfg) {
6262
if (initialized_) return;
6363
cfg_ = cfg;
64+
usePSRAMBuffers_ = cfg_.usePSRAMBuffers;
65+
66+
TimerVector<TimeoutItem> timeoutStorage{TimerAllocator<TimeoutItem>(usePSRAMBuffers_)};
67+
timeouts_.swap(timeoutStorage);
68+
TimerVector<IntervalItem> intervalStorage{TimerAllocator<IntervalItem>(usePSRAMBuffers_)};
69+
intervals_.swap(intervalStorage);
70+
TimerVector<SecItem> secStorage{TimerAllocator<SecItem>(usePSRAMBuffers_)};
71+
secs_.swap(secStorage);
72+
TimerVector<MsItem> msStorage{TimerAllocator<MsItem>(usePSRAMBuffers_)};
73+
mss_.swap(msStorage);
74+
TimerVector<MinItem> minStorage{TimerAllocator<MinItem>(usePSRAMBuffers_)};
75+
mins_.swap(minStorage);
6476

6577
if (!mutex_) {
6678
mutex_ = xSemaphoreCreateMutex();
@@ -380,9 +392,10 @@ void ESPTimer::minTaskTrampoline(void* arg) { static_cast<ESPTimer*>(arg)->minTa
380392
void ESPTimer::timeoutTask() {
381393
while (running_.load(std::memory_order_acquire)) {
382394
const uint32_t now = millis();
383-
std::vector<std::function<void()>> toCall;
395+
TimerVector<std::function<void()>> toCall{TimerAllocator<std::function<void()>>(usePSRAMBuffers_)};
384396

385397
lock();
398+
toCall.reserve(timeouts_.size());
386399
// Collect callbacks due and remove completed
387400
auto it = timeouts_.begin();
388401
while (it != timeouts_.end()) {
@@ -410,9 +423,10 @@ void ESPTimer::timeoutTask() {
410423
void ESPTimer::intervalTask() {
411424
while (running_.load(std::memory_order_acquire)) {
412425
const uint32_t now = millis();
413-
std::vector<std::function<void()>> toCall;
426+
TimerVector<std::function<void()>> toCall{TimerAllocator<std::function<void()>>(usePSRAMBuffers_)};
414427

415428
lock();
429+
toCall.reserve(intervals_.size());
416430
for (auto& it : intervals_) {
417431
if (it.status == ESPTimerStatus::Running) {
418432
if (now - it.lastFireMs >= it.periodMs) {
@@ -442,9 +456,10 @@ void ESPTimer::secTask() {
442456
while (running_.load(std::memory_order_acquire)) {
443457
const uint32_t now = millis();
444458
struct Call { std::function<void(int)> fn; int arg; };
445-
std::vector<Call> toCall;
459+
TimerVector<Call> toCall{TimerAllocator<Call>(usePSRAMBuffers_)};
446460

447461
lock();
462+
toCall.reserve(secs_.size());
448463
for (auto& it : secs_) {
449464
if (it.status == ESPTimerStatus::Running) {
450465
if (now - it.lastTickMs >= 1000) {
@@ -482,9 +497,10 @@ void ESPTimer::msTask() {
482497
while (running_.load(std::memory_order_acquire)) {
483498
const uint32_t now = millis();
484499
struct Call { std::function<void(uint32_t)> fn; uint32_t arg; };
485-
std::vector<Call> toCall;
500+
TimerVector<Call> toCall{TimerAllocator<Call>(usePSRAMBuffers_)};
486501

487502
lock();
503+
toCall.reserve(mss_.size());
488504
for (auto& it : mss_) {
489505
if (it.status == ESPTimerStatus::Running) {
490506
// Fire at ~1ms cadence; on busy systems it may be coarser
@@ -519,9 +535,10 @@ void ESPTimer::minTask() {
519535
while (running_.load(std::memory_order_acquire)) {
520536
const uint32_t now = millis();
521537
struct Call { std::function<void(int)> fn; int arg; };
522-
std::vector<Call> toCall;
538+
TimerVector<Call> toCall{TimerAllocator<Call>(usePSRAMBuffers_)};
523539

524540
lock();
541+
toCall.reserve(mins_.size());
525542
for (auto& it : mins_) {
526543
if (it.status == ESPTimerStatus::Running) {
527544
if (now - it.lastTickMs >= 60000) {

src/esp_timer/timer.h

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <freertos/FreeRTOS.h>
88
#include <freertos/task.h>
99
#include <freertos/semphr.h>
10+
#include "timer_allocator.h"
1011

1112
// Public types
1213
enum class ESPTimerStatus : uint8_t {
@@ -38,6 +39,10 @@ struct ESPTimerConfig {
3839
int8_t coreSec = -1;
3940
int8_t coreMs = -1;
4041
int8_t coreMin = -1;
42+
43+
// Prefer PSRAM-backed buffers for timer-owned dynamic containers.
44+
// Falls back to default heap automatically when unavailable.
45+
bool usePSRAMBuffers = false;
4146
};
4247

4348
class ESPTimer {
@@ -128,11 +133,11 @@ class ESPTimer {
128133
};
129134

130135
// Storage per type
131-
std::vector<TimeoutItem> timeouts_;
132-
std::vector<IntervalItem> intervals_;
133-
std::vector<SecItem> secs_;
134-
std::vector<MsItem> mss_;
135-
std::vector<MinItem> mins_;
136+
TimerVector<TimeoutItem> timeouts_;
137+
TimerVector<IntervalItem> intervals_;
138+
TimerVector<SecItem> secs_;
139+
TimerVector<MsItem> mss_;
140+
TimerVector<MinItem> mins_;
136141

137142
// FreeRTOS bits
138143
SemaphoreHandle_t mutex_ = nullptr;
@@ -146,6 +151,7 @@ class ESPTimer {
146151
bool initialized_ = false;
147152
std::atomic<bool> running_{false};
148153
uint32_t nextId_ = 1;
154+
bool usePSRAMBuffers_ = false;
149155

150156
uint32_t nextId();
151157
void lock();

src/esp_timer/timer_allocator.h

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#pragma once
2+
3+
#if __has_include(<ESPBufferManager.h>)
4+
#include <ESPBufferManager.h>
5+
#define ESP_TIMER_HAS_BUFFER_MANAGER 1
6+
#elif __has_include(<esp_buffer_manager/buffer_manager.h>)
7+
#include <esp_buffer_manager/buffer_manager.h>
8+
#define ESP_TIMER_HAS_BUFFER_MANAGER 1
9+
#else
10+
#define ESP_TIMER_HAS_BUFFER_MANAGER 0
11+
#endif
12+
13+
#include <cstddef>
14+
#include <cstdlib>
15+
#include <limits>
16+
#include <new>
17+
#include <vector>
18+
19+
namespace timer_allocator_detail {
20+
inline void* allocate(std::size_t bytes, bool usePSRAMBuffers) noexcept {
21+
#if ESP_TIMER_HAS_BUFFER_MANAGER
22+
return ESPBufferManager::allocate(bytes, usePSRAMBuffers);
23+
#else
24+
(void)usePSRAMBuffers;
25+
return std::malloc(bytes);
26+
#endif
27+
}
28+
29+
inline void deallocate(void* ptr) noexcept {
30+
#if ESP_TIMER_HAS_BUFFER_MANAGER
31+
ESPBufferManager::deallocate(ptr);
32+
#else
33+
std::free(ptr);
34+
#endif
35+
}
36+
} // namespace timer_allocator_detail
37+
38+
template <typename T>
39+
class TimerAllocator {
40+
public:
41+
using value_type = T;
42+
43+
TimerAllocator() noexcept = default;
44+
explicit TimerAllocator(bool usePSRAMBuffers) noexcept : usePSRAMBuffers_(usePSRAMBuffers) {}
45+
46+
template <typename U>
47+
TimerAllocator(const TimerAllocator<U>& other) noexcept : usePSRAMBuffers_(other.usePSRAMBuffers()) {}
48+
49+
T* allocate(std::size_t n) {
50+
if (n == 0) {
51+
return nullptr;
52+
}
53+
if (n > (std::numeric_limits<std::size_t>::max() / sizeof(T))) {
54+
#if defined(__cpp_exceptions)
55+
throw std::bad_alloc();
56+
#else
57+
std::abort();
58+
#endif
59+
}
60+
61+
void* memory = timer_allocator_detail::allocate(n * sizeof(T), usePSRAMBuffers_);
62+
if (memory == nullptr) {
63+
#if defined(__cpp_exceptions)
64+
throw std::bad_alloc();
65+
#else
66+
std::abort();
67+
#endif
68+
}
69+
return static_cast<T*>(memory);
70+
}
71+
72+
void deallocate(T* ptr, std::size_t) noexcept {
73+
timer_allocator_detail::deallocate(ptr);
74+
}
75+
76+
bool usePSRAMBuffers() const noexcept {
77+
return usePSRAMBuffers_;
78+
}
79+
80+
template <typename U>
81+
bool operator==(const TimerAllocator<U>& other) const noexcept {
82+
return usePSRAMBuffers_ == other.usePSRAMBuffers();
83+
}
84+
85+
template <typename U>
86+
bool operator!=(const TimerAllocator<U>& other) const noexcept {
87+
return !(*this == other);
88+
}
89+
90+
private:
91+
template <typename>
92+
friend class TimerAllocator;
93+
94+
bool usePSRAMBuffers_ = false;
95+
};
96+
97+
template <typename T>
98+
using TimerVector = std::vector<T, TimerAllocator<T>>;

test/test_basic/test_main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ ESPTimer timer;
66

77
void test_api_compiles() {
88
ESPTimerConfig cfg;
9+
cfg.usePSRAMBuffers = true;
910
timer.init(cfg);
1011

1112
auto id1 = timer.setTimeout([]() {}, 1000);

0 commit comments

Comments
 (0)