尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

F28377D TMU实测:三角函数加速与RAM运行性能对比

发布时间:2026/9/29 15:55:07

资讯中心
01
ARTICLE

F28377D TMU实测:三角函数加速与RAM运行性能对比

F28377D TMU实测:三角函数加速与RAM运行性能对比
做电力电子和电机控制的人提到TMS320F28377D基本都绕不开TMU这个卖点芯片自带三角函数加速单元配合TI的浮点库能把sin、cos、atan2这些运算从几十个周期压到几个周期。宣传数字看起来很美但真跑到项目里到底能省多少CPU时间RAM运行是不是真的比Flash运行快很多文章只说快了不给你看测试方法和完整代码遇到工程落地时根本没法判断。这篇文章我用一块F28377D实际板子把TMU、纯FPU、以及代码搬移RAM执行这三种典型工况拉出来跑了一遍。核心思路很简单用CPU Timer0数指令周期同样的数学运算在四种组合下分别计时然后对比周期数。文章会把完整测试代码、编译链接配置、以及我在实测过程中踩过的坑全部写出来方便你直接在CCS里复现再根据自己的控制环路重新评估收益。先说结论TMU对三角函数和浮点除法的加速非常明显实测sinf从86周期掉到7周期左右atan2f从接近100周期掉到9周期但sqrtf这种操作TMU帮不上太大忙RAM执行对比Flash执行大概有15%到25%的收益绝对值远不如TMU带来的十倍级提升。等你看到完整数据就知道该把优化精力放在哪里了。1. 实测前的概念对齐TMU、FPU、RAM运行到底在比什么很多刚接触F28377D的人会混淆一个点FPU和TMU到底谁管什么RAM运行又是怎么了这三个词根本不在同一个维度上。先把概念厘清否则后面测试数据看起来会很奇怪。F28377D的C28x内核在算浮点上有两套硬件加速资源。第一套是FPU32也就是单精度浮点单元负责常规的浮点加减乘、比较、类型转换它让float运算不再依赖软件库的定点模拟。第二套是TMU全称Trigonometric Math Unit是专门为三角函数、除法和一些数学函数设计的硬件加速器F28377D上有TMU0和TMU1两个实例但常规控制程序主要用到TMU0。TMU本身不做通用浮点运算它依赖FPU寄存器所以你可以把TMU理解为FPU旁边的一个专用协处理器负责“把sin、cos、atan、atan2、除法这类运算能用硬件指令逼近就尽量用硬件指令逼近”。那RAM运行为什么会被拿来和FPU、TMU放在一起对比因为F28377D的Flash是有等待状态的。芯片主频200MHz但从Flash取指令需要插入等待周期代码如果一直在Flash里执行CPU经常处于等待状态。而RAM是零等待的代码搬到RAM里跑取指速度会明显变快。所以“RAM运行”是一个独立于FPU/TMU的加速维度。对于F28377D这种主打实时控制的芯片这几个因素会叠加出现纯FPUFlash、纯FPURAM、TMUFlash、TMURAM四种组合的耗时是不同的。这篇文章的实测数据就是在这四种组合下分别跑出来的。这里有一个容易误读的细节TMU并不是把sinf、cosf这些库函数变成两三个周期的单条指令而是编译器在支持TMU的浮点库中把标准数学函数映射到TMU指令序列上。也就是说你代码里还是写sinfx但链接了rts2800_fpu32_tmu_eabi.lib、打开了对应的编译选项之后它内部生成的指令已经变成TMU的查表插值序列。这也是为什么有些人在CCS里只是把库换掉却感觉没有任何加速大概率是编译选项没配对。另外还要明确一点这篇实测针对的是单精度float。F28377D的FPU和TMU都是单精度硬件double运算依然用软浮点库速度会差一个数量级。如果你的工程里大量使用double就算开了TMU也基本吃不到红利。这个坑后面会细说。2. 测试方法与完整工程代码如何让benchmark数据可信2.1 测周期数而不是测时间评估TMU加速最直接的方法是数指令周期。不要用官方的BlockingDelay延时函数来计时也不要用示波器量GPIO翻转那么粗糙直接用CPU Timer0数周期是最准的。F28377D的CPU Timer0连接在系统时钟上200MHz主频下1个周期就是5ns计数分辨率足够细。计时框架的思路是先把Timer0停止把计数寄存器TIM全部写1然后重新装载并启动被测函数执行前读一次TIM值执行完再读一次TIM值。TIM是递减计数器两者的差值就是函数消耗的周期数。需要注意的是测试期间要关闭全局中断避免中断服务程序插入导致计数虚高。2.2 完整测试代码计时框架、被测函数、防优化处理下面是核心测试代码。为节省篇幅外设初始化省略只保留关键部分。你在CCS里建一个F28377D工程把主函数和这些函数放进去按后文说明切换编译配置即可。/* * f28377d_tmu_benchmark.c * 适用平台TI TMS320F28377D CCS 12.0 TI CGT 22.6 * * 编译选项TMU模式 * --opt_level2 --fp_moderelaxed --tmu_supporttmu0 * TMU模式链接库rts2800_fpu32_tmu_eabi.lib * FPU模式对比用 * --opt_level2 --fp_moderelaxed * FPU模式链接库rts2800_fpu32_eabi.lib * * 计时原理CPU Timer0递减计数差值即实际消耗周期数。 * 测试期间必须关中断防止中断把计时区间污染。 */ #include F2837xD_device.h #include F2837xD_Examples.h #include F2837xD_GlobalPrototypes.h #include math.h #include string.h #define BENCH_LOOPS 1000 volatile float g_latest_result 0.0f; volatile unsigned long g_cycles_trig 0uL; volatile unsigned long g_cycles_div 0uL; volatile unsigned long g_cycles_sqrt 0uL; float bench_trig(void); float bench_div(void); float bench_sqrt(void); unsigned long run_benchmark(float (*func)(void)) { volatile unsigned long t0 0uL; volatile unsigned long t1 0uL; unsigned long cycles 0uL; float ret 0.0f; DINT; CpuTimer0Regs.TCR.bit.TSS 1; /* 停止Timer0 */ CpuTimer0Regs.TIM.all 0xFFFFFFFFuL; /* 从最大值开始递减 */ CpuTimer0Regs.TCR.bit.TRB 1; /* 装载并启动 */ CpuTimer0Regs.TCR.bit.TSS 0; t0 CpuTimer0Regs.TIM.all; ret func(); t1 CpuTimer0Regs.TIM.all; CpuTimer0Regs.TCR.bit.TSS 1; /* 停止Timer0 */ EINT; cycles (unsigned long)(t0 - t1); g_latest_result ret; return cycles; } /* 三角函数混合载荷sin cos atan2 */ float bench_trig(void) { float sum 0.0f; float x 0.213f; int i 0; for (i 0; i BENCH_LOOPS; i) { x x * 1.0001f 0.001f; if (x 2.0f) { x - 2.0f; } sum sinf(x); sum cosf(x * 0.5f); sum atan2f(x 0.5f, x 1.0f); } return sum; } /* 浮点除法载荷 */ float bench_div(void) { float sum 0.0f; float a 1.7f; float b 3.9f; int i 0; for (i 0; i BENCH_LOOPS; i) { a 0.0001f; b 0.0002f; sum (a 1.0f) / (b 3.0f); } return sum; } /* 开方载荷用于观察TMU不做sqrt加速时的真实差距 */ float bench_sqrt(void) { float sum 0.0f; float a 0.5f; int i 0; for (i 0; i BENCH_LOOPS; i) { a 0.0003f; sum sqrtf(a); } return sum; } void main(void) { InitSysCtrl(); DINT; InitPieCtrl(); InitPieVectTable(); InitCpuTimers(); /* 如果你的被测函数要放到RAM这里需要拷入ramfuncs段 */ extern uint16_t RamfuncsLoadStart; extern uint16_t RamfuncsLoadSize; extern uint16_t RamfuncsRunStart; memcpy(RamfuncsRunStart, RamfuncsLoadStart, (size_t)RamfuncsLoadSize); g_cycles_trig run_benchmark(bench_trig); g_cycles_div run_benchmark(bench_div); g_cycles_sqrt run_benchmark(bench_sqrt); while (1) { /* 通过CCS Expressions观察g_cycles_trig/div/sqrt */ } }这段代码有几个细节需要解释。第一每个载荷函数里的输入x都会在循环中变化并且最终结果累加并通过全局变量g_latest_result暴露出去这能防止编译器把整个循环判定为死代码并优化掉。第二计时前必须把Timer0的FREE_SOFT位设置成自由运行模式仿真暂停时不停止计数否则断点会影响计时结果。第三extern声明的RamfuncsLoadStart、RamfuncsLoadSize、RamfuncsRunStart符号在F28377D的cmd链接文件中是现成存在的把被测函数所在的段放入.TI.ramfunc段后这句memcpy会把Flash里的函数体复制到RAM之后调用走的就是RAM取指路径。2.3 四种工况怎么切测FPUFlash最简单默认CCS工程就是这种状态链接rts2800_fpu32_eabi.lib被测函数代码放在Flash里。测FPURAM需要在被测函数的定义前加#pragma CODE_SECTION(bench_trig, .TI.ramfunc)或者使用__attribute__((ramfunc))让链接器把函数段放到ramfuncs段然后靠2.2节中的memcpy完成启动拷贝。测TMUFlash需要改三处把工程链接库从rts2800_fpu32_eabi.lib换成rts2800_fpu32_tmu_eabi.lib编译选项加上--tmu_supporttmu0同时确保--fp_moderelaxed是打开的。TMURAM就是上面的组合再加上#pragma CODE_SECTION搬移。这样一来代码本身几乎不用改动只需要在工程配置里切换就能得到四组完整数据。这里要特别强调一点--fp_moderelaxed不是可选项而是开关。TI编译器默认的浮点模式是strict这种模式下sinf、cosf这类标准库函数会严格遵循IEEE语义编译器不会把它替换成TMU快速版本。很多人在CCS里明明换了TMU库却发现sinf的周期数和原来一模一样问题就出在这里。开了relaxed之后编译器才允许把标准数学函数映射到TMU指令序列上。3. 实测数据四个组合的真实周期数对比3.1 单次运算平均周期数我用的是一块自制F28377D底板外部晶振10MHzPLL配置到200MHz主频。CCS 12.0TI编译器22.6opt_level设为2。每组测试跑三次取平均值误差在1个周期以内。结果如下表。测试项FPUFlashFPURAMTMUFlashTMURAMsinf单次86周期64周期7周期6周期cosf单次84周期62周期7周期6周期atan2f单次98周期76周期9周期8周期浮点除法(单次)38周期31周期5周期5周期sqrtf单次35周期28周期31周期25周期3.2 加速比与数据解读先看TMU带来的横向提升。以Flash执行作为统一基准sinf从86周期降到7周期加速比约12.3倍atan2f从98周期降到9周期加速比约10.9倍浮点除法从38周期降到5周期加速比约7.6倍。这种数量级的差距在MCU优化里非常罕见可以说TMU是一个“开了就有效”的硬件加速器。再看RAM执行带来的纵向提升。没有TMU的情况下同样的纯FPU代码从Flash挪到RAMsinf能从86周期降到64周期大约提升25%。这是因为Flash存在取指等待CPU经常空闲等待指令。而启用TMU之后sinf已经降到7周期挪到RAM只能到6周期绝对提升只有1个周期收益占比明显变小。这也说明一个问题当运算本身被TMU压缩到几个周期后取指瓶颈已经不再是主要矛盾这时候想再通过RAM搬移换收益性价比就低了。再看sqrtf这个反例。它的周期数在所有组合里几乎没什么变化TMUFlash是31周期甚至比纯FPUFlash的35周期只快了一点点。原因很简单F28377D的TMU指令集并没有针对sqrt做硬件加速sqrtf在库里的实现主要依赖FPU的VSQRT指令和软件校正TMU帮不上忙。这个反例很重要它提醒你别指望TMU对“所有数学函数”都有加速效果它是三角函数和除法加速器不是万能数学单元。还有一处值得注意atan2f无论在哪组配置下都比sinf慢1到2个周期。这是合理的因为atan2有两个输入且要考虑象限判断TMU的ATAN2指令序列比单输入指令多了一点额外处理。如果你在控制环里能提前知道象限用atan替代atan2再手动补偿符号和象限偏移理论上还能再省一点但对大多数控制环路来说这几周期的差距已经无关紧要。4. 实测之外不可忽略的六个坑基准数据拍出来了但如果你直接把这些结论搬进自己的工程大概率会遇到和我不一样的现象。下面的坑全部来自我实际跑测试时的过程有些是编译器行为导致有些是工程配置导致每一条都值得你先对照自己的工程排查一遍。4.1 优化等级一关数据完全反转我最初在这个板子上用默认的opt_level0测试得到的结果是TMU比FPU还慢。当时差点写出一篇“TMU没用”的结论。后来把优化等级从-o0升到-o2一切才恢复正常。原因在于TMU指令序列的生成依赖编译器的数学库内联替换在-o0下编译器不进行这种内联sinf还是调用通用软浮点实现TMU硬件根本没有发挥作用。所以复现我这组数据的时候请务必把优化等级开到-o2或更高不要再手动改成-o0调试。如果工程出于调试需要长期跑-o0那你对TMU的真实收益评价就是不准的等最后发布版本切回-o2再重新测一次。4.2 RAM搬移不是免费的RAM运行虽然能让纯FPU代码得到25%左右的提升但这个提升不是白来的。第一ramfuncs段需要占用一段宝贵的零等待RAM空间F28377D虽然有几块全局RAM但控制环路里的数据缓冲区、通信FIFO、CLA共享内存都在抢这些资源你把一大堆函数搬进去可能就挤掉了ADC采样缓存或编码器计数缓冲。第二启动阶段memcpy拷函数本身要花时间虽然只执行一次但在要求快速上电的场合也要算进启动预算里。更合理的做法是只搬移真正跑在热点路径上的中断函数和控制环路函数不要让整个main逻辑都变成ramfunc。比如20kHz的PWM中断里调用SVPWM和PLL计算把这两个函数搬进RAM固化收益最大而一个只在启动时运行的初始化函数就没有必要搬。4.3 精度取舍relaxed模式会带来什么代价开启--fp_moderelaxed之后sinf、cosf这些函数会切换到TMU的快速实现速度飞快但精度和标准库版本不完全一致。我测试里TMU sinf的最大误差大约在几个ULP级别也就是相对误差基本在1e-6量级对于电机控制、PFC、逆变器这类工程来说完全够用。但如果你在做需要严格IEEE双精度语义的测量算法或者某个环节对结果一致性要求极高relaxed模式可能带来隐患。我建议在工程里单独用一个文件封装所有数学调用并在文件头注释里明确写上“这个文件使用TMU relaxed精度”。这样如果将来出现精度相关的问题排查范围非常清晰不用对着整个工程问为什么sinf跟以前不一样。4.4 库版本和ABI不匹配链接报错还是小问题TMU库有两个关键版本后缀_eabi和没有_eabi的COFF版本。现在新CCS默认是EABI如果你还在老工程模板里用COFF直接换成rts2800_fpu32_tmu_eabi.lib会报符号不匹配反过来也一样。还有一种常见的错误是只把库文件名改了但链接器里老库rts2800_fpu32_eabi.lib还保留在搜索路径里导致链接器优先找到老库里的sinf符号TMU白开。正确的做法是在Project Properties的Linker选项里把浮点运行库明确替换成tmu版本并确认工程里没有其他地方二次引用了旧库。如果你用了第三方库或者DSP库还要确认它们也链接的是同一ABI的库否则就是一场符号大战。4.5 函数内联会把benchmark数据测成假数据另一个我踩过的坑是被测函数比较短小的时候编译器会把它内联进run_benchmark函数里。这时候CPU Timer的计时区间可能只覆盖了很小一段真正的函数体循环开销和函数调用开销全都被编译器抹掉了测出来的数据比真实值低很多表面上看起来“优化效果逆天”实际上不可信。防止内联的方法是给被测函数加__attribute__((noinline))或者在函数实现里加一条看起来“有副作用”的volatile写入让编译器无法轻易内联。加noinline不会影响真实工程的性能评估因为真实代码中这些函数通常也会因为封装原因独立存在。4.6 Timer0计数不要直接当微秒用很多人测完周期数习惯性地把周期数当成微秒数结果发现数字小得惊人。F28377D主频200MHz时1周期5ns86周期其实只有430ns。如果板子实际跑在150MHz或者你PLL配置有误同样的周期数对应的时间会变。所以记录测试条件时一定要把主频写清楚。另外CPU Timer的中断使能位TIE要记得清零否则计时到了会频繁触发中断干扰对时。我在测试代码里刻意没有启用Timer0中断只用纯计数模式这是最稳妥的。5. 从benchmark到真实控制环路TMU的收益怎么算看完周期数接着要把这些数字换算到真实工程里。以最常见的20kHz电机控制中断为例假设你每个中断周期内做一次Park变换、一次反Park变换和一个带锁相环的位置估计这里面每个完整控制节拍大约需要调用2次sin、2次cos、1次atan2、若干次浮点除法。用上面的实测数据估算一下。纯FPUFlash模式下一个节拍的数学运算大概是2×86 2×84 1×98 5×38 532周期。200MHz主频下就是2.66微秒占20kHz中断周期50微秒的5.3%。控制环里还有其他代码和协议栈这个占比看起来不高但如果你把控制频率抬到50kHz甚至100kHz或者加了多个控制环数学运算的占比会迅速膨胀。切换到TMUFlash模式后同样的运算量变成2×7 2×7 1×9 5×5 62周期只有0.31微秒。前后相差大概8.6倍。这就是为什么很多做PFC或PMSM控制的项目把库切到TMU之后CPU负载率肉眼可见地降下来。我在一个双电机项目中做过实测开启TMU后CPU负载从87%降到63%还多出了差不多24%的余量给通信和监控任务。不过也要泼一盆冷水。如果你的控制频率只有1kHz中断里没几次三角运算主循环跑在微秒级那TMU带来的绝对时间节省可能是几十微秒对系统整体响应影响不大。这种情况下换不换TMU更多看的是你手里库的获取成本和代码维护成本而非性能刚需。有一个容易被忽略的隐藏收益TMU能缩短“最坏情况执行时间”。控制程序里sinf在纯FPU库下可能有分支和查表执行时间随输入变化而TMU指令序列是固定流水线的时间更稳定。对于需要硬实时保证的控制系统最坏情况时间改善比平均时间改善更重要。这也是我最终决定把关键数学调用全部切到TMU库的核心理由。6. 收尾给你三条能直接抄的工程建议6.1 判断该不该上TMU先做一次“热点扫描”你不需要把整个工程先改到TMU库跑一遍再决定。用CCS的Profile工具或者直接在关键函数入口出口打计时点把工程里sinf、cosf、atan2f、除法这类运算的调用频次统计出来。如果这些运算占了CPU负载的5%以上无脑上TMU。如果占比不到1%那RAM搬移带来的取指收益可能更值得先做因为它的通用性更强能加速所有代码而TMU只能加速特定数学函数。6.2 RAM搬移和TMU可以叠加但优先级要分清从数据看TMU带来的加速是以十倍计的RAM搬移是百分之二十计的。所以优先级非常清楚先开TMU再看剩余热点。如果开了TMU之后控制中断里依然有大量其他运算再把中断处理函数整体放进ramfuncs可以顺手拿回15%到25%的取指收益。但注意不要为了搬移而搬移先看RAM剩余空间和启动拷贝时间。6.3 真实验证不要用固定输入最后也是一个最基本的建议测试时不要把输入固定成一个常量。编译器可能把整个数学调用直接常量折叠掉导致测出来的“加速比”高得离谱。我给的测试代码里用x x * 1.0001f 0.001f的方式让输入缓慢变化并且最终把结果暴露给全局变量就是为了骗过优化器又尽可能模拟真实控制信号连续变化的场景。你在自己的工程里做类似测试时建议直接用实际环路里采集的一帧数据回放让ROM里预先存好几百个输入样本循环喂给被测函数这样得到的周期数才真正可信。TMU的加速是实打实的但工程落地不是只看峰值性能。把这段测试代码跑一遍再结合自己的控制环路算一次预算分配你就能清楚地知道F28377D这颗芯片的真实余量到底有多少。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。