# 柯蒂斯电机控制器 CAN 通讯协议 ## 概述 本技能文档提供柯蒂斯(Curtis)1298+1220C 电机控制器 CAN 通讯协议参考信息及完整的通讯实现案例。 **协议名称:** Can 通讯协议(1298+1220C) **版本:** V4.0 **公司:** 上海诺力智能科技有限公司 **控制器组件:** - **Curtis 1298 软件:** Noblift_PS20V_Agv_9820C_191125_12_0_P209_V403_1.exe - **Curtis 1220C 参数:** 1220C-2201_20191106.CPF - **Rema 手柄通讯:** 09-01-14 CAN Protocol Specification **功能说明:** - 车辆行走、升降、转向均通过 CAN 总线指令进行控制 - 支持手动驾驶和自动驾驶模式切换 - 与 USB-CAN 接口配套使用(参见 can-protocol 技能) --- ## CAN 总线设置 ### 基本参数 ``` 波特率:125 kbps Timing0: 0x03 Timing1: 0x1C ``` ### CAN ID 分配 **Curtis 控制器发送(MISO):** - `0x260`:PDO1_MISO - 状态反馈数据 - `0x360`:PDO2_MISO - 保留 - **发送频率:** 20ms ± 5ms **AGV 控制器发送(MOSI):** - `0x1E5`:PDO1_MOSI - 控制命令(行走、提升) - `0x2E5`:PDO1_MOSI - 转向角度命令 **注意:** REMA CAN 手柄使用 ID `0x1E0` ### 驾驶模式切换条件 **手动驾驶模式:** - 手柄下的接近开关 `J9/SW_3` 必须闭合(ON)才能有效 **自动驾驶模式:** - `J24/SW_1 = ON` 为自动驾驶模式有效 **通讯超时:** - "AGV 通讯超时"参数可在诺力设置中配置 - Curtis 监控 AGV 的 CAN 帧是否在线 - **重要:** 必须以 ≤20ms 周期持续发送控制命令,否则控制器会进入超时保护 --- ## 数据帧格式定义 ### PDO1_MOSI(AGV → Curtis)- 0x1E5 **功能:** AGV 上位机向 Curtis 控制器发送控制命令 #### Byte0 - 控制位 1 | Bit | 功能 | 0 | 1 | |-----|------|---|---| | 0 | 调速开关 | 有效 | 无效 | | 1 | 紧急反向开关 | 无效 | 有效 | | 2 | 龟速开关 | 无效 | 有效 | | 3 | 喇叭开关 | 无效 | 有效 | | 4 | 左侧提升开关 | 无效 | 有效 | | 5 | 左侧下降开关 | 无效 | 有效 | | 6 | 右侧提升开关 | 无效 | 有效 | | 7 | 右侧下降开关 | 无效 | 有效 | #### Byte1 - 控制位 2(仅 AGV CAN 发送,REMA 手柄不发送) | Bit | 功能 | 0 | 1 | |-----|------|---|---| | 0 | AGV 标识 | 无效 | AGV 连接 | | 1 | 紧急停车 | 无效 | 紧停 | | 2 | 找中完成标记 | - | 完成 | | 3-7 | 保留 | - | - | #### Byte2-3 - 行走调速信号 - **范围:** -4096 ~ +4095(有符号 16 位) - **Byte2:** 低 8 位 - **Byte3:** 高 8 位 #### Byte4-5 - 提升/上下倾调速信号(左侧) - **范围:** -4096 ~ +4095(有符号 16 位) - **Byte4:** 低 8 位 - **Byte5:** 高 8 位 #### Byte6-7 - 提升调速信号(右侧) - **范围:** -4096 ~ +4095(有符号 16 位) - **Byte6:** 低 8 位 - **Byte7:** 高 8 位 --- ### PDO1_MOSI(AGV → Curtis)- 0x2E5 **功能:** 转向角度控制命令 #### Byte0-1 - 转向角度要求 - **范围:** -900 ~ +900(对应 -90° ~ +90°) - **Byte0:** 低 8 位 - **Byte1:** 高 8 位 **重要配置:** 1220C 参数需设置为: ``` command device/can/ can steer left stop to centre = -900 can steer right stop to centre = 900 ``` #### Byte2-7 - 保留 - 填充 0x00 --- ### PDO1_MISO(Curtis → AGV)- 0x260 **功能:** Curtis 控制器向 AGV 上位机反馈状态数据 #### Byte0 - 行走故障代码 - **格式:** 16 进制 - **说明:** 行走电机相关故障代码 #### Byte1 - 转向故障代码 - **格式:** 16 进制 - **说明:** 转向电机相关故障代码 #### Byte2-3 - 转向角度反馈 - **范围:** -16383 ~ +16383(对应 -90° ~ +90°) - **Byte2:** 低位 - **Byte3:** 高位 #### Byte4-5 - 电机转速 - **范围:** -4096 ~ +4095(区分正反转) - **Byte4:** 低 8 位 - **Byte5:** 高 8 位 #### Byte6 - 电量 - **范围:** 0 ~ 100(百分比) #### Byte7 - 状态位 | Bit | 功能 | 0 | 1 | |-----|------|---|---| | 0 | 手自动状态 | 手动 | 自动 | | 1 | 紧急停车 | 无急停 | 急停 | | 2-7 | 预留 | - | - | --- ### PDO2_MISO(Curtis → AGV)- 0x360 **功能:** 保留用途 **数据字节定义:** - Byte0 ~ Byte7:保留,填充 0x00 --- ## 通讯协议摘要表 ### 上位机向控制器发送(MOSI) | 类型 | CAN ID | Data1 | Data2 | Data3 | Data4 | Data5 | Data6 | Data7 | Data8 | |------|--------|-------|-------|-------|-------|-------|-------|-------|-------| | MOSI1 | 0x1E5 | 控制位1 | 控制位2 | 行走低位 | 行走高位 | 左提升低位 | 左提升高位 | 右提升低位 | 右提升高位 | | MOSI2 | 0x2E5 | 角度低位 | 角度高位 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | ### 控制器回复上位机(MISO) | 类型 | CAN ID | Data1 | Data2 | Data3 | Data4 | Data5 | Data6 | Data7 | Data8 | |------|--------|-------|-------|-------|-------|-------|-------|-------|-------| | MISO1 | 0x260 | 行走故障 | 转向故障 | 角度低位 | 角度高位 | 转速低位 | 转速高位 | 电量 | 状态位 | | MISO2 | 0x360 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | --- ## 完整通讯实现案例 ### 头文件和数据结构定义 ```c // curtis_motor_controller.h #ifndef CURTIS_MOTOR_CONTROLLER_H #define CURTIS_MOTOR_CONTROLLER_H #include "ControlCAN.h" #include #include #include #include // CAN 设备配置 #define CAN_DEVICE_TYPE VCI_USBCAN2 // 4 #define CAN_DEVICE_INDEX 0 // 第一个设备 #define CAN_CHANNEL 0 // CAN1 通道 // Curtis CAN ID 定义 #define CURTIS_ID_DRIVE_CMD 0x1E5 // 行走/提升命令 #define CURTIS_ID_STEER_CMD 0x2E5 // 转向命令 #define CURTIS_ID_STATUS 0x260 // 状态反馈 #define CURTIS_ID_RESERVED 0x360 // 保留 // 数据范围定义 #define DRIVE_SPEED_MIN -4096 #define DRIVE_SPEED_MAX 4095 #define LIFT_SPEED_MIN -4096 #define LIFT_SPEED_MAX 4095 #define STEER_ANGLE_MIN -900 // -90度 #define STEER_ANGLE_MAX 900 // +90度 // 控制位掩码定义(Byte0) #define CTRL_BIT0_SPEED_SWITCH 0x01 // 调速开关(0=有效) #define CTRL_BIT1_EMERGENCY_REVERSE 0x02 // 紧急反向 #define CTRL_BIT2_TURTLE_MODE 0x04 // 龟速开关 #define CTRL_BIT3_HORN 0x08 // 喇叭 #define CTRL_BIT4_LIFT_LEFT_UP 0x10 // 左侧提升 #define CTRL_BIT5_LIFT_LEFT_DOWN 0x20 // 左侧下降 #define CTRL_BIT6_LIFT_RIGHT_UP 0x40 // 右侧提升 #define CTRL_BIT7_LIFT_RIGHT_DOWN 0x80 // 右侧下降 // 控制位掩码定义(Byte1 - AGV专用) #define CTRL_AGV_CONNECTED 0x01 // AGV连接标识 #define CTRL_EMERGENCY_STOP 0x02 // 紧急停车 #define CTRL_CENTERING_DONE 0x04 // 找中完成 // 状态位掩码定义(Byte7) #define STATUS_AUTO_MODE 0x01 // 0=手动, 1=自动 #define STATUS_EMERGENCY_STOP 0x02 // 0=无急停, 1=急停 /** * Curtis 控制器状态结构 */ typedef struct { uint8_t driveError; // 行走故障代码 uint8_t steerError; // 转向故障代码 float currentAngle; // 当前转向角度(度) int16_t motorSpeed; // 电机转速 uint8_t batteryLevel; // 电量百分比 0-100 bool isAutoMode; // 是否自动模式 bool isEmergencyStop; // 是否急停状态 uint32_t lastUpdateTime; // 最后更新时间戳(毫秒) } CurtisStatus; /** * Curtis 控制命令结构 */ typedef struct { int16_t driveSpeed; // 行走速度 -4096~4095 int16_t liftSpeedLeft; // 左侧提升速度 -4096~4095 int16_t liftSpeedRight; // 右侧提升速度 -4096~4095 int16_t steerAngle; // 转向角度 -900~900 uint8_t controlBits0; // 控制位0 uint8_t controlBits1; // 控制位1(AGV专用) } CurtisCommand; /** * Curtis 控制器管理器 */ typedef struct { DWORD deviceType; DWORD deviceIndex; DWORD canChannel; bool isConnected; CurtisStatus status; CurtisCommand lastCommand; } CurtisController; // 函数声明 bool Curtis_Initialize(CurtisController* ctrl); bool Curtis_Connect(CurtisController* ctrl); void Curtis_Disconnect(CurtisController* ctrl); bool Curtis_SendCommand(CurtisController* ctrl, const CurtisCommand* cmd); bool Curtis_ReceiveStatus(CurtisController* ctrl); void Curtis_EmergencyStop(CurtisController* ctrl); int16_t Curtis_SpeedPercentToCAN(float percent); float Curtis_SpeedCANToPercent(int16_t canValue); int16_t Curtis_AngleDegreeToCAN(float degrees); float Curtis_AngleCANToDegree(int16_t canValue); #endif // CURTIS_MOTOR_CONTROLLER_H ``` --- ### 实现文件 ```c // curtis_motor_controller.c #include "curtis_motor_controller.h" #include /** * 初始化 Curtis 控制器结构 */ bool Curtis_Initialize(CurtisController* ctrl) { if (ctrl == NULL) { return false; } memset(ctrl, 0, sizeof(CurtisController)); ctrl->deviceType = CAN_DEVICE_TYPE; ctrl->deviceIndex = CAN_DEVICE_INDEX; ctrl->canChannel = CAN_CHANNEL; ctrl->isConnected = false; return true; } /** * 连接到 Curtis 控制器(通过 CAN 总线) */ bool Curtis_Connect(CurtisController* ctrl) { if (ctrl == NULL) { printf("[Curtis] 错误: 控制器指针为空\n"); return false; } if (ctrl->isConnected) { printf("[Curtis] 警告: 已经连接\n"); return true; } // 步骤1: 打开 CAN 设备 printf("[Curtis] 正在打开 CAN 设备 (类型=%d, 索引=%d)...\n", ctrl->deviceType, ctrl->deviceIndex); DWORD ret = VCI_OpenDevice(ctrl->deviceType, ctrl->deviceIndex, 0); if (ret != 1) { printf("[Curtis] 错误: 打开设备失败 (返回值=%d)\n", ret); return false; } printf("[Curtis] 设备打开成功\n"); // 步骤2: 配置 CAN 参数(125kbps) VCI_INIT_CONFIG config; config.AccCode = 0x80000008; config.AccMask = 0xFFFFFFFF; // 接收所有帧 config.Filter = 1; // 接收标准帧和扩展帧 config.Timing0 = 0x03; // 125 kbps config.Timing1 = 0x1C; config.Mode = 0; // 正常模式 config.Reserved = 0; printf("[Curtis] 正在初始化 CAN (125kbps)...\n"); ret = VCI_InitCAN(ctrl->deviceType, ctrl->deviceIndex, ctrl->canChannel, &config); if (ret != 1) { printf("[Curtis] 错误: 初始化 CAN 失败 (返回值=%d)\n", ret); VCI_CloseDevice(ctrl->deviceType, ctrl->deviceIndex); return false; } printf("[Curtis] CAN 初始化成功\n"); // 步骤3: 启动 CAN 通道 printf("[Curtis] 正在启动 CAN 通道...\n"); ret = VCI_StartCAN(ctrl->deviceType, ctrl->deviceIndex, ctrl->canChannel); if (ret != 1) { printf("[Curtis] 错误: 启动 CAN 失败 (返回值=%d)\n", ret); VCI_CloseDevice(ctrl->deviceType, ctrl->deviceIndex); return false; } printf("[Curtis] CAN 通道启动成功\n"); // 步骤4: 清空缓冲区 VCI_ClearBuffer(ctrl->deviceType, ctrl->deviceIndex, ctrl->canChannel); ctrl->isConnected = true; printf("[Curtis] ✓ 连接成功!准备通讯\n"); return true; } /** * 断开与 Curtis 控制器的连接 */ void Curtis_Disconnect(CurtisController* ctrl) { if (ctrl == NULL) { return; } if (!ctrl->isConnected) { printf("[Curtis] 警告: 尚未连接\n"); return; } // 发送停止命令 printf("[Curtis] 正在发送停止命令...\n"); CurtisCommand stopCmd = {0}; stopCmd.controlBits0 = 0x00; // 调速开关有效 stopCmd.controlBits1 = CTRL_AGV_CONNECTED; // 保持AGV连接标识 Curtis_SendCommand(ctrl, &stopCmd); Sleep(50); // 等待命令发送 // 关闭设备 printf("[Curtis] 正在关闭 CAN 设备...\n"); VCI_CloseDevice(ctrl->deviceType, ctrl->deviceIndex); ctrl->isConnected = false; printf("[Curtis] ✓ 断开连接完成\n"); } /** * 发送控制命令到 Curtis 控制器 */ bool Curtis_SendCommand(CurtisController* ctrl, const CurtisCommand* cmd) { if (ctrl == NULL || cmd == NULL) { return false; } if (!ctrl->isConnected) { printf("[Curtis] 错误: 未连接到控制器\n"); return false; } // 限制数值范围 int16_t driveSpeed = cmd->driveSpeed; if (driveSpeed < DRIVE_SPEED_MIN) driveSpeed = DRIVE_SPEED_MIN; if (driveSpeed > DRIVE_SPEED_MAX) driveSpeed = DRIVE_SPEED_MAX; int16_t liftLeft = cmd->liftSpeedLeft; if (liftLeft < LIFT_SPEED_MIN) liftLeft = LIFT_SPEED_MIN; if (liftLeft > LIFT_SPEED_MAX) liftLeft = LIFT_SPEED_MAX; int16_t liftRight = cmd->liftSpeedRight; if (liftRight < LIFT_SPEED_MIN) liftRight = LIFT_SPEED_MIN; if (liftRight > LIFT_SPEED_MAX) liftRight = LIFT_SPEED_MAX; int16_t steerAngle = cmd->steerAngle; if (steerAngle < STEER_ANGLE_MIN) steerAngle = STEER_ANGLE_MIN; if (steerAngle > STEER_ANGLE_MAX) steerAngle = STEER_ANGLE_MAX; // 发送帧1: 0x1E5 - 行走和提升命令 VCI_CAN_OBJ frame1; memset(&frame1, 0, sizeof(VCI_CAN_OBJ)); frame1.ID = CURTIS_ID_DRIVE_CMD; frame1.SendType = 0; // 正常发送(自动重试) frame1.RemoteFlag = 0; // 数据帧 frame1.ExternFlag = 0; // 标准帧 frame1.DataLen = 8; frame1.Data[0] = cmd->controlBits0; frame1.Data[1] = cmd->controlBits1 | CTRL_AGV_CONNECTED; // 确保AGV标识位 frame1.Data[2] = driveSpeed & 0xFF; frame1.Data[3] = (driveSpeed >> 8) & 0xFF; frame1.Data[4] = liftLeft & 0xFF; frame1.Data[5] = (liftLeft >> 8) & 0xFF; frame1.Data[6] = liftRight & 0xFF; frame1.Data[7] = (liftRight >> 8) & 0xFF; DWORD sent = VCI_Transmit(ctrl->deviceType, ctrl->deviceIndex, ctrl->canChannel, &frame1, 1); if (sent != 1) { printf("[Curtis] 错误: 发送 0x1E5 失败\n"); return false; } // 发送帧2: 0x2E5 - 转向命令 VCI_CAN_OBJ frame2; memset(&frame2, 0, sizeof(VCI_CAN_OBJ)); frame2.ID = CURTIS_ID_STEER_CMD; frame2.SendType = 0; frame2.RemoteFlag = 0; frame2.ExternFlag = 0; frame2.DataLen = 8; frame2.Data[0] = steerAngle & 0xFF; frame2.Data[1] = (steerAngle >> 8) & 0xFF; // Byte2-7 保留,填充0 sent = VCI_Transmit(ctrl->deviceType, ctrl->deviceIndex, ctrl->canChannel, &frame2, 1); if (sent != 1) { printf("[Curtis] 错误: 发送 0x2E5 失败\n"); return false; } // 保存最后发送的命令 ctrl->lastCommand = *cmd; return true; } /** * 接收 Curtis 控制器状态反馈 */ bool Curtis_ReceiveStatus(CurtisController* ctrl) { if (ctrl == NULL) { return false; } if (!ctrl->isConnected) { return false; } VCI_CAN_OBJ rxFrames[2500]; DWORD count = VCI_Receive(ctrl->deviceType, ctrl->deviceIndex, ctrl->canChannel, rxFrames, 2500, 0); if (count == (DWORD)-1) { printf("[Curtis] 错误: USB 断开或设备不存在\n"); ctrl->isConnected = false; return false; } bool statusUpdated = false; for (DWORD i = 0; i < count; i++) { if (rxFrames[i].ID == CURTIS_ID_STATUS) { // 解析 0x260 状态帧 ctrl->status.driveError = rxFrames[i].Data[0]; ctrl->status.steerError = rxFrames[i].Data[1]; int16_t angleRaw = (int16_t)((rxFrames[i].Data[3] << 8) | rxFrames[i].Data[2]); ctrl->status.currentAngle = angleRaw / 10.0f; ctrl->status.motorSpeed = (int16_t)((rxFrames[i].Data[5] << 8) | rxFrames[i].Data[4]); ctrl->status.batteryLevel = rxFrames[i].Data[6]; ctrl->status.isAutoMode = (rxFrames[i].Data[7] & STATUS_AUTO_MODE) != 0; ctrl->status.isEmergencyStop = (rxFrames[i].Data[7] & STATUS_EMERGENCY_STOP) != 0; ctrl->status.lastUpdateTime = GetTickCount(); statusUpdated = true; } else if (rxFrames[i].ID == CURTIS_ID_RESERVED) { // 0x360 保留帧,暂不处理 } } return statusUpdated; } /** * 紧急停车 */ void Curtis_EmergencyStop(CurtisController* ctrl) { if (ctrl == NULL || !ctrl->isConnected) { return; } printf("[Curtis] !!! 执行紧急停车 !!!\n"); CurtisCommand stopCmd = {0}; stopCmd.controlBits0 = 0x00; // 调速开关有效 stopCmd.controlBits1 = CTRL_EMERGENCY_STOP | CTRL_AGV_CONNECTED; stopCmd.driveSpeed = 0; stopCmd.liftSpeedLeft = 0; stopCmd.liftSpeedRight = 0; stopCmd.steerAngle = 0; // 连续发送3次确保收到 for (int i = 0; i < 3; i++) { Curtis_SendCommand(ctrl, &stopCmd); Sleep(10); } } /** * 速度百分比转 CAN 值 (-100% ~ +100% -> -4096 ~ +4095) */ int16_t Curtis_SpeedPercentToCAN(float percent) { if (percent > 100.0f) percent = 100.0f; if (percent < -100.0f) percent = -100.0f; return (int16_t)(percent * 40.95f); } /** * CAN 值转速度百分比 */ float Curtis_SpeedCANToPercent(int16_t canValue) { return canValue / 40.95f; } /** * 角度转 CAN 值 (-90° ~ +90° -> -900 ~ +900) */ int16_t Curtis_AngleDegreeToCAN(float degrees) { if (degrees > 90.0f) degrees = 90.0f; if (degrees < -90.0f) degrees = -90.0f; return (int16_t)(degrees * 10.0f); } /** * CAN 值转角度 */ float Curtis_AngleCANToDegree(int16_t canValue) { return canValue / 10.0f; } ``` --- ### 使用示例 - 主程序 ```c // main_curtis_demo.c #include "curtis_motor_controller.h" #include #include /** * 打印 Curtis 状态信息 */ void printStatus(const CurtisStatus* status) { printf("\n========== Curtis 状态 ==========\n"); printf("行走故障: 0x%02X 转向故障: 0x%02X\n", status->driveError, status->steerError); printf("转向角度: %.1f°\n", status->currentAngle); printf("电机转速: %d\n", status->motorSpeed); printf("电量: %d%%\n", status->batteryLevel); printf("模式: %s 急停: %s\n", status->isAutoMode ? "自动" : "手动", status->isEmergencyStop ? "是" : "否"); printf("================================\n\n"); } /** * 主程序示例:演示完整的 Curtis 控制流程 */ int main() { CurtisController curtis; bool running = true; printf("===== Curtis 电机控制器 CAN 通讯演示 =====\n\n"); // ============ 1. 初始化 ============ printf("步骤 1: 初始化控制器...\n"); if (!Curtis_Initialize(&curtis)) { printf("初始化失败!\n"); return -1; } // ============ 2. 连接 ============ printf("\n步骤 2: 连接到 Curtis 控制器...\n"); if (!Curtis_Connect(&curtis)) { printf("连接失败!\n"); return -1; } // 等待控制器稳定 Sleep(100); // ============ 3. 主控制循环 ============ printf("\n步骤 3: 进入主控制循环\n"); printf("控制说明:\n"); printf(" W/S - 前进/后退\n"); printf(" A/D - 左转/右转\n"); printf(" Q/E - 提升/下降\n"); printf(" 空格 - 停止\n"); printf(" ESC - 退出程序\n"); printf("\n开始控制...\n\n"); CurtisCommand cmd = {0}; cmd.controlBits0 = 0x00; // 调速开关有效 cmd.controlBits1 = CTRL_AGV_CONNECTED; DWORD lastSendTime = GetTickCount(); DWORD lastPrintTime = GetTickCount(); int loopCount = 0; while (running) { DWORD currentTime = GetTickCount(); // ---- 接收状态(每次循环) ---- Curtis_ReceiveStatus(&curtis); // ---- 键盘控制 ---- if (_kbhit()) { char ch = _getch(); switch (ch) { case 'w': case 'W': cmd.driveSpeed = Curtis_SpeedPercentToCAN(30.0f); // 前进30% printf("[控制] 前进 30%%\n"); break; case 's': case 'S': cmd.driveSpeed = Curtis_SpeedPercentToCAN(-30.0f); // 后退30% printf("[控制] 后退 30%%\n"); break; case 'a': case 'A': cmd.steerAngle = Curtis_AngleDegreeToCAN(-30.0f); // 左转30度 printf("[控制] 左转 30°\n"); break; case 'd': case 'D': cmd.steerAngle = Curtis_AngleDegreeToCAN(30.0f); // 右转30度 printf("[控制] 右转 30°\n"); break; case 'q': case 'Q': cmd.liftSpeedLeft = Curtis_SpeedPercentToCAN(50.0f); // 提升 printf("[控制] 提升\n"); break; case 'e': case 'E': cmd.liftSpeedLeft = Curtis_SpeedPercentToCAN(-50.0f); // 下降 printf("[控制] 下降\n"); break; case ' ': // 空格 - 停止 cmd.driveSpeed = 0; cmd.steerAngle = 0; cmd.liftSpeedLeft = 0; cmd.liftSpeedRight = 0; printf("[控制] 全部停止\n"); break; case 27: // ESC - 退出 printf("\n[控制] 用户请求退出\n"); running = false; break; } } // ---- 检查故障和急停 ---- if (curtis.status.driveError != 0 || curtis.status.steerError != 0) { printf("[警告] 检测到故障!行走=0x%02X, 转向=0x%02X\n", curtis.status.driveError, curtis.status.steerError); } if (curtis.status.isEmergencyStop) { printf("[警告] 急停状态!停止所有运动\n"); cmd.driveSpeed = 0; cmd.steerAngle = 0; cmd.liftSpeedLeft = 0; cmd.liftSpeedRight = 0; } // ---- 定时发送命令(≤20ms周期) ---- if (currentTime - lastSendTime >= 15) { // 15ms周期 if (!Curtis_SendCommand(&curtis, &cmd)) { printf("[错误] 发送命令失败!\n"); running = false; } lastSendTime = currentTime; } // ---- 定时打印状态(1秒) ---- if (currentTime - lastPrintTime >= 1000) { printStatus(&curtis.status); lastPrintTime = currentTime; } // 避免CPU占用过高 Sleep(5); loopCount++; } printf("\n总循环次数: %d\n", loopCount); // ============ 4. 断开连接 ============ printf("\n步骤 4: 断开连接...\n"); Curtis_Disconnect(&curtis); printf("\n===== 程序结束 =====\n"); return 0; } ``` --- ### 高级示例 - 路径跟踪控制 ```c // path_tracking_demo.c #include "curtis_motor_controller.h" #include #include /** * 路径点结构 */ typedef struct { float x; float y; float targetSpeed; // 目标速度百分比 } PathPoint; /** * 执行路径跟踪控制 */ bool executePathTracking(CurtisController* ctrl, const PathPoint* path, int pathLength) { if (ctrl == NULL || path == NULL || pathLength <= 0) { return false; } printf("[路径跟踪] 开始执行,共 %d 个路径点\n", pathLength); CurtisCommand cmd = {0}; cmd.controlBits0 = 0x00; cmd.controlBits1 = CTRL_AGV_CONNECTED; for (int i = 0; i < pathLength; i++) { printf("\n[路径跟踪] 执行路径点 %d/%d: (%.2f, %.2f), 速度=%.1f%%\n", i + 1, pathLength, path[i].x, path[i].y, path[i].targetSpeed); // 设置速度 cmd.driveSpeed = Curtis_SpeedPercentToCAN(path[i].targetSpeed); // 模拟转向角度计算(实际应用中根据路径规划算法计算) float steerAngleDeg = 0.0f; if (i + 1 < pathLength) { float dx = path[i + 1].x - path[i].x; float dy = path[i + 1].y - path[i].y; steerAngleDeg = atan2f(dy, dx) * 180.0f / 3.14159f; // 限制在 ±45 度 if (steerAngleDeg > 45.0f) steerAngleDeg = 45.0f; if (steerAngleDeg < -45.0f) steerAngleDeg = -45.0f; } cmd.steerAngle = Curtis_AngleDegreeToCAN(steerAngleDeg); // 执行该路径点(持续1秒) DWORD startTime = GetTickCount(); while (GetTickCount() - startTime < 1000) { // 接收状态 Curtis_ReceiveStatus(ctrl); // 检查故障 if (ctrl->status.driveError || ctrl->status.steerError) { printf("[路径跟踪] 检测到故障,中止路径跟踪\n"); Curtis_EmergencyStop(ctrl); return false; } // 检查急停 if (ctrl->status.isEmergencyStop) { printf("[路径跟踪] 检测到急停,中止路径跟踪\n"); return false; } // 发送命令(15ms周期) Curtis_SendCommand(ctrl, &cmd); Sleep(15); } } // 路径结束,停止 printf("\n[路径跟踪] 路径执行完成,停止车辆\n"); cmd.driveSpeed = 0; cmd.steerAngle = 0; for (int i = 0; i < 5; i++) { Curtis_SendCommand(ctrl, &cmd); Sleep(15); } return true; } /** * 路径跟踪示例主程序 */ int main() { CurtisController curtis; printf("===== Curtis 路径跟踪演示 =====\n\n"); // 初始化并连接 if (!Curtis_Initialize(&curtis) || !Curtis_Connect(&curtis)) { printf("初始化失败!\n"); return -1; } Sleep(100); // 定义测试路径(矩形路径) PathPoint path[] = { {0.0f, 0.0f, 30.0f}, // 起点 {5.0f, 0.0f, 30.0f}, // 直行5米 {5.0f, 3.0f, 25.0f}, // 右转前进3米 {0.0f, 3.0f, 25.0f}, // 左转前进5米 {0.0f, 0.0f, 20.0f} // 返回起点 }; int pathLength = sizeof(path) / sizeof(PathPoint); // 执行路径跟踪 bool success = executePathTracking(&curtis, path, pathLength); if (success) { printf("\n✓ 路径跟踪成功完成\n"); } else { printf("\n✗ 路径跟踪失败\n"); } // 断开连接 Curtis_Disconnect(&curtis); printf("\n===== 程序结束 =====\n"); return 0; } ``` --- ## 错误处理和最佳实践 ### 错误处理 ```c /** * 检查通讯超时 */ bool Curtis_CheckTimeout(CurtisController* ctrl, DWORD timeoutMs) { if (ctrl == NULL) { return true; // 视为超时 } DWORD currentTime = GetTickCount(); DWORD elapsed = currentTime - ctrl->status.lastUpdateTime; if (elapsed > timeoutMs) { printf("[Curtis] 警告: 通讯超时 (%d ms)\n", elapsed); return true; } return false; } /** * 通讯恢复处理 */ bool Curtis_Reconnect(CurtisController* ctrl) { printf("[Curtis] 尝试重新连接...\n"); Curtis_Disconnect(ctrl); Sleep(500); if (Curtis_Connect(ctrl)) { printf("[Curtis] 重新连接成功\n"); return true; } else { printf("[Curtis] 重新连接失败\n"); return false; } } ``` ### 最佳实践清单 1. **命令发送频率** - 必须以 ≤20ms 周期持续发送命令 - 推荐 10-15ms 周期 - 超时会导致控制器进入保护模式 2. **初始化顺序** ``` Curtis_Initialize() → Curtis_Connect() → 主循环 → Curtis_Disconnect() ``` 3. **故障处理** - 实时监控 driveError 和 steerError - 检测到故障立即停止运动 - 记录故障代码用于诊断 4. **急停处理** - 检测到急停状态立即停止所有命令 - 发送停止命令(速度=0) - 等待急停解除后才能恢复 5. **数值范围检查** - 所有命令值都应进行范围限制 - 使用提供的转换函数确保数据正确 6. **AGV标识位** - 所有命令必须设置 AGV 连接标识(Byte1 Bit0) - 否则控制器可能不响应 7. **断开连接** - 程序退出前必须调用 Curtis_Disconnect() - 发送停止命令后再关闭设备 --- ## 常见问题 ### 问:无法控制车辆? - 检查 CAN 波特率是否为 125kbps(Timing0=0x03, Timing1=0x1C) - 确认 AGV 标识位已设置(Byte1 Bit0 = 1) - 手动模式下检查 J9/SW_3 是否闭合 - 自动模式下检查 J24/SW_1 是否为 ON - 确认命令发送频率 ≤20ms ### 问:控制器频繁超时? - 检查命令发送周期,必须 ≤20ms - 检查"AGV 通讯超时"参数设置 - 验证 CAN 总线连接是否稳定 - 检查是否有其他设备干扰总线 ### 问:转向角度不准确? - 确认 1220C 参数配置: - `can steer left stop to centre = -900` - `can steer right stop to centre = 900` - 验证角度转换:-900~+900 对应 -90°~+90° - 检查转向电机是否正常工作 ### 问:收到故障代码? - 查看 Byte0(行走故障)和 Byte1(转向故障) - 参考 Curtis 故障代码手册诊断 - 检查电机连接和电源 - 检查控制器参数配置 ### 问:急停无法解除? - 检查硬件急停开关是否复位 - 确认 Byte7 Bit1 状态 - 发送命令清除软件急停(Byte1 Bit1 = 0) - 检查安全回路 --- ## 协议版本历史 **V4.0** (2021-06-05) - 当前版本 - 拟制:zuofuxing --- ## 相关文档 - **can-protocol.md** - USB-CAN 接口函数库参考(ControlCAN.dll) - **CAN_Protocol.pdf** - CAN 通讯协议基础 - **09-01-14 CAN Protocol Specification** - REMA 手柄通讯协议 **技术支持:** 上海诺力智能科技有限公司 --- ## 快速参考 ### CAN 配置 ``` 波特率: 125 kbps Timing0: 0x03 Timing1: 0x1C ``` ### CAN ID 映射 ``` AGV → Curtis: 0x1E5 - 控制命令(行走、提升) 0x2E5 - 转向角度 Curtis → AGV: 0x260 - 状态反馈 0x360 - 保留 ``` ### 数据范围 ``` 行走速度: -4096 ~ +4095 提升速度: -4096 ~ +4095 转向角度命令: -900 ~ +900 (-90° ~ +90°) 转向角度反馈: -16383 ~ +16383 (-90° ~ +90°) 电量: 0 ~ 100 (%) ``` ### 控制位快速索引 ``` Byte0 Bit0: 调速开关 (0=有效) Byte1 Bit0: AGV标识 (1=连接) ← 必须设置 Byte1 Bit1: 紧急停车 (1=急停) Byte7 Bit0: 手自动 (1=自动) Byte7 Bit1: 急停状态 (1=急停) ``` ### 关键API ```c Curtis_Initialize() // 初始化控制器 Curtis_Connect() // 连接(打开设备、初始化CAN、启动) Curtis_SendCommand() // 发送控制命令(≤20ms周期) Curtis_ReceiveStatus() // 接收状态反馈 Curtis_EmergencyStop() // 紧急停车 Curtis_Disconnect() // 断开连接(发送停止、关闭设备) ```