GD32F103 硬件SPI通信

本文详细介绍了SPI通信原理,GD32和STM32的SPI框图,以及如何配置SPI寄存器、主模式和使用w25Q32Flash进行数据操作,包括时钟设置、GPIO配置和具体编程示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. SPI的通信原理

SPI既可以做主机也可以做从机。

当做主机时。MOSI,SCK,CS都是作为输出。 而作为从机时。MOSI,SCK,CS都是作为输入。

 所以SPI的硬件电路应该实现这样的功能。

2. GD32/STM32的SPI框图 

1. GD32框图

如下图做主机的数据流向:

 

如下图做从机的数据流向: 

 

2. STM32框图 

通过一些寄存器的配置来控制电路。跟GD32的差不多。

波特率配置越高,采样越快。SPI的速率越快。

3. SPI的寄存器介绍

 1. 控制寄存器0(SPI_CTL0)

 

 

2. 控制寄存器1(SPI_CTL1) 

3. 状态寄存器(SPI_STAT 

 

4. 数据寄存器(SPI_DATA 

4. SPI主模式配置

 

1. 发送数据 

先判断发送主机发送缓冲器是否为空。

2. 接收数据

接收数据缓冲器是否为空。如果为空就等待,否则就接收。

 

5. dome (硬件SPI访问w25Q32)

NSS\SCK\MISO\MOSI  对应的 PA4\PA5\PA6\PA7引脚。

1. 具体的SPI配置步骤。

1. SPI时钟使能,SPI对应的GPIO时钟使能。复用时钟使能。

2. SPI的GOIP配置。

3. SPI的初始化配置

4. SPI使能。

2. 代码实现

spi.h

#ifndef _SPI_H
#define _SPI_H

#include "gd32f10x.h"


void w25qxx_rcu_init(void);
void w25qxx_io_init(void);
void w25qxx_spi_init(void);

#endif

spi.c

#include "spi.h"

// 使能外设时钟
void w25qxx_rcu_init(void)
{
	rcu_periph_clock_enable(RCU_GPIOA); //使能GPIOA时钟
	rcu_periph_clock_enable(RCU_AF);    //使能AF时钟
	rcu_periph_clock_enable(RCU_SPI0);  //使能SPI0时钟
}
	
// IO口进行配置,使之复用为SPI0, PA4\PA5\PA6\PA7,NSS\SCK\MISO\MOSI
void w25qxx_io_init(void)
{
	gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6); // MISO 浮空输入
	
	gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_7); // SCK\MOSI 复用推挽
	
	gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);// NSS片选口 普通的推挽输出
}
	
// SPI0初始化
void w25qxx_spi_init(void)
{
	spi_parameter_struct spi_struct;
	spi_struct.device_mode = SPI_MASTER;                    /*!< SPI master  做主机*/
  spi_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;         /*!< SPI transfer type 全双工 */
  spi_struct.frame_size =  SPI_FRAMESIZE_8BIT;              /*!< SPI frame size  一次8字节 */
  spi_struct.nss = SPI_NSS_SOFT;                            /*!< SPI NSS control by software 软件CS */
  spi_struct.endian = SPI_ENDIAN_MSB;                       /*!< SPI big endian or little endian  传输高字节在前*/
  spi_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; /*!< SPI clock phase and polarity 空闲低电平 第一个边沿进行采样*/
  spi_struct.prescale = SPI_PSC_8;                          /*!< SPI prescaler factor 8分频*/
	spi_init(SPI0, &spi_struct);
}

w25qxx.h 

#ifndef _W25QXX_SPI_H
#define _W25QXX_SPI_H

#include "gd32f10x.h"
#include "w25qxx_ins.h"
#include "gd32f10x_spi.h"

#define W25QXX_ID_1           1

#define W25QXX_SR_ID_1        1
#define W25QXX_SR_ID_2        2
#define W25QXX_SR_ID_3        3
void w25qxx_init(void);
void w25qxx_wait_busy(void);
uint8_t w25qxx_read_sr(uint8_t sregister_id);  // 读状态寄存器
	
void w25qxx_read(uint8_t *p_buffer, uint32_t read_addr, uint16_t num_read_bytes);

void w25qxx_write(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes);
void w25qxx_write_nocheck(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes); //
void w25qxx_write_page(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes);   // page program

void w25qxx_erase_sector(uint32_t sector_addr);
void w25qxx_erase_chip(void);

void w25qxx_write_enable(void);
void w25qxx_write_disable(void);

void w25qxx_power_down(void);
void w25qxx_wake_up(void);

void w25qxx_cs_enable(uint8_t cs_id);
void w25qxx_cs_disable(uint8_t cs_id);
uint8_t w25qxx_swap(uint8_t byte_to_send);


#endif

w25qxx.c

#include "w25qxx.h"
#include "spi.h"

void w25qxx_init(void){
	// 使能外设时钟
	w25qxx_rcu_init();
	
	// IO口进行配置,使之复用为SPI0, PA4\PA5\PA6\PA7,NSS\SCK\MISO\MOSI
	w25qxx_io_init();
	
	// SPI0初始化
	w25qxx_spi_init();
	// SPI使能
	spi_enable(SPI0);
}


// 如果SR-1的BUSY位为1的话,一直等待,直到BUSY位为0,结束等待
void w25qxx_wait_busy(void){
	while((w25qxx_read_sr(W25QXX_SR_ID_1) & 0x01) == 0x01){
		;
	}
}

// 读状态寄存器
uint8_t w25qxx_read_sr(uint8_t sregister_id){
	uint8_t command, result;
	switch(sregister_id){
		case W25QXX_SR_ID_1:
			command = W25QXX_READ_STATUS_REGISTER_1;
		break;
		case W25QXX_SR_ID_2:
			command = W25QXX_READ_STATUS_REGISTER_2;
		break;
		case W25QXX_SR_ID_3:
			command = W25QXX_READ_STATUS_REGISTER_3;
		break;
		default:
			command = W25QXX_READ_STATUS_REGISTER_1;
		break;
	}
	
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(command);
	result = w25qxx_swap(0xFF);
	w25qxx_cs_disable(W25QXX_ID_1);
	
	return result;
}

// 读flash的数据
// *p_buffer 读回的数据的存放位置
void w25qxx_read(uint8_t *p_buffer, uint32_t read_addr, uint16_t num_read_bytes){
	uint16_t i;
	
	w25qxx_cs_enable(W25QXX_ID_1);
	
	w25qxx_swap(W25QXX_READ_DATA); //发送读数据的指令
	w25qxx_swap(read_addr >> 16);  //发送24bit地址
	w25qxx_swap(read_addr >> 8);
	w25qxx_swap(read_addr);
	
	for(i=0; i < num_read_bytes; i++){
		p_buffer[i] = w25qxx_swap(0xFF);
	}
	
	w25qxx_cs_disable(W25QXX_ID_1);
}

// 
uint8_t W25QXX_Buffer[4096];  //用来存放从sector读出的bytes
void w25qxx_write(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes){
	uint32_t sec_num;
	uint16_t sec_remain;
	uint16_t sec_off;
	uint16_t i;
	
	sec_num	= write_addr / 4096;              //要写入的位置处在第sec_num个扇区上
	sec_off = write_addr % 4096;
	
	sec_remain = 4096 - sec_off;
	
	if(num_write_bytes <= sec_remain){
		w25qxx_read(W25QXX_Buffer, sec_num * 4096, 4096);  //扇区的数据读出来
		
		for(i = 0; i < sec_remain; i++){
			if(W25QXX_Buffer[i + sec_off] != 0xFF)  //说明这个扇区的第i+sec_off位没有擦除
				break;
		}
		
		if(i < sec_remain){ // 扇区没有擦除
			w25qxx_erase_sector(sec_num * 4096);
			for(i = 0; i < sec_remain; i++){
				W25QXX_Buffer[i + sec_off] = p_buffer[i];
			}
			w25qxx_write_nocheck(W25QXX_Buffer, sec_num * 4096, 4096);
		}else{              // 扇区sec_remain部分是擦除过的
			w25qxx_write_nocheck(p_buffer, write_addr, num_write_bytes);
		}
	}else{
		w25qxx_read(W25QXX_Buffer, sec_num * 4096, 4096);  //扇区的数据读出来
		
		for(i = 0; i < sec_remain; i++){
			if(W25QXX_Buffer[i + sec_off] != 0xFF)  //说明这个扇区的第i+sec_off位没有擦除
				break;
		}
		
		if(i < sec_remain){ // 扇区没有擦除
			w25qxx_erase_sector(sec_num * 4096);
			for(i = 0; i < sec_remain; i++){
				W25QXX_Buffer[i + sec_off] = p_buffer[i];
			}
			w25qxx_write_nocheck(W25QXX_Buffer, sec_num * 4096, 4096);
		}else{              // 扇区sec_remain部分是擦除过的
			w25qxx_write_nocheck(p_buffer, write_addr, sec_remain);
		}
		
		write_addr += sec_remain;
		p_buffer += sec_remain;
		num_write_bytes -= sec_remain;
		w25qxx_write(p_buffer, write_addr, num_write_bytes);
	}
		
	//判断读出来的数据是否都为0xFF
	;//扇区是否删除
	 //判断是否跨页
}

// 调用之前先确保扇区删除
void w25qxx_write_nocheck(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes){
	uint16_t page_remain = 256 - write_addr % 256;
	
	if(num_write_bytes <= page_remain){
		w25qxx_write_page(p_buffer, write_addr, num_write_bytes);
	}else{
		w25qxx_write_page(p_buffer, write_addr, page_remain);
		p_buffer += page_remain;
		write_addr += page_remain;
		num_write_bytes -= page_remain;
		w25qxx_write_nocheck(p_buffer, write_addr, num_write_bytes);
	}
}

// page program
// 保证没有跨页写的前提下调用此函数往某个页上写内容
void w25qxx_write_page(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes){
	uint16_t i;
	
	w25qxx_write_enable();
	
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_PAGE_PROGRAM);
	w25qxx_swap(write_addr >> 16);  //发送24bit地址
	w25qxx_swap(write_addr >> 8);
	w25qxx_swap(write_addr);
	
	for(i = 0; i < num_write_bytes; i++){
		w25qxx_swap(p_buffer[i]);
	}
	w25qxx_cs_disable(W25QXX_ID_1);
	
	w25qxx_wait_busy();
}

void w25qxx_erase_sector(uint32_t sector_addr){
	w25qxx_write_enable();
	
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_SECTOR_ERASE_4KB);
	w25qxx_swap(sector_addr >> 16);
	w25qxx_swap(sector_addr >> 8);
	w25qxx_swap(sector_addr);
	w25qxx_cs_disable(W25QXX_ID_1);
	
	w25qxx_wait_busy();
}

void w25qxx_erase_chip(void){
	w25qxx_write_enable();
	
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_CHIP_ERASE);
	w25qxx_cs_disable(W25QXX_ID_1);
	
	w25qxx_wait_busy();
}

void w25qxx_write_enable(void){
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_WRITE_ENABLE);
	w25qxx_cs_disable(W25QXX_ID_1);
}

void w25qxx_write_disable(void){
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_WRITE_DISABLE);
	w25qxx_cs_disable(W25QXX_ID_1);
}

// 低电量休眠
void w25qxx_power_down(void){
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_POWER_DOWN);
	w25qxx_cs_disable(W25QXX_ID_1);
}

// 唤醒
void w25qxx_wake_up(void){
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_RELEASE_POWER_DOWN_HPM_DEVICE_ID);
	w25qxx_cs_disable(W25QXX_ID_1);
}

/*
brief:使能片选引脚cs
cs_id: cs引脚的序号,即第几个w25qxx flash
*/
void w25qxx_cs_enable(uint8_t cs_id){
	switch(cs_id){
		case W25QXX_ID_1:
			gpio_bit_reset(GPIOA, GPIO_PIN_4);
		break;
		default:
			break;
	}
}

void w25qxx_cs_disable(uint8_t cs_id){
	switch(cs_id){
		case W25QXX_ID_1:
			gpio_bit_set(GPIOA, GPIO_PIN_4);
		break;
		default:
			break;
	}
}

/*
主从数据交换
*/
uint8_t w25qxx_swap(uint8_t byte_to_send){
	while(spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) == RESET){ // 等待SPI发送缓冲器为空
		;
	}
	spi_i2s_data_transmit(SPI0, byte_to_send);            // 把数据放到发生缓冲器
	while(spi_i2s_flag_get(SPI0, SPI_FLAG_TRANS) == SET){ // 等待通信结束
		;
	}
	
	while(spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE) == RESET){ // 等待SPI接收缓冲器非空
		;
	}	
	return spi_i2s_data_receive(SPI0); /* 把接收到的数据返回(从接收缓冲器里拿出) */
}

### GD32 微控制器 SPI 配置与使用 GD32 系列微控制器作为高性能嵌入式处理器,其 SPI 接口的设计类似于 STM32 系列。以下是关于 GD32 微控制器 SPI 配置和使用的详细介绍。 #### 1. GPIO 初始化设置 GPIO 的初始化对于 SPI 正常工作至关重要。在配置 SPI 引脚时,需确保引脚模式被设置为复用功能 (AF),并调整引脚的速度参数以满足性能需求。 例如,在 STM32 平台中,`GPIO_InitStructure.GPIO_Speed` 参数决定了 GPIO 引脚的最大切换频率[^1]。类似的逻辑也适用于 GD32,具体实现可以通过以下代码片段展示: ```c // 设置 GPIO 引脚为 AF 模式 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; // SCK, MISO, MOSI GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出 GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉电阻 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速模式 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); ``` #### 2. SPI 工作速率设定 SPI 总线的工作速率由波特率分频器 (`SPI_BaudRatePrescaler`) 控制。通过修改该寄存器的值可以调节数据传输速度。较低的预分频系数会提高通信速率,但也可能增加信号不稳定的风险。 在 GD32 中,SPI 波特率计算方式遵循标准公式: \[ \text{实际波特率} = \frac{\text{PCLK}}{\text{(2 × BaudRatePrescaler)}} \] 其中 `BaudRatePrescaler` 是一个可编程值,范围取决于硬件设计。下面是一个简单的配置示例: ```c // 配置 SPI 参数 SPI_HandleTypeDef hspi1; hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; // 主设备模式 hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 双向通信 hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 数据宽度 8bit hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // 时钟极性低电平有效 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // 时钟相位第一个边沿采样 hspi1.Init.NSS = SPI_NSS_SOFT; // 软件管理 NSS hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 分频因子 4 hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // MSB 在前 if (HAL_SPI_Init(&hspi1) != HAL_OK) { Error_Handler(); } ``` #### 3. 定义时钟配置宏 为了优化系统的运行效率,通常会在项目中定义特定的时钟配置宏。这些宏可以在函数调用之前控制是否启用自定义时钟配置。例如,在某些 STM32 库文件中有这样的定义[^2]: ```c #ifndef CONFIG_ARCH_BOARD_STM32L4_CUSTOM_CLOCKCONFIG #define CONFIG_ARCH_BOARD_STM32L4_CUSTOM_CLOCKCONFIG #endif ``` 虽然这是针对 STM32L4 的例子,但在 GD32 开发环境中也可以采用类似的方法来定制时钟树结构。 #### 4. 使用 VSCode 查阅文档或调试 如果开发者倾向于利用现代 IDE 如 Visual Studio Code 来开发基于 Linux 或其他操作系统的应用程序,则可通过插件安装支持 C/C++ 编程环境以及远程构建工具链[^4]。例如,交叉编译选项 `-target=arm-linux` 和程序前缀指定符可以帮助生成适合目标平台的二进制文件。 #### 5. 中断处理机制概述 GD32 支持丰富的中断资源,NVIC(Nested Vectored Interrupt Controller)能够管理和调度多达数百个外部事件请求[^5]。尽管具体的中断号分配表因芯片型号而异,但总体框架保持一致——即系统保留少量固定编号用于内部用途,其余动态分配给外设模块。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值