新增 pMotor 推进电机操作程序:经 pCanBridge(CAN1) 与电机控制器交互
build-test-deploy / ci (push) Failing after 32m37s

- 协议模块(protocol/MotorCan):12 条 CAN ID、指令帧 0x18EF2010 编码、
  状态帧(转速/母线电压/温度)与支路一/二故障报警帧解码,故障位按协议
  逐位映射中文描述(docs/推进电机通信协议20260321.docx)
- 主程序(MOOS 应用):订阅 pCanBridge 透传 CAN_0x* 解码电机状态;
  500ms 周期下发指令帧(CAN_TX_0x18EF2010,m_sSrcAux=CAN1),
  可选 red_channel 冗余下发 0x18EF2011;复位位保持 reset_hold_sec
- 网页(18081):按协议指令帧字段独立下发(使能命令 Byte0 bit0 /
  复位命令 Byte0 bit1 / 期望转速 Byte2/3,无联动按钮);
  WebSocket 1Hz 推送转速/电压/温度/故障告警/链路统计
- 配置接入:missions/h100.moos 增加 pMotor 配置块(can_channel=CAN1)
- 测试:pmotorTest 92 项(指令帧字节级对照协议示例、解码/故障表)
- 部署:scripts/build-board.sh 增加 pMotor 产物拷贝 + pMotor.service;
  .gitignore 补 bin/pMotor、bin/pmotorTest
- 联调验证:MOOSDB+pCanBridge+假CANET 全链路,下行帧 01 00 E8 03
  与协议文档示例一致,复位位 3s 保持行为正确
This commit is contained in:
zjk
2026-09-02 10:50:28 +08:00
parent 67e732045a
commit 873a1a9b09
20 changed files with 2007 additions and 10 deletions
+154
View File
@@ -0,0 +1,154 @@
#ifndef PMOTOR_CAN_H
#define PMOTOR_CAN_H
#include <cstdint>
#include <string>
#include <vector>
namespace motor {
//============================================================================
// 推进电机控制器 CAN 通信协议(docs/推进电机通信协议20260321.docx)
//
// 数据来源:pCanBridge 透传的 MOOS 消息
// 变量名 CAN_0x%08X = CAN ID(29 位扩展帧),m_sVal = 二进制数据域(8 字节),
// m_dfVal2 = 原始帧信息字节。本模块只负责按报文 ID 解析/编码数据域字段。
//
// 物理/链路层:CAN2.0B 扩展帧,250kbps(默认),数据域 8 字节,
// 多字节数值采用 Intel 编码(低字节在前,高字节在后)。
//
// 【报文清单】
// 下行(pMotor -> 电机控制器,周期 500ms):
// 0x18EF2010 主控指令帧(冗余 CAN 0x18EF2011)
// 0x18EF2110 主控限制帧(冗余 CAN 0x18EF2111,全预留,暂不发送)
// 上行(电机控制器 -> pMotor):
// 0x18EF1020 支路一故障报警状态帧1(200ms,冗余 CAN 0x18EF1021)
// 0x18EF1120 支路二故障报警状态帧2(200ms,冗余 CAN 0x18EF1121)
// 0x18EF1220 状态帧3:反馈转速/母线电压(100ms,冗余 0x18EF1221)
// 0x18EF1320 状态帧4:温度(100ms,冗余 0x18EF1321)
//
// 【指令帧 0x18EF2010】数据域 8 字节:
// byte0 bit0 = 使能命令(0 无效 1 有效,IGBT 开关;需先预充后使能)
// byte0 bit1 = 复位命令(0 无效 1 有效,仅故障状态下有效,故障支路复位)
// byte0 bit2 = 支路一通信状态(0 正常 1 中断)
// byte0 bit3 = 支路二通信状态(0 正常 1 中断)
// byte0 bit4~7 / byte1 / byte4~7 = 预留(发 0)
// byte2/3 = (转速模式)期望转速 s16(偏移 0,分辨率 1,Intel)
// 文档示例:启动并给定 1000 转速 -> 01 00 E8 03 00 00 00 00
// 停机 -> 00 00 00 00 00 00 00 00
// 复位 -> 02 00 00 00 00 00 00 00
//
// 【状态帧3 0x18EF1220】byte0/1 反馈转速 s16(Intel),byte2/3 母线电压 u16(Intel)
//
// 【状态帧4 0x18EF1320】byte0~5 依次为 A/B/C 相绕组、逆变组件底板、
// 输出滤波器、筒内空气温度,均为 s8(偏移 0,分辨率 1)
//
// 【故障帧 0x18EF1020/0x18EF1120】4 个 16 位字(byte0|1、byte2|3、byte4|5、byte6|7),
// 字内 bit 定义见 MotorCan.cpp 中描述表;前两帧仅支路一/支路二
// byte0|1 的 bit7 不同(支路一=筒内空气过温故障,支路二=预留)。
//============================================================================
// CAN ID 定义(29 位扩展帧)
enum MotorCanId : uint32_t {
MOTOR_CAN_CMD = 0x18EF2010u, // 主控指令帧(pMotor -> 电机)
MOTOR_CAN_CMD_RED = 0x18EF2011u, // 主控指令帧 冗余 CAN
MOTOR_CAN_LIMIT = 0x18EF2110u, // 主控限制帧(全预留,暂不使用)
MOTOR_CAN_LIMIT_RED = 0x18EF2111u, // 主控限制帧 冗余 CAN
MOTOR_CAN_FAULT1 = 0x18EF1020u, // 支路一故障报警状态帧1
MOTOR_CAN_FAULT1_RED = 0x18EF1021u, // 支路一 冗余 CAN
MOTOR_CAN_FAULT2 = 0x18EF1120u, // 支路二故障报警状态帧2
MOTOR_CAN_FAULT2_RED = 0x18EF1121u, // 支路二 冗余 CAN
MOTOR_CAN_STATE3 = 0x18EF1220u, // 状态帧3(转速/母线电压)
MOTOR_CAN_STATE3_RED = 0x18EF1221u, // 状态帧3 冗余 CAN
MOTOR_CAN_STATE4 = 0x18EF1320u, // 状态帧4(温度)
MOTOR_CAN_STATE4_RED = 0x18EF1321u, // 状态帧4 冗余 CAN
};
// 指令动作(网页 /api/motor_cmd -> 队列 -> Iterate 应用)
// 与协议指令帧字段一一对应,独立下发、无联动:
enum MotorAction {
MOTOR_ACT_ENABLE = 1, // 使能命令=1(Byte0 bit0,保持当前期望转速)
MOTOR_ACT_DISABLE = 2, // 使能命令=0(Byte0 bit0,协议"停机"示例:期望转速清 0)
MOTOR_ACT_SPEED = 3, // 期望转速(Byte2/3,speed 参数,使能状态保持不变)
MOTOR_ACT_RESET = 4, // 复位命令=1(Byte0 bit1,保持高 resetHold 秒,仅故障态有效)
};
// 网页下发的控制指令
struct MotorCmd {
int action = 0; // MotorAction
int16_t speed = 0; // MOTOR_ACT_SPEED / MOTOR_ACT_START 有效
};
// 单条电机上行消息的新鲜度 + 原始数据
struct MotorBranchState {
int branch = 0; // 1 / 2
uint16_t word[4] = {0, 0, 0, 0}; // 4 个 16 位字(故障1/故障2/故障3/报警)
std::vector<std::string> faults; // 当前置位的故障描述(word0~2)
std::vector<std::string> alarms; // 当前置位的报警描述(word3)
bool valid = false; // 是否收到过该支路故障帧
double lastRxTime = 0; // 最近收到时刻(MOOSTime,调用方设置)
};
// 电机最新合成状态(各帧部分更新合并)
struct MotorState {
// 状态帧3 0x18EF1220
int16_t feedbackSpeed = 0; // 反馈转速
uint16_t busVoltage = 0; // 母线电压
bool state3Valid = false;
double state3LastRx = 0;
// 状态帧4 0x18EF1320(s8,偏移 0)
int8_t tempA = 0, tempB = 0, tempC = 0; // A/B/C 相绕组温度
int8_t tempInv = 0, tempFilter = 0, tempAir = 0; // 逆变底板/滤波器/筒内空气
bool state4Valid = false;
double state4LastRx = 0;
// 故障/报警
MotorBranchState branch1;
MotorBranchState branch2;
// 计数
uint32_t rxCount = 0; // 命中电机报文的帧计数
};
// 是否为电机协议定义的 CAN ID(含冗余 CAN 变体)
inline bool isCanMotorId(uint32_t canId) {
switch (canId) {
case MOTOR_CAN_CMD:
case MOTOR_CAN_CMD_RED:
case MOTOR_CAN_LIMIT:
case MOTOR_CAN_LIMIT_RED:
case MOTOR_CAN_FAULT1:
case MOTOR_CAN_FAULT1_RED:
case MOTOR_CAN_FAULT2:
case MOTOR_CAN_FAULT2_RED:
case MOTOR_CAN_STATE3:
case MOTOR_CAN_STATE3_RED:
case MOTOR_CAN_STATE4:
case MOTOR_CAN_STATE4_RED:
return true;
default:
return false;
}
}
// 故障/报警字索引(帧内):0=byte0|1 1=byte2|3 2=byte4|5 3=byte6|7(报警)
extern const char* kMotorWordName[4];
// 按支路(1/2)与字索引查故障/报警描述表,把 bits 中置位的描述填入 out;
// 返回描述条数。wordIdx 非法或无置位返回 0。
int motorFaultTexts(int branch, int wordIdx, uint16_t bits,
std::vector<std::string>& out);
// 编码指令帧数据域(8 字节,经 MOOS CAN_TX_0x%08X 下发 pCanBridge 发送):
// [0] = enable(bit0) | reset(bit1)(支路通信状态位恒 0)
// [1] = 0 [2]/[3] = speed s16 Intel(停用时调用方传 0) [4..7] = 0
void buildMotorCmdData(bool enable, bool reset, int16_t speed, uint8_t out[8]);
// 解析一帧 CAN 数据域;命中电机上行报文返回 true 并部分更新 st:
// 故障帧 -> 对应支路 word[4] + 故障/报警描述
// 状态帧3 -> feedbackSpeed / busVoltage
// 状态帧4 -> 6 路温度
// 非电机报文或 data/dlc 非法返回 false。lastRxTime 由调用方设置。
bool decodeMotorFrame(uint32_t canId, const uint8_t* data, int dlc, MotorState& st);
} // namespace motor
#endif // PMOTOR_CAN_H