去年做某嵌入式项目时甲方要求 DSP 与 FPGA 之间用一类高速、低延迟、可全双工传输的接口交换数据。起初方案选的是 SPI结果评估完发现速率不够、帧格式还要来回协商实在别扭。翻了一圈 OMAP L138 手册发现常被大家默认为“音频专用”的 McASP 外设其实完全能干这活。这篇文章就是那段时间折腾 McASP 做非音频数据通道的完整记录从引脚、寄存器到 EDMA 再到调试验证全部覆盖。McASP全称 Multichannel Audio Serial Port是 TI 处理器上的多通道音频串行端口。可一旦放下“音频”两个字它就是一对支持时分复用、可配置位宽、可自定义帧结构的高速串行收发器。很多做 DSP 开发的同事一看到 McASP 就想到 I2S、音频 Codec思路直接被带偏了实际上它的底层机制非常通用完全可以用在 FPGA、ADC/DAC非音频、工业总线网关等场景。这篇文章适合手里有 L138 平台、需要和 FPGA 高速交换数据的嵌入式工程师也适合一些想在 SPI/UART 之外寻找更优解又不想碰复杂 EMIF 总线的人。1. 内容整体设计与思路拆解1.1 为什么不能用 SPI 或 UART 硬扛不少项目组在 DSP 对外通信时第一反应就是 SPI 或者 UART。如果数据量小、速率要求一般这确实够用。但到了 DSP 和 FPGA 之间要持续交换图像数据、波形数据或者雷达信号处理中间结果时那点带宽就很紧张。SPI 全双工速率看起来不错但帧结构固定、没有硬件帧同步信号CS 握手还要 FPGA 实时配合等调试到后面就发现线程、时序、中断全搅和在一起很痛苦。UART 更不适合这种场景。UART 的握手是字节级、异步帧格式传输距离和速率上限都受限而且没有稳定的位时钟输出给对端FPGA 侧要么做过采样恢复要么干脆用一根专用时钟线绕来绕去反而增加硬件复杂度。EMIF 并行总线性能更强但占用引脚非常多L138 本身引脚紧张再加上 EMIF 时序配置锁相环、读写周期对齐调试周期短的话根本不够用。说实话我见过不少项目最后都是各种接口勉强凑合稳定性全靠运气。所以当时我的思路很明确找一个引脚占用少、有独立位时钟和帧同步引脚、支持 DMA 和中断的串行接口。McASP 恰好全部满足。1.2 把“音频外设”当“通用数据管道”看McASP 之所以适合做非音频通讯原因在于它的底层协议本质上就是“位时钟 帧同步 串行数据”和音频完全解耦。音频只是它的一种用法而已跟 Linux 里把 UART 的 GPIO 复用搞成普通 IO 是一个逻辑。McASP 的核心机制是时分复用 TDM发送端在一个帧周期内可以划分多个时隙每个时隙单独指定数据位宽。这就意味着我可以自定义一帧里塞多少通道、每个通道多少位完全不受音频编解码器那套“左右声道 16 位/24 位”的约束。在 FPGA 通讯场景里帧同步信号 AFSX 可以被当作数据包的起始标志BCLK 当作比特时钟数据线 AXR 走真正的数据流这样对端 FPGA 只需要根据时钟沿采数据、按帧同步对齐数据包不需要解析复杂的协议头逻辑资源也省了。1.3 整体方案概览我最终采用的拓扑是L138 作为主设备产生位时钟和帧同步信号FPGA 作为从设备接收数据和时钟。DSP 侧把要发送的数据打包成“帧”每帧固定时隙数、固定位宽通过 EDMA 自动从内存搬运到 McASP 发送 FIFOFPGA 这边按照帧同步信号对齐后存入内部 FIFO同时它回传的数据也挂在 AXR 引脚上由 McASP 接收。整条链路的数据吞吐核心在于McASP 的位时钟可以配得很高加上 EDMA 搬运几乎零 CPU 开销发送接收都能做到流水线式处理。实测下来在一个需要持续传输大量数据的模拟项目 X 里稳定跑满约 20Mbps 全双工没有任何问题这对绝大多数控制类、采集类场景已经非常宽裕。2. 核心细节解析与实操要点2.1 引脚角色别被名字里的 Audio 骗了McASP 引脚名称确实全是 Audio 的缩写但功能上纯粹是串行通讯的概念逐个过一遍ACLKX/ACLKR发送/接收位时钟。对主设备来说就是自己产生时钟输出对从设备就是外部输入时钟。FPGA 通讯里通常让 DSP 做时钟源FPGA 直接采样这个时钟。AFSX/AFSR发送/接收帧同步。相当于“一帧数据的起始脉冲”。对 FPGA 来说这是数据包对齐的关键信号。AXR[0..n]串行数据线。L138 上有多个 AXR 引脚可以配置为发送、接收或双向。回环测试时可以让 AXR0 接 AXR1 来模拟对端。还需要提一下的是 AHCLKX/AHCLKR。这两个是高频率时钟输入/输出引脚配置时钟分频时需要用到。如果外部给了一个高频参考时钟内部再分频到想要的位时钟这种情况下就离不开 AHCLK。我项目里采用了外部有源晶振喂给 AHCLKX然后内部再分频到目标位时钟这样做的好处是参考频率稳定分频系数选择更灵活。2.2 帧、时隙与位宽把整包数据装进一个“帧”对于搞通信的人来说McASP 的数据结构非常直观下面我用打比方的方式拆开讲。一辆公交车帧按固定路线时钟行驶每天早上同一时刻帧同步始发车上有若干座位时隙每个座位大小固定位宽。你要发数据就把数据放到座位上FPGA 这位乘客只要盯着“始发站时刻表”就知道哪些位置是给自己的。这就是 McASP 的帧机制。配置时要明确几个数值每帧时隙数可以配 2、4、8、16、32 等。一个时隙是 32 位或按自己的位宽算。对 FPGA 通讯我喜欢配成 4 个时隙、每个时隙 32 位这样一帧正好 128 位非常方便组织内存缓冲区也可以直接和 FPGA 内部的 AXI-Stream 对齐。位宽McASP 支持 8/16/24/32 位数据。实际配置建议优先用 32 位因为 L138 的 EDMA 搬运是 32 位对齐的省去打包拆包的功夫。帧同步脉宽可以配置为单 bit 宽或按位宽长度。我给 FPGA 端配的是单 bit 宽这样对端逻辑只要在一个时钟沿看到 AFSX 拉高一个周期就知道新帧开始了不需要额外计数器。2.3 硬件连接与 FPGA 侧接口约定硬件连接看起来简单但对端的 FPGA 逻辑设计直接取决于你的 McASP 配置。我自己定义的接口引脚表如下信号L138 Pin方向FPGA 侧连接说明ACLKX位时钟输出DSP - FPGA接到 FPGA 全局时钟输入或 IO 时钟所有数据线同步AFSX帧同步输出DSP - FPGA数据包起始标志高有效单脉冲AXR0DSP 发送数据DSP - FPGAFPGA 串行输入端数据高位先出AXR1DSP 接收数据FPGA - DSPFPGA 串行输出端全双工回传GND地公共地回流路径必须短粗FPGA 侧接收逻辑的核心是在 ACLKX 的上升沿采样 AXR0当检测到 AFSX 脉冲后连续采样固定数量的 bit 组成一个数据包存入 FIFO再交给后续处理模块。如果配置的位序是高位先行FPGA 端移位寄存器需要从 MSB 开始收两边一旦理解不同数据就整个翻转。注意McASP 有多个 AXR 引脚有些被默认配置成复用功能。硬件设计时必须确认 pinmux 没有冲突否则怎么配都出不来数据。3. 实操过程与核心环节实现3.1 基础三件套引脚复用、PSC 时钟、复位OMAP L138 这类 SoC外设初始化绕不开三步开 PSC 时钟、配置 PINMUX、解除复位。PSC 就是把外设的时钟树打开。对应 McASP0 的 PSC 域是 LPSC 模块具体编号不同板子可能略有差异但一般是用PSC_PSC0这类头文件宏定义。配置好之后必须等待PTSTAT寄存器表明模块已经处于使能状态否则后续寄存器访问全是无效读写。PINMUX 这块最容易踩坑。L138 的 McASP 引脚本身和 LCD、EMIFA、GPIO 等功能复用。需要在 C 代码里把对应 PINMUX 寄存器选到 McASP 功能。如果配置错误引脚会一直输出 GPIO 电平示波器上完全看不到时钟。复位管理比较简单但对新手来说必须养成习惯任何外设初始化之前先把对应复位寄存器拉一下让所有 McASP 内部模块回到默认状态避免上次运行残留状态干扰新配置。3.2 串行器与数据格式寄存器配置McASP 的发送和接收数据通路是靠“串行器”这个模块实现的。通俗讲就是把并行的内存数据转成串行的 bit 流或者把串行 bit 流攒成并行数据。配置串行器时要做以下事设置数据引脚方向发送就用输出接收就用输入。选择数据位序高位先出还是低位先出。与 FPGA 侧对齐后统一选高位先出。配置 slot 偏移默认从时隙的第 0 bit 开始放数据也可以偏移若干 bit。非音频场景建议设 0省得两边都去算偏移。寄存器层面关注这几个PFUNC/PDIR引脚复用与方向XSTAT/RSTAT中断和错误状态SRCTL串行器模式控制XFMT/RFMT:数据格式控制XDATCTL/RDATCTL时隙和位宽定义实际操作中我会先把SRCTL配成“发送使能”“接收使能”再配XFMT的位宽和位序最后配XDATCTL的时隙总数。3.3 时钟和帧同步的定制非音频场景的灵魂音频场景的标准时序是位时钟 采样率 × 通道数 × 位宽比如 48kHz、2 声道、16 位位时钟就是 1.536MHz。但 FPGA 通讯场景里我根本不关心采样率只关心位时钟能不能匹配 FPGA 的接口要求。配置时钟时两步走第一步确定位时钟来源。我选择让外部晶振经过 AHCLKX 输入再内部分频到目标位时钟。比如外部晶振 24.576MHz我想得到约 12.288MHz 的位时钟就直接分频 2。这样整条链路都是整数分频相位抖动最小。第二步配置帧同步。帧同步有两种节奏很关键周期帧同步每个帧一个脉冲。突发帧同步只在有数据时才拉高帧同步。FPGA 通讯一般用周期帧同步。如果 DSP 持续在发数据周期帧同步就是稳定的数据包时钟。如果数据是突发性的可以考虑突发模式避免 FPGA 端在无数据时频繁收到空帧。AFSXCTL控制帧同步极性、脉宽、帧长。要把帧长和时隙数、位宽匹配好否则对端 FPGA 对齐完会发现长度不对。3.4 EDMA 搬运让 CPU 闲下来CPU 搬数据永远是最浪费的方案。McASP 支持与 EDMA 联动数据从内存到外设完全自动流转CPU 只在最后接收完成时进一次中断。具体思路发送方向配置 EDMA 通道源地址指向 DSP 内存里的数据缓冲区目的地址指向 McASP 发送缓冲区寄存器。触发事件用 McASP 发送事件XEVT。每写完一段缓冲区EDMA 自动搬运下一次数据。接收方向配置 EDMA 通道源地址指向 McASP 接收缓冲区寄存器目的地址指向内存缓冲区。接收事件REVT触发 EDMA数据搬运完成后可以产生中断通知 CPU。我习惯把数据缓冲区做成“双缓冲”或者“环形缓冲”。双缓冲可以做到 CPU 在填新数据的同时 EDMA 在搬旧数据两者互不干扰效率非常高。环形缓冲适合持续流式数据FPGA 不断回传数据时DSP 这边只管从环形缓冲区取非常省心。3.5 关键配置示例下面是这个项目里实际用到的初始化顺序骨架写出来方便大家对着自己板子改void McASP0_Init(void) { // 1. 使能 PSC 时钟 PSC_Module_Enable(PSC0_MDC, McASP0_LPSC); // 2. 配置 PINMUX, McASP 引脚映射 PINMUX_Config(0, PINMUX_MCASP0); // 3. 复位 McASP, 等待模块退出复位 McASP0-GBLCTL ~(MCASP_GBLCTL_RXH|MCASP_GBLCTL_RXR|MCASP_GBLCTL_TXH|MCASP_GBLCTL_TXR); McASP0-GBLCTL | MCASP_GBLCTL_RXH|MCASP_GBLCTL_RXR|MCASP_GBLCTL_TXH|MCASP_GBLCTL_TXR; // 4. 配置串行器方向, AXR0 TX / AXR1 RX McASP0-SRCTL[0] MCASP_SRCTL_TX_ENABLE; McASP0-SRCTL[1] MCASP_SRCTL_RX_ENABLE; // 5. 配置数据格式32位时隙高位先出 McASP0-XFMT MCASP_XFMT_32BIT_SLOT; McASP0-RFMT MCASP_RFMT_32BIT_SLOT; // 6. 配置时隙数量每帧4个时隙 McASP0-XDATCTL MCASP_SLOT_COUNT(4); McASP0-RDATCTL MCASP_SLOT_COUNT(4); // 7. 时钟配置外部晶振经AHCLKX输入内部分频2 McASP0-AHCLKXCTL MCASP_AHCLK_SOURCE_EXT | MCASP_AHCLK_DIV(1); McASP0-CLKXCTL MCASP_CLK_DIV(2); // 8. 帧同步单bit高有效周期模式 McASP0-AFSXCTL MCASP_AFSX_SINGLE_BIT | MCASP_AFSX_HIGH | MCASP_AFSX_PERIODIC; // 9. 配置 EDMA 通道使能发送接收事件 EDMA_ConfigChannel(EDMA_CH_MCASP0_TX, (uint32_t)TxBuf, (uint32_t)McASP0-XBUF, MCASP_EDMA_SIZE, EDMA_TRIG_MCASP_XEVT); EDMA_ConfigChannel(EDMA_CH_MCASP0_RX, (uint32_t)McASP0-RBUF, (uint32_t)RxBuf, MCASP_EDMA_SIZE, EDMA_TRIG_MCASP_REVT); // 10. 使能收发 McASP0-GBLCTL | MCASP_GBLCTL_XEN | MCASP_GBLCTL_REN; }这个骨架里细节较多实际芯片寄存器名字可能有差异核心顺序是“时钟 → 串行器 → 数据格式 → 帧结构 → DMA”。跑通之后再针对自己的 FPGA 接口微调位时钟和时隙数即可。3.6 数据打包策略设计帧格式时建议最前面固定 16 或者 32 bit 的“魔数/帧头”比如0x5A5A或0xCAFE。FPGA 端抓帧同步后先看帧头对不对作为一级校验。因为帧同步信号偶尔会被 PCB 噪声干扰拉高一个周期如果没有帧头校验那整帧就会错位后面解析逻辑全乱。我的数据包结构如下第 1 个时隙帧头0x5A5A5A5A第 2 个时隙数据长度 / 序号第 3 - 16 个时隙业务数据末位时隙CRC32 校验值DSP 软件算好这样一个包 16 时隙 × 32 位 512 bit 64 字节FPGA 收到完整包后可以直接按位解析不用对数据进行二次打包。我常把这套结构叫“轻量私有链路层”简单但够用。3.7 性能实测结果项目实际跑下来我记录了三组典型配置数据位时钟时隙数/位宽每帧时间理论净吞吐单向1.024 MHz4 × 32 bit125 us512 kbps12.288 MHz4 × 32 bit10.4 us6.144 Mbps24.576 MHz8 × 32 bit10.4 us12.288 Mbps考虑到帧头和 CRC 开销实际业务负载率大概在 85%~95%对绝大多数 FPGA 高速通讯场景都已经很充足。这边 CPU 几乎不参与数据复制负载很低。4. 常见问题与排查技巧实录4.1 引脚复用冲突最常见且最隐蔽有次惯例调试先看时钟波形示波器探针一搭上去没有任何时钟输出。自查寄存器全没问题引脚配置看起来也对最后查 PINMUX 才发现 GPIO 复用优先级不对某个引脚被 LCD 控制器占用了。这个坑很隐蔽。排查建议凡是“寄存器配了但没有波形”先回 PINMUX 拿万用表量引脚电平再查复用表。4.2 帧同步出来了但 FPGA 收不到有效数据低概率性现象AFSX 脉冲正常数据线上也有活动但 FPGA 解析出来的全是乱码。这类问题多半是数据位序或时隙偏移不一致。我用逻辑分析仪抓了三根线的波形回看发现 AXR0 的第一个有效 bit 位和 AFSX 脉冲并不是绝对对齐的存在一个时钟周期的偏差。FPGA 端在 AFSX 到来后的第一个时钟沿就开始采样和 McASP 实际发送时序错开了一个 bit。后来的处理方式是让 FPGA 侧在检测到 AFSX 后再多等一个时钟沿对齐到 TDM 标准时序的第二个沿之后再开始收数。这个问题不太起眼但影响致命。4.3 EDMA 不触发数据传输卡死EDMA 事件配置经常有问题。排查思路分三步确认 McASP 是否真的产生了XEVT/REVT。看XSTAT/RSTAT里的事件位有没有翻转。确认 EDMA 对应通道正确关联了事件。EDMA 事件是“事件号”驱动的如果事件号映射错了搬几次就空转。确认缓冲区地址没有跨 64KB 边界。EDMA 是地址计数搬运缓冲区如果跨越了边界可能出现未知地址跳变。我当时就把发送缓冲区放在一个大数组中间导致跨边界最后数据跑偏也是费了不少功夫。4.4 数据回环测试怎么验证 DSP 侧本身没问题建议先做板内回环把 AXR0 和 AXR1 直接短接不接 FPGA。DSP 发送一串已知数据再用接收通道把数据收回来比对。如果一致说明 McASP 收发通路本身没问题。不一致就回头检查位宽、时隙数、数据格式。这个步骤非常节约联调时间千万不要直接上 FPGA不然后面排查起来很难定位问题。4.5 常见问题速查表现象排查方向处理建议无时钟输出PINMUX、PSC确认引脚复用配置无误确认外设时钟已使能时钟有但帧同步无帧同步控制寄存器、时隙配置确认帧同步已使能、极性正确、帧长度匹配数据乱码位序、时隙偏移、FPGA 采样沿统一高位先出必要时调 FPGA 采样沿通讯偶发错帧帧头校验配置包结构、帧头校验、CRC 校验EDMA 不搬事件映射、地址边界确认XEVT/REVT在翻转避免跨 64KB 地址4.6 调时序的时间同步技巧DSP 和 FPGA 之间时间同步不能只靠帧同步。每个数据包里带一个序号FPGA 每收一帧就检查序号是否连续不连续就报告丢帧。这样哪怕偶尔崩一帧也能知道具体在哪丢的不至于下游数据对不上时间轴。在需要严格时间同步的采集、回放系统里这一招非常实用。5. 写在最后一个让我省下大量 debug 时间的习惯整个项目调试下来我自己最大的体会是非音频场景用 McASP 的难点不是配置本身而是脑子里要随时区分“外设功能”和“音频协议”。McASP 只是提供了一个通用串行骨架并不关心你在上面跑的是 I2S 还是自定义链路。音频 Codec 把数据格式约定死了会让你误以为 McASP 只能那么配等你跳脱出来看它的灵活度非常高。给后来者一个非常直观的建议先用回环测试跑一个“已知字节序列”再用逻辑分析仪把这串字节的波形和理论波形对照确认引脚极性、位序、时隙长度三个关键信息完全一致后再连 FPGA。这一步花十几分钟但至少能省出后面两三个晚上的排查时间。最后分享一个小技巧在帧头里放一个固定魔数例如0x5A5A或者0xA5A5每包数据第一个时隙固定发它第二位放包计数。FPGA 端校验魔数不匹配时自动丢弃并计数DSP 端通过回传的包计数判断链路质量。这一招让我在后续调试 FPGA 逻辑时快速定位了很多对端问题非常推荐加入到大家的链路设计里。McASP 在非音频场景的应用潜力远比大多数人想的要大多折腾几次你会越来越喜欢它。