1. 项目概述为什么我们需要CLA在电机控制、数字电源或者任何需要高频率、高精度闭环调节的嵌入式系统里主CPU比如C28x内核常常会感到“力不从心”。它要处理系统调度、通信协议、外设管理还要实时计算复杂的控制算法比如PID、PARK/CLARKE变换、SVPWM一个控制周期可能只有几十微秒。这时候如果所有计算都压在CPU上要么控制频率上不去要么CPU负载率爆表系统稳定性堪忧。TI C2000系列微控制器里的控制律加速器CLA就是为了解决这个痛点而生的。你可以把它理解成一个专为浮点运算和控制算法定制的“数学协处理器”。它有自己的指令集、寄存器组、程序和数据存储器最关键的是它能和主CPU并行工作。CPU负责“管家”事务CLA则心无旁骛地处理ADC采样、算法计算、PWM更新这一条“控制流水线”。这种架构带来的性能提升是立竿见影的尤其是在需要高频比如100kHz以上电流环的应用中CLA几乎是实现高性能的标配。然而用好CLA远不止写个算法函数那么简单。它是一套独立的子系统从内存映射、中断响应到调试、流水线优化都有其独特的规则和“脾气”。很多开发者初次接触时容易在调试时遇到CLA“跑飞”导致整个系统卡死或者因为流水线对齐问题导致计算结果时对时错。本文将从实战出发结合TMS320F28P65x这款芯片手把手带你走通CLA从初始化、调试到性能优化的全流程特别是那些官方手册一笔带过、但实际开发中又绕不开的“坑”。2. CLA系统初始化搭建CPU与CLA的协作桥梁CLA的初始化核心目标是建立CPU与CLA两个“世界”之间的通信与协作规则。这个过程必须严谨顺序错了或者配置漏了CLA就可能不工作或者行为异常。2.1 内存空间映射为CLA划好“自留地”CLA不能直接访问主CPU的全部内存它有自己的“领地”——CLA程序RAM和数据RAM。第一步就是告诉内存控制器哪些RAM块归CLA管。操作步骤与原理声明所有权通过配置内存配置寄存器MemCfgRegs.LSxMSEL[MSEL_LSx]将特定的LSx如LS0-LS9内存块的所有权分配给CLA。这相当于在硬件上给这块RAM贴上了“CLA专用”的标签。指定用途进一步配置MemCfgRegs.LSxCLAPGM[CLAPGM_LSx]将该内存块指定为CLA的程序存储器。这一步很关键它决定了这块RAM是存放CLA的可执行代码。注意一旦某块LSx内存被配置为CLA程序内存主CPU在CLA取指周期内是无法访问它的调试访问除外。这是为了避免总线冲突。因此通常的做法是CPU在初始化阶段将编译好的CLA程序代码通常是.cla或.c文件编译后的段加载到这块RAM中之后除非重新编程否则不再修改。实操心得规划要趁早在链接器命令文件.cmd里就要明确划分出给CLA程序和数据使用的内存段。例如将RAMLS0和RAMLS1分配给CLA程序RAMLS2和RAMLS3分配给CLA数据消息RAM。避免和CPU的变量、堆栈区域冲突。配置后勿动手册明确指出内存映射通常在初始化时完成一次。如果运行时非要改变必须先禁用所有CLA中断并等待所有任务完成通过检查MIRUN寄存器否则会导致不可预知的行为。在99%的应用中初始化后就不应该再改动这个配置。2.2 中断向量与寄存器初始化建立通信“热线”CLA通过中断与CPU交互。当一个CLA任务完成或者发生溢出/下溢时它需要“通知”CPU。关键配置点PIE向量表你需要为CLA可能用到的任务中断Task1-Task8以及溢出/下溢中断在PIE外设中断扩展向量表中分配对应的中断服务程序ISR入口。虽然CLA任务本身在CLA中执行但任务完成通知是发给CPU的CPU的ISR可以用于后续处理比如启动下一个控制周期或进行数据后处理。CLA中断使能在CLA自己的中断使能寄存器MIER中使能你计划使用的具体任务。只有被使能的CLA任务才会响应外部的触发信号。一个至关重要的顺序问题 手册里特别警告了一个常见陷阱先配置CLA再使能外设中断。为什么因为CLA任务触发依赖于中断信号的下降沿。如果你先使能了ADC的周期中断而ADC在CLA配置好之前就产生了中断脉冲这个“边沿”CLA就错过了即使后来配置好它也会一直等待那个永远不会再来的“第一次下降沿”导致任务无法启动。避坑指南标准初始化序列初始化系统时钟、GPIO等基础外设。配置CLA内存映射。初始化CLA任务函数指针、消息RAM等。配置并启用CLA的中断设置MIER。清除所有可能触发CLA任务的外设如ePWM、ADC的中断标志位清除PIE和IFR中的相应位。最后才去初始化并启动那些会触发CLA任务的外设如配置ADC采样、ePWM周期中断。双保险在启动外设前除了清除中断标志还可以暂时屏蔽其PIE中断。等所有配置就绪再统一开启。2.3 消息RAMCPU与CLA的“共享信箱”CPU和CLA有各自独立的内存空间不能直接互相访问变量。它们通过消息RAM来传递数据。这通常是一块被映射为CLA数据内存的RAM区域CPU和CLA都能访问。典型用法CPU到CLA消息RAMCPU将控制参数、参考值、ADC采样命令等写入此区域。例如一个结构体包含Vref电压参考、Iabc_measured三相电流采样值。CLA到CPU消息RAMCLA将计算完成的结果如PWM占空比、新的控制状态、故障标志等写入此区域。CPU在CLA任务完成中断中读取这些结果。数据一致性保障 由于是共享内存需要考虑数据竞争。通常采用“生产者-消费者”模型并通过软件协议保证使用独立的“数据就绪”标志位通常是一个volatile变量。CPU写数据 → 设置“CPU数据就绪”标志 → CLA读取数据并清零该标志 → CLA计算 → CLA写结果并设置“CLA数据就绪”标志 → CPU读取结果并清零标志。避免同时读写同一数据块。3. CLA代码调试驯服独立的协处理器调试CLA代码是新手最容易“翻车”的地方。因为CLA独立运行一旦它进入死循环不仅自己停不下来还可能因为总线占用而“拖死”主CPU的调试器连接。TMS320F28P65x的CLAType 2提供了真正的软件断点支持这是调试利器。3.1 软件断点MDEBUGSTOP1精准控制的暂停键MDEBUGSTOP1是一条特殊的CLA指令用作软件断点。它的强大之处在于对流水线的智能处理。工作原理 当你在代码中插入MDEBUGSTOP1或使用C编译器内置函数__mdebugstop()调试器会用这条指令替换目标地址的原指令。CLA执行到MDEBUGSTOP1时会在其流水线的D2阶段暂停。关键在于它会清空Flush流水线中已经被预取但尚未执行到D2阶段的后续指令。为什么需要清空流水线考虑一个顺序执行的代码流i1, i2, i3, i4, i5(MDEBUGSTOP1), i6, i7, i8...。当i5被替换为断点指令并执行到D2阶段暂停时i6, i7, i8可能已经被预取到流水线的F1/F2/D1阶段。如果不清空在单步执行Step时CLA会从i6始执行这就跳过了原本的i5指令导致逻辑错误。MDEBUGSTOP1的清空机制确保了恢复执行时会重新取指i5此时仍是MDEBUGSTOP1由调试器在单步前瞬间换回原指令从而精确地在断点处中断和恢复。操作流程在代码中放置断点在CLA C代码中使用__mdebugstop();或者在汇编代码中直接插入MDEBUGSTOP1。然后编译、链接、下载程序到芯片。连接CLA核心在CCSCode Composer Studio的Debug视图中你需要像连接主CPU核心一样连接CLA核心。CLA会作为一个独立的调试目标出现。启用CLA断点在CLA的调试上下文中启用断点功能。触发任务运行通过外设中断、CPU执行IACK指令或在调试器手动写MIFRC寄存器来启动一个CLA任务。调试CLA会在MDEBUGSTOP1处暂停此时你可以查看和修改CLA的寄存器、内存。使用单步Step可以逐条指令执行注意CLA的单步是让流水线前进一个周期与C28x CPU的“执行完一条指令”有所不同。3.2 传统断点MDEBUGSTOP与注意事项MDEBUGSTOP是旧式断点指令它不会清空流水线。这意味着它必须作为代码的一部分被编译进去调试器无法动态插入。它通常用于更简单的调试场景或者与MDEBUGSTOP1结合使用。一个关键限制无论是MDEBUGSTOP还是MDEBUGSTOP1都不能放在延迟条件指令MBCNDD,MCCNDD,MRCNDD的前后三条指令之内。这是因为这些指令的执行与流水线的紧密耦合断点插入会破坏其执行逻辑导致不可预测的分支行为。编译器在遇到__mdebugstop()时会自动处理这个限制但如果你写汇编必须自己注意。3.3 调试死锁与恢复最头疼的问题CLA死循环导致CPU调试器锁死当CLA代码有bug比如一个条件永远为真的循环CLA会持续运行并占用程序总线取指。此时调试器通过CPU发起的对CLA程序空间的读访问会被阻塞导致CCS界面“卡住”无法暂停或查看状态。解决方案预防在开发初期CLA任务函数里先写一个简单的、有限次数的循环或者尽早加入MDEBUGSTOP1进行调试确保基本逻辑正确后再编写复杂算法。逃逸如果已经锁死不要慌张不要强行关闭CCS。首选软复位在CCS的调试工具栏找到并执行对CLA核心的软复位Soft Reset。这会将CLA的MCTL[SOFTRESET]位置1停止当前任务清空中断使能让CLA回到可控的初始状态同时通常不会影响主CPU的调试连接。次选硬复位或系统复位如果软复位无效再尝试对CLA的硬复位MCTL[HARDRESET]或整个芯片的系统复位。这会重置所有CLA寄存器但也会中断主CPU的运行。调试状态下的任务切换 当你在调试一个任务比如Task A时另一个任务Task B的请求可能到来。手册详细描述了两种情形MPC停在MSTOP且无任务挂起这是比较棘手的情况。如果单步执行到Task A的MSTOP任务结束指令此时没有其他任务挂起然后Task B中断来了。由于CLA处于调试停止状态Task B可能无法正常启动。可靠的方法是对CLA执行一次软复位重新配置MIER然后再触发并调试Task B。有任务挂起如果Task B在Task A执行到MSTOP之前就已挂起那么继续单步通过MSTOP后CLA会正常仲裁并启动Task B。实操建议在调试多任务CLA应用时尽量一次只使能并调试一个任务减少并发带来的复杂性。等单个任务稳定后再逐步添加其他任务。4. CLA流水线深度解析与优化实践CLA采用8级流水线F1, F2, D1, D2, R1, R2, EXE, W与C28x CPU类似。理解流水线对于编写高效、正确的代码至关重要特别是避免一些隐蔽的时序错误。4.1 关键流水线冲突与规避4.1.1 写后读Write-After-Read依赖这是CLA编程中最需要警惕的一点。CLA没有C28x CPU那样的硬件写后读保护机制。问题场景你向一个外设寄存器比如PWM的比较寄存器CMPA写入一个新值紧接着下一两条指令就要从另一个可能受此写入影响的寄存器比如同一个ePWM模块的状态寄存器读取状态。MMOV32 _EPwm1Regs.CMPA.half.CMPA, MR0 // 写入CMPA MMOV32 MR1, _EPwm1Regs.TZFLG // 读取故障标志在流水线中读操作R2阶段发生在写操作W阶段之前。这意味着读取标志时写入CMPA的操作可能尚未完成你读到的可能是旧状态。解决方案插入空操作MNOP或安排其他不相关的指令确保写操作完成后再读。通常插入2条MNOP指令是安全的因为写操作在W阶段完成而后续指令的读在R2阶段中间隔了EXE阶段。MMOV32 _EPwm1Regs.CMPA.half.CMPA, MR0 // 写 MNOP // 等待1个周期 MNOP // 等待2个周期 MMOV32 MR1, _EPwm1Regs.TZFLG // 读更优的做法是将后续不依赖于此次写操作的指令填充到这两个周期中提高效率。4.1.2 延迟条件指令MBCNDD/MCCNDD/MRCNDD这些是延迟分支/调用/返回指令。它们的特点是无论条件是否成立紧随其后的3条指令I5, I6, I7都一定会被执行。编程约束前面三条指令I2, I3, I4不能是MSTOP、MDEBUGSTOP或任何延迟条件指令。它们可以修改状态标志MSTF但这些修改不会影响当前这条延迟条件指令的判断因为条件判断发生在该指令自身的D2阶段而前面指令对标志的修改发生在更晚的EXE或W阶段。后面三条指令I5, I6, I7同样不能是MSTOP、MDEBUGSTOP或任何延迟条件指令。它们构成了“延迟槽”常用于填充有用的计算以隐藏分支带来的流水线停顿开销。示例与技巧 假设我们要根据比较结果跳转MMOV32 MR0, _AdcResult0 ; I1: 读取ADC结果 MBCNDD _Label_OverVoltage, GEQ ; 延迟条件分支指令 ; 以下三条指令无论如何都会执行 MMPYF32 MR1, MR1, MR2 ; I5: 可以做一些与分支判断无关但有用的计算 MADDF32 MR3, MR3, #1.0 ; I6: 例如累加一个计数器 MNOP ; I7: 如果没别的可做就放NOP ; 分支目标处或顺序执行处 _Label_Normal: ... _Label_OverVoltage: ...编译器在将C语言条件编译成CLA汇编时通常会处理好这些约束。但如果你进行手写汇编优化必须严格遵守。4.1.3 加载辅助寄存器MAR0/MAR1加载MAR0或MAR1例如MMOVI16 MAR0, #_Array发生在EXE阶段。但是使用间接寻址如MAR0导致的MARx后增发生在D2阶段。影响加载指令后的前两条指令I1, I2使用的仍是MARx的旧值。第三条指令I3不能使用MARx因为此时新值加载和旧值后增更新存在冲突。从第四条指令I4开始才能安全使用更新后的MARx值。MMOVI16 MAR0, #_X ; 加载新地址 #_X (假设20) 到MAR0原MAR050 MMOV32 MR0, MAR0 ; I1: 使用的是旧地址50的内容 MMOV32 MR1, MAR0 ; I2: 使用的仍然是旧地址50的内容 ; I3: 这里绝对不能使用 MAR0 或任何修改MAR0的指令 MMOV32 MR2, MAR0 ; I4: 安全使用的是新地址20的内容避坑指南在编写循环或数组操作时规划好MARx的加载位置。通会在循环开始前提前加载好MARx或者确保在加载指令和首次使用新地址的指令之间留有足够的指令间隔或插入NOP。4.2 ADC早期中断与CLA的极致响应这是展现CLA实时性优势的经典场景。ADC可以配置为在转换完成前几个周期就产生早期中断Early Interrupt来触发CLA任务。原理CLA任务被触发后需要4个系统时钟周期SYSCLK来取指并让第一条指令到达流水线的D2阶段。ADC转换需要N个SYSCLK周期。通过精确计算和配置ADCINTCYCLE寄存器可以让CLA任务的第一条读ADC结果的指令其R2阶段数据读取刚好对准ADC结果锁存到结果寄存器的那个时钟边沿。计算示例 假设ADC为12位模式ADCCLK SYSCLK/4转换需要10.5个ADCCLK周期即42个SYSCLK周期。目标让CLA在转换完成的那一周期第42周期读取结果。CLA中断响应延迟4周期触发到第一条指令D2。第一条读结果指令的R2阶段发生在该指令的第6个周期F1, F2, D1, D2, R1, R2。因此我们需要ADC的早期中断在转换完成前的(42 - 2) - 4 36个SYSCLK周期时发出。这里“-2”是因为从指令进入D2阶段到其R2阶段还需要2个周期R1, R2。配置将ADCINTCYCLE寄存器设置为36。这样ADC在转换开始后第6个ADCCLK周期即第24个SYSCLK周期发出中断CLA任务启动经过4周期延迟和指令流水刚好在第42个SYSCLK周期转换完成时执行到读结果指令的R2阶段实现“零等待”采样。优化价值这节省了传统上等待ADC转换完成、再触发中断、再启动任务、再取指执行所带来的数十个周期的延迟对于极高频率的控制环如200kHz的开关频率至关重要。4.3 并行指令榨干CLA的每一拍CLA支持强大的单周期并行指令如MADDF32 || MMOV32浮点加与数据移动并行或MMPYF32 || MADDF32浮点乘与加并行。这能将理论计算吞吐量翻倍。使用要点无流水线冲突并行指令的两个操作被视为一个整体没有特殊的对齐要求。注意操作数依赖在MMPYF32 || MADDF32 MR1, MR2, MR0中MADDF32使用的MR0是MMPYF32执行前的旧值而不是乘法的结果。因为两条指令是同时开始的。编译器支持CLA C编译器能够自动识别某些可以并行的操作并生成并行指令。但为了极致优化在关键循环中手动使用汇编和内联汇编来安排并行指令是常见做法。示例计算一个滤波器的迭代; 假设 MR0 x[n], MR1 x[n-1], MR2 coeff_a, MR3 coeff_b ; 计算 y a*x[n] b*x[n-1] MMPYF32 MR4, MR0, MR2 ; MR4 a * x[n] || MADDF32 MR4, MR4, MR3 ; 错误此处的MR4是乘法开始前的值(可能为0)不是结果正确的写法需要避免这种即时依赖MMPYF32 MR4, MR0, MR2 ; MR4 a * x[n] || MMOV32 MR5, MR1 ; MR5 x[n-1] MMPYF32 MR5, MR5, MR3 ; MR5 b * x[n-1] MADDF32 MR4, MR4, MR5 ; MR4 a*x[n] b*x[n-1]或者利用两次并行; 假设还有其它计算可以填充 MMPYF32 MR4, MR0, MR2 ; MR4_temp a * x[n] || MMOV32 MR6, _OtherData ; 并行加载其他数据 MMPYF32 MR5, MR1, MR3 ; MR5 b * x[n-1] || MADDF32 MR7, MR7, MR6 ; 并行进行其他累加 MADDF32 MR4, MR4, MR5 ; 最终求和5. 高级主题任务延迟、背景任务与实战示例分析5.1 CLA任务执行延迟分析了解任务切换的精确周期数有助于评估系统的最坏情况响应时间。新任务触发无背景任务运行从触发信号到任务第一条指令进入D2阶段需要8个周期。这包括了中断同步、取指等开销。新任务触发有背景任务运行需要9个周期。多出的一个周期用于在背景任务的流水线D2阶段强制插入一个MSTOP指令以终止它。从常规任务返回背景任务需要5个周期。用于恢复背景任务的上下文和程序计数器。注意如果背景任务正在执行不可中断的延迟条件指令MBCNDD等当新任务触发时CLA必须等待这些指令执行完毕至少3个周期这会进一步增加任务切换延迟。在编写对时间极其敏感的代码时应避免在背景任务中使用这类指令或者确保其执行时间可预测。5.2 背景任务Background Task的使用与考量背景任务Task 8是优先级最低的任务当没有其他任务运行时CLA会自动执行它。它适合处理非实时性的后台计算、状态估计等。启用背景任务需要在编译器选项中设置cla_background_task标志为on。这会使得编译器在每个常规CLA任务的开始和结束处自动插入上下文保存和恢复代码以便在背景任务被常规任务中断时能正确保存/恢复寄存器状态。性能权衡优点充分利用CLA的空闲时间实现“永远在线”的计算。缺点增加延迟常规任务的触发延迟会增加因为多了上下文保存/恢复的开销通常几条指令。增加代码大小每个任务都多了保存/恢复的代码。增加复杂度需要管理背景任务与常规任务之间的数据共享和同步。建议对于追求极致实时性能的应用如数字电源的峰值电流控制关闭背景任务让CLA只在被触发时才运行可以获得最短、最确定的任务响应时间。对于计算资源有富余且有一些低优先级计算如参数辨识、慢速滤波的应用可以开启背景任务。5.3 官方示例代码解读与移植要点TI的C2000Ware提供了丰富的CLA示例是极好的学习起点。以cla_ex5_adc_just_in_time.c即时ADC采样为例核心逻辑配置ADC使能早期中断并根据前述公式计算并设置ADCINTCYCLE。配置ePWM产生周期性的触发信号启动ADC转换。编写CLA任务任务开头读取ADC结果寄存器AdcResult.ADCRESULT0。由于配置了早期中断这条指令执行时结果刚好就绪。任务主体执行控制算法计算如PID。任务结尾将计算出的占空比写入ePWM的比较寄存器EPwm1Regs.CMPA并通知CPU任务完成通过消息RAM或中断。CPU端在CLA任务完成中断中可以准备下一周期的参考值等数据或者进行监控。移植到你自己项目的关键步骤复制工程框架从一个示例工程开始修改比从头创建要稳妥得多。修改链接器文件(.cmd)根据你的芯片型号和内存规划调整CLA程序RAM、数据RAM、消息RAM的映射地址和大小。替换算法核心将示例中的简单算法如增减PWM占空比替换为你自己的控制算法函数。调整外设配置根据你的硬件连接修改ADC通道、ePWM模块、GPIO引脚等配置。验证时序使用示波器或CCS的图形化工具测量从ADC触发到PWM更新的实际延迟确保满足你的控制周期要求。6. 常见问题排查与调试技巧实录即使按照指南操作实际开发中仍会遇到各种问题。下面是一些典型问题的排查思路。问题1CLA任务根本不启动。检查清单内存映射确认LSxMSEL和LSxCLAPGM位已正确设置并且CLA程序已成功加载到该内存区域。可以通过CCS Memory Browser查看CLA程序RAM内容是否正确。中断使能顺序是否在使能外设中断之前就配置并开启CLA的MIER是否清除了外设的悬挂中断标志触发源确认触发CLA任务的外设如ePWM、ADC已正确配置并运行。用示波器或CCS寄存器视图查看中断标志是否被置位。任务函数链接确认CLA任务函数如cla1Task1的地址是否正确填写到了CLA1TASK1VECT寄存器中消息RAM访问CLA任务的第一条指令是否试图访问一个未正确初始化或映射的消息RAM地址非法内存访问可能导致任务立即停止。问题2CLA任务运行一次后不再触发。可能原因任务结束时没有正确执行MSTOP指令或者没有清除CLA任务完成标志。每个CLA任务必须以MSTOP指令结束。在C语言中编译器会自动添加。在汇编中你必须自己写。排查检查任务结束处的汇编代码确认有MSTOP。同时在CPU的中断服务程序中需要清除PIE中对应的CLA任务中断标志位以响应下一次中断。问题3CLA计算的结果时对时错尤其是涉及数组循环时。首要怀疑流水线冲突特别是写后读和MARx加载后立即使用的问题。调试方法在可疑的写操作尤其是对外设寄存器的写后面插入2个MNOP看问题是否消失。检查数组操作中MAR0/MAR1的加载和使用间隔是否符合“加载后隔3条指令再使用”的规则。使用MDEBUGSTOP1单步执行观察每次执行时相关寄存器和内存值的变化定位具体是哪条指令读到了错误的数据。问题4系统运行一段时间后CLA似乎“死”了但CPU还正常。可能原因CLA遇到了非法指令Illegal Opcode。这可能是由于程序指针MPC跑飞指向了非代码区或数据区取到了非法的操作码。CLA的行为遇到非法指令时CLA会在该指令的D2阶段停止就像遇到断点并触发对应的任务中断同时MIRUN位保持置位。排查检查CLA的MPC寄存器看它停在哪里。检查该地址的内存内容确认是否是有效的CLA指令。检查数组越界、指针错误等问题。问题5启用背景任务后系统实时性变差。量化分析使用CCS的Profile或Cycle Counter功能测量常规任务的执行时间从触发到MSTOP以及任务间隔时间。对比关闭背景任务时的数据。优化减少背景任务的计算量或执行频率。确保背景任务中不使用MBCNDD等长延迟指令。如果实时性要求严格考虑关闭背景任务将后台计算移到CPU的空闲时间或更低优先级的任务中。调试技巧使用CCS的CLA寄存器视图和反汇编窗口寄存器视图CCS的Debug视图下可以添加CLA的专用寄存器组MCTL, MIER, MIRUN, MPC, MSTF等。监控MIRUN可以知道哪个任务正在运行监控MPC可以知道CLA执行到哪里。反汇编窗口将CLA的程序内存地址加载到反汇编窗口可以直观地看到CLA正在执行的机器码和对应的汇编指令对于排查非法指令、理解编译器生成代码、手动插入断点都非常有帮助。掌握CLA的调试和优化是一个从“能用”到“用好”的关键跨越。它要求开发者不仅关注算法逻辑更要理解底层硬件的运行机制。通过系统地实践初始化流程、熟练运用软件断点、深刻理解流水线约束并善于利用官方示例和调试工具你就能充分发挥这颗协处理器的强大威力构建出响应更快、更稳定的嵌入式实时控制系统。