1085 lines
30 KiB
Markdown
1085 lines
30 KiB
Markdown
# 柯蒂斯电机控制器 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() // 断开连接(发送停止、关闭设备)
|
||
```
|