1. 项目概述
在嵌入式开发的底层世界里,我们每天都在和微控制器(MCU)打交道,写代码、调时序、优化内存。但你是否曾停下来想过,当你写下一条
LDA $1000
或者
JSR Delay
时,CPU内部究竟发生了什么?它如何找到
$1000
这个地址里的数据?中断发生时,又是如何“暂停”手头工作,去处理紧急事务,然后还能“丝滑”地回到原处的?这些问题,都指向了CPU最核心的机制:寻址模式、中断处理与指令集。今天,我们就以Freescale(现NXP)经典的HCS08系列CPU为例,进行一次深潜。这不仅仅是技术手册的翻译,更是结合了我多年在汽车电子和工业控制项目中使用HCS08的经验,为你拆解那些手册里一笔带过,但在实际调试中却能让你少熬几个通宵的关键细节。
HCS08作为M68HC08的进化版,在保持低成本、低功耗优势的同时,引入了更高效的寻址方式和增强的中断处理能力,广泛应用于对成本和可靠性要求极高的领域。理解它的CPU内核,是写出高效、稳定嵌入式固件的基石。无论你是刚接触HCS08的新手,还是想深化理解的老兵,这篇文章都将带你从“知道指令怎么写”到“明白指令为什么这么执行”,从而在资源受限的8位MCU上榨取出每一分性能。
2. 核心寻址模式深度解析
寻址模式,说白了就是CPU“找数据”的方法。HCS08提供了丰富的寻址方式,这不仅是为了编程灵活,更是为了在有限的指令字节和时钟周期内,实现最高效的内存访问。手册里列出了很多种,我们重点剖析几个在复杂程序中最常用、也最容易产生困惑的模式。
2.1 变址寻址(Indexed Addressing):灵活性的核心
变址寻址是HCS08的亮点,它使用一个16位的索引寄存器对
H:X
(H是高8位,X是低8位)作为基地址,再加上一个偏移量来形成最终的操作数地址。这种模式特别适合处理数组、结构体和查表。
2.1.1 16位偏移变址寻址(IX2)
这是最“强大”的一种变址模式。指令中包含一个16位的偏移量,与
H:X
寄存器的内容相加,得到最终地址。
-
操作数构成
:
oprx16,X。例如指令LDA $1234,X,其中$1234就是那个16位的偏移量。 -
执行过程
:CPU取出指令后,会先读取紧随操作码后的两个字节(即
$1234),将其与H:X寄存器的当前值相加。假设H:X = $1000,那么有效地址就是$1000 + $1234 = $2234。CPU随后从这个地址读取数据加载到累加器A。 - 指令长度与周期 :这类指令通常是4字节(1字节操作码 + 2字节偏移量 + 可能的数据字节),执行需要4个总线周期。虽然比直接寻址慢,但它能访问64KB地址空间内的任何位置,且基址可动态改变。
-
实战心得
:IX2模式是
实现数据结构和跳转表的神器
。比如,你有一个传感器读数数组存放在从
SENSOR_DATA开始的连续地址,用X寄存器作为索引(0, 1, 2...),就可以用LDA SENSOR_DATA,X来遍历。但要注意,H:X是16位寄存器,进行16位加法时, 要小心进位影响到H字节 。通常我们会确保数组基址和索引值都在一个较小的范围内,避免复杂的进位处理。
2.2 堆栈指针相对寻址(SP-Relative):高效访问局部变量
这是HCS08相对于前代产品一个非常实用的增强。它允许我们像使用索引寄存器一样使用堆栈指针(SP)来寻址。这在函数调用中访问局部变量和参数时,效率远超传统的“先弹出到寄存器,再使用”的方式。
2.2.1 8位偏移SP相对寻址(SP1) 指令中包含一个 无符号 的8位偏移量,与SP相加得到有效地址。
-
操作数构成
:
oprx8,SP。例如LDA 4,SP。 -
执行过程
:假设当前
SP = $0230,指令LDA 4,SP会计算有效地址$0230 + $04 = $0234,然后从该地址加载数据。注意,偏移量是无符号的,所以只能访问SP 向上 (地址增大方向)的255个字节范围。 -
核心价值
:在子程序开头,我们经常通过
AIS #-10(给SP减去10)来在栈上分配10字节的局部变量空间。之后,就可以用0,SP到9,SP来高效访问这些局部变量,无需额外的寄存器中转。这 极大地优化了函数调用的性能 。
2.2.2 16位偏移SP相对寻址(SP2) 与SP1类似,但偏移量是16位的,因此可以访问SP附近更大范围的内存(理论上整个64KB空间,但通常用于访问较远的栈帧或固定栈区数据)。
-
操作数构成
:
oprx16,SP。例如STA $0100,SP。 - 注意事项 :SP2模式需要更多的指令字节(通常多1个字节)和执行周期(多1个周期)。因此, 优先使用SP1模式 ,仅在局部变量区超过255字节(这在8位MCU中极为罕见)或需要访问较远栈数据时才使用SP2。
重要提示 :使用SP相对寻址时,必须对堆栈的布局有清晰的认识。SP在压栈时是递减的(向低地址生长)。
0,SP指向的是 最后一次压栈的数据 ,1,SP指向上一次,以此类推。在分配局部变量后,正偏移访问的是局部变量区,而负偏移(理论上,但HCS08不支持负偏移寻址)则指向调用者压入的参数和返回地址。通常,我们会用汇编宏或编译器来管理这些偏移量,避免手动计算出错。
3. 特殊操作序列:CPU的“后台管理”
除了执行常规指令,CPU还需要处理一些特殊事件,如复位、中断、低功耗模式切换等。这些操作不是标准的指令,而是一系列硬件自动执行的微操作序列。理解它们,对于编写可靠的启动代码、中断服务程序(ISR)和电源管理至关重要。
3.1 复位序列(Reset Sequence):一切的开端
复位是MCU最彻底的“重启”信号。它可以由多种原因触发:上电、看门狗超时、外部复位引脚拉低等。
- 关键特性 :HCS08的复位是 异步且立即生效 的。这意味着CPU不会傻傻地等到当前指令执行完,而是立刻停止手头一切工作。这保证了系统在异常状态下能被强行拉回起点,是安全性的基础。
-
6周期序列
:复位事件结束后,CPU会执行一个固定的6周期序列:
-
从固定地址
$FFFE和$FFFF取出复位向量(一个16位的地址)。 - 将这个地址加载到程序计数器(PC)。
- 从这个新地址开始,预取指令队列,准备执行第一条用户程序指令。
-
从固定地址
-
实操要点
:在你的链接器脚本或工程设置中,
必须确保
$FFFE-$FFFF这两个字节被正确编程为你的main函数或启动代码的入口地址 。否则MCU上电后会跑飞。通常开发工具链(如CodeWarrior的.prm文件)会帮你处理。我曾遇到过一个坑:在手动修改Hex文件时,不小心覆盖了复位向量,导致板子“变砖”,只能通过背景调试模式(BDM)重新擦写。
3.2 中断序列(Interrupt Sequence):响应“紧急呼叫”
中断是MCU响应外部事件的核心机制。HCS08的中断处理非常规整。
- 响应时机 :与复位不同,CPU 必须完成当前正在执行的指令 ,才会响应中断。这保证了指令的原子性。
-
现场保存
:响应中断时,CPU自动将5个关键寄存器按顺序压栈:
PCL、PCH、X、A、CCR。 这里有一个极其重要的兼容性细节 :为了向前兼容M68HC05, H寄存器(H:X的高字节)不会被自动保存! 这是很多从HC05/HC08移植代码过来的工程师容易踩的大坑。 -
中断服务程序(ISR)编写铁律
:
-
必须手动保存H寄存器
:如果你的ISR中会修改H:X,或者使用会改变H的指令(如某些带自动递增的变址寻址),必须在ISR开头用
PSHH保存H,在返回前用PULH恢复。 -
全局中断屏蔽
:压栈CCR后,CPU会自动将CCR中的中断屏蔽位
I置1,防止新的中断嵌套。虽然可以用CLI指令在ISR内打开中断以实现嵌套,但 强烈不建议在8位资源紧张的系统中这么做 ,它会急剧增加栈溢出和调试的复杂度。 -
向量获取
:CPU根据中断源,从特定的向量地址(如IRQ中断向量在
$FFFC-$FFFD)取出ISR的入口地址,并跳转执行。
-
必须手动保存H寄存器
:如果你的ISR中会修改H:X,或者使用会改变H的指令(如某些带自动递增的变址寻址),必须在ISR开头用
-
软件中断(SWI)
:这是一个特殊的指令,其行为类似硬件中断,但它不受全局中断屏蔽位
I的控制,且是同步的(由程序指令触发)。常用于实现操作系统调用或调试断点。
3.3 低功耗模式操作:WAIT与STOP
在电池供电设备中,低功耗是硬指标。HCS08提供了WAIT和STOP两种模式。
-
WAIT模式
:执行
WAIT指令后,CPU清除I位(允许中断),然后停止CPU时钟以省电,但外设时钟可能仍在运行。任何中断或复位事件都能唤醒它。 唤醒后的第一件事就是处理这个唤醒中断 ,然后继续执行WAIT之后的指令。 -
STOP模式
:执行
STOP指令后,可以停止所有时钟(包括晶振),功耗降至最低。唤醒通常依赖于外部引脚信号或特定的内部模块(如低功耗定时器)。 这里有个开发调试的救命特性 :当通过背景调试接口(BDM)连接且使能时,即使进入STOP模式,振荡器也会被强制保持活动,以便主机调试器能通过发送BACKGROUND命令唤醒并访问MCU。这意味着你不会因为代码进入STOP模式而“失去”板子。 - 模式选择建议 : 短期休眠用WAIT,长期待机用STOP 。WAIT唤醒更快,但功耗相对较高;STOP功耗极低,但唤醒需要时钟重新启动,有延迟。务必查阅具体型号的数据手册,确认STOP模式下哪些时钟源可以保持运行。
3.4 扩展内存调用:CALL与RTC指令
对于内存大于64KB的HCS08型号,引入了分页机制。
CALL
和
RTC
指令就是用于管理程序在多个内存页之间跳转的“高级”调用指令。
-
CALL指令流程
:
- 计算下一条指令地址(返回地址)并压栈。
- 将当前的程序页寄存器(PPAGE)值压栈 。这是关键一步,保存了当前在哪个内存页。
- 将指令中提供的新的页号写入PPAGE,切换到目标程序页。
- 跳转到目标地址执行。
-
RTC指令流程
:与CALL对应,用于从跨页子程序返回。
- 从栈中弹出PPAGE值并恢复。
- 弹出16位返回地址到PC。
- 继续执行。
-
重要约束与最佳实践
:
-
CALL/RTC必须成对使用。即使调用者和被调用者在同一页,只要子程序可能被其他页的代码调用,就必须用CALL进入,用RTC返回。 -
JSR/RTS只能用于 同一页内 或访问非分页内存区域的调用。 -
性能权衡
:
CALL/RTC比JSR/RTS执行周期长。因此,在内存规划时,应尽量将频繁调用的核心函数库放在同一页或非分页区域,避免不必要的页切换开销。
-
4. HCS08指令集详解与实战编码技巧
指令集是CPU的“词汇表”。HCS08的指令集继承了M68HC08的丰富性,并做了增强。下面我们不是简单罗列表格,而是从编程实战角度,分类解析关键指令和那些容易用错的功能。
4.1 数据传送与移动指令
这是最基础的指令组,包括
LDA
、
LDX
、
LDHX
、
STA
、
STX
、
STHX
,以及独特的
MOV
。
-
LDHX与STHX:这是16位加载/存储指令,用于一次性操作索引寄存器H:X。例如LDHX #$1234将立即数载入,STHX $1000将H:X存储到$1000(低字节)和$1001(高字节)。 在初始化指针或进行16位循环时非常高效 。 -
MOV指令 :这是HCS08的一个亮点,它能在内存与内存之间直接移动数据,无需经过累加器中转。支持多种模式:-
MOV opr8a,opr8a:直接页内两个地址间移动。 -
MOV opr8a,X+:从直接页地址读取数据,存储到X指向的地址,然后X自动加1 。这是 块移动或填充内存的利器 。 -
MOV ,X+,opr8a:从X指向的地址读取数据,存储到直接页地址,然后X自动加1 。 -
使用技巧
:在初始化数组或复制数据块时,用
MOV配合X自动递增,可以写出非常紧凑且快速的循环。例如,用LDHX #SOURCE和MOV ,X+, DEST构成的循环,比用LDA/STA的传统循环更快。
-
4.2 算术与逻辑运算指令
包括
ADD
、
ADC
、
SUB
、
SBC
、
AND
、
ORA
、
EOR
、
INC
、
DEC
、
NEG
、
CMP
、
CPX
、
CPHX
等。
-
带进位与不带进位
:
ADD和SUB是不带进位的加减,用于8位运算。ADC(加进位)和SBC(减进位)用于多字节(如16位、24位)运算。 进行16位加法时,标准流程是 :先对低字节用ADD,再对高字节用ADC。; 计算 HL = DE + BC (16位加法) LDA DEE_LOW ; 加载DE低字节 ADD BC_LOW ; 加BC低字节,影响C标志 STA HL_LOW ; 存结果低字节 LDA DEE_HIGH ; 加载DE高字节 ADC BC_HIGH ; 加BC高字节和来自低位的进位 STA HL_HIGH ; 存结果高字节 -
比较指令的妙用
:
CMP、CPX、CPHX本质上做的是减法,但只更新条件码寄存器(CCR),不保存结果。它们为后面的条件跳转指令(BEQ、BNE、BLO等)铺路。CPHX是比较16位数的大杀器 ,一条指令搞定两个字节的比较。 -
DAA(十进制调整)指令 :当使用BCD码(二进制编码的十进制)进行加法(ADD或ADC)后,执行DAA可以将结果调整回正确的BCD格式。这在需要直接驱动数码管显示或处理某些老式通信协议时有用,但多数现代应用已不再使用BCD。
4.3 位操作与测试指令
包括
BSET
、
BCLR
、
BRSET
、
BRCLR
、
BIT
。
-
BSET/BCLR:直接对内存的指定位进行置1或清0。例如BSET 3, PORTA将PORTA寄存器的第3位置1(从0开始计数)。 这是操作硬件寄存器标志位最清晰、最原子化的方式 ,避免了“读-改-写”过程可能被中断打断的风险。 -
BRSET/BRCLR:测试内存的指定位,并根据结果跳转。例如BRSET 5, STATUS_REG, LED_ON会检查STATUS_REG的第5位,如果为1则跳转到LED_ON标签。 这实现了高效的标志位轮询 。 -
BIT指令 :类似于AND,但只更新标志位(N和Z),不改变累加器A。常用于快速测试某个内存值的特定位,而无需破坏A的内容。
4.4 移位与循环指令
包括
ASL
、
LSR
、
ROL
、
ROR
、
ASR
。
-
ASL与LSL:在HCS08中,ASL(算术左移)和LSL(逻辑左移)是 同一条指令的两种助记符 ,功能完全一样:最高位移入C标志,最低位补0。常用于无符号数乘2。 -
LSR(逻辑右移) :最低位移入C标志,最高位补0。用于无符号数除2。 -
ASR(算术右移) :最低位移入C标志,但最高位(符号位)保持不变。用于有符号数除2,能保持符号。 -
ROL/ROR(带进位循环移位) :将C标志位纳入循环链中。这是 实现多精度移位和位流处理的核心 。例如,要将一个24位数左移一位:CLC ; 清空进位 ROL LOW_BYTE ; 低字节左移,原最高位进入C,C原来的值(0)移入最低位 ROL MID_BYTE ; 中字节左移,接收来自低字节的进位 ROL HIGH_BYTE; 高字节左移,接收来自中字节的进位
4.5 控制转移指令
包括
JMP
、
JSR
、
BSR
、
RTS
、所有条件分支(
BEQ
、
BNE
、
BCC
等)、
CALL
、
RTC
。
-
条件分支的范围
:所有以
B开头的条件分支指令(如BEQ、BCS)都是 相对寻址 ,跳转范围是当前PC的-128到+127字节。如果目标太远,编译器或汇编器会报错,此时需要改用JMP或JSR(绝对跳转)。 -
JSRvsBSR:JSR是绝对地址跳转到子程序,BSR是相对地址跳转。BSR生成的代码更短(2字节 vs 2或3字节),但范围受限。 对于短距离、局部的工具函数,优先用BSR。 -
分支指令的“反逻辑”使用
:有时为了优化代码大小或速度,会使用与直觉相反的条件分支。例如,想“如果相等则跳过一段代码”,可以写成
BNE SKIP...SKIP:。这比BEQ跳转到更远的代码末尾有时更高效。
4.6 栈操作与处理器控制指令
包括
PSHA
、
PSHX
、
PSHH
、
PULA
、
PULX
、
PULH
、
AIS
、
TSX
、
TXS
、
NOP
、
BGND
、
STOP
、
WAIT
。
-
栈指针操作
:
AIS用于快速调整SP,分配或释放栈空间。TSX将SP+1的值传送到H:X,常用于获取栈帧基址。TXS则将H:X-1传送到SP,用于手动设置SP(需极其谨慎)。 -
NOP(空操作) :除了消耗2个时钟周期,什么都不做。它的用途广泛: 代码对齐 (让后续代码落在对齐地址上,有时能提升取指效率)、 精确延时 (在时序要求极严的位操作中插入)、以及 临时替换被删除的指令 作为占位符。 -
BGND指令 :这是HCS08特有的背景调试指令。当背景调试模块(BDM)使能时,执行此指令会使CPU停止用户程序,进入活动背景模式,等待主机调试器命令。 这是实现软件断点的关键 :调试器将目标地址的指令操作码临时替换为BGND的机器码($82)。当程序执行到这里,就会自动进入调试状态。
5. 指令集汇总表精读与编码实战指南
手册中的指令集汇总表(Table 7-2)和操作码映射表(Table 7-3)是宝藏,但需要知道怎么看。
5.1 解读汇总表的关键列
-
Source Form
:汇编语言格式。例如
LDA #opr8i表示立即寻址的LDA指令。 - Address Mode :寻址模式缩写。对照表下方的图例:IMM(立即)、DIR(直接页)、EXT(扩展)、IX2(16位偏移变址)等。
-
Object Code
:指令的机器码(十六进制)。这是你反汇编或手动编码时需要的。例如
LDA #$55的机器码是A6 55。 - Cycles :指令执行所需的 总线周期数 。注意,一个总线周期可能等于一个或多个CPU时钟周期,取决于具体型号的时钟配置。这是评估代码执行时间、编写精确延时函数的基础。
-
Cyc-by-Cyc Details
:周期级细节。例如
rpp表示:一个读操作数周期(r),两个程序取指周期(p)。这让你能精确理解总线活动。 -
Affect on CCR
:对条件码寄存器(CCR)各标志位的影响。
·表示可能置1或清0,–表示不影响,0或1表示强制清0或置1。 理解这个对编写条件判断逻辑至关重要 。
5.2 实战编码技巧与避坑指南
-
优化代码大小
:在资源紧张的HCS08上,代码空间宝贵。优先使用
直接页寻址
(DIR,操作码短,周期少)访问前256字节(
$0000-$00FF)的变量。将高频访问的全局变量和寄存器映射到直接页。 -
优化执行速度
:循环内部使用
无偏移变址寻址(IX)
或
8位偏移变址(IX1)
,它们比16位偏移(IX2)和扩展寻址(EXT)更快。避免在循环内使用
CALL/RTC。 -
中断服务程序(ISR)的黄金法则
:
- 尽可能短 :只做最必要的事情(如设置标志、清除中断源),将耗时处理放到主循环。
-
保存所有用到的寄存器
:除了CPU自动保存的,
别忘了H寄存器(如果需要)
!用
PSHH/PULH。 - 谨慎操作栈 :ISR入口和出口的栈必须平衡。错误操作SP会导致程序崩溃,且极难调试。
-
使用工具链的便利
:现代HCS08开发(如使用NXP CodeWarrior或IAR Embedded Workbench)大多用C语言。但理解汇编让你能:
- 读懂编译器生成的汇编列表,进行深度优化。
- 编写编译器不支持的底层硬件操作(如精确时序循环、特殊位操作)。
- 调试时,能看懂反汇编窗口,定位到C语言无法直接揭示的底层问题。
-
调试利器:BGND与断点
:利用
BGND指令和调试器设置软件断点。理解操作码映射表(Table 7-3)可以帮助你在没有源码的情况下,通过内存查看器识别出被调试器替换为$82的断点位置。
6. 常见问题排查与调试心得
在实际项目中,与HCS08 CPU相关的问题往往隐蔽且棘手。这里分享几个我踩过的坑和解决方法。
6.1 程序跑飞或复位异常
- 症状 :程序不定期重启,或者完全死机。
-
排查思路
:
-
检查栈溢出
:这是8位MCU最常见的问题。如果递归调用太深、ISR嵌套、或局部变量分配过大,SP会覆盖全局变量甚至程序代码。
在软件设计阶段就估算最大栈深度
,并留出足够安全空间。调试时,可以在初始化时将栈区域填充为特定模式(如
$AA),运行一段时间后查看被修改的范围。 -
检查复位向量
:确认
$FFFE-$FFFF处的向量指向有效的、已初始化的程序地址。向量错误会导致上电后执行随机代码。 - 检查看门狗(COP) :如果看门狗被使能但未定期喂狗,会导致系统复位。确认看门狗超时时间设置合理,并在主循环或定时中断中正确清狗。
-
检查中断向量
:未使用的中断向量应指向一个安全的“捕获”函数(如无限循环或复位),而不是
$0000。否则意外中断会导致跑飞。
-
检查栈溢出
:这是8位MCU最常见的问题。如果递归调用太深、ISR嵌套、或局部变量分配过大,SP会覆盖全局变量甚至程序代码。
在软件设计阶段就估算最大栈深度
,并留出足够安全空间。调试时,可以在初始化时将栈区域填充为特定模式(如
6.2 中断不响应或响应异常
- 症状 :外部事件触发了,但ISR没执行。
-
排查步骤
:
-
确认全局中断使能
:主程序初始化后,必须执行
CLI指令来清除CCR的I位。 -
确认具体中断源使能
:例如,要使能定时器中断,除了全局
CLI,还需设置定时器控制寄存器中的中断使能位。 - 清除中断标志 :在ISR中, 必须在处理完事务后,清除触发该中断的硬件标志位 。否则,一退出ISR, pending的中断标志会立刻再次触发中断,导致CPU大部分时间陷在ISR里。
- 检查中断优先级 :HCS08有固定的硬件中断优先级。如果低优先级ISR执行时间过长,可能会阻塞高优先级中断。优化ISR执行时间。
-
确认全局中断使能
:主程序初始化后,必须执行
6.3 数据读写错误(尤其是跨页访问)
- 症状 :读取的变量值不对,或写入后读回不一致。
-
可能原因
:
-
指针错误
:在使用
H:X进行变址寻址时,确保H:X的值是正确的16位地址。常见的错误是只更新了X(低8位)而忘了H(高8位),导致指针“折返”到零页。 -
CALL/RTC使用不当 :在分页内存系统中,用JSR调用了一个可能位于其他页的子程序,或者用RTS返回了一个由CALL调用的子程序。这会导致PC跳转到错误的页,执行垃圾代码。 严格配对使用CALL/RTC和JSR/RTS。 - 内存访问越界 :变址寻址时,计算出的地址超出了物理内存或外设地址范围。访问不存在的地址可能导致总线错误或读取到随机值。
-
指针错误
:在使用
6.4 低功耗模式无法唤醒或唤醒后异常
-
症状
:进入
STOP或WAIT模式后,MCU无法被唤醒,或唤醒后程序状态错乱。 -
检查点
:
- 唤醒源配置 :确认你期望的唤醒源(如外部引脚中断、RTC闹钟)已在进入低功耗模式前正确配置并使能。
- STOP模式下的时钟 :确认在STOP模式下,你期望的唤醒时钟源(如内部1kHz时钟)是否被配置为保持运行。有些型号需要特殊设置。
- IO状态 :进入低功耗前,将未使用的IO引脚设置为已知状态(通常输出低或配置为输入带上拉),防止漏电流。
- 唤醒后的初始化 :从STOP模式唤醒后,系统时钟可能从低速时钟重新开始。需要检查并可能重新初始化主时钟(如PLL)。从WAIT模式唤醒则通常不需要。
理解HCS08 CPU的寻址模式、中断机制和指令集,就像拿到了嵌入式系统底层世界的详细地图。它不能直接让你写出漂亮的C语言应用层代码,但当你需要优化关键循环、调试硬件相关故障、或者在最底层“雕刻”代码以满足苛刻的时序和功耗要求时,这份知识就是你的“手术刀”。记住,在嵌入式领域,对硬件的理解深度,直接决定了你解决问题的能力上限。多读手册,多写汇编,多调板子,这些经验最终都会内化成你的工程直觉。

459


被折叠的 条评论
为什么被折叠?



