30 KiB
30 KiB
柯蒂斯电机控制器 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 |
完整通讯实现案例
头文件和数据结构定义
// 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
实现文件
// 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;
}
使用示例 - 主程序
// 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;
}
高级示例 - 路径跟踪控制
// 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;
}
错误处理和最佳实践
错误处理
/**
* 检查通讯超时
*/
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;
}
}
最佳实践清单
-
命令发送频率
- 必须以 ≤20ms 周期持续发送命令
- 推荐 10-15ms 周期
- 超时会导致控制器进入保护模式
-
初始化顺序
Curtis_Initialize() → Curtis_Connect() → 主循环 → Curtis_Disconnect() -
故障处理
- 实时监控 driveError 和 steerError
- 检测到故障立即停止运动
- 记录故障代码用于诊断
-
急停处理
- 检测到急停状态立即停止所有命令
- 发送停止命令(速度=0)
- 等待急停解除后才能恢复
-
数值范围检查
- 所有命令值都应进行范围限制
- 使用提供的转换函数确保数据正确
-
AGV标识位
- 所有命令必须设置 AGV 连接标识(Byte1 Bit0)
- 否则控制器可能不响应
-
断开连接
- 程序退出前必须调用 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 = -900can 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
Curtis_Initialize() // 初始化控制器
Curtis_Connect() // 连接(打开设备、初始化CAN、启动)
Curtis_SendCommand() // 发送控制命令(≤20ms周期)
Curtis_ReceiveStatus() // 接收状态反馈
Curtis_EmergencyStop() // 紧急停车
Curtis_Disconnect() // 断开连接(发送停止、关闭设备)