在嵌入式开发中,SPI(Serial Peripheral Interface)因其高速、全双工的特性,成为连接传感器、存储芯片和外设模块的首选协议之一。GD32F103作为一款广泛应用的国产单片机,其SPI外设功能强大但配置细节较多,让不少开发者感到困惑。本文将带你从零开始,通过一个完整的全双工通信案例,掌握GD32 SPI的核心配置技巧。
SPI通信需要四条基本信号线:
对于GD32F103系列,SPI0和SPI1的默认引脚映射如下:
| 外设 | NSS | SCK | MISO | MOSI |
|---|---|---|---|---|
| SPI0 | PA4 | PA5 | PA6 | PA7 |
| SPI1 | PB12 | PB13 | PB14 | PB15 |
实际连接时需注意:
时钟配置是SPI的核心参数之一,GD32F103的SPI时钟源来自APB2总线(最高108MHz),通过分频器可得到多种通信速率:
c复制// 常用分频系数定义
#define SPI_PSC_2 ((uint16_t)0x0000) // 54MHz
#define SPI_PSC_4 ((uint16_t)0x0008) // 27MHz
#define SPI_PSC_8 ((uint16_t)0x0010) // 13.5MHz
#define SPI_PSC_16 ((uint16_t)0x0018) // 6.75MHz
GD32的标准外设库使用spi_parameter_struct结构体进行配置,每个参数直接影响通信行为:
c复制typedef struct
{
uint32_t device_mode; // 主机/从机模式
uint32_t trans_mode; // 传输模式(全双工/半双工等)
uint32_t frame_size; // 数据帧长度(8/16位)
uint32_t clock_polarity_phase; // 时钟极性和相位
uint32_t nss; // 硬件/软件片选
uint32_t prescale; // 时钟预分频
uint32_t endian; // 数据端序
} spi_parameter_struct;
关键参数配置示例:
c复制spi_parameter_struct spi_init_struct = {0};
spi_struct_para_init(&spi_init_struct); // 重置结构体
// 主机全双工配置
spi_init_struct.device_mode = SPI_MASTER;
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
spi_init_struct.nss = SPI_NSS_HARD;
spi_init_struct.prescale = SPI_PSC_8;
spi_init_struct.endian = SPI_ENDIAN_MSB;
时钟极性和相位(CPOL/CPHA)组合决定了数据采样时机:
| 模式 | CPOL | CPHA | 空闲时钟 | 采样边沿 |
|---|---|---|---|---|
| 0 | 0 | 0 | 低电平 | 上升沿 |
| 1 | 0 | 1 | 低电平 | 下降沿 |
| 2 | 1 | 0 | 高电平 | 下降沿 |
| 3 | 1 | 1 | 高电平 | 上升沿 |
提示:大多数SPI设备支持模式0和3,具体需查阅器件手册
c复制void SPI_Master_Init(void)
{
// 启用GPIO和SPI时钟
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_SPI0);
// 配置SPI引脚
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_7); // NSS,SCK,MOSI
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6); // MISO
// SPI参数配置
spi_parameter_struct spi_init_struct;
spi_struct_para_init(&spi_init_struct);
spi_init_struct.device_mode = SPI_MASTER;
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
spi_init_struct.nss = SPI_NSS_HARD;
spi_init_struct.prescale = SPI_PSC_8;
spi_init_struct.endian = SPI_ENDIAN_MSB;
spi_init(SPI0, &spi_init_struct);
spi_nss_output_enable(SPI0); // 使能硬件NSS输出
spi_enable(SPI0); // 使能SPI外设
}
c复制void SPI_Slave_Init(void)
{
// 启用GPIO和SPI时钟
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_SPI1);
// 配置SPI引脚
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_14); // MISO
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,
GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_15); // NSS,SCK,MOSI
// SPI参数配置
spi_parameter_struct spi_init_struct;
spi_struct_para_init(&spi_init_struct);
spi_init_struct.device_mode = SPI_SLAVE;
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
spi_init_struct.nss = SPI_NSS_HARD;
spi_init_struct.prescale = SPI_PSC_8;
spi_init_struct.endian = SPI_ENDIAN_MSB;
spi_init(SPI1, &spi_init_struct);
spi_enable(SPI1); // 使能SPI外设(从机无需NSS输出)
}
c复制void SPI_FullDuplex_Exchange(uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len)
{
for(uint16_t i = 0; i < len; i++) {
// 等待发送缓冲区空
while(spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) == RESET);
// 写入发送数据(触发传输)
spi_i2s_data_transmit(SPI0, tx_buf[i]);
// 等待接收完成
while(spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE) == RESET);
// 读取接收数据
rx_buf[i] = spi_i2s_data_receive(SPI0);
}
}
无通信信号
数据错位或丢失
片选信号异常
c复制printf("SPI Status: TBE=%d, RBNE=%d, TRANS=%d\n",
spi_i2s_flag_get(SPI0, SPI_FLAG_TBE),
spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE),
spi_i2s_flag_get(SPI0, SPI_FLAG_TRANS));
c复制// DMA配置示例(发送通道)
dma_parameter_struct dma_init_struct;
dma_struct_para_init(&dma_init_struct);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;
dma_init_struct.memory_addr = (uint32_t)tx_buffer;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_init_struct.number = data_length;
dma_init_struct.periph_addr = (uint32_t)&SPI_DATA(SPI0);
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.periph_width = DMA_PERIPH_WIDTH_8BIT;
dma_init_struct.priority = DMA_PRIORITY_HIGH;
dma_init(DMA0, DMA_CH0, &dma_init_struct);
通过以上步骤,你应该已经建立起完整的SPI全双工通信能力。在实际项目中,根据具体外设要求调整参数即可实现稳定可靠的数据交换。