13. SPI 协议
📝本节您将了解 SPI 总线通信协议的原理,学习如何配置 MSPM0G3519 的硬件 SPI 外设,并通过天巧星板载 W25Q64 Flash 芯片进行读写验证。
🏆本章目标
1️⃣ 理解 SPI 总线的通信原理和四种工作模式
2️⃣ 了解 MSPM0G3519 的 SPI 硬件外设资源
3️⃣ 通过 SysConfig 配置硬件 SPI
4️⃣ 实现与 W25Q64 Flash 的基础读写通信
13.1 SPI 总线介绍
SPI(Serial Peripheral Interface)是一种同步串行全双工通信协议,用于 MCU 与外设之间的高速数据传输。相比 I2C,SPI 速率更高、协议更简单,但需要更多信号线。
SPI vs I2C 对比
| 特性 | SPI | I2C |
|---|---|---|
| 信号线 | 4 根(MOSI/MISO/SCLK/CS) | 2 根(SDA/SCL) |
| 通信方式 | 全双工 | 半双工 |
| 速率 | 可达数十 MHz | 通常 100k~400kHz |
| 寻址方式 | 硬件片选(CS) | 软件地址 |
| 多设备 | 每设备独立 CS 线 | 共享总线,地址区分 |
| 复杂度 | 协议简单,硬件多线 | 协议复杂,硬件简单 |
信号线定义
| 信号 | 全称 | 方向 | 说明 |
|---|---|---|---|
| MOSI | Master Out Slave In | 主→从 | 主机发送数据 |
| MISO | Master In Slave Out | 从→主 | 主机接收数据 |
| SCLK | Serial Clock | 主→从 | 时钟信号,主机产生 |
| CS/NSS | Chip Select | 主→从 | 片选,低电平有效 |
硬件连接
MCU (主机) W25Q64 (从机)
MOSI ──────────→ DI (数据输入)
MISO ←────────── DO (数据输出)
SCLK ──────────→ CLK (时钟)
CS ──────────→ CS (片选,低有效)2
3
4
5
13.2 SPI 工作模式
SPI 有 4 种工作模式,由时钟极性(CPOL)和时钟相位(CPHA)决定:
| 模式 | CPOL | CPHA | 说明 |
|---|---|---|---|
| Mode 0 | 0 | 0 | 空闲低电平,第一个边沿采样 |
| Mode 1 | 0 | 1 | 空闲低电平,第二个边沿采样 |
| Mode 2 | 1 | 0 | 空闲高电平,第一个边沿采样 |
| Mode 3 | 1 | 1 | 空闲高电平,第二个边沿采样 |
- CPOL(Clock Polarity):时钟空闲时的电平。0=低电平,1=高电平
- CPHA(Clock Phase):数据采样边沿。0=第一个跳变沿,1=第二个跳变沿
W25Q64 的 SPI 模式
W25Q64 支持 Mode 0 和 Mode 3。本教程使用 Mode 0(CPOL=0, CPHA=0)。
13.3 天巧星 SPI 资源
天巧星板载 W25Q64 Flash 通过 SPI1 连接:
| SPI1 信号 | 引脚 | 连接 |
|---|---|---|
| CS | PB6 | W25Q64 片选 |
| MISO | PB7 | W25Q64 数据输出 |
| MOSI | PB8 | W25Q64 数据输入 |
| SCLK | PB9 | W25Q64 时钟 |
W25Q64 规格:
- 容量:64Mbit(8MB)
- 最高 SPI 时钟:20MHz(天巧星设计)
- 页大小:256 Bytes
- 扇区大小:4KB
- 块大小:64KB
13.4 SysConfig 配置硬件 SPI
13.4.1 添加 SPI 外设
- 打开
.syscfg文件 - 左侧面板找到 SPI 选项卡
- 点击 ADD 添加 SPI 实例
13.4.2 配置参数
| 参数 | 设置值 | 说明 |
|---|---|---|
| Name | SPI_1 | 实例名称 |
| SPI Module | SPI1 | 选择 SPI1 |
| Mode | Controller (Master) | 主机模式 |
| Bit Order | MSB First | 高位先发 |
| Clock Polarity | Low (CPOL=0) | 空闲低电平 |
| Clock Phase | First Edge (CPHA=0) | 第一个边沿采样 |
| Bit Rate | 4000000 | 4MHz(保守值) |
| Data Size | 8 Bits | 8 位数据帧 |
| Chip Select | Software | 软件控制 CS |
13.4.3 配置引脚
| 信号 | 引脚 |
|---|---|
| MOSI | PB8 |
| MISO | PB7 |
| SCLK | PB9 |
CS(PB6)使用 GPIO 软件控制,单独配置为 GPIO 输出:
| 参数 | 设置值 |
|---|---|
| Port | PORTB |
| Pin | 6 |
| Direction | Output |
| Initial Value | High(CS 默认高,不选中) |
保存并编译。
为什么用软件 CS
硬件 CS 在每个字节传输间会自动释放(拉高),而 W25Q64 的很多操作需要在多字节传输过程中保持 CS 为低。使用软件控制 CS 可以精确控制片选时序。
13.5 SPI 相关函数
常用 SPI DriverLib 函数
发送并接收一个字节:
uint8_t DL_SPI_transmitData8(SPI_Regs *spi, uint8_t data);
uint8_t DL_SPI_receiveData8(SPI_Regs *spi);2
检查收发状态:
bool DL_SPI_isBusy(SPI_Regs *spi);
bool DL_SPI_isRXFIFOEmpty(SPI_Regs *spi);
bool DL_SPI_isTXFIFOEmpty(SPI_Regs *spi);2
3
13.6 SPI 通信封装
13.6.1 基础收发函数
#include "ti_msp_dl_config.h"
// CS 控制
#define SPI_CS_LOW() DL_GPIO_clearPins(GPIO_SPI_CS_PORT, GPIO_SPI_CS_PIN_CS_PIN)
#define SPI_CS_HIGH() DL_GPIO_setPins(GPIO_SPI_CS_PORT, GPIO_SPI_CS_PIN_CS_PIN)
// SPI 收发一个字节(全双工:发送同时接收)
uint8_t spi_transfer(uint8_t tx_data)
{
// 等待发送 FIFO 有空间
while (DL_SPI_isBusy(SPI_1_INST));
// 发送数据
DL_SPI_transmitData8(SPI_1_INST, tx_data);
// 等待接收完成
while (DL_SPI_isRXFIFOEmpty(SPI_1_INST));
// 读取接收数据
return DL_SPI_receiveData8(SPI_1_INST);
}
// 只发送不关心接收(写操作时用)
void spi_write_byte(uint8_t data)
{
spi_transfer(data);
}
// 只接收(发送 0xFF 作为时钟驱动)
uint8_t spi_read_byte(void)
{
return spi_transfer(0xFF);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
13.7 W25Q64 基础操作
13.7.1 W25Q64 常用命令
| 命令 | 指令码 | 说明 |
|---|---|---|
| Read ID | 0x9F | 读取芯片 ID(验证通信) |
| Read Data | 0x03 | 读取数据 |
| Page Program | 0x02 | 页编程(写入,最多 256B) |
| Sector Erase | 0x20 | 扇区擦除(4KB) |
| Write Enable | 0x06 | 写使能(写/擦除前必须执行) |
| Read Status | 0x05 | 读状态寄存器(判断忙/空闲) |
13.7.2 读取芯片 ID
验证 SPI 通信是否正常的最快方式:
#include "ti_msp_dl_config.h"
#include <stdio.h>
void uart_send_string(const char *str)
{
while (*str) {
while (DL_UART_Main_isBusy(UART_0_INST));
DL_UART_Main_transmitData(UART_0_INST, *str++);
}
}
// 读取 W25Q64 的 JEDEC ID
uint32_t w25q_read_id(void)
{
SPI_CS_LOW();
spi_write_byte(0x9F); // Read JEDEC ID 命令
uint8_t mfr = spi_read_byte(); // 厂商 ID
uint8_t mem = spi_read_byte(); // 存储器类型
uint8_t cap = spi_read_byte(); // 容量
SPI_CS_HIGH();
return ((uint32_t)mfr << 16) | ((uint32_t)mem << 8) | cap;
}
int main(void)
{
SYSCFG_DL_init();
char buf[64];
uint32_t id = w25q_read_id();
// W25Q64 的 JEDEC ID 应为 0xEF4017
sprintf(buf, "Flash ID: 0x%06lX\r\n", id);
uart_send_string(buf);
if (id == 0xEF4017) {
uart_send_string("W25Q64 detected OK!\r\n");
} else {
uart_send_string("Error: unexpected ID\r\n");
}
while (1) { __WFI(); }
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
W25Q64 ID 说明
- 厂商 ID:0xEF(Winbond)
- 存储器类型:0x40
- 容量代码:0x17(2^23 = 8MB = 64Mbit)
- 完整 JEDEC ID:0xEF4017
如果读到 0x000000 或 0xFFFFFF,说明 SPI 通信异常——检查接线和 SysConfig 配置。
13.7.3 等待 Flash 空闲
Flash 的写入和擦除操作需要时间,操作期间芯片处于忙状态:
// 等待 Flash 空闲
void w25q_wait_busy(void)
{
SPI_CS_LOW();
spi_write_byte(0x05); // Read Status Register
while (spi_read_byte() & 0x01); // bit0 = BUSY
SPI_CS_HIGH();
}
// 写使能(写/擦除操作前必须调用)
void w25q_write_enable(void)
{
SPI_CS_LOW();
spi_write_byte(0x06); // Write Enable
SPI_CS_HIGH();
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
13.7.4 扇区擦除
Flash 的特性:写入前必须先擦除(将数据位从 0 恢复为 1)。最小擦除单位为一个扇区(4KB)。
// 擦除指定地址所在的 4KB 扇区
void w25q_sector_erase(uint32_t addr)
{
w25q_write_enable();
SPI_CS_LOW();
spi_write_byte(0x20); // Sector Erase 命令
spi_write_byte((addr >> 16) & 0xFF); // 地址高 8 位
spi_write_byte((addr >> 8) & 0xFF); // 地址中 8 位
spi_write_byte(addr & 0xFF); // 地址低 8 位
SPI_CS_HIGH();
w25q_wait_busy(); // 等待擦除完成(典型 45ms)
}2
3
4
5
6
7
8
9
10
11
12
13
14
13.7.5 页编程(写入)
Flash 写入以页为单位,每页 256 字节。单次写入不能跨页。
// 页编程:向指定地址写入数据(单次最多 256 字节,不跨页)
void w25q_page_program(uint32_t addr, uint8_t *data, uint16_t len)
{
if (len > 256) len = 256;
w25q_write_enable();
SPI_CS_LOW();
spi_write_byte(0x02); // Page Program 命令
spi_write_byte((addr >> 16) & 0xFF);
spi_write_byte((addr >> 8) & 0xFF);
spi_write_byte(addr & 0xFF);
for (uint16_t i = 0; i < len; i++) {
spi_write_byte(data[i]);
}
SPI_CS_HIGH();
w25q_wait_busy(); // 等待写入完成(典型 0.7ms)
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
13.7.6 读取数据
// 从指定地址读取数据(无长度限制)
void w25q_read_data(uint32_t addr, uint8_t *data, uint16_t len)
{
SPI_CS_LOW();
spi_write_byte(0x03); // Read Data 命令
spi_write_byte((addr >> 16) & 0xFF);
spi_write_byte((addr >> 8) & 0xFF);
spi_write_byte(addr & 0xFF);
for (uint16_t i = 0; i < len; i++) {
data[i] = spi_read_byte();
}
SPI_CS_HIGH();
}2
3
4
5
6
7
8
9
10
11
12
13
14
13.8 实验:Flash 读写验证
完整实验——擦除扇区、写入数据、读回验证:
#include "ti_msp_dl_config.h"
#include <stdio.h>
#include <string.h>
void uart_send_string(const char *str)
{
while (*str) {
while (DL_UART_Main_isBusy(UART_0_INST));
DL_UART_Main_transmitData(UART_0_INST, *str++);
}
}
int main(void)
{
SYSCFG_DL_init();
char buf[64];
// 1. 读取 ID 验证通信
uint32_t id = w25q_read_id();
sprintf(buf, "Flash ID: 0x%06lX\r\n", id);
uart_send_string(buf);
// 2. 擦除第 0 扇区(地址 0x000000)
uart_send_string("Erasing sector 0...\r\n");
w25q_sector_erase(0x000000);
uart_send_string("Erase done.\r\n");
// 3. 写入测试数据
uint8_t write_buf[] = "Hello Tianqiaoxing W25Q64!";
uint16_t data_len = strlen((char *)write_buf) + 1;
uart_send_string("Writing data...\r\n");
w25q_page_program(0x000000, write_buf, data_len);
uart_send_string("Write done.\r\n");
// 4. 读回验证
uint8_t read_buf[64] = {0};
w25q_read_data(0x000000, read_buf, data_len);
sprintf(buf, "Read back: %s\r\n", read_buf);
uart_send_string(buf);
// 5. 对比
if (memcmp(write_buf, read_buf, data_len) == 0) {
uart_send_string("Verify OK! Data match.\r\n");
} else {
uart_send_string("Verify FAILED! Data mismatch.\r\n");
}
while (1) { __WFI(); }
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
运行结果:串口输出 Flash ID、写入确认、读回的字符串,以及验证结果。
13.9 Flash 使用注意事项
注意
先擦后写:Flash 只能将 bit 从 1 写为 0,不能从 0 写为 1。写入前必须擦除目标区域。
页对齐:页编程不能跨越 256 字节页边界。例如从地址 200 开始写 100 字节,实际只能写 56 字节(200~255),剩余需要写入下一页。
擦除粒度:最小擦除单位为 4KB 扇区。擦除会将整个扇区全部置为 0xFF。
写入寿命:Flash 每个扇区有写入次数限制(通常 10 万次)。频繁写同一地址会缩短寿命。
写使能:每次写入/擦除操作前必须先发送 Write Enable (0x06) 命令。
等待忙:擦除和写入是异步操作,发送命令后需轮询状态寄存器等待完成。
13.10 知识总结
| 内容 | 说明 |
|---|---|
| SPI 信号 | MOSI / MISO / SCLK / CS,4 线制全双工 |
| 工作模式 | Mode 0~3(CPOL + CPHA 组合) |
| 天巧星 SPI1 | PB6(CS) / PB7(MISO) / PB8(MOSI) / PB9(SCLK) |
| 板载 Flash | W25Q64,8MB,JEDEC ID 0xEF4017 |
| 操作流程 | 擦除 → 写使能 → 页编程 → 等忙 → 读取验证 |
| CS 控制 | 软件 GPIO 控制,多字节操作期间保持低电平 |
至此,入门手册的 13 章核心内容全部完成。你已经掌握了 MSPM0G3519 的 GPIO、时钟、中断、UART、定时器、PWM、输入捕获、ADC、DAC、DMA、I2C、SPI 等基础外设的使用方法。接下来可以进入板载外设高阶教程,学习 OLED 显示、WS2812 RGB、无线串口等更丰富的应用。