☰
TMS320F28335 McBSP配置为SPI的完整指南
2026/10/6 9:02:13 网站建设 项目流程

玩过TMS320F28335的人大概都有过这种体会:芯片外设看着挺全,用起来总差一口气。单看SPI,28335就一个SPI-A模块,接口引出来了,可项目里SPI Flash要占一路、SPI ADC又占一路,偶尔还想挂个传感器,接口就不够用了。我当时的处理办法是打McBSP的主意——这个外设平时多数人被它“多通道缓冲串口”的名字劝退,觉得是给音频Codec用的,实际上TI在McBSP里专门留了一个CLKSTP时钟停止模式,目的就是让它能充当SPI。本篇就用28335为例,把McBSP配置成SPI模式的完整思路、寄存器要点、可以直接抄的初始化和收发代码,以及我在实际调试里踩过的坑,一次讲清楚。

1. 为什么非要用McBSP实现SPI——不只是“省一个外设”

1.1 28335外设资源的现实矛盾

TMS320F28335是一颗在电机控制、数字电源、电力电子领域非常常见的DSP,算力够、PWM和ADC资源也足。但它的SPI资源确实局促:整个芯片只有一个SPI-A模块。做过整机项目的人都知道,一个系统里SPI设备往往不止一个——W25Q系列Flash用于参数存储,SPI接口的ADC/DAC用于采样和输出,还有可能挂OLED屏、SPI传感器、外部看门狗等等。

把这些设备全部挂到同一根SPI总线上,不是不行,但很别扭。不同从设备的最高时钟不一样,有的要求Mode 0,有的要求Mode 3,片选切换又依赖软件频繁翻转GPIO。一旦总线上某个设备把MISO拉死或者片选逻辑出错,整个SPI链路都受影响。更麻烦的是,如果主程序的中断里既要响应SPI接收,又要处理PWM故障保护,一个SPI模块很容易成为瓶颈。

所以当项目里出现“第二个SPI需求”时,我的第一反应不是去软件模拟,而是先看芯片上还有没有空闲的硬件串行口。28335除了SPI-A,还有两路SCI、一路I2C、一路CAN,以及两路McBSP。SCI和I2C做不了SPI,CAN更不可能,但McBSP可以——它的数据线、时钟线、帧同步线本身就是为通用串行通信设计的,甚至比SPI模块更灵活。

1.2 McBSP硬件的SPI基因:时钟停止模式

McBSP的全称是Multi-channel Buffered Serial Port,确实经常出现在音频编解码器的连接方案里。它的传统工作模式是:帧同步信号FSX/FSR标记一帧数据的起点,时钟CLKX/CLKR连续运行,数据按位在DX/DR上传输。这种“时钟连续跑、靠帧同步对齐”的机制,天然适合I2S等音频协议。

但SPI的时序和这个不一样。SPI总线在空闲时,SCLK一般是静止的(停在某个电平),片选CS拉高,主机开始通信时把CS拉低、再输出时钟脉冲。McBSP如果老老实实跑连续时钟,从设备根本不会认。TI在硬件设计时意识到了这个问题,于是给McBSP加了一个叫CLKSTP(Clock Stop)的位。这个位一旦配置为非零值,McBSP就进入“时钟停止模式”:时钟只在传输期间产生,传输结束时钟停止,空闲状态的电平可以通过极性位设置。这就是McBSP能冒充SPI的根本原因。

可以这么理解:CLKSTP就是TI官方留给McBSP的“SPI开关”。把这个开关打开,McBSP的硬件移位寄存器、双缓冲、DMA事件触发、中断请求这些能力全部保留,只是时序行为变成了SPI的样子。

1.3 与软件模拟SPI相比的取舍

可能有人会问:既然SPI不够用,为什么不直接用GPIO翻转来模拟?低速场景下当然可以,比如用GPIO模拟SPI读个温湿度传感器,频率几十kHz,完全没问题。但软件模拟有几个先天短板:

  • CPU占用高。每个bit都要软件延时、拉高拉低时钟,传输一个字节往往要几十条指令,MCU被锁死在轮询里。
  • 没有硬件缓冲和DMA联动。大批量数据读写时,软件模拟基本没法同时兼顾主流程。
  • 时序抖动大。中断嵌套、优先级翻转都会导致模拟时钟出现毛刺,高速设备容易误采样。

McBSP配置成SPI之后,以上问题都不存在。它是纯硬件外设,数据移位、时钟产生、接收采样全部由外设逻辑完成,CPU只需要在XRDY/RRDY标志位就绪时读写数据寄存器。如果配合PIE中断,甚至能做到收发不阻塞主程序。资源占用上,只是用掉了两路McBSP中的一路,以及几个GPIO复用引脚,性价比很高。

我把这个方案和常规软件模拟在几个核心维度上做了对比,列在这里供参考:

对比项软件模拟SPI硬件SPI模块McBSP配置为SPI
实现难度低低中,需要理解McBSP寄存器
最高时钟取决于GPIO翻转速度,通常百kHz级可达十几MHz取决于LSPCLK分频,实测MHz级无压力
CPU占用高低低
支持中断取决于实现方式支持支持
支持DMA联动不支持支持支持
额外资源消耗2-4个GPIO无1个McBSP模块加4个引脚

如果你的项目只是临时扩一路低速SPI,软件模拟完全够用;但如果后面有DMA传输、多字节批量读写、中断里收发数据的需求,把McBSP变成SPI是更长远的选择。

2. 先把引脚和时序对齐:McBSP与SPI的映射关系

2.1 引脚对应表

McBSP的命名方式和SPI完全不一样,刚上手时最容易被绕晕。它的核心引脚有四个:MCLKXA(发送时钟)、MDXA(发送数据)、MDRA(接收数据)、MFSXA(发送帧同步)。和SPI主机一一对应起来其实很直观:

McBSP引脚方向(主机模式)SPI中的角色说明
MCLKXA输出SCLK时钟信号,由采样率发生器产生
MDXA输出MOSI主机发送数据
MDRA输入MISO主机接收数据
MFSXA输出CS帧同步信号,当作片选使用
MCLKRA输入(不使用)主机模式下接收时钟内部生成
MFSRA输入(不使用)主机模式下接收帧同步内部生成

在TMS320F28335上,McBSP-A模块的这几个引脚并不是独立引脚,而是复用GPIOB14到GPIOB19。MFSXA对应GPB14,MCLKXA对应GPB15,MDXA对应GPB16,MDRA对应GPB17。如果只是做SPI主机,MCLKRA和MFSRA对应的GPB18、GPB19可以不用管。28335另一个McBSP-B的引脚不在这个位置,接线前先看一眼GPIO功能分配表,别搞混。

顺带提醒一句:很多开发板上GPB14到GPB19不只是接了McBSP,旁边可能还并着SCI、CAN或者其他外设的测试点、跳线帽。我遇到过一块板子,MCU的McBSP引脚出厂时被默认接到音频Codec芯片上,结果不管怎么配寄存器,MCLKXA引脚一直没波形,查了半天才发现是被Codec芯片拉住了。所以第一步一定是先把原理图里这几个引脚的网络确认清楚。

2.2 时钟极性和相位的四种模式怎么对应

SPI有四种工作模式,核心是两个参数:CPOL(时钟极性)决定空闲时SCLK是高还是低;CPHA(时钟相位)决定数据是在第一个边沿还是第二个边沿采样。McBSP里没有直接叫CPOL/CPHA的位,但它用两个手段组合出四种模式:

  • CLKSTP:时钟停止模式的两种变体,分别对应“数据无延迟”和“数据延迟半个时钟周期”。
  • CLKXP / CLKRP:发送时钟极性和接收时钟极性,控制空闲电平和采样边沿。

我在实际项目里的经验配置是:

SPI模式CPOL / CPHA常用McBSP配置组合适用设备举例
Mode 00 / 0CLKSTP=10b,CLKXP=0,CLKRP=0W25Q系列Flash、AD7606、MAX6675
Mode 10 / 1需要具体按器件手册微调较少见
Mode 21 / 0需要具体按器件手册微调较少见
Mode 31 / 1CLKSTP=11b,CLKXP=1,CLKRP=1部分SPI Flash、部分传感器

之所以Mode 0和Mode 3用得最多,因为大多数SPI从设备的手册都会明确说支持这两种模式之一,有的两种都支持。CLKSTP=10b和CLKSTP=11b的区别,本质上是第一个有效数据边沿相对于时钟启动时刻有没有半拍延迟。这个延迟对应到SPI里就是CPHA的差异。

这里我不建议死记硬背映射表,最靠谱的办法是:确定从设备手册要求的是哪种SPI模式,查清楚它要求的空闲时钟电平、数据采样边沿,然后对照CLKXP/CLKRP去推。手头有示波器的话,直接抓一下CLKX引脚的空闲电平和数据线上的采样点,比对着寄存器猜快得多。时序这种东西,寄存器配置写得再漂亮,示波器看到不对就是不对。

2.3 帧同步信号如何扮演片选角色

这是McBSP做SPI时最容易让人迷糊的地方。McBSP原本的帧同步FSX是一个脉冲信号,用来告诉收发双方“一帧数据从这里开始”。而SPI的CS是一个电平信号,传输期间保持低电平,传输结束拉高。两者行为不完全一致,但McBSP在设计SPI模式时做了适配:FSX会按照片选的方式工作,并且可以通过PCR寄存器里的FSXP位配置极性。

SPI从机的片选绝大多数是低有效,也就是CS平时为高,传输时拉低。对应到McBSP上,就需要把FSXP设置为1,让帧同步信号以低电平有效。FSXM要设置为1,表示FSX引脚由内部产生,也就是McBSP自己做主机;FSXM=0则表示FSX作为输入,适用于从机模式。

有个细节很多人不注意:McBSP的FSX是按“帧”来拉的,不是像硬件SPI的CS那样,CPU可以控制它一拉到底。如果一帧只传输8位或16位数据,FSX在每次数据传输前后都会产生跳变。这对大多数SPI从设备没影响,因为它们只认CS电平,每次片选拉低就准备接收一帧。但对于某些连续传输型的从设备,比如要求在CS持续拉低期间一次性灌入好几个字节的Flash页编程命令,McBSP默认的逐帧FSX行为就有可能出问题。遇到这种场景,我的做法是单独拿一个GPIO做片选,McBSP只负责SCLK、MOSI、MISO三根线,控制反而更稳。

3. 寄存器配置流程:按顺序把每个关键位说清楚

3.1 第一步:GPIO复用配置

28335的引脚基本都是多功能复用的,使用McBSP之前,必须先把对应的GPIO口从普通的GPIO模式切换到外设功能模式。这个动作由GPxMUX寄存器控制。以McBSP-A为例,需要把GPB14到GPB17设置为MFSXA、MCLKXA、MDXA、MDRA对应的功能。GPB14、GPB15在GPBMUX1里,GPB16、GPB17在GPBMUX2里。配置时建议先按位清零,再置位,避免改动其他引脚的功能。

// 清除GPB14/15的功能选择位 GpioCtrlRegs.GPBMUX1.all &= 0xFFF0FFFF; // 设置GPB14/15为McBSP-A相关外设功能,具体值以芯片手册GPIO功能表为准 GpioCtrlRegs.GPBMUX1.all |= 0x50000000; // 清除GPB16-19的功能选择位 GpioCtrlRegs.GPBMUX2.all &= 0xFFFFFF00; // 设置GPB16/17为MDXA/MDRA外设功能,GPB18/19用不到可不配 GpioCtrlRegs.GPBMUX2.all |= 0x00000005;

0x50000000这个值是我在ControlSUITE例程基础上按GPB14/15功能值调整出来的示例。你手里的头文件版本或者具体的功能映射如果不一样,一定要以TMS320F28335数据手册里的GPIO功能分配表为准。我自己就吃过亏,照着网上一个老帖子的MUX值抄,结果GPB16/17的功能完全不对,MDRA引脚怎么都读不到数据。这种坑不是逻辑问题,纯粹是文档版本没对齐。

GPIO切到外设功能后,引脚方向就由外设接管了,不需要再去设置GPxDIR。MCU内部会按照CLKXM、CLKRM、FSXM这些位的配置自动决定引脚是输入还是输出。

3.2 第二步:复位状态下配置串口控制寄存器

TI的外设几乎都有一个原则:配置寄存器之前先让外设进入复位状态,配置完成后再解除复位。McBSP也不例外。SPCR1里的SWRST位控制整个串口的软复位,SPCR2里的GRST控制采样率发生器复位,XRST控制发送器复位,RRST控制接收器复位。初始化时先全部清零,保证配置过程中外设不干活。

McBspaRegs.SPCR1.bit.SWRST = 0; McBspaRegs.SPCR2.bit.XRST = 0; McBspaRegs.SPCR2.bit.RRST = 0; McBspaRegs.SPCR2.bit.GRST = 0;

之后就是设置CLKSTP位。在McBSP的SPCR1寄存器里,CLKSTP占两个bit,10b和11b都表示进入时钟停止模式,只是数据延迟不同。我在上一节已经说过,这里直接给结论:读大多数SPI Flash和ADC的默认配置,CLKSTP=10b、CLKXP=0、CLKRP=0,对应Mode 0最常用。如果你的从设备需要Mode 3,再改成CLKSTP=11b、CLKXP=1、CLKRP=1。

调试阶段建议把SPCR1里的DLB位设置为1,也就是数字环回模式。这个模式会在芯片内部把发送数据直接绕回接收通路,不需要外部接线就能验证寄存器配置和中断链路是否正常。等环路测通了,再把DLB去掉接真实设备,能省不少排查时间。

3.3 第三步:采样率发生器与分频计算

McBSP内部有一个采样率发生器(Sample Rate Generator),用来产生发送和接收时钟。SPI模式下要让它工作,必须在SRGR2里把CLKSM设置为1,表示时钟源来自内部;然后在SRGR1的CLKGDV里写入分频值。采样率发生器的参考时钟是LSPCLK,在28335默认配置下,LSPCLK等于SYSCLKOUT的四分之一。比如主频150MHz,LSPCLK就是37.5MHz。

CLKX引脚上输出的SPI时钟频率按下式计算:

SCLK = LSPCLK / (CLKGDV + 1)

举个例子,系统主频150MHz,LOSPCP默认4分频,LSPCLK=37.5MHz。如果想让SCLK大约是1MHz:

CLKGDV = 37.5 / 1 - 1 = 36.5

取整后CLKGDV=36,实际SCLK约为37.5/(36+1)=1.0135MHz,已经足够接近。如果只是测试,CLKGDV=9,SCLK就是3.75MHz,大多数SPI从设备都能接受。往下配的时候注意,CLKGDV最小是0,也就是最小分频1,这时候SCLK=LSPCLK。这个频率对普通SPI设备太高了,一般不建议直接跑。

采样率发生器还有一个GRST位要处理,它就是采样率发生器的复位开关。配置阶段保持GRST=0,所有寄存器配置完成后,最后一步把GRST置1,时钟才会真正开始跑。这个顺序很多新手搞反,一上来先置GRST,后面改分频值,结果分频值完全没生效。

3.4 第四步:数据格式与帧同步方向设置

数据格式由发送控制寄存器XCR1和接收控制寄存器RCR1决定。SPI模式下最常见的是8位数据,对应XWDLEN1和RWDLEN1都设为0。

McBspaRegs.XCR1.bit.XWDLEN1 = 0; // 发送字长8位 McBspaRegs.RCR1.bit.RWDLEN1 = 0; // 接收字长8位

如果设置16位数据,则这两个字段设为1。McBSP还支持更长的字长,但超过16位会拆到DXR1/DXR2两个寄存器里,SPI场景基本用不到,这里不展开。

引脚方向相关的控制集中在PCR寄存器。做SPI主机时,CLKXM=1表示CLKX引脚作为输出,由内部采样率发生器驱动;CLKRM=1表示内部发送时钟也用来驱动接收时钟,这样CLKR引脚不用接外部信号;FSXM=1表示FSX引脚输出帧同步;FSRM=1表示接收帧同步由内部产生。FSXP设置为1,片选低有效,这一步别漏。

McBspaRegs.PCR.bit.CLKXM = 1; McBspaRegs.PCR.bit.CLKRM = 1; McBspaRegs.PCR.bit.FSXM = 1; McBspaRegs.PCR.bit.FSRM = 1; McBspaRegs.PCR.bit.FSXP = 1; McBspaRegs.PCR.bit.FSRP = 1;

帧同步的参数FPER和FWID(帧周期、帧宽度)一般保持默认即可。至少在接W25Q64、AD7606这些常规设备时,我没有因为这两个参数出过问题。如果之后遇到片选时序特别苛刻的从设备,再回到SRGR寄存器去调整这两个字段不迟。

3.5 第五步:解除复位并启动收发(这个顺序不能乱)

所有配置写完之后,启动顺序是:先启动采样率发生器,再启动发送器和接收器,最后解除串口软复位。不要一次性把所有复位位都置1,中间留一点延时会更稳妥。

McBspaRegs.SPCR2.bit.GRST = 1; // 启动采样率发生器 DELAY_US(2); McBspaRegs.SPCR2.bit.XRST = 1; // 启动发送器 McBspaRegs.SPCR2.bit.RRST = 1; // 启动接收器 DELAY_US(2); McBspaRegs.SPCR1.bit.SWRST = 1; // 解除串口软复位

这个顺序的原理是:采样率发生器是时钟源头,它不动,发送器和接收器即使解除了复位也没有时钟可用;接收器又依赖发送器在SPI模式下产生传输机会,所以发送器先于接收器启动。SWRST在最后,是为了让前面所有模块的初始化都完成后,整个串口再进入工作状态。配置过程中如果发现某个参数要改,建议重新把SWRST清零、修改寄存器、再重新按这个顺序启动,不要直接在运行状态下改CLKGDV之类的关键参数,那样大概率不生效,甚至会引起总线错误。

4. 可以直接用的初始化代码和收发函数

4.1 工程全局变量与头文件核对

不同版本的DSP2833x工程里,McBSP模块的寄存器全局变量名略有不同。有的工程里叫McBspaRegs,有的叫McBspA,还有的旧工程直接沿用了SpiaRegs这种命名。我写这篇文章的时候,默认你的工程能在DSP2833x_GlobalVariableDefs.c里找到McBspaRegs和McBspbRegs这两个变量。如果找不到,先花两分钟确认一下你手上头文件里实际的名字,把下面代码里的标识符替换掉即可。

寄存器位定义也一样。新版ControlSUITE里MCBSP_REGS结构体对SPCR1、SPCR2、PCR等寄存器都做了位域定义,可以直接用.bit.xxx访问。老版本如果没拆分位域,就只能整个寄存器赋值,那就得小心别把保留位也写了。建议尽量用新版头文件。

4.2 初始化函数完整示例

这是一个完整的McBSP配置为SPI主机的初始化函数,参数按8位数据、Mode 0、3.75MHz时钟来配:

void McBSP_InitAsSPI_Master(void) { EALLOW; // 1. 使能McBSP-A外设时钟 SysCtrlRegs.PCLKCR0.bit.MCBSPAENCLK = 1; DELAY_US(1); // 2. GPIO引脚复用:GPB14 -> MFSXA, GPB15 -> MCLKXA // GPB16 -> MDXA, GPB17 -> MDRA GpioCtrlRegs.GPBMUX1.all &= 0xFFF0FFFF; GpioCtrlRegs.GPBMUX1.all |= 0x50000000; GpioCtrlRegs.GPBMUX2.all &= 0xFFFFFF00; GpioCtrlRegs.GPBMUX2.all |= 0x00000005; // 3. 全部进入复位状态 McBspaRegs.SPCR1.bit.SWRST = 0; McBspaRegs.SPCR2.bit.XRST = 0; McBspaRegs.SPCR2.bit.RRST = 0; McBspaRegs.SPCR2.bit.GRST = 0; // 4. SPI模式:CLKSTP=10b,对应Mode 0 McBspaRegs.SPCR1.bit.CLKSTP = 2; McBspaRegs.SPCR1.bit.RINTM = 0; McBspaRegs.SPCR2.bit.XINTM = 0; McBspaRegs.SPCR1.bit.DLB = 0; // 自测时改为1 // 5. 采样率发生器:SCLK = LSPCLK / (CLKGDV+1) // 假设LSPCLK=37.5MHz,CLKGDV=9,SCLK=3.75MHz McBspaRegs.SRGR1.bit.CLKGDV = 9; McBspaRegs.SRGR2.bit.CLKSM = 1; // 内部时钟源 // 6. 数据格式:8位,单相 McBspaRegs.XCR1.bit.XWDLEN1 = 0; McBspaRegs.RCR1.bit.RWDLEN1 = 0; // 7. 引脚控制 McBspaRegs.PCR.bit.CLKXM = 1; // CLKX输出 McBspaRegs.PCR.bit.CLKRM = 1; // 内部时钟驱动接收 McBspaRegs.PCR.bit.FSXM = 1; // FSX输出 McBspaRegs.PCR.bit.FSRM = 1; // 内部产生接收帧同步 McBspaRegs.PCR.bit.FSXP = 1; // FSX低有效,匹配CS低有效 McBspaRegs.PCR.bit.FSRP = 1; McBspaRegs.PCR.bit.CLKXP = 0; // 时钟极性,配合Mode 0 McBspaRegs.PCR.bit.CLKRP = 0; // 8. 按顺序启动 McBspaRegs.SPCR2.bit.GRST = 1; DELAY_US(2); McBspaRegs.SPCR2.bit.XRST = 1; McBspaRegs.SPCR2.bit.RRST = 1; DELAY_US(2); McBspaRegs.SPCR1.bit.SWRST = 1; EDIS; }

GPIO复用那两行的数值,我在前面已经提醒过了,一定要对照自己的芯片手册核对。特别是GPBMUX2里那几位的功能值,不同封装的28335可能存在差异。我在自己板子上用的功能值和网上某些教程给的有出入,最后是拿TI官方数据手册GPIO功能表一行一行核出来的。

4.3 单字节全双工收发函数的写法

McBSP本质上是一个全双工串口。每次向DXR1写入数据,就会启动一次传输,同时接收移位寄存器同步接收从设备返回的数据,完成后RRDY置位,读DRR1就是本次传输收到的内容。所以发一个字节和收一个字节是同一个动作,写一个函数就够了:

Uint16 McBSP_TransferByte(Uint16 txData) { // 等待发送就绪 while (McBspaRegs.SPCR2.bit.XRDY == 0); // 写入数据,启动一次传输 McBspaRegs.DXR1.all = txData; // 等待接收完成 while (McBspaRegs.SPCR1.bit.RRDY == 0); // 返回接收数据 return McBspaRegs.DRR1.all; }

这个函数最关键的一点:不能只等XRDY写完就返回,必须再等RRDY把接收数据读完。因为SPI主机每发一个字节,时钟会产生8个脉冲,从设备就在这8个脉冲里把数据返回给主机。如果不等RRDY,下一次写入DXR1可能会把还没读走的接收数据覆盖掉。我第一次写这个函数时就只看了XRDY,结果连续读寄存器时数据总是不对,后来在RRDY上仔细看了时序才明白原因。

如果要做多字节传输,就循环调用这个函数。注意从MCU到从设备的“发数据”和“读数据”是同时发生的:主机发0x9F的时候,收到的是从设备在命令阶段返回的无效数据,直接丢弃;主机接着发3个0x00的时候,才是真正在读取JEDEC ID的3个字节。

4.4 用读W25Q64 JEDEC ID验证整个链路

理论讲再多不如跑一个实际用例。我常用W25Q64来验证McBSP配置是否成功,因为它的JEDEC ID读取指令0x9F非常简单,从设备收到命令后会自动返回3个字节:制造商ID、存储类型、容量代码。W25Q64对应的返回值是0xEF、0x40、0x17。

测试代码如下:

#define W25QXX_CMD_JEDEC_ID 0x9F Uint8 w25q_id[3]; McBSP_TransferByte(W25QXX_CMD_JEDEC_ID); // 发送命令,收到的数据丢弃 w25q_id[0] = (Uint8)McBSP_TransferByte(0x00); w25q_id[1] = (Uint8)McBSP_TransferByte(0x00); w25q_id[2] = (Uint8)McBSP_TransferByte(0x00);

如果读出来的三个字节和预期一致,说明整条链路基本没问题:GPIO复用正确、CLKSTP模式正确、时钟极性正确、片选逻辑正确。如果读出来全0xFF,优先检查MDRA引脚是不是没配置成外设功能,或者从设备的MISO和主机的MDRA接反了。如果读出来全0x00,多半是MOSI/MISO搞反,或者时钟相位不对,从设备根本没把数据送出来。先用这个用例把链路打通,再接入具体业务,后面就顺了。

5. 调试过程中踩到的三个坑(先说结论再给链路)

5.1 坑一:CLKSTP配了但SCLK完全没波形

先说一个我犯过的蠢事。有次换了一块新板子,寄存器配置完全复制的上一块板工程,结果示波器探针点在MCLKXA引脚上,硬是看不到一点时钟波形。当时第一反应是CLKSTP没生效,来回改寄存器值,浪费了一下午。

实际原因是新板子GPB15引脚默认被跳线帽短接到其他网络上去了。查原理图才发现,这个引脚同时引到了板上预留的音频接口和SPI Flash测试点,跳线帽默认焊死,时钟信号被旁路掉了。把跳线改掉之后,波形立刻出来了。

所以排查顺序应该是这个链路:先确认PCLKCR0里MCBSPAENCLK有没有置1,外设时钟没开一切免谈;再确认GPIO复用是否配置成功,手动读一下GPBMUX1和GPBMUX2的寄存器值,看是不是和预期一致;然后检查有没有其他外设跳线、测试点、上下拉电阻干扰这个引脚;最后才轮到怀疑CLKSTP和采样率发生器。

示波器探头接触不良这种低级问题也遇到过,所以一旦没波形,先把探头在别的引脚上测一下确认仪器正常。

5.2 坑二:读回来的数据是0xFF或者连续错位

读回0xFF这个问题在SPI调试里特别典型。从设备没有回应时,MISO引脚在主机侧通常会被上拉电阻拉高,所以主机读到的就是0xFF。我遇到过的情况有四种:MDRA引脚复用没配对、从设备供电没起来、从设备复位脚被拉低、FSX极性配置反了导致从设备根本没被选中。其中FSX极性最容易被忽视。SPI从机的CS是低有效,而McBSP的FSX默认可能是高有效,如果FSXP没设成1,从机一直处于未选中状态,自然不回应。

数据连续错位的问题则多半出在时钟极性/相位的匹配上。可以从最基础的Mode 0开始试,然后用示波器同时抓SCLK和MISO,对照从设备手册的时序图,看清从设备到底是在上升沿还是下降沿输出数据、主机又是在哪个边沿采样。两个边沿对不上,数据就会整体错半拍,表现出来就是读回的数据像是“左移”或者“右移”了一位。

这里有个非常实用的排查方法:先把SPCR1的DLB置1做内部环回。DLB模式下,发送数据在芯片内部直接回到接收通路,无需外部接线。如果环回模式下自发自收还能收到自己发的数据,说明McBSP本身的收发通路没问题;如果环回都不通,那就是寄存器配置本身有错,跟从设备无关。这个技巧能大大缩小排查范围。

5.3 坑三:片选信号看起来“有点怪”——FSX和CS的差异

McBSP的FSX在SPI模式下的行为,和硬件SPI模块的CS不完全一样。硬件SPI的CS是一个电平信号,你可以通过软件把它拉低,然后连续发任意多个字节,再统一拉高。McBSP的FSX则是按帧工作的,每传输一帧数据,FSX都会拉低一下、传输完再拉高。如果你的从设备只是“发命令然后读几个字节”,这个行为完全没问题;但如果从设备要求CS在整个操作期间持续为低,McBSP默认的FSX行为就会出岔子。

最典型的例子是某些Flash芯片的页编程或连续读操作,要求CS保持低电平一整段序列,中间不允许拉高。这时候如果还用FSX当CS,就可能出现在MCU看来一切正常、但从设备实际已经提前退出操作的情况。

我的处理方案很简单:遇到这类从设备,就放弃用FSX做片选,单独用一个GPIO手动控制CS。McBSP只承担SCLK、MOSI、MISO三线的工作,CS由普通GPIO控制,传输开始前拉低,整个命令序列结束后拉高,中间FSX怎么跳变都不影响从设备。反正McBSP的时钟只在传输时跑,只要CS不拉高,从设备就会认为当前操作还在持续。这样等于把McBSP的帧同步限制绕开了,可靠性和普通硬件SPI没有区别。

// 用GPIO做片选的示例思路 GpioDataRegs.GPBDAT.bit.GPIOB12 = 0; // 手动拉低CS McBSP_TransferByte(0x06); // 发送写使能命令 // ... 连续发多个字节 ... GpioDataRegs.GPBDAT.bit.GPIOB12 = 1; // 操作结束拉高CS

看清楚,这里GPIOB12只是举例,实际用哪个引脚看你的电路。这个方法牺牲一个GPIO,换来的是对从设备更严格的兼容性。如果你把代码从硬件SPI往McBSP移植,建议一开始就评估从设备的CS行为,别等出了诡异问题再回来补。

我在实际项目里用McBSP配置成SPI跑过W25Q64、AD7606和几个SPI接口的传感器,只要把CS策略选对,稳定性和硬件SPI没有可感知的差别。CLKSTP这个位就是TI为SPI场景专门留的口子,好好利用它,28335的资源压力能缓解不少。如果你也在做SPI设备扩容,建议先拿DLB自环和读JEDEC ID这两个用例把链路验证通过,再接入具体业务设备。调试时记得多依赖示波器,少依赖猜。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询