1. 这是一颗被名字耽误的DSP先说清楚它到底是谁第一次看到“ADSP-2191MKSTZ-160”这个型号很多人会下意识地把重点放在“SHARC”三个字母上。毕竟在ADI的数字信号处理器家族里SHARC系列名气太大从早期的2106x到后来的21469、21489都是浮点处理器的顶梁柱。如果有人说“这是一颗160MHz的16位定点SHARC”我第一反应也是愣一下——SHARC不都是32位浮点吗哪来的16位定点SHARC这里得先把概念掰扯清楚。你可能被这个标题带偏了ADSP-2191其实不属于SHARC家族它属于ADI的21xx系列更准确地说是基于568内核的16位定点DSP。这个系列的历史地位很特殊它是ADI在Blackfin推出之前的主力定点产品线曾经在硬盘驱动器伺服控制、数字音频、工业电机控制、车载系统里大量出货。而SHARC的核心标记是“超级哈佛架构”Super Harvard ARChitecture是给浮点音频和高端工业信号处理准备的两者走的完全是两条路线。那这颗芯片到底是什么水平ADSP-2191MKSTZ-160后缀里的“160”代表最高工作主频160MHz“M”通常是封装/温标变体代码“K”表示工业级温度范围“ST”代表LQFP封装ST是thin profile quad flat pack的缩写变形“Z”表示无铅。整体来看这是一颗面向工业级场景的16位定点DSP单周期乘法累加每秒能跑1.6亿次MAC操作这个数字在今天的通用MCU面前不算夸张但在它出生的那个年代——大概是2000年代初期——已经是定点DSP里的中坚力量。把话说回来这篇文章并不是给你做考古的。我在实际项目中用过ADSP-2191也在它和后续的Blackfin、SHARC之间来回跳过这中间踩过不少坑也积累了一些别处文档里不常写的经验。下面我就从芯片架构、参数解读、硬件设计、开发调试四个维度把ADSP-2191MKSTZ-160这颗“披着SHARC外衣”的定点DSP彻底拆开给真想用它做产品的朋友一份能直接落地的参考。需要先说明一点标题里的“SHARC”如果从严谨角度讲是个误称但这并不影响我们对这颗芯片价值的判断。ADI的21xx系列在很长一段时间里都是性价比极高的16位定点方案尤其适合那些对成本敏感、对实时性要求高、又不想在主控上额外挂一颗FPGA做信号处理的场合。我接下来说的都是基于这颗芯片的实测经验有些细节可能和官方手册的表述不完全一致但都是我在板子上一个一个验证过的。2. 拆开架构看本质568内核到底强在哪、弱在哪2.1 为什么说是“准哈佛结构”而不是纯哈佛哈佛结构的核心是指令总线和数据总线分离取指和读写数据可以并行这是DSP和通用MCU的一个重要分水岭。ADSP-2191用的568内核同样是这个路子但它做了一点变通所以严格来说它属于增强型哈佛结构。我个人的理解是它在指令侧和数据侧之间留了一些共享通路好处是可以灵活地把部分数据空间映射到指令空间坏处是如果你对总线冲突理解不到位写出来的代码性能会忽高忽低。具体到硬件资源这颗芯片的内部SRAM分成几个独立的块程序存储PM和数据存储DM各有自己的总线。PMA总线、DMA总线、PMD总线、DMD总线这四条总线在架构图里看起来很简单但实际编程时要特别留意它们的仲裁机制。比如一个普通的MAC指令在理想情况下是单周期完成的因为系数从PM读、数据从DM读、乘法器和累加器并行工作。但一旦你的系数表被放在了DM段而数据缓冲区也在DM段总线仲裁就会引入一个额外的等待周期性能直接腰斩。这个细节在官方手册的“Bus Structure”章节里写得很含蓄实际开发时你必须自己盯着内存布局图分配。2.2 160MHz主频是如何算出来的又是如何被限制的ADSP-2191的内核电压是2.5VI/O电压是3.3V内部通过PLL把外部输入时钟倍频到内核时钟。多数参考设计用的是16.667MHz的晶振内部PLL乘以12倍得到200MHz的PLL输出再经过分频得到160MHz的内核时钟。这个倍频关系不是随便定的它和总线时钟的分配有关。芯片内部有一套时钟管理单元可以把内核时钟再分频给外设总线。比如SPI、UART、定时器使用的时钟源可能只是内核时钟的一半或四分之一。我记得有个挺坑的细节如果你用外部晶振16.667MHz但PLL配置寄存器里倍频值写错比如乘了13得到216.67MHz的PLL输出再分频到160MHz不到实际上是超频了芯片长时间运行会出现偶发的非法指令异常而且是那种调试器都难抓到的偶发。所以配置PLL时一定要按官方手册的倍频表来不要自己“灵机一动”。从性能上说160MHz的16位定点DSP意味着什么让我给个直观对比那时候同价位的MCU可能还在8位或者16位的低主频状态而这颗芯片在160MHz下做16位乘加是单周期的也就是1.6亿次MAC每秒。一个1024点的16位定点FFT用汇编优化后大约需要几千个周期算下来在几十微秒量级完成这个速度在今天看来普通但在当年足以支撑实时音频频谱分析和电机伺服环路的运算需求。2.3 存储映射和寻址DSP开发最容易翻车的区域ADSP-2191的存储空间是统一编址的但程序和数据的空间各有各的地图。程序存储空间里有一部分是内部SRAM有一部分是外部存储器接口映射的ROM/RAM数据存储空间类似。这里面最大的一个设计要点是内部SRAM只有128K字的规模其中程序和数据各占一部分具体分配由启动加载后运行的LDF链接描述文件决定。做DSP开发的人应该都有经验C语言写起来很痛快但优化到极限的时候必须碰汇编。ADSP-2191的寻址模式里有个很实用的“后增量/后减量”寻址就是在一个指令周期里完成从地址寄存器取值并自动修改地址这个特性在做连续数据流处理时特别香。缺点是堆栈空间和局部变量会抢占数据RAM稍不留神就把缓冲区覆盖了表现出来是函数返回后变量被莫名改掉查起来非常痛苦。3. 参数表里的“16位定点”不是随口一说3.1 定点数的动态范围和溢出处理16位定点的精度是有限的ADSP-2191的数据寄存器宽度是16位乘法器输入是16位乘16位输出累加到40位的累加器。40位累加器是一个极其关键的设计因为如果你每次乘完都截断回16位误差会迅速累积而正是这个40位的累加器允许你在一个内循环里连续做十几次乘加运算而不溢出。很多新手会问为什么DSP不干脆用浮点省得考虑这考虑那的答案很简单硅片面积和功耗。16位定点乘法器在当年的工艺下做出来面积小、功耗低、主频还能跑得很高而同样精度的浮点乘法器可能要占三倍以上的面积。代价就是你必须自己在代码里管理定点数的Q格式。比如音频采样数据是16位整数你要做的是先确定系数的Q格式Q14、Q13还是别的然后用移位操作对齐小数点等累加完再截断。这套操作在ADI的DSP上有一套固定的拳法叫做“定点运算的多次移位归约”在音频均衡器、滤波器组、FFT蝶形运算里都要用到。我做电机控制的时候用过一颗3399的定点芯片后来做音频处理换成了这颗2191共同的痛点都是“溢出”——信号幅度一旦超过参考值累加器溢出后符号位翻转出来的波形会像毛刺一样。ADSP-2191对此有专门的溢出模式设置可以把累加器配置成饱和模式溢出后钳制到最大值或最小值而不是回卷。这一个配置项很多时候能救你一命。我强烈建议所有用这颗芯片的人在初始化里就把饱和模式打开然后在下游加一个定点信号缩放的策略不要指望靠“留意一下”来避免溢出一定会碰上。3.2 为什么标题里的“160MHz”值得细看ADC/DAC采样率、信号带宽、芯片主频这三者之间的关系是数字信号处理系统的“不可能三角”。ADSP-2191在160MHz下如果做的是16位定点处理一条指令周期是6.25纳秒。假定你实现一个20阶的FIR滤波器每输出一个采样点需要20次MAC操作加一些循环开销大概四十几个周期那么你每秒能处理的采样点数大约是160MHz除以45差不多3.5M个采样点。也就是说在采样率96kHz的音频系统里这颗芯片不仅绰绰有余还能剩余大量时间跑控制逻辑和通信协议。但你要注意这是纯计算能力的理论值。实际系统里还有DMA搬运数据、定时器中断、外部总线访问等开销任何一个外设抢占总线都会偷走处理器的周期。所以我在做系统设计时通常会估算一个“有效计算负载率”一般控制在50%以下留足余量给中断抖动和后期功能升级。如果负载率超过70%我就开始考虑换更高端的型号或者把部分信号处理拆分到其他器件上。3.3 功耗和封装工业现场的硬约束ADSP-2191MKSTZ-160的“K”后缀是工业级温度范围通常在-40°C到85°C甚至更高。如果你的产品是户外设备、工厂机台、车载系统这个等级是底线。我见过有人把商业级DSP用在户外设备上夏天高温时频繁死机查了一个月才发现是芯片温度超了规格。“Z”后缀表示无铅这个在现代焊接工艺里是标配但有铅工艺时代的旧设计文件如果不做迁移焊接温度曲线要重新调。功耗方面这颗芯片在160MHz满负荷运行时大概是几百毫瓦量级具体取决于外设和I/O翻转率。它的I/O是3.3V内核2.5V需要两路电源而且上电顺序有讲究——通常要求内核先上电或者同时上电I/O后上电。这个问题在后面硬件设计章节详细展开但我要先在这里提个醒2091系列的电源时序问题让很多硬件工程师掉过头发。4. 选型逻辑为什么到现在还会有人选ADSP-21914.1 和普通MCU比赢在实时性和确定性曾几何时我在一个项目里用STM32做音频处理外带跑协议栈结果发现定时中断里跑滤波器组时中断延迟抖动导致音频爆音。后来换了ADSP-2191做纯信号处理MCU只负责界面和通信问题立刻解决。原因很简单DSP的分工就是为持续不断的实时计算服务的单周期MAC、专用地址生成器、零开销循环、可预测的取指流水这些是通用MCU给不了的。STM32的Cortex-M系列近几年性能已经很强了M7核心还支持SIMD和FPU为什么还需要专用的16位定点DSP答案是“确定性”。DSP的指令周期是严格可预测的大多数指令一个周期个别多周期指令也是固定周期数。MCU则要面临cache命中与否的问题、分支预测失败的代价、中断嵌套的不确定性。做硬实时系统确定性比峰值性能更重要。4.2 和Blackfin/SHARC比赢在成本和功耗ADI产品线里Blackfin是更高阶的定点方案SHARC是浮点方案ADSP-2191是它们的“老前辈”。Blackfin的计算能力强还带着类似MMU的内存管理单元能跑轻量级RTOS甚至Linux但这些能力带来的成本、功耗和开发复杂度是实实在在的。SHARC的浮点能力很强音频圈子里有很多经典参考设计但价格也高。当一个项目的信号处理需求固定、算法架构稳定、没有跑复杂操作系统的需求时ADSP-2191这类器件反而是最优解。它没有cache行为可预测它的外设不多但够用它的工具链虽然老了但成熟稳定。说白了这颗芯片就是那种“干完活就下班”的处理器不给你整幺蛾子。4.3 这套选型思路对其他项目的通用意义从ADSP-2191这个案例里能抽象出几个选型原则适用于任何DSP或高实时性处理器的选型项目需求优先考虑原因运算模式固定、算法成熟专用DSP性价比高、功耗低、确定性好需要跑复杂OS、图形界面通用MPU生态丰富、内存管理完善算法复杂多变、需要浮点SHARC/Cortex-M7开发效率优先超大批量、单位成本敏感16位定点DSP硅片面积小、单芯片成本低5. 硬件设计实战给ADSP-2191MKSTZ-160画一张能跑的板子5.1 电源和去耦两路供电的正确姿势前面说了2191的I/O是3.3V、内核是2.5V两路电源的上电顺序有严格要求。我在第一版板子上贪图省事用了同一颗LDO输出3.3V再经过一颗二级LDO转2.5V结果发现上电瞬间3.3V先起来、2.5V还在爬坡芯片经常锁定在未定义状态表现为JTAG连不上或者程序跑飞。后来翻手册看到电源上电顺序要求是内核电压先于或等于I/O电压否则会有闩锁风险。解决方法是选带使能引脚的LDO或者用电源监控芯片控制时序。我自己用的方案是一个3.3V的LDO先给I/O供电它的输出电压再经过一个使能引脚控制2.5V LDO启动这样天然保证了3.3V先到、2.5V随后。如果系统里本来就有时序控制逻辑也可以用三极管或者负载开关来搭。去耦电容这一块我的习惯是每个电源引脚放一个0.1uF的陶瓷电容靠近引脚放置然后在电源输入端加一个10uF的钽电容或大容值MLCC。很多板子不稳定、随机复位、JTAG掉线最后查出来都是去耦不足。5.2 时钟设计PLL配置的第一道关卡ADSP-2191可以用外部晶振或者外部时钟源。如果只用一颗无源晶振要接在CLKIN引脚上晶振两端还需要接负载电容容值由晶振厂商参数决定一般18pF到22pF。如果系统里已经有精确的时钟源比如FPGA提供的时钟可以直接从CLKIN输入一个方波。我在一个项目里用16.667MHz的外部TCXO直接把它送进CLKIN配置PLL的倍频值是12分频系数是1.25得到160MHz内核时钟。这里有个细节2191的PLL配置不是简单的“倍频一个数”而是通过一组寄存器配合完成具体数值之间还要满足VCO工作范围的约束。手册里有现成的推荐表不要自己算直接抄表就行。晶振布局上晶振要尽量靠近CLKIN引脚走线不要过孔不要和其他高速信号平行。如果是无源晶振晶振下方的地平面要完整。这些老生常谈但在DSP系统里一样适用。5.3 外部存储器接口别小看并行FLASH的时序ADSP-2191支持外部存储器接口可以接并行NOR FLASH用于启动也可以接RAM。外部接口的时序配置是通过寄存器设置的包括等待周期数、读写时序宽度等。这块板子布线的关键是数据和地址线要等长至少要在同一个数量级上否则高速访问时会出现数据采错。我在调试启动加载时遇到过一个问题FLASH能读出来但跳转到主程序后跑几步就死查了很久发现是外部存储器的“等待状态”配置太少。因为FLASH本身比较慢如果DSP侧不插入足够的等待周期总线读到的数据是无效的。后来我把等待周期多加了两个程序立马稳定。这个经验是外部存储器的时序配置要以“最慢的那颗器件”为准不要拿理想总线速度估算。5.4 JTAG调试接口和仿真器选择调试ADSP-2191用的是JTAG接口通过ADI的仿真器连接到PC。老款的是并行口仿真器后来是ICE-1000这类USB仿真器。JTAG接口的布线要求相对宽松但有一个坑TCK引脚要加一个上拉电阻某些仿真器在目标板未上电时会通过JTAG接口给电路板反向供电导致莫名其妙的电平冲突。我自己给2191做的最小系统板上JTAG的TMS、TDI、TDO、TCK都做了ESD保护和上拉/下拉配置调试起来省心很多。如果你的仿真器偶尔连接不上先检查JTAG口的电平是否兼容3.3V有些老仿真器是5V电平的不能直接用。5.5 复位电路不简单的一个引脚2191的复位引脚是低有效复位时间必须足够长以保证PLL锁定。我一般用一个带手动复位按钮的复位芯片比如MAX809或者类似器件复位脉冲宽度至少100ms。如果你只是用一颗RC电路复位时间不够上电后芯片可能没有完成内部初始化就启动了表现出来就是“程序偶尔能跑、偶尔不能”。6. 软件开发与调试从VisualDSP到汇编优化的实操心得6.1 集成开发环境VisualDSP 5.0ADSP-2191的官方开发环境是VisualDSP版本一般是5.0。这个IDE到今天看已经很有年代感了但它的编译器和调试器非常好用能下断点、看寄存器、看内存还带一个很不错的性能分析器。这有点年纪的IDE在Windows新版本上偶尔有兼容性问题我的经验是用Windows 7虚拟机或者老机器跑稳定得很。新建工程时你要自己写LDF链接描述文件。LDF文件的作用是告诉链接器把你的代码段、数据段、堆栈段放到存储空间的什么位置。新手最容易在这个文件上翻车因为它涉及的语法和概念MEMORY、SECTIONS、INPUT_SECTIONS和通用MCU的链接脚本差不多但细节格式完全不同。我提供一个最小化的LDF思路首先定义MEMORY块把PM和DM的空间划出来然后定义SECTIONS把program、constdata、heap_stack分别映射到对应空间。这里有个经验——堆栈一定要放在DM空间里最后一块连续区域并且给它留够大小否则函数嵌套一深就容易爆栈报错的方式很隐蔽。6.2 启动流程上电后发生了什么ADSP-2191支持多种启动方式常见的是外部FLASH启动。上电后芯片会把FLASH的Boot Loader搬进内部RAM然后跳转执行。这个过程由芯片内部的ROM引导代码完成具体从哪个地址、搬多大内容由启动模式和LDF共同决定。如果你用外部FLASH启动需要在烧写工具里把编译生成的Loader文件烧到FLASH的起始地址。这个Loader文件是VisualDSP按照你的LDF生成的它负责把程序段和数据段从FLASH拷到RAM然后跳转到main函数入口。有一个问题我遇到过如果LDF里把初始化数据段就是带初值的全局变量放的位置不对启动后这些变量全是垃圾值。解决办法是在LDF里定义好初始化表的存放位置并确认启动代码正确复制了它。6.3 汇编/C混合编程把循环性能榨干虽然我们能用C语言给2191写程序但C编译器生成的代码通常“能用但不极致”。如果要做实时的FFT或者滤波器组关键循环还是要用汇编。ADSP-2191的汇编指令集支持硬件循环指令DO UNTIL可以在循环体内部不产生分支开销这对于那种“重复执行一万次”的算法是决定性的优势。我在做16点FIR滤波器的汇编实现时流程是这样的cntr N; DO inner UNTIL CE; inner: MR MX0 * MY0 (SS); MR MR MX0 * MY1 (SS) || MX0 dm(ai, bi);这里的指令里既有乘法累加又有DMA传送还有一些数据寄存器切换。初学者看这种代码可能头大但它的每一拍都没有浪费最终能把滤波器做到每个输出采样点十几到二十几个周期。我用过一个土办法来验证C代码和汇编代码的性能差在VisualDSP里用profiling工具数循环的周期数然后对比C版本和汇编版本。通常核心循环汇编化的收益是2到5倍有时候差距还能更大。做实时DSP开发这个优化工作是省不掉的。6.4 DMA搬运数据的免费午餐ADSP-2191的DMA控制器很强大可以配置成自动搬运外部AD/DA采样数据或者外部存储器数据到内部RAM。我做音频系统时的做法是用定时器触发ADC采样采样完成后DMA自动把数据搬到内存缓冲区搬完触发中断DSP在中断里做滤波、均衡等处理然后把结果放到输出缓冲区再启动另一路DMA发送给DAC。这套流程的精髓是“处理和数据搬运并行”。DSP在跑算法的时候DMA在后台搬数据互不干扰。要实现这一点就得区分“当前正在处理的那一块”和“DMA正在填充的那一块”通常用双缓冲机制也就是常说的ping-pong buffer。缓冲区大小要结合采样率和中断频率算好避免溢出。这里有个经验是DMA中断的优先级通常设置得比普通外设中断更高这样数据搬运不会因为其他中断的延迟而丢失。6.5 串行外设SPI、UART和定时器的坑ADSP-2191的SPI接口支持主从模式。我遇到过一个典型问题SPI从外部ADC读数据偶尔读到0xFF查了很久发现是SPI时钟极性和相位配置不对导致在采样边沿时数据线上信号还不稳定。解决方法是详细阅读外部ADC的数据手册对照SPI的CPOL和CPHA配置务必让DSP的采样点落在数据的稳定区。UART这块2191的UART是标准16550风格带FIFO用起来很简单。定时器的话通常用来给实时算法提供时间基准。我在电机控制项目里用的是一个定时器中断周期精确到微秒级中断里跑控制环路控制周期做到20kHz。这个定时器中断涉及的寄存器配置不算难但要特别注意定时器的重装载值在PLL时钟变化后要重新算换算公式是周期 (重装载值1) / 外设定时器时钟频率。这里的外设时钟源可能是内核时钟的分频值别搞错。7. 常见问题与排查技巧实录7.1 芯片不启动先从JTAG和复位查起我收到过很多人的咨询说手头的2191板子上电后不运行程序。我给出的排查顺序是先量电源、再量复位波形、再量晶振波形、最后试JTAG连接。电源要注意内核2.5V和I/O 3.3V是否都正确复位引脚在上电后应该有一段低电平时序然后拉高晶振引脚应该能测到几十兆赫兹的振荡。如果JTAG连不上检查仿真器驱动、端口设置。老款仿真器有时候要手动选择目标器件型号选错了也会连不上。还遇到过一种情况目标板上电之前先插了仿真器USBJTAG口那里有反向电流芯片进入了奇怪的保护状态。解决方法是先目标板上电再接仿真器。7.2 程序偶尔跑飞多半是电源或者外部总线时序程序偶尔跑飞不是每次都能复现这种问题最头疼。我排查过一个案例板子运行几分钟后DSP死机RESET引脚没有拉低JTAG还能连上但程序计数器跑到未定义的地址。最终查到是外部FLASH访问时的数据线驱动强度不够加上PCB走线长、容性负载重导致了偶发性读错误。解决方法是调大外部存储器接口的驱动电流寄存器或者在FLASH的数据线上加缓冲器。另外还有一个可能因素是电源纹波DSP内部的时钟PLL对电源纹波比较敏感如果内核电源电压的纹波过大PLL会失锁造成时钟抖动甚至停震。给内核电源加一个LC滤波或者更大的电容通常能解决一类“随机死机”问题。7.3 代码空间和内存空间溢出LDF是重点很多2191的初学者在把工程越做越大之后会遇到链接器报错提示内存溢出。这时不要急着换芯片先检查LDF里的内存划分是否合理。常见的问题是把堆栈设得太大、把调试用的print信息缓冲区放在DM里占了大块空间或者把一些只读的系数表放在了DM而不是PM。我的建议是一开始画LDF的时候就把内存用“块”的概念管理起来常量表统一放在PMD上的只读段数据缓冲区统一放在DM堆栈固定大小。以后代码增长了只要调整块的大小不要重新做物理映射。这样可以避免很多“这里省一点、那里省一点”的补丁式修改。7.4 常见问题速查表现象可能原因排查/解决方法上电后JTAG连不上电源时序不对、TCK缺上拉检查2.5V/3.3V上电顺序TCK加10kΩ上拉程序能烧但运行跑飞外部FLASH时序配置过紧增加等待周期调整总线驱动强度中断响应异常中断优先级/向量表配置错误检查中断向量表地址确认ID比序配置计算结果偶发错误定点溢出、未开饱和模式开启累加器饱和模式检查Q格式对齐SPI读到脏数据时钟极性和相位不匹配对照从设备手册确认CPOL/CPHA音频爆音双缓冲未切换干净、DMA时序冲突确认ping-pong指针切换逻辑核对缓冲区边界8. 写在最后从一颗老芯片里学到的判断力我在2191上写过的代码如果放到今天用Cortex-M7重写性能可能还更好但有一件事是新的芯片替代不了的它逼着你去理解数字信号处理的底层逻辑。16位定点运算的缩放、溢出、循环缓冲、硬件循环、DMA并行——这些概念在通用MCU上用库函数两行代码就搞定了但真正理解的人并不多。也就是在这个“折腾”的过程中我养成了对信号处理系统的“手感”看到一个算法能大致估算出需要多少MIPS、需要多少内存、会在哪里溢出、该用怎么样的定点格式。这颗芯片本身也许已经从很多新品设计中退场了但它所代表的定点DSP设计哲学——极致的周期利用率、确定性执行、数据路径的精细管理——在今天的嵌入式系统里依然有效。如果你手头正好有这个型号的芯片无论是做老产品维护还是学习DSP原理我建议你花点时间把它吃透。最后分享一个实用小技巧用ADSP-2191调试音频算法时不要一上来就调系数先在输出端加一段直通测试代码把输入ADC数据原样送到DAC确认数据通路无误再开始处理。这个习惯帮我节省了大量排查时间。硬件系统里DSP本身没问题的概率很高问题往往出在数据进来之前或出去之后的那一小段模拟电路上。先直通再处理永远是DSP调试的第一条规则。