第21章 SPI串行外设接口实验(通俗版讲解)
结合普中DSP28335开发板+CCS6环境,从零拆解SPI,避开晦涩术语,从「是什么→硬件→寄存器→代码→实操」一步步讲,新手也能看懂。
一、先搞懂:SPI到底是什么?
1. 通俗理解SPI
SPI(串行外设接口)是一种高速同步串行通信总线 ,简单说:
它是DSP和外部芯片之间的有线通信通道 ,用来在两个芯片之间互相传数据。
- 同步:收发双方共用同一根时钟线,节奏完全统一,不会出现数据错位;
- 串行:数据一位一位依次传输,不是一次性传一整串;
- 优势:通信速度快、硬件接线少、协议简单,是嵌入式最常用的通信方式之一。
2. SPI 4根核心引脚(必记)
SPI 标准接口一共4根线,功能分工明确,结合28335开发板讲解:
| 引脚名称 | 中文名称 | 作用 |
|---|---|---|
| SPICLK | 串行时钟 | 通信节拍器,由**主机(DSP)**发出时钟脉冲,控制数据收发速度 |
| SPISIMO | 主机输出/从机输入 | DSP 发数据给外部芯片(DSP→外设) |
| SPISOMI | 主机输入/从机输出 | 外部芯片 发数据给DSP(外设→DSP) |
| SPISTE | 片选引脚 | 设备选择开关。低电平选中对应外设,高电平断开通信 |
补充知识点:
- 主从模式 :28335 DSP 默认作为主机 (主动发起通信、提供时钟),外接芯片为从机(被动响应),这是我们实验的默认模式;
- 片选SPISTE:一块DSP可以挂载多个SPI外设,靠这根线区分当前和哪个芯片通信。
3. SPI 四种工作模式(重点简化)
SPI 靠**时钟极性(CPOL)和时钟相位(CPHA)**组合出4种模式,本质只有两个规则:
- CPOL(时钟空闲电平):总线不通信时,时钟线是高电平还是低电平;
- CPHA(数据采样沿) :在时钟上升沿/下降沿读取数据。
日常开发、本实验最常用:模式0(CPOL=0,CPHA=0)
- 空闲时:SPICLK = 低电平;
- 时钟第一个上升沿 采样数据,下降沿移位;
我们整个实验都使用SPI模式0,不用纠结其他模式。
二、硬件电路(开发板接线)
1. 板载SPI电路说明
普中PZ-DSP28335-L开发板上,F28335自带1路硬件SPI,引脚固定复用:
- SPICLK → GPIO56
- SPISIMO → GPIO55
- SPISOMI → GPIO54
- SPISTE → GPIO57
这组引脚默认和板上外设预留接口相连,实验分两种场景:
- 板载测试:直接使用开发板预留SPI接口,无需额外飞线;
- 外接SPI设备(如SPI Flash、SPI数码屏):将外设对应4根线,一一接到上述GPIO引脚即可,共地(GND必须相连,否则通信失败)。
关键提醒:
所有串行通信必须共地,GND相连是基础,否则电平参考不一致,数据乱码。
2. 硬件接线总结
DSP(SPI主机) ↔ 外部SPI从机
- SPICLK ↔ SPICLK(时钟对齐)
- SPISIMO ↔ 从机DATA_IN(DSP发数据)
- SPISOMI ↔ 从机DATA_OUT(DSP收数据)
- SPISTE ↔ 从机CS(片选)
- GND ↔ GND(必接)
三、SPI核心寄存器(只讲实验要用的,剔除冗余)
F28335的SPI寄存器很多,实验只用到5个核心,结合功能通俗讲解,不用死记,代码里会对应使用。
1. 外设时钟寄存器(PCLKCR0)
作用:开启SPI模块时钟
DSP所有外设默认时钟关闭,不开启时钟,SPI完全无法工作。
对应位:PCLKCR0.bit.SPIAENCLK = 1 → 使能SPI时钟。
2. SPI配置控制寄存器(SPICCR)
核心配置项:
- SPI软件复位位:初始化前先复位SPI,清空错误状态;
- 时钟极性CPOL:配置空闲电平(我们设为0,空闲低电平);
- 字符长度:设置单次传输位数(本实验用8位,最常用)。
3. SPI操作寄存器(SPICTL)
核心配置项:
- 主/从模式选择 :
MASTER/SLAVE,我们设为主机模式; - 时钟相位CPHA:搭配CPOL使用(模式0设为0);
- 收发使能:开启发送、接收功能。
4. 波特率寄存器(SPIBRR)
作用:设置SPI通信速度
SPI时钟频率计算公式(主机模式):
SPI时钟 = 低速外设时钟LSPCLK / (SPIBRR + 1)
举例:
系统主频150MHz,低速分频后LSPCLK=37.5MHz
若设置 SPIBRR=3 → 通信频率 = 37.5/(3+1) = 9.375MHz
数值越大,通信速度越慢,根据外设手册调整即可。
5. 数据收发寄存器(SPIDAT / SPITX / SPIRX)
- SPITX:发送缓冲区,往这个寄存器写数据,DSP就会自动通过SPI发出去;
- SPIRX:接收缓冲区,外部设备发来的数据,会存在这里,读取即可;
- SPIDAT:移位寄存器,底层硬件自动操作,代码一般不用手动写。
6. 状态寄存器(SPISTS)
作用:判断通信状态
- 接收满标志:收到新数据,标志位置1;
- 发送空标志:数据发送完成,标志位置1;
代码中可查询状态,避免数据覆盖、丢包。
四、工程与代码实现(分模块讲解,逐行看懂)
前置准备
- 使用之前建好的通用工程模板 (复制一份重命名为
Example21_SPI实验); - 工程中添加底层文件:
DSP2833x_Spi.c(SPI底层驱动,模板自带); - 编译环境:CCS6,仿真器正常连接、开发板上电。
整体代码框架
分3个部分:
- SPI初始化函数(时钟+模式+引脚+波特率配置)
- SPI读写函数(核心收发接口)
- 主函数(功能测试:DSP自发自收 / 外接设备通信)
1. 头文件 spi.h(函数声明、宏定义)
c
#ifndef _SPI_H_
#define _SPI_H_
#include "DSP2833x_Device.h"
#include "DSP2833x_Examples.h"
// 片选引脚SPISTE(GPIO57) 宏定义
#define SPI_CS_L() GpioDataRegs.GPBCLEAR.bit.GPIO57 = 1 // 片选拉低,选中设备
#define SPI_CS_H() GpioDataRegs.GPBSET.bit.GPIO57 = 1 // 片选拉高,断开设备
void SPI_Init(void); // SPI初始化
Uint8 SPI_ReadWrite(Uint8 dat); // SPI读写一体函数(发同时收)
#endif
讲解:
- 宏定义
SPI_CS_L/H:简化片选操作,选中外设必须拉低; - 声明初始化、读写两个核心函数,主文件调用即可。
2. 底层文件 spi.c(初始化+读写函数)
(1)SPI初始化函数 SPI_Init
c
#include "spi.h"
void SPI_Init(void)
{
EALLOW;
// 1. 开启SPI外设时钟(必须第一步)
SysCtrlRegs.PCLKCR0.bit.SPIAENCLK = 1;
// 2. 配置SPI复用引脚:GPIO54/55/56/57 设为SPI功能
GpioCtrlRegs.GPBMUX2.bit.GPIO54 = 1; // SPISOMI
GpioCtrlRegs.GPBMUX2.bit.GPIO55 = 1; // SPISIMO
GpioCtrlRegs.GPBMUX2.bit.GPIO56 = 1; // SPICLK
GpioCtrlRegs.GPBMUX2.bit.GPIO57 = 1; // SPISTE
// 3. SPI软件复位,清空状态
SpiaRegs.SPICCR.bit.SPISWRESET = 0;
DELAY_US(10); // 短暂延时,复位稳定
SpiaRegs.SPICCR.bit.SPISWRESET = 1;
// 4. 配置SPI模式0 + 8位数据
SpiaRegs.SPICCR.bit.CPOL = 0; // 时钟空闲低电平
SpiaRegs.SPICCR.bit.CPHA = 0; // 第一个沿采样(模式0)
SpiaRegs.SPICCR.bit.SPICHAR = 7;// 0~7对应8位数据
// 5. 配置为主机模式,使能收发
SpiaRegs.SPICTL.bit.MASTER = 1; // DSP作为主机
SpiaRegs.SPICTL.bit.TALK = 1; // 使能发送功能
// 6. 设置SPI波特率(LSPCLK/4)
SpiaRegs.SPIBRR = 3;
// 7. 默认片选拉高,不选中外设
SPI_CS_H();
EDIS;
}
逐段讲解:
- 开启时钟:所有外设第一步,时钟不工作,模块完全无效;
- 引脚复用:把普通GPIO切换为SPI专用功能(2833引脚都是多功能复用);
- 软件复位:初始化前复位SPI,避免上一次残留状态影响;
- 模式配置 :固定为实验通用SPI模式0,数据位设为8位(字节传输);
- 主机模式:DSP主动发时钟、主动通信;
- 波特率:设置通信速度;
- 片选默认高:开机默认不选中任何外设。
(2)SPI读写一体函数 SPI_ReadWrite
SPI 特性:发送和接收同步进行,发1字节的同时,必然会收到1字节。
c
Uint8 SPI_ReadWrite(Uint8 dat)
{
SpiaRegs.SPITX = dat; // 写入发送寄存器,开始发送数据
while(SpiaRegs.SPISTS.bit.INT_FLAG == 0); // 等待收发完成(标志置1)
return SpiaRegs.SPIRX; // 读取收到的数据并返回
}
讲解:
- 把要发送的数据写入
SPITX,硬件自动产生时钟、移位发送; while循环阻塞等待通信完成(查询状态位);- 通信结束后,从
SPIRX读取对方发来的数据并返回。
3. 主函数 main.c(功能测试)
这里做最基础的自收发测试(不用外接设备,验证SPI本身是否正常),也可改为外接外设测试。
c
#include "DSP2833x_Device.h"
#include "DSP2833x_Examples.h"
#include "spi"
Uint8 send_data = 0x55; // 待发送数据 0x55
Uint8 recv_data = 0; // 接收数据缓存
void main(void)
{
InitSysCtrl(); // 系统时钟初始化(150MHz)
SPI_Init(); // 初始化SPI
while(1)
{
SPI_CS_L(); // 拉低片选,选中设备
recv_data = SPI_ReadWrite(send_data); // 发送+接收
SPI_CS_H(); // 拉高片选,结束通信
send_data++; // 数据自增,循环测试
DELAY_US(500000); // 延时500ms
}
}
功能说明:
- 系统初始化 → SPI初始化;
- 死循环中:选中设备 → 收发1字节 → 断开设备;
- 数据每次+1,间隔500ms循环,可在CCS变量窗口 观察
send_data和recv_data。
五、实验操作与现象
1. 上机步骤
- 工程编译:无报错、无警告;
- 连接仿真器 + 开发板上电,点击
Connect连接DSP; - 进入调试模式,打开变量观测窗口 ,添加
send_data、recv_data; - 点击运行,观察变量变化。
2. 实验现象(分两种场景)
场景1:SPI自发自收(短接SPISIMO与SPISIMI)
把DSP的发送脚和接收脚直接短接(自己发自己收):
- 现象:
send_data和recv_data数值完全一致,每次+1,循环变化; - 结论:SPI硬件、代码、配置全部正常。
场景2:外接标准SPI从设备(如SPI Flash)
按硬件接线接好外设后:
- DSP发送指令,外设返回对应数据;
- 变量窗口可看到DSP收到外设的应答数据。
3. 常见故障排查(新手必看)
- 变量不变、数据全0
- 排查:SPI时钟是否开启?引脚复用是否配置正确?片选是否拉低?
- 收发数据乱码
- 排查:双方SPI模式(CPOL/CPHA)是否一致?波特率是否匹配?有没有共地?
- 程序卡死在
while等待位- 排查:硬件接线断路、片选未选中、外设未上电。
六、知识点总结&学习拓展
1. 核心知识点复盘
- SPI是同步串行通信,4根基本引脚,主机提供时钟;
- 优先使用模式0(工业最通用);
- 2833配置三步:开时钟 → 配引脚复用 → 设模式/波特率;
- SPI收发同步进行,读写函数一体。
2. 进阶学习方向
- 中断方式SPI(替代查询方式,提高CPU效率);
- 多SPI设备挂载(靠片选区分不同外设);
- 外接SPI外设实战:SPI Flash、OLED屏、ADC芯片等;
- SPI四种模式的切换与适配不同外设。
3. 学习建议
- 先跑通自发自收实验,确认SPI基础功能;
- 再尝试简单SPI外设(如SPI OLED),结合外设手册核对模式、波特率;
- 不要死记寄存器,结合代码对应寄存器功能,理解配置逻辑即可。当前文件内容过长,豆包只阅读了前 29%。