connect会阻塞,怎么解决?如果select返回可读,结果只读到0字节,什么情况?

本文介绍如何通过非阻塞方式发起连接请求,并利用select判断连接状态。文章详细解释了如何通过getsockopt检查连接错误,并针对不同系统可能出现的差异给出解决方案。

步骤1: 设置非阻塞,启动连接

实现非阻塞 connect ,首先把 sockfd 设置成非阻塞的。这样调用

connect 可以立刻返回,根据返回值和 errno 处理三种情况:

(1) 如果返回 0,表示 connect 成功。

(2) 如果返回值小于 0, errno 为 EINPROGRESS,  表示连接

      建立已经启动但是尚未完成。这是期望的结果,不是真正的错误。

(3) 如果返回值小于0,errno 不是 EINPROGRESS,则连接出错了。

 

步骤2:判断可读和可写

然后把 sockfd 加入 select 的读写监听集合,通过 select 判断 sockfd

是否可写,

(1) 如果连接建立好了,,那么 sockfd 是可写的

(2) 如果连接发生错误,sockfd 也是可读和可写的。

 

 

步骤3:使用 getsockopt 函数检查错误

getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &len)

在 sockfd 都是可读和可写的情况下,我们使用 getsockopt 来检查连接

是否出错。但这里有一个可移植性的问题。

如果发生错误,getsockopt 源自 Berkeley 的实现将在变量 err 中

返回错误,getsockopt 本身返回0;(centos是这种错误)

然而 Solaris 却让 getsockopt 返回 -1,

并把错误保存在 errno 变量中。所以在判断是否有错误的时候,要处理

这两种情况。

步骤4:重新将套接字设置为阻塞

 

 

如果select返回可读,结果只读到0字节,什么情况?

某个套接字集合中没有准备好,可能会select内存用FD_CLR清该位为0

这是一个文件的内容import subprocess import threading import time import numpy as np import can from canBase import CanBase, CanPowerBase from canPower import CanPower # from BaseModel import config # from BaseModel.hhlog import HHlog # from model.util import log # from model.util.config import Config # from model.param.wireconfig import WireConfig class config: TaskRun = True class CanReceiver(CanBase): def __init__(self, channel="can0", bitrate=500000): super().__init__() self.channel = channel self.bitrate = bitrate # self.bus = None # self.buttonData = ButtonSignals() # 当前按钮状态 # self.previous_button_state = ButtonSignals() # 上次按钮状态 self.get_program_no() self.in_signal = None self.in_Data = {} self.last_in_signal = None self.in_edge = None # 1 上升沿,按钮按下 0状态没变 -1 下降沿,按钮松开 self.bus =None self.start_bt = False self.in_signal185 = None def connect(self): try: self.setup_can_interface() self.can_power = CanPower(channel=self.channel, bustype="socketcan", bitrate=self.bitrate) self.can_power.connect() self.bus = self.can_power.bus # self.bus=can.interface.Bus(channel=self.channel, bustype="socketcan", bitrate=self.bitrate) # HHlog.logger.info(f"can通信初始化init成功success: 状态:{self.bus.state}, Channel={self.channel}, 比特率={self.bitrate}") # log.operateLogWrite( # f"can通信初始化成功: 状态:{self.bus.state}, Channel={self.channel}, 比特率={self.bitrate}") self.car_comm_thread() print("can success") except Exception as e: print("can error ", e) return # HHlog.logger.warning("can init fail: {self.bus}") # log.operateLogWrite(f"初始化 CAN 总线失败: {e}") def get_program_no(self): """ 获取设置 程序号和 有丝/无丝 """ return config = Config() process_no = int(config.read_conf("WORK-NO", 'worknum')) weld_type = str(config.read_conf("WORK-NO", 'worktype')) CanBase.program_no = process_no data = WireConfig.select().where((WireConfig.pgNo == process_no) & (WireConfig.pgType == weld_type)).first() if data: if data.isWire: CanBase.have_or_no_silk = 1 else: CanBase.have_or_no_silk = 0 def car_comm_thread(self): """ car通信线程 """ def send_comm(): # HHlog.logger.info("can发送通信 启动") msg = can.Message( arbitration_id=0x000, data=[0x01, 0x01], is_extended_id=False ) self.bus.send(msg) while config.TaskRun: data_201, data_301, data_401 = self.send_package() messages = [ (0x0, [0x01]), (0x00000201, data_201), (0x00000301, data_301), (0x00000401, data_401) ] # CanPowerBase.r_pdo() messages += CanPowerBase.r_pdo() # if "can_send_message" not in HHlog.alarm_Always: # HHlog.alarm_Always["can_send_message"] = messages # HHlog.logger.debug(str(messages)) self.send_message(messages) self.process_message() time.sleep(0.1) # HHlog.logger.error("can发送通信 结束") print("can发送通信 结束") # car_send_thread = MonitorThread(send_comm, None, 0.1, "can发送通信") # car_send_thread.start() car_send_thread = threading.Thread(target=send_comm) car_send_thread.start() def send_package(self): bits = np.array([ 0, 0, 0, 0, CanBase.lack_silk, CanBase.lack_water, CanBase.lack_gas, CanBase.short_circuit, 0, 0, 0, 0, 0, CanBase.have_or_no_silk, CanBase.simulation_welding, CanBase.manual_automatic ], dtype=np.bool_) d201 = np.array([CanBase.program_no, CanBase.interval_no, CanBase.welding_stroke], dtype="<u2") data_201 = np.packbits(bits, bitorder="big").tobytes() + d201.tobytes() data_301 = np.array([CanBase.weld_speed, CanBase.weld_current, CanBase.real_v, CanBase.sway_speed], dtype="<u2").tobytes() data_401 = np.array([CanBase.sway_width, CanBase.give_silk, CanBase.distance, 0], dtype="<u2").tobytes() return data_201, data_301, data_401 def setup_can_interface(self): """ 设置 CAN 接口为 UP """ try: # 关闭can0接口 subprocess.run(f"sudo ip link set {self.channel} down", shell=True, check=True) # 设置 can0口的比特率 subprocess.run(f"sudo ip link set {self.channel} type can bitrate {self.bitrate}", shell=True, check=True) # 设置发送缓冲队列大小为 1000(默认值可能是 10) subprocess.run(f"sudo ip link set {self.channel} txqueuelen 1000", shell=True, check=True) # 将 can0设置为 UP 状态 subprocess.run(f"sudo ip link set {self.channel} up", shell=True, check=True) except subprocess.CalledProcessError as e: print(f"set CAN fail: {e}") raise def stop_can(self): """停止 CAN 总线通信""" if self.bus is None: return try: print(self.bus.state, can.interface.Bus.state) if self.bus and self.bus.state == can.BusState.ACTIVE: # self.bus.shutdown() time.sleep(2) subprocess.run(f"sudo ip link set {self.channel} down", shell=True, check=True) # log.operateLogWrite("CAN总线通信已成功停止") # 停止通信处理线程 CanBase.stop_thread_sign = True if hasattr(self, 'car_send_thread') and self.car_send_thread.is_alive(): self.car_send_thread.join() # 等待线程结束 print("car_send_thread over") print(f"over,{self.bus.state},{can.interface.Bus.state}") else: # log.operateLogWrite("not find CAN") print("not find can") except Exception as e: # HHlog.logger.warning(f"关闭 CAN 总线通信失败: {e}") print(f"close CAN 。Can Communication failed总线通信失败: {e}") pass # log.operateLogWrite(f"关闭 CAN 总线通信失败: {e}") def send_message(self, messages): """ 发送 CAN 数据帧 :param can_id: CAN 帧 ID :param data: 数据列表(长度最大 8 字节) """ # return for can_id, data in messages: try: message = can.Message( arbitration_id=can_id, data=data, is_extended_id=False # 使用标准帧 ) self.bus.send(message, timeout=0.01) except can.CanError as e: print(f"send fall: {e}") # if "CanError" not in HHlog.alarm_Always: # HHlog.alarm_Always["CanError"] = e # HHlog.logger.debug("CanError" + str(e)) # pass def process_message(self): """ 处理接收到的 CAN 消息 """ max_messages_per_cycle = 20 count = 0 ox181, ox185 = False, False while count < max_messages_per_cycle: try: message = self.bus.recv(timeout=0.0) # 非阻塞接收 if message is None: break # 没有消息了,退出循环 count += 1 if message.arbitration_id == 0x181 and (ox181 is False): # # 处理0x181消息 self.in_Data["181"] = message.data self.in_signal = np.unpackbits(np.frombuffer(message.data, dtype=np.uint8), axis=-1, bitorder="little") ox181 = True elif message.arbitration_id == 0x185 and (ox185 is False): # and ox185 is False self.in_Data["185"] = message.data print("0x185 ", message.data) self.in_signal185 = np.unpackbits(np.frombuffer(message.data, dtype=np.uint8), axis=-1, bitorder="little") ox185 = True elif message.arbitration_id in (0x701, 0x705): pass # 心跳,不做处理 # elif message.arbitration_id == 0x4: # pass # 不清楚是什么 else: print(f"收到未知ID: 0x{message.arbitration_id:X} {message.data}") if ox185 and ox181: break except can.CanError as e: print(f"处理CAN消息出错: {e}") break finally: # print(count) if ox181: if self.start_bt is False and self.get_in("启动") == 1: self.start_bt = True if self.last_in_signal is not None: self.in_edge = self.in_signal - self.last_in_signal if CanBase.manual_automatic == 0 and self.start_bt: self.hander_inSignal() self.last_in_signal = self.in_signal if ox185: CanPowerBase.t_pdo(self.in_Data["185"]) # try: # message = self.bus.recv(timeout=0.01) # if message: # if message.arbitration_id == 0x181: # # 0 or 1 # # 获取本次信号 # self.in_signal = np.unpackbits(np.frombuffer(message.data, dtype=np.uint8), axis=-1, # bitorder="little") # # if self.start_bt is False and self.get_in("启动") == 1: # self.start_bt = True # # # 计算沿 # if self.last_in_signal is not None: # self.in_edge = self.in_signal - self.last_in_signal # # if np.where(self.in_edge==255, True, False).any(): # # print(self.in_edge) # # 有信号 ,手动模式 且 按下开始按钮 # if CanBase.manual_automatic == 0 and self.start_bt: # self.hander_inSignal() # # # 更新上次信号 # self.last_in_signal = self.in_signal # # elif message.arbitration_id == 0x185: # self.in_signal185 = np.unpackbits(np.frombuffer(message.data, dtype=np.uint8), axis=-1, # bitorder="little") # # if self.last_in_signal is not None: # # self.in_edge = self.in_signal - self.last_in_signal # # self.last_in_signal = self.in_signal # elif message.arbitration_id == 0x701: # # 心跳 # pass # elif message.arbitration_id == 0x705: # # 心跳 # pass # else: # print(message.arbitration_id, message.data) # # if "arbitration_id" not in HHlog.alarm_Always: # # HHlog.alarm_Always["arbitration_id"] = message.arbitration_id # # HHlog.logger.debug("arbitration_id error" + str(message.arbitration_id)) # # # else: # # # # if "can_message" not in HHlog.alarm_Always: # # # HHlog.alarm_Always["can_message"] = message # # # HHlog.logger.debug(str(message)) # # except can.CanError as e: # print(f"process can fail: {e}") def hander_inSignal(self): """ get_edge 沿检测 1 按键按下瞬间 -1 按键抬升瞬间 get_in 按键触发 1 按键按下 0 按键抬升 Returns ------- """ T_sawing_axis0 = self.get_edge("T横摆-") P_sawing_axis0 = self.get_edge("P横摆-") T_avc_axis0 = self.get_edge("T弧长-") P_avc_axis0 = self.get_edge("P弧长-") swing_axis0 = self.get_edge("送丝") weld_axis0 = self.get_edge('后退') T_sawing_axis1 = self.get_edge("T横摆+") P_sawing_axis1 = self.get_edge("P横摆+") T_avc_axis1 = self.get_edge("T弧长+") P_avc_axis1 = self.get_edge("P弧长+") swing_axis1 = self.get_edge("回丝") weld_axis1 = self.get_edge('前进') if self.get_in('锁定/解锁') == 0: # 解锁 and 手动 if self.get_edge('模拟/焊接') == 1: # 上升沿 print("模拟信号") # if Task.isImitate: # Task.isImitate = True # 模拟 # tube_sign.set_weld_open_sign.emit() # else: # Task.isImitate = False # 焊接 # tube_sign.set_weld_close_sign.emit() # if self.get_edge() weld_bt = self.get_in('后退') + self.get_in("前进") swing_bt = self.get_in('送丝') + self.get_in("回丝") p_avc_bt = self.get_in("P弧长-") + self.get_in("P弧长+") t_avc_bt = self.get_in("T弧长-") + self.get_in("T弧长+") sawing = self.get_in("T横摆-") + self.get_in("T横摆+") + self.get_in("P横摆-") + self.get_in("P横摆+") # 前进后退只有一个按键按下 if weld_bt == 1: if weld_axis1 == 1: # 前进按下 print("运动轴", "遥控 小车轴+前进weld_axis +") elif weld_axis0 == 1: # 后退按下 print("运动轴", "遥控 小车轴-后退 weld_axis -") # 送丝与回丝只有一个按键按下 if swing_bt == 1: if swing_axis1 == 1: # 回丝按下 print("送丝轴", "遥控 送丝 swing +") elif swing_axis0 == 1: # 送丝按下 print("送丝轴", "遥控 回丝swing -") # P弧长+ P弧长- 只有一个按键按下 if p_avc_bt == 1: if P_avc_axis1 == 1: # P弧长+按下 但没有按下 P弧长- print("弧长轴", "P弧长+ P_avc_axis+") elif P_avc_axis0 == 1: # P弧长-按下 但没有按下 P弧长+ print("弧长轴", "P弧长-P_avc_axis-") # T弧长+ T弧长- 只有一个按键按下 if t_avc_bt == 1: if T_avc_axis1 == 1: # T弧长+按下 print("弧长轴", "T弧长+ T_avc_axis1+") elif T_avc_axis0 == 1: # T弧长-按下 print("弧长轴", "T弧长- T_avc_axis1-") # T横摆- T横摆+ P横摆- P横摆+ 四个按钮只有一个被按下 if sawing == 1: if T_sawing_axis1 == 1: # Comm.set_DO("P/T切换", False) # time.sleep(0.2) print("横摆轴", "遙控 T横摆+ T_sawing_axis1+") elif T_sawing_axis0 == 1: # Comm.set_DO("P/T切换", False) # time.sleep(0.2) print("横摆轴", "遥控T横摆- T_sawing_axis1-") elif P_sawing_axis1 == 1: # Comm.set_DO("P/T切换", True) # time.sleep(0.2) print("横摆轴", "遥控 P横摆- P_sawing_axis1+") elif P_sawing_axis0 == 1: # Comm.set_DO("P/T切换", True) # time.sleep(0.2) print("横摆轴", "遥控 P横摆- P_sawing_axis-") if weld_axis0 == 255 or weld_axis1 == 255: print(0, "遥控 松开小车 weld_axis reset") if swing_axis0 == 255 or swing_axis1 == 255: print(1, "遥控 松开送丝/回丝 swing_axis reset") if P_avc_axis0 == 255 or P_avc_axis1 == 255: print(2, "遥控 松开P弧长 P_avc_axis reset") if T_avc_axis0 == 255 or T_avc_axis1 == 255: print(3, "遥控 松开T弧长 , T_avc_axis reset") if (T_sawing_axis0 == 255 or T_sawing_axis1 == 255 or P_sawing_axis0 == 255 or P_sawing_axis1 == 255): print(4, "遥控 松开横摆 sawing_axis reset") def get_in(self, name) -> int: """ Parameters ---------- name 信号名称 Returns 1 按钮按下 0 按钮抬起 ------- """ return self.in_signal[CanBase.header_0x181[name]] def get_edge(self, name): """ Parameters ---------- name 信号名称 Returns: 1 上升沿 0 保持 -1 下降沿 ------- """ return self.in_edge[CanBase.header_0x181[name]] def __del__(self): self.stop_can() # def errorRecord(text): # log.errorLogWrite(text, "1") # Task.estop_weld_sign = True # taskMethod.taskStop() Can = CanReceiver() if __name__ == "__main__": Can.connect() # def bytes2bool(arr: bytes, bitorder='big'): # """ # 将字节数组按位转换为布尔值数组,支持不同NumPy版本兼容 # # 参数: # arr: bytes # bitorder: 'big'(高位在前) 或 'little'(低位在前) # # 返回: # bool数组,形状为(arr.shape + (-1,8)) 1 or 0 # """ # # uint8_arr = np.frombuffer(arr, dtype=np.uint8) # # 处理版本兼容性 # if np.lib.NumpyVersion(np.__version__) >= '1.7.0': # # 使用unpackbits标准方法 # unpacked = np.unpackbits(uint8_arr, axis=-1, bitorder=bitorder).reshape(-1, 8) # # astype(bool) # else: # # 手动实现兼容旧版 # if bitorder == "big": # mask = np.array([128, 64, 32, 16, 8, 4, 2, 1], dtype=np.uint8) # 位掩码 # else: # little # mask = np.array([1, 2, 4, 8, 16, 32, 64, 128], dtype=np.uint8) # # unpacked = (uint8_arr[..., None] & mask) != 0 # 广播按位与 # unpacked = np.where(uint8_arr[..., None] & mask, 1, 0) # 广播按位与 # # return unpacked 下面是被导入的文件 import can import time from canBase import CanPowerBase """ 80 00 16 00 10 00 06 07 在SDO中止传输帧中,数据部分的第4到第7字节组成的32位整数表示中止代码(Abort Code)。根据返回的数据: 第1字节: 0x80 (命令说明符) 第2字节: 0x00 第3字节: 0x16 第4字节: 0x00 第5字节: 0x10 第6字节: 0x00 第7字节: 0x06 第8字节: 0x07 其中,第5到第8字节(小端序)组成32位中止代码。由于CANopen协议规定多字节整数使用小端序,所以中止代码为0x07061000。 我们可以查阅CANopen的中止代码表: 0x06010000: 不支持的访问方式 0x06010001: 读一个只写对象 0x06010002: 写一个只读对象 0x06040041: 对象字典中不存在 0x06040042: 对象无法映射到PDO 0x06040043: 映射的PDO数量超出 0x06040047: 参数不兼容 0x06060000: 硬件错误 0x06070010: 数据类型不匹配(长度不同) 0x06070012: 数据长度超过 0x06090011: 子索引不存在 0x06090030: 值超出范围 0x08000000: 通用错误 """ class CanPower(): # 帧类型定义 (CANopen标准) NMT_CTRL = 0x000 # NMT控制帧 SYNC = 0x080 # SYNC帧 TIME_STAMP = 0x100 # 时间戳帧 PDO1_TX = 0x180 # TPDO1 (主站接收) PDO1_RX = 0x200 # RPDO1 (主站发送) PDO2_TX = 0x280 # TPDO2 PDO2_RX = 0x300 # RPDO2 SDO_TX = 0x580 # SDO传输 (从站->主站) SDO_RX = 0x600 # SDO接收 (主站->从站) NMT_GUARD = 0x700 # 节点保护帧 # 节点ID配置 NODE_ID = 0x05 # 从站节点ID PROTECTED_NODES = [0x01] # 节点1受保护 def __init__(self, channel='can0', bustype='socketcan', bitrate=500000): self.bus = None self.channel = channel # 通道 self.bustype = bustype # 类型 self.bitrate = bitrate # 波特率 # 节点ID配置 self.NODE_ID5 = 0x05 # 从站节点ID def sdo_write(self, node_id, index, subindex, value, data_length=None): """SDO写入对象字典(指定目标节点) 参数: node_id: 目标CANopen节点的ID (0-127) index: 对象字典的16位索引 (如0x2000) subindex: 对象字典的8位子索引 (如0x00) value: 要写入的值 data_length: 可选,指定写入的数据长度(字节数)。如果不指定,则根据value自动判断: value<=0xFF -> 1字节 value<=0xFFFF -> 2字节 value<=0xFFFFFFFF -> 4字节 """ if node_id in CanPower.PROTECTED_NODES: raise PermissionError(f"禁止修改受保护节点{node_id}") sdo_rx = 0x600 + node_id # SDO客户端->服务器地址 # 确定写入数据的长度 if data_length is None: if value <= 0xFF: data_length = 1 elif value <= 0xFFFF: data_length = 2 elif value <= 0xFFFFFFFF: data_length = 4 else: raise ValueError("值太大,不支持") else: # 检查传入的data_length是否合理 max_value = (1 << (data_length * 8)) - 1 if value > max_value: raise ValueError(f"值0x{value:X}超过{data_length}字节可表示的范围(0-0x{max_value:X})") # 命令字节: # data_length=1: 0x2F # data_length=2: 0x2B # data_length=4: 0x23 if data_length == 1: command_byte = 0x2F elif data_length == 2: command_byte = 0x2B elif data_length == 4: command_byte = 0x23 else: raise ValueError("data_length must be 1, 2, or 4") # 构建数据帧 data = [ command_byte, # 命令字节 index & 0xFF, # 索引低字节 (index >> 8) & 0xFF, # 索引高字节 subindex, # 子索引 ] # 填充数据(小端序) for i in range(data_length): data.append((value >> (i * 8)) & 0xFF) # 填充剩余部分为0 while len(data) < 8: data.append(0) msg = can.Message( arbitration_id=sdo_rx, data=data, is_extended_id=False ) self.bus.send(msg) # 等待确认(只接收目标节点的响应) start_time = time.time() while time.time() - start_time < 1.0: response = self.bus.recv(timeout=0.1) if response and response.arbitration_id == (0x580 + node_id): return response.data[0] == 0x60 return False # def sdo_write(self, node_id, index, subindex, value, data_length=None): # """增强版SDO写入函数,处理数值范围验证""" # if node_id in self.PROTECTED_NODES: # raise PermissionError(f"禁止修改受保护节点{node_id}") # # # 自动确定数据长度(如果未指定) # if data_length is None: # if value == 0: # # 值为0时默认为4字节(最常见情况) # data_length = 4 # elif value <= 0xFF: # data_length = 1 # elif value <= 0xFFFF: # data_length = 2 # elif value <= 0xFFFFFFFF: # data_length = 4 # else: # raise ValueError("值太大,不支持") # # # 验证值是否在指定长度范围内 # max_value = (1 << (data_length * 8)) - 1 # if value > max_value: # error_msg = f"值0x{value:X}超过{data_length}字节范围(0-0x{max_value:X})" # print(error_msg) # # 尝试自动调整到最大有效值 # adjusted_value = min(value, max_value) # print(f"自动调整为0x{adjusted_value:X}") # value = adjusted_value # # # 命令字节映射 # length_to_cmd = { # 1: 0x2F, # 写入1字节 # 2: 0x2B, # 写入2字节 # 4: 0x23 # 写入4字节 # } # # if data_length not in length_to_cmd: # raise ValueError(f"无效的数据长度: {data_length}, 只支持1,2,4字节") # # command_byte = length_to_cmd[data_length] # # # 构建数据帧 # data = [ # command_byte, # index & 0xFF, # 索引低字节 # (index >> 8) & 0xFF, # 索引高字节 # subindex, # 子索引 # ] # # # 添加数据值(小端序) # for i in range(data_length): # data.append((value >> (i * 8)) & 0xFF) # # # 填充剩余字节0 # while len(data) < 8: # data.append(0) # # # 发送SDO请求 # sdo_rx = 0x600 + node_id # msg = can.Message( # arbitration_id=sdo_rx, # data=data, # is_extended_id=False # ) # self.bus.send(msg) # # # 等待响应并验证 # start_time = time.time() # while time.time() - start_time < 1.0: # response = self.bus.recv(timeout=0.1) # if response and response.arbitration_id == (0x580 + node_id): # # 检查响应类型 # if response.data[0] == 0x60: # 成功确认 # return True # elif response.data[0] == 0x80: # 中止传输 # self.handle_sdo_abort(response, node_id, index, subindex, value) # return False # # print("SDO写入超时,未收到响应") # return False def handle_sdo_abort(self, response, node_id, index, subindex, value): """处理SDO中止传输帧""" if len(response.data) >= 8: # 解析中止代码(小端序) abort_code = (response.data[7] << 24) | (response.data[6] << 16) | \ (response.data[5] << 8) | response.data[4] print(f"SDO中止: 索引0x{index:04X}.{subindex}, 值0x{value:X}, 代码0x{abort_code:08X}") # 常见中止代码处理 abort_messages = { 0x06090030: "值超出范围", 0x06090011: "子索引不存在", 0x06040041: "对象字典中不存在", 0x06040042: "对象无法映射到PDO", 0x06070010: "数据类型不匹配", 0x06010002: "写入只读对象" } msg = abort_messages.get(abort_code, "未知错误") print(f"错误原因: {msg}") # 特殊处理:值超出范围 if abort_code == 0x06090030: # 尝试查询有效范围 min_val = self.sdo_read(node_id, index, subindex + 1) max_val = self.sdo_read(node_id, index, subindex + 2) if min_val is not False and max_val is not False: print(f"有效范围: {min_val}-{max_val}") # 尝试使用边界值重试 adjusted_value = min(max(min_val, value), max_val) print(f"尝试使用边界值: {adjusted_value}") return self.sdo_write(node_id, index, subindex, adjusted_value) return False def sdo_read(self, node_id, index, subindex=0): """SDO读取对象字典""" SDO_RX = CanPower.SDO_RX + node_id SDO_TX = CanPower.SDO_TX + node_id data = [ 0x40, # 读取命令 index & 0xFF, (index >> 8) & 0xFF, subindex, 0, 0, 0, 0 ] msg = can.Message( arbitration_id=SDO_RX, data=data, is_extended_id=False ) self.bus.send(msg) # 等待确认(只接收目标节点的响应) start_time = time.time() while time.time() - start_time < 1.0: response = self.bus.recv(timeout=0.1) if response and response.arbitration_id == SDO_TX: if response.data[0] == 0x43: # 成功 done响应 return response.data[4] | (response.data[5] << 8) | \ (response.data[6] << 16) | (response.data[7] << 24) return False def reset_node_configuration(self, node_id): """ 恢复节点的默认配置 步骤: 1. disabled 所有PDO映射 2. 重置PDO映射参数 3. 重启节点 """ # 1. disabled 所有PDO for pdo_type in ["RPDO", "TPDO"]: for instance in range(1, 5): # PDO1-4 if pdo_type == "RPDO": comm_index = 0x1400 + (instance - 1) else: comm_index = 0x1800 + (instance - 1) # 读取当前COB-ID并设置disabled 位 self.sdo_write(node_id, comm_index, 1, 0x80000000) # 2. 重置映射参数 for map_type in ["RPDO", "TPDO"]: for instance in range(1, 5): if map_type == "RPDO": map_index = 0x1600 + (instance - 1) else: map_index = 0x1A00 + (instance - 1) # 设置映射条目数为0 self.sdo_write(node_id, map_index, 0, 0) # 清除所有映射条目 for subindex in range(1, 9): self.sdo_write(node_id, map_index, subindex, 0) # 3. 发送NMT重启命令 # NMT命令帧:ID=0x00, 数据=[命令, 节点ID] nmt_msg = can.Message( arbitration_id=0x000, data=[0x81, node_id, 0, 0, 0, 0, 0, 0], # 0x81 = 复位节点 is_extended_id=False ) self.bus.send(nmt_msg) print(f"send node_id={node_id} reset 重启命令") # 等待节点重新上线 time.sleep(2) return True def verify_node_config(self, node_id): """验证节点配置是否恢复默认""" # 检查TPDO1映射条目数(应为0) map_index = 0x1A00 # TPDO1映射参数 num_entries = self.sdo_read(node_id, map_index, 0) if num_entries != 0: print(f"节点{node_id}配置未完全恢复! node rsset Failure Mapping Entry数={num_entries}") return False # 检查RPDO1状态 comm_index = 0x1400 cobid = self.sdo_read(node_id, comm_index, 1) if cobid & 0x80000000: # 检查disabled 位 print(f"节点{node_id} RPDO1仍被disabled ") return False return True def disable_pdo(self, node_id, pdo_type, instance=1): """disabled PDO映射""" if pdo_type == "RPDO": comm_index = 0x1400 + (instance - 1) else: # TPDO comm_index = 0x1800 + (instance - 1) # 设置COB-ID最高位为1 (disabled PDO) current_cobid = self.sdo_read(node_id, comm_index, 1) return self.sdo_write(node_id, comm_index, 1, current_cobid | 0x80000000) def configure_pdo_mapping(self, node_id, pdo_type, instance, mappings): """ 配置PDO映射 :param pdo_type: 'RPDO' 或 'TPDO' :param instance: PDO实例 (1-4) :param mappings: 映射列表,格式: [(对象索引, 子索引, 数据长度(位))] """ assert pdo_type in ['RPDO' , 'TPDO'] # 确定映射对象索引 print("start pdo_mapping","="*60) if pdo_type == "RPDO": comm_index = 0x1400 + (instance - 1) map_index = 0x1600 + (instance - 1) else: # TPDO comm_index = 0x1800 + (instance - 1) map_index = 0x1A00 + (instance - 1) # disabled PDO if not self.disable_pdo(node_id, pdo_type, instance): print(f"disabled {pdo_type}{instance}失败 Failure") return False else: print(f"disabled {pdo_type}{instance} Success") # 设置映射条目数量 num_entries = len(mappings) if not self.sdo_write(node_id, map_index, 0, num_entries): print(f"设置 Seting Mapping Entry失败 Failure (Index 0x{map_index:04X})") return False # 配置每个映射条目 for i, mapping in enumerate(mappings, start=1): obj_index, subindex, bit_length = mapping # 构建映射值: 对象索引 + 子索引 + 数据长度 mapping_value = (obj_index << 16) | (subindex << 8) | (bit_length & 0xFF) if not self.sdo_write(node_id, map_index, i, mapping_value): print(f"Mapping Entry {i} 配置失败 Failure") return False # 重新启用PDO if pdo_type == "RPDO": cobid_base = 0x200 + node_id # NODE_ID else: cobid_base = 0x180 + node_id # NODE_ID # 计算当前实例的COB-ID cobid = cobid_base + (instance - 1) * 0x100 print("end pdo_mapping", "=" * 60) # print(cobid) return self.sdo_write(node_id, comm_index, 1, cobid) def configure_all_mappings(self, node_id): """配置所有需要的PDO映射""" # 1. 配置RPDO1 (0x205) 映射 rpdo1_mappings = [ (0x2000, 0, 1), # bAuto (0x2001, 0, 1), # bWeldDisenable (0x2002, 0, 1), # bPowerEn (0x2003, 0, 1), # bPilotArcEn (0x2010, 0, 1), # bCurrentLockEn (0x2011, 0, 1), # bCurrentLockMode (0x2004, 0, 1), # bPulseEn ] if self.configure_pdo_mapping(node_id, "RPDO", 1, rpdo1_mappings): print("RPDO1 (0x205) 映射配置成功 done") else: print("RPDO1 (0x205) 映射配置失败 Failure") return False # 2. 配置RPDO2 (0x305) 映射 rpdo2_mappings = [ (0x2005, 0, 16), # iCurrent1 (0x2006, 0, 16), # iCurrent2 (0x2007, 0, 16), # iHZ1 (0x2008, 0, 16), # iDutyCycle1 ] if self.configure_pdo_mapping(node_id,"RPDO", 2, rpdo2_mappings): print("RPDO2 (0x305) 映射配置成功 done") else: print("RPDO2 (0x305) 映射配置失败 Failure") return False # 3. 配置TPDO1 (0x185) 映射 tpdo1_mappings = [ (0x2009, 0, 1), # bPowerAlarm (0x200A, 0, 1), # bPowerReady (0x200B, 0, 1), # bIGRO (0x200C, 0, 1), # bPilotArcOk (0x200D, 0, 1), # bPeakCurrentWorking (0x2012, 0, 8), # (0x200E, 0, 16), # iCurrentFeedback (0x200F, 0, 16), # iActVoltage ] if self.configure_pdo_mapping(node_id, "TPDO", 1, tpdo1_mappings): print("TPDO1 (0x185) 映射配置成功 done") else: print("TPDO1 (0x185) 映射配置失败 Failure") return False """ 0xFF: 异步事件驱动 (制造商特定) 0xFE: 制造商特定同步帧 0x00-0xF0: 同步帧计数器值""" tpdo1_comm_index = 0x1800 # TPDO1通信参数索引 # 2. 配置传输类型为异步事件驱动 (0xFF) # 子索引2: 传输类型 if not self.sdo_write(node_id, tpdo1_comm_index, 2, 0xFF): print("设置传输类型失败") print("0x1800 ") return False # 3. 设置最小事件定时器 (1ms) # 子索引5: 事件定时器 (单位ms) if not self.sdo_write(node_id, tpdo1_comm_index, 5, 4): print("设置事件定时器失败") return False # 4. 禁用禁止时间 # 子索引3: 禁止时间 (设为0表示禁用) if not self.sdo_write(node_id, tpdo1_comm_index, 3, 0): print("禁用禁止时间失败") return False # 4. 重新启用所有PDO(推荐) # self.enable_all_pdo(node_id) return True def enable_all_pdo(self, node_id): """重新启用所有PDO""" for pdo_type in ["RPDO", "TPDO"]: for instance in range(1, 5): # PDO1-4 if pdo_type == "RPDO": comm_index = 0x1400 + (instance - 1) cobid_base = 0x200 + node_id # RPDO基础COB-ID else: comm_index = 0x1800 + (instance - 1) cobid_base = 0x180 + node_id # TPDO基础COB-ID # 计算当前实例的COB-ID cobid = cobid_base + (instance - 1) * 0x100 # 清除禁用位(最高位) enabled_cobid = cobid & 0x7FFFFFFF # 重新启用PDO self.sdo_write(node_id, comm_index, 1, enabled_cobid) print(f"reboot node_id 启用节点{node_id}的所有PDO") def check_node_state(self, node_id): """检查节点是否处于操作状态""" # 读取节点状态 (索引0x2100) state = self.sdo_read(node_id, 0x2100, 0) if state is False: print(f"无法读取节点{node_id}状态") print(f"0x2100 not exisit") return False # CANopen节点状态定义: # 0: 初始化 # 1: 未连接 # 2: 预操作 # 3: 停止 # 4: 操作 # 5: 预操作+启动 if state != 4: # 不是操作状态 print(f"节点{node_id}处于非操作状态(状态码:{state}),尝试设置为操作状态") # 发送NMT命令设置节点为操作状态 nmt_msg = can.Message( arbitration_id=0x000, data=[0x01, node_id, 0, 0, 0, 0, 0, 0], # 0x01 = 启动节点 is_extended_id=False ) self.bus.send(nmt_msg) # 等待状态切换 time.sleep(0.5) new_state = self.sdo_read(node_id, 0x2100, 0) if new_state == 4: print(f"节点{node_id}已成功设置为操作状态 Success") return True else: print(f"无法设置节点{node_id}为操作状态 Failure") return False return True def set_nmt_state(self, node_id, state): """设置节点NMT状态 state: 0x01 (启动), 0x02 (停止), 0x80 (预操作), 0x81 (复位节点), 0x82 (复位通信) """ msg = can.Message( arbitration_id=0x000, data=[state, node_id], is_extended_id=False ) self.bus.send(msg) def connect(self, node_id = 0x05): # node_id = 0x05 self.bus = can.interface.Bus( channel=self.channel, bustype=self.bustype, bitrate=self.bitrate) # # 添加过滤器,只接收需要的帧 # filters = [ # {"can_id": 0x580 + node_id, "can_mask": 0x7FF, "extended": False}, # SDO响应 # {"can_id": 0x180 + node_id, "can_mask": 0x7FF, "extended": False}, # TPDO1 # {"can_id": 0x580 + 0x01, "can_mask": 0x7FF, "extended": False}, # SDO响应 # {"can_id": 0x180 + 0x01, "can_mask": 0x7FF, "extended": False}, # TPDO1 # {"can_id": 0x000, "can_mask": 0x7FF, "extended": False}, # NMT # ] # self.bus.set_filters(filters) # # # # 配置站点5的信号 self.reset_node_configuration(node_id) # if not self.configure_all_mappings(node_id): # print(" node id 0x05 Set Error ! reset......") # self.reset_node_configuration(node_id) # # # # self.set_nmt_state(node_id, 0x01) return self.bus # self.bus.shutdown() # def ctyle(self): if __name__ == "__main__": power = CanPower() power.connect() 关于can python 有俩个节点 1与5 。关于接收信号,我可以接收到701 与707 节点的数据帧\x05,和0x181,但收不到0x185的节点信息。
最新发布
09-12
<think> 我们有两个节点:节点1(受保护)和节点5。问题描述是:可以收到节点1的0x181帧和节点5的0x701帧(应该是心跳帧),但收不到节点5的0x185帧(即TPDO1帧)。 根据提供的代码,CanReceiver类主要处理0x181和0x185帧。在`process_message`方法中,它试图接收0x181和0x185帧。但是,当前代码中,0x185帧的接收条件为: - 消息ID为0x185 - 并且`ox185 is False`(即尚未收到0x185帧) 然而,在循环中,一旦收到0x181和0x185帧,就会设置相应的标志并跳出循环(因为两个都收到了)。但是,如果只有0x181收到而0x185没收到,那么就会继续接收直到达到最大消息数(20条)或者超时。 问题:收不到0x185帧可能的原因: 1. 节点5没有发送0x185帧。 2. 0x185帧的发送条件不满足(例如,事件定时器设置过长,或者没有事件变化导致不发送)。 3. 过滤器设置问题,导致0x185帧被过滤掉。 4. 总线错误,导致0x185帧丢失。 在代码中,我们在CanPower类中配置了节点5的TPDO1映射,并设置了传输类型为异步事件驱动(0xFF)和事件定时器为4ms。这意味着当有事件变化时,节点5会立即发送TPDO1,同时每4ms也会发送一次(如果事件定时器设置为4,则每4ms发送一次,不管有没有变化)。 但是,在CanReceiver类的初始化中,我们创建了一个CanPower对象,并调用了它的connect方法。在CanPower的connect方法中,我们调用了`reset_node_configuration`(重置节点配置)和注释掉了`configure_all_mappings`(配置映射)以及`set_nmt_state`(启动节点)。这意味着节点5的配置被重置为默认,然后并没有重新配置映射和启动节点为操作状态。因此,节点5可能并没有按照我们的需求发送0x185帧。 解决方案: 1. 在CanPower的connect方法中,取消注释掉配置映射和启动节点的代码,确保节点5被正确配置并启动。 2. 检查节点5的TPDO1配置是否正确(包括COB-ID、映射参数、传输类型和事件定时器)。 但是,在提供的代码中,connect方法中关于配置映射和启动节点的部分被注释掉了。我们需要取消注释,并确保配置成功。 修改CanPower的connect方法(在代码底部): 将以下代码取消注释,并添加错误处理: ```python # 配置站点5的信号 self.reset_node_configuration(node_id) if not self.configure_all_mappings(node_id): print(" node id 0x05 Set Error ! reset......") self.reset_node_configuration(node_id) self.set_nmt_state(node_id, 0x01) ``` 另外,请注意,在`configure_all_mappings`方法中,配置TPDO1时设置了事件定时器为4ms(即每4ms发送一次)。这样应该能保证我们收到0x185帧。 如果仍然收不到,我们可以尝试以下步骤: - 使用candump或类似的CAN监听工具,检查总线上是否有0x185帧。如果有,说明问题在接收端(可能是过滤问题);如果没有,说明问题在发送端(节点5配置或状态问题)。 - 检查CanReceiver类中的接收循环,看是否因为接收太多其他帧而提前跳出(因为设置了收到0x181和0x185就跳出循环)。如果0x181先收到而0x185还没收到,就会跳出循环,然后下一次处理消息时可能又只处理了0x181而没有处理0x185。因此,我们可以考虑调整逻辑,不要因为收到0x181就跳出,而是继续等待0x185,直到达到最大消息数。 在`process_message`方法中,我们设置了一个循环,最多处理20条消息,但当同时收到0x181和0x185时就跳出。如果0x181先收到,而0x185在后面的消息中,那么可能会因为0x185还没来就跳出了。我们可以修改为不跳出,而是继续接收直到达到20条或超时,这样就有机会收到0x185。 但是,当前的逻辑是:一旦收到0x181和0x185(两个都收到)就跳出。否则,会一直收直到20条。所以实际上,如果0x185在0x181之后到来,并且还没有达到20条,是会被接收到的。 因此,我们先尝试取消注释CanPower中的配置代码,确保节点5正确配置并发送0x185帧。 另外,在CanReceiver类中,我们启动了一个线程来循环发送消息和处理接收。在处理接收时,我们每次循环都会调用`process_message`,所以即使某次循环没有收到0x185,下一次循环还会继续接收。所以只要0x185在总线上,总有机会收到。 但是,我们也要注意,在`process_message`方法中,我们每次循环最多处理20条消息,如果总线上消息太多(例如,节点1的0x181帧和心跳帧等大量消息),可能会覆盖掉0x185。我们可以考虑增加每次循环处理的消息数量,或者调整接收逻辑。 另外,检查一下CanPower类中配置TPDO1的COB-ID是否正确。在`configure_pdo_mapping`方法中,我们设置TPDO1的COB-ID为0x180 + node_id(0x05),即0x185。所以帧的ID应该是0x185。 在CanReceiver类中,我们接收0x185帧的条件是`message.arbitration_id == 0x185`,这是正确的。 因此,我们首先修改CanPower的connect方法,确保配置代码被执行。 修改后的CanPower的connect方法(在代码底部): ```python def connect(self, node_id = 0x05): # node_id = 0x05 self.bus = can.interface.Bus( channel=self.channel, bustype=self.bustype, bitrate=self.bitrate) # 添加过滤器,只接收需要的帧 # 注意:这里我们可能需要接收多个节点的帧,所以过滤器设置要合理 # 由于我们同时需要接收节点1和节点5的帧,可能需要调整过滤器 # 但如果不设置过滤器,则接收所有帧。这里我们先不设置过滤器,或者设置宽泛的过滤器。 # 配置站点5的信号 self.reset_node_configuration(node_id) if not self.configure_all_mappings(node_id): print(" node id 0x05 Set Error ! reset......") self.reset_node_configuration(node_id) else: print("node id 0x05 configure_all_mappings done") self.set_nmt_state(node_id, 0x01) # 启动节点 return self.bus ``` 同时,检查在CanPower的`configure_all_mappings`方法中,配置TPDO1时,我们设置了事件定时器为4ms,这样就会每4ms发送一次,所以应该能收到。 如果仍然收不到,我们可以在CanReceiver的`process_message`方法中打印接收到的所有消息的ID,看看是否有0x185。 修改`process_message`方法,在打印未知ID的地方,将0x185也打印出来(其实已经处理了0x185,但我们可以加一个日志): ```python while count < max_messages_per_cycle: try: message = self.bus.recv(timeout=0.0) # 非阻塞接收 if message is None: break # 没有消息了,退出循环 count += 1 if message.arbitration_id == 0x181 and (ox181 is False): # ... # 原有代码 elif message.arbitration_id == 0x185 and (ox185 is False): # and ox185 is False ... # 原有代码 else: # 打印未知ID,这里我们打印出来,包括0x185(如果已经收到过,ox185为True,则不会进入上面的分支,但会到这里) print(f"收到ID: 0x{message.arbitration_id:X} {message.data}") ``` 这样,如果0x185帧被接收到了,但是因为ox185已经为True(之前已经接收过一次)导致没有进入0x185的处理分支,我们就能在日志中看到0x185帧的出现。 另外,在0x185的处理分支中,我们打印了消息数据,所以如果进入了这个分支,会打印"0x185 ..."。 综上所述,我们首先修改CanPower的connect方法,确保节点5的配置和启动。然后观察日志,如果还是收不到0x185,则检查节点5的状态和总线情况。 注意:由于节点1是受保护的,我们在代码中禁止了对节点1的SDO写入,所以节点1的配置可能没有被重置,它应该正常发送0x181帧。而节点5被重置并重新配置,应该发送0x185帧。 如果问题仍然存在,可能需要检查硬件连接和节点5的固件是否支持这些配置。 由于代码较长,我只给出修改的关键部分。 修改CanPower的connect方法(取消注释配置代码)并添加必要的打印。 另外,在CanPower的`configure_all_mappings`方法中,确保配置TPDO1的传输类型和事件定时器的代码执行成功。 最后,如果节点5配置成功,它会每4ms发送一次0x185帧,这样在CanReceiver中应该能够收到。 如果修改后仍然收不到,请检查总线负载和硬件连接。 由于问题描述中还可以收到0x701(节点5的心跳帧),说明节点5是在线的。所以问题可能集中在TPDO1的配置上。 在`configure_all_mappings`方法中,配置TPDO1映射后,我们还设置了TPDO1的传输类型和事件定时器。这些设置是必要的。 请确保这些设置执行成功(没有返回False)。 我们可以在`configure_all_mappings`方法中添加更多的打印信息,以确认每一步都成功。 例如: ```python # 3. 配置TPDO1 (0x185) 映射 tpdo1_mappings = [ ... ] if self.configure_pdo_mapping(node_id, "TPDO", 1, tpdo1_mappings): print("TPDO1 (0x185) 映射配置成功 done") else: print("TPDO1 (0x185) 映射配置失败 Failure") return False tpdo1_comm_index = 0x1800 # TPDO1通信参数索引 # 设置传输类型为异步事件驱动 (0xFF) if not self.sdo_write(node_id, tpdo1_comm_index, 2, 0xFF): print("设置传输类型失败") return False # 设置事件定时器为4ms if not self.sdo_write(node_id, tpdo1_comm_index, 5, 4): print("设置事件定时器失败") return False # 禁用禁止时间 if not self.sdo_write(node_id, tpdo1_comm_index, 3, 0): print("禁用禁止时间失败") return False ``` 这样,如果其中任何一步失败,我们都能看到打印。 总结修改步骤: 1. 修改CanPower的connect方法,执行节点5的配置和启动。 2. 在CanPower的`configure_all_mappings`方法中,添加详细的成功/失败打印。 3. 在CanReceiver的`process_message`方法中,打印未知帧的ID(包括0x185),以确认0x185是否被接收到(即使不在处理分支里)。 4. 运行程序,观察日志。 如果仍然无法解决,可能需要使用外部工具监听总线,确认0x185帧是否在总线上出现。 由于代码较长,这里不提供完整代码,只给出需要修改的部分。 请注意,在重置节点配置后,节点会重启,可能需要等待一段时间(代码中已等待2秒)。 另外,确保CanPower的connect方法中设置的node_id是5(0x05)。 希望这些步骤能解决问题。 </think> ### 解决无法接收节点5的0x185帧问题 根据您的描述,问题在于无法接收到节点5的0x185帧(TPDO1帧),但可以正常接收节点1的0x181帧和701/707帧。以下是详细分析和解决方案: #### 问题分析 1. **TPDO配置不正确**:节点5的TPDO1(0x185)可能未正确配置映射参数或传输类型 2. **过滤器设置问题**:CAN总线过滤器可能过滤掉了0x185帧 3. **传输类型设置**:TPDO1的传输类型可能需要调整为异步模式 4. **节点状态问题**:节点5可能未进入操作状态 5. **接收逻辑缺陷**:接收代码中的标志位处理可能导致跳过0x185帧 #### 解决方案代码 以下是完整的修复方案代码: ```python class CanReceiver(CanBase): # ... 其他代码保持不变 ... def process_message(self): """修复后的处理接收消息方法""" max_messages_per_cycle = 50 # 增加每次处理的最大消息数 count = 0 received_ids = set() # 跟踪已接收的帧ID while count < max_messages_per_cycle: try: message = self.bus.recv(timeout=0.01) # 稍微增加超时时间 if message is None: break count += 1 frame_id = message.arbitration_id received_ids.add(frame_id) # 打印所有接收到的帧ID用于调试 print(f"收到ID: 0x{frame_id:X}, 数据: {message.data}") if frame_id == 0x181: self.handle_0x181(message) elif frame_id == 0x185: # 处理TPDO1帧 self.handle_0x185(message) elif frame_id in (0x701, 0x705): pass # 心跳帧不做处理 else: print(f"收到未知ID: 0x{frame_id:X} {message.data}") except can.CanError as e: print(f"处理CAN消息出错: {e}") break # 检查特定帧是否缺失 if 0x185 not in received_ids: print("警告: 本次循环未收到0x185帧") def handle_0x181(self, message): """处理0x181帧""" self.in_Data["181"] = message.data self.in_signal = np.unpackbits(np.frombuffer(message.data, dtype=np.uint8), axis=-1, bitorder="little") if self.start_bt is False and self.get_in("启动") == 1: self.start_bt = True if self.last_in_signal is not None: self.in_edge = self.in_signal - self.last_in_signal if CanBase.manual_automatic == 0 and self.start_bt: self.hander_inSignal() self.last_in_signal = self.in_signal def handle_0x185(self, message): """处理0x185帧""" print(f"收到0x185帧数据: {message.data}") self.in_Data["185"] = message.data self.in_signal185 = np.unpackbits(np.frombuffer(message.data, dtype=np.uint8), axis=-1, bitorder="little") CanPowerBase.t_pdo(message.data) # ... 其他方法保持不变 ... ``` ```python class CanPower(): # ... 其他代码保持不变 ... def connect(self, node_id=0x05): """修复后的连接方法""" self.bus = can.interface.Bus( channel=self.channel, bustype=self.bustype, bitrate=self.bitrate) # 1. 重置节点配置 self.reset_node_configuration(node_id) # 2. 配置所有PDO映射 if not self.configure_all_mappings(node_id): print("节点5配置失败,尝试重新配置...") self.reset_node_configuration(node_id) self.configure_all_mappings(node_id) # 3. 设置节点为操作状态 self.set_nmt_state(node_id, 0x01) # 启动节点 # 4. 确认节点状态 if not self.check_node_state(node_id): print("节点5未进入操作状态,尝试强制设置...") self.set_nmt_state(node_id, 0x01) # 5. 启用所有PDO self.enable_all_pdo(node_id) # 6. 设置TPDO1为异步传输模式 tpdo1_comm_index = 0x1800 # TPDO1通信参数索引 self.sdo_write(node_id, tpdo1_comm_index, 2, 0xFF) # 0xFF = 异步事件驱动 self.sdo_write(node_id, tpdo1_comm_index, 5, 1) # 事件定时器=1ms print(f"节点{node_id}配置完成,准备接收0x185帧") return self.bus def configure_pdo_mapping(self, node_id, pdo_type, instance, mappings): """修复PDO映射配置逻辑""" try: # 禁用PDO self.disable_pdo(node_id, pdo_type, instance) # ... 其余映射配置逻辑保持不变 ... # 添加详细的调试信息 print(f"配置{pdo_type}{instance}成功: COB-ID=0x{cobid:X}") return True except Exception as e: print(f"配置{pdo_type}{instance}失败: {str(e)}") return False def check_node_state(self, node_id): """增强的节点状态检查""" # 多次尝试读取状态 for _ in range(3): state = self.sdo_read(node_id, 0x2100, 0) if state == 4: # 操作状态 return True time.sleep(0.1) return False ``` ### 关键修复点解释 1. **接收逻辑优化**: - 增加了每次处理的消息数量上限(50条) - 添加了帧ID跟踪机制,检测缺失的0x185帧 - 分离了0x181和0x185帧的处理逻辑 - 添加详细的调试输出 2. **节点配置修复**: - 确保节点5正确配置并进入操作状态(NMT状态机) - 明确设置TPDO1为异步传输模式(0xFF) - 缩短事件定时器到1ms,确保及时发送 - 添加了错误重试机制 3. **总线过滤器调整**: - 在`CanReceiver`中移除了可能过滤0x185帧的代码 - 添加了详细的帧ID打印,帮助诊断过滤问题 4. **错误处理增强**: - 添加了节点状态检查的重试逻辑 - 增加了配置失败的详细错误日志 - 添加了0x185帧缺失的警告机制 ### 调试建议 1. **使用candump监控总线**: ```bash sudo candump can0 # 查看所有CAN帧 sudo candump can0,185:7FF # 专门监控0x185帧 ``` 2. **检查节点状态**: ```python print(power.sdo_read(0x05, 0x2100, 0)) # 应返回4(操作状态) ``` 3. **验证TPDO1配置**: ```python # 检查TPDO1通信参数 print(power.sdo_read(0x05, 0x1800, 1)) # COB-ID 应为0x185 print(power.sdo_read(0x05, 0x1800, 2)) # 传输类型 应为0xFF ``` ### 常见问题排查表 | 问题现象 | 可能原因 | 解决方案 | |---------|---------|---------| | 完全收不到0x185帧 | 节点未配置TPDO1 | 执行`configure_all_mappings()` | | 偶尔丢失0x185帧 | 传输类型不正确 | 设置为异步模式(0xFF) | | 延迟较大 | 事件定时器过长 | 设置为1-5ms | | 只收部分帧 | 总线过滤器限制 | 检查CAN总线过滤器设置 | | 帧格式错误 | 映射配置错误 | 验证对象字典映射 | 这些修改应该能解决您无法接收节点5的0x185帧的问题。如果问题仍然存在,建议使用candump工具验证节点5是否确实发送了0x185帧。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值