
前阵子调一块FPGA板子既要通过SPI从Flash里读固件又得用JTAG烧录和调试比特流。老办法是桌上摆两个调试器FTDI一个、J-Link一个换个环节就拔插一次。直到我把一根FT2232H的转接线同时跑起两套协议才发现之前完全是在给自己找麻烦——这颗芯片内置的MPSSE引擎天生就能在SPI、JTAG、I2C之间自由切换而且一个通道跑SPI、另一个通道跑JTAG互不干扰。这篇文章就聊聊我是怎么把FT2232H从SPI一路切换到JTAG并附上自己整理好的配置代码和实测中踩过的坑适合手里正好有FT2232H开发板、又需要在多个调试接口之间切换的硬件工程师。1. FT2232H凭什么一芯多能MPSSE不是简单的IO翻转1.1 从多根调试线到一根USB线FT2232H的定位FT2232H是FTDI的双通道高速USB转接芯片单芯片支持两个独立的物理通道每个通道都能通过EEPROM配置成UART、FIFO、SPI、JTAG等多种模式。它最大的卖点不是“USB转串口”而是内部那个叫MPSSE的引擎。MPSSE全称Multi-Protocol Synchronous Serial Engine说人话就是一个专门生成同步串行时序的硬件状态机。主机的CPU只需要告诉它“把哪根线赋成什么电平、往哪根数据线上输出几个bit、时钟怎么跑”剩下的波形生成全部由芯片自动完成不再需要CPU参与每一个bit的翻转。这个设计和用普通IO口做软件SPI完全不同软件SPI每个bit都要CPU去置位/复位引脚频率上到一两兆就很难稳定MPSSE则是先把命令打包发给芯片芯片内部按命令在硬件层生成时序FT2232H的基础时钟是60MHz实测在5MHz、10MHz甚至20MHz下都能稳定输出。很多开源调试器和FPGA下载器都用FT2232H正是看中它“一根USB线、多种协议”的能力。我自己最初也是拿它当普通串口用后来才发现两个通道都可以配置成MPSSE这才是这颗芯片真正值钱的地方。1.2 双通道如何真正做到“同时跑两套协议”FT2232H的A通道引脚是ADBUS0-7B通道引脚是BDBUS0-7两套引脚完全独立。通过FT_Prog这个上位机工具可以把A通道配置为MPSSEB通道配置为普通UART也可以把两个通道都配成MPSSE。我实际项目里就是这么干的A通道开JTAG接FPGAB通道开SPI接SPI Flash。同一根USB线插在电脑上通过FTDI驱动区分接口0和接口1。上位机这边的感觉就是两个独立的设备但物理上我只用了一个FT2232H模块。这在调试多协议目标板时非常方便不用再准备一堆USB转接器。有一点必须提前说清楚两个通道虽然逻辑独立但底层共用同一个USB总线总吞吐率是有上限的。如果A通道跑20MHz SPI的同时B通道跑10MHz JTAG实际带宽会被USB 2.0 High-Speed480Mbps限制虽然大多数调试验证场景根本跑不满但不要指望两通道都能各自跑满最大时钟。1.3 什么情况该选FT2232H什么时候没必要如果你的需求只是“USB转串口”FT2232H有点浪费FT232RL更便宜。但如果涉及到多协议调试FT2232H的价值就体现出来了需要同时连接目标板的SPI和JTAG接口且希望用一根USB线解决需要写FPGA/CPLD的SPI Flash又需要用JTAG在线调试逻辑需要产线工具支持多型号芯片协议可以动态切换想学透MPSSE原理做自己的协议转换工具。相比FT232H单通道MPSSE、FT4232H四通道但高速模式有差异FT2232H双通道都支持完整MPSSE是最均衡的选择。如果预算有限而且只用单协议FT232H也能跑但本文的“双通道分流”思路就不成立了。2. 拿到板子先做三件事EEPROM配置、引脚映射、电平确认2.1 先用FT_Prog把通道切到MPSSE否则后面全是白搭很多FT2232H开发板出厂默认两个通道都是UART模式。如果直接调FT_SetBitMode去切MPSSE你会遇到一个典型的诡异现象FT_Write返回正常但引脚上就是没有时钟波形逻辑分析仪看过去一片死静。我第一块二手板就是这样折腾了半小时才发现EEPROM还是UART模式MPSSE命令全被当成串口数据丢掉了。正确的启动流程上FTDI官网下载FT_Prog工具插上板子用Devices - Scan and Parse扫描到FT2232H在左侧设备树找到Hardware Specific节点把Channel A和Channel B的Mode从UART改成MPSSE其他参数保持默认点闪电图标写入EEPROM拔掉USB线重新上电让FT2232H重新枚举再用FT_Prog读一遍EEPROM确认Mode确实变成了MPSSE。这里有个坑FT2232H的EEPROM写入后如果配置里有非法参数可能造成设备无法正常枚举。遇到这种情况先在FT_Prog里用Devices - Blank Check擦掉EEPROM再写入全FF模板恢复出厂状态然后再重新配置。我之前有一次把两通道都配成奇怪的FIFO模式结果Windows直接识别不了设备用这个办法救回来的。EEPROM模式是持久化保存的区分于FT_SetBitMode的临时切换。即使你在代码里调了FT_SetBitMode(handle, 0, 0x02)如果EEPROM不允许MPSSE模式仍然无法进入。所以第一件事永远是先确认EEPROM。2.2 引脚映射SPI和JTAG共用ADBUS0-3但含义完全不同FT2232H的MPSSE引脚映射在不同开发板上并不完全统一一定要先看原理图。最常见的标准映射如下表功能ADBUS0ADBUS1ADBUS2ADBUS3SPISCKMOSIMISOCS软件控制JTAGTCKTDITDOTMS注意看SPI和JTAG在物理上共用ADBUS0-3这四根脚但每个引脚的角色完全变了。很多转接板的丝印是按JTAG命名的比如把ADBUS2标成TDO而你接SPI的时候要把它当MISO用。反过来也容易踩坑你以为ADBUS1是MOSI结果板子丝印标的是TDI。更麻烦的是ADBUS4-7在不同设计里可能有额外用途。标准JTAG定义里可选TRST和SRST很多FT2232H参考设计把ADBUS4分配给TRST、ADBUS5分配给SRST。如果你的转接板是这样直接用JTAG时这两个脚可能有特殊功能。拿不准的时候先翻原理图或者用万用表测引脚静态电平再决定接不接。2.3 电平问题最容易把目标板打挂FT2232H的IO电平由VCCIO引脚供电决定。大多数转接板出厂默认VCCIO接3.3V如果你的目标板是1.8V系统比如某些新式FPGA和低功耗MCU直接把FT2232H接上去TDO/TMS上的3.3V电平很可能把目标板IO引脚钳位出问题轻则通信失败重则损坏引脚。检查VCCIO电压很简单板子通电后用万用表量ADBUS0的静态高电平如果接近3.3V那VCCIO就是3.3V。如果目标是1.8V系统最好是找带VCCIO跳线的转接板把VCCIO改成和目标板一致。若目标板是5V系统强烈建议加电平转换芯片TXS0108E或SN74LVC4245直接硬接有风险。我见过不少人因为“以防万一”把SPI Flash和FPGA都接在一起导致FT2232H的IO和FPGA的Bank电压冲突JTAG扫描死活过不了。排查到最后就是VCCIO不匹配。2.4 线材和接地的两个小细节除了芯片本身物理连接也会影响稳定性。两点教训第一调试线尽量短。SPI和JTAG都是同步串行协议信号线超过20cm就容易出现过冲和振铃我当时用30cm杜邦线跑到10MHz时SCK上升沿有接近2V的过冲后面降到5MHz才稳定。短接线不只是玄学是实打实影响时序质量。第二FT2232H和目标板必须共地。有一次我只接了信号线忘了接GND片上GPIO输出电平看着正常但通信总是随机失败接上GND后立刻恢复。原因很简单没有共地参考电平不一致接收端采样就乱了。3. MPSSE命令机制一段字节流如何变成完整时序3.1 为什么MPSSE不提供现成的SPI/JTAG APIFTDI没有把“读SPI Flash”封装成一个现成API新手第一次接触MPSSE时容易懵“我明明发的是SPI命令为什么芯片不搭理我”答案在于MPSSE的定位它是一个灵活的协议引擎不是一个专用协议控制器。芯片只做一件事按照你提供的命令字节流生成对应的时钟和数据信号。至于这段时序是SPI还是JTAG完全由上位机软件根据协议规则来编排命令。这跟FPGA里的软件SPI很相似把时序通过状态机表达上层逻辑决定什么时候拉CS、什么时候送地址、什么时候采数据。MPSSE就是FT2232H里的那个状态机你只需要往它嘴里喂命令。3.2 命令集主线GPIO控制、字节时钟、位时钟MPSSE命令很多但按用途可以分成三条主线命令类型典型命令用途GPIO方向/值控制0x80/0x82新版、0x10/0x12旧版兼容设置ADBUS0-7方向手动拉高/拉低片选等字节时钟数据命令0x11/0x12/0x82等按字节连续产生时钟同时输出/接收数据位时钟数据命令0x4A/0x6A等按bit产生时钟适合JTAG的TMS状态切换GPIO方向命令后面跟两个字节第一个是方向掩码1表示对应位为输出0为输入第二个是输出值表示这8根脚初始的高/低电平。这就是软件片选的基础——通过GPIO命令把CS先后拉低、再拉高从而圈出一段完整的SPI传输窗口。字节时钟命令负责真正的数据搬运。以上升沿输出、下降沿输入的命令为例它后面跟两个字节长度计数再跟数据块。这里有个特别容易出错的设定长度计数是“实际字节数减一”。比如你要发2个字节数据长度字段应该写0x01而不是0x02。如果写错芯片不是多等一个字节就是少发一个字节通信直接错位。3.3 一个最简命令流示例读SPI Flash状态寄存器不依靠任何库直接用FTD2XX API发MPSSE命令流程大概长这样FT_HANDLE ftHandle; FT_Open(0, ftHandle); FT_ResetDevice(ftHandle, 0); FT_SetBaudRate(ftHandle, 115200); FT_SetBitMode(ftHandle, 0x0, 0x02); // 0x02 MPSSE mode DWORD dwWritten; unsigned char io_cmd[3]; // 设置ADBUS0/1/3输出ADBUS2输入CS初始高电平 io_cmd[0] 0x80; // 设置低字节方向FT2232H MPSSE io_cmd[1] 0x0B; // 二进制 00001011D0/D1/D3输出D2输入 io_cmd[2] 0x08; // D31CS空闲高 FT_Write(ftHandle, io_cmd, 3, dwWritten); // CS拉低开始传输 io_cmd[2] 0x00; FT_Write(ftHandle, io_cmd, 3, dwWritten); // 发送 0x05 读状态寄存器同时读回1个字节 unsigned char cmd[5], rx[1]; cmd[0] 0x82; // 时钟输出输入 cmd[1] 0x00; // 长度-1高字节 cmd[2] 0x00; // 长度-1低字节实际发1字节 cmd[3] 0x05; // SPI指令字Read Status Register FT_Write(ftHandle, cmd, 4, dwWritten); FT_Read(ftHandle, rx, 1, dwWritten); // CS拉高结束传输 io_cmd[2] 0x08; FT_Write(ftHandle, io_cmd, 3, dwWritten);这段代码里有几个值得注意的细节CS拉低和拉高之间是两条独立的USB命令意味着从CS有效到第一个SCK上升沿之间有一段USB传输延迟。这个延迟是“软件片选”独有的问题硬件片选不会有。好在多数Flash设备对CS建立时间要求不高但在高速模式下如果通信不稳定片选时序就是首要怀疑对象。理解了这段底层代码你就明白为什么很多人最终选择pyftdi或OpenOCD——不是底层命令学不会而是手工拼命令流太容易出错尤其涉及长度字段和多字节传输时封装的库能大大降低犯错概率。4. SPI模式实战配置从初始化到一次完整读写4.1 软件准备与初始化我推荐用pyftdi做快速原型验证。它是纯Python库跨平台底层封装了MPSSE命令上层提供了SpiController、JtagEngine这些友好接口。from pyftdi.spi import SpiController spi SpiController() # ftdi://ftdi:2232h/1 表示FT2232H的B通道 spi.configure(ftdi://ftdi:2232h/1, frequency1_000_000) flash spi.get_port(cs0, mode0, freq1_000_000) # 读JEDEC ID9F命令会返回3字节厂商和设备ID dev_id flash.exchange([0x9F], 3) print(Flash ID:, dev_id.hex())在配置URL里/0表示A通道/1表示B通道。A通道对应ADBUS0-7B通道对应BDBUS0-7。如果你用的转接板把Flash接在A通道记得把URL改成ftdi://ftdi:2232h/0。pyftdi的exchange函数会自动处理片选在传输开始前拉低CS结束后拉高CS。这个行为对普通读取足够但如果你要自己精确控制片选时序就要绕过SpiController直接用GPIO命令手动控制CS。4.2 一次完整的读Flash ID流程含CS控制细节结合手动GPIO控制和exchange你可以更清楚地看到“软件片选”和“硬件片选”的区别from pyftdi.gpio import GpioController from pyftdi.spi import SpiController gpio GpioController() gpio.configure(ftdi://ftdi:2232h/1) # ADBUS0/1/3输出ADBUS2输入掩码0x0B gpio.set_direction(0x0B, 0x0B) # CS初始高 gpio.write(0x08) spi SpiController() spi.configure(ftdi://ftdi:2232h/1, frequency10_000_000) flash spi.get_port(cs0, mode0, freq10_000_000) # 手动拉低CS gpio.write(0x00) # 发0x9F并读3字节 dev_id flash.exchange([0x9F], 3) # 手动拉高CS gpio.write(0x08)硬件片选由外设自动控制时序高度精确软件片选则靠CPU操作GPIO引脚优点是灵活缺点是CS建立时间、释放时间完全由软件决定。FT2232H没有真正的硬件片选自动控制功能所以这里用的只能是软件片选。好在通过USB命令的延迟在毫秒级以下绝大多数SPI外设都完全够用。在调这类代码时强烈建议用逻辑分析仪抓一下CS、SCK、MOSI、MISO四根线的波形。CS拉低后SCK应该出现整8*N个时钟周期MOSI上的数据在上升沿稳定MISO上的数据在下降沿被采样。如果看到CS低电平期间时钟数量不对多半是长度字段填错了。4.3 时钟极性与相位CPOL/CPHA怎么选SPI有四种工作模式由时钟极性CPOL和时钟相位CPHA控制。FT2232H的MPSSE命令在设计上允许你通过选择“上升沿输出”或“下降沿输出”来匹配不同SPI模式。pyftdi的mode参数直接对应标准SPI模式SPI ModeCPOLCPHA特征000空闲低电平上升沿采样101空闲低电平下降沿采样210空闲高电平上升沿采样311空闲高电平下降沿采样大多数SPI Flash芯片包括W25Q系列和GD25Q系列都支持mode 0和mode 3。我在实际调试中如果不知道目标器件支持哪种模式会先用mode 0试读回全0xFF或者全0x00时再切mode 3。用错模式的典型现象是SCK波形正常但读回的数据全是0xFF或者全部错位。这时候不要怀疑芯片坏了先检查CPOL/CPHA是否和datasheet一致。另外要注意MPSSE的默认空闲电平和模式选择直接相关切换模式后CS上的初始电平也可能需要同步调整。4.4 高频SPI调好的几个土办法如果要把FT2232H的SPI频率跑到10MHz以上有几个土办法非常有效在SCK、MOSI线上串联33Ω电阻可以明显减小振铃把杜邦线换成品线或者双绞线减少串扰在MISO线上对地加10pF电容滤掉高频毛刺保证目标板电源稳定SPI Flash供电电压波动会直接影响时序阈值。我在一块密集布局的FPGA开发板上调SPI时SCK过冲从2V降到0.6V就是靠串33Ω电阻和缩短地线做到的。这类问题用示波器看波形最快没有示波器就用逻辑分析仪至少能看出数据是否错位。5. 切到JTAG模式不是换几根线那么简单5.1 JTAG状态机与MPSSE的关系JTAG和SPI最大的区别在于JTAG不是简单的“发字节”就完事而是通过TMS引脚上的电平序列驱动目标芯片内部的TAP状态机在16个状态之间跳转。每个TCK上升沿采样一次TMSTMS高低电平决定下一个状态。也就是说你必须在正确的时刻、按正确的顺序把TMS引脚拉高或拉低。比如要进入Shift-IR状态TMS序列可能是“1-1-0-0”这段序列不按字节对齐而是按bit对齐。这正是MPSSE“位时钟命令”发挥作用的地方。理解这一点很重要FT2232H从SPI切到JTAG并不是在芯片内部按一下“切换开关”而是上位机换了一套完全不同的命令序列。硬件引脚可以共用但软件行为完全是另一套逻辑。这也是为什么OpenOCD这类工具要专门为FT2232H写驱动它内部处理的就是TMS/TDI/TDO的位级时序。5.2 从SPI切换到JTAG的软切换代码实际操作中同一个FT2232H频道从SPI切到JTAG不需要重新烧EEPROM也不需要重启设备。你只需要关闭当前的SPI控制器重新初始化FTDI设备然后让JTAG引擎接管。用pyftdi演示from pyftdi.ftdi import Ftdi from pyftdi.jtag import JtagEngine from pyftdi.spi import SpiController # 切换前先把SPI控制器的引用释放 spi SpiController() spi.configure(ftdi://ftdi:2232h/1, frequency1_000_000) # ... 做SPI操作 ... spi.terminate() # 重新打开同一通道作为JTAG ftdi Ftdi() ftdi.open(ftdi://ftdi:2232h/1, frequency3_000_000) jtag JtagEngine(ftdi) # 复位TAP状态机 jtag.reset() # 读IDCODE示例 idcode jtag.read_idcode() print(hex(idcode))这段代码的背后其实是重新设置了GPIO方向然后开始发送JTAG专用的TMS/TDI位时钟命令。pyftdi把这些细节全部封装了你不需要手动拼命令流但你必须理解“切换协议”的本质是切换命令序列而不是切换某个开关。如果你的工作需要频繁切换我的建议是不要反复开关控制器而是在同一个会话里封装好两组操作函数一组SPI读写一组JTAG操作在需要时按需调用。这样能够减少USB重枚举带来的不稳定因素。5.3 用OpenOCD验证切换是否成功OpenOCD是验证JTAG连接是否打通的可靠工具。FT2232H有官方接口配置打开OpenOCD后通常只需要指定source [find interface/ft2232h.cfg] transport select jtag adapter speed 1000如果你的转接板不是标准FT2232H原理图就可能需要覆盖ftdi_layout_init和ftdi_layout_signal。一个常见配置片段interface ftdi ftdi_vid_pid 0x0403 0x6010 ftdi_layout_init 0x0f08 0x0f2b ftdi_layout_signal nTRST -data 0x0010 ftdi_layout_signal nSRST -data 0x0020连接后执行reset halt和scan_chain如果能正确读到目标芯片的IDCODE就说明FT2232H到JTAG的链路已经打通。这是我最常用的验证手段比用示波器看波形更直接。OpenOCD常见的几个报错基本都对应明确的物理或配置问题报错信息常见原因error (209040): cant access jtag chain接线错误、电平不匹配、目标板没上电error (209053): unexpected error in目标芯片JTAG引脚被复用、芯片进入低功耗模式cant perform jtag flash, because openocd server is not running上位机工具没连上OpenOCD server先启动server再连swd/jtag communication failure目标板进入休眠、复位电路异常、时钟问题遇到209040时先用逻辑分析仪看TCK是否有波形再看TMS能不能被我方拉高拉低。如果TCK有波但TMS一点反应都没有基本可以确认是接线或电平问题而不是芯片问题。5.4 两个通道如何做到一个SPI一个JTAGFT2232H双通道的威力就在这里。你可以把A通道配给JTAG接FPGAB通道配给SPI接Flash两个通道同时在MPSSE模式下工作互不干扰。在pyftdi里用两个不同的URL同时打开即可from pyftdi.jtag import JtagEngine from pyftdi.ftdi import Ftdi from pyftdi.spi import SpiController # B通道做SPI spi SpiController() spi.configure(ftdi://ftdi:2232h/1, frequency10_000_000) flash spi.get_port(cs0) # A通道做JTAG ftdi Ftdi() ftdi.open(ftdi://ftdi:2232h/0, frequency3_000_000) jtag JtagEngine(ftdi) jtag.reset() # 现在可以一边读Flash一边调试JTAG flash_id flash.exchange([0x9F], 3) idcode jtag.read_idcode()这里要注意两个通道会争抢同一个USB带宽。实测中SPI跑10MHz、JTAG跑3MHz时没有明显卡顿如果两边都跑很高频率USB总线上可能出现调度延迟表现为偶发超时。工程上做产线工具时我一般把两个通道的总吞吐率控制在合理范围比如SPI降到5MHzJTAG用1MHz稳定性和速度都能兼顾。6. 实测踩坑记录与调优心得6.1 时钟频率上不去先查VCCIO和杜邦线我曾经在FT2232H上读一颗W25Q645MHz下一切正常一上20MHz就频繁读到乱码。用示波器看SCK波形上升沿振铃严重过冲接近2VMOSI数据线上也有明显毛刺。排查后发现转接板的VCCIO跳线接的是5V而目标板是3.3V系统。电平失配导致FT2232H输出驱动能力太强过冲更严重。把VCCIO跳到3.3V再在信号线上串了33Ω电阻20MHz稳定运行。MPSSE的理论最大时钟可达30MHz但实际能用多少完全取决于你的接线方式和目标芯片电气特性。不要一开始就追求高速先用低频率跑通功能再逐步提高直到出现误码然后再往回调一档。6.2 OpenOCD报JTAG chain错误先怀疑目标板引脚复用STM32和GD32这类MCU默认是支持JTAG的但如果你之前烧过程序把PA15、PB3、PB4这些JTAG引脚配置成了普通GPIO第二次再用JTAG连接就会发现OpenOCD报209040或者swd/jtag communication failure。解决思路是先用SWD或者ST-Link把芯片恢复成可调试状态去掉禁用JTAG的代码。GD32F4系列可以在代码里用GPIO_ConfigPinRemap(GPIO_REMAP_CTRL_SWJ_JTAG_DISABLE, ENABLE)把JTAG复用关闭但要注意操作之后JTAG引脚变成普通GPIO只能用SWD。如果是临时救急可以把Boot0拉高、复位进入系统Boot模式再用OpenOCD擦除固件。这个经验对用FT2232H调试STM32/GD32的人特别有用别一上来就怀疑FT2232H坏了先确认目标芯片的JTAG引脚当前到底是什么功能。6.3 关于软件片选我的一些理解FT2232H的MPSSE没有硬件自动片选功能CS时序完全靠GPIO命令控制。这带来一个好处你可以自由控制CS低电平的持续时间甚至可以在一次CS低电平期间完成“写命令写地址读数据”的全流程而不像某些硬件SPI控制器那样受限于FIFO长度。缺点是在高速模式下CS建立时间可能因USB调度产生抖动。我在写调试工具时习惯把CS控制的代码单独封装void spi_cs_low(FT_HANDLE h) { uint8_t buf[3] {0x80, 0x0B, 0x00}; DWORD w; FT_Write(h, buf, 3, w); } void spi_cs_high(FT_HANDLE h) { uint8_t buf[3] {0x80, 0x0B, 0x08}; DWORD w; FT_Write(h, buf, 3, w); }这样在调Flash扇区擦除这类需要长延时的操作时可以先拉低CS发指令然后做延时最后拉高CS逻辑非常清晰。6.4 配置代码的整理建议最后聊一下为什么很多人写了半天MPSSE代码还老是出问题我觉得关键问题在于没有把“协议”和“引脚”两层分开。MPSSE命令里GPIO方向、引脚电平是硬件层SPI命令、JTAG命令是协议层。切换协议时只需要切换协议层的命令生成逻辑但GPIO方向配置往往也要跟着变。我习惯把配置收敛成一个结构体typedef struct { uint8_t gpio_dir; uint8_t gpio_idle; uint8_t protocol; // PROTO_SPI / PROTO_JTAG } mpsse_config; const mpsse_config spi_config {0x0B, 0x08, PROTO_SPI}; const mpsse_config jtag_config {0x0F, 0x00, PROTO_JTAG};换板子时只需要改这个配置结构体协议函数本身基本不用动。这套封装我已经用在了产线测试工具里同一个FT2232H设备先通过SPI读取Flash固件版本再切到JTAG做在线烧录整个过程不需要拔USB线也不需要重启OpenOCD server既省时间又省心。如果你也准备用自己的FT2232H做类似的事情建议先从最小代码开始把SPI读Flash跑通再把JTAG用OpenOCD跑通最后才做协议切换。一次性上全功能出了问题很难定位是硬件还是软件还是配置的问题。一步步来你会发现这个看起来复杂的MPSSE其实也没有那么难搞定。