MSPM33 DMA控制器:从基础模式到高级应用全面解析

发布时间:2026/7/24 3:28:48
MSPM33 DMA控制器:从基础模式到高级应用全面解析 1. MSPM33 DMA控制器从基础到高级应用的全面解析在嵌入式系统开发中尤其是面对实时数据采集、高速通信或图形刷新这类任务时CPU如果被频繁的数据搬运操作所拖累整个系统的响应速度和效率就会大打折扣。这时直接内存访问DMA就成了我们的“救星”。它就像一个独立、高效的搬运工能在内存和外设之间直接搬运数据完全解放CPU。德州仪器TI的MSPM33系列微控制器集成了一个功能强大的DMA控制器其设计之灵活、功能之丰富远超许多同类产品。今天我就结合自己多年的嵌入式开发经验带大家深入拆解MSPM33的DMA控制器不仅讲清楚它的四种基础传输模式和三种扩展模式更会分享如何通过寄存器配置、通道级联和中断优化在实际项目中榨干它的性能潜力。无论你是刚接触DMA的新手还是想深入了解高级用法的老手这篇文章都能给你带来实实在在的收获。2. DMA核心架构与工作模式深度剖析MSPM33的DMA控制器并非一个简单的数据搬运通道而是一个高度可配置、支持多种工作模式的智能数据引擎。理解其架构是灵活运用的前提。2.1 通道类型基础与全功能通道的差异首先MSPM33的DMA通道并非千篇一律它分为基础通道BASIC和全功能通道FULL两种类型。这个区分直接决定了你能使用哪些高级功能。基础通道BASIC通常用于简单的、一次性的数据传输任务。它支持单次传输Single Transfer和块传输Block Transfer模式。你可以把它理解为“标准版”搬运工能完成从A点到B点的搬运工作但缺乏一些自动化脚本能力。全功能通道FULL这是“专业版”搬运工。除了支持基础通道的所有功能外它还额外支持重复单次传输Repeated Single Transfer和重复块传输Repeated Block Transfer模式。更重要的是只有全功能通道才能使用扩展模式Extended Modes如填充模式Fill和表模式Table。在项目规划初期你就需要根据数据流的复杂程度合理分配哪些外设如ADC、SPI使用全功能通道哪些使用基础通道。注意具体到你的MSPM33型号哪些通道是BASIC哪些是FULL务必查阅对应型号的器件数据手册Datasheet。错误地将需要重复传输或扩展模式的任务配置到基础通道上会导致功能无法实现。2.2 核心寄存器组与数据传输三要素无论哪种通道其数据传输行为都由一组核心寄存器控制理解它们就如同掌握了搬运工的“工作清单”DMASASource Address数据从哪里来。可以是内存地址如数组首地址也可以是外设数据寄存器地址如UART-RXD。DMADADestination Address数据到哪里去。与源地址对应可以是内存或外设寄存器。DMASZTransfer Size要搬运多少“件”数据。这里的“件”数由数据宽度DMASRCWDTH/DMADSTWDTH决定。例如设置DMASZ100且数据宽度为32位WORD则表示要搬运100个32位数据总计400字节。地址增量与数据宽度这是配置中最容易出错的地方之一。DMASRCINCR和DMADSTINCR控制每次传输后地址是递增、递减还是不变。而DMASRCWDTH和DMADSTWDTH则定义了单次传输操作的数据宽度8/16/32/64/128位。关键点在于地址的增减步长是“数据宽度”的倍数。例如当DMADSTWDTH232位字且DMADSTINCR3递增时每完成一次传输目标地址DMADA实际会增加4个字节1 * 4字节。如果设置成DMADSTINCR8步幅2则每次地址增加8字节2 * 4字节。务必在脑中建立“传输次数 * 数据宽度 总字节偏移量”这个等式才能正确访问连续的内存区域。3. 四大基础传输模式详解与实战配置传输模式由DMACTL寄存器中的DMATM位域控制。选择哪种模式完全取决于你的应用场景。3.1 单次传输模式DMATM 0这是最简单直接的模式。每次外设产生一个触发信号如UART收到一个字节DMA就执行一次数据传输搬运一个DMASRCWDTH定义宽度的数据。传输完成后DMASZ减1。当DMASZ递减到0时DMAEN位会自动清零传输停止并可产生中断如果使能了。适用场景非常适合非周期性的、低速的或事件驱动的数据传输。例如一个手动按钮按下后通过DMA搬运一组配置参数到某个外设。配置要点设置DMATM 0。配置DMASRCINCR/DMADSTINCR决定地址是否变化。写入DMASZ传输总数。使能DMAEN1。此后每一个外部触发都会引起一次传输直到DMASZ为0。3.2 块传输模式DMATM 1在此模式下一个触发信号会启动一连串的、不间断的数据传输直到完成DMASZ所定义的整个数据块。传输过程中DMASZ实时递减指示剩余传输次数。块传输完成后DMAEN位同样会被自动清除。适用场景这是最常用的模式之一用于搬运一段连续的数据。例如从ADC的采样结果缓冲区一组连续内存地址一次性搬运256个采样点到另一个处理缓冲区。实战配置示例从ADC结果寄存器搬运100个16位采样值到数组// 假设ADC结果寄存器地址为0x400CB000目标数组为uint16_t adc_buffer[100] DMACTRLx.DMATM 1; // 块传输模式 DMACTRLx.DMASRCINCR 0; // 源地址ADC寄存器固定不变 DMACTRLx.DMADSTINCR 3; // 目标地址数组每次递增 DMACTRLx.DMASRCWDTH 1; // 源数据宽度16位HALF-WORD DMACTRLx.DMADSTWDTH 1; // 目标数据宽度16位 DMASAx 0x400CB000; // 源地址ADC结果寄存器 DMADAx (uint32_t)adc_buffer[0]; // 目标地址数组首地址 DMASZx 100; // 传输100个数据 DMACTRLx.DMAEN 1; // 使能DMA通道 // 配置ADC在采样完成后触发DMA通过DMATCTLx.DMATSEL一旦ADC完成一次采样并触发DMADMA控制器就会“一口气”将100个采样值从ADC寄存器搬到adc_buffer数组中期间完全不需要CPU干预。3.3 重复单次传输模式DMATM 2这是全功能通道独有的模式。其核心特点是“可持续性”。在此模式下DMAEN位在传输过程中始终保持为1。DMASA、DMADA和DMASZ的初始值会被复制到临时寄存器中。每次触发执行一次传输DMASZ减1地址根据配置增减。当DMASZ减到0时硬件会自动从临时寄存器中重新加载初始值并置位相应的RIS标志位然后通道继续等待下一个触发。整个过程DMAEN不会被清除。适用场景需要循环处理固定大小数据缓冲区的“乒乓缓冲Ping-Pong Buffer”机制。例如音频流处理一个缓冲区正在被DMA填充从I2S接收另一个缓冲区正在被CPU处理解码、滤波。当DMA填满缓冲区A并触发中断DMASZ0时CPU开始处理A同时DMA自动重置参数开始填充缓冲区B。重要限制在重复单次传输模式下不能通过清除再使能DMAEN来暂停和继续传输。因为DMAEN始终保持有效这种操作会导致不可预知的行为。如果需要暂停应考虑使用触发控制或切换模式。3.4 重复块传输模式DMATM 3同样是全功能通道专属。它与重复单次传输模式类似区别在于每次触发传输整个数据块DMASZ定义的数量。一个块传输完成后DMAEN保持为1DMASA、DMADA、DMASZ自动重载等待下一个触发信号开始下一个块的传输。适用场景需要定期、批量更新数据的场景。比如一个LED点阵屏的显示缓存刷新。你可以将一帧显示数据作为一个块由定时器定期触发DMA将数据从内存搬运到显示驱动器的数据端口。每次触发整帧数据被送出DMA参数自动重置准备下一次刷新。模式选择心法单次 vs 块问自己“一个事件需要搬一个数据还是一批数据”单次/块 vs 重复单次/重复块问自己“数据流是单次任务还是持续不断的循环任务任务完成后参数是否需要自动重置”重复单次 vs 重复块问自己“循环的节奏是由单个数据到达事件驱动还是由定时器等周期性事件驱动一批数据”4. 高级功能与扩展模式实战MSPM33 DMA的威力很大程度上体现在其扩展模式和高级控制特性上。4.1 步幅模式数据重排的利器步幅模式并非一个独立的DMAEM模式而是内置于地址增量控制DMASRCINCR/DMADSTINCR中的一个特性。当我们将增量值设置为STRIDE_n例如8h代表STRIDE_2Ch代表STRIDE_6时地址的增量不再是简单的1倍数据宽度而是n倍。经典应用案例解交织数据假设一个外部ADC通过SPI发送数据每帧6个16位字Word但你希望将这6个字分别存放到6个不同的数组中例如6通道采集。原始数据流是[ch1_sample1, ch2_sample1, ch3_sample1, ch4_sample1, ch5_sample1, ch6_sample1, ch1_sample2, ch2_sample2...]。// 目标将数据解交织到6个单独的数组 ch_data[6][BUFFER_SIZE] uint16_t ch_data[6][100]; // 配置DMA以通道0为例搬运ch1的数据 DMACTL0.DMADSTINCR 3; // 目标地址正常递增1*宽度 // 源地址SPI数据寄存器需要跳跃式前进 DMACTL0.DMASRCINCR 0xCh; // STRIDE_6即每次传输后源地址增加 6 * 2字节 12字节 DMACTL0.DMASRCWDTH 1; // 16位数据 DMACTL0.DMADSTWDTH 1; DMASA0 (uint32_t)SPI-RXBUF; // 源SPI接收寄存器 DMADA0 (uint32_t)ch_data[0][0]; // 目标通道1的数据数组 DMASZ0 100; // 搬运100个点 // 需要为其他5个通道配置额外的DMA通道它们的源地址初始偏移分别为2,4,6,8,10字节并采用相同的STRIDE_6设置。通过巧设步幅DMA硬件层面就完成了复杂的数据重组CPU无需再进行繁琐的解码计算极大提升了效率。4.2 填充模式DMAEM 2快速内存初始化填充模式将DMASA寄存器的内容作为填充数据Pattern写入到DMADA指定起始地址的一片连续内存区域中。此时DMATM被忽略强制使用块传输逻辑。应用场景显存清零快速将LCD显示缓冲区填充为0黑屏或0xFFFF白屏。初始化数组将某个数组全部初始化为特定的值如-1或0。生成序列通过设置DMASRCINCR和DMASRCWDTH可以让填充数据本身在每次写入后递增或递减从而快速填充一个等差数列的内存区域。例如设置DMASA0DMASRCINCR3递增DMASRCWDTH232位则可以快速填充0, 1, 2, 3...的序列。配置示例用0xAA55AA55填充1KB内存区域DMACTLx.DMAEM 2; // 使能填充模式 DMACTLx.DMASRCINCR 0; // 填充数据保持不变 DMACTLx.DMASRCWDTH 2; // 源数据即填充值宽度为32位 DMACTLx.DMADSTINCR 3; // 目标地址递增 DMACTLx.DMADSTWDTH 2; // 目标宽度32位 DMASAx 0xAA55AA55; // 填充模式下的“源地址”寄存器存放的是填充数据 DMADAx (uint32_t)target_memory_base; DMASZx 256; // 1024字节 / 4字节每传输 256次传输 DMACTLx.DMAEN 1; DMACTLx.DMAREQ 1; // 软件触发4.3 表模式DMAEM 3外设寄存器批量配置神器表模式是MSPM33 DMA一个极具创新性的功能。它允许DMA读取一个在内存中预先定义好的“地址-数据”对表格然后自动将数据写入对应的地址。这相当于让DMA执行了一段简单的“存储”指令序列。工作原理你需要在内存在创建一个对齐到64位8字节的表格。每个表项占64位其中低32位是一个目标地址高32位是要写入该地址的数据。配置DMA为表模式DMAEM3并设置DMASRCWDTH364位模式DMADSTWDTH232位模式。将表格的起始地址写入DMASA。DMA启动后会从表格中读取一个64位值将高32位数据写入低32位指定的地址。然后根据DMASRCINCR移动到下一个表项直到DMASZ递减为0。应用场景系统上电时需要快速配置多个不同地址的外设寄存器。传统方法需要CPU写多条存储指令而使用表模式只需DMA一次触发即可完成所有配置速度极快且不占用CPU时间。表格构建示例内存中地址 数据 (64位高32位为数据低32位为地址) 0x2000_1000: 0x00000001 (数据) | 0x4000_1000 (地址) // 向GPIOA数据寄存器写1 0x2000_1008: 0x0000_00FF | 0x4000_1004 // 向GPIOA方向寄存器写0xFF 0x2000_1010: 0x0000_0055 | 0x4000_2000 // 向UART控制寄存器写0x55DMA会依次执行向0x40001000写入1向0x40001004写入0xFF向0x40002000写入0x55。5. 通道级联、优先级与中断机制精讲5.1 通道级联构建自动化数据流水线通道级联功能允许一个DMA通道的完成事件自动触发另一个DMA通道开始工作。这是通过设置DMATCTLx.DMATINT1并将DMATSEL设置为另一个通道的编号来实现的。实战案例数据采集、搬运与校验流水线设想一个应用UART接收数据存入SRAM缓冲区Channel 0接收完成后自动触发CRC计算Channel 1CRC计算完成后再触发一个传输将结果送出Channel 2。// 通道0UART RX - SRAM Buffer DMATCTL0.DMATSEL UART_RX_TRIGGER; // 触发源为UART接收 DMATCTL0.DMATINT 0; // 外部触发 // 通道1SRAM Buffer - CRC引擎 (由通道0完成触发) DMATCTL1.DMATSEL 0; // 触发源选择为“通道0完成” DMATCTL1.DMATINT 1; // 内部通道触发 // 配置通道1的源地址为缓冲区目标地址为CRC数据寄存器 // 通道2CRC结果寄存器 - 发送缓冲区 (由通道1完成触发) DMATCTL2.DMATSEL 1; // 触发源选择为“通道1完成” DMATCTL2.DMATINT 1; // 配置通道2的源地址为CRC结果寄存器目标地址为发送缓冲区这样只需要启动UART接收后续的搬运、计算、发送全部由DMA自动串联完成CPU仅在最终需要处理结果时才被中断实现了极高的效率。5.2 优先级与轮询调度当多个DMA通道同时有请求时需要仲裁机制。MSPM33 DMA支持两种优先级策略固定优先级通道号越小优先级越高DMA0 DMA1 ...。这是默认模式ROUNDROBIN0。轮询优先级ROUNDROBIN1时启用。最近完成传输的通道其优先级会被降到最低。这保证了所有活跃通道都能公平地获得服务时间避免低编号通道“饿死”高编号通道。突发块模式Burst Block Mode这是一个非常实用的特性由DMAPRIO.BURSTSZ配置。在块传输模式下DMA并非一定要完成整个块才释放总线控制权。你可以设置突发大小如8、16、32次传输。DMA每完成一个突发大小的传输就会暂停当前通道重新评估所有道的优先级让更高优先级的通道得以执行。这极大地改善了高优先级、小数据量传输的实时响应性避免了低优先级的大块传输长时间阻塞总线。5.3 中断与事件系统深度优化MSPM33 DMA的中断系统设计得非常精细。核心中断逻辑每个通道都有一个原始中断状态位RIS中的DMACHx当该通道的DMASZ计数到0时此位被置1。中断掩码寄存器IMASK用于控制哪些通道的中断能上报。被掩码允许的中断会反映在屏蔽中断状态寄存器MIS中并最终汇集到一个单一的中断向量。CPU进入中断服务程序后需要读取中断索引寄存器IIDX。这个寄存器的值直接指示了当前最高优先级的、已发生的、且被使能的中断源编号如0x01代表DMA通道0完成。读取IIDX的操作会自动清除对应的RIS和MIS位这是硬件自动完成的简化了软件清中断的步骤。预中断Pre-IRQ这是全功能通道的一个杀手级特性。通过设置DMAPREIRQ你可以让DMA在传输完成前还剩N个数据时提前产生中断。例如在Ping-Pong Buffer应用中设置DMAPREIRQ为缓冲区大小的一半可以在DMA填充完半个缓冲区时就通知CPU开始处理前半部分同时DMA继续填充后半部分。这实现了处理与传输的完美重叠几乎消除了缓冲区切换的延迟。事件发布与订阅除了传统的CPU中断MSPM33 DMA还集成了事件路由器。DMA可以作为事件发布者FPUB_1向其他外设发送事件如传输完成也可以作为事件订阅者FSUB_0/1接收来自其他外设的事件来触发自己。这为构建纯硬件触发的、无CPU干预的信号链提供了可能。例如ADC采样完成事件直接触发DMA搬运DMA搬运完成事件再直接触发DAC更新形成一个闭环的模拟信号处理链路。6. 关键寄存器配置指南与避坑要点理解了原理最终都要落实到寄存器配置上。这里针对几个关键寄存器分享一些手册上不一定写明但实践中至关重要的细节。6.1 DMACTL[j]控制寄存器的精妙配置DMACTL是每个通道的大脑其每个位域都需仔细斟酌。DMAAUTOEN自动使能这是一个提升易用性的功能。当设置为DMASZ写自动使能值3时你只需配置好DMASA、DMADA最后写入DMASZ硬件就会自动置位DMAEN如果通道是硬件触发或同时置位DMAEN和DMAREQ如果通道是软件触发。这能确保配置顺序正确避免先使能后写大小导致的误触发。但在调试阶段建议先关闭此功能手动控制使能以便于单步调试。DMAPREIRQ预中断仅在FULL通道有效。它的值代表“还剩多少次传输时产生中断”。注意它是在DMASZ递减过程中比较的。例如初始DMASZ100DMAPREIRQ4那么当DMASZ减到4时即已传输96个预中断标志PREIRQCHx会被置位。配置时务必确保DMAPREIRQ的值小于初始DMASZ否则永远不会触发。DMAREQ软件请求这是一个“瞬态”触发位。向该位写1会立即启动一次软件触发的传输如果DMAEN1且DMATSEL配置为软件触发。硬件会在传输启动后自动清除该位。因此在代码中不需要手动清除它。常见的错误是循环里不断写DMAREQ1这可能导致意外的多次触发。6.2 触发控制DMATCTL[j]与传输启停触发源选择DMATSEL必须在DMAEN0时修改此寄存器。如果在通道使能状态下修改可能会立即捕获到一个旧的、未处理的触发信号导致无法预测的传输。启停控制启动传输需要两个条件DMAEN1且 收到有效的触发信号硬件或软件。停止一个块传输可以通过清除DMAEN位来实现DMA会在完成当前这一次传输周期后停止。但请注意停止后若想恢复除了重新置位DMAEN还必须重新提供一个触发信号。对于单次传输由于其原子性无法在单次传输过程中被中断。6.3 地址与数据错误处理DMA控制器能检测到两种错误地址错误当尝试访问一个不存在的内存地址时触发。错误标志在RIS寄存器的ADDRERR位。数据错误当访问的存储单元如带ECC的SRAM或Flash发生奇偶校验或ECC错误时触发。错误标志在RIS寄存器的DATAERR位。重要提示DMA本身不进行内存保护范围的检查。如果你配置DMA向一个受保护的或只读的内存区域如Flash的某些段写入写入操作不会报地址错误但实际写入的数据会是0。这可能导致难以排查的数据丢失问题。因此在配置源地址和目标地址时必须由软件确保地址的有效性和访问权限。7. 低功耗模式下的DMA操作MSPM33 DMA的一个突出优点是它在低功耗模式下的活跃性。在SLEEP模式CPU停止外设时钟运行和STOP等低功耗模式下DMA控制器在特定条件下仍然可以工作。RUN模式全功能运行无限制。SLEEP模式CPU时钟关闭但外设时钟如DMA、UART、ADC的时钟可能仍在运行。只要触发DMA的外设能在该模式下工作例如由运行在低功耗时钟下的定时器触发DMA传输就可以照常进行。这是实现“CPU休眠DMA干活”超低功耗数据采集的关键。STANDBY等更深睡眠模式通常需要所有高速时钟都关闭DMA无法运行。具体支持情况需查阅芯片参考手册的电源管理章节。设计策略在电池供电应用中可以设计让CPU大部分时间处于SLEEP模式由低速定时器周期性唤醒ADC进行采样ADC采样完成触发DMA将数据存入RAM。仅当RAM缓冲区满时DMA传输完成中断才唤醒CPU进行批量处理。这样可以最大化降低平均功耗。8. 实战配置一个完整的UART DMA接收回环示例让我们用一个具体的例子把上面的知识点串起来。目标使用DMA通道0实现UART接收将数据存入缓冲区收满一定数量后使用DMA通道1自动将数据回环发送出去。步骤1外设与DMA基础配置// 1. 启用外设时钟此处为示意具体寄存器请参考手册 SYSCTL-CLOCK_ENABLE | (CLK_UART0 | CLK_DMA); // 2. 配置UART波特率等参数略 // ... // 3. 配置UART以DMA模式接收 UART0-CTRL | UART_RX_DMA_ENABLE; // 4. 配置DMA通道0UART RX - Buffer DMACTL0.DMATM 1; // 块传输模式 DMACTL0.DMASRCINCR 0; // 源地址(UART数据寄存器)固定 DMACTL0.DMADSTINCR 3; // 目标地址(缓冲区)递增 DMACTL0.DMASRCWDTH 0; // 源数据宽度8位字节 DMACTL0.DMADSTWDTH 0; // 目标数据宽度8位 DMASA0 (uint32_t)UART0-RXDATA; // 源地址 DMADA0 (uint32_t)rx_buffer; // 目标地址 DMASZ0 RX_BUFFER_SIZE; // 缓冲区大小 DMACTL0.DMAEN 1; // 使能通道 // 配置触发源为UART接收 DMATCTL0.DMATSEL UART0_RX_TRIGGER_ID; // 具体ID查表 DMATCTL0.DMATINT 0; // 5. 配置DMA通道1Buffer - UART TX由通道0完成触发 DMACTL1.DMATM 1; // 块传输模式 DMACTL1.DMASRCINCR 3; // 源地址(缓冲区)递增 DMACTL1.DMADSTINCR 0; // 目标地址(UART数据寄存器)固定 DMACTL1.DMASRCWDTH 0; // 8位 DMACTL1.DMADSTWDTH 0; // 8位 DMASA1 (uint32_t)rx_buffer; // 源地址 DMADA1 (uint32_t)UART0-TXDATA; // 目标地址 DMASZ1 RX_BUFFER_SIZE; DMACTL1.DMAEN 1; // 使能通道 // 关键触发源配置为内部通道0完成 DMATCTL1.DMATSEL 0; // 选择通道0作为触发源 DMATCTL1.DMATINT 1; // 内部通道触发 // 6. 可选使能通道0传输完成中断用于通知CPU一轮收发完成 IMASK | (1 0); // 使能通道0中断 NVIC_EnableIRQ(DMA_IRQn); // 使能DMA全局中断步骤2中断服务程序处理void DMA_IRQHandler(void) { uint32_t iidx DMA-IIDX; // 读取中断索引自动清除最高优先级中断标志 switch(iidx) { case 0x01: // DMA通道0完成 // 可以在这里处理接收到的数据或者只是简单地标志一轮完成 g_dma_rx_complete true; // 注意由于我们使用了通道级联发送是自动开始的无需在此启动发送。 // 如果需要重新开始下一轮接收需要重新配置DMASZ0和DMADA0如果用了循环缓冲区并确保DMAEN1。 // 对于重复块传输模式则无需重新配置。 break; // 可以处理其他通道中断... default: break; } }避坑总结地址对齐确保源和目标地址符合数据宽度的对齐要求如32位访问最好4字节对齐。非对齐访问可能引发硬件错误或性能下降。缓冲区溢出DMA不会检查目标地址的边界。确保DMASZ和地址增量设置不会导致写入超出缓冲区范围否则会覆盖其他数据造成系统崩溃。数据一致性在CPU和DMA共享的内存区域缓冲区上操作时要注意缓存一致性问题如果MCU有Cache。通常需要在DMA操作前执行缓存清理Clean在CPU读取DMA写入的数据前执行缓存无效Invalidate。中断风暴如果在一个高频率触发的DMA通道上使能了完成中断且中断服务程序处理时间过长会导致中断嵌套或丢失。对于高速数据流考虑使用预中断来提前处理或者使用轮询模式检查RIS寄存器而不是依赖中断。低功耗协调在进入低功耗模式前确认所有DMA传输都已完成或者你确实希望DMA在低功耗模式下继续工作。意外的DMA访问可能会阻止芯片进入更深的睡眠状态。通过以上从理论到实践的全面梳理相信你已经对MSPM33的DMA控制器有了深入的理解。它的功能丰富性使其不仅能胜任简单的数据搬运更能通过模式组合、通道级联和事件联动构建出高度自动化、高效能的数据处理流水线。花时间仔细研究并上手调试这些功能必将让你在嵌入式系统性能优化上游刃有余。