Skip to content

Commit c2e9e98

Browse files
committed
Update NetworkMgr
1 parent c646c3b commit c2e9e98

File tree

8 files changed

+127
-52
lines changed

8 files changed

+127
-52
lines changed

docs/DETAILS.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ The DelegateMQ C++ library enables function invocations on any callable, either
2222
- [CMake](#cmake)
2323
- [Generic (Make/IDE)](#generic-makeide)
2424
- [Porting Guide](#porting-guide)
25+
- [Embedded Systems](#embedded-systems)
2526
- [Quick Start](#quick-start)
2627
- [Basic Examples](#basic-examples)
2728
- [Publish/Subscribe Example](#publishsubscribe-example)
@@ -275,6 +276,22 @@ Numerous predefined platforms are already supported such as Windows, Linux, Free
275276
* Example: `DMQ_ALLOCATOR` to switch between standard Heap (new/delete) and the deterministic Fixed Block Allocator.
276277
7. **Implement Fault Handling**: Customize `Fault.cpp` to route errors to your system's logger or crash handler.
277278

279+
## Embedded Systems
280+
281+
Running C++ messaging on embedded targets (like STM32) requires specific attention to resources.
282+
283+
1. **Stack Usage & Debug Mode:**
284+
285+
**Issue:** In Debug mode (-O0), C++ templates generate deep call stacks, possibly causing stack overflows.
286+
**Fix:** Increase task stack (e.g. 8KB) or in Release mode (-O2), stacks shrink significantly.
287+
288+
2. **Transport Implementation (if using remote delegates):**
289+
290+
**Issue:** Blocking UART calls (e.g., `HAL_UART_Receive`) starve high-priority tasks.
291+
**Fix:** Use an Interrupt-Driven Ring Buffer. The ISR captures data immediately, and the Receive Task sleeps on a Semaphore until data exists.
292+
293+
See the `stm32-freertos` example `README.md` for a complete implementation of static stacks, interrupt-driven UART, and correct FreeRTOS configuration.
294+
278295
# Quick Start
279296

280297
Simple delegate examples showing basic functionality. See [Sample Projects](#sample-projects) for more sample code.

example/sample-projects/system-architecture-no-deps/common/NetworkMgr.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ NetworkMgr::NetworkMgr()
1414
int NetworkMgr::Create()
1515
{
1616
// Bind signals
17-
m_alarmMsgDel.Bind(OnAlarm.get(), &SignalSafe<void(AlarmMsg&, AlarmNote&)>::operator(), ids::ALARM_MSG_ID);
18-
m_dataMsgDel.Bind(OnData.get(), &SignalSafe<void(DataMsg&)>::operator(), ids::DATA_MSG_ID);
19-
m_commandMsgDel.Bind(OnCommand.get(), &SignalSafe<void(CommandMsg&)>::operator(), ids::COMMAND_MSG_ID);
20-
m_actuatorMsgDel.Bind(OnActuator.get(), &SignalSafe<void(ActuatorMsg&)>::operator(), ids::ACTUATOR_MSG_ID);
21-
22-
// Register for error callbacks
23-
m_alarmMsgDel.OnError += MakeDelegate(this, &NetworkMgr::OnError);
24-
m_dataMsgDel.OnError += MakeDelegate(this, &NetworkMgr::OnError);
25-
m_commandMsgDel.OnError += MakeDelegate(this, &NetworkMgr::OnError);
26-
m_actuatorMsgDel.OnError += MakeDelegate(this, &NetworkMgr::OnError);
17+
m_alarmMsgDel.Bind(this, &NetworkMgr::ForwardAlarm, ids::ALARM_MSG_ID);
18+
m_dataMsgDel.Bind(this, &NetworkMgr::ForwardData, ids::DATA_MSG_ID);
19+
m_commandMsgDel.Bind(this, &NetworkMgr::ForwardCommand, ids::COMMAND_MSG_ID);
20+
m_actuatorMsgDel.Bind(this, &NetworkMgr::ForwardActuator, ids::ACTUATOR_MSG_ID);
21+
22+
// Register for error callbacks using single assignment (=)
23+
m_alarmMsgDel.OnError = MakeDelegate(this, &NetworkMgr::OnError);
24+
m_dataMsgDel.OnError = MakeDelegate(this, &NetworkMgr::OnError);
25+
m_commandMsgDel.OnError = MakeDelegate(this, &NetworkMgr::OnError);
26+
m_actuatorMsgDel.OnError = MakeDelegate(this, &NetworkMgr::OnError);
2727

2828
// Register endpoints with the Base Engine (So Incoming() can find them)
2929
RegisterEndpoint(ids::ALARM_MSG_ID, &m_alarmMsgDel);

example/sample-projects/system-architecture-no-deps/common/NetworkMgr.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,22 @@ class NetworkMgr : public NetworkEngine
8383
NetworkMgr();
8484
~NetworkMgr() = default;
8585

86+
// Helper functions to forward incoming data to the Signals
87+
void ForwardAlarm(AlarmMsg& msg, AlarmNote& note) { if (OnAlarm) (*OnAlarm)(msg, note); }
88+
void ForwardCommand(CommandMsg& msg) { if (OnCommand) (*OnCommand)(msg); }
89+
void ForwardData(DataMsg& msg) { if (OnData) (*OnData)(msg); }
90+
void ForwardActuator(ActuatorMsg& msg) { if (OnActuator) (*OnActuator)(msg); }
91+
92+
// TYPE ALIAS: Unicast, Unsafe, Member Delegate bound to 'NetworkMgr'
93+
// This allows exact binding to 'this' in Create()
94+
template <typename... Args>
95+
using EndpointType = RemoteEndpoint<NetworkMgr, void(Args...)>;
96+
8697
// Specific endpoints
87-
RemoteEndpoint<dmq::MulticastDelegateSafe<void(AlarmMsg&, AlarmNote&)>, void(AlarmMsg&, AlarmNote&)> m_alarmMsgDel;
88-
RemoteEndpoint<dmq::MulticastDelegateSafe<void(CommandMsg&)>, void(CommandMsg&)> m_commandMsgDel;
89-
RemoteEndpoint<dmq::MulticastDelegateSafe<void(DataMsg&)>, void(DataMsg&)> m_dataMsgDel;
90-
RemoteEndpoint<dmq::MulticastDelegateSafe<void(ActuatorMsg&)>, void(ActuatorMsg&)> m_actuatorMsgDel;
98+
EndpointType<AlarmMsg&, AlarmNote&> m_alarmMsgDel;
99+
EndpointType<CommandMsg&> m_commandMsgDel;
100+
EndpointType<DataMsg&> m_dataMsgDel;
101+
EndpointType<ActuatorMsg&> m_actuatorMsgDel;
91102
};
92103

93104
#endif

example/sample-projects/system-architecture/common/NetworkMgr.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ NetworkMgr::NetworkMgr()
1414
int NetworkMgr::Create()
1515
{
1616
// Bind signals
17-
m_alarmMsgDel.Bind(OnAlarm.get(), &SignalSafe<void(AlarmMsg&, AlarmNote&)>::operator(), ids::ALARM_MSG_ID);
18-
m_dataMsgDel.Bind(OnData.get(), &SignalSafe<void(DataMsg&)>::operator(), ids::DATA_MSG_ID);
19-
m_commandMsgDel.Bind(OnCommand.get(), &SignalSafe<void(CommandMsg&)>::operator(), ids::COMMAND_MSG_ID);
20-
m_actuatorMsgDel.Bind(OnActuator.get(), &SignalSafe<void(ActuatorMsg&)>::operator(), ids::ACTUATOR_MSG_ID);
21-
22-
// Register for error callbacks
23-
m_alarmMsgDel.OnError += MakeDelegate(this, &NetworkMgr::OnError);
24-
m_dataMsgDel.OnError += MakeDelegate(this, &NetworkMgr::OnError);
25-
m_commandMsgDel.OnError += MakeDelegate(this, &NetworkMgr::OnError);
26-
m_actuatorMsgDel.OnError += MakeDelegate(this, &NetworkMgr::OnError);
17+
m_alarmMsgDel.Bind(this, &NetworkMgr::ForwardAlarm, ids::ALARM_MSG_ID);
18+
m_dataMsgDel.Bind(this, &NetworkMgr::ForwardData, ids::DATA_MSG_ID);
19+
m_commandMsgDel.Bind(this, &NetworkMgr::ForwardCommand, ids::COMMAND_MSG_ID);
20+
m_actuatorMsgDel.Bind(this, &NetworkMgr::ForwardActuator, ids::ACTUATOR_MSG_ID);
21+
22+
// Register for error callbacks using single assignment (=)
23+
m_alarmMsgDel.OnError = MakeDelegate(this, &NetworkMgr::OnError);
24+
m_dataMsgDel.OnError = MakeDelegate(this, &NetworkMgr::OnError);
25+
m_commandMsgDel.OnError = MakeDelegate(this, &NetworkMgr::OnError);
26+
m_actuatorMsgDel.OnError = MakeDelegate(this, &NetworkMgr::OnError);
2727

2828
// Register endpoints with the Base Engine (So Incoming() can find them)
2929
RegisterEndpoint(ids::ALARM_MSG_ID, &m_alarmMsgDel);

example/sample-projects/system-architecture/common/NetworkMgr.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,22 @@ class NetworkMgr : public NetworkEngine
8282
NetworkMgr();
8383
~NetworkMgr() = default;
8484

85+
// Helper functions to forward incoming data to the Signals
86+
void ForwardAlarm(AlarmMsg& msg, AlarmNote& note) { if (OnAlarm) (*OnAlarm)(msg, note); }
87+
void ForwardCommand(CommandMsg& msg) { if (OnCommand) (*OnCommand)(msg); }
88+
void ForwardData(DataMsg& msg) { if (OnData) (*OnData)(msg); }
89+
void ForwardActuator(ActuatorMsg& msg) { if (OnActuator) (*OnActuator)(msg); }
90+
91+
// TYPE ALIAS: Unicast, Unsafe, Member Delegate bound to 'NetworkMgr'
92+
// This allows exact binding to 'this' in Create()
93+
template <typename... Args>
94+
using EndpointType = RemoteEndpoint<NetworkMgr, void(Args...)>;
95+
8596
// Specific endpoints
86-
RemoteEndpoint<dmq::MulticastDelegateSafe<void(AlarmMsg&, AlarmNote&)>, void(AlarmMsg&, AlarmNote&)> m_alarmMsgDel;
87-
RemoteEndpoint<dmq::MulticastDelegateSafe<void(CommandMsg&)>, void(CommandMsg&)> m_commandMsgDel;
88-
RemoteEndpoint<dmq::MulticastDelegateSafe<void(DataMsg&)>, void(DataMsg&)> m_dataMsgDel;
89-
RemoteEndpoint<dmq::MulticastDelegateSafe<void(ActuatorMsg&)>, void(ActuatorMsg&)> m_actuatorMsgDel;
97+
EndpointType<AlarmMsg&, AlarmNote&> m_alarmMsgDel;
98+
EndpointType<CommandMsg&> m_commandMsgDel;
99+
EndpointType<DataMsg&> m_dataMsgDel;
100+
EndpointType<ActuatorMsg&> m_actuatorMsgDel;
90101
};
91102

92103
#endif

src/delegate-mq/predef/util/FreeRTOSConditionVariable.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,14 @@ namespace dmq
4444
{
4545
if (!m_sem) return;
4646

47-
// Check if we are in an ISR to use the correct FreeRTOS API
47+
#if defined(_WIN32) || defined(WIN32)
48+
// Windows Port:
49+
// "Interrupts" are simulated threads. The hardware ISR check
50+
// does not exist. Standard API is safe here.
51+
xSemaphoreGive(m_sem);
52+
#else
53+
// Embedded (e.g., ARM Cortex-M):
54+
// Must check if running in ISR context to use FromISR API.
4855
if (xPortIsInsideInterrupt())
4956
{
5057
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
@@ -55,6 +62,7 @@ namespace dmq
5562
{
5663
xSemaphoreGive(m_sem);
5764
}
65+
#endif
5866
}
5967

6068
/// @brief Wait indefinitely until predicate is true

src/delegate-mq/predef/util/NetworkEngine.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ using namespace std;
99
const std::chrono::milliseconds NetworkEngine::SEND_TIMEOUT(100);
1010
const std::chrono::milliseconds NetworkEngine::RECV_TIMEOUT(2000);
1111

12+
// [STM32-FreeRTOS] Define Static Stack for Network Thread
13+
// Increase size to 2048 words (8KB) to handle Debug mode call depths.
14+
#if defined(DMQ_TRANSPORT_STM32_UART) && defined(DMQ_THREAD_FREERTOS)
15+
static StackType_t g_networkThreadStack[2048];
16+
#endif
17+
1218
NetworkEngine::NetworkEngine()
1319
: m_thread("NetworkEngine"),
1420
m_transportMonitor(RECV_TIMEOUT),
@@ -31,6 +37,11 @@ NetworkEngine::NetworkEngine()
3137
, m_reliableTransport(m_sendTransport, m_retryMonitor)
3238
#endif
3339
{
40+
#if defined(DMQ_TRANSPORT_STM32_UART) && defined(DMQ_THREAD_FREERTOS)
41+
// Apply the static stack buffer to prevent overflow
42+
m_thread.SetStackMem(g_networkThreadStack, 2048);
43+
#endif
44+
3445
m_thread.CreateThread();
3546
}
3647

src/delegate-mq/predef/util/ThreadXConditionVariable.h

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,40 @@
77
namespace dmq
88
{
99
/// @brief Production-grade wrapper around ThreadX Semaphore to mimic std::condition_variable
10+
/// @details
11+
/// - Uses a Counting Semaphore (initialized to 0).
12+
/// - ISR-safe notification logic (tx_semaphore_put).
13+
/// - Robust tick overflow handling using elapsed time subtraction.
14+
///
15+
/// @note Limitation: Unlike std::cv, a semaphore retains its signal state.
16+
/// If notify() occurs before wait(), the wait will effectively "fall through".
1017
class ThreadXConditionVariable
1118
{
1219
public:
13-
ThreadXConditionVariable()
14-
{
15-
// Create a binary semaphore (initially 0)
16-
// TX_NO_INHERIT: Priority inheritance not needed for signaling
17-
if (tx_semaphore_create(&m_sem, "DMQ_CondVar", 0) != TX_SUCCESS)
20+
ThreadXConditionVariable()
21+
{
22+
// Create a semaphore with initial count 0.
23+
// Cast string literal to (CHAR*) to satisfy strict C++ compilers interfacing with C API.
24+
if (tx_semaphore_create(&m_sem, (CHAR*)"DMQ_CondVar", 0) != TX_SUCCESS)
1825
{
19-
// In production, handle error or assert
26+
// In a real application, handle allocation failure (e.g., trap or assert)
2027
// configASSERT(false);
2128
}
2229
}
2330

24-
~ThreadXConditionVariable()
25-
{
31+
~ThreadXConditionVariable()
32+
{
2633
tx_semaphore_delete(&m_sem);
2734
}
2835

2936
ThreadXConditionVariable(const ThreadXConditionVariable&) = delete;
3037
ThreadXConditionVariable& operator=(const ThreadXConditionVariable&) = delete;
3138

32-
/// @brief Wake up one waiting thread
39+
/// @brief Wake up one waiting thread (ISR Safe)
3340
void notify_one() noexcept
3441
{
35-
// ThreadX tx_semaphore_put is generally ISR-safe.
36-
// However, ensuring we don't overflow a "binary" concept logic:
37-
// We usually don't check for ceiling in CV logic because spurious
38-
// wakes are handled by the predicate loop.
42+
// ThreadX tx_semaphore_put is ISR-safe.
43+
// It increments the semaphore count.
3944
tx_semaphore_put(&m_sem);
4045
}
4146

@@ -56,18 +61,24 @@ namespace dmq
5661
template <typename Lock, typename Predicate>
5762
bool wait_for(Lock& lock, std::chrono::milliseconds timeout, Predicate pred)
5863
{
59-
// 1. Convert timeout to ticks
60-
// ThreadX usually runs at 100Hz or 1000Hz (TX_TIMER_TICKS_PER_SECOND)
64+
// 1. Convert timeout to ticks safely
65+
// Use unsigned long long to prevent overflow during multiplication (ms * ticks_per_sec)
6166
const ULONG ticksPerSec = TX_TIMER_TICKS_PER_SECOND;
62-
const ULONG timeoutTicks = (static_cast<ULONG>(timeout.count()) * ticksPerSec) / 1000;
63-
67+
unsigned long long totalTicks = (static_cast<unsigned long long>(timeout.count()) * ticksPerSec) / 1000ULL;
68+
69+
// Ensure at least 1 tick if timeout > 0 to avoid immediate timeout
70+
if (totalTicks == 0 && timeout.count() > 0) totalTicks = 1;
71+
72+
// Cap at ThreadX max wait if necessary, though unlikely for ms ranges
73+
const ULONG timeoutTicks = (totalTicks > 0xFFFFFFFF) ? 0xFFFFFFFF : (ULONG)totalTicks;
74+
6475
const ULONG startTick = tx_time_get();
6576

6677
while (!pred())
6778
{
6879
ULONG now = tx_time_get();
69-
70-
// ThreadX time is ULONG (usually 32-bit), logic same as FreeRTOS
80+
81+
// Overflow-safe subtraction (works because ULONG is unsigned)
7182
ULONG elapsed = now - startTick;
7283

7384
if (elapsed >= timeoutTicks)
@@ -76,17 +87,23 @@ namespace dmq
7687
ULONG remaining = timeoutTicks - elapsed;
7788

7889
lock.unlock();
79-
90+
8091
// Wait for the semaphore or timeout
8192
UINT res = tx_semaphore_get(&m_sem, remaining);
82-
93+
8394
lock.lock();
8495

8596
if (res != TX_SUCCESS)
8697
{
87-
// TX_NO_INSTANCE usually means timeout in this context
88-
return pred();
98+
// TX_NO_INSTANCE (0x0D) or TX_WAIT_ABORTED means we didn't get the token.
99+
// Timeout occurred or wait aborted.
100+
return pred();
89101
}
102+
103+
// If res == TX_SUCCESS, we consumed a token.
104+
// Loop around and check pred() again.
105+
// If pred() is false, we consumed a signal intended for state that isn't ready,
106+
// effectively acting as a "spurious wakeup" handler.
90107
}
91108

92109
return true;

0 commit comments

Comments
 (0)