Compare commits

6 Commits
Author SHA1 Message Date
chen 0587f7ac6c feat: 实现 Axis 核心属性与 MC_Power 功能块
- Axis: 新增运动学/功率/错误管理属性(_position 等 10+ 项)
- Axis: 新增 doPower()/readState()/cycle() 接口骨架
- MC_Power: 实现功率使能功能块(边沿检测 + 状态输出)
- GlobalResource: 新增 CreateAxis/GetAxisRefById 全局轴管理
- App/CMakeLists.txt: 添加 GlobalResource.cpp 源文件
- docs: 更新 Axis 属性清单与 MC_Power 设计文档
2026-07-03 10:01:06 +00:00
chen 7f73fb48ef feat: 添加多线程框架与轴周期接口
- 新增 MotionTickThread 线程模块
- main.cpp: 引入双线程架构(UserAppThread + MotionTickThread)
- Axis: 新增 cycle() 接口,添加 #pragma once 头文件保护
- App/CMakeLists.txt: 链接 pthread,重组源文件列表
- .gitignore: 排除 docs/html、.vscode、compile_commands.json
2026-07-03 08:10:24 +00:00
chen 818041201b fix: 修复 Types.h 缺少头文件保护导致的重复定义错误
- Types.h: 添加 #pragma once 防止重复包含
- CMakeLists.txt: C++ 标准 17 → 20
- App/CMakeLists.txt: 移除重复的 C++11 覆盖,统一继承根 CMake 标准
- Axis.cpp: 添加 #include <string> 显式包含
2026-07-03 03:52:40 +00:00
chen d9813ea8fe CMake模块构建工程 2026-07-02 10:24:20 +00:00
chen e606b9e53c Merge branch 'dev_create_project' into develop 2026-07-02 08:22:14 +00:00
chen 2e95bebe8e 初始化仓库 2026-07-02 08:15:54 +00:00
25 changed files with 3828 additions and 30 deletions
+7
View File
@@ -0,0 +1,7 @@
# 指定 compile_commands.json 所在目录(相对路径)
CompileFlags:
# 告诉 clangdcompile_commands.json 在 build 目录下
CompilationDatabase: build
+4 -30
View File
@@ -1,32 +1,6 @@
# Prerequisites build
*.d docs/html
.vscode/
compile_commands.json
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
+15
View File
@@ -0,0 +1,15 @@
{
"configurations": [
{
"name": "Linux",
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "linux-gcc-x64"
},
{
}
],
"version": 4
}
+6
View File
@@ -0,0 +1,6 @@
{
"clangd.arguments": ["--compile-commands-dir=build"],
}
+70
View File
@@ -0,0 +1,70 @@
# Motion — AI Agent Instructions
本项目是一个 **PLCopen 运动控制库** 的 C++ 实现,模拟 IEC 61131-3 标准的轴控制功能。
## 快速开始
```bash
# Debug 构建
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
# 运行
./build/Debug/UNIX/MotionApp
```
## 项目结构
```
Motion/
├── CMakeLists.txt # 根构建文件 (C++17)
├── AGENTS.md # AI 代理指令 (本文件)
├── App/ # 可执行目标 MotionApp
│ ├── CMakeLists.txt # (注意: 覆盖为 C++11)
│ ├── Inc/ # 应用级头文件
│ └── Src/main.cpp # 入口点
└── MotionLib/ # 共享库 libMotionLib.so
├── CMakeLists.txt # (C++17)
├── Inc/
│ ├── Types.h # IEC 61131-3 类型映射 & 枚举
│ └── Axis.h # 轴类 & 运动命令
└── Src/Axis.cpp # 构造函数实现
```
## 架构要点
- **命名空间**: 所有运动控制代码在 `namespace plcopen { ... }`
- **两层架构**: `MotionLib` (共享库, 领域逻辑) → `MotionApp` (可执行文件, 入口)
- **轴状态机**: `Disabled → Standstill → DiscreteMotion/ContinuousMotion/Homing → Stopping/ErrorStop`
- **轴引用**: `AXIS_REF` 结构体持有轴编号、名称和指向 `Axis` 实例的指针
## 编码约定
| 项目 | 规范 |
|------|------|
| 语言标准 | C++17 (根), 但 `App/` 使用 C++11 |
| 命名空间 | 小写: `plcopen` |
| 类 | PascalCase: `Axis`, `MotionCommand` |
| 枚举类 | SCREAMING_SNAKE_CASE, 值带 `mc` 前缀 |
| 类型别名 | UPPER_SNAKE_CASE: `BOOL`, `REAL`, `UINT` |
| 成员变量 | `snake_case`, 引用用 `ref_` 前缀 |
| 文档 | Doxygen 格式 (`@brief`, `@param`, `@return`) |
| 平台输出 | `build/<CONFIG>/UNIX/` (Linux), `WIN32/` (Windows), `APPLE/` (macOS) |
## 关键约定
- 包含策略: `.cpp` 引入对应 `.h`, `.h` 引入所需公共头文件
- 无第三方库依赖, 无测试框架
- 使用 `enum class` 而非裸枚举
- GCC UTF-8 编译标志已启用
## 当前开发状态
- [x] 类型系统 (Types.h)
- [x] 轴引用与基础 Axis 类
- [ ] 轴状态机转换逻辑 (`cycle()` 方法)
- [ ] 运动学 (轨迹规划)
- [ ] 功能块 (MC_Power, MC_MoveAbsolute, MC_Stop 等)
- [ ] 应用逻辑 (main.cpp 初始化/循环)
- [ ] 单元测试 (GTest/Catch2)
- [ ] API 文档
+28
View File
@@ -0,0 +1,28 @@
# CMake最低版本
cmake_minimum_required(VERSION 3.21)
# 工程名、版本号、工程描述
project(MotionApp
VERSION 0.1
DESCRIPTION "Test")
set(SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/Src/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Src/MotionTickThread.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Src/UserAppThread.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Src/GlobalResource.cpp
)
# 工程源文件
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
# 工程包含目录
include_directories(./Inc)
# 链接 moduleA,自动继承其 PUBLIC 的头文件路径和编译属性
target_link_libraries(${PROJECT_NAME}
PRIVATE # 如果 moduleB 只是内部使用 moduleA,选 PRIVATE
pthread
MotionLib
)
+35
View File
@@ -0,0 +1,35 @@
/**
* @file GlobalResource.h
* @author
* @brief
* @version 0.1
* @date 2026-07-03
*
* @copyright Copyright (c) 2026
*
*/
#pragma once
#include "Axis.h"
#include "Types.h"
/**
* \brief 创建轴并注册到全局表
* \param id 轴编号
* \param name 轴名称
* \return 轴引用。若 id 已存在,返回已有轴的引用(不覆盖)
*/
plcopen::AXIS_REF& CreateAxis(plcopen::UINT id, const std::string& name);
/**
* \brief 通过编号获取轴引用
* \param id 轴编号
* \return AXIS_REF 引用。若不存在,返回 axis==nullptr 的哨兵引用
*/
plcopen::AXIS_REF& GetAxisRefById(plcopen::UINT id);
+23
View File
@@ -0,0 +1,23 @@
/**
* @file MotionTickThread.h
* @author
* @brief
* @version 0.1
* @date 2026-07-03
*
* @copyright Copyright (c) 2026
*
*/
#pragma once
#include <memory>
#include <vector>
#include "Axis.h"
#include "Types.h"
void MotionTickThread(std::unique_ptr<std::vector<plcopen::Axis>> axisList);
+20
View File
@@ -0,0 +1,20 @@
/**
* @file UserAppThread.h
* @author
* @brief
* @version 0.1
* @date 2026-07-03
*
* @copyright Copyright (c) 2026
*
*/
#pragma once
int UserAppThread();
+60
View File
@@ -0,0 +1,60 @@
/**
* @file GlobalResource.cpp
* @author
* @brief 全局轴资源管理 — 持有并管理所有轴实例
* @version 0.1
* @date 2026-07-03
*
* @copyright Copyright (c) 2026
*
*/
#include "GlobalResource.h"
#include <unordered_map>
#include <vector>
#include <mutex>
namespace {
// 轴实例存储 — 拥有所有轴的内存
std::vector<plcopen::Axis> g_axes;
// 快速查找表:id → AXIS_REF& (指向 g_axes 中的元素)
std::unordered_map<plcopen::UINT, std::size_t> g_indexMap;
// 线程安全
std::mutex g_mutex;
// id 不存在时返回的哨兵
plcopen::AXIS_REF g_invalidRef{0, "", nullptr};
} // namespace
plcopen::AXIS_REF& CreateAxis(plcopen::UINT id, const std::string& name) {
std::lock_guard<std::mutex> lock(g_mutex);
// id 已存在 → 返回已有轴的引用
auto it = g_indexMap.find(id);
if (it != g_indexMap.end()) {
return g_axes[it->second].ref();
}
// 新建轴
g_axes.emplace_back(id, name);
std::size_t idx = g_axes.size() - 1;
g_indexMap[id] = idx;
return g_axes[idx].ref();
}
plcopen::AXIS_REF& GetAxisRefById(plcopen::UINT id) {
std::lock_guard<std::mutex> lock(g_mutex);
auto it = g_indexMap.find(id);
if (it != g_indexMap.end()) {
return g_axes[it->second].ref();
}
g_invalidRef.axisNo = id;
g_invalidRef.axis = nullptr;
return g_invalidRef;
}
+32
View File
@@ -0,0 +1,32 @@
/**
* @file MotionTickThread.cpp
* @author
* @brief
* @version 0.1
* @date 2026-07-03
*
* @copyright Copyright (c) 2026
*
*/
#include "MotionTickThread.h"
/**
* @brief
*
* @param axisList
*/
void MotionTickThread(std::unique_ptr<std::vector<plcopen::Axis>> axisList)
{
while(1)
{
for (auto& axis : *axisList) {
axis.cycle();
}
}
}
+30
View File
@@ -0,0 +1,30 @@
/**
* @file UserAppThread.cpp
* @author
* @brief
* @version 0.1
* @date 2026-07-03
*
* @copyright Copyright (c) 2026
*
*/
#include "UserAppThread.h"
#include "Axis.h"
#include "FunctionBlocks.h"
#include "MC_Power.h"
int UserAppThread()
{
plcopen::MC_Power mcPowerInstance0()
while(1)
{
}
return 0;
}
+40
View File
@@ -0,0 +1,40 @@
/**
* @file main.cpp
* @author
* @brief
* @version 0.1
* @date 2026-07-03
*
* @copyright Copyright (c) 2026
*
*/
#include <thread>
#include <mutex>
#include <vector>
#include "MotionTickThread.h"
#include "UserAppThread.h"
#include "Axis.h"
#include "Types.h"
std::vector<plcopen::Axis> axisList;
int main()
{
plcopen::Axis axis1(1, "axis1");
plcopen::Axis axis2(2, "axis2");
axisList.push_back(axis1);
axisList.push_back(axis2);
std::thread userAppThread(UserAppThread);
std::thread motionTickThread(MotionTickThread,
std::move(std::make_unique<std::vector<plcopen::Axis>>(axisList)));
motionTickThread.join();
return 0;
}
+43
View File
@@ -0,0 +1,43 @@
cmake_minimum_required(VERSION 3.22)
project(Motion)
# 开启编译数据库导出(强制开启,不被缓存覆盖)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")
# 针对 GCC 编译器添加 UTF-8 编码选项
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") # 仅对 GCC 生效
# 添加 C++ 编译选项
add_compile_options(-finput-charset=UTF-8)
add_compile_options(-fexec-charset=UTF-8)
# 若需要支持 C 语言,补充 C 编译选项
add_compile_options($<$<COMPILE_LANGUAGE:C>:-finput-charset=UTF-8>)
add_compile_options($<$<COMPILE_LANGUAGE:C>:-fexec-charset=UTF-8>)
endif()
if(WIN32)
# Windows平台特定的设置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/$<CONFIGURATION>/WIN32")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/$<CONFIGURATION>/WIN32")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/$<CONFIGURATION>/WIN32/static_lib")
elseif(APPLE)
# Apple平台特定的设置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/$<CONFIGURATION>/APPLE")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/$<CONFIGURATION>/APPLE")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/$<CONFIGURATION>/APPLE/static_lib")
elseif(UNIX) # 注意:Linux和macOS都满足UNIX为TRUE,所以通常先判断APPLE
# Linux或其他类Unix系统的设置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/$<CONFIGURATION>/UNIX")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/$<CONFIGURATION>/UNIX")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/$<CONFIGURATION>/UNIX/static_lib")
endif()
set(CMAKE_C_STANDARD 20)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(${CMAKE_SOURCE_DIR}/MotionLib)
add_subdirectory(${CMAKE_SOURCE_DIR}/App)
+2579
View File
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
project(MotionLib
VERSION 0.0.1 # 版本 0.0.1
LANGUAGES CXX # C++
DESCRIPTION "运动控制库" # 描述
)
set(INC_DIRECTORES
${CMAKE_CURRENT_SOURCE_DIR}/Inc
)
set(LIB_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Src/Axis.cpp
)
set(LIB_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/Inc/Types.h
${CMAKE_CURRENT_SOURCE_DIR}/Inc/Axis.h
)
add_library(${PROJECT_NAME} SHARED
${LIB_SOURCES}
${LIB_HEADERS}
)
target_include_directories(${PROJECT_NAME}
PUBLIC
${INC_DIRECTORES}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Src
)
+96
View File
@@ -0,0 +1,96 @@
/**
* @file Axis.h
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2026-07-02
*
* @copyright Copyright (c) 2026
*
*/
#pragma once
#include "Types.h"
namespace plcopen {
/**
* \enum MotionCommandType
* \brief 内部运动指令类型(功能块 → 轴模型映射)
*/
enum class MotionCommandType {
None, /*!< 无活动指令 */
PointToPoint, /*!< MC_MoveAbsolute / MC_MoveRelative */
Velocity, /*!< MC_MoveVelocity */
Stop, /*!< MC_Stop */
Halt, /*!< MC_Halt */
Home, /*!< MC_Home */
};
/**
* \struct MotionCommand
* \brief 当前活动运动指令的参数快照
*/
struct MotionCommand {
MotionCommandType type = MotionCommandType::None;
REAL targetPosition = 0.0;
REAL targetVelocity = 0.0;
REAL maxVelocity = 0.0;
REAL acceleration = 0.0;
REAL deceleration = 0.0;
REAL homePosition = 0.0;
MC_AXIS_STATE resultingState = MC_AXIS_STATE::DiscreteMotion;
};
/**
* \class Axis
* \brief 单轴仿真对象,实现 PLCopen 轴状态机与运动学
* \note 不要在功能块 cycle() 之外直接修改 Axis 内部状态。
*/
class Axis {
public:
/**
* \brief 构造轴
* \param axisNo 轴编号
* \param name 轴名称
*/
explicit Axis(UINT axisNo = 1, std::string name = "Axis1");
void cycle();
MC_AXIS_STATE readState();
void doPower(bool en);
/** \brief 获取轴编号 */
UINT axisNo() const { return _ref.axisNo; }
/** \brief 获取内部 AXIS_REF(供全局注册表使用) */
AXIS_REF& ref() { return _ref; }
private:
AXIS_REF _ref;
MC_AXIS_STATE _state = MC_AXIS_STATE::Disabled;
MC_AXIS_STATE _resultingSstate = MC_AXIS_STATE::Disabled;
MotionCommand _activeCommand;
// 运动学属性
REAL _position;
REAL _actualPosition;
REAL _velocity;
REAL _actualVelocity;
REAL _acceleration;
REAL _actualAcceleration;
// 功率与使能
BOOL _powerEnabled;
BOOL _homed;
// 错误管理
MC_ERROR_ID _errorId;
BOOL _errorStop;
};
}
+13
View File
@@ -0,0 +1,13 @@
/**
* @file FunctionBlocks.h
* @author
* @brief
* @version 0.1
* @date 2026-07-03
*
* @copyright Copyright (c) 2026
*
*/
#include "MC_Power.h"
+44
View File
@@ -0,0 +1,44 @@
/**
* @file MC_Power.h
* @author
* @brief
* @version 0.1
* @date 2026-07-03
*
* @copyright Copyright (c) 2026
*
*/
#pragma once
#include "Axis.h"
#include "Types.h"
namespace plcopen {
class MC_Power{
public:
explicit MC_Power(AXIS_REF& axisRef);
void cycle();
// --- 输入参数 (VAR_INPUT) ---
BOOL Enable = false;
BOOL Enable_Positive = true;
BOOL Enable_Negative = true;
// --- 输出参数 (VAR_OUTPUT) ---
BOOL Status = false;
BOOL Busy = false;
BOOL Error = false;
MC_ERROR_ID ErrorID = MC_ERROR_ID::mcNoError;
private:
AXIS_REF& _axisRef; // 轴引用,不持有所有权
bool _prevEnable = false; // 上一周期的 Enable 值(边沿检测)
};
}
+122
View File
@@ -0,0 +1,122 @@
/**
* @file types.h
* @author
* @brief
* @version 0.1
* @date 2026-07-02
*
* @copyright Copyright (c) 2026
*
*/
#pragma once
#include <cstdint>
#include <string>
namespace plcopen {
class Axis;
/** \typedef BOOL IEC 61131-3 BOOL 类型映射 */
using BOOL = bool;
/** \typedef REAL IEC 61131-3 REAL 类型映射(双精度浮点) */
using REAL = double;
/** \typedef WORD IEC 61131-3 WORD 类型映射(16 位无符号,常用于 ErrorID */
using WORD = std::uint16_t;
/** \typedef UINT IEC 61131-3 UINT 类型映射(32 位无符号) */
using UINT = std::uint32_t;
/**
* \enum MC_BUFFER_MODE
* \brief 运动指令缓冲与混合模式 (MC_BUFFER_MODE)
*
* 用于带 BufferMode 输入的功能块,决定新指令与当前运动的关系。
*/
enum class MC_BUFFER_MODE : std::uint8_t {
mcAborting = 0, /*!< 默认:立即执行,中断当前运动并清空缓冲 */
mcBuffered = 1, /*!< 等待前一指令 Done 后顺序执行,无混合 */
mcBlendingLow = 2, /*!< 混合:在衔接点取两指令速度的较低值 */
mcBlendingPrevious = 3, /*!< 混合:在衔接点保持前一指令速度 */
mcBlendingNext = 4, /*!< 混合:在衔接点使用当前指令速度 */
mcBlendingHigh = 5, /*!< 混合:在衔接点取两指令速度的较高值 */
};
/**
* \enum MC_DIRECTION
* \brief 定位运动方向 (MC_DIRECTION)
*/
enum class MC_DIRECTION : std::uint8_t {
mcPositiveDirection = 0, /*!< 正向运动 */
mcShortestWay = 1, /*!< 最短路径(线性轴默认策略) */
mcNegativeDirection = 2, /*!< 负向运动 */
mcCurrentDirection = 3, /*!< 保持当前运动方向 */
};
/**
* \enum MC_SOURCE
* \brief 运动数据读取来源 (MC_SOURCE)
*/
enum class MC_SOURCE : std::uint8_t {
mcCommandedValue = 0, /*!< 指令值 */
mcSetValue = 1, /*!< 设定值 */
mcActualValue = 2, /*!< 实际值 */
};
/**
* \enum MC_AXIS_STATE
* \brief 轴状态机状态 (PLCopen 状态图)
*
* \par 典型状态转换
* - Disabled → StandstillMC_Power.Enable = TRUE
* - Standstill → DiscreteMotionMC_MoveAbsolute / MC_MoveRelative
* - Standstill → ContinuousMotionMC_MoveVelocity
* - Standstill → HomingMC_Home
* - 任意 → ErrorStop:轴错误或功率故障
* - ErrorStop → StandstillMC_Reset
*
* \note 同一时刻仅有一个状态位为 TRUE(由 MC_ReadStatus 反映)。
*/
enum class MC_AXIS_STATE : std::uint8_t {
Disabled = 0, /*!< 初始/下电状态,功率未使能 */
Standstill, /*!< 静止,功率已使能,可接受运动指令 */
Homing, /*!< 回零进行中 (MC_Home) */
DiscreteMotion, /*!< 离散运动(点到点),完成后自动停止 */
ContinuousMotion, /*!< 连续运动(如 MC_MoveVelocity */
Stopping, /*!< MC_Stop 激活,减速中且拒绝新运动 */
ErrorStop, /*!< 错误停止,需 MC_Reset 恢复 */
SynchronizedMotion, /*!< 同步运动(Cam/Gear,本库预留) */
};
/**
* \enum MC_ERROR_ID
* \brief 功能块与轴通用错误码
*/
enum class MC_ERROR_ID : WORD {
mcNoError = 0, /*!< 无错误 */
mcInvalidParameter = 1, /*!< 参数非法(如速度 ≤ 0) */
mcInvalidState = 2, /*!< 当前状态不允许该操作 */
mcActionNotAllowed = 3, /*!< 轴未上电或处于 Stopping/ErrorStop */
mcAxisNotPowered = 4, /*!< 轴未使能 */
mcAxisNotHomed = 5, /*!< 轴未完成回零 */
mcCommandAborted = 6, /*!< 指令被中断 */
mcStopActive = 7, /*!< MC_Stop 占用轴 */
mcPowerFailure = 8, /*!< 功率级故障 */
mcFollowingError = 9, /*!< 跟随误差超限 */
};
/**
* \struct AXIS_REF
* \brief 轴引用 (VAR_IN_OUT),所有 MC 功能块的公共轴参数
*
* \details
* 在 IEC 61131-3 程序中作为 VAR_IN_OUT 传递;本 C++ 实现中,
* `axis` 指针由 Axis 对象构造时自动绑定,应用层无需手动设置。
*/
struct AXIS_REF {
UINT axisNo = 0; /*!< 轴编号(用户定义) */
std::string axisName; /*!< 轴名称 */
Axis* axis = nullptr; /*!< 内部绑定指针,指向仿真轴实例 */
};
}
+96
View File
@@ -0,0 +1,96 @@
/**
* @file Axis.cpp
* @author
* @brief
* @version 0.1
* @date 2026-07-02
*
* @copyright Copyright (c) 2026
*
*/
#include <string>
#include "Types.h"
#include "Axis.h"
namespace plcopen {
Axis::Axis(UINT axisNo, std::string name) {
_ref.axisNo = axisNo;
_ref.axisName = std::move(name);
_ref.axis = this;
}
void Axis::cycle()
{
if (_errorStop) return;
if (!_powerEnabled) return;
switch (_state) {
case MC_AXIS_STATE::Disabled :
{
break;
}
case MC_AXIS_STATE::Standstill :
{
break;
}
case MC_AXIS_STATE::Stopping :
{
break;
}
// 独立运动
case MC_AXIS_STATE::DiscreteMotion :
{
break;
}
// 持续运动
case MC_AXIS_STATE::ContinuousMotion :
{
break;
}
// 同步运动
case MC_AXIS_STATE::SynchronizedMotion :
{
break;
}
// 回零
case MC_AXIS_STATE::Homing :
{
break;
}
default:
{
break;
}
}
}
void Axis::doPower(bool en)
{
if(en)
{
_powerEnabled = true;
}
else {
_powerEnabled = false;
_activeCommand.type = MotionCommandType::None;
_velocity = 0.0;
_acceleration = 0.0;
_errorStop = false;
_state = MC_AXIS_STATE::Disabled;
}
}
MC_AXIS_STATE Axis::readState()
{
return _state;
}
}
+84
View File
@@ -0,0 +1,84 @@
/**
* @file MC_Power.cpp
* @author
* @brief
* @version 0.1
* @date 2026-07-03
*
* @copyright Copyright (c) 2026
*
*/
#include "MC_Power.h"
#include "Types.h"
namespace plcopen {
MC_Power::MC_Power(AXIS_REF& axisRef)
: _axisRef(axisRef) { }
void MC_Power::cycle()
{
if(_axisRef.axis == nullptr)
{
Error = true;
ErrorID = MC_ERROR_ID::mcInvalidParameter;
Status = false;
Busy = false;
return;
}
Axis* axis = _axisRef.axis;
Error = false;
ErrorID = MC_ERROR_ID::mcNoError;
// 上升沿
if(Enable && !_prevEnable)
{
axis->doPower(true);
}
// 下降沿
if(!Enable && _prevEnable)
{
axis->doPower(false);
}
// 在使能状态下持续更新轴状态
if(Enable)
{
auto state = axis->readState();
if(state == MC_AXIS_STATE::Standstill)
{
Busy = false;
Status = true;
}
else if(state == MC_AXIS_STATE::ErrorStop)
{
Busy = false;
Status = false;
Error = true;
ErrorID = MC_ERROR_ID::mcPowerFailure;
}
else
{
Status = false;
Busy = false;
}
}
// 保存前置状态
_prevEnable = Enable;
}
}
+1
View File
@@ -0,0 +1 @@
/home/chen/Workspace/T153/App/Motion/build/compile_commands.json
+164
View File
@@ -0,0 +1,164 @@
# Axis 类必须属性
`Axis` 是 PLCopen 运动控制库的核心仿真类,实现 IEC 61131-3 单轴状态机与运动学。
## 设计决策
### 可见性:运动学属性为 `private`
功能块不直接读写轴内部状态,而是通过 `AXIS_REF` 调用公开接口。这是 IEC 61131-3 的标准模式——轴是功能块黑盒:
```
功能块 (MC_MoveAbsolute) Axis
───────────────────── ─────────────
AXIS_REF → axis private: position_, velocity_, ...
axis.acceptCommand(cmd) ──────→ 只有 cycle() 能写运动学数据
axis.readActualPosition() ←─── public: getter 只读
```
> 运动控制线程定时调用 `cycle()` 独占写权限,避免数据竞争。
### 数值类型:浮点 `REAL` (double)
遵循 IEC 61131-3 / PLCopen 规范,使用 `double` 表示物理量(mm、°/s 等)。`double` 有 52 位尾数(约 15 位十进制有效数字),以 10ms 周期运行 1 小时,累积的浮点舍入误差在 $10^{-10}$ 量级,工程上可完全忽略。若未来对接真实驱动器,在数据交换层做脉冲数换算即可。
---
## 1. 当前已实现
### 1.1 轴标识与自引用 — `AXIS_REF _ref` (private)
| 字段 | 类型 | 说明 |
|------|------|------|
| `axisNo` | `UINT` | 轴编号,由用户在构造时指定,默认 1 |
| `axisName` | `std::string` | 轴名称,默认 `"Axis1"` |
| `axis` | `Axis*` | 指向自身实例的指针,供功能块间接访问轴对象 |
### 1.2 轴状态 — `MC_AXIS_STATE _state` (private)
当前状态,初始值 `Disabled`。新增辅助状态 `_resultingState` 用于记录指令完成后的目标状态。
状态转换由 `cycle()` 方法驱动(逻辑待实现)。
### 1.3 运动学属性 (private) ✅
`cycle()` 独占写入;外部通过 `readXxx()` 只读访问。
| 属性 | 类型 | 说明 |
|------|------|------|
| `_position` | `REAL` | 当前指令位置 |
| `_actualPosition` | `REAL` | 当前实际位置(含跟随误差仿真) |
| `_velocity` | `REAL` | 当前指令速度 |
| `_actualVelocity` | `REAL` | 当前实际速度 |
| `_acceleration` | `REAL` | 当前指令加速度 |
| `_actualAcceleration` | `REAL` | 当前实际加速度 |
| `_deceleration` | `REAL` | 减速度(尚未添加) |
### 1.4 功率与使能 (private) ✅
| 属性 | 类型 | 说明 |
|------|------|------|
| `_powerEnabled` | `BOOL` | 功率级是否使能 |
| `_homed` | `BOOL` | 是否已完成回零 |
### 1.5 错误管理 (private) ✅
| 属性 | 类型 | 说明 |
|------|------|------|
| `_errorId` | `MC_ERROR_ID` | 当前错误码,`mcNoError` 表示正常 |
| `_errorStop` | `BOOL` | 是否触发错误停止 |
### 1.6 构造函数 & cycle() (public)
```cpp
explicit Axis(UINT axisNo = 1, std::string name = "Axis1");
void cycle(); // 骨架已生成,逻辑待实现
```
初始化 `_ref` 并自绑定 `axis` 指针,`_state` 默认 `Disabled`
---
## 2. 待实现的属性与方法
### 2.1 活动指令 — `MotionCommand _activeCommand` (private)
当前正在执行的运动指令参数快照,类型为 `MotionCommand`
| 字段 | 类型 | 说明 |
|------|------|------|
| `type` | `MotionCommandType` | 指令类型(点到点 / 速度 / 停止 / 暂停 / 回零) |
| `targetPosition` | `REAL` | 目标位置 |
| `targetVelocity` | `REAL` | 目标速度 |
| `maxVelocity` | `REAL` | 最大速度 |
| `acceleration` | `REAL` | 加速度 |
| `deceleration` | `REAL` | 减速度 |
| `homePosition` | `REAL` | 回零位置 |
| `resultingState` | `MC_AXIS_STATE` | 指令完成后要进入的状态 |
### 2.2 待补充的运动学属性
| 属性 | 说明 |
|------|------|
| `_deceleration` | 减速度(已有 `_acceleration`,缺独立减速参数) |
### 2.3 轴状态机方法 — `cycle()` (public, 实现待完成)
```cpp
void cycle();
```
每周期由运动控制线程调用,待实现逻辑:
1. 根据 `_activeCommand` 计算运动学输出
2. 更新 `_position` / `_velocity`
3. 评估状态转换条件
4. 在离散运动到达目标时自动切换到 `Standstill`
### 2.4 功能块交互接口 (public, 全部待实现)
| 方法 | 可见性 | 说明 |
|------|--------|------|
| `acceptCommand(const MotionCommand&)` | public | 接收功能块下发的运动指令 |
| `doPower(BOOL enable)` | public | 功率使能/去使能 |
| `doStop()` / `doHalt()` | public | 停止 / 暂停 |
| `doReset()` | public | 从 ErrorStop 恢复 |
| `doHome(REAL homePos)` | public | 发起回零 |
| `readStatus()` | public | 读取当前状态快照 |
| `readActualPosition()` | public | 读取实际位置 |
| `readActualVelocity()` | public | 读取实际速度 |
| `readError()` | public | 读取当前错误码 |
---
## 3. 完整属性清单
```
private
───────
Axis
├── _ref (AXIS_REF) ✅
├── _state (MC_AXIS_STATE) ✅
├── _resultingState (MC_AXIS_STATE) ✅ 指令完成目标状态
├── _position (REAL) ✅
├── _actualPosition (REAL) ✅
├── _velocity (REAL) ✅
├── _actualVelocity (REAL) ✅
├── _acceleration (REAL) ✅
├── _actualAcceleration (REAL) ✅
├── _deceleration (REAL) ⬜ 待添加
├── _powerEnabled (BOOL) ✅
├── _homed (BOOL) ✅
├── _errorId (MC_ERROR_ID) ✅
├── _errorStop (BOOL) ✅
└── _activeCommand (MotionCommand) ⬜ 活动指令
public
──────
Axis(axisNo, name) ✅
cycle() 🟡 骨架已就绪
acceptCommand(cmd) ⬜
doPower / doStop / doHalt ⬜
doReset / doHome ⬜
readStatus / readActualPosition ⬜
readActualVelocity / readError ⬜
```
+185
View File
@@ -0,0 +1,185 @@
# MC_Power 功能块设计
`MC_Power` 是 PLCopen 运动控制库的基础功能块,控制轴的功率级使能与去使能。
---
## 1. IEC 61131-3 接口定义
```
FUNCTION_BLOCK MC_Power
VAR_IN_OUT
Axis : AXIS_REF; (* 轴引用 *)
END_VAR
VAR_INPUT
Enable : BOOL; (* TRUE=使能, FALSE=去使能 *)
Enable_Positive : BOOL; (* 允许正向运动,本库保留 *)
Enable_Negative : BOOL; (* 允许负向运动,本库保留 *)
END_VAR
VAR_OUTPUT
Status : BOOL; (* 轴已使能且就绪 *)
Busy : BOOL; (* 功能块正在处理中 *)
Error : BOOL; (* 错误标志 *)
ErrorID : MC_ERROR_ID;(* 错误码 *)
END_VAR
```
## 2. C++ 类实现
### 2.1 类声明 — `MotionLib/Inc/MC_Power.h`
```cpp
#pragma once
#include "Types.h"
namespace plcopen {
class MC_Power {
public:
/**
* \brief 构造 MC_Power 实例
* \param axisRef 轴引用 (VAR_IN_OUT)
*/
explicit MC_Power(AXIS_REF& axisRef);
/**
* \brief 周期调用,执行功率控制逻辑
*
* 上位机每个运动控制周期调用一次。
*/
void cycle();
// --- 输入参数 (VAR_INPUT) ---
BOOL Enable = false;
BOOL Enable_Positive = true; // 本库默认允许
BOOL Enable_Negative = true; // 本库默认允许
// --- 输出参数 (VAR_OUTPUT) ---
BOOL Status = false;
BOOL Busy = false;
BOOL Error = false;
MC_ERROR_ID ErrorID = MC_ERROR_ID::mcNoError;
private:
AXIS_REF& _axisRef; // 轴引用,不持有所有权
bool _prevEnable = false; // 上一周期的 Enable 值(边沿检测)
};
} // namespace plcopen
```
### 2.2 实现 — `MotionLib/Src/MC_Power.cpp`
```cpp
#include "MC_Power.h"
#include "Axis.h"
namespace plcopen {
MC_Power::MC_Power(AXIS_REF& axisRef)
: _axisRef(axisRef) {}
void MC_Power::cycle() {
// ① 有效性检查
if (_axisRef.axis == nullptr) {
Error = true;
ErrorID = MC_ERROR_ID::mcInvalidParameter;
Status = false;
Busy = false;
return;
}
Axis* axis = _axisRef.axis;
Error = false;
ErrorID = MC_ERROR_ID::mcNoError;
// ② Enable 上升沿 — 使能
if (Enable && !_prevEnable) {
axis->doPower(true);
Busy = true;
}
// ③ Enable 下降沿 — 去使能
if (!Enable && _prevEnable) {
axis->doPower(false);
Busy = false;
}
// ④ 持续使能状态 — 等待轴进入 Standstill
if (Enable) {
MC_AXIS_STATE state = axis->readState();
if (state == MC_AXIS_STATE::Standstill) {
Busy = false;
Status = true; // 使能成功
} else if (state == MC_AXIS_STATE::ErrorStop) {
Busy = false;
Status = false;
Error = true;
ErrorID = MC_ERROR_ID::mcPowerFailure;
}
} else {
// 去使能后
Status = false;
Busy = false;
}
// ⑤ 保存前置状态
_prevEnable = Enable;
}
} // namespace plcopen
```
## 3. 行为时序
```
周期 | Enable | axis 状态 | Status | Busy | 说明
─────┼────────┼───────────────┼────────┼──────┼────────────────
0 | FALSE | Disabled | FALSE | FALSE| 初始状态
1 | TRUE ↑ | Disabled | FALSE | TRUE | 上升沿,doPower(true)
2 | TRUE | Disabled | FALSE | TRUE | cycle() 待处理
3 | TRUE | Standstill | TRUE | FALSE| 使能完成 ✅
4 | FALSE ↓| Standstill | FALSE | FALSE| 下降沿,doPower(false)
5 | FALSE | Disabled | FALSE | FALSE| 去使能完成
```
## 4. 与 Axis 的交互
```
MC_Power Axis
──────── ────
cycle()
├── Enable↑ → axis->doPower(true) ──→ _powerEnabled = true
│ _state 将在 cycle() 中切换
├── Enable↓ → axis->doPower(false) ──→ _powerEnabled = false
│ _state → Disabled
└── axis->readState() ←────────────── 读取 _state
axis->readError() ←───────────── 读取 _errorId
```
## 5. Axis 侧需要的新方法
```cpp
// Axis.h — 新增方法
class Axis {
public:
void doPower(BOOL enable); // 功率使能控制
MC_AXIS_STATE readState() const; // 读当前状态
MC_ERROR_ID readError() const; // 读当前错误码
};
```
## 6. 设计要点
| 要点 | 说明 |
|------|------|
| 不持有 Axis | `_axisRef` 是引用,MC_Power 不负责轴的生命周期 |
| 上升/下降沿检测 | `_prevEnable` 确保 doPower() 只在边沿调用一次 |
| Busy 的语义 | 从 Enable↑ 到 Status=TRUE 之间为 Busy |
| Error 路径 | 轴进入 ErrorStop 时,Status→FALSEError→TRUE |
| Enable_Positive/Negative | IEC 完整规范字段,本库保留输入但暂不实现逻辑 |