《Arduino 手册(思路与案例)》栏目介绍:
在电子制作与智能控制的应用领域,本栏目涵盖了丰富的内容,包括但不限于以下主题:Arduino BLDC、Arduino CNC、Arduino E-Ink、Arduino ESP32 SPP、Arduino FreeRTOS、Arduino FOC、Arduino GRBL、Arduino HTTP、Arduino HUB75、Arduino IoT Cloud、Arduino JSON、Arduino LCD、Arduino OLED、Arduino LVGL、Arduino PID、Arduino TFT,以及Arduino智能家居、智慧交通、月球基地、智慧校园和智慧农业等多个方面与领域。不仅探讨了这些技术的基础知识和应用领域,还提供了众多具体的参考案例,帮助读者更好地理解和运用Arduino平台进行创新项目。目前,本栏目已有近4000篇相关博客,旨在为广大电子爱好者和开发者提供全面的学习资源与实践指导。通过这些丰富的案例和思路,读者可以获取灵感,推动自己的创作与开发进程。
https://blog.csdn.net/weixin_41659040/category_12422453.html
Arduino RTOS 中的带回调的非递归 DFS 遍历
1、主要特点
非递归实现:
带回调的非递归深度优先遍历(DFS)通过栈数据结构模拟递归过程,避免了递归调用带来的栈溢出风险,适合在资源受限的嵌入式系统中使用。
回调机制:
在遍历过程中,用户可以传递一个回调函数,用于处理当前节点的数据。这种机制使得遍历过程中的节点处理更加灵活,用户可以根据需求实现不同的操作。
灵活性与可扩展性:
由于采用回调机制,用户可以轻松扩展遍历的功能,例如统计节点数量、查找特定值或执行复杂的节点处理逻辑。
状态管理:
通过显式的栈结构,用户可以更好地管理遍历状态,确保在遍历过程中可以实现动态调整和状态回退。
2、应用场景
树形数据结构遍历:
在需要遍历树形数据结构(如文件系统、XML数据解析等)的应用中,带回调的非递归 DFS 能够高效处理各种节点数据。
图形算法:
在图形处理、网络拓扑分析等领域,带回调的非递归 DFS 可用于遍历图的节点,执行特定的图算法(如寻找路径、连通分量等)。
游戏开发:
在游戏开发中,非递归 DFS 可用于遍历游戏对象的层次结构,实现碰撞检测、AI 行为决策等功能。
数据分析:
在数据分析中,带回调的 DFS 可用于遍历复杂的数据结构,进行数据挖掘和统计分析。
3、注意事项
栈溢出风险:
虽然非递归 DFS 避免了递归引起的栈溢出,但在使用显式栈时,仍需注意栈的大小,确保不会耗尽内存。
回调函数性能:
回调函数的执行效率直接影响遍历性能,需确保回调逻辑简洁高效,以避免影响整体遍历速度。
状态管理:
需要合理管理栈状态,确保在遍历过程中能够正确处理节点的访问状态,避免重复访问或遗漏节点。
错误处理:
遍历过程中可能会遇到异常情况(如空节点、循环引用等),需设计健壮的错误处理机制,以确保系统稳定运行。
性能监控:
在大规模数据处理时,需对遍历性能进行监控,确保在效率和资源使用之间取得平衡。
1、基础非递归 DFS 遍历
#include <Arduino.h>
#include <FreeRTOS.h>
#include <Stack.h>
typedef struct Node {
int value;
struct Node* left;
struct Node* right;
} Node;
Node* createNode(int value) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->value = value;
newNode->left = newNode->right = NULL;
return newNode;
}
void dfsTraversal(Node* root, void (*callback)(int));
Node* root;
void setup() {
Serial.begin(9600);
// 创建简单的二叉树
root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);
dfsTraversal(root, [](int value) {
Serial.print("Visited Node: ");
Serial.println(value);
});
}
void loop() {
// 主循环空着
}
void dfsTraversal(Node* root, void (*callback)(int)) {
Stack<Node*> stack;
if (root != NULL) {
stack.push(root);
}
while (!stack.isEmpty()) {
Node* current = stack.pop();
callback(current->value);
// 先右后左,以保证左子树优先
if (current->right != NULL) {
stack.push(current->right);
}
if (current->left != NULL) {
stack.push(current->left);
}
}
}
2、 带状态的非递归 DFS 遍历
#include <Arduino.h>
#include <FreeRTOS.h>
#include <Stack.h>
typedef struct Node {
int value;
struct Node* left;
struct Node* right;
} Node;
Node* createNode(int value) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->value = value;
newNode->left = newNode->right = NULL;
return newNode;
}
void dfsTraversal(Node* root, void (*callback)(int));
Node* root;
void setup() {
Serial.begin(9600);
root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);
dfsTraversal(root, [](int value) {
Serial.print("Visited Node: ");
Serial.println(value);
});
}
void loop() {
// 主循环空着
}
void dfsTraversal(Node* root, void (*callback)(int)) {
Stack<Node*> stack;
if (root != NULL) {
stack.push(root);
}
while (!stack.isEmpty()) {
Node* current = stack.pop();
callback(current->value);
// 先右后左,以保证左子树优先
if (current->right != NULL) {
stack.push(current->right);
}
if (current->left != NULL) {
stack.push(current->left);
}
}
}
3、多任务环境下的非递归 DFS 遍历
#include <Arduino.h>
#include <FreeRTOS.h>
#include <Stack.h>
typedef struct Node {
int value;
struct Node* left;
struct Node* right;
} Node;
Node* createNode(int value) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->value = value;
newNode->left = newNode->right = NULL;
return newNode;
}
void dfsTraversal(Node* root, void (*callback)(int));
Node* root;
void setup() {
Serial.begin(9600);
root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);
xTaskCreate([](void* params) {
dfsTraversal(root, [](int value) {
Serial.print("Visited Node: ");
Serial.println(value);
});
vTaskDelete(NULL);
}, "DFS Task", 1000, NULL, 1, NULL);
}
void loop() {
// 主循环空着
}
void dfsTraversal(Node* root, void (*callback)(int)) {
Stack<Node*> stack;
if (root != NULL) {
stack.push(root);
}
while (!stack.isEmpty()) {
Node* current = stack.pop();
callback(current->value);
// 先右后左,以保证左子树优先
if (current->right != NULL) {
stack.push(current->right);
}
if (current->left != NULL) {
stack.push(current->left);
}
}
}
要点解读
非递归实现:
通过使用栈来管理节点,实现非递归的深度优先搜索(DFS)遍历。与递归实现相比,非递归方法避免了栈溢出问题,适合处理较深的树结构。
回调函数:
使用回调函数来处理访问的节点值,使得遍历过程更加灵活。用户可以根据需要自定义处理逻辑,而无需修改 DFS 遍历的核心实现。
任务调度:
在第三个示例中,使用 FreeRTOS 创建任务来执行 DFS 遍历。这种设计使得树遍历可以在多任务环境中运行,增强了系统的并发性。
动态内存管理:
使用动态内存分配创建树节点,需注意内存管理,避免内存泄漏。在实际应用中,及时释放不再使用的内存是非常重要的。
灵活性与扩展性:
该实现可以轻松扩展到不同类型的树结构或其他数据结构,只需调整节点的定义和回调函数的实现。这使得该 DFS 实现具有良好的适应性,适合多种场景。
4、文件系统目录遍历(模拟)
#include <Arduino.h>
#include <FreeRTOS.h>
#include <task.h>
#include <stack>
// 模拟文件系统节点
struct FSNode {
String name;
bool isDir;
std::vector<FSNode*> children;
};
// 回调函数类型定义
typedef void (*DFSCallback)(FSNode* node, bool isEnter);
// 非递归DFS实现
void dfs_traverse(FSNode* root, DFSCallback callback) {
std::stack<FSNode*> nodeStack;
std::stack<bool> visitedStack;
nodeStack.push(root);
visitedStack.push(false);
while (!nodeStack.empty()) {
FSNode* current = nodeStack.top();
bool visited = visitedStack.top();
if (!visited) {
// 进入节点回调
callback(current, true);
visitedStack.pop();
visitedStack.push(true);
// 如果是目录,逆序压栈保证顺序处理
if (current->isDir) {
for (int i = current->children.size() - 1; i >= 0; i--) {
nodeStack.push(current->children[i]);
visitedStack.push(false);
}
}
} else {
// 离开节点回调
callback(current, false);
nodeStack.pop();
visitedStack.pop();
}
}
}
// 示例回调函数:打印目录结构
void print_fs_callback(FSNode* node, bool isEnter) {
if (isEnter) {
Serial.print("Enter: ");
for (int i = 0; i < nodeStack.size() - 1; i++) Serial.print(" "); // 缩进
Serial.println(node->name + (node->isDir ? "/" : ""));
} else {
Serial.print("Leave: ");
for (int i = 0; i < nodeStack.size(); i++) Serial.print(" ");
Serial.println(node->name);
}
}
// RTOS任务
void task_dfs_demo(void *pvParameters) {
// 构建模拟文件系统
FSNode root{"root", true};
FSNode dir1{"dir1", true};
FSNode file1{"file1.txt", false};
FSNode dir2{"dir2", true};
FSNode file2{"file2.log", false};
dir1.children.push_back(&file1);
dir2.children.push_back(&file2);
root.children.push_back(&dir1);
root.children.push_back(&dir2);
// 执行DFS遍历
dfs_traverse(&root, print_fs_callback);
vTaskDelete(NULL);
}
void setup() {
Serial.begin(115200);
xTaskCreate(task_dfs_demo, "DFSDemo", 4096, NULL, 1, NULL);
}
void loop() {}
场景说明:
模拟文件系统目录遍历,使用回调函数在进入/离开节点时执行自定义操作(如打印日志)。
5、设备树状态检查
#include <Arduino.h>
#include <FreeRTOS.h>
#include <task.h>
#include <stack>
// 设备节点结构
struct DeviceNode {
String id;
uint8_t status; // 0=offline, 1=online, 2=error
std::vector<DeviceNode*> children;
};
// 回调函数类型
typedef void (*DeviceCallback)(DeviceNode* device, bool isPreOrder);
// 带状态检查的DFS
void dfs_check_devices(DeviceNode* root, DeviceCallback callback) {
std::stack<std::pair<DeviceNode*, bool>> stack;
stack.push({root, false});
while (!stack.empty()) {
auto& [node, visited] = stack.top();
if (!visited) {
// 前序回调:检查设备状态
callback(node, true);
stack.top().second = true;
// 逆序压入子设备
for (auto it = node->children.rbegin(); it != node->children.rend(); ++it) {
stack.push({*it, false});
}
} else {
// 后序回调:汇总子设备状态
callback(node, false);
stack.pop();
}
}
}
// 示例回调:状态监控
void device_monitor_callback(DeviceNode* device, bool isPreOrder) {
if (isPreOrder) {
if (device->status == 2) {
Serial.print("[ERROR] Device ");
Serial.print(device->id);
Serial.println(" needs attention!");
}
} else {
// 后序处理可计算子树状态
int onlineCount = 0;
for (auto child : device->children) {
if (child->status == 1) onlineCount++;
}
Serial.print("Device ");
Serial.print(device->id);
Serial.print(": ");
Serial.print(onlineCount);
Serial.println(" children online");
}
}
// RTOS任务
void task_device_check(void *pvParameters) {
// 创建设备树
DeviceNode gateway{"gateway", 1};
DeviceNode sensor1{"temp_sensor", 1};
DeviceNode sensor2{"humidity_sensor", 2}; // 故障设备
DeviceNode actuator{"pump", 0};
gateway.children.push_back(&sensor1);
gateway.children.push_back(&sensor2);
sensor1.children.push_back(&actuator);
// 定期检查(每5秒)
while (1) {
Serial.println("\n=== Starting device check ===");
dfs_check_devices(&gateway, device_monitor_callback);
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
xTaskCreate(task_device_check, "DeviceCheck", 4096, NULL, 1, NULL);
}
void loop() {}
场景说明:
遍历设备树结构,前序回调检查设备状态,后序回调汇总统计信息,适合物联网设备监控场景。
6、JSON配置解析
#include <Arduino.h>
#include <FreeRTOS.h>
#include <task.h>
#include <stack>
#include <ArduinoJson.h>
// JSON节点结构
struct JsonNode {
String key;
JsonVariant value;
std::vector<JsonNode*> children;
};
// 回调类型定义
typedef void (*JsonCallback)(JsonNode* node, bool isVisit);
// JSON文档转树结构
JsonNode* json_to_tree(const String& jsonStr) {
DynamicJsonDocument doc(1024);
deserializeJson(doc, jsonStr);
JsonObject root = doc.as<JsonObject>();
JsonNode* rootNode = new JsonNode{"root", JsonVariant(), {}};
std::stack<std::pair<JsonObject, JsonNode*>> stack;
stack.push({root, rootNode});
while (!stack.empty()) {
auto [obj, parent] = stack.top();
stack.pop();
for (JsonPair kv : obj) {
JsonNode* node = new JsonNode{
kv.key().c_str(),
kv.value(),
{}
};
parent->children.push_back(node);
if (kv.value().is<JsonObject>()) {
stack.push({kv.value().as<JsonObject>(), node});
}
}
}
return rootNode;
}
// 带回调的DFS遍历
void dfs_json(JsonNode* root, JsonCallback callback) {
std::stack<std::pair<JsonNode*, bool>> stack;
stack.push({root, false});
while (!stack.empty()) {
auto [node, visited] = stack.top();
if (!visited) {
callback(node, true); // 访问前
stack.top().second = true;
// 逆序压入子节点
for (auto it = node->children.rbegin(); it != node->children.rend(); ++it) {
stack.push({*it, false});
}
} else {
callback(node, false); // 访问后
stack.pop();
}
}
}
// 示例回调:配置验证
void config_validate_callback(JsonNode* node, bool isVisit) {
if (isVisit) {
// 前序处理:检查必填字段
if (node->key == "threshold" && node->value.is<int>()) {
int val = node->value.as<int>();
if (val < 0 || val > 100) {
Serial.print("[WARN] Invalid threshold: ");
Serial.println(val);
}
}
} else {
// 后序处理:复杂验证
if (node->key == "sensor_config") {
bool hasType = false, hasPin = false;
for (auto child : node->children) {
if (child->key == "type") hasType = true;
if (child->key == "pin") hasPin = true;
}
if (!(hasType && hasPin)) {
Serial.println("[ERROR] Incomplete sensor config");
}
}
}
}
// RTOS任务
void task_json_parser(void *pvParameters) {
const char* jsonConfig = R"(
{
"threshold": 120,
"sensor_config": {
"type": "DHT22",
"interval": 2000
},
"network": {
"ssid": "MyAP",
"password": null
}
}
)";
JsonNode* configTree = json_to_tree(jsonConfig);
dfs_json(configTree, config_validate_callback);
// 清理内存(实际应设计更安全的释放机制)
// ...
vTaskDelete(NULL);
}
void setup() {
Serial.begin(115200);
xTaskCreate(task_json_parser, "JsonParser", 8192, NULL, 1, NULL);
}
void loop() {}
场景说明:
解析JSON配置文件,前序回调进行简单验证,后序回调实现复杂结构检查,适合嵌入式配置管理。
要点解读
非递归实现关键
使用显式栈(std::stack)替代系统调用栈,避免递归深度限制
通过visited标记区分首次访问(前序)和回溯阶段(后序)
逆序压入子节点保证处理顺序与递归一致
回调机制设计
前序回调(isEnter/isPreOrder)适合资源预分配和初步检查
后序回调(isLeave)适合状态汇总和复杂验证
回调函数应保持轻量级,避免阻塞RTOS调度
内存管理策略
动态分配节点时需配套释放机制(案例三简化处理)
对于资源受限设备,建议使用对象池预分配节点
注意ArduinoJson文档的生命周期管理
RTOS集成要点
长时间运行的DFS应拆分为多个任务片段(本例未展示,可通过xTaskDelay分段)
共享数据结构(如设备树)需通过队列或互斥锁保护
任务堆栈大小需根据树深度调整(案例二使用4KB)
错误处理增强
栈操作前检查stack.empty()防止异常
回调函数中增加异常状态上报(如通过RTOS队列)
添加看门狗监控防止遍历任务阻塞(如configASSERT)
注意,以上案例只是为了拓展思路,仅供参考。它们可能有错误、不适用或者无法编译。您的硬件平台、使用场景和Arduino版本可能影响使用方法的选择。实际编程时,您要根据自己的硬件配置、使用场景和具体需求进行调整,并多次实际测试。您还要正确连接硬件,了解所用传感器和设备的规范和特性。涉及硬件操作的代码,您要在使用前确认引脚和电平等参数的正确性和安全性。