GD32F103 SPI实战:手把手教你配置主机从机全双工通信(附完整代码)
GD32F103 SPI全双工通信实战:从硬件配置到代码调试全解析
刚拿到GD32开发板时,最让人头疼的就是外设配置。特别是对于从STM32转过来的开发者,虽然两者相似,但细节差异往往让人踩坑。SPI作为高速通信的利器,在传感器、存储芯片等场景中应用广泛。本文将用一块GD32F103C8T6开发板,带你完整实现主机与从机的全双工通信,过程中会特别标注与STM32的差异点。
1. 硬件连接与SPI基础
SPI通信需要四根基础线:NSS(片选)、SCK(时钟)、MOSI(主机输出从机输入)和MISO(主机输入从机输出)。在GD32F103C8T6上,SPI0的默认引脚映射如下:
| 引脚功能 | 引脚编号 |
|---|---|
| NSS | PA4 |
| SCK | PA5 |
| MISO | PA6 |
| MOSI | PA7 |
关键点注意:
- 硬件NSS和软件NSS的选择:硬件NSS可以自动控制片选信号,减少CPU干预
- 时钟极性(CKP)和相位(CPHA)必须主机从机一致
- GD32的SPI最高时钟可达18MHz,但实际使用需考虑线路质量和传输距离
与STM32的主要差异:
- 库函数前缀从
SPI_变为spi_ - 部分寄存器位定义不同,例如GD32的SPI_CTL0寄存器中的位排列
2. 主机端配置详解
主机配置是通信的发起方,需要正确设置时钟和通信模式。以下是完整的初始化代码:
void SPI_Master_Init(void) { // 开启GPIO和SPI时钟 rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_SPI0); // 配置GPIO为复用功能 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 = {0}; spi_struct_para_init(&spi_init_struct); spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; spi_init_struct.device_mode = SPI_MASTER; 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; // 系统时钟108MHz,8分频后13.5MHz spi_init_struct.endian = SPI_ENDIAN_MSB; spi_init(SPI0, &spi_init_struct); spi_nss_output_enable(SPI0); // 使能硬件NSS输出 spi_enable(SPI0); // 使能SPI }配置要点解析:
- 时钟分频(prescale):决定了通信速率,GD32F103的SPI时钟源是APB2总线时钟(通常108MHz)
- 时钟极性和相位(clock_polarity_phase):
SPI_CK_PL_LOW_PH_1EDGE表示空闲时时钟为低,数据在第一个边沿采样- 这是最常用的模式,与多数SPI设备兼容
- 硬件NSS使能后,发送时会自动拉低NSS,发送完成自动拉高
3. 从机端配置关键点
从机配置与主机类似,但有几点特别注意:
void SPI_Slave_Init(void) { // 开启GPIO和SPI时钟 rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_SPI1); // 配置GPIO - 注意从机的NSS是输入 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 = {0}; spi_struct_para_init(&spi_init_struct); spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; spi_init_struct.device_mode = SPI_SLAVE; // 从机模式 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; // 硬件NSS spi_init_struct.prescale = SPI_PSC_8; // 从机模式下分频设置不影响实际时钟 spi_init_struct.endian = SPI_ENDIAN_MSB; spi_init(SPI1, &spi_init_struct); spi_enable(SPI1); // 使能SPI }从机特殊注意事项:
- 从机的SCK由主机提供,所以分频系数设置不影响实际通信速率
- 从机的NSS必须配置为输入,由主机控制
- 从机的MISO需要配置为输出,其他引脚为输入
4. 全双工通信实现与调试
全双工通信允许主机和从机同时收发数据。下面是一个完整的通信示例:
void SPI_FullDuplex_Exchange(void) { uint8_t master_tx[10] = {0xA1,0xB2,0xC3,0xD4,0xE5,0xF6,0x07,0x18,0x29,0x3A}; uint8_t master_rx[10] = {0}; uint8_t slave_tx[10] = {0x1A,0x2B,0x3C,0x4D,0x5E,0x6F,0x70,0x81,0x92,0xA3}; uint8_t slave_rx[10] = {0}; // 主从机同时收发 for(int i=0; i<10; i++){ // 从机准备发送数据 while(spi_i2s_flag_get(SPI1, SPI_FLAG_TBE) == RESET); spi_i2s_data_transmit(SPI1, slave_tx[i]); // 主机发送并接收 while(spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) == RESET); spi_i2s_data_transmit(SPI0, master_tx[i]); // 从机接收 while(spi_i2s_flag_get(SPI1, SPI_FLAG_RBNE) == RESET); slave_rx[i] = spi_i2s_data_receive(SPI1); // 主机接收 while(spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE) == RESET); master_rx[i] = spi_i2s_data_receive(SPI0); } // 打印收发结果 printf("Master Received: "); for(int i=0; i<10; i++) printf("%02X ", master_rx[i]); printf("\nSlave Received: "); for(int i=0; i<10; i++) printf("%02X ", slave_rx[i]); printf("\n"); }调试技巧:
- 先用逻辑分析仪或示波器检查SCK、MOSI、MISO信号
- 如果通信失败,首先检查:
- 时钟极性和相位是否匹配
- NSS信号是否正确
- 线序是否正确(MOSI接MOSI,不要交叉)
- 降低时钟频率测试,排除信号完整性问题
- 检查电源和地线连接,SPI对共地要求严格
5. 常见问题与性能优化
问题1:数据错位或全为0xFF/0x00
- 检查时钟极性和相位设置
- 确认MISO/MOSI没有接反
- 检查从机是否被正确使能
问题2:通信速度不达标
- 确认APB2时钟配置正确
- 尝试减小分频系数
- 检查PCB走线,过长或过近的走线会影响信号质量
性能优化建议:
- 使用DMA传输减少CPU开销:
// 配置SPI 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 = BUFFER_SIZE; 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);- 合理设置FIFO阈值,减少中断频率
- 硬件NSS比软件NSS节省CPU资源
- 对于大批量传输,考虑使用CRC校验确保数据完整性
在完成基础通信后,可以尝试更复杂的应用场景,如连接SPI Flash、TFT屏幕或多个SPI设备。这时需要注意片选信号的管理,硬件NSS只能控制一个从机,多个从机时需要软件控制额外的GPIO作为片选。
