# 柯蒂斯电机控制器 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 <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

// 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 <windows.h>

/**
 * 初始化 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 <windows.h>
#include <conio.h>

/**
 * 打印 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 <windows.h>
#include <math.h>

/**
 * 路径点结构
 */
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()       // 断开连接（发送停止、关闭设备）
```
