笔记整理于deepseek.
注意
###############################
第 5 行.SPICTL.bit.CLK_PHASE时钟相位
// 原来程序用的:
//时钟相位:CPHA=0,配合 CPOL=1 组成 SPI 模式 2
// 数据在下降沿输出,在上升沿采样
SpiaRegs.SPICTL.bit.CLK_PHASE = 0; // CLK_PHASE=0:下降沿输出,上升沿采样
// 待改为正确的:
// 时钟相位:CPHA=1,配合 CPOL=1 组成 SPI 模式 3
// 数据在下降沿输出,在上升沿采样(FM25CL64B 官方支持的模式)
SpiaRegs.SPICTL.bit.CLK_PHASE = 1; // CLK_PHASE=1:下降沿输出,上升沿采样
##############################
一、器件背景
| 项目 | 内容 |
|---|---|
| 主控芯片 | TMS320F28002x(C2000系列DSP) |
| 铁电存储器 | FM25CL64(Cypress,64Kbit = 8KByte,SPI接口) |
| SPI外设 | SPI-A(SpiaRegs) |
| 片选信号 | GPIO11(低有效) |
| SPI时钟 | LSPCLK / (SPIBRR+1) |
FM25CL64 专属要点
| 要点 | 内容 |
|---|---|
| 容量 | 64Kbit = 8K×8,地址范围 0x0000~0x1FFF |
| 地址位数 | 13 位(A12~A0) ,需要 2 个地址字节(高 8 位在前,低 8 位在后) |
| 命令字节 | 纯命令,不嵌地址位:WRITE=0x02,READ=0x03,WREN=0x06,WRDI=0x04,RDSR=0x05,WRSR=0x01 |
| 写操作序列 | WREN → CS拉低 → WRITE(0x02) → 地址高字节 → 地址低字节 → 数据 → CS拉高 |
| 读操作序列 | CS拉低 → READ(0x03) → 地址高字节 → 地址低字节 → 读数据 → CS拉高 |
| 地址自增 | 连续读写时,芯片内部地址指针自动+1,不用每次重发地址 |
| 支持模式 | SPI Mode 0(CPOL=0, CPHA=0)和 Mode 3(CPOL=1, CPHA=1) |
三、FM25CL64B 关键参数速查(补充)
| 项目 | 值 | 备注 |
|---|---|---|
| 容量 | 64Kbit = 8KByte | |
| 地址范围 | 0x0000 ~ 0x1FFF(13 位地址) | |
| 最高 SPI 时钟 | 20MHz | |
| 本工程 SPI 时钟 | 6.25MHz | 在《SP_28002x的SPI代码注释_文0》里有详解 |
| 支持模式 | Mode 0 (0,0)、Mode 3 (1,1) | |
| 不支持模式 | Mode 1 (0,1)、Mode 2 (1,0) | |
| 指令长度 | 8 位 | |
| 数据宽度 | 8 位字节,按字节读写 |
四、FM25CL64B 指令速查(补充)
| 指令 | 编码 | 作用 | 需要 WREN |
|---|---|---|---|
| WREN | 0x06 | 写使能,置 WEL=1 | --- |
| WRDI | 0x04 | 写禁止,清 WEL=0 | --- |
| RDSR | 0x05 | 读状态寄存器 | 否 |
| WRSR | 0x01 | 写状态寄存器 | 是 |
| READ | 0x03 | 读数据 | 否 |
| WRITE | 0x02 | 写数据 | 是 |
宏定义与全局变量
cpp
#define WREN_INST 0x0600 // 写使能指令 (Write Enable)
#define WRDI_INST 0x0400 // 写禁止指令 (Write Disable)
#define RDSR_INST 0x0500 // 读状态寄存器 (Read Status Register)
#define WRSR_INST 0x0100 // 写状态寄存器 (Write Status Register)
#define READ_INST 0x0300 // 读数据指令 (Read)
#define WRITE_INST 0x0200 // 写数据指令 (Write)
#define STATUS_REG 0x0000 // 状态寄存器写值
#define XP_ADDR 0x0010 // 参数区起始地址
#define XP_NUM 210 // 参数个数
#define SET_ADDR 43 // 参数在数组中的起始下标
Uint16 spixdat; // SPI发送数据缓存
int32 spixdatInt32; // 32位数据缓存(本函数未用)
Uint32 spixdatUint32; // 32位数据缓存(本函数未用)
Uint16 spiimg; // SPI接收数据缓存(从SPIRXBUF读出)
Uint16 spirec; // 读回校验值
Uint16 spiercun; // 读回错误计数
Uint16 wdata; // 写数据缓存
Uint16 xaddress; // 地址缓存
Uint16 xstut; // 状态缓存
Uint16 sdata; // 发送数据缓存
Uint16 rdata; // 接收数据缓存
关键点:FM25CL64 的指令是 8位 ,但这里定义成 0x0600、0x0300 等16位值,原因是 F28002x 的 SPI 配置为 8位字符长度 ,发送时只取低8位有效(高8位被忽略或作为第二个字节发送)。这是"把两个字节拼在一个16位字里"的简化写法,实际每个 spisend() 发一个16位字,SPI硬件按8位字符长度发送,会先发高8位再发低8位------这一点非常关键。
二、函数InitSpi()
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
2.1、骨架
第一层:一句话主线
关 SPI → 配 SPI → 开 SPI → 操作 FRAM。
一关二配三开四用。
配里九项:回环极性位长相位,主从发送中断波特率,再加仿真。
再展开一点:
关 SPI(复位)→ 配 SPI(回环/极性/位长/相位/主从/发送/中断/波特率/仿真)→ 开 SPI(退复位)→ 操作 FRAM(WREN → WRSR → 读参数)。
这一层是骨架。
第二层:分组 + 每组一句话
| 组 | 行 | 一句话 |
|---|---|---|
| A 关 | 1 | 清 SPISWRESET,进入复位,才能改配置 |
| B 配 | 2~10 | 配 SPI 怎么工作:不回环、空闲高、8 位、下降沿输出上升沿采样、主机、能发、不中断、6.25MHz、调试不停 |
| C 开 | 11 | 置 SPISWRESET,退出复位,SPI 开始工作 |
| D 用 | 12~14 | 对 FRAM:先 WREN,再 WRSR,再批量读参数 |
这一层是"目录",看到某行能归到哪组。
配 SPI(9 项)
-
第 2 行:不回环,因为要接外部 FRAM。
第 3 行:空闲高,CPOL = 1。
第 4 行:8 位,SPICHAR = 0x7,实际位数 = 值 + 1。
第 5 行 :相位 1,CPHA = 1,配合 CPOL = 1 是模式 3(FM25CL64 支持模式 0 和模式 3)。
第 6 行:主机,主动产生时钟和片选。
第 7 行:能发,TALK = 1。
第 8 行:不中断,查询方式。
第 9 行:6.25MHz,SPIBRR = 3,公式 LSPCLK/(SPIBRR+1)。
第 10 行:调试不停,FREE = 1。
再记两个关键组合:
-
模式 3 = CPOL 1 + CPHA 1 → 下降沿输出,下降沿采样。(FM25CL64 支持模式 0 和模式 3,这里选模式 3)
SPICHAR 规则:实际位数 = 值 + 1,0x7 就是 8 位。
第三层:每行的"三件套"
每行只记三件事,不要更多:
-
写什么值
-
为什么写这个值
-
写错会怎样
| 序 | 行 | 寄存器/位 | 值 | 一句话 | 写错会怎样 |
|---|---|---|---|---|---|
| 1 | 2 | SPICCR.SPILBK | 0 | 不回环,要接外部 FRAM | 写 1 内部短接,FRAM 收不到 |
| 2 | 3 | SPICCR.CLKPOLARITY | 1 | 空闲高,CPOL=1 | 极性反了,FRAM 采样错 |
| 3 | 4 | SPICCR.SPICHAR | 0x7 | 8 位,实际位数=值+1 | 写 0x0 只发 1 位,命令全错 |
| 4 | 5 | SPICTL.CLK_PHASE | 1 | 相位 1,CPHA=1,模式 3 | 相位反了,数据错位 |
| 5 | 6 | SPICTL.MASTER_SLAVE | 1 | 主机,产生时钟和片选 | 写 0 变从机,没时钟 |
| 6 | 7 | SPICTL.TALK | 1 | 能发,SPISOMI 接通 | 写 0 高阻,发不出去 |
| 7 | 8 | SPICTL.SPIINTENA | 0 | 不中断,用查询 | 写 1 没 ISR,反复进中断 |
| 8 | 9 | SPIBRR.all | 3 | 6.25MHz,LSPCLK/(3+1) | 写 0/1/2 都是 LSPCLK/4 |
| 9 | 10 | SPIPRI.FREE | 1 | 调试不停,断点也跑 | 写 0 断点打断 SPI 时序 |
操作 FRAM 三步(顺序不能反)
| 序 | 行 | 调用 | 一句话 | 为什么 |
|---|---|---|---|---|
| 1 | 12 | wren_cmd() | WREN 写使能 | FRAM 写前必须写使能 |
| 2 | 13 | wrsr_cmd() | WRSR 写状态寄存器 | 清写保护,允许后续读写 |
| 3 | 14 | sread_FRAM() | 批量读参数到 RAM | 上电把 FRAM 参数加载到 paramet\[\] |
顺序口诀:先使能,再状态,再读参。
四、怎么验证自己记住了
合上笔记,回答这 6 个问题:
- InitSpi 分哪四步?
- 配 SPI 的 9 项口诀是什么?
- SPICHAR = 0x7 是几位?写错会怎样?
- CPOL=1、CPHA=1 是什么模式?谁决定选它?
- FM25CL64 的地址是多少位?发几个地址字节?
- 为什么最后要 SPISWRESET = 1?
参考答案:
- 关 → 配 → 开 → 用。
- 回环极性位长相位,主从发送中断波特率,再加仿真。
- 8 位;写 0x0 只发 1 位,命令全错。
- 模式 3 ;由 FM25CL64 数据手册决定(支持模式 0 和模式 3)。
- 13 位地址 ,发 2 个地址字节(高 8 位在前,低 8 位在后),命令字节是纯的 0x02/0x03,不嵌地址位。
- 不退复位 SPI 不工作,spisend 会卡在等 INT_FLAG。
能答出来,就是真记住了。答不出来,说明还在"字典层",没到"骨架层"。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
一句话总结:
InitSpi()先清SPISWRESET进入复位,再配置禁止回环、CPOL=1、8 位字符、CPHA=0、主机模式、发送使能、禁止中断、SPIBRR=3 得 6.25MHz、FREE=1,最后置SPISWRESET=1退出复位;随后对 FRAM 执行写使能、写状态寄存器、批量读取参数。核心点是 复位-配置-退复位顺序、SPICHAR 位数换算、SPI 模式 2、SPIBRR 公式、查询式收发、FRAM 命令时序。
2.2、整段注释
一句话总结
InitSpi() 先清 SPISWRESET 进入复位,再配置禁止回环、CPOL=1、8 位字符、CPHA=1(模式 3) 、主机模式、发送使能、禁止中断、SPIBRR=3 得 6.25MHz、FREE=1,最后置 SPISWRESET=1 退出复位;随后对 FM25CL64 执行写使能、写状态寄存器、批量读取参数。核心点是复位-配置-退复位顺序、SPICHAR 位数换算、SPI 模式 3、SPIBRR 公式、查询式收发、FM25CL64 的 13 位地址 + 2 字节地址格式 + 纯命令码(不嵌地址位)。
cpp
/*******************************************************************************
* 函数名 : InitSpi()
* 简述 : SPI-A 初始化 + 铁电上电初始化 + 参数加载
*
* 功能 : 初始化 SPI-A 外设并对 FRAM 执行上电初始化,具体分 4 步:
* 1. 配置 F28002x 的 SPI-A 外设为主机模式、8 位字符、CPOL=?、
* CPHA=?、波特率 6.25MHz、禁止中断、自由运行;
* 2. 发送 WREN(0x06) 写使能指令,让铁电允许写;
* 3. 写状态寄存器为 0x00,解除 FM25CL64 的块保护;
* 4. 从铁电批量读参数到 paramet[] 数组。
*
* 参数 : 无
* 返回值 : 无
* 调用关系 : 上层初始化流程调用:在 main() 里 InitGpio()、InitPieCtrl() 之后调用,
* 必须在 InitSci() 之前,因为 SCI 参数要从铁电加载;
* 内部调用 wren_cmd()、wrsr_cmd()、sread_FRAM()。
*
* 公式计算 : 主机模式,8 位字符长度
* SPICLK = LSPCLK / (SPIBRR+1) = 25MHz / 4 = 6.25MHz
* CLKPOLARITY = 1, CLK_PHASE = 0
* 查询方式收发,不使用 SPI 中断
*
* 片选 : GPIO11 手动控制,低有效(GPACLEAR 拉低,GPASET 拉高)
*
* 注意事项 : 1. 配置前必须先 SPISWRESET=0 进入复位,配完再置 1 退出复位;
* 2. 配置完成后必须置 SPISWRESET = 1 才能收发;
* 3. 片选 GPIO11 方向需在 GPIO 初始化中配置为输出;
* 4. FRAM 的 WREN/WRSR/READ 时序依赖 delay(),不能省。
******************************************************************************/
void InitSpi(void)
{
// 进入复位状态:配置 SPI 前必须先清 SPISWRESET,保证配置位可写
SpiaRegs.SPICCR.bit.SPISWRESET = 0; // SPISWRESET=0:SPI 进入复位状态
// 禁止回环:正常外部收发,TX 不内部短接到 RX
SpiaRegs.SPICCR.bit.SPILBK = 0; // SPILBK=0:禁止 SPI 回环模式
// 时钟极性:SPICLK 空闲时为高电平
SpiaRegs.SPICCR.bit.CLKPOLARITY = 1; // CLKPOLARITY=1:空闲高电平
// 字符长度:SPICHAR=0x7,实际位数 = 0x7 + 1 = 8 位
SpiaRegs.SPICCR.bit.SPICHAR = 0x7; // SPICHAR=0x7:每次收发 8 位
// 时钟相位:CPHA=0,配合 CPOL=1 组成 SPI 模式 2
// 数据在下降沿输出,在上升沿采样
SpiaRegs.SPICTL.bit.CLK_PHASE = 0; // CLK_PHASE=0:下降沿输出,上升沿采样
// 主机模式:SPI 作为主机,主动产生 SPICLK 和片选
SpiaRegs.SPICTL.bit.MASTER_SLAVE = 1; // MASTER_SLAVE=1:SPI 配置为主机
// 发送使能:允许发送,SPISOMI 接通
SpiaRegs.SPICTL.bit.TALK = 1; // TALK=1:使能发送
// 禁止 SPI 中断:当前驱动使用查询方式,不用中断
SpiaRegs.SPICTL.bit.SPIINTENA = 0; // SPIINTENA=0:禁止 SPI 发送/接收中断
// 波特率分频:SPIBRR=3,SPI_CLK = LSPCLK/(SPIBRR+1) = 25MHz/4 = 6.25MHz
SpiaRegs.SPIBRR.all = 0x03; // SPIBRR=3:25MHz/(3+1)=6.25MHz
// 自由运行:仿真挂起时 SPI 继续运行,避免调试断点打断时序
SpiaRegs.SPIPRI.bit.FREE = 1; // FREE=1:仿真挂起时 SPI 继续运行
// 退出复位:配置完成,SPI 准备接收或发送
SpiaRegs.SPICCR.bit.SPISWRESET = 1; // SPISWRESET=1:SPI 退出复位,开始工作
// FRAM 初始化:写使能 → 写状态寄存器 → 批量读取参数
wren_cmd(); // 发送 WREN:FRAM 写使能
wrsr_cmd(); // 发送 WRSR:写 FRAM 状态寄存器
sread_FRAM(); // 批量读取 FRAM 参数到 paramet[]
}
第 1 行:.SPICCR.bit.SPISWRESET
// 进入复位状态:配置 SPI 前必须先清 SPISWRESET,保证配置位可写
SpiaRegs.SPICCR.bit.SPISWRESET = 0; // SPISWRESET=0:SPI 进入复位状态
位含义
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| bit7 | SPISWRESET | 0 | SPI软件复位。 0=复位状态,所有SPI逻辑被强制到已知状态; 1=正常工作。SPI 退出复位,准备接收/发送,配置SPI前必须先写0。 |
为什么先写 0
TI SPI 要求:修改 SPI 配置前,先把 SPISWRESET 清 0,否则某些位可能不生效或行为异常。
Q:SPISWRESET 置 0 后,SPI 还能收发吗?
A:不能。此时 SPI 处于复位状态,必须配置完成后置 1 才能收发。
Q:SPISWRESET 和 SPICCR 其他位谁先配?
A:先清 SPISWRESET,再配其他位,最后置 SPISWRESET = 1。
第 2 行:.SPILBK回环模式位
// 禁止回环:正常外部收发,TX 不内部短接到 RX
SpiaRegs.SPICCR.bit.SPILBK = 0; // SPILBK=0:禁止 SPI 回环模式
位含义
-
SPICCR:SPI 配置控制寄存器。 -
SPILBK:SPI 回环模式位。-
0:禁止回环,正常外部收发。 -
1:回环,TX 内部连到 RX,用于自测。
-
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| bit6 | SPILBK | 0 | SPI 回环模式。 0=禁止,SIMO 和 SOMI 走外部引脚,正常接 FM25CL64; 1=使能,SIMO 内部短接到 SOMI,用于自测。 |
为什么写 0
本工程要接外部 FM25CL64 铁电,数据必须走 SIMO/SOMI 引脚。写 1 会把 TX 内部短接到 RX,FM25CL64 收不到任何数据。
Q&A
-
Q:回环模式有什么用?
A:调试 SPI 外设本身时,用来验证"发出去的数据能不能收回来",不用外部接线。本工程用不到。
-
Q:写错会怎样?
A:写 1 时,SPI 发出去的数据立刻从内部回到接收缓冲,spiimg 会等于 spixdat,但 FM25CL64 完全收不到,读写铁电全失败。
第 3 行.CLKPOLARITY空闲电平极性// 时钟极性:SPICLK 空闲时为高电平
SpiaRegs.SPICCR.bit.CLKPOLARITY = 1; // CLKPOLARITY=1:空闲高电平
位含义
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| bit5 | CLKPOLARITY | 1 | 时钟极性(CPOL)。 0=SPICLK 空闲为低; 1=SPICLK 空闲为高。 |
为什么写 1
配合 CLK_PHASE=1 组成 SPI Mode 3 (CPOL=1, CPHA=1):空闲高电平,下降沿切换数据,上升沿采样数据。FM25CL64B 数据手册明确支持 Mode 0 和 Mode 3,Mode 3 是官方推荐之一。
Q&A
-
Q:CPOL=1 和 CPHA=1 组合是什么模式?
A:SPI Mode 3。对应"空闲高、下降沿换数据、上升沿采样"。
-
Q:FM25CL64B 支持哪些模式?
A:官方数据手册明确写 Mode 0 (CPOL=0, CPHA=0) 和 Mode 3 (CPOL=1, CPHA=1) 两种。两者共同点:下降沿换数据,上升沿采样。
-
Q:写错会怎样?
A:极性反了,FM25CL64B 在错误的边沿采样,读到的数据全错。
Q:SPI 四种模式怎么对应 CPOL/CPHA?
第 4 行.SPICHAR字符长度选择
// 字符长度:SPICHAR=0x7,实际位数 = 0x7 + 1 = 8 位
SpiaRegs.SPICCR.bit.SPICHAR = 0x7; // SPICHAR=0x7:每次收发 8 位
位含义
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| bit3:0 | SPICHAR | 0x7 | 字符长度选择,4 位。 编码规则:实际位数 = 值 + 1。 0x0=1 位,0x1=2 位,...,0x7=8 位,0xF=16 位。 |
为什么写 0x7
FM25CL64B 的指令、地址、数据都是按 8 位字节传输,所以 SPI 必须配 8 位。0x7 + 1 = 8,正确。
Q&A
-
Q:SPICHAR=0x7 是几位?
A:8 位。因为实际位数 = 值 + 1。
-
Q:写 0x0 会怎样?
A:只发 1 位,FM25CL64B 收到的指令全错,读写全失败。
-
Q:写 0xF 会怎样?
A:发 16 位,与 FM25CL64B 的 8 位时序不匹配,命令错位。
第 5 行.SPICTL.bit.CLK_PHASE时钟相位
// 原来程序用的:
//时钟相位:CPHA=0,配合 CPOL=1 组成 SPI 模式 2
// 数据在下降沿输出,在上升沿采样
SpiaRegs.SPICTL.bit.CLK_PHASE = 0; // CLK_PHASE=0:下降沿输出,上升沿采样
// 待改为正确的:
// 时钟相位:CPHA=1,配合 CPOL=1 组成 SPI 模式 3
// 数据在下降沿输出,在上升沿采样(FM25CL64B 官方支持的模式)
SpiaRegs.SPICTL.bit.CLK_PHASE = 1; // CLK_PHASE=1:下降沿输出,上升沿采样
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| bit2 | CLK_PHASE | 1 | 时钟相位(CPHA)。 0=Mode 0/2,数据在第一个边沿输出; 1=Mode 1/3,数据在第二个边沿输出。具体哪个边沿取决于 CLKPOLARITY。 |
为什么写 1(而不是原来的 0)
FM25CL64B 数据手册明确要求:
"The FM25CL64B operates in SPI Mode 0 and Mode 3 . In both modes, data changes on the falling edge and is stable on the rising edge."
代码用 CPOL=1,要匹配 FM25CL64B,必须再配 CPHA=1 → Mode 3 。原来的 CLK_PHASE=0 配出的是 Mode 2 (CPOL=1, CPHA=0),不在 FM25CL64B 官方支持列表里。虽然 6.25MHz 低时钟下可能"能跑",但属于在规格边缘工作,存在隐患。
Q&A
-
Q:Mode 2 和 Mode 3 有什么区别?
A:两者空闲都是高电平(CPOL=1),区别在 CPHA。Mode 2 在片选拉低后第一个下降沿才开始驱动数据;Mode 3 在第一个下降沿之前数据就已准备好。FM25CL64B 要求"第一个上升沿就要采到数据",Mode 3 更稳妥。
-
Q:为什么原来写 0 也能跑?
A:6.25MHz 时钟周期 160ns,第一个下降沿到第一个上升沿约 80ns,数据建立时间勉强够。但温度、电压、批次变化时可能偶发失败。
-
Q:改成 1 后需要改别的吗?
A:不需要。只改这一位即可。CPOL=1 + CPHA=1 = Mode 3,与 FM25CL64B 完全匹配。
第 6 行.MASTER_SLAVE
// 主机模式:SPI 作为主机,主动产生 SPICLK 和片选
SpiaRegs.SPICTL.bit.MASTER_SLAVE = 1; // MASTER_SLAVE=1:SPI 配置为主机
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| bit1 | MASTER_SLAVE | 1 | 主从选择。0=从机 ,接收外部 SPICLK;1=主机,主动产生 SPICLK。 |
为什么写 1
F28002x 要主动给 FM25CL64B 提供时钟,所以必须做主机。片选 GPIO11 也是主机控制的。
Q&A
-
Q:主机和从机的区别?
A:主机产生 SPICLK 和片选;从机接收外部 SPICLK,片选由主机给。
-
Q:写错会怎样?
A:写 0 变从机,SPI 不产生时钟,FM25CL64B 收不到任何时钟,spisend 会卡在等 INT_FLAG。
第 7 行.TALK ?
// 发送使能:允许发送,SPISOMI 接通
SpiaRegs.SPICTL.bit.TALK = 1; // TALK=1:使能发送
TALK 是 SPI 发送的"静音键":TALK=1 正常发,TALK=0 输出高阻不发但能收。4线全双工永远置1,3线半双工收发切换时要动态开关。
| TALK 值 | 效果 |
|---|---|
| 1(使能) | 允许发送,SPISOMI 接通。数据输出引脚正常驱动,SPI 可以发送数据 |
| 0(禁止) | 禁止发送,SPISOMI 高阻。数据输出引脚置于高阻态(Hi-Z),不发送任何数据,但仍可接收数据、更新状态标志 |
TALK = 发送使能开关,控制 SPI 数据输出引脚(MOSI/MISO)是否驱动总线。
关键特性
- 不影响接收:TALK=0 时,SPI 仍然能接收从机数据、更新 RX 标志位,时钟和片选信号也照常工作。
- 不影响 FIFO/移位器:如果禁用 TALK 时 FIFO 里有数据,移位器仍会继续工作,只是输出引脚被"静音"。
- 复位默认值:系统复位后 TALK 自动清零(禁止发送)。
典型应用场景
- 3线 SPI(半双工):收发共用一根数据线,发送时 TALK=1,接收前必须 TALK=0,避免主从同时驱动总线冲突。
- 多从机共享总线:防止未选中的从机干扰总线。
- 纯接收模式:DSP 作为从机只收不发时,可关闭 TALK 避免 MISO 回传垃圾数据。
4线全双工场景(如 W25Q64)
始终 TALK=1 即可,因为 MOSI 和 MISO 是独立的两根线,不存在总线冲突问题。
为什么写 1
当前需要向 FRAM 发命令、地址、数据,必须使能发送。
Q:四线 SPI 的 SPISTE 作用?
A:从机片选。主机通过 SPISTE 选中从机。
Q&A
-
Q:TALK=0 时还能收吗?
A:能收,接收通道不受 TALK 影响。但本工程既要发又要收,TALK 必须为 1。
-
Q:写错会怎样?
A:写 0 时 SIMO 高阻,FM25CL64B 收不到指令,读写全失败。
第 8 行.SPIINTENA中断总使能开关
// 禁止 SPI 中断:当前驱动使用查询方式,不用中断
SpiaRegs.SPICTL.bit.SPIINTENA = 0; // SPIINTENA=0:禁止 SPI 发送/接收中断
SPIINTENA = SPI 中断总使能开关,控制 SPI 模块是否向 PIE 模块发起中断请求。
SPIINTENA 是 SPI 中断的"总闸门":=0 用查询方式轮询状态位,=1 用中断方式自动响应。C2000 中断需三级使能(外设→PIE→CPU)缺一不可,查询方式无需配置 PIE,代码更简单。
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| bit3 | SPIINTENA | 0 | SPI 中断使能。 0=禁止发送/接收中断;SPI 事件(发送/接收完成)触发时,向 PIE 发送中断请求 1=使能 ,收发完成时产生中断。禁止 SPI 发送/接收中断,只能用查询方式检测 SPI 状态 |
为什么写 0
当前驱动用查询方式:
while(SpiaRegs.SPISTS.bit.INT_FLAG==0);不用中断,所以关掉。
关键特性
- 只控制"是否中断",不影响通信本身:SPIINTENA=0 时,SPI 收发功能完全正常,只是不会触发中断。
- 复位默认值:系统复位后 SPIINTENA 自动清零(禁止中断)。
- 需要三级使能才能生效 :C2000 的中断体系是三级架构,仅设置 SPIINTENA=1 还不够,必须同时满足:
- 外设级:SPIINTENA = 1(本位)
- PIE 级:PIEIER 对应组位 = 1 + PIEACK 应答
- CPU 级:IER 对应 INTx = 1 + 全局中断 EINT 开启
查询方式 vs 中断方式
| 对比项 | 查询方式(SPIINTENA=0) | 中断方式(SPIINTENA=1) |
|---|---|---|
| 实现复杂度 | 简单,无需配置 PIE/IER/EINT | 复杂,需三级使能 + 编写 ISR |
| CPU 占用 | 高,主循环需轮询状态位 | 低,事件触发才响应 |
| 实时性 | 取决于轮询频率 | 高,硬件自动响应 |
| 适用场景 | 低速、简单通信(如 W25Q64 初始化) | 高速、大数据量、实时性要求高 |
查询方式如何检测收发完成?
SPIINTENA=0 时,通过查询 SPISTS 状态寄存器 的 SPI INT FLAG 位(bit6)来判断:
// 等待发送/接收完成
while (SpiaRegs.SPISTS.bit.SPI_INT_FLAG != 1);
// 读取接收数据
data = SpiaRegs.SPIRXBUF.all;
Q:查询和中断方式各自优缺点?
A:
-
查询:简单,但 CPU 空转,实时性差。
-
中断:CPU 利用率高,但需要 ISR,复杂度高。
Q:INT_FLAG 什么时候置 1?
A:一个字符收发完成后置 1,读 SPIRXBUF 可清标志。
-
Q:查询和中断哪个好?
A:数据量小、实时性要求不高时,查询简单可靠;数据量大或要并行处理时,用中断。
-
Q:写错会怎样?
A:写 1 但没写 ISR,每次收发完成都会反复进中断,可能死机。
第 9 行.SPIBRR波特率寄存器
// 波特率分频:SPIBRR=3,SPI_CLK = LSPCLK/(SPIBRR+1) = 25MHz/4 = 6.25MHz
SpiaRegs.SPIBRR.all = 0x03; // SPIBRR=3:25MHz/(3+1)=6.25MHz
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| bit6:0 | SPIBRR | 3 | 波特率分频。SPI_CLK = LSPCLK / (SPIBRR+1)。 特殊规则:SPIBRR=0→分频 2;=1→分频 4;=2→分频 8; ≥3→分频 = SPIBRR+1。 |
寄存器说明
- SPIBRR:SPI 波特率寄存器,7 位(0~127),决定主模式下的 SPI 时钟频率。
- LSPCLK:低速外设时钟,由 SYSCLK 经 LOSPCP 分频得到(默认 /4)。
波特率公式(两段式)
- SPIBRR = 3 ~ 127 :
SPI_CLK = LSPCLK / (SPIBRR + 1) - SPIBRR = 0 / 1 / 2 :
SPI_CLK = LSPCLK / 4(最低分频固定为 4,无法更高)
示例解析
SpiaRegs.SPIBRR.all = 0x03; // SPIBRR = 3
- LSPCLK = 25MHz,SPIBRR = 3 落入第一段公式
SPI_CLK = 25MHz / (3+1) = 6.25MHz
为什么写 3 而不是其他值?
LSPCLK = 25MHz(代码注释写 25M),SPIBRR=3 → 分频 4 → SPICLK = 6.25MHz。6.25MHz 对 FM25CL64B(最高支持 20MHz)来说安全,信号完整性好
- SPIBRR=0/1/2 被"锁死"在 LSPCLK/4,无法通过减小 SPIBRR 获得更高频率。
- 要获得更高 SPI 频率,只能提高 LSPCLK(修改 LOSPCP 寄存器降低分频比)。
- SPIBRR=3 是第一段公式的最小值 ,对应
LSPCLK/4,恰好与第二段上限衔接,无跳变。
反向计算
已知目标 SPI_CLK 和 LSPCLK,求 SPIBRR:
SPIBRR = (LSPCLK / SPI_CLK) - 1 // 结果需 ≥ 3
例:LSPCLK=25MHz,目标 SPI_CLK=1MHz → SPIBRR = 25/1 - 1 = 24
注意事项
- 最大 SPI 频率 = LSPCLK / 4,这是硬件上限,任何 SPIBRR 都无法突破。
- 从机模式下 SPIBRR 无效,时钟由主机提供。
- 动态修改 SPIBRR 需先清除 SPISWRESET,改完再置位,防止配置过程中产生毛刺。
- 与 STM32 对比:STM32 的 BR 位是 2/4/8/16... 固定分频档位;C2000 的 SPIBRR 是连续可编程分频(3~127),粒度更细。
Q:LSPCLK 是什么?
A:低速外设时钟,由系统时钟分频而来。
Q:SPIBRR = 0、1、2 时为什么都是 LSPCLK/4?
A:TI 规定低值有最小分频限制。
Q:6.25MHz 对 FRAM 是否合适?
A:要看 FRAM 最大 SPI 时钟。FM25CL64B 最高频率(20MHz),安全;若有的型号只支持 5MHz,就超了。
-
Q:SPIBRR=3 时分频是多少?
A:4。因为 ≥3 时,分频 = SPIBRR+1 = 4。
-
Q:SPICLK 太快会怎样?
A:超过 ,或线长导致信号完整性差,读写会出错。
####################################################################
"7位(0~127)"的含义
7位 指的是 SPIBRR 寄存器中真正参与波特率计算的位宽,即 bit6:0 这 7 个二进制位。
SPIBRR 是 7 位寄存器(bit6:0),取值 0~127,但 0/1/2 效果相同,所以实际有 126 种波特率可选。
为什么是 7 位?
SPIBRR 是一个 16 位寄存器,但只有低 7 位(bit6:0)是有效的波特率控制位,其余高位(bit15:7)是保留位,写无效、读返回 0。
SPIBRR 寄存器位域:
bit15 ~ bit7: 保留位(读0,写无效)
bit6 ~ bit0: SPI_BIT_RATE[6:0] ← 这7位决定波特率
为什么范围是 0~127?
7 位二进制能表示的最大值:
bit6 bit5 bit4 bit3 bit2 bit1 bit0
1 1 1 1 1 1 1 = 2⁷ - 1 = 127
所以 SPIBRR 的合法取值范围是 0 ~ 127,共 128 个值。
实际有效分频档位
虽然能写 0~127 共 128 个值,但由于 0/1/2 三个值都被"锁死"在 LSPCLK/4,所以实际可用的分频档位是:
| SPIBRR 值 | 分频结果 | 有效档位数 |
|---|---|---|
| 0, 1, 2 | LSPCLK / 4(三个值效果相同) | 1 档 |
| 3 ~ 127 | LSPCLK / (SPIBRR+1),即 /4 ~ /128 | 125 档 |
| 合计 | --- | 126 种有效波特率 |
第 10 行.SPIPRI.bit.FREE优先级/仿真控制寄存器
// 自由运行:仿真挂起时 SPI 继续运行,避免调试断点打断时序
SpiaRegs.SPIPRI.bit.FREE = 1; // FREE=1:仿真挂起时 SPI 继续运行
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| bit5 | FREE | 1 | 自由运行。 0=软挂起,仿真器暂停 CPU 时 SPI 停止; 1=自由运行,仿真器暂停 CPU 时 SPI 继续工作。 |
SPIPRI:SPI 优先级/仿真控制寄存器。
为什么写 1
调试时打断点,CPU 停了但 SPI 还在发数据。如果写 0,SPI 被冻结在中间状态,可能打断 FM25CL64B 的时序,导致铁电读写异常。写 1 让 SPI 不受断点影响。
-
Q:仿真挂起是什么?
A:用 CCS 调试时,CPU 被仿真器暂停(断点、单步),但外设可以继续运行,取决于 FREE/SOFT 位。
-
Q:写错会怎样?
A:写 0 时断点打断 SPI 时序,FM25CL64B 可能收到半个字节,读写失败。
Q:FREE 位实际调试中有什么用?
A:调试断点/挂起时,SPI 是否继续跑。
Q:SPIPRI 还有什么位?
A:还有 SOFT、SPIPRI 等,控制仿真行为和优先级。
第 11 行.SPISWRESET 软件复位位
// 退出复位:配置完成,SPI 准备接收或发送
SpiaRegs.SPICCR.bit.SPISWRESET = 1; // SPISWRESET=1:SPI 退出复位,开始工作
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| bit7 | SPISWRESET | 1 | SPI 软件复位。 0=复位状态; 1=正常工作,SPI 退出复位,可以收发。 |
为什么写 1
前面 10 行都在配置,SPI 处于复位状态。配置完成后必须置 1 退出复位,SPI 才能开始工作。
Q&A
-
Q:不退复位会怎样?
A:SPI 不工作,
spisend()会卡在while(SPISTS.bit.INT_FLAG==0),死循环。 -
Q:为什么最后才置 1?
A:配置过程中 SPI 不能工作,否则可能产生错误时钟或误发送。先配好,最后开。
Q:如果忘记置 1 会怎样?
A:SPI 一直处于复位状态,无法收发,spisend() 会卡在等待 INT_FLAG。
Q:SPISWRESET 置 1 后还能改配置吗?
A:一般要先清 0 再改,改完再置 1。
配置顺序口诀(11 行)
先清复位(1),
再配九项:回环(2)极性(3)位长(4)相位(5),主从(6)发送(7)中断(8)波特率(9),再加仿真(10),
最后退复位(11)。
本次修正 :第 5 行 CLK_PHASE 由 0 → 1,SPI 由 Mode 2 → Mode 3,匹配 FM25CL64B 官方支持。
第 12 行wren_cmd()
// FRAM 初始化:写使能 → 写状态寄存器 → 批量读取参数
wren_cmd(); // 发送 WREN:FRAM 写使能
功能含义
-
发送
WREN命令,FRAM 写使能。 -
FRAM 写操作前必须先 WREN。
为什么调用
后面要写状态寄存器、写参数,必须先写使能。
Q:WREN 之后如果没写操作,WREN 会自动清除吗?
A:会。WREN 是一次性使能,写操作完成后自动清除。
第 13 行wrsr_cmd()
wrsr_cmd(); // 发送 WRSR:写 FRAM 状态寄存器
功能含义
-
发送
WRSR命令,写 FRAM 状态寄存器。 -
这里写
STATUS_REG = 0,清除写保护等。
为什么调用
确保 FRAM 状态寄存器配置正确,允许后续读写。
Q:为什么先 WREN 再 WRSR?
A:FRAM 写状态寄存器前必须先写使能,否则 WRSR 被忽略。
第 14 行sread_FRAM()
sread_FRAM(); // 批量读取 FRAM 参数到 paramet[]
功能含义
-
批量读取 FRAM 参数到
paramet[]。 -
循环调用
FRAM_rd()。
为什么调用
上电初始化时把 FRAM 中保存的参数加载到 RAM。
Q:sread_FRAM() 放在初始化里合适吗?
A:如果参数必须上电加载,合适;但会增加初始化时间,也可放到独立任务。
Q:sread_FRAM() 里地址为什么是 XP_ADDR + (i<<1)?
A:i 是字索引,FRAM 按字节编址,每个 16 位字占 2 字节,所以地址乘 2。
总结表
| 顺序 | 代码 | 寄存器/位 | 值 | 中文含义 | 面试要点 |
|---|---|---|---|---|---|
| 1 | SPISWRESET = 0 |
SPICCR.SPISWRESET | 0 | SPI 进入复位 | 配置前必须先清 0 |
| 2 | SPILBK = 0 |
SPICCR.SPILBK | 0 | 禁止回环 | 回环用于自测 |
| 3 | CLKPOLARITY = 1 |
SPICCR.CLKPOLARITY | 1 | 空闲高 | 与 CPHA=0 组成模式 2 |
| 4 | SPICHAR = 0x7 |
SPICCR.SPICHAR | 0x7 | 8 位 | 实际位数 = 值 + 1 |
| 5 | CLK_PHASE = 0 |
SPICTL.CLK_PHASE | 0 | 相位 0 | 下降沿输出,上升沿采样 |
| 6 | MASTER_SLAVE = 1 |
SPICTL.MASTER_SLAVE | 1 | 主机 | 主机产生时钟 |
| 7 | TALK = 1 |
SPICTL.TALK | 1 | 发送使能 | 0 时 SPISOMI 高阻 |
| 8 | SPIINTENA = 0 |
SPICTL.SPIINTENA | 0 | 禁中断 | 查询方式 |
| 9 | SPIBRR = 0x03 |
SPIBRR.all | 3 | 分频 | 25M/(3+1)=6.25MHz |
| 10 | FREE = 1 |
SPIPRI.FREE | 1 | 自由运行 | 仿真挂起继续跑 |
| 11 | SPISWRESET = 1 |
SPICCR.SPISWRESET | 1 | 退出复位 | 必须最后置 1 |
| 12 | wren_cmd() |
--- | --- | FRAM 写使能 | WREN 后才能写 |
| 13 | wrsr_cmd() |
--- | --- | 写状态寄存器 | 先 WREN 再 WRSR |
| 14 | sread_FRAM() |
--- | --- | 批量读参数 | 地址按字索引乘 2 |