How to Build a Scalable Embedded Power‑Management Framework with Observer and Responsibility Chains
This article explains how AIoT‑era embedded devices evolve from simple C/assembly code to reusable, portable frameworks by applying design patterns such as the Observer pattern and a custom responsibility‑chain model for low‑power management, complete with code examples, memory‑pool handling, and test cases.
Traditional embedded software is usually written in C or assembly, runs on resource‑constrained hardware, and follows simple procedural logic. With the rise of AIoT, chips become more powerful and application logic grows more complex, demanding higher code reuse, portability, and faster development cycles.
AIoT Embedded Software Frameworks
Common AIoT frameworks (e.g., from RT‑Thread) are shown in the diagram below, illustrating layers such as hardware abstraction, kernel, middleware, and application.
Design Patterns in Embedded Systems
Design patterns describe proven solutions to recurring problems and are language‑independent. Although most pattern literature uses Java, the same concepts can be implemented in C, making them valuable for embedded developers.
Observer Pattern
The observer pattern defines a one‑to‑many dependency: when the subject’s state changes, all registered observers are automatically notified.
Typical C implementation:
struct observer_ops {
void *(*handle)(uint8_t evt);
};
struct observer_intf {
struct observer_intf *next;
const char *name;
void *condition;
const struct observer_ops *ops;
};
int observer_register(struct topical *top, struct observer_intf *observer);When the subject changes, it iterates over the observer list and calls each handle function, optionally checking a condition to decide whether to notify.
void XXXX_topical_evt(uint8_t evt) {
struct observer_intf *cur_observer = observer_list.next;
while (cur_observer != NULL) {
void *condition = (void *)cur_observer->condition;
if (condition == NULL || *(uint8_t *)condition) {
if (cur_observer->ops->handle) {
cur_observer->ops->handle(evt);
}
}
cur_observer = cur_observer->next;
}
}Low‑Power Management Framework (PM)
The PM component provides a uniform interface for power‑related modules. Each peripheral registers its init, deinit, and a condition pointer indicating whether the module can enter low‑power mode.
struct pm {
struct pm *next;
const char *name;
void (*init)(void);
void (*deinit)(void);
void *condition;
};
static struct pm pm_list;
static uint8_t pm_num;
static uint8_t pm_status;
int pm_register(const struct pm *pm, const char *name) {
struct pm *cur_pm = &pm_list;
while (cur_pm->next) {
cur_pm = cur_pm->next;
}
cur_pm->next = (struct pm *)pm;
((struct pm *)pm)->next = NULL;
((struct pm *)pm)->name = name;
pm_num++;
return 0;
}The main loop checks all conditions; if none are active, the system enters a sleep state, otherwise it stays awake.
void pm_loop(void) {
uint32_t pm_condition = 0;
struct pm *cur_pm = pm_list.next;
while (cur_pm) {
if (cur_pm->condition) {
pm_condition |= *((uint32_t *)cur_pm->condition);
}
cur_pm = cur_pm->next;
}
if (pm_condition == 0) {
// enter sleep after a few idle cycles
} else {
// stay normal
}
}Responsibility‑Chain Model for Sequential Tasks
Many embedded scenarios require a series of tasks to be executed in order, each with its own timeout, delay, and retry parameters. The responsibility‑chain abstracts each step as a node and manages execution, timeout handling, and error recovery.
Node definition (shadow node):
typedef struct shadow_resp_chain_node {
uint16_t timeout;
uint16_t duration;
uint8_t init_retry;
uint8_t param_type;
uint16_t retry;
struct shadow_resp_chain_node *mp_prev;
struct shadow_resp_chain_node *mp_next;
struct shadow_resp_chain_node *next;
node_resp_handle_fp handle;
void *param;
} shadow_resp_chain_node_t;A memory pool stores a limited number of nodes to avoid static allocation of every possible command.
Chain structure:
typedef struct resp_chain {
bool enable;
bool is_ans;
uint8_t state;
const char *name;
void *param;
TimerEvent_t timer;
bool timer_is_running;
shadow_resp_chain_node_t node; // head of node list
void (*resp_done)(void *result);
} resp_chain_t;Key API functions: resp_chain_init – initializes the chain and timer. resp_chain_node_add – allocates a node from the pool, sets its handler, parameters, timeout, duration, and retry values, then links it to the chain. resp_chain_start – enables the chain for execution. resp_chain_set_ans – records an asynchronous answer for the current node. resp_chain_run – state machine that processes nodes, handles delays, busy waiting, timeouts, retries, and final callbacks.
Example node handlers:
int node1_req(resp_chain_node_t *cfg, void *param) {
cfg->duration = 1000; // 1 s delay
return RESP_STATUS_DELAY;
}
int node2_req(resp_chain_node_t *cfg, void *param) {
if (param == NULL) {
cfg->init_retry = 5;
cfg->timeout = 1000;
return RESP_STATUS_BUSY; // wait for answer
}
// answer received
return RESP_STATUS_OK;
}
int node3_req(resp_chain_node_t *cfg, void *param) {
return RESP_STATUS_OK; // immediate success
}Test flow:
void chain_test_init(void) {
resp_chain_init(&test_req_chain, "test request", test_req_callback);
}
void chain_test_tigger(void) {
resp_chain_node_add(&test_req_chain, node1_req, NULL);
resp_chain_node_add(&test_req_chain, node2_req, NULL);
resp_chain_node_add(&test_req_chain, node3_req, NULL);
resp_chain_start(&test_req_chain);
}
void chain_test_run(void) {
resp_chain_run(&test_req_chain);
}When an asynchronous answer arrives, the framework calls resp_chain_set_ans, which triggers the waiting node’s handler.
Conclusions
The PM module can be added to any peripheral by implementing init, deinit, and providing a condition flag, enabling modular power‑management.
The responsibility‑chain abstracts sequential, timed, and retry‑capable tasks, reducing code scattering across multiple functions.
Using a memory pool for nodes conserves RAM on constrained devices while still supporting dynamic task graphs.
The combined observer‑based PM and chain‑based task scheduler simplify development of complex AIoT embedded applications without tightly coupling modules.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
