首先,叠加环节:
1.才入门dsp,我的理解和代码可能会有错误,主要供作者本人参考!!!
2.如果您发现我的文章错误请联系我,谢谢。
3.基本所有资料都是从网上查找的,如有侵权,请联系我。
4.我是用的芯片TMS320F280039
目录
一、SPI主要结构
SPI通信:高速、全双工、同步。
以上是SPI的主要硬件结构:
1.区分主机、从机 。主从不同,相关的配置也不一样。
2.接口:
MOSI连接SIMO:主设备输出、从设备输入。
MISO连接SOMI:主设备输入、从设备输出。
SCLK:时钟
CS(STE):片选信号
注意:当有数据从主机发出,那么必有数据从从机发出,也就是说在主机发出数据,从机没有发送数据的同时,主机这个时候去接收也会接收到一串数据,我遇到的情况就是接收到了主机接收到了主机发出的数据。
SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 "发送者(Transmitter)" 或者 "接收者(Receiver)". 在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据(不管主设备好还是从设备), 相当于该设备有一个 bit 大小的数据被交换了。
具体看这篇文章:
https://blog.csdn.net/qq_41899480/article/details/100041428
二、SPI配置
1.SPI外设时钟配置
首先使能SPI的外设时钟,我只使用了SPIA,SPIB可以不写:
EALLOW;
CpuSysRegs.PCLKCR8.bit.SPI_A = 1;
CpuSysRegs.PCLKCR8.bit.SPI_B = 1;
EDIS;
2. GPIO初始化
首先,查找芯片手册,找对应引脚数的芯片,然后找使用的功能的复用通道。
注意,每个功能可能有很多个GPIO可以使用,要找到自己想用的,或者开发板支持的引脚。我使用的是f280039。对应的引脚号:
SPI SIMOA --> GPIO54
SPI SOMIA --> GPIO55
SPI CLKA --> GPIO12
SPI STEA --> GPIO57
然后是GPIO初始化:
SCLK:主设备推挽输出、从设备浮空输入,一般不上拉。
MOSI:主设备推挽输出、从设备浮空输入,一般不上拉。
MISO:若从设备推挽输出,则主设备浮空输入。
若从设备开漏输出,则主设备上拉输入。
CS/STE:主设备推挽输出、从设备浮空输入。
2.1 主设备GPIO配置
2.2 从设备GPIO配置:
从设备这里只写了gpio,也需要加上EALLOW、EDIS、外设时钟使能。
3.外设时钟分频配置
SPI的时钟源是LSPCLK。
时钟频率配置过程:
输入时钟信号--->refdiv分频-->imult倍频-->odiv分频-->SYSCLK(主时钟)-->LSPCLKDIV(低速外设时钟分频)-->LSPCLK(低速外设时钟)
(1)查看时钟源:SPI的时钟源是外设时钟,查看开发板原理图的外部晶振频率,我的外部晶振是20Mhz。
(2)查看倍频、分频:芯片手册查看不同的值对应的功能。
主时钟配置如下(使用的官方库函数 在f28003x_sysctrl.c文件的void InitSysCtrl(void) 中 ):
一般不用改这里,官方基本是配置好的
计算过程:20Mhz*48/2/4=120Mhz(主时钟频率)
低速外设时钟配置如下:
这里赋值3是因为参考手册写的0x011的时候为6分频。
计算过程:120Mhz/6=20Mhz
4.SPI初始化配置
// SPI核心寄存器配置(复位状态下设置)
SpiaRegs.SPICCR.bit.SPISWRESET = 0; // 复位SPI模块(!!!必须先复位才能修改配置!!!)
SpiaRegs.SPICCR.bit.CLKPOLARITY = 0; // 时钟极性=0:上升沿发送数据,下降沿接收数据
SpiaRegs.SPICCR.bit.SPILBK = 0; // 禁用回环模式(正常外部通信)******我不确定这个可不可以不写******
SpiaRegs.SPICCR.bit.SPICHAR = 0xF; // 字符长度=16位(0xF + 1 = 16)
SpiaRegs.SPICTL.bit.CLK_PHASE = 0; // 时钟相位=0:无延迟(数据在时钟第一个边沿采样)
SpiaRegs.SPICTL.bit.MASTER_SLAVE = 1; // 主模式(1=主机,0=从机)
SpiaRegs.SPICTL.bit.TALK = 1; // 使能数据传输(允许主机发送数据)******主机要写,从机应该为0******
SpiaRegs.SPICTL.bit.SPIINTENA = 0; // 禁用标准SPI中断(使用FIFO中断替代)
SpiaRegs.SPIBRR.bit.SPI_BIT_RATE = 0x0031; // 波特率寄存器值=49,波特率=LSPCLK/(SPIBRR+1)=20MHz/50=400kHz
// 高级控制:调试模式设置
SpiaRegs.SPIPRI.bit.FREE = 1; // 自由运行模式:调试断点不影响SPI传输******可以不写*****
// FIFO发送配置
SpiaRegs.SPIFFTX.bit.SPIRST = 1; // 复位FIFO发送缓冲区
SpiaRegs.SPIFFTX.bit.SPIFFENA = 1; // 使能FIFO功能(替代标准SPI缓冲区)
SpiaRegs.SPIFFTX.bit.TXFFINTCLR = 1; // 清除发送FIFO中断标志(先设1后清0)
SpiaRegs.SPIFFTX.bit.TXFFINTCLR = 0; // 完成清除操作
SpiaRegs.SPIFFTX.bit.TXFFIENA = 0; // 禁用发送FIFO中断*****一般写1来打开TXFFIENA 中断,我有特殊要求所以关闭****
SpiaRegs.SPIFFTX.bit.TXFFIL = 4; // 发送FIFO中断阈值=4
// FIFO接收配置
SpiaRegs.SPIFFRX.bit.RXFFOVFCLR = 1; // 清除接收FIFO溢出标志
SpiaRegs.SPIFFRX.bit.RXFFOVFCLR = 0; // 完成清除操作
SpiaRegs.SPIFFRX.bit.RXFFINTCLR = 1; // 清除接收FIFO中断标志
SpiaRegs.SPIFFRX.bit.RXFFIENA = 0; // 禁用接收FIFO中断*****一般写1来打开RXFFIENA 中断,我有特殊要求所以关闭****
SpiaRegs.SPIFFRX.bit.RXFFIL = 4; // 接收FIFO中断阈值=4
// FIFO传输延迟控制
SpiaRegs.SPIFFCT.bit.TXDLY = 0; // 发送字符间延迟=0(无额外延迟)
SpiaRegs.SPIFFCT.all = 0x00; // 整个延迟寄存器清零(确保默认状态)*****我不确定是不是必须写*****
// 激活FIFO缓冲区
SpiaRegs.SPIFFTX.bit.TXFIFO = 0; // 复位发送FIFO
SpiaRegs.SPIFFTX.bit.TXFIFO = 1; // 使能发送FIFO
SpiaRegs.SPIFFRX.bit.RXFIFORESET = 0; // 复位接收FIFO
SpiaRegs.SPIFFRX.bit.RXFIFORESET = 1; // 使能接收FIFO
// 最终使能SPI模块
SpiaRegs.SPICCR.bit.SPISWRESET = 1; // 释放SPI复位,模块开始工作
步骤说明(我的配置和下面的步骤差不多):
1.配置SPI_CR.MASTER=0;
2.主从模式选择,选择极性、相位、数据长度、波特率;
3.使能FIFO(SPIFFENA);
4.清除 FIFO 标志(TXFFINTCLR、RXFFOVFCLR 和 RXFFINTCLR);
5.使能接收、发送FIFO中断(TXFFIENA、RXFFIENA);
6.解锁发送和接收 FIFO 重置(TXFFIFO 和 RXFFIORESET);
7.配置SPI_CR.MASTER=1;
5.FIFO中断
中断原理
中断触发过程
外设级:
- 外设事件触发中断标志位,若使能位开启,则向 PIE 提交请求。
PIE 级:
- 将 96 个外设中断分组为 12 组(INT1–INT12),每组 8 个中断(INTx.1–INTx.8)。
- 通过 PIEIERx(组使能)、PIEIFRx(组标志)、PIEACKx(组应答)三级寄存器控制中断传递 。
CPU 级:
- 接收 PIE 的 INTx 信号后,置位 IFR 标志位。
- 若 IER 使能对应 INTx 且 INTM = 0(全局中断开启),则 CPU 响应该中断 。
中断优先级:
-
CPU 级优先级:
INT1 > INT2 > ... > INT12
(固定) -
PIE 组内优先级:
INTx.1 > INTx.2 > ... > INTx.8
(硬件固定) -
若需调整 SPI 中断优先级,需通过
PIEIERx
的使能顺序实现。
FIFO 阈值:
-
TXFFIL
:建议设为 FIFO 深度的 1/4(如 16 级 FIFO 设TXFFIL=4
) -
RXFFIL
:建议略大于预期单次数据包大小(避免频繁中断)
SPI FIFO中断触发机:
以下是符合要求的表格格式,可直接插入文章:
FIFO阈值控制说明
寄存器 | 功能描述 | 触发条件 | 设计目的 |
---|---|---|---|
RXFFIL | 接收FIFO数据量 ≥ 阈值 | RXFFST ≥ RXFFIL | 避免频繁中断,数据积累到一定量再处理 |
TXFFIL | 发送FIFO数据量 ≤ 阈值 | TXFFST ≤ TXFFIL | 防止FIFO空转,维持连续传输 |
- 当接收FIFO中的数据量(
RXFFST
)大于或等于 RXFFIL设定的阈值时,若RXFFIENA=1
(接收中断使能),则触发SPIRXINT中断 。 - 当发送FIFO中的数据量(
TXFFST
)小于或等于 TXFFIL设定的阈值时,若TXFFIENA=1
(发送中断使能),则触发SPITXINT中断 。
具体触发中断的方法:
//发送中断触发
for(i=0; i<4; i++)//深度是4,所以一次性填写4次,TXFFST=4等于TXFFIL=4。触发FIFO发送中断。填写的次数小于4也可以触发。
{
SpiaRegs.SPITXBUF = SpiFrame.TxBuf[TxIndex];
TxIndex++;
}
//接收中断触发
for(i=0;i<Spi_Len;i++)//深度是4,所以一次性填写4次,RXFFST=4等于RXFFIL=4。触发FIFO接收中断。填写的次数大于4也可以触发。
{
SpiFrame.RxBuf[RxIndex]=SpiaRegs.SPIRXBUF;
TxIndex++;
}
注意:当在spi的中断配置时使能发送中断(SpiaRegs.SPIFFTX.bit.TXFFIENA = 1;)会导致spi初始化一结束就会触发发送中断,进入发送中断服务函数。
SPI中断配置
// 中断配置:使能CPU级和PIE级中断
EALLOW;
IER |= M_INT6; // 使能CPU中断组6(SPI中断通常映射到此组)
EINT; // 开启全局中断(INTM = 0)
PieCtrlRegs.PIEIER6.bit.INTx1 = 1; // 使能PIE组6的INTx1(接收中断)
PieCtrlRegs.PIEIER6.bit.INTx2 = 1; // 使能PIE组6的INTx2(发送中断)
PieVectTable.SPIA_RX_INT = &SpiRxIsr; // 绑定接收中断服务程序
PieVectTable.SPIA_TX_INT = &SpiTxIsr; // 绑定发送中断服务程序
EDIS;
中断服务函数
interrupt void SpiTxIsr(void)
{
......//处理发送数据
SpiaRegs.SPIFFTX.bit.TXFFINTCLR = 1; // 清除发送中断标志
PieCtrlRegs.PIEACK.all = PIEACK_GROUP6; // 中断返回前清楚该标志位,我的是GROUP6。在对应的GROUP6的bit上写1。
}
interrupt void SpiRxIsr(void)
{
......//处理发送数据
SpiaRegs.SPIFFRX.bit.RXFFOVFCLR = 1; // 清除接收溢出中断标志
SpiaRegs.SPIFFRX.bit.RXFFINTCLR = 1; // 清除接收中断标志
PieCtrlRegs.PIEACK.all = PIEACK_GROUP6;
}
步骤说明:
1.需要写寄存器保护的解除、结束要写恢复。
2.配置外设中断,即初始化PIE向量表,将发送(SpiTxIsr)、接收(SpiRxIsr)中断服务函数绑定。
3.使能PIE中断。
4.使能CPU中断。
5.开启全局中断。
6.!!!!中断服务函数void前写interrupt 。
7.在中断服务函数结束时清除外设中断标志、打开闸门(PieCtrlRegs.PIEACK相应的bit写1)。
可能遇到的问题:
- 同组高优先级中断阻塞低优先级:原因是PIEACKx 未及时清除,必须在 ISR 内清除 PIEACKx来解决。
当中断从PIE传播到CPU中断线时,该中断组的PIEACK位会被设置。这可以防止在处理第一个中断时,该组中的其他中断继续传播到CPU。向PIEACK位写入1可以清除该位,允许来自同一组的其他中断继续。
中断启用逻辑(并行触发中断、并行执行中断服务函数)
因为是全双工,所以软件层面来说可以同时发送、接收。主要看硬件层面是否支持。
如果硬件层面两个中断没有互斥逻辑就可以触发。
场景 | 是否并行触发 | 是否并行响应 | 约束条件 |
---|---|---|---|
FIFO阈值同时满足 | ✅ 是 | ✅ 是(硬件支持) | 无资源冲突 |
CPU单核处理 | ✅ 是 | ⚠️ 顺序响应 | 优先级决定顺序(INT6.1先于INT6.2) |
中断嵌套使能 | ✅ 是 | ✅ 是 | 需配置 INTM=0 及嵌套优先级 |
F280039中SPITXINT、SPIRXINT是两个独立的外设中断源,映射到PIE的不同中断线上:INT6.1、INT6.2。
1)并行触发中断

这一部分我也不太清楚,大概是这个意思:F280039硬件支持并行触发中断,所以可以并行触发中断.
2)并行执行中断服务函数
在CPU中断使能的情况下,它们可以同时被挂起,但由于CPU单核的限制,中断服务函数(ISR)不能真正同时执行(除非使用中断嵌套),让高优先级中断打断低优先级中断。
但是DSP\F280039好像是支持并行处理的。
一下是AI生成的解释(有没有大佬帮我看看这部分对不对):
- 发送和接收中断分别映射到INT6.1和INT6.2(假设接收为INT6.1,发送为INT6.2),组内优先级接收高于发送。
- 如果接收中断服务函数正在执行,此时发生发送中断,那么发送中断会等待,直到接收中断执行完毕(除非在接收中断中打开了全局中断,允许嵌套)。
因此,要实现“同时进行”的效果,可以在接收中断服务函数中开启全局中断(EINT),这样发送中断就可以打断接收中断,从而形成嵌套。这样,发送中断服务函数就可以在接收中断服务函数执行过程中被执行。
附录一:相关资料
TI的spi配置文档:
https://coecsl.ece.illinois.edu/me461/Labs/SPICondensed_TechRef.pdf
和我一起学习dsp开发的人写的SPI的链接,这一篇从机的配置多一些可以参考:
https://blog.csdn.net/2301_76477834/article/details/148395050?spm=1001.2014.3001.5502
附录二:代码
void InitSpiAGpio(void)
{
EALLOW;
CpuSysRegs.PCLKCR8.bit.SPI_A = 1; // SPI-A 外设时钟使能
GPIO_SetupPinMux(GPIONUM_54, GPIO_MUX_CPU1, 1);
GPIO_SetupPinOptions(GPIONUM_54, GPIO_OUTPUT, GPIO_OUTPUT, GPIO_PUSHPULL);
GPIO_SetupPinMux(GPIONUM_55, GPIO_MUX_CPU1, 1);
GPIO_SetupPinOptions(GPIONUM_55, GPIO_INPUT, GPIO_PULLUP);
GPIO_SetupPinMux(GPIONUM_12, GPIO_MUX_CPU1, 11);
GPIO_SetupPinOptions(GPIONUM_12, GPIO_OUTPUT, GPIO_PUSHPULL);
GPIO_SetupPinMux(GPIONUM_57, GPIO_MUX_CPU1, 1);
GPIO_SetupPinOptions(GPIONUM_57, GPIO_OUTPUT, GPIO_PUSHPULL);
EDIS;
}
void SpiAInit(void)
{
InitSpiAGpio();//GPIO
EALLOW;
ClkCfgRegs.LOSPCP.bit.LSPCLKDIV = 3; // 120mhs/6=20mhs
EDIS;
EALLOW;
IER |= M_INT6; // Enable interrupts:
PieCtrlRegs.PIEIER6.bit.INTx1 = 1;
PieCtrlRegs.PIEIER6.bit.INTx2 = 1;
PieVectTable.SPIA_RX_INT = &SpiRxIsr; //
PieVectTable.SPIA_TX_INT = &SpiTxIsr; //
EDIS;
// SPI核心寄存器配置(复位状态下设置)
SpiaRegs.SPICCR.bit.SPISWRESET = 0; // 复位SPI模块(!!!必须先复位才能修改配置!!!)
SpiaRegs.SPICCR.bit.CLKPOLARITY = 0; // 时钟极性=0:上升沿发送数据,下降沿接收数据
SpiaRegs.SPICCR.bit.SPILBK = 0; // 禁用回环模式(正常外部通信)******我不确定这个可不可以不写******
SpiaRegs.SPICCR.bit.SPICHAR = 0xF; // 字符长度=16位(0xF + 1 = 16)
SpiaRegs.SPICTL.bit.CLK_PHASE = 0; // 时钟相位=0:无延迟(数据在时钟第一个边沿采样)
SpiaRegs.SPICTL.bit.MASTER_SLAVE = 1; // 主模式(1=主机,0=从机)
SpiaRegs.SPICTL.bit.TALK = 1; // 使能数据传输(允许主机发送数据)******主机要写,从机应该为0******
SpiaRegs.SPICTL.bit.SPIINTENA = 0; // 禁用标准SPI中断(使用FIFO中断替代)
SpiaRegs.SPIBRR.bit.SPI_BIT_RATE = 0x0031; // 波特率寄存器值=49,波特率=LSPCLK/(SPIBRR+1)=20MHz/50=400kHz
// 高级控制:调试模式设置
SpiaRegs.SPIPRI.bit.FREE = 1; // 自由运行模式:调试断点不影响SPI传输******可以不写*****
// FIFO发送配置
SpiaRegs.SPIFFTX.bit.SPIRST = 1; // 复位FIFO发送缓冲区
SpiaRegs.SPIFFTX.bit.SPIFFENA = 1; // 使能FIFO功能(替代标准SPI缓冲区)
SpiaRegs.SPIFFTX.bit.TXFFINTCLR = 1; // 清除发送FIFO中断标志(先设1后清0)
SpiaRegs.SPIFFTX.bit.TXFFINTCLR = 0; // 完成清除操作
SpiaRegs.SPIFFTX.bit.TXFFIENA = 0; // 禁用发送FIFO中断*****一般写1来打开TXFFIENA 中断,我有特殊要求所以关闭****
SpiaRegs.SPIFFTX.bit.TXFFIL = 4; // 发送FIFO中断阈值=4
// FIFO接收配置
SpiaRegs.SPIFFRX.bit.RXFFOVFCLR = 1; // 清除接收FIFO溢出标志
SpiaRegs.SPIFFRX.bit.RXFFOVFCLR = 0; // 完成清除操作
SpiaRegs.SPIFFRX.bit.RXFFINTCLR = 1; // 清除接收FIFO中断标志
SpiaRegs.SPIFFRX.bit.RXFFIENA = 0; // 禁用接收FIFO中断*****一般写1来打开RXFFIENA 中断,我有特殊要求所以关闭****
SpiaRegs.SPIFFRX.bit.RXFFIL = 4; // 接收FIFO中断阈值=4
// FIFO传输延迟控制
SpiaRegs.SPIFFCT.bit.TXDLY = 0; // 发送字符间延迟=0(无额外延迟)
SpiaRegs.SPIFFCT.all = 0x00; // 整个延迟寄存器清零(确保默认状态)*****我不确定是不是必须写*****
// 激活FIFO缓冲区
SpiaRegs.SPIFFTX.bit.TXFIFO = 0; // 复位发送FIFO
SpiaRegs.SPIFFTX.bit.TXFIFO = 1; // 使能发送FIFO
SpiaRegs.SPIFFRX.bit.RXFIFORESET = 0; // 复位接收FIFO
SpiaRegs.SPIFFRX.bit.RXFIFORESET = 1; // 使能接收FIFO
// 最终使能SPI模块
SpiaRegs.SPICCR.bit.SPISWRESET = 1; // 释放SPI复位,模块开始工作
}
interrupt void SpiTxIsr(void)
{
......//处理发送数据
SpiaRegs.SPIFFTX.bit.TXFFINTCLR = 1; // 清除发送中断标志
PieCtrlRegs.PIEACK.all = PIEACK_GROUP6; // 中断返回前清楚该标志位,我的是GROUP6。在对应的GROUP6的bit上写1。
}
interrupt void SpiRxIsr(void)
{
......//处理发送数据
SpiaRegs.SPIFFRX.bit.RXFFOVFCLR = 1; // 清除接收溢出中断标志
SpiaRegs.SPIFFRX.bit.RXFFINTCLR = 1; // 清除接收中断标志
PieCtrlRegs.PIEACK.all = PIEACK_GROUP6;
}