532 lines
16 KiB
Markdown
532 lines
16 KiB
Markdown
# API-RTU — RTU 主程序模块
|
||
|
||
> 版本: v3.0
|
||
> 日期: 2026-07-08
|
||
> 来源: 基于原工程 e173631 重构,升级为 X-Macro 框架
|
||
> 状态: 已实现,x86 + ARM 双平台编译通过
|
||
|
||
---
|
||
|
||
## 1. 模块概述
|
||
|
||
RTU 模块是 RTU_ALL_AI 项目的**主程序入口和系统管理框架**,负责:
|
||
|
||
| 功能 | 说明 |
|
||
|------|------|
|
||
| 模块生命周期管理 | X-Macro 驱动的线程模块注册、初始化(init1→init2→start→stop) |
|
||
| 配置文件启停控制 | 通过 `config/SYSTEM/app_config.json` 按需启用/禁用模块 |
|
||
| 三级定时器 | 为每个模块自动创建 10ms / 100ms / 1000ms 三级周期性定时器 |
|
||
| 事件驱动循环 | 每个模块通过 `stru_task_event_t` 等待定时器事件和自己业务事件 |
|
||
| 优雅关闭 | `EV_STOP` 信号 + `pthread_join` 确保所有线程安全退出 |
|
||
| 命令行交互 | 内置 `version` / `uptime` 命令,各模块可注册自定义命令 |
|
||
|
||
### 核心设计理念
|
||
|
||
**新增模块只需修改 [app_modules.h](../../../src/system/inc/app_modules.h) 中一行宏**,无需改动框架代码。模块的枚举值、`extern` 声明、`g_vec_app` 初始化列表全部由 X-Macro 自动展开生成,消除了传统运行时注册的样板代码。
|
||
|
||
### 与原始框架(e173631)对比
|
||
|
||
| 方面 | 原始(运行时注册) | 升级后(X-Macro) |
|
||
|------|---------------|--------------|
|
||
| 模块注册 | `app_module_register()` 运行时调用 | `APP_MODULE()` 编译时宏,零运行时开销 |
|
||
| 启停控制 | 无 | `app_config.json` 按需启用 |
|
||
| 定时器 | 无(模块自行实现) | 自动三级定时器 |
|
||
| 停止 | `app_modules_stop/join` 独立函数 | `app_sys_stop()` 一体化 |
|
||
| 新增模块 | 修改 main.c + app_modules.c | 仅修改 app_modules.h 一行 |
|
||
|
||
---
|
||
|
||
## 2. 文件结构
|
||
|
||
```
|
||
src/system/
|
||
├── inc/
|
||
│ ├── app_modules.h # 模块注册表(X-Macro 中心列表)
|
||
│ └── mySystem.h # 公共定义(stru_app / enum_app / API 声明)
|
||
└── RTU/
|
||
└── src/
|
||
├── main.c # 主程序入口(信号处理 → 初始化 → 主循环 → 关闭)
|
||
└── app_sys.cpp # 系统管理实现(配置解析 / 模块初始化 / 停止)
|
||
|
||
config/
|
||
└── SYSTEM/
|
||
└── app_config.json # 运行时模块启停配置(部署到 RK 装置)
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 架构
|
||
|
||
```
|
||
app_config.json → cJSON 解析
|
||
│
|
||
▼
|
||
g_enabled_apps (std::set)
|
||
│
|
||
▼
|
||
app_modules.h ──X-Macro──▶ g_vec_app (std::vector<stru_app>)
|
||
│ │
|
||
│ ┌─────────┼─────────┐
|
||
│ ▼ ▼ ▼
|
||
│ app_init1 app_init2 创建定时器
|
||
│ │ │ (10/100/1000ms)
|
||
│ │ │ │
|
||
│ ▼ ▼ ▼
|
||
│ ┌────────────────────────────┐
|
||
└── is_app_enabled( ) ──▶ pthread_create() │
|
||
│ 独立线程运行 fun_cb │
|
||
└──────────┬─────────┘
|
||
│
|
||
┌──────────▼──────────┐
|
||
│ task_event_wait() │
|
||
│ ← EV_TIMER1/2/3 │
|
||
│ ← EV_STOP │
|
||
└──────────────────────┘
|
||
```
|
||
|
||
### 启动流程
|
||
|
||
```
|
||
main()
|
||
│
|
||
├── signal(SIGINT/SIGTERM) → sig_handler()
|
||
│
|
||
├── log_init() / dc_init()
|
||
│
|
||
├── app_sys_init()
|
||
│ ├── [1] app_config_load()
|
||
│ │ ├── get_system_config_path()
|
||
│ │ │ ├── WORK_PATH 环境变量(RK 装置)
|
||
│ │ │ └── func_proc_self_dir()(本地调试)
|
||
│ │ └── cJSON_Parse(app_config.json)
|
||
│ │ → g_enabled_apps
|
||
│ │
|
||
│ ├── [2] app_init()
|
||
│ │ ├── init1: 遍历 g_vec_app → 调用各模块 init1()
|
||
│ │ ├── init2: 遍历 g_vec_app → 调用各模块 init2()
|
||
│ │ └── start: 遍历 g_vec_app
|
||
│ │ ├── task_event_create()
|
||
│ │ ├── app_timer_create() × 3
|
||
│ │ └── pthread_create() → fun_cb
|
||
│ │
|
||
│ └── return 0
|
||
│
|
||
├── while(g_running) → 命令行输入循环
|
||
│
|
||
└── app_sys_stop() ← Ctrl+C
|
||
├── EV_STOP → 各模块
|
||
└── pthread_join → 各线程
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 核心数据结构
|
||
|
||
### 4.1 模块枚举(enum_app)
|
||
|
||
由 `app_modules.h` X-Macro 生成,无需手动定义:
|
||
|
||
```c
|
||
/* app_modules.h */
|
||
APP_MODULE(MODBUS_M, modbus_m, app_modbus_m_init1, app_modbus_m_init2, app_modbus_m)
|
||
// 新增模块只需追加一行
|
||
|
||
/* mySystem.h 自动展开为 */
|
||
typedef enum
|
||
{
|
||
ENUM_APP_MODBUS_M, // ← 自动生成
|
||
// ENUM_APP_XXX, // ← 新增模块的枚举值
|
||
ENUM_APP_MAX
|
||
} enum_app;
|
||
```
|
||
|
||
### 4.2 模块上下文(stru_app)
|
||
|
||
每个线程模块一个实例,由 `app_sys.cpp` 自动创建和管理。
|
||
|
||
```c
|
||
typedef struct
|
||
{
|
||
const char *name; /**< 模块名称,如 "app_modbus_m" */
|
||
uint32_t app_id; /**< 模块 ID(枚举值) */
|
||
app_init_fn app_init1; /**< 阶段 1 初始化(信号注册前) */
|
||
app_init_fn app_init2; /**< 阶段 2 初始化(信号注册后) */
|
||
app_func_fn fun_cb; /**< 主循环入口(独立线程) */
|
||
void *arg; /**< 线程参数(指向自身) */
|
||
stru_task_event_t p_event; /**< 事件句柄(定时器 + 停止信号) */
|
||
stru_rtx_data rtx; /**< 收发数据缓冲区 */
|
||
uint32_t timer_arg[TIMER_NUM]; /**< 定时器参数([app_id:8, idx:8]) */
|
||
stru_task_timer_t p_timer[TIMER_NUM]; /**< 三级定时器句柄 */
|
||
uint32_t run_cnt; /**< 运行计数 */
|
||
} stru_app;
|
||
```
|
||
|
||
### 4.3 事件定义
|
||
|
||
```c
|
||
#define TIMER_NUM 3 /* 定时器级数 */
|
||
#define EV_TIMER1 (0x01 << 0) /* 10ms 定时器事件 */
|
||
#define EV_TIMER2 (0x01 << 1) /* 100ms 定时器事件 */
|
||
#define EV_TIMER3 (0x01 << 2) /* 1000ms 定时器事件 */
|
||
#define EV_STOP (0x80) /* 模块停止信号 */
|
||
#define MSG_SIZE 2048 /* 收发缓冲区大小 */
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 接口函数
|
||
|
||
### 5.1 app_sys_init()
|
||
|
||
```c
|
||
int app_sys_init(void);
|
||
```
|
||
|
||
**功能**: 初始化所有 X-Macro 注册的模块。包括加载 `app_config.json`、执行各模块 init1/init2、创建事件 + 定时器 + 启动线程。
|
||
|
||
**参数**: 无
|
||
|
||
**返回值**:
|
||
|
||
| 值 | 说明 |
|
||
|----|------|
|
||
| 0 | 初始化成功 |
|
||
| -1 | 初始化失败(配置文件解析/模块 init/线程创建) |
|
||
|
||
**调用位置**: `main.c` 中 `dc_init()` 之后、主循环之前。
|
||
|
||
### 5.2 app_sys_stop()
|
||
|
||
```c
|
||
void app_sys_stop(void);
|
||
```
|
||
|
||
**功能**: 停止所有模块。先向每个模块发送 `EV_STOP` 信号通知其退出主循环,然后 `pthread_join` 等待所有线程安全退出。
|
||
|
||
**调用位置**: `main.c` 中收到 SIGINT/SIGTERM 或 `Ctrl+D` 后、`dc_cleanup()` 之前。
|
||
|
||
### 5.3 app_get_ptr()
|
||
|
||
```c
|
||
stru_app *app_get_ptr(uint32_t app_id);
|
||
```
|
||
|
||
**功能**: 根据模块 ID 获取 `stru_app` 指针。供模块内部或外部代码访问其他模块数据。
|
||
|
||
**参数**:
|
||
|
||
| 参数 | 说明 |
|
||
|------|------|
|
||
| app_id | 模块 ID(`enum_app` 枚举值) |
|
||
|
||
**返回值**: 有效模块指针,超出范围返回 `NULL`。
|
||
|
||
---
|
||
|
||
## 6. 配置文件
|
||
|
||
### 6.1 app_config.json
|
||
|
||
位置:`config/SYSTEM/app_config.json`
|
||
|
||
```json
|
||
{
|
||
"apps": [
|
||
{"name": "app_modbus_m", "enable": true}
|
||
]
|
||
}
|
||
```
|
||
|
||
**字段说明**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `apps` | Array | 模块配置列表 |
|
||
| `name` | String | 模块名称,格式为 `app_<配置名>`,与 `app_modules.h` 中 cfg_name 对应 |
|
||
| `enable` | Boolean | `true`=启用,`false`=禁用 |
|
||
|
||
**配置路径优先级**:
|
||
1. `WORK_PATH` 环境变量 → `$WORK_PATH/config/SYSTEM/`(RK3568 装置运行时由启动脚本设定)
|
||
2. 可执行文件所在目录 → `<exe_dir>/config/SYSTEM/`(x86 本地调试用 fallback)
|
||
|
||
**部署约定**: `config/` 整个目录直接同步到 RK 装置可执行文件的同级目录下。
|
||
|
||
### 6.2 配置文件缺失
|
||
|
||
若 `app_config.json` 未找到,框架**默认启用所有模块**,不视为错误。日志输出:
|
||
```
|
||
app_config.json not found, enabling all modules by default
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 新增模块步骤
|
||
|
||
只需三步:
|
||
|
||
### Step 1: 在 `app_modules.h` 追加一行
|
||
|
||
```c
|
||
/* src/system/inc/app_modules.h */
|
||
APP_MODULE(NEW_MODULE, new_module, app_new_module_init1, app_new_module_init2, app_new_module)
|
||
```
|
||
|
||
**宏参数说明**:
|
||
|
||
| 参数 | 示例 | 说明 |
|
||
|------|------|------|
|
||
| `name` | `NEW_MODULE` | 枚举后缀,生成 `ENUM_APP_NEW_MODULE` |
|
||
| `cfg_name` | `new_module` | 配置名,生成名称 `"app_new_module"` |
|
||
| `init1` | `app_new_module_init1` | 阶段 1 初始化函数(信号注册前) |
|
||
| `init2` | `app_new_module_init2` | 阶段 2 初始化函数(信号注册后),可为 `NULL` |
|
||
| `func` | `app_new_module` | 主循环入口函数(独立线程) |
|
||
|
||
### Step 2: 实现模块代码
|
||
|
||
在 `src/system/<模块名>/src/` 下实现四个函数:
|
||
|
||
```c
|
||
/* init1: 信号注册前(可解析配置文件) */
|
||
int app_new_module_init1(void *arg)
|
||
{
|
||
stru_app *p_app = (stru_app *)arg;
|
||
// 1. 注册输出信号到 datacenter
|
||
// 2. 解析模块配置文件(XML/JSON)
|
||
return 0;
|
||
}
|
||
|
||
/* init2: 信号注册后(注册遥控/遥调信号及回调) */
|
||
int app_new_module_init2(void *arg)
|
||
{
|
||
// 注册 yk/ao 信号及其回调函数
|
||
return 0;
|
||
}
|
||
|
||
/* 主循环: 事件驱动 */
|
||
void *app_new_module(void *arg)
|
||
{
|
||
stru_app *p_app = (stru_app *)arg;
|
||
uint32_t event;
|
||
|
||
while (1)
|
||
{
|
||
task_event_wait(p_app->p_event, &event, 0);
|
||
|
||
if (event & EV_STOP)
|
||
{
|
||
break; /* 退出循环 */
|
||
}
|
||
|
||
if (event & EV_TIMER1)
|
||
{
|
||
/* 10ms 周期任务 */
|
||
p_app->run_cnt++;
|
||
}
|
||
|
||
/* ... 其他事件处理 ... */
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
```
|
||
|
||
### Step 3: 更新构建系统
|
||
|
||
在 `release/src/system/` 下创建模块子目录和 makefile,并在 `release/src/system/makefile` 中注册:
|
||
|
||
```makefile
|
||
SUBDIRS := ./new_module ./libmodbus_m ./RTU
|
||
```
|
||
|
||
### Step 4: 更新配置文件
|
||
|
||
在 `config/SYSTEM/app_config.json` 中添加模块条目:
|
||
|
||
```json
|
||
{"name": "app_new_module", "enable": true}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 模块编写规范
|
||
|
||
### 8.1 主循环模板
|
||
|
||
```c
|
||
void *app_xxx(void *arg)
|
||
{
|
||
stru_app *p_app = (stru_app *)arg;
|
||
if (!p_app || !p_app->p_event)
|
||
{
|
||
LOG_E("app_xxx arg null");
|
||
return NULL;
|
||
}
|
||
|
||
uint32_t event;
|
||
uint32_t cnt_10ms = 0;
|
||
uint32_t cnt_100ms = 0;
|
||
uint32_t cnt_1s = 0;
|
||
|
||
while (1)
|
||
{
|
||
task_event_wait(p_app->p_event, &event, 0);
|
||
|
||
if (event & EV_STOP)
|
||
{
|
||
LOG_I("app_xxx stopping, total runs: 10ms=%u 100ms=%u 1s=%u",
|
||
cnt_10ms, cnt_100ms, cnt_1s);
|
||
break;
|
||
}
|
||
|
||
if (event & EV_TIMER1)
|
||
{
|
||
cnt_10ms++;
|
||
/* 10ms 周期处理 */
|
||
}
|
||
|
||
if (event & EV_TIMER2)
|
||
{
|
||
cnt_100ms++;
|
||
/* 100ms 周期处理 */
|
||
}
|
||
|
||
if (event & EV_TIMER3)
|
||
{
|
||
cnt_1s++;
|
||
/* 1000ms 周期处理 */
|
||
p_app->run_cnt++;
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
```
|
||
|
||
### 8.2 事件处理顺序
|
||
|
||
事件处理遵循 **先停止后业务** 原则:`EV_STOP` 检查放在最前面,确保收到停止信号后立刻退出。
|
||
|
||
### 8.3 配置路径约定
|
||
|
||
模块内部读取配置文件时,遵循与框架一致的路径约定(参考 `modbus_m.cpp` 的 `get_base_path()`):
|
||
|
||
```c
|
||
LOCAL std::string get_base_path()
|
||
{
|
||
char buf[512] = {0};
|
||
if (0 == func_get_work_path(buf, sizeof(buf))) /* 优先 WORK_PATH */
|
||
{
|
||
return std::string(buf) + "/";
|
||
}
|
||
if (0 != func_proc_self_dir(buf, sizeof(buf))) /* fallback 可执行文件目录 */
|
||
{
|
||
LOG_E("get_base_path failed");
|
||
return "";
|
||
}
|
||
return std::string(buf) + "/";
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 编译与构建
|
||
|
||
### 9.1 编译 RTU 可执行文件
|
||
|
||
```bash
|
||
# x86 本地编译
|
||
cd release && ./build.sh
|
||
|
||
# ARM 交叉编译(RK3568)
|
||
cd release && ./build.sh arm
|
||
```
|
||
|
||
### 9.2 产物
|
||
|
||
| 平台 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| x86 | `release/x86/exe/RTU` | ELF 64-bit x86-64, with debug_info |
|
||
| ARM | `release/arm/exe/RTU` | ELF 64-bit ARM aarch64, with debug_info |
|
||
|
||
### 9.3 链接依赖
|
||
|
||
RTU 可执行文件链接以下库:
|
||
|
||
```
|
||
libmodbus_m libmodbus lib60870
|
||
libdatacenter libxml libcmd libtask
|
||
liblog libfunc libmy_xxhash libcJSON libmd5 libcomm
|
||
libpthread libstdc++
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 依赖关系
|
||
|
||
### 10.1 依赖的库模块
|
||
|
||
| 模块 | 用途 |
|
||
|------|------|
|
||
| `liblog` | 日志输出 |
|
||
| `libfunc` | 文件操作、环境变量路径、字符串处理 |
|
||
| `libdatacenter` | 信号数据中心(dc_init/dc_cleanup) |
|
||
| `libtask` | 事件机制、定时器、延迟 |
|
||
| `libcmd` | 命令行注册与交互 |
|
||
| `libcJSON` | app_config.json 解析 |
|
||
| `libmodbus` | 版本号显示 |
|
||
| `libmodbus_m` | Modbus 主站线程模块 |
|
||
|
||
### 10.2 被依赖
|
||
|
||
RTU 是最顶层模块,不被其他模块依赖。
|
||
|
||
### 10.3 模块间依赖图
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ RTU │
|
||
│ (main.c + app_sys.cpp) │
|
||
│ │
|
||
│ 信号 → libtask (事件/定时器) │
|
||
│ 日志 → liblog │
|
||
│ 配置 → libcJSON + libfunc (文件/路径) │
|
||
│ 命令 → libcmd (linenoise) │
|
||
└──────┬──────────────────────────────────────────┘
|
||
│ app_modules.h X-Macro
|
||
│
|
||
▼
|
||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||
│ libmodbus_m │ │ libcom_decode│ │ libweb_server│
|
||
│ (已实现) │ │ (预留) │ │ (预留) │
|
||
└──────┬───────┘ └──────────────┘ └──────────────┘
|
||
│
|
||
▼
|
||
┌──────────────┐
|
||
│ libdatacenter│
|
||
│ libmodbus │
|
||
│ lib60870 │
|
||
└──────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 命令行参考
|
||
|
||
| 命令 | 说明 |
|
||
|------|------|
|
||
| `help` | 显示所有已注册命令(libcmd 内置) |
|
||
| `version` | 显示固件版本 + libmodbus 版本 |
|
||
| `uptime` | 显示运行状态 |
|
||
| `modbus_m info` | 显示 Modbus 主站通道和点表信息 |
|
||
| `datacenter stat` | 显示数据中心统计(libdatacenter 内置) |
|
||
|
||
---
|
||
|
||
## 12. 变更历史
|
||
|
||
| 日期 | 版本 | 变更 |
|
||
|------|------|------|
|
||
| 2026-07-08 | v3.0 | 配置文件独立化(`config/` 目录)、添加 `app_sys_stop()`、代码风格统一 |
|
||
| 2026-07-08 | v2.0 | X-Macro 框架重构 + `app_config.json` 启停控制 + 三级定时器 |
|
||
| 2026-07-08 | v1.0 | 原始运行时注册框架(`app_modules.c` + `main.c`) |
|