├── script ├── __init__.py ├── __pycache__ │ └── sf_can_controller.cpython-313.pyc ├── main.py └── sf_can_controller.py ├── include ├── CAN_twai.h ├── CAN_comm.h ├── CAN_twai.cpp └── CAN_comm.cpp ├── CMakeLists.txt ├── src ├── config.h └── main.cpp ├── README.md └── LICENSE /script/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /script/__pycache__/sf_can_controller.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seeed-Projects/Stackforce-Motor-SDK/main/script/__pycache__/sf_can_controller.cpython-313.pyc -------------------------------------------------------------------------------- /include/CAN_twai.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _CAN_TWAI_H 3 | #define _CAN_TWAI_H 4 | 5 | #include 6 | 7 | class CAN_twai 8 | { 9 | public: 10 | CAN_twai(int num); 11 | ~CAN_twai(); 12 | void CAN_Init(uint8_t TX_PIN,uint8_t RX_PIN); 13 | void CAN_Send(uint32_t* id_buf,uint8_t* buf); 14 | 15 | private: 16 | int _num; 17 | int _socket_fd; 18 | }; 19 | 20 | 21 | #endif 22 | 23 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(sfmotor_control LANGUAGES CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | 8 | add_executable(sfmotor_control 9 | src/main.cpp 10 | include/CAN_comm.cpp 11 | include/CAN_twai.cpp 12 | ) 13 | 14 | target_include_directories(sfmotor_control 15 | PRIVATE 16 | ${CMAKE_CURRENT_SOURCE_DIR}/include 17 | ${CMAKE_CURRENT_SOURCE_DIR}/src 18 | ) 19 | 20 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFIG_H_ 2 | #define _CONFIG_H_ 3 | 4 | #define CAN_ID __int_reg[0] 5 | 6 | extern float __float_reg[]; 7 | extern int __int_reg[]; 8 | 9 | typedef struct 10 | { 11 | float pos; 12 | float vel; 13 | float kp; 14 | float kd; 15 | float tor; 16 | float posMin = -3.14f; 17 | float posMax = 3.14f; 18 | float velMin = -45.0f; 19 | float velMax = 45.0f; 20 | float kpMin = 0.0f; 21 | float kpMax = 500.0f; 22 | float kdMin = 0.0f; 23 | float kdMax = 5.0f; 24 | float torMin = -18.0f; 25 | float torMax = 18.0f; 26 | }MIT; 27 | 28 | typedef struct{ 29 | MIT MITData; 30 | }controller; 31 | 32 | typedef struct{ 33 | 34 | }motorconfig; 35 | 36 | typedef struct{ 37 | uint8_t deviceID; 38 | bool enable; 39 | }MotorState; 40 | 41 | #endif -------------------------------------------------------------------------------- /include/CAN_comm.h: -------------------------------------------------------------------------------- 1 | #ifndef _CAN_COMM_H_ 2 | #define _CAN_COMM_H_ 3 | 4 | #include 5 | #include "config.h" 6 | 7 | #define DEVICEID_BITS 5 8 | #define COMMANDID_BITS 6 9 | 10 | #define HEARTBEAT_ID 0X00 11 | 12 | 13 | 14 | #define MITCOMMAND_ID 0X01 15 | 16 | extern uint32_t sendNum; 17 | extern uint32_t recNum; 18 | 19 | extern MIT devicesState[4]; 20 | 21 | // Function ID 22 | #define HEARTBEAT_FUNC_ID 0X780 23 | #define FUNC_ID_NMT 0x000 24 | #define FUNC_ID_RPDO1 0x200 25 | #define FUNC_ID_RPDO2 0x300 26 | #define FUNC_ID_RPDO3 0x400 27 | #define FUNC_ID_RPDO4 0x500 28 | #define FUNC_ID_TPDO1 0x180 29 | #define FUNC_ID_TPDO2 0x280 30 | #define FUNC_ID_TPDO3 0x380 31 | #define FUNC_ID_TPDO4 0x480 32 | #define FUNC_ID_SDO_REQUEST 0x600 33 | #define FUNC_ID_SDO_RESPONSE 0x580 34 | 35 | 36 | #define LIMIT_MIN_MAX(x,min,max) (x) = (((x)<=(min))?(min):(((x)>=(max))?(max):(x))) 37 | const int rx_queue_size = 10; 38 | 39 | 40 | 41 | void CANInit(); 42 | 43 | void MITState_callback(uint8_t nodeID, uint8_t *data); 44 | 45 | void sendCANCommand(uint32_t nodeID, uint32_t msgID, uint8_t *data); 46 | void recCANMessage(); 47 | 48 | void sendMITCommand(uint8_t nodeID, MIT command); 49 | void disable(uint8_t nodeID); 50 | void enable(uint8_t nodeID); 51 | void zeroPos(uint8_t nodeID); 52 | float uint_to_float(int x_int, float x_min, float x_max, int bits); 53 | uint16_t float_to_uint(float x, float x_min, float x_max, uint8_t bits); 54 | 55 | 56 | 57 | #endif 58 | 59 | -------------------------------------------------------------------------------- /include/CAN_twai.cpp: -------------------------------------------------------------------------------- 1 | #include "CAN_twai.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | CAN_twai::CAN_twai(int num) 15 | : _num(num), _socket_fd(-1) {} 16 | 17 | CAN_twai::~CAN_twai() { 18 | if (_socket_fd >= 0) { 19 | close(_socket_fd); 20 | _socket_fd = -1; 21 | } 22 | } 23 | 24 | void CAN_twai::CAN_Init(uint8_t TX_PIN,uint8_t RX_PIN) 25 | { 26 | (void)TX_PIN; 27 | (void)RX_PIN; 28 | 29 | if (_socket_fd >= 0) { 30 | return; 31 | } 32 | 33 | _socket_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW); 34 | if (_socket_fd < 0) { 35 | std::perror("socket"); 36 | return; 37 | } 38 | 39 | char ifname[IFNAMSIZ] = {}; 40 | std::snprintf(ifname, sizeof(ifname), "can%d", _num); 41 | 42 | struct ifreq ifr{}; 43 | std::strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); 44 | if (ioctl(_socket_fd, SIOCGIFINDEX, &ifr) < 0) { 45 | std::perror("ioctl SIOCGIFINDEX"); 46 | close(_socket_fd); 47 | _socket_fd = -1; 48 | return; 49 | } 50 | 51 | struct sockaddr_can addr{}; 52 | addr.can_family = AF_CAN; 53 | addr.can_ifindex = ifr.ifr_ifindex; 54 | if (bind(_socket_fd, reinterpret_cast(&addr), sizeof(addr)) < 0) { 55 | std::perror("bind"); 56 | close(_socket_fd); 57 | _socket_fd = -1; 58 | return; 59 | } 60 | 61 | int flags = fcntl(_socket_fd, F_GETFL, 0); 62 | if (flags >= 0) { 63 | fcntl(_socket_fd, F_SETFL, flags | O_NONBLOCK); 64 | } 65 | 66 | std::printf("CAN_twai initialized on %s\n", ifname); 67 | } 68 | 69 | 70 | void CAN_twai::CAN_Send(uint32_t* id_buf,uint8_t* buf) { 71 | if (_socket_fd < 0) { 72 | std::fprintf(stderr, "CAN_twai socket not initialized\n"); 73 | return; 74 | } 75 | 76 | struct can_frame message{}; 77 | message.can_id = id_buf ? (id_buf[0] & CAN_SFF_MASK) : 0; 78 | message.can_dlc = 8; 79 | if (buf) { 80 | std::memcpy(message.data, buf, 8); 81 | } 82 | 83 | if (write(_socket_fd, &message, sizeof(message)) == sizeof(message)) { 84 | std::printf("Message queued for transmission\n"); 85 | } else { 86 | std::perror("write"); 87 | } 88 | } -------------------------------------------------------------------------------- /script/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import select 4 | 5 | # 导入核心控制包 (假设 sf_can_controller.py 位于同一目录下) 6 | from sf_can_controller import MotorController 7 | 8 | # --- 核心配置 --- 9 | IFACE = "can0" 10 | MOTOR_ID = 1 11 | UPDATE_RATE_HZ = 100.0 12 | PRINT_EVERY = 2 13 | INITIAL_TARGET_DEG = 0.0 14 | 15 | # --- 主控制循环 --- 16 | def run_simple_test() -> None: 17 | """运行简化的位置控制循环。""" 18 | 19 | # 1. 初始化 20 | update_period = 1.0 / UPDATE_RATE_HZ 21 | target_rad = INITIAL_TARGET_DEG 22 | 23 | KP, KD = 0.5, 0.3 # 默认 MIT 参数 24 | 25 | controller = MotorController(interface=IFACE, motor_id=MOTOR_ID) 26 | print(f"--- SF Motor Test Start ---") 27 | print(f"接口: {IFACE}, ID: {MOTOR_ID}, 频率: {UPDATE_RATE_HZ} Hz") 28 | 29 | # 2. 使能 30 | controller.enable() 31 | 32 | last_send_time = time.perf_counter() 33 | print_counter = 0 34 | 35 | inputCheckCount = 0; 36 | # 3. 主循环 37 | while True: 38 | controller.poll_rx() 39 | current_state = controller.get_motor_state() 40 | 41 | now = time.perf_counter() 42 | 43 | # --- 周期性输入检查 (每 100 个循环) --- 44 | inputCheckCount += 1 45 | if inputCheckCount >= 500: 46 | inputCheckCount = 0 47 | 48 | # 使用阻塞 I/O 等待用户输入(会暂停控制循环) 49 | # 注意:如果输入非数字,程序将抛出 ValueError 崩溃。 50 | line = input("请输入目标关节角度: ").strip() 51 | if line: 52 | angle_deg = float(line) 53 | target_rad = angle_deg 54 | print(f"已更新目标关节角度:{angle_deg:.3f} °") 55 | 56 | # 周期性发送 MIT 命令 57 | if now - last_send_time >= update_period: 58 | last_send_time = now 59 | 60 | # 发送目标位置命令 61 | controller.send_mit_command( 62 | pos=target_rad, 63 | vel=0.0, 64 | kp=KP, 65 | kd=KD, 66 | tor=0.0 67 | ) 68 | 69 | # 打印状态 70 | print_counter += 1 71 | if print_counter >= PRINT_EVERY: 72 | print_counter = 0 73 | print( 74 | f"Cmd={target_rad:.2f} | " 75 | f"Pos={current_state.pos:.2f} (Vel={current_state.vel:.2f})" 76 | ) 77 | 78 | time.sleep(0.001) 79 | 80 | 81 | if __name__ == "__main__": 82 | # 运行测试 83 | run_simple_test() -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "CAN_comm.h" 7 | #include "config.h" 8 | 9 | 10 | #define myID 0x10 11 | 12 | MIT devicesState[4]; 13 | 14 | uint32_t sendNum; // for test send speed 15 | uint32_t recNum; 16 | 17 | MIT MITCtrlParam; 18 | 19 | 20 | uint16_t sendCounter = 0; 21 | bool motorEnable = true; 22 | int receivedNumber = 0; 23 | uint64_t prev_ts = 0; 24 | float t = 0.0f; 25 | float targetJointAngle = 0.0f; // 目标关节角度,可在运行时输入修改 26 | 27 | namespace { 28 | uint64_t micros_steady(){ 29 | using namespace std::chrono; 30 | return duration_cast(steady_clock::now().time_since_epoch()).count(); 31 | } 32 | } 33 | 34 | 35 | void setup() { 36 | std::printf("SF Motor Control (Jetson) start\n"); 37 | CANInit(); 38 | enable(0x01);//使能ID为0X01的电机 39 | prev_ts = micros_steady(); 40 | t = 0.0f; 41 | } 42 | 43 | uint16_t printCount = 0; 44 | uint16_t recCount = 0; 45 | 46 | 47 | void loop() { 48 | 49 | recCANMessage(); 50 | 51 | // 检查是否有新的关节角度输入(每100次循环检查一次,避免频繁检查) 52 | static uint16_t inputCheckCount = 0; 53 | if(++inputCheckCount >= 1000){ 54 | inputCheckCount = 0; 55 | float newAngle; 56 | if(std::scanf("%f", &newAngle) == 1){ 57 | targetJointAngle = newAngle; 58 | std::printf("已更新目标关节角度: %.3f rad\n", newAngle); 59 | } 60 | } 61 | 62 | static int IDswitch = 0x01; 63 | uint64_t current_ts = micros_steady(); 64 | 65 | /* 66 | * 函数功能:根据时间差更新控制参数,并发送MIT命令。 67 | * 参数说明: 68 | * - rent_ts: 当前时间戳 69 | * - prev_ts: 上一次记录的时间戳 70 | * - t: 时间变量,用于计算正弦和余弦函数 71 | * - MITCtrlParam: 控制参数结构体,包含位置、速度、位置增益、速度增益和扭矩等参数 72 | * - IDswitch: 用于切换ID的计数器 73 | * 返回值:无 74 | */ 75 | if(current_ts - prev_ts >= 1000){//1ms 76 | // 更新时间变量t,增加1毫秒 77 | t+=0.001; 78 | // 设置控制参数中的目标位置、目标速度和位置增益、速度增益和扭矩 79 | MITCtrlParam.pos = targetJointAngle; 80 | MITCtrlParam.vel = 0; 81 | MITCtrlParam.kp = 0.5; 82 | MITCtrlParam.kd = 0.3; 83 | MITCtrlParam.tor = 0; 84 | // 更新上一次记录的时间戳 85 | prev_ts = current_ts; 86 | 87 | // IDswitch++; 88 | // 如果IDswitch超过0x04,则重置为0x01 89 | // if(IDswitch > 0x04){ 90 | // IDswitch = 0x01; 91 | // } 92 | sendMITCommand(IDswitch, MITCtrlParam);// 发送MIT命令 93 | 94 | printCount++; 95 | if(printCount >= 100){ // 96 | printCount = 0; 97 | // 仅当IDswitch为0x01时,分别打印发送出去和接收电机状态的pos和vel值 98 | if(IDswitch == 0x01){ 99 | std::printf("%.2f,%.2f,%.2f,%.2f\n", MITCtrlParam.pos, MITCtrlParam.vel,devicesState[IDswitch - 1].pos, devicesState[IDswitch - 1].vel); 100 | } 101 | } 102 | } 103 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 104 | } 105 | 106 | int main(){ 107 | setup(); 108 | 109 | while(true){ 110 | loop(); 111 | } 112 | disable(0x01); 113 | return 0; 114 | } 115 | 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SF Motor Control 2 | 3 | SF Motor Control 是一个基于 C++ 的电机控制项目,用于通过 CAN 总线与 SF 系列电机控制器通信,实现对电机的精确控制。 4 | 5 | ## 项目概述 6 | 7 | 该项目提供了通过 CAN 总线控制 SF 系列电机的功能,支持电机使能、失能、零位设置以及 MIT(Motor Identity Transform)控制模式等功能。MIT 控制模式允许用户直接设定电机的位置、速度、Kp和Kd等参数。 8 | 9 | 项目同时提供了 C++ 和 Python 两种语言的实现 10 | 11 | ## 功能特点 12 | 13 | - 支持 SF 系列电机的完整控制(使能/失能/零位设置) 14 | - 实现 MIT 控制模式,可精确控制电机位置、速度和力矩 15 | - 支持多电机控制(最多 4 个电机) 16 | - 提供实时反馈,可以读取电机的实际位置、速度和扭矩 17 | - 同时提供 C++ 和 Python 实现 18 | 19 | ## 硬件要求 20 | 21 | - 支持 CAN 总线的主机系统(如 Jetson Nano、树莓派等) 22 | - SF 系列电机及相应的驱动器 23 | - CAN 总线收发器 24 | - 正确连接的 CAN 总线网络 25 | 26 | ## 软件依赖 27 | 28 | ### C++ 版本 29 | - C++17 兼容的编译器(如 GCC 7+) 30 | - CMake 3.10+ 31 | - Linux 系统(需要 socketCAN 支持) 32 | 33 | ### Python 版本 34 | - Python 3.6+ 35 | 36 | ## 使用方法 37 | 38 | 在运行之前,请确保系统已经正确配置了 CAN 接口。例如,对于 `can0` 接口: 39 | 40 | ```bash 41 | sudo ip link set can0 up type can bitrate 1000000 42 | sudo ip link set can0 up 43 | ``` 44 | 45 | ### C++ 版本 46 | 47 | ```bash 48 | cd build 49 | cmake .. 50 | make 51 | ``` 52 | 53 | 编译后的可执行文件将位于 `build/sfmotor_control`。运行程序: 54 | 55 | ```bash 56 | ./sfmotor_control 57 | ``` 58 | 59 | 程序默认会控制 ID 为 0x01 的电机,在运行过程中可以通过键盘输入目标角度值,单位rad。同时接收电机角度,角速度的反馈数据。 60 | 61 | ### Python 版本 62 | 63 | Python 脚本位于 `script/` 目录中,可以直接运行无需编译。 64 | 65 | ```bash 66 | python main.py 67 | ``` 68 | 69 | 程序默认会控制 ID 为 0x01 的电机,在运行过程中可以通过键盘输入目标角度值,单位rad。同时接收电机角度,角速度的反馈数据。 70 | 71 | ## 代码结构 72 | 73 | ``` 74 | SFmotor_control/ 75 | ├── include/ # 头文件及实现文件 76 | │ ├── CAN_comm.cpp # CAN 通信实现 77 | │ ├── CAN_comm.h # CAN 通信接口定义 78 | │ ├── CAN_twai.cpp # TWAI 协议实现 79 | │ └── CAN_twai.h # TWAI 协议接口定义 80 | ├── script/ # Python 控制脚本 81 | │ ├── __init__.py 82 | │ ├── main.py # Python 示例主程序 83 | │ └── sf_can_controller.py # Python CAN 控制器实现 84 | ├── src/ # C++ 主程序源码 85 | │ ├── config.h # 配置和数据结构定义 86 | │ └── main.cpp # 主控制程序 87 | └── CMakeLists.txt # CMake 构建配置 88 | ``` 89 | 90 | ## 通信协议 91 | 92 | 项目实现了针对 SF 系列电机的专用 CAN 通信协议: 93 | 94 | - 使用标准 CAN 帧(11 位标识符) 95 | - 支持多种功能码(NMT、RPDO、TPDO 等) 96 | - MIT 控制模式采用专有数据格式 97 | 98 | ### 主要功能码 99 | 100 | | 功能码 | 值 | 用途 | 101 | |--------|--------|------------------| 102 | | NMT | 0x000 | 网络管理 | 103 | | RPDO1 | 0x200 | 实时过程数据输出 | 104 | | TPDO1 | 0x180 | 实时过程数据输入 | 105 | 106 | ### 控制命令 107 | 108 | 1. **使能命令**:通过 NMT 功能码发送使能信号 109 | 2. **失能命令**:通过 NMT 功能码发送失能信号 110 | 3. **MIT 控制命令**:通过 RPDO1 发送位置、速度、Kp、Kd 和扭矩参数 111 | 112 | ## API 参考 113 | 114 | ### C++ API 115 | 116 | 主要函数包括: 117 | 118 | - `CANInit()` - 初始化 CAN 接口 119 | - `enable(uint8_t nodeID)` - 使能指定 ID 的电机 120 | - `disable(uint8_t nodeID)` - 失能指定 ID 的电机 121 | - `sendMITCommand(uint8_t nodeID, MIT command)` - 发送 MIT 控制命令 122 | - `recCANMessage()` - 接收 CAN 消息 123 | 124 | ### Python API 125 | 126 | 主要类和方法: 127 | 128 | - `MotorController` 类 129 | - `enable()` - 使能电机 130 | - `disable()` - 失能电机 131 | - `send_mit_command(pos, vel, kp, kd, tor)` - 发送 MIT 控制命令 132 | - `poll_rx()` - 轮询接收 CAN 消息 133 | 134 | ## 注意事项 135 | 136 | 1. 在运行程序前必须确保 CAN 接口已经正确配置并启动 137 | 2. 电机控制涉及大功率设备,请注意电气安全 138 | 3. 项目目前假设 CAN 接口名称为 `can0`,如有不同请修改源代码 139 | 4. 控制参数(如 Kp、Kd)需要根据具体应用场景进行调整 140 | 141 | ## 故障排除 142 | 143 | 常见问题及解决方案: 144 | 145 | 1. **无法打开 CAN 接口** 146 | - 检查是否正确配置并启动了 CAN 接口 147 | - 确认使用的接口名称是否匹配(默认为 can0) 148 | 149 | 2. **无法与电机通信** 150 | - 检查 CAN 总线物理连接 151 | - 确认电机 ID 设置是否正确 152 | - 验证 CAN 波特率设置是否匹配 153 | 154 | 3. **控制效果不佳** 155 | - 调整 PID 参数(Kp、Kd) 156 | - 检查电机和负载的机械连接 157 | - 确保供电电压稳定 158 | -------------------------------------------------------------------------------- /script/sf_can_controller.py: -------------------------------------------------------------------------------- 1 | # sf_can_controller.py 2 | 3 | import socket 4 | import struct 5 | import sys 6 | import select 7 | from dataclasses import dataclass 8 | from typing import List, Optional, Tuple 9 | 10 | # CAN 帧格式和 ID 定义 11 | CAN_FRAME_FMT = "=IB3x8s" 12 | CAN_FRAME_SIZE = struct.calcsize(CAN_FRAME_FMT) 13 | 14 | HEARTBEAT_FUNC_ID = 0x780 15 | FUNC_ID_NMT = 0x000 16 | FUNC_ID_RPDO1 = 0x200 17 | 18 | 19 | def float_to_uint(x: float, x_min: float, x_max: float, bits: int) -> int: 20 | """浮点数转定点整数。""" 21 | span = x_max - x_min 22 | x = max(min(x, x_max), x_min) 23 | return int((x - x_min) * ((1 << bits) - 1) / span + 0.5) 24 | 25 | 26 | def uint_to_float(x_int: int, x_min: float, x_max: float, bits: int) -> float: 27 | """定点整数转浮点数。""" 28 | span = x_max - x_min 29 | return float(x_int) * span / ((1 << bits) - 1) + x_min 30 | 31 | 32 | @dataclass 33 | class MITState: 34 | """SF 电机状态和限制。""" 35 | pos: float = 0.0 36 | vel: float = 0.0 37 | tor: float = 0.0 38 | posMin: float = -3.14 39 | posMax: float = 3.14 40 | velMin: float = -45.0 41 | velMax: float = 45.0 42 | kpMin: float = 0.0 43 | kpMax: float = 500.0 44 | kdMin: float = 0.0 45 | kdMax: float = 5.0 46 | torMin: float = -18.0 47 | torMax: float = 18.0 48 | 49 | 50 | class SocketCAN: 51 | """SocketCAN 封装类。""" 52 | def __init__(self, interface: str): 53 | self.sock = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW) 54 | self.sock.setblocking(False) 55 | self.sock.bind((interface,)) 56 | 57 | def send(self, can_id: int, data: bytes) -> None: 58 | """发送 CAN 帧。""" 59 | payload = data.ljust(8, b"\x00") 60 | frame = struct.pack(CAN_FRAME_FMT, can_id, len(data), payload) 61 | self.sock.send(frame) 62 | 63 | def recv(self) -> Optional[Tuple[int, int, bytes]]: 64 | """接收 CAN 帧 (非阻塞)。""" 65 | try: 66 | frame = self.sock.recv(CAN_FRAME_SIZE) 67 | except BlockingIOError: 68 | return None 69 | can_id, dlc, data = struct.unpack(CAN_FRAME_FMT, frame) 70 | return can_id, dlc, data 71 | 72 | def close(self) -> None: 73 | """关闭 SocketCAN 连接。""" 74 | self.sock.close() 75 | 76 | 77 | class MotorController: 78 | """SF 电机控制逻辑类。""" 79 | def __init__(self, interface: str, motor_id: int): 80 | self.can = SocketCAN(interface) 81 | self.motor_id = motor_id 82 | # 存储所有电机的状态 83 | self.devices_state: List[MITState] = [MITState() for _ in range(4)] 84 | self.command = MITState() # 用于封包限制 85 | 86 | def _send_cmd(self, base_id: int, data: bytes) -> None: 87 | """发送 CAN 命令的私有方法。""" 88 | can_id = (base_id + self.motor_id) & socket.CAN_SFF_MASK 89 | self.can.send(can_id, data) 90 | 91 | # --- NMT 控制命令 --- 92 | def enable(self) -> None: 93 | """使能电机。""" 94 | data = bytes([0xFF] * 7 + [0xFC]) 95 | self._send_cmd(FUNC_ID_NMT, data) 96 | 97 | def disable(self) -> None: 98 | """失能电机。""" 99 | data = bytes([0xFF] * 7 + [0xFD]) 100 | self._send_cmd(FUNC_ID_NMT, data) 101 | 102 | # --- MIT 控制命令 --- 103 | def send_mit_command(self, pos: float, vel: float, kp: float, kd: float, tor: float) -> None: 104 | """打包并发送 MIT 控制命令。""" 105 | cmd = self.command 106 | 107 | pos_int = float_to_uint(pos, cmd.posMin, cmd.posMax, 16) 108 | vel_int = float_to_uint(vel, cmd.velMin, cmd.velMax, 12) 109 | kp_int = float_to_uint(kp, cmd.kpMin, cmd.kpMax, 12) 110 | kd_int = float_to_uint(kd, cmd.kdMin, cmd.kdMax, 12) 111 | tor_int = float_to_uint(tor, cmd.torMin, cmd.torMax, 12) 112 | 113 | MITcommand = bytearray(8) 114 | 115 | MITcommand[0] = (pos_int >> 8) 116 | MITcommand[1] = (pos_int & 0xFF) 117 | MITcommand[2] = (vel_int >> 4) 118 | MITcommand[3] = ((vel_int & 0xF) << 4) | (kp_int >> 8) 119 | MITcommand[4] = (kp_int & 0xFF) 120 | MITcommand[5] = (kd_int >> 4) 121 | MITcommand[6] = ((kd_int & 0xF) << 4) | (tor_int >> 8) 122 | MITcommand[7] = (tor_int & 0xFF) 123 | 124 | self._send_cmd(FUNC_ID_RPDO1, bytes(MITcommand)) 125 | 126 | # --- 接收和状态处理 --- 127 | def handle_frame(self, can_id: int, dlc: int, data: bytes) -> Optional[MITState]: 128 | """处理收到的单个 CAN 帧,更新电机状态。""" 129 | func_id = can_id & 0x780 130 | node_id = can_id & 0x7F 131 | 132 | if func_id != HEARTBEAT_FUNC_ID or node_id == 0: 133 | return None 134 | 135 | index = node_id - 1 136 | if index >= len(self.devices_state): 137 | return None 138 | 139 | # 解包数据 140 | pos_int = (data[1] << 8) | data[2] 141 | vel_int = (data[3] << 4) | ((data[4] >> 4) & 0x0F) 142 | tor_int = ((data[4] & 0x0F) << 8) | data[5] 143 | 144 | # 更新状态 145 | state = self.devices_state[index] 146 | state.pos = uint_to_float(pos_int, state.posMin, state.posMax, 16) 147 | state.vel = uint_to_float(vel_int, state.velMin, state.velMax, 12) 148 | state.tor = uint_to_float(tor_int, state.torMin, state.torMax, 12) 149 | 150 | return state 151 | 152 | def poll_rx(self) -> None: 153 | """轮询接收 CAN 帧,直到接收不到数据,更新内部状态。""" 154 | while True: 155 | frame = self.can.recv() 156 | if frame is None: 157 | break 158 | self.handle_frame(*frame) 159 | 160 | def get_motor_state(self): 161 | """获取目标电机的当前状态。""" 162 | return self.devices_state[self.motor_id - 1] 163 | 164 | def close(self) -> None: 165 | """关闭 CAN 连接。""" 166 | self.can.close() -------------------------------------------------------------------------------- /include/CAN_comm.cpp: -------------------------------------------------------------------------------- 1 | #include "CAN_comm.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace { 15 | int g_can_socket = -1; 16 | constexpr const char* kDefaultCanIf = "can0"; 17 | 18 | void close_can_socket() { 19 | if (g_can_socket >= 0) { 20 | close(g_can_socket); 21 | g_can_socket = -1; 22 | } 23 | } 24 | } // namespace 25 | 26 | void CANInit(){ 27 | if(g_can_socket >= 0){ 28 | return; 29 | } 30 | 31 | g_can_socket = socket(PF_CAN, SOCK_RAW, CAN_RAW); 32 | if(g_can_socket < 0){ 33 | std::perror("socket"); 34 | return; 35 | } 36 | 37 | struct ifreq ifr{}; 38 | std::strncpy(ifr.ifr_name, kDefaultCanIf, IFNAMSIZ - 1); 39 | if(ioctl(g_can_socket, SIOCGIFINDEX, &ifr) < 0){ 40 | std::perror("ioctl SIOCGIFINDEX"); 41 | close_can_socket(); 42 | return; 43 | } 44 | 45 | struct sockaddr_can addr{}; 46 | addr.can_family = AF_CAN; 47 | addr.can_ifindex = ifr.ifr_ifindex; 48 | if(bind(g_can_socket, reinterpret_cast(&addr), sizeof(addr)) < 0){ 49 | std::perror("bind"); 50 | close_can_socket(); 51 | return; 52 | } 53 | 54 | int flags = fcntl(g_can_socket, F_GETFL, 0); 55 | if(flags >= 0){ 56 | fcntl(g_can_socket, F_SETFL, flags | O_NONBLOCK); 57 | } 58 | 59 | std::printf("CAN socket initialized on %s\n", kDefaultCanIf); 60 | } 61 | 62 | void recCANMessage(){ 63 | if(g_can_socket < 0){ 64 | return; 65 | } 66 | 67 | struct can_frame rxFrame{}; 68 | ssize_t nbytes = recv(g_can_socket, &rxFrame, sizeof(rxFrame), MSG_DONTWAIT); 69 | if(nbytes < 0){ 70 | if(errno == EAGAIN || errno == EWOULDBLOCK){ 71 | return; 72 | } 73 | std::perror("recv"); 74 | return; 75 | } 76 | 77 | if(static_cast(nbytes) < sizeof(struct can_frame)){ 78 | return; 79 | } 80 | 81 | recNum++; 82 | bool isExtended = (rxFrame.can_id & CAN_EFF_FLAG) != 0; 83 | bool isRTR = (rxFrame.can_id & CAN_RTR_FLAG) != 0; 84 | if(!isExtended && !isRTR){ 85 | uint8_t DLC = rxFrame.can_dlc; 86 | uint8_t nodeID = rxFrame.can_id & 0x7F; 87 | uint32_t funcID = rxFrame.can_id & 0x780; 88 | if(DLC >= 6 && funcID == HEARTBEAT_FUNC_ID){ 89 | MITState_callback(nodeID, rxFrame.data); 90 | } 91 | } 92 | } 93 | 94 | 95 | void sendCANCommand(uint32_t nodeID, uint32_t msgID, uint8_t *data){ 96 | if(g_can_socket < 0){ 97 | std::fprintf(stderr, "CAN socket not initialized\n"); 98 | return; 99 | } 100 | 101 | struct can_frame txFrame{}; 102 | txFrame.can_id = (msgID + nodeID) & CAN_SFF_MASK; 103 | txFrame.can_dlc = 8; 104 | std::memcpy(txFrame.data, data, 8); 105 | 106 | if (write(g_can_socket, &txFrame, sizeof(txFrame)) == sizeof(txFrame)) { 107 | sendNum++; 108 | } else { 109 | std::perror("write"); 110 | } 111 | } 112 | 113 | void MITState_callback(uint8_t nodeID, uint8_t *data){ 114 | uint32_t posInt = (data[1]<<8) | data[2]; 115 | uint32_t velInt = (data[3]<<4) | (data[4]>>4 & 0xF); 116 | uint32_t torInt = ((data[4]&0xF)<<8) | data[5]; 117 | 118 | uint8_t index = nodeID - 1; 119 | if(index < (sizeof(devicesState)/sizeof(devicesState[0]))){ 120 | devicesState[index].pos = uint_to_float(posInt,devicesState[index].posMin, devicesState[index].posMax, 16); 121 | devicesState[index].vel = uint_to_float(velInt,devicesState[index].velMin, devicesState[index].velMax, 12); 122 | devicesState[index].tor = uint_to_float(torInt,devicesState[index].torMin, devicesState[index].torMax, 12); 123 | } 124 | 125 | } 126 | 127 | 128 | 129 | 130 | void sendMITCommand(uint8_t nodeID,MIT command){ 131 | uint8_t MITcommand[8]; 132 | 133 | LIMIT_MIN_MAX(command.pos, command.posMin, command.posMax); 134 | LIMIT_MIN_MAX(command.vel, command.velMin, command.velMax); 135 | LIMIT_MIN_MAX(command.kp, command.kpMin, command.kpMax); 136 | LIMIT_MIN_MAX(command.kd, command.kdMin, command.kdMax); 137 | LIMIT_MIN_MAX(command.tor, command.torMin, command.torMax); 138 | 139 | uint32_t posInt = float_to_uint(command.pos, command.posMin, command.posMax, 16); 140 | uint32_t velInt = float_to_uint(command.vel, command.velMin, command.velMax, 12); 141 | uint32_t kpInt = float_to_uint(command.kp, command.kpMin, command.kpMax, 12); 142 | uint32_t kdInt = float_to_uint(command.kd, command.kdMin, command.kdMax, 12); 143 | uint32_t torInt = float_to_uint(command.tor, command.torMin, command.torMax, 12); 144 | 145 | MITcommand[0] = posInt>>8; 146 | MITcommand[1] = posInt&0xFF; 147 | MITcommand[2] = velInt>>4; 148 | MITcommand[3] = ((velInt&0xF)<<4) | (kpInt>>8); 149 | MITcommand[4] = kpInt&0xFF; 150 | MITcommand[5] = kdInt>>4; 151 | MITcommand[6] = ((kdInt&0xF)<<4) | (torInt>>8); 152 | MITcommand[7] = torInt&0xFF; 153 | 154 | sendCANCommand(nodeID, FUNC_ID_RPDO1, MITcommand); 155 | } 156 | 157 | float uint_to_float(int x_int, float x_min, float x_max, int bits){ 158 | float span = x_max - x_min; 159 | float offset = x_min; 160 | return ((float)x_int)*span/((float)((1<