DSP28335 SPI通信实验:从零到实战

第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 片选引脚 设备选择开关。低电平选中对应外设,高电平断开通信

补充知识点:

  1. 主从模式 :28335 DSP 默认作为主机 (主动发起通信、提供时钟),外接芯片为从机(被动响应),这是我们实验的默认模式;
  2. 片选SPISTE:一块DSP可以挂载多个SPI外设,靠这根线区分当前和哪个芯片通信。

3. SPI 四种工作模式(重点简化)

SPI 靠**时钟极性(CPOL)时钟相位(CPHA)**组合出4种模式,本质只有两个规则:

  1. CPOL(时钟空闲电平):总线不通信时,时钟线是高电平还是低电平;
  2. 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

这组引脚默认和板上外设预留接口相连,实验分两种场景:

  1. 板载测试:直接使用开发板预留SPI接口,无需额外飞线;
  2. 外接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)

核心配置项:

  1. SPI软件复位位:初始化前先复位SPI,清空错误状态;
  2. 时钟极性CPOL:配置空闲电平(我们设为0,空闲低电平);
  3. 字符长度:设置单次传输位数(本实验用8位,最常用)。

3. SPI操作寄存器(SPICTL)

核心配置项:

  1. 主/从模式选择MASTER/SLAVE,我们设为主机模式
  2. 时钟相位CPHA:搭配CPOL使用(模式0设为0);
  3. 收发使能:开启发送、接收功能。

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;
    代码中可查询状态,避免数据覆盖、丢包。

四、工程与代码实现(分模块讲解,逐行看懂)

前置准备

  1. 使用之前建好的通用工程模板 (复制一份重命名为 Example21_SPI实验);
  2. 工程中添加底层文件:DSP2833x_Spi.c(SPI底层驱动,模板自带);
  3. 编译环境:CCS6,仿真器正常连接、开发板上电。

整体代码框架

分3个部分:

  1. SPI初始化函数(时钟+模式+引脚+波特率配置)
  2. SPI读写函数(核心收发接口)
  3. 主函数(功能测试: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;
}

逐段讲解

  1. 开启时钟:所有外设第一步,时钟不工作,模块完全无效;
  2. 引脚复用:把普通GPIO切换为SPI专用功能(2833引脚都是多功能复用);
  3. 软件复位:初始化前复位SPI,避免上一次残留状态影响;
  4. 模式配置 :固定为实验通用SPI模式0,数据位设为8位(字节传输);
  5. 主机模式:DSP主动发时钟、主动通信;
  6. 波特率:设置通信速度;
  7. 片选默认高:开机默认不选中任何外设。
(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; // 读取收到的数据并返回
}

讲解

  1. 把要发送的数据写入SPITX,硬件自动产生时钟、移位发送;
  2. while循环阻塞等待通信完成(查询状态位);
  3. 通信结束后,从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
    }
}

功能说明

  1. 系统初始化 → SPI初始化;
  2. 死循环中:选中设备 → 收发1字节 → 断开设备;
  3. 数据每次+1,间隔500ms循环,可在CCS变量窗口 观察send_datarecv_data

五、实验操作与现象

1. 上机步骤

  1. 工程编译:无报错、无警告;
  2. 连接仿真器 + 开发板上电,点击Connect连接DSP;
  3. 进入调试模式,打开变量观测窗口 ,添加 send_datarecv_data
  4. 点击运行,观察变量变化。

2. 实验现象(分两种场景)

场景1:SPI自发自收(短接SPISIMO与SPISIMI)

把DSP的发送脚和接收脚直接短接(自己发自己收):

  • 现象:send_datarecv_data 数值完全一致,每次+1,循环变化;
  • 结论:SPI硬件、代码、配置全部正常。
场景2:外接标准SPI从设备(如SPI Flash)

按硬件接线接好外设后:

  • DSP发送指令,外设返回对应数据;
  • 变量窗口可看到DSP收到外设的应答数据。

3. 常见故障排查(新手必看)

  1. 变量不变、数据全0
    • 排查:SPI时钟是否开启?引脚复用是否配置正确?片选是否拉低?
  2. 收发数据乱码
    • 排查:双方SPI模式(CPOL/CPHA)是否一致?波特率是否匹配?有没有共地?
  3. 程序卡死在while等待位
    • 排查:硬件接线断路、片选未选中、外设未上电。

六、知识点总结&学习拓展

1. 核心知识点复盘

  1. SPI是同步串行通信,4根基本引脚,主机提供时钟;
  2. 优先使用模式0(工业最通用);
  3. 2833配置三步:开时钟 → 配引脚复用 → 设模式/波特率;
  4. SPI收发同步进行,读写函数一体。

2. 进阶学习方向

  1. 中断方式SPI(替代查询方式,提高CPU效率);
  2. 多SPI设备挂载(靠片选区分不同外设);
  3. 外接SPI外设实战:SPI Flash、OLED屏、ADC芯片等;
  4. SPI四种模式的切换与适配不同外设。

3. 学习建议

  1. 先跑通自发自收实验,确认SPI基础功能;
  2. 再尝试简单SPI外设(如SPI OLED),结合外设手册核对模式、波特率;
  3. 不要死记寄存器,结合代码对应寄存器功能,理解配置逻辑即可。当前文件内容过长,豆包只阅读了前 29%。
相关推荐
2601_956139422 小时前
性价比高的VI设计质量
大数据·人工智能·python·物联网
Zyed3 小时前
[STM32]Day14独立看门狗+窗口看门狗
stm32·单片机·嵌入式硬件
H__Rick3 小时前
C51学习-DAY7
单片机·嵌入式硬件·学习·51单片机
济6173 小时前
BMS系统专栏:认知电池管理系统BMS的知识与功能
嵌入式硬件·嵌入式·ros2·机器人开发·机器人方向
欢乐熊嵌入式编程3 小时前
第2讲:什么是优秀的软件架构?
stm32·单片机·freertos·低功耗蓝牙·嵌入式架构·efr32
嵌入式ZYXC3 小时前
第9篇:《面试题:ADC前端为什么要加运放跟随器?什么情况下可以不加?》
stm32·单片机·嵌入式硬件·面试·职场和发展
DS小龙哥3 小时前
基于STM32设计的电动车智能充电计费系统
stm32·单片机·嵌入式硬件
普中科技4 小时前
【普中STM32F1xx开发攻略--标准库版】-- 第 49 章 FLASH 字库实验
stm32·单片机·嵌入式硬件·flash·gbk·字库·普中科技
kyle~4 小时前
机器人日志系统
c++·单片机·嵌入式硬件·机器人·ros2