SPI — SPI1 主机·轮询收发(SPI1_Master_Poll)
固件库: CIU32F003_STDLib V1.0.3
芯片: CIU32F003(Cortex-M0)
源文件: CIU32/CIU32F003_STDLib_V1.0.3/Projects/SPI/SPI1_Master_Poll/Source/
一、功能简介
本示例把 SPI1 配置为主机模式,采用查询(轮询)方式与从机进行全双工数据收发。
通信速率为 PCLK 的 32 分频,即 48MHz / 32 ≈ 1.5MHz。
主机先拉低片选 NSS 选中从机,随后发送 8 字节数据(0x01~0x08),每发送一字节同时读回一字节,
最后拉高 NSS 结束通信。
把 MOSI 与 MISO 用杜邦线短接(自环),可验证发送的数据能被原样收回。
二、硬件准备
- CIU32F003 开发板
- 若接真实从机:从机模块的 SCK/MOSI/MISO/NSS 与开发板对应相连,并共地
- 若做自环测试:用杜邦线把 PA0(MOSI) 与 PA1(MISO) 短接
- 引脚映射(本示例):
| SPI1 信号 |
引脚 |
| NSS |
PB1 |
| SCK |
PB0 |
| MISO |
PA1 |
| MOSI |
PA0 |
三、完整代码
3.1 主流程 main.c
1
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
|
#include "main.h"
uint8_t g_send_buffer[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
uint8_t g_recv_buffer[8] = {0};
int main(void)
{
uint32_t index = 0;
system_clock_config(); /* 系统时钟 RCH 48MHz */
gpio_init(); /* 配置 SPI1 引脚为复用 */
spi1_init(); /* 配置 SPI1 为主机 */
std_spi_set_nss_output(SPI_NSS_OUTPUT_LOW); /* ① 拉低 NSS,选中从机 */
/* ② 逐字节全双工收发 */
for (index = 0; index < 8; index++)
{
while (std_spi_get_flag(SPI_FLAG_TXFE) != SPI_FLAG_TXFE); /* 等待发送寄存器空 */
std_spi_write_data(g_send_buffer[index]); /* 发送 1 字节 */
while (std_spi_get_flag(SPI_FLAG_RXFNE) != SPI_FLAG_RXFNE); /* 等待收到数据 */
g_recv_buffer[index] = std_spi_read_data(); /* 读回 1 字节 */
}
/* ③ 等待发送完成、总线空闲,再拉高 NSS 结束通信 */
while (!std_spi_get_flag(SPI_FLAG_TXFE));
while (std_spi_get_flag(SPI_FLAG_BUSY));
std_spi_set_nss_output(SPI_NSS_OUTPUT_HIGH);
while (1) { }
}
|
3.2 引脚与 SPI 初始化 common.c
1
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
|
/* GPIO 初始化:把引脚映射为 SPI1 复用功能 */
void gpio_init(void)
{
std_gpio_init_t gpio_config = {0};
std_rcc_gpio_clk_enable(RCC_PERIPH_CLK_GPIOA | RCC_PERIPH_CLK_GPIOB);
/* PA0(MOSI)/PA1(MISO):复用功能 AF0_SPI1,推挽,无上下拉 */
gpio_config.pin = GPIO_PIN_0 | GPIO_PIN_1;
gpio_config.mode = GPIO_MODE_ALTERNATE;
gpio_config.pull = GPIO_NOPULL;
gpio_config.output_type = GPIO_OUTPUT_PUSHPULL;
gpio_config.alternate = GPIO_AF0_SPI1;
std_gpio_init(GPIOA, &gpio_config);
/* PB0(SCK)/PB1(NSS):复用功能,上拉(NSS 空闲保持高电平) */
gpio_config.pin = GPIO_PIN_0 | GPIO_PIN_1;
gpio_config.pull = GPIO_PULLUP;
std_gpio_init(GPIOB, &gpio_config);
}
/* SPI1 初始化为主机 */
void spi1_init(void)
{
std_spi_init_t spi_config = {0};
std_rcc_apb2_clk_enable(RCC_PERIPH_CLK_SPI1); /* 使能 SPI1 时钟 */
std_spi_disable(); /* 配置前先禁止 SPI */
spi_config.mode = SPI_MODE_MASTER; /* 主机模式 */
spi_config.baud_rate_prescaler = SPI_BAUD_PCLKDIV_32; /* 波特率 = PCLK/32 = 1.5MHz */
spi_config.clk_polarity = SPI_POLARITY_HIGH; /* CPOL = 1 */
spi_config.clk_phase = SPI_PHASE_1EDGE; /* CPHA = 0(即第 1 个边沿采样) */
spi_config.bitorder = SPI_FIRSTBIT_MSB; /* 高位先发 */
std_spi_init(&spi_config);
std_spi_nss_output_enable(); /* NSS 由硬件自动作为输出 */
std_spi_enable(); /* 使能 SPI1 */
}
|
四、代码分段讲解
① 片选 NSS
SPI 没有地址,靠 NSS 选片。主机开始通信前拉低 NSS,告诉从机“现在和你说话”;
结束后拉高,释放总线。本例使用硬件 NSS 输出(std_spi_nss_output_enable())。
② 全双工收发
SPI 每发送 1 位的同时也接收 1 位,所以“发送”和“接收”是同一个过程的两面:
- 先等
TXFE(发送 FIFO 空)再写数据;
- 再等
RXFNE(接收 FIFO 非空)读出刚收到的数据。
循环 8 次即完成 8 字节交换。
③ 收尾
发完所有数据后,必须等 TXFE(发送完毕)且 BUSY 清零(移位完成),
才能拉高 NSS,否则最后一字节可能没发完就被结束了。
④ 模式匹配
主机与从机的 CPOL / CPHA 必须一致。本例 CPOL=HIGH、CPHA=1EDGE(即 SPI 模式 2),
对接从机时要确认其支持该模式。
五、实验现象
- 自环测试时:
g_recv_buffer 应收回 {0x01,0x02,...,0x08},与发送缓冲一致;
- 接真实从机时:从机按协议返回对应数据。
六、常见问题
| 现象 |
可能原因 |
解决办法 |
| 收不到数据 / 全 0 |
主机从机 CPOL/CPHA 不一致 |
确认双方 SPI 模式相同 |
| 收到数据错位 |
收尾时过早拉高 NSS |
先等 TXFE 且 BUSY 清零再拉高 NSS |
| 通信速率异常 |
波特率分频理解错 |
速率为 PCLK / 分频系数,本例 48M/32 |
| 从机不响应 |
NSS 极性/接线错 |
检查 NSS 是否真正拉低,且 SCK/MOSI/MISO 交叉正确 |
七、小结
SPI 主机轮询收发三步走:配引脚为复用 → 配 SPI 为主机(定模式/波特率)→ 拉低 NSS 后“等空就写、等有就读”、结束拉高 NSS。
注意全双工特性:写的同时就在读;模式(CPOL/CPHA)与从机必须一致。
八、拓展应用
- 用普通 GPIO 手动控制 NSS,实现“一主多从”多器件选片(见
SPI1_Master_GPIO_As_NSS);
- 改用 DMA 或中断传输,释放 CPU(适合大批量数据);
- 驱动 SPI LCD / Flash,把“发命令 + 发数据”封装成函数。