<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet type="text/xsl" href="/rss.xsl"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Module 11 Debug Tools - Cygnus Tech Blog</title><link>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/</link><description>技术 × 产品 × 机器人</description><generator>Hugo 0.160.1 & FixIt v0.4.6-20260517062719-ed8ec5c2</generator><language>zh-CN</language><managingEditor>ruohuyang@163.com (Cygnus Yang)</managingEditor><webMaster>ruohuyang@163.com (Cygnus Yang)</webMaster><lastBuildDate>Sat, 23 May 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/index.xml" rel="self" type="application/rss+xml"/><item><title>can-utils 工具集详解</title><link>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/02-can-utils-tools/</link><pubDate>Sat, 23 May 2026 00:00:00 +0800</pubDate><author>ruohuyang@163.com (Cygnus Yang)</author><guid>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/02-can-utils-tools/</guid><description>&lt;p&gt;&lt;code&gt;can-utils&lt;/code&gt; 是 Linux 上最常用的 CAN 调试工具集，包含多个实用工具，几乎涵盖所有基础 CAN 调试需求。&lt;/p&gt;
&lt;h2 class="heading-element" id="1-can-utils-安装"&gt;&lt;span&gt;1. can-utils 安装&lt;/span&gt;
 &lt;a href="#1-can-utils-%e5%ae%89%e8%a3%85" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;h3 class="heading-element" id="11-ubuntudebian"&gt;&lt;span&gt;1.1 Ubuntu/Debian&lt;/span&gt;
 &lt;a href="#11-ubuntudebian" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install can-utils&lt;/code&gt;&lt;/pre&gt;&lt;h3 class="heading-element" id="12-从源码编译推荐获取最新版本"&gt;&lt;span&gt;1.2 从源码编译（推荐获取最新版本）&lt;/span&gt;
 &lt;a href="#12-%e4%bb%8e%e6%ba%90%e7%a0%81%e7%bc%96%e8%af%91%e6%8e%a8%e8%8d%90%e8%8e%b7%e5%8f%96%e6%9c%80%e6%96%b0%e7%89%88%e6%9c%ac" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;git clone https://github.com/linux-can/can-utils.git
cd can-utils
./autogen.sh
./configure
make
sudo make install&lt;/code&gt;&lt;/pre&gt;&lt;h3 class="heading-element" id="13-验证安装"&gt;&lt;span&gt;1.3 验证安装&lt;/span&gt;
 &lt;a href="#13-%e9%aa%8c%e8%af%81%e5%ae%89%e8%a3%85" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;# 查看所有可用工具
candump --help
cansend --help
canplayer --help&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 class="heading-element" id="2-核心工具详解"&gt;&lt;span&gt;2. 核心工具详解&lt;/span&gt;
 &lt;a href="#2-%e6%a0%b8%e5%bf%83%e5%b7%a5%e5%85%b7%e8%af%a6%e8%a7%a3" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;h3 class="heading-element" id="21-candump---can-帧捕获工具"&gt;&lt;span&gt;2.1 candump - CAN 帧捕获工具&lt;/span&gt;
 &lt;a href="#21-candump---can-%e5%b8%a7%e6%8d%95%e8%8e%b7%e5%b7%a5%e5%85%b7" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;candump&lt;/code&gt; 是最常用的工具，用于捕获和显示 CAN 总线上的所有帧。&lt;/p&gt;</description></item><item><title>CANopen 常见问题排查指南</title><link>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/05-common-issues/</link><pubDate>Sat, 23 May 2026 00:00:00 +0800</pubDate><author>ruohuyang@163.com (Cygnus Yang)</author><guid>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/05-common-issues/</guid><description>&lt;p&gt;本节汇总了 CANopen 调试中最常见的问题，按照症状、原因和排查步骤组织。&lt;/p&gt;
&lt;h2 class="heading-element" id="1-硬件层问题"&gt;&lt;span&gt;1. 硬件层问题&lt;/span&gt;
 &lt;a href="#1-%e7%a1%ac%e4%bb%b6%e5%b1%82%e9%97%ae%e9%a2%98" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;h3 class="heading-element" id="11-问题完全无通信"&gt;&lt;span&gt;1.1 问题：完全无通信&lt;/span&gt;
 &lt;a href="#11-%e9%97%ae%e9%a2%98%e5%ae%8c%e5%85%a8%e6%97%a0%e9%80%9a%e4%bf%a1" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;症状&lt;/strong&gt;:&lt;/p&gt;</description></item><item><title>CANopen 日志分析与自动化</title><link>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/04-log-analysis/</link><pubDate>Sat, 23 May 2026 00:00:00 +0800</pubDate><author>ruohuyang@163.com (Cygnus Yang)</author><guid>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/04-log-analysis/</guid><description>&lt;p&gt;持续的系统需要完善的日志系统。本节介绍如何构建 CANopen 日志分析流水线，实现自动化问题诊断。&lt;/p&gt;
&lt;h2 class="heading-element" id="1-日志采集策略"&gt;&lt;span&gt;1. 日志采集策略&lt;/span&gt;
 &lt;a href="#1-%e6%97%a5%e5%bf%97%e9%87%87%e9%9b%86%e7%ad%96%e7%95%a5" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;h3 class="heading-element" id="11-分层日志架构"&gt;&lt;span&gt;1.1 分层日志架构&lt;/span&gt;
 &lt;a href="#11-%e5%88%86%e5%b1%82%e6%97%a5%e5%bf%97%e6%9e%b6%e6%9e%84" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;┌─────────────────────────────────────────────────┐
│ 应用日志 │ ← 业务逻辑、异常状态
│ - Python logging / C&amp;#43;&amp;#43; spdlog │
├─────────────────────────────────────────────────┤
│ CANopen 协议日志 │ ← SDO/PDO 交易记录
│ - 对象字典变化 │
│ - 状态机转换 │
├─────────────────────────────────────────────────┤
│ CAN 原始帧日志 │ ← candump 捕获
│ - 所有 CAN 帧 │
│ - 时间戳精确到微秒 │
├─────────────────────────────────────────────────┤
│ 系统日志 │ ← 内核 CAN 驱动
│ - /var/log/syslog │
│ - dmesg 输出 │
└─────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;h3 class="heading-element" id="12-日志规范"&gt;&lt;span&gt;1.2 日志规范&lt;/span&gt;
 &lt;a href="#12-%e6%97%a5%e5%bf%97%e8%a7%84%e8%8c%83" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;# 日志文件命名规范
can_&amp;lt;interface&amp;gt;_&amp;lt;timestamp&amp;gt;_&amp;lt;scenario&amp;gt;.log

# 示例
can_vcan0_20240520_143022_sdo_timeout.log
can_can0_20240520_150100_production.log&lt;/code&gt;&lt;/pre&gt;&lt;h3 class="heading-element" id="13-日志头格式"&gt;&lt;span&gt;1.3 日志头格式&lt;/span&gt;
 &lt;a href="#13-%e6%97%a5%e5%bf%97%e5%a4%b4%e6%a0%bc%e5%bc%8f" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;#!/bin/bash
# log_header.sh - 生成标准日志头

cat &amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
# CANopen Log File
# Generated: $(date -Iseconds)
# Interface: $INTERFACE
# Bitrate: $BITRATE
# Node IDs: $NODES
# Scenario: $SCENARIO
# Tool: candump
# Version: $(candump -h | head -1)
#
# Frame Format: (timestamp) interface id#data
# Timestamp Precision: microseconds
#
EOF&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 class="heading-element" id="2-命令行日志分析工具"&gt;&lt;span&gt;2. 命令行日志分析工具&lt;/span&gt;
 &lt;a href="#2-%e5%91%bd%e4%bb%a4%e8%a1%8c%e6%97%a5%e5%bf%97%e5%88%86%e6%9e%90%e5%b7%a5%e5%85%b7" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;h3 class="heading-element" id="21-基础统计"&gt;&lt;span&gt;2.1 基础统计&lt;/span&gt;
 &lt;a href="#21-%e5%9f%ba%e7%a1%80%e7%bb%9f%e8%ae%a1" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;#!/bin/bash
# log_stats.sh - 日志统计工具

LOG_FILE=$1

if [ -z &amp;#34;$LOG_FILE&amp;#34; ]; then
 echo &amp;#34;Usage: $0 &amp;lt;log_file&amp;gt;&amp;#34;
 exit 1
fi

echo &amp;#34;=== CANopen 日志分析 ===&amp;#34;
echo &amp;#34;文件: $LOG_FILE&amp;#34;
echo &amp;#34;&amp;#34;

# 总帧数
total_frames=$(wc -l &amp;lt; &amp;#34;$LOG_FILE&amp;#34;)
echo &amp;#34;总帧数: $total_frames&amp;#34;

# 各消息类型统计
echo &amp;#34;&amp;#34;
echo &amp;#34;=== 消息类型分布 ===&amp;#34;
awk &amp;#39;{print $3}&amp;#39; &amp;#34;$LOG_FILE&amp;#34; | cut -d&amp;#39;#&amp;#39; -f1 | sort | uniq -c | sort -rn

# 节点统计
echo &amp;#34;&amp;#34;
echo &amp;#34;=== 节点分布 ===&amp;#34;
awk &amp;#39;{print $3}&amp;#39; &amp;#34;$LOG_FILE&amp;#34; | cut -d&amp;#39;#&amp;#39; -f1 | grep -o &amp;#39;.$&amp;#39; | sort | uniq -c | sort -rn

# 时间跨度
first_time=$(head -1 &amp;#34;$LOG_FILE&amp;#34; | grep -oP &amp;#39;^\(\K[0-9.]&amp;#43;&amp;#39;)
last_time=$(tail -1 &amp;#34;$LOG_FILE&amp;#34; | grep -oP &amp;#39;^\(\K[0-9.]&amp;#43;&amp;#39;)
duration=$(echo &amp;#34;$last_time - $first_time&amp;#34; | bc)
echo &amp;#34;&amp;#34;
echo &amp;#34;时间跨度: $duration 秒&amp;#34;
echo &amp;#34;平均帧率: $(echo &amp;#34;scale=2; $total_frames / $duration&amp;#34; | bc) 帧/秒&amp;#34;&lt;/code&gt;&lt;/pre&gt;&lt;h3 class="heading-element" id="22-sdo-会话提取"&gt;&lt;span&gt;2.2 SDO 会话提取&lt;/span&gt;
 &lt;a href="#22-sdo-%e4%bc%9a%e8%af%9d%e6%8f%90%e5%8f%96" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;#!/bin/bash
# extract_sdo.sh - 提取 SDO 会话

LOG_FILE=$1

echo &amp;#34;=== SDO 会话 ===&amp;#34;
grep -E &amp;#34;(60[0-9A-F]{2}|58[0-9A-F]{2})#&amp;#34; &amp;#34;$LOG_FILE&amp;#34; | \
awk &amp;#39;
BEGIN { last_cmd = &amp;#34;&amp;#34;; }
{
 frame = $0
 id = substr($3, 1, 3)
 data = substr($3, 5)

 # SDO Request
 if (id ~ /^60/) {
 ccs = substr(data, 1, 1)
 idx = substr(data, 4, 2) substr(data, 2, 2)
 subidx = substr(data, 6, 2)

 last_cmd = frame
 last_id = id
 last_idx = idx
 last_subidx = subidx

 # Decode CCS
 if (ccs == &amp;#34;2&amp;#34;) printf &amp;#34;Read 0x%s:%s -&amp;gt; &amp;#34;, idx, subidx
 if (ccs == &amp;#34;3&amp;#34;) printf &amp;#34;Write 0x%s:%s -&amp;gt; &amp;#34;, idx, subidx
 }
 # SDO Response
 else if (id ~ /^58/ &amp;amp;&amp;amp; last_id == substr(id, 2, 2)) {
 scs = substr(data, 1, 1)

 # Decode SCS
 if (scs == &amp;#34;4&amp;#34;) {
 value = substr(data, 8, 2) substr(data, 6, 2) substr(data, 4, 2) substr(data, 2, 2)
 printf &amp;#34;Value: 0x%s\n&amp;#34;, value
 } else if (scs == &amp;#34;8&amp;#34;) {
 abort = substr(data, 8, 2) substr(data, 6, 2) substr(data, 4, 2) substr(data, 2, 2)
 printf &amp;#34;ABORT: 0x%s\n&amp;#34;, abort
 }

 last_cmd = &amp;#34;&amp;#34;
 }
}&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;h3 class="heading-element" id="23-心跳监控"&gt;&lt;span&gt;2.3 心跳监控&lt;/span&gt;
 &lt;a href="#23-%e5%bf%83%e8%b7%b3%e7%9b%91%e6%8e%a7" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;#!/bin/bash
# heartbeat_monitor.sh - 心跳状态监控

LOG_FILE=$1
TIMEOUT_MS=3000

echo &amp;#34;=== 心跳状态监控 (超时: ${TIMEOUT_MS}ms) ===&amp;#34;

# 提取心跳消息
grep -E &amp;#34;7[0-9A-F]{2}#&amp;#34; &amp;#34;$LOG_FILE&amp;#34; | \
awk -v timeout=$TIMEOUT_MS &amp;#39;
BEGIN {
 last_time[256] = 0
}

{
 # 解析帧
 match($0, &amp;#34;\\(([^)]&amp;#43;)\\)&amp;#34;, ts_arr)
 match($3, &amp;#34;([0-9A-F]&amp;#43;)#&amp;#34;, id_arr)
 match($3, &amp;#34;#([0-9A-F]&amp;#43;)&amp;#34;, data_arr)

 time = ts_arr[1]
 node_id = strtonum(&amp;#34;0x&amp;#34; substr(id_arr[1], 2, 2))
 state = strtonum(&amp;#34;0x&amp;#34; data_arr[1])

 # 检查超时
 if (last_time[node_id] &amp;gt; 0) {
 elapsed = (time - last_time[node_id]) * 1000
 if (elapsed &amp;gt; timeout) {
 printf &amp;#34;[%.3f] 节点 %d 心跳超时: %.1f ms\n&amp;#34;, time, node_id, elapsed
 }
 }

 # 输出状态变化
 if (last_state[node_id] != &amp;#34;&amp;#34; &amp;amp;&amp;amp; last_state[node_id] != state) {
 states[&amp;#34;00&amp;#34;] = &amp;#34;Boot-up&amp;#34;
 states[&amp;#34;04&amp;#34;] = &amp;#34;Stopped&amp;#34;
 states[&amp;#34;05&amp;#34;] = &amp;#34;Operational&amp;#34;
 states[&amp;#34;7F&amp;#34;] = &amp;#34;Pre-Operational&amp;#34;
 printf &amp;#34;[%.3f] 节点 %d: %s -&amp;gt; %s\n&amp;#34;, time, node_id, states[last_state[node_id]], states[state]
 }

 last_time[node_id] = time
 last_state[node_id] = state
}&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 class="heading-element" id="3-python-日志分析工具"&gt;&lt;span&gt;3. Python 日志分析工具&lt;/span&gt;
 &lt;a href="#3-python-%e6%97%a5%e5%bf%97%e5%88%86%e6%9e%90%e5%b7%a5%e5%85%b7" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;h3 class="heading-element" id="31-日志解析器"&gt;&lt;span&gt;3.1 日志解析器&lt;/span&gt;
 &lt;a href="#31-%e6%97%a5%e5%bf%97%e8%a7%a3%e6%9e%90%e5%99%a8" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
# canopen_log_parser.py - CANopen 日志解析器

import re
from dataclasses import dataclass
from typing import List, Optional, Dict
from datetime import datetime

@dataclass
class CANFrame:
 &amp;#34;&amp;#34;&amp;#34;CAN 帧数据结构&amp;#34;&amp;#34;&amp;#34;
 timestamp: float
 interface: str
 can_id: int
 data: bytes
 is_extended: bool = False
 is_error: bool = False

 @property
 def cob_id(self) -&amp;gt; int:
 &amp;#34;&amp;#34;&amp;#34;获取 COB-ID（不含功能码）&amp;#34;&amp;#34;&amp;#34;
 return self.can_id &amp;amp; 0x7F

 @property
 def function_code(self) -&amp;gt; int:
 &amp;#34;&amp;#34;&amp;#34;获取功能码&amp;#34;&amp;#34;&amp;#34;
 return self.can_id &amp;amp; 0x780

 @property
 def is_nmt(self) -&amp;gt; bool:
 return self.can_id == 0x000

 @property
 def is_sync(self) -&amp;gt; bool:
 return self.can_id == 0x080

 @property
 def is_emergency(self) -&amp;gt; bool:
 return self.function_code == 0x080

 @property
 def is_time_stamped(self) -&amp;gt; bool:
 return self.function_code == 0x100

 @property
 def is_sdo_request(self) -&amp;gt; bool:
 return (self.can_id &amp;amp; 0x7F0) == 0x600

 @property
 def is_sdo_response(self) -&amp;gt; bool:
 return (self.can_id &amp;amp; 0x7F0) == 0x580

 @property
 def is_heartbeat(self) -&amp;gt; bool:
 return (self.can_id &amp;amp; 0x780) == 0x700

 @property
 def is_tpdo(self) -&amp;gt; bool:
 return 0x180 &amp;lt;= self.can_id &amp;lt;= 0x4FF

 @property
 def is_rpdo(self) -&amp;gt; bool:
 return 0x200 &amp;lt;= self.can_id &amp;lt;= 0x57F


class CANopenLogParser:
 &amp;#34;&amp;#34;&amp;#34;CANopen 日志解析器&amp;#34;&amp;#34;&amp;#34;

 def __init__(self, log_file: str):
 self.log_file = log_file
 self.frames: List[CANFrame] = []

 def parse(self) -&amp;gt; List[CANFrame]:
 &amp;#34;&amp;#34;&amp;#34;解析日志文件&amp;#34;&amp;#34;&amp;#34;
 log_pattern = re.compile(
 r&amp;#39;\((?P&amp;lt;timestamp&amp;gt;[0-9.]&amp;#43;)\)\s&amp;#43;&amp;#39;
 r&amp;#39;(?P&amp;lt;interface&amp;gt;\w&amp;#43;)\s&amp;#43;&amp;#39;
 r&amp;#39;(?P&amp;lt;id&amp;gt;[0-9A-Fa-f]&amp;#43;)#&amp;#39;
 r&amp;#39;(?P&amp;lt;data&amp;gt;[0-9A-Fa-f]*)&amp;#39;
 )

 with open(self.log_file, &amp;#39;r&amp;#39;) as f:
 for line in f:
 line = line.strip()
 if not line or line.startswith(&amp;#39;#&amp;#39;):
 continue

 match = log_pattern.match(line)
 if match:
 frame = CANFrame(
 timestamp=float(match.group(&amp;#39;timestamp&amp;#39;)),
 interface=match.group(&amp;#39;interface&amp;#39;),
 can_id=int(match.group(&amp;#39;id&amp;#39;), 16),
 data=bytes.fromhex(match.group(&amp;#39;data&amp;#39;))
 )
 self.frames.append(frame)

 return self.frames

 def filter_by_node(self, node_id: int) -&amp;gt; List[CANFrame]:
 &amp;#34;&amp;#34;&amp;#34;过滤特定节点的帧&amp;#34;&amp;#34;&amp;#34;
 return [f for f in self.frames if f.cob_id == node_id]

 def get_sdo_sessions(self) -&amp;gt; List[Dict]:
 &amp;#34;&amp;#34;&amp;#34;提取 SDO 会话&amp;#34;&amp;#34;&amp;#34;
 sessions = []
 pending = {}

 for frame in self.frames:
 if frame.is_sdo_request:
 # 解析 SDO Request
 ccs = frame.data[0] &amp;amp; 0xF
 idx = (frame.data[2] &amp;lt;&amp;lt; 8) | frame.data[1]
 subidx = frame.data[3]

 node_id = frame.cob_id
 pending[node_id] = {
 &amp;#39;request_time&amp;#39;: frame.timestamp,
 &amp;#39;node_id&amp;#39;: node_id,
 &amp;#39;ccs&amp;#39;: ccs,
 &amp;#39;index&amp;#39;: idx,
 &amp;#39;subindex&amp;#39;: subidx
 }

 elif frame.is_sdo_response:
 node_id = frame.cob_id
 if node_id in pending:
 session = pending[node_id]
 session[&amp;#39;response_time&amp;#39;] = frame.timestamp
 session[&amp;#39;latency_ms&amp;#39;] = (frame.timestamp - session[&amp;#39;request_time&amp;#39;]) * 1000

 # 解析 SDO Response
 scs = frame.data[0] &amp;amp; 0xF
 session[&amp;#39;scs&amp;#39;] = scs
 session[&amp;#39;success&amp;#39;] = (scs == 0x4) or (scs == 0x2)

 sessions.append(session)
 del pending[node_id]

 return sessions

 def get_heartbeat_states(self) -&amp;gt; Dict[int, List[tuple]]:
 &amp;#34;&amp;#34;&amp;#34;获取心跳状态历史&amp;#34;&amp;#34;&amp;#34;
 states = {}

 for frame in self.frames:
 if frame.is_heartbeat and len(frame.data) &amp;gt;= 1:
 node_id = frame.cob_id
 state = frame.data[0]

 if node_id not in states:
 states[node_id] = []
 states[node_id].append((frame.timestamp, state))

 return states


if __name__ == &amp;#39;__main__&amp;#39;:
 import sys

 if len(sys.argv) &amp;lt; 2:
 print(f&amp;#34;Usage: {sys.argv[0]} &amp;lt;log_file&amp;gt;&amp;#34;)
 sys.exit(1)

 parser = CANopenLogParser(sys.argv[1])
 frames = parser.parse()

 print(f&amp;#34;总帧数: {len(frames)}&amp;#34;)

 # SDO 会话分析
 sdo_sessions = parser.get_sdo_sessions()
 print(f&amp;#34;SDO 会话: {len(sdo_sessions)}&amp;#34;)

 timeout_sessions = [s for s in sdo_sessions if not s[&amp;#39;success&amp;#39;]]
 print(f&amp;#34;SDO 失败: {len(timeout_sessions)}&amp;#34;)

 # 心跳分析
 states = parser.get_heartbeat_states()
 print(f&amp;#34;心跳节点: {len(states)}&amp;#34;)&lt;/code&gt;&lt;/pre&gt;&lt;h3 class="heading-element" id="32-问题检测器"&gt;&lt;span&gt;3.2 问题检测器&lt;/span&gt;
 &lt;a href="#32-%e9%97%ae%e9%a2%98%e6%a3%80%e6%b5%8b%e5%99%a8" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
# canopen_detector.py - CANopen 问题自动检测

from canopen_log_parser import CANopenLogParser, CANFrame

class CANopenIssueDetector:
 &amp;#34;&amp;#34;&amp;#34;CANopen 问题检测器&amp;#34;&amp;#34;&amp;#34;

 def __init__(self, parser: CANopenLogParser):
 self.parser = parser
 self.issues = []

 def detect_all(self):
 &amp;#34;&amp;#34;&amp;#34;检测所有问题&amp;#34;&amp;#34;&amp;#34;
 self.detect_sdo_timeouts()
 self.detect_heartbeat_missing()
 self.detect_nmt_start_failure()
 self.detect_bus_overload()
 self.detect_emergency_messages()
 return self.issues

 def detect_sdo_timeouts(self, timeout_ms=100):
 &amp;#34;&amp;#34;&amp;#34;检测 SDO 超时&amp;#34;&amp;#34;&amp;#34;
 sdo_sessions = self.parser.get_sdo_sessions()

 for session in sdo_sessions:
 if not session.get(&amp;#39;success&amp;#39;):
 self.issues.append({
 &amp;#39;type&amp;#39;: &amp;#39;SDO_TIMEOUT&amp;#39;,
 &amp;#39;severity&amp;#39;: &amp;#39;ERROR&amp;#39;,
 &amp;#39;node_id&amp;#39;: session[&amp;#39;node_id&amp;#39;],
 &amp;#39;index&amp;#39;: hex(session[&amp;#39;index&amp;#39;]),
 &amp;#39;subindex&amp;#39;: hex(session[&amp;#39;subindex&amp;#39;]),
 &amp;#39;message&amp;#39;: f&amp;#34;SDO 读取失败: 0x{session[&amp;#39;index&amp;#39;]:04X}:{session[&amp;#39;subindex&amp;#39;]:02X}&amp;#34;
 })

 def detect_heartbeat_missing(self, timeout_ms=3000, node_ids=None):
 &amp;#34;&amp;#34;&amp;#34;检测心跳丢失&amp;#34;&amp;#34;&amp;#34;
 if node_ids is None:
 # 自动检测所有节点
 frames = self.parser.frames
 node_ids = set(f.cob_id for f in frames if f.is_heartbeat)

 for node_id in node_ids:
 heartbeat_frames = [f for f in self.parser.frames
 if f.is_heartbeat and f.cob_id == node_id]

 for i in range(1, len(heartbeat_frames)):
 prev_time = heartbeat_frames[i-1].timestamp
 curr_time = heartbeat_frames[i].timestamp
 interval_ms = (curr_time - prev_time) * 1000

 if interval_ms &amp;gt; timeout_ms:
 self.issues.append({
 &amp;#39;type&amp;#39;: &amp;#39;HEARTBEAT_MISSING&amp;#39;,
 &amp;#39;severity&amp;#39;: &amp;#39;WARNING&amp;#39;,
 &amp;#39;node_id&amp;#39;: node_id,
 &amp;#39;interval_ms&amp;#39;: interval_ms,
 &amp;#39;message&amp;#39;: f&amp;#34;节点 {node_id} 心跳间隔过大: {interval_ms:.1f} ms&amp;#34;
 })

 def detect_nmt_start_failure(self):
 &amp;#34;&amp;#34;&amp;#34;检测 NMT 启动失败&amp;#34;&amp;#34;&amp;#34;
 # 查找 NMT Start 命令
 nmt_start = [f for f in self.parser.frames if f.is_nmt and f.data[0] == 0x01]

 # 检查是否所有节点都响应
 for frame in nmt_start:
 target_node = frame.data[1]
 if target_node == 0: # 广播，检查所有节点
 continue

 # 检查是否有 Bootup 消息
 bootup = [f for f in self.parser.frames
 if f.is_heartbeat and f.cob_id == target_node
 and f.data[0] == 0x00]

 if not bootup:
 self.issues.append({
 &amp;#39;type&amp;#39;: &amp;#39;NMT_START_FAILURE&amp;#39;,
 &amp;#39;severity&amp;#39;: &amp;#39;ERROR&amp;#39;,
 &amp;#39;node_id&amp;#39;: target_node,
 &amp;#39;message&amp;#39;: f&amp;#34;节点 {target_node} 未响应 NMT Start 命令&amp;#34;
 })

 def detect_bus_overload(self, threshold=0.8, window_sec=1.0):
 &amp;#34;&amp;#34;&amp;#34;检测总线过载&amp;#34;&amp;#34;&amp;#34;
 frames = self.parser.frames
 if not frames:
 return

 start_time = frames[0].timestamp
 end_time = frames[-1].timestamp
 duration = end_time - start_time

 # 计算平均帧率
 avg_rate = len(frames) / duration

 # 计算瞬时负载
 windows = int(duration / window_sec)
 for i in range(windows):
 window_start = start_time &amp;#43; i * window_sec
 window_end = window_start &amp;#43; window_sec

 window_frames = [f for f in frames if window_start &amp;lt;= f.timestamp &amp;lt; window_end]
 instant_rate = len(window_frames) / window_sec

 # 500kbps CANopen 理论最大帧率约为 5000 帧/秒
 max_rate = 5000
 load = instant_rate / max_rate

 if load &amp;gt; threshold:
 self.issues.append({
 &amp;#39;type&amp;#39;: &amp;#39;BUS_OVERLOAD&amp;#39;,
 &amp;#39;severity&amp;#39;: &amp;#39;WARNING&amp;#39;,
 &amp;#39;timestamp&amp;#39;: window_start,
 &amp;#39;load_pct&amp;#39;: load * 100,
 &amp;#39;message&amp;#39;: f&amp;#34;总线过载: 负载 {load*100:.1f}% ({instant_rate:.0f} 帧/秒)&amp;#34;
 })

 def detect_emergency_messages(self):
 &amp;#34;&amp;#34;&amp;#34;检测紧急消息&amp;#34;&amp;#34;&amp;#34;
 emergency_frames = [f for f in self.parser.frames if f.is_emergency]

 for frame in emergency_frames:
 error_code = (frame.data[1] &amp;lt;&amp;lt; 8) | frame.data[0]
 error_register = frame.data[2]

 self.issues.append({
 &amp;#39;type&amp;#39;: &amp;#39;EMERGENCY&amp;#39;,
 &amp;#39;severity&amp;#39;: &amp;#39;ERROR&amp;#39;,
 &amp;#39;node_id&amp;#39;: frame.cob_id,
 &amp;#39;error_code&amp;#39;: hex(error_code),
 &amp;#39;error_register&amp;#39;: hex(error_register),
 &amp;#39;message&amp;#39;: f&amp;#34;紧急消息: 节点 {frame.cob_id}, 错误码 0x{error_code:04X}&amp;#34;
 })

 def print_report(self):
 &amp;#34;&amp;#34;&amp;#34;打印检测报告&amp;#34;&amp;#34;&amp;#34;
 if not self.issues:
 print(&amp;#34;未检测到问题&amp;#34;)
 return

 print(f&amp;#34;\n=== CANopen 问题检测报告 ===&amp;#34;)
 print(f&amp;#34;检测到 {len(self.issues)} 个问题\n&amp;#34;)

 for issue in self.issues:
 severity = issue[&amp;#39;severity&amp;#39;].ljust(8)
 msg = issue[&amp;#39;message&amp;#39;]
 print(f&amp;#34;[{severity}] {msg}&amp;#34;)


if __name__ == &amp;#39;__main__&amp;#39;:
 import sys

 if len(sys.argv) &amp;lt; 2:
 print(f&amp;#34;Usage: {sys.argv[0]} &amp;lt;log_file&amp;gt;&amp;#34;)
 sys.exit(1)

 parser = CANopenLogParser(sys.argv[1])
 parser.parse()

 detector = CANopenIssueDetector(parser)
 detector.detect_all()
 detector.print_report()&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 class="heading-element" id="4-实时监控仪表板"&gt;&lt;span&gt;4. 实时监控仪表板&lt;/span&gt;
 &lt;a href="#4-%e5%ae%9e%e6%97%b6%e7%9b%91%e6%8e%a7%e4%bb%aa%e8%a1%a8%e6%9d%bf" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;h3 class="heading-element" id="41-基于-ncurses-的实时监控"&gt;&lt;span&gt;4.1 基于 ncurses 的实时监控&lt;/span&gt;
 &lt;a href="#41-%e5%9f%ba%e4%ba%8e-ncurses-%e7%9a%84%e5%ae%9e%e6%97%b6%e7%9b%91%e6%8e%a7" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
# canopen_monitor.py - 实时 CANopen 监控

import curses
import time
from threading import Thread
from queue import Queue
import subprocess
import re

class CANopenMonitor:
 def __init__(self, interface):
 self.interface = interface
 self.frame_queue = Queue()
 self.running = False
 self.stats = {
 &amp;#39;total_frames&amp;#39;: 0,
 &amp;#39;sdo_requests&amp;#39;: 0,
 &amp;#39;sdo_responses&amp;#39;: 0,
 &amp;#39;pdo_tx&amp;#39;: 0,
 &amp;#39;pdo_rx&amp;#39;: 0,
 &amp;#39;heartbeat&amp;#39;: 0,
 &amp;#39;errors&amp;#39;: 0,
 &amp;#39;nodes&amp;#39;: {}
 }

 def start_capture(self):
 &amp;#34;&amp;#34;&amp;#34;启动 CAN 帧捕获&amp;#34;&amp;#34;&amp;#34;
 self.running = True

 def capture_thread():
 process = subprocess.Popen(
 [&amp;#39;candump&amp;#39;, self.interface],
 stdout=subprocess.PIPE,
 stderr=subprocess.PIPE,
 text=True
 )

 while self.running:
 line = process.stdout.readline()
 if line:
 self.frame_queue.put(line.strip())

 process.terminate()

 Thread(target=capture_thread, daemon=True).start()

 def update_stats(self, frame_str):
 &amp;#34;&amp;#34;&amp;#34;更新统计信息&amp;#34;&amp;#34;&amp;#34;
 match = re.match(r&amp;#39;.*\s&amp;#43;([0-9A-F]&amp;#43;)#([0-9A-Fa-f]*)&amp;#39;, frame_str)
 if not match:
 return

 can_id = int(match.group(1), 16)
 node_id = can_id &amp;amp; 0x7F

 self.stats[&amp;#39;total_frames&amp;#39;] &amp;#43;= 1

 # 消息类型统计
 if (can_id &amp;amp; 0x7F0) == 0x600:
 self.stats[&amp;#39;sdo_requests&amp;#39;] &amp;#43;= 1
 elif (can_id &amp;amp; 0x7F0) == 0x580:
 self.stats[&amp;#39;sdo_responses&amp;#39;] &amp;#43;= 1
 elif 0x180 &amp;lt;= can_id &amp;lt;= 0x1FF:
 self.stats[&amp;#39;pdo_tx&amp;#39;] &amp;#43;= 1
 elif 0x200 &amp;lt;= can_id &amp;lt;= 0x27F:
 self.stats[&amp;#39;pdo_rx&amp;#39;] &amp;#43;= 1
 elif (can_id &amp;amp; 0x780) == 0x700:
 self.stats[&amp;#39;heartbeat&amp;#39;] &amp;#43;= 1
 if node_id not in self.stats[&amp;#39;nodes&amp;#39;]:
 self.stats[&amp;#39;nodes&amp;#39;][node_id] = {&amp;#39;last_seen&amp;#39;: time.time(), &amp;#39;count&amp;#39;: 0}
 self.stats[&amp;#39;nodes&amp;#39;][node_id][&amp;#39;last_seen&amp;#39;] = time.time()
 self.stats[&amp;#39;nodes&amp;#39;][node_id][&amp;#39;count&amp;#39;] &amp;#43;= 1

 def run(self, stdscr):
 &amp;#34;&amp;#34;&amp;#34;运行监控界面&amp;#34;&amp;#34;&amp;#34;
 self.start_capture()

 curses.curs_set(0)
 stdscr.nodelay(1)

 while self.running:
 # 处理新帧
 while not self.frame_queue.empty():
 frame = self.frame_queue.get()
 self.update_stats(frame)

 # 检查按键
 key = stdscr.getch()
 if key == ord(&amp;#39;q&amp;#39;):
 break

 # 更新显示
 self.render(stdscr)

 stdscr.refresh()
 time.sleep(0.1)

 self.running = False

 def render(self, stdscr):
 &amp;#34;&amp;#34;&amp;#34;渲染监控界面&amp;#34;&amp;#34;&amp;#34;
 stdscr.clear()

 h, w = stdscr.getmaxyx()

 # 标题
 title = f&amp;#34;CANopen 实时监控 - {self.interface}&amp;#34;
 stdscr.addstr(0, (w - len(title)) // 2, title, curses.A_BOLD)

 # 统计信息
 line = 2
 stdscr.addstr(line, 2, f&amp;#34;总帧数: {self.stats[&amp;#39;total_frames&amp;#39;]}&amp;#34;)
 stdscr.addstr(line&amp;#43;1, 2, f&amp;#34;SDO 请求: {self.stats[&amp;#39;sdo_requests&amp;#39;]}&amp;#34;)
 stdscr.addstr(line&amp;#43;2, 2, f&amp;#34;SDO 响应: {self.stats[&amp;#39;sdo_responses&amp;#39;]}&amp;#34;)
 stdscr.addstr(line&amp;#43;3, 2, f&amp;#34;TPDO: {self.stats[&amp;#39;pdo_tx&amp;#39;]}&amp;#34;)
 stdscr.addstr(line&amp;#43;4, 2, f&amp;#34;RPDO: {self.stats[&amp;#39;pdo_rx&amp;#39;]}&amp;#34;)
 stdscr.addstr(line&amp;#43;5, 2, f&amp;#34;心跳: {self.stats[&amp;#39;heartbeat&amp;#39;]}&amp;#34;)

 # 节点状态
 line = 8
 stdscr.addstr(line, 2, &amp;#34;活动节点:&amp;#34;, curses.A_BOLD)
 line &amp;#43;= 1

 for node_id, info in self.stats[&amp;#39;nodes&amp;#39;].items():
 elapsed = time.time() - info[&amp;#39;last_seen&amp;#39;]
 status = &amp;#34;OK&amp;#34; if elapsed &amp;lt; 3.0 else &amp;#34;TIMEOUT&amp;#34;
 stdscr.addstr(line, 4, f&amp;#34;节点 {node_id}: {info[&amp;#39;count&amp;#39;]} 帧 [{status}]&amp;#34;)
 line &amp;#43;= 1

 # 帧率
 stdscr.addstr(h-2, 2, f&amp;#34;按 &amp;#39;q&amp;#39; 退出&amp;#34;)


if __name__ == &amp;#39;__main__&amp;#39;:
 import sys

 if len(sys.argv) &amp;lt; 2:
 print(f&amp;#34;Usage: {sys.argv[0]} &amp;lt;interface&amp;gt;&amp;#34;)
 sys.exit(1)

 interface = sys.argv[1]

 try:
 monitor = CANopenMonitor(interface)
 curses.wrapper(monitor.run)
 except KeyboardInterrupt:
 pass&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;p&gt;&lt;code&gt;★ Insight ─────────────────────────────────────&lt;/code&gt;&lt;/p&gt;</description></item><item><title>CANopen 硬件调试与信号分析</title><link>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/06-hardware-debug/</link><pubDate>Sat, 23 May 2026 00:00:00 +0800</pubDate><author>ruohuyang@163.com (Cygnus Yang)</author><guid>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/06-hardware-debug/</guid><description>&lt;p&gt;物理层问题是 CANopen 系统中最难调试的部分，本节介绍硬件调试工具和方法。&lt;/p&gt;
&lt;h2 class="heading-element" id="1-基础测量工具"&gt;&lt;span&gt;1. 基础测量工具&lt;/span&gt;
 &lt;a href="#1-%e5%9f%ba%e7%a1%80%e6%b5%8b%e9%87%8f%e5%b7%a5%e5%85%b7" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;h3 class="heading-element" id="11-万用表测量"&gt;&lt;span&gt;1.1 万用表测量&lt;/span&gt;
 &lt;a href="#11-%e4%b8%87%e7%94%a8%e8%a1%a8%e6%b5%8b%e9%87%8f" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;h4 class="heading-element" id="测量终端电阻"&gt;&lt;span&gt;测量终端电阻&lt;/span&gt;
 &lt;a href="#%e6%b5%8b%e9%87%8f%e7%bb%88%e7%ab%af%e7%94%b5%e9%98%bb" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;预期值:&lt;/p&gt;</description></item><item><title>CANopen 调试实践项目</title><link>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/07-practice-project/</link><pubDate>Sat, 23 May 2026 00:00:00 +0800</pubDate><author>ruohuyang@163.com (Cygnus Yang)</author><guid>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/07-practice-project/</guid><description>&lt;p&gt;本节通过一个完整的实践项目，综合运用前面学习的所有调试技巧。&lt;/p&gt;
&lt;h2 class="heading-element" id="项目目标"&gt;&lt;span&gt;项目目标&lt;/span&gt;
 &lt;a href="#%e9%a1%b9%e7%9b%ae%e7%9b%ae%e6%a0%87" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;构建一个 CANopen 调试工具套件，包括：&lt;/p&gt;</description></item><item><title>CANopen 调试概述</title><link>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/01-debug-overview/</link><pubDate>Sat, 23 May 2026 00:00:00 +0800</pubDate><author>ruohuyang@163.com (Cygnus Yang)</author><guid>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/01-debug-overview/</guid><description>&lt;h2 class="heading-element" id="1-调试方法论"&gt;&lt;span&gt;1. 调试方法论&lt;/span&gt;
 &lt;a href="#1-%e8%b0%83%e8%af%95%e6%96%b9%e6%b3%95%e8%ae%ba" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;CANopen 系统的调试需要&lt;strong&gt;分层思维&lt;/strong&gt;，从物理层到应用层逐层排查：&lt;/p&gt;</description></item><item><title>Wireshark CANopen 协议分析</title><link>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/03-wireshark-analysis/</link><pubDate>Sat, 23 May 2026 00:00:00 +0800</pubDate><author>ruohuyang@163.com (Cygnus Yang)</author><guid>https://cygnusyang.github.io/posts/02-%E5%B7%A5%E7%A8%8B%E9%82%A3%E4%BA%9B%E4%BA%8B/03-canopen/docs/module-11-debug-tools/03-wireshark-analysis/</guid><description>&lt;p&gt;Wireshark 是功能强大的网络协议分析工具，配合 CANopen 解码插件，可以直观地分析 CANopen 通信流程。&lt;/p&gt;
&lt;h2 class="heading-element" id="1-wireshark-安装与配置"&gt;&lt;span&gt;1. Wireshark 安装与配置&lt;/span&gt;
 &lt;a href="#1-wireshark-%e5%ae%89%e8%a3%85%e4%b8%8e%e9%85%8d%e7%bd%ae" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;h3 class="heading-element" id="11-安装-wireshark"&gt;&lt;span&gt;1.1 安装 Wireshark&lt;/span&gt;
 &lt;a href="#11-%e5%ae%89%e8%a3%85-wireshark" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;# Ubuntu/Debian
sudo apt-get install wireshark

# Fedora
sudo dnf install wireshark

# macOS
brew install --cask wireshark&lt;/code&gt;&lt;/pre&gt;&lt;h3 class="heading-element" id="12-安装-canopen-解码插件"&gt;&lt;span&gt;1.2 安装 CANopen 解码插件&lt;/span&gt;
 &lt;a href="#12-%e5%ae%89%e8%a3%85-canopen-%e8%a7%a3%e7%a0%81%e6%8f%92%e4%bb%b6" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;Wireshark 默认支持 CAN 帧解析，但需要额外插件才能完整解码 CANopen 协议。&lt;/p&gt;</description></item></channel></rss>