ARM Cortex-M3调试接口深度解析:JTAG与SWD原理、切换及实战避坑指南
1. 项目概述在嵌入式开发领域调试接口是连接开发者思维与芯片内部世界的桥梁。无论是追踪一个诡异的死机问题还是单步执行代码观察寄存器变化都离不开一个可靠、高效的调试通道。对于基于ARM Cortex-M3内核的微控制器如TI的Stellaris LM3S2965JTAG和SWD是两种最核心的调试接口协议。很多刚接触底层调试的朋友可能会觉得这些协议手册读起来像天书满篇的状态机、移位序列和寄存器描述。今天我就结合自己多年“摸爬滚打”的经验以LM3S2965为例把JTAG和SWD那层神秘的面纱彻底揭开不仅讲清楚它们是怎么工作的更会分享在实际硬件调试中如何配置、切换以及避坑。简单来说你可以把芯片想象成一个拥有复杂内部结构的黑盒子。JTAG和SWD就是两套不同的“问询协议”用来与盒子内部一个叫做“调试访问端口”的“前台”进行对话。JTAG是业界元老功能全面但“手续”稍显繁琐SWD则是ARM推出的“精简高效版”用更少的线实现核心调试功能。很多现代ARM芯片都同时支持这两种协议并且共享物理引脚这就需要我们掌握在两者间安全切换的“口令”。理解这套机制不仅能让你在调试器连接失败时快速定位问题更能让你对芯片的底层行为有更深刻的把握从“会用工具”进阶到“懂其原理”。2. 调试接口核心原理与架构解析要玩转调试接口不能只停留在“连线-点击下载”的层面必须理解其底层架构。这就像开车知道油门刹车是基础但了解发动机和变速箱的工作原理才能应对复杂路况。2.1 TAP控制器协议状态机的核心无论是JTAG还是SWD其灵魂都是一个名为测试访问端口的状态机。这是一个由IEEE 1149.1标准定义的16状态有限状态机。它通过TCKJTAG时钟或SWCLKSWD时钟的驱动以及TMSJTAG模式选择或SWDIOSWD数据线上信号序列的控制在不同的状态间跳转。TAP控制器的状态主要分为两大部分指令寄存器路径和数据寄存器路径。上电或复位后状态机默认停留在Test-Logic-Reset状态。当我们通过调试器发送一系列特定的时钟和信号序列时状态机就会沿着路径移动。例如进入Shift-IR状态可以让我们向指令寄存器串行移入新的指令代码进入Shift-DR状态则允许我们对当前指令所选择的数据寄存器进行读写操作。为什么需要这么复杂的状态机其根本目的是为了通过极少的引脚JTAG通常为4-5根SWD仅需2根以串行、时序可控的方式访问芯片内部可能非常庞大且复杂的调试资源。状态机提供了严格的操作时序框架确保了通信的可靠性和标准化。2.2 JTAG与SWD的异同与选型考量虽然共享物理引脚但JTAG和SWD在协议层是两套不同的体系。JTAG通常使用4-5根线TCK测试时钟由调试器提供。TMS测试模式选择决定TAP状态机的走向。TDI测试数据输入数据从调试器移入芯片。TDO测试数据输出数据从芯片移出到调试器。nTRST测试复位可选用于异步复位TAP控制器。JTAG的优势在于其通用性和强大的边界扫描测试能力可以用于测试PCB上芯片间的连接性。但其引脚占用较多在小型化、高密度设计中是个负担。SWD则精简为2根线SWCLK串行线时钟等同于TCK。SWDIO串行线数据输入输出这是一根双向数据线分时复用替代了TMS、TDI和TDO的功能。SWD的优势非常明显节省引脚仅需2个引脚极大释放了GPIO资源。更高的通信效率协议开销更小在相同时钟频率下数据传输速率通常高于JTAG。更好的抗干扰潜力由于是双向单线协议在某些布局受限的情况下布线更简单。在实际项目中如何选择我的经验是优先使用SWD。除非你的调试器不支持SWD或者你需要使用JTAG特有的边界扫描功能来排查硬件焊接故障否则SWD在绝大多数嵌入式软件开发场景下都是更优解。现在主流的调试探头如J-Link、ST-Link、DAPLink都完美支持SWD。2.3 ARM Cortex-M3调试系统架构在Cortex-M3内部调试功能是通过一个名为CoreSight的架构实现的。其核心组件是调试访问端口。DAP是外部调试器与芯片内部调试组件如处理器内核的调试寄存器、断点单元、数据观察点、内存访问端口等之间的桥梁。DAP可以工作在两种模式下JTAG-DP或SW-DP。这就是为什么芯片能支持两种协议。在芯片内部有一个SWJ-DP模块它实现了这两种协议接口的切换。我们通过物理引脚发送的特定序列实际上就是告诉SWJ-DP“请切换到JTAG模式”或“请切换到SWD模式”。DAP之上还有访问端口。AP是实际执行内存/寄存器访问的组件。最常用的AP是AHB-AP它通过芯片的AHB总线可以让调试器直接读写内存、外设寄存器甚至控制CPU的运行。我们平时在IDE里查看变量内存、修改寄存器值这些操作最终都是通过DAP-AP-AHB总线这条路径完成的。理解这个架构就能明白为什么调试接口如此强大它本质上是在总线上开了一个具有最高权限的“后门”让调试器可以绕过正在运行的CPU直接与内存系统交互。3. 硬件连接、初始化与关键配置理论懂了接下来就是动手。硬件连接是第一步也是最容易踩坑的地方。3.1 最小系统连接与引脚复用以LM3S2965为例其JTAG/SWD接口引脚通常是复用的GPIO。在芯片复位后这些引脚默认可能被配置为调试功能但你的应用程序代码可能会在初始化时将它们重新配置为普通GPIO这将导致调试器后续无法连接。关键引脚如下TCK/SWCLK时钟线。必须连接通常需要上拉。TMS/SWDIO模式选择/数据线。必须连接通常需要上拉。TDIJTAG数据输入。仅在JTAG模式下需要通常需要上拉。TDOJTAG数据输出。仅在JTAG模式下需要通常需要下拉或直接连接。nTRSTJTAG复位可选。建议连接并下拉以确保JTAG逻辑的确定状态。RST系统复位。强烈建议连接。调试器通过控制此引脚可以实现可靠的系统复位和连接恢复。一个稳健的SWD四线连接方案如下调试器引脚 --- 目标板引脚 SWCLK --- TCK/SWCLK (串联22-100Ω电阻可选用于抑制过冲) SWDIO --- TMS/SWDIO (串联22-100Ω电阻) GND --- GND nRST --- RST (串联100-470Ω电阻) Vref(3.3V)--- VDD (用于电平检测非供电)注意上拉电阻通常4.7kΩ-10kΩ对SWDIO和SWCLK的稳定至关重要尤其是在线缆较长或接口热插拔时。很多开发板已集成自行设计时切勿遗漏。3.2 复位电路与调试器协同RST引脚的处理是硬件设计的一个重点。LM3S2965的数据手册强调了RST走线应尽可能短。在调试场景中调试器需要控制这个引脚。为什么调试器要控制RST连接恢复当芯片因程序跑飞、进入低功耗模式或IO配置冲突导致调试端口“死锁”时调试器可以通过拉低RST来对芯片进行硬复位使其回到已知的初始状态调试端口使能从而重新建立连接。可靠编在烧录程序前进行系统复位可以确保芯片从初始状态开始执行烧录流程避免残留程序状态的影响。调试控制实现“复位并暂停”功能让程序在main()函数的第一条指令处就停下来方便从头调试。在你的原理图中如果已经有RC复位电路或复位按钮调试器的nRST线应通过一个数百欧姆的电阻连接到芯片的RST引脚以避免多个驱动源的冲突。3.3 软件初始化恢复调试功能如果你的程序在启动后无法再连接调试器很可能是代码中改动了调试引脚的功能。对于LM3S2965你需要检查并恢复相关GPIO的复用功能。关键寄存器是GPIOAFSEL。例如JTAG引脚可能对应PB7和PC[3:0]。在应用程序初始化早期例如在配置系统时钟之后但在大量外设初始化之前你应该确保这些引脚的正确配置// 假设使用Stellaris外设驱动库 #include inc/hw_memmap.h #include driverlib/sysctl.h #include driverlib/gpio.h void RestoreDebugPort(void) { // 1. 使能对应GPIO端口的时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC); // 等待外设就绪良好习惯 while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB) || !SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOC)) { } // 2. 解锁并配置JTAG/SWD引脚为硬件复用功能 // 注意某些芯片的JTAG引脚可能默认被锁定需要解锁 HWREG(GPIO_PORTB_BASE GPIO_O_LOCK) GPIO_LOCK_KEY; HWREG(GPIO_PORTB_BASE GPIO_O_CR) | 0x80; // 解锁PB7 HWREG(GPIO_PORTB_BASE GPIO_O_LOCK) 0; // 配置PB7 (TCK/SWCLK) 和 PC0-PC3 (TMS/SWDIO, TDI, TDO, nTRST) 为硬件复用功能 GPIOPinTypeGPIOOutput(GPIO_PORTB_BASE, GPIO_PIN_7); // 先设为输出避免浮空 GPIOPinConfigure(GPIO_PB7_TDO); // 具体复用功能号需查数据手册 GPIOPinTypeGPIOInput(GPIO_PORTB_BASE, GPIO_PIN_7); // 再由硬件控制 // 类似地配置GPIOC的引脚... // GPIOPinConfigure(GPIO_PC0_SWDIO); // ... // 3. 在GPIOAFSEL寄存器中使能复用功能 HWREG(GPIO_PORTB_BASE GPIO_O_AFSEL) | 0x80; HWREG(GPIO_PORTC_BASE GPIO_O_AFSEL) | 0x0F; }这段代码需要在你的main()函数最开始或者在一个你能通过其他方式如Bootloader调用的地方执行。一个更常见的做法是在调试阶段直接在初始化代码中注释掉或避免任何会修改这些调试引脚GPIO模式的代码。4. 协议切换序列的深度剖析与实操这是JTAG/SWD混合接口中最精妙也最容易出问题的一环。芯片上电后SWJ-DP模块可能处于未知状态可能是JTAG模式也可能是SWD模式或者非活动状态。调试器在开始通信前必须发送一个确定的序列来将其切换到目标模式。4.1 JTAG-to-SWD切换序列详解根据ARM的调试接口架构规范从JTAG模式切换到SWD模式需要发送一个特定的16位命令。这个命令是0xE79E注意是LSB先发即二进制b1110.0111.1001.1110从最低位开始发送。完整的切换序列步骤如下确保线复位在TCK/SWCLK上产生至少50个时钟周期同时保持TMS/SWDIO为高电平。这个操作的目的是强制TAP控制器进入Test-Logic-Reset状态。这是一个“清扫战场”的动作无论之前状态机处于何种混乱状态这个长复位序列都能将其拉回确定的起点。发送切换命令在TCK/SWCLK的驱动下将16位的命令0xE79E通过TMS/SWDIO线按照LSB优先的顺序一位一位地移入。这16个时钟边沿和对应的数据位会驱动TAP状态机走过一系列特定的状态最终这个“暗号”被SWJ-DP模块识别并执行模式切换。确认SWD复位再次产生至少50个TCK/SWCLK周期TMS/SWDIO保持高电平。这一步是为了确保如果SWJ-DP之前已经在SWD模式这个长复位序列也能让SWD协议本身进入线复位状态准备接受新的SWD命令。如何验证切换成功调试器在发送完序列后会立即尝试进行一次SWD操作通常是读取DAP的IDCODE寄存器通过SWD的IDCODE读取请求。对于Cortex-M3这个ID值通常是0x0BB11477ARM的JTAG-AP IDR或0x1BA01477SW-DP IDR。如果读回的ID与预期匹配说明切换成功SWD通信链路已建立。4.2 SWD-to-JTAG切换序列详解从SWD切换回JTAG的过程类似只是命令不同。切换命令是0xE73CLSB先发二进制b1110.0111.0011.1100。完整切换序列确保线复位同样先发送至少50个SWCLK周期SWDIO保持高电平。这一步对于SWD协议会使其进入线复位状态。发送切换命令在SWCLK驱动下将16位命令0xE73C通过SWDIO线LSB优先发送。确认JTAG复位再次发送至少50个SWCLK周期SWDIO保持高电平。这将确保TAP控制器进入Test-Logic-Reset状态。验证方法切换后调试器会通过JTAG协议发送IDCODE指令IR中移入1110然后从DR中移出数据。对于LM3S2965其JTAG IDCODE通常包含制造商和部件信息例如TI的制造商ID。读取成功即表明切换回JTAG模式。4.3 调试器中的实际处理流程我们平时使用IDE如Keil、IAR或命令行工具OpenOCD、pyOCD时并不需要手动计算和发送这些序列。调试器软件和调试探头固件已经封装了这一切。以常见的J-Link为例其连接流程通常是这样的用户点击“连接”或“下载”。调试器软件通过USB命令询问J-Link探头“目标芯片是什么”软件根据芯片型号加载对应的J-Link配置文件其中包含了调试接口类型JTAG/SWD、时钟速度、复位方式以及最重要的初始化序列。J-Link探头按照配置先控制nRST引脚进行硬件复位如果已连接。探头根据配置自动发送JTAG-to-SWD切换序列如果配置为SWD模式。探头尝试读取DAP IDCODE。如果成功则报告连接成功如果失败可能会尝试不同的时钟速度、不同的复位策略如连接前复位、连接后复位甚至尝试JTAG模式并重复步骤5-6。连接成功后调试器才开始进行擦除、编程、调试等上层操作。一个重要的实操心得当你遇到“Cannot connect to target”这类错误时不要只盯着软件配置。首先检查硬件连接电源、地线、SWDIO/SWCLK然后尝试降低SWD时钟频率比如从4MHz降到100kHz。很多时候过高的时钟速度在布线不理想或存在干扰时会导致通信失败。在OpenOCD的配置文件中你可以通过adapter speed 100这样的命令来设置速度。5. 通信同步与可靠性保障机制调试通信并非简单的“发送-接收”尤其在异步时钟域下需要握手机制来保证可靠性。5.1 ACK响应与等待机制在SWD协议中每一次读或写操作后目标芯片都会返回一个3位的ACK响应。这个响应告诉调试器上一次请求的结果OKAY (001)成功。WAIT (010)忙需要重试。FAULT (100)错误。LM3S2965的数据手册提到了一个关键点由于调试时钟SWCLK和系统时钟SysClk可能运行在不同频率必须注维持可靠通信。在Capture-DR状态芯片会返回前一个事务的结果和ACK响应。软件调试器应该检查ACK响应以确认前一个操作是否完成然后再发起新的事务。这是一种严格的同步方式。但是手册也给出了一个优化条如果系统时钟至少比调试时钟快8倍SysClk 8 * SWCLK那么前一个操作有足够的时间在下一个调试时钟周期前完成此时可以不检查ACK位。这实际上是一种用时钟频率差来换取通信简化可能更快的策略。在实际应用中意味着什么如果你的系统跑在50MHz而SWD时钟设为1MHz满足8倍关系调试器可能采用更激进的无等待通信模式。如果你把系统时钟降到了1MHz例如在低功耗模式而SWD时钟仍为1MHz则不满足条件。此时如果调试器仍采用无等待模式就可能因目标芯片响应不及而出错。这也是为什么在调试低功耗应用系统时钟大幅降低时经常出现调试器连接不稳定或访问内存失败的原因之一。解决方案在进入低功耗模式前要么降低SWD时钟频率使其远低于系统时钟要么确保你的调试器驱动/配置能够在这种场景下正确处理ACK等待。5.2 时钟域交叉与稳定性设计调试接口通信本质上是跨时钟域操作。SWCLK由外部调试器产生而芯片内部的处理逻辑运行在SysClk下。这两个时钟是异步的。为了可靠地捕获SWDIO上的数据和产生响应芯片内部会有同步器电路。但同步器需要时间这就解释了为什么需要ACK等待机制以及为什么过高的SWCLK在低SysClk下容易失败。设计建议在PCB布局时将SWCLK和SWDIO当作敏感的模拟信号对待走线尽量短远离高频噪声源如开关电源、晶振。确保SWCLK上有合适的上拉电阻如10kΩ为信号提供明确的默认状态增强抗干扰能力。在软件上如果需要进行可靠的调试通信如在系统时钟变化的复杂场景主动在代码中插入短暂延时或使用调试器提供的“连接前复位并延时”功能给电源和时钟电路足够的稳定时间。6. 核心调试指令与寄存器实战解析理解了通信链路我们来看看通过这个链路能做什么。这主要通过JTAG的指令寄存器来实现。6.1 关键JTAG指令详解IR是一个4位寄存器它决定了当前通过TDI-TDO链路访问的是哪个数据寄存器。LM3S2965支持的核心指令如下表所示IR[3:0]指令描述1110IDCODE默认指令。读取芯片的IDCODE用于识别器件。1111BYPASS旁路指令。将TDI直连到TDO形成一个1位移位寄存器用于跳过当前不关心的芯片提高长扫描链测试效率。1010DPACC访问ARM调试端口寄存器。这是进行所有调试操作如读写内存、控制CPU的核心门户。1011APACC访问ARM访问端口寄存器。通过AP可以访问更具体的调试组件如AHB-AP来访问系统总线。1000ABORT向DAP发送中止命令用于清除错误状态或中止未完成的请求。0010SAMPLE/PRELOAD连接边界扫描数据寄存器。用于采样I/O引脚状态或预加载测试数据。0000EXTEST外部测试。使用SAMPLE/PRELOAD预加载的数据驱动芯片输出引脚用于测试PCB板级连接。0001INTEST内部测试。使用预加载的数据驱动信号到芯片核心用于芯片内部测试。对于嵌入式软件开发者最常用的是IDCODE、DPACC和APACC。IDCODE用于识别DPACC和APACC是实际进行调试读写的工具。6.2 通过DPACC/APACC访问内存一个实例推演这是调试器实现“查看变量地址0x20000000的值”背后的底层操作。过程比想象中复杂但理解它有助于排查深层调试问题。选择DP首先需要通过DPACC操作向DP的选择寄存器写入值以选择要操作的AP例如AHB-AP和该AP内的某个寄存器。配置AP然后通过APACC操作向AHB-AP的控制状态寄存器写入配置比如设置访问大小32位、设置自动增量等。执行读写最后再通过APACC操作向AHB-AP的数据读写寄存器发起实际的读写请求。这个请求会被AHB-AP转换成AHB总线事务访问目标内存地址。整个过程是高度流水线和有状态的。调试器软件如OpenOCD的DAP驱动封装了这些繁琐的步骤。但当你遇到“Cannot access target memory”错误时可能意味着DAP状态机出现了混乱例如一个未完成的访问被异常中断。此时的标准恢复操作是通过ABORT指令清除DAP的任何待处理错误。重新初始化DAP的配置序列即重新执行上述1-3步。很多调试器在连接时自动执行的“DAP初始化”脚本干的就是这个事情。6.3 边界扫描功能的应用SAMPLE/PRELOAD、EXTEST、INTEST指令主要用于边界扫描测试。每个GPIO引脚在芯片内部都对应一个边界扫描单元包含输入、输出和输出使能三个信号。SAMPLE/PRELOAD在Capture-DR状态可以非侵入式地捕获所有GPIO引脚当前的电平状态输入值、输出值、输出使能并通过TDO移出查看。这就像给芯片的所有引脚拍了一张快照对于硬件调试极其有用。同时你还可以通过TDI移入新的数据预加载到寄存器中供后续EXTEST或INTEST使用。EXTEST将预加载的数据驱动到芯片的输出引脚上。这可以用来测试电路板上的连线是否短路、开路。例如可以让芯片的某个引脚输出高电平然后用万用表测量板子另一端对应网络是否为高。INTEST将预加载的数据驱动到芯片内部核心逻辑的输入侧。这主要用于芯片生产时的内部逻辑测试。对于软件工程师SAMPLE/PRELOAD的“快照”功能在排查硬件问题时非常实用。一些高级的调试器或专门的边界扫描测试仪如XJTAG可以图形化地显示这些引脚状态。7. 常见问题排查与实战技巧理论最终要服务于实践。下面是我在多年调试中总结的一些典型问题和对策。7.1 连接类问题速查表问题现象可能原因排查步骤与解决方案调试器报告“No device found”或“Cannot connect to target”1. 物理连接问题线缆、虚焊2. 目标板未供电或电压不符3. 复位引脚状态异常4. 调试引脚被软件复用为GPIO5. 芯片处于低功耗模式系统时钟停止1.万用表检查VDD、GND、SWCLK、SWDIO、RST电压。SWCLK/SWDIO应有上拉至VDD。RST在无复位时应为高电平。2.示波器/逻辑分析仪观察点击连接时SWCLK是否有脉冲SWDIO是否有数据波形。这是最直接的证据。3.尝试降低SWD速度在调试器设置中将时钟从4MHz降至100kHz或更低。4.使用复位策略在调试器配置中启用“Connect under reset”或“Hardware reset”。5.检查代码确认初始化代码未改变调试引脚功能。可在main()最开始加死循环while(1);来测试。可以连接并识别ID但无法擦除/编程Flash1. 芯片的Flash编程算法未正确加载或配置起始地址、大小、扇区结构。2. 芯片写保护未解除。3. 系统时钟配置异常导致Flash控制器访问失败。4. 电源不稳定。1.核对算法文件确认IDE中选择的芯片型号与实物完全一致。2.解除保护使用芯片厂商提供的专用工具如LM Flash Programmer进行全片擦除解除写保护。3.检查时钟确保在编程操作期间系统时钟配置正确例如不能使用PLL输出作为Flash时钟源时却启用了PLL。4.测量电源纹波尤其在编程瞬间电流变化大可能导电压跌落。调试时断点不生效、单步运行异常1. 调试器配置中未使能硬件断点或数量超过芯片限制Cortex-M3通常为6个。2. 代码在RAM中运行但未正确初始化RAM或配置MPU/内存属性。3. 芯片的调试组件如FPB、DWT被意外禁用。1.检查断点设置确认使用的是硬件断点而非软件断点Flash中也可用软件断点但原理不同。2.检查向量表重映射如果代码在RAM中运行需确认SCB-VTOR寄存器已指向RAM中的向量表。3.检查调试使能确认未在代码中设置CoreDebug-DHCSR寄存器的C_DEBUGEN位为0。运行中偶尔出现调试连接断开1. 电源噪声或纹波过大。2. SWD线受到强干扰如靠近电机、继电器。3. 芯片进入深度睡眠调试时钟域被关闭。1.加强电源滤波在芯片电源引脚就近增加钽电容和瓷片电容。2.改善布线使用双绞线远离干扰源。可尝试在SWDIO/SWCLK上串联小电阻22-100Ω并增加对地小电容10-100pF滤波。3.配置调试休眠行为在IDE中配置调试器“保持连接在低功耗模式”或在芯片低功耗代码中避免完全关闭调试模块所需的时钟域。7.2 高级技巧与心得逻辑分析仪是你的好朋友遇到棘手的通信问题一个支持协议分析的逻辑分析仪如Saleae能直接捕获SWD/JTAG波形解码出命令、地址、数据、ACK让你一眼看出是主机没发对还是从机没响应效率远超盲目猜测。善用“Connect under reset”这是解决大多数“玄学”连接问题的利器。该模式会在发起调试连接前先拉低目标芯片的RST引脚在整个连接初始化过程中保持复位状态待协议切换、速度协商完成后再释放复位。这确保了芯片内核和调试端口都以一个绝对干净的状态开始对话。理解芯片的启动顺序与调试端口使能有些芯片的调试端口可能在某种启动模式下如从系统存储器启动默认被禁用。需要查阅芯片参考手册的“调试配置”章节检查是否有相关的选项字节需要编程。开源工具链的配置如果你使用OpenOCD它的配置文件.cfg文件威力巨大。你可以在这里详细定义复位方式、切换序列、适配器速度、TAP描述等。例如针对LM3S2965你可能需要类似下面的配置片段来正确初始化和切换# openocd.cfg 片段示例 adapter speed 1000 transport select swd reset_config srst_only srst_nogate # 定义TAP jtag newtap lm3s cpu -irlen 4 -expected-id 0x0ba01477 # 在初始化阶段可能还需要发送一些特定于芯片的JTAG指令来配置调试端口仔细研读和编写这些配置脚本能解决很多官方IDE自动处理但开源工具链下需要手动处理的问题。保持耐心与记录硬件调试问题往往具有偶发性。养成好习惯每次改变一个变量如降低时钟、加滤波电容、修改代码并记录结果。最终你会发现绝大部分“玄学”问题背后都有其确定的物理或逻辑原因。