当前位置: 首页 > news >正文

《嵌入式 – GD32开发实战指南》第22章 SPI

开发环境:
MDK:Keil 5.30
开发板:GD32F207I-EVAL
MCU:GD32F207IK

22.1 SPI简介

SPI,是Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。是一种高速全双工的通信总线,它由摩托罗拉公司提出,当前最新的为 V04.01—2004 版。它被广泛地使用在ADC、LCD 等设备与 MCU 间通信的场合。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。

22.1.1 SPI 信号线

SPI 包含 4 条总线,SPI 总线包含 4 条总线,分别为SS、SCK、MOSI、MISO。它们的作用介绍如下 :

1)SS ( Slave Select):片选信号线,当有多个 SPI 设备与 MCU 相连时,每个设备的这个片选信号线是与 MCU 单独的引脚相连的,而其他的 SCK、MOSI、MISO 线则为多个设备并联到相同的 SPI 总线上,见下图。当 SS 信号线为低电平时,片选有效,开始SPI 通信。

在这里插入图片描述

2)SCK (Serial Clock):时钟信号线,由主通信设备产生,不同的设备支持的时钟频率不一样,如 GD32 的 SPI 时钟频率最大为 f PCLK /2。

3)MOSI (Master Output, Slave Input):主设备输出 / 从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入数据,即这条线上数据的方向为主机到从机。

4)MISO(Master Input, Slave Output):主设备输入 / 从设备输出引脚。主机从这条信号线读入数据,从机的数据则由这条信号线输出,即在这条线上数据的方向为从机到主机。

22.1.2 SPI模式

SPI通信中可作为从机也可以作为主机,这取决于硬件设计和软件设置。

当器件作为主机时,使用一个IO引脚拉低相应从机的选择引脚(NSS),传输的起始由主机发送数据来启动,时钟(SCK)信号由主机产生。通过MOSI发送数据,同时通过MISO引脚接收从机发出的数据。
当器件作为从机时,传输在从机选择引脚(NSS)被主机拉低后开始,接收主机输出的时钟信号,在读取主机数据的同时通过MISO引脚输出数据。

根据 SPI 时钟极性(CKPL)和时钟相位(CKPH) 配置的不同,分为 4 种 SPI 模式。
时钟极性是指 SPI 通信设备处于空闲状态时(也可以认为这是 SPI 通信开始时,即SS 为低电平时),SCK 信号线的电平信号。CKPL=0 时, SCK 在空闲状态时为低电平,CKPL=1 时则相反。
时钟相位是指数据采样的时刻,当 CKPH =0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的奇数边沿被采样。当 CKPH=1 时,数据线在 SCK 的偶数边沿采样。

在这里插入图片描述

我们来分析这个 CKPH =0 的时序图。首先,由主机把片选信号线SS 拉低,即为图中的SS (O)时序,意为主机输出,SS (I)时序实际上也是SS 线信号,SS (I)时序表示从机接收到SS 片选被拉低的信号。
在SS 被拉低的时刻,SCK 分为两种情况,若我们设置为 CKPL=0,则 SCK 时序在这个时刻为低电平,若设置为 CKPL=1,则 SCK 在这个时刻为高电平。

无论 CKPL=0 还是=1,因为我们配置的时钟相位 CKPH =0,在采样时刻的时序中我们可以看到,采样时刻都是在 SCK 的奇数边沿(注意奇数边沿有时为下降沿,有时为上升沿)。因此,MOSI 和 MISO 数据线的有效信号在 SCK 的奇数边沿保持不变,这个信号将在SCK 奇数边沿时被采集,在非采样时刻,MOSI 和 MISO 的有效信号才发生切换。

对于 CKPH =1 的情况也很类似,但数据信号的采样时刻为偶数边沿。使用 SPI 协议通信时,主机和从机的时序要保持一致,即两者都选择相同的 SPI 模式。

22.1.3 SPI特性

GD32的小容量有一个SPI接口,中容量有2个,大容量有3个接口,其特性如下所示。

 具有全双工和单工模式的主从操作;
 16位宽度,独立的发送和接收缓冲区;
 8位或16位数据帧格式;
 低位在前或高位在前的数据位顺序;
 软件和硬件NSS管理;
 硬件CRC计算、发送和校验;
 发送和接收支持DMA模式;
 支持SPI四线功能的主机模式(只有SPI0)。

22.2 SPI架构

下图所示为GD32的 SPI 架构图,可以看到 MISO 数据线接收到的信号经移位寄存器处理后把数据转移到接收缓冲区,然后这个数据就可以由我们的软件从接收缓冲区读出了。

在这里插入图片描述

当要发送数据时,我们把数据写入发送缓冲区,硬件将会把它用移位寄存器处理后输出到 MOSI 数据线。

SCK 的时钟信号则由波特率发生器产生,我们可以通过波特率控制位(PSC)来控制它输出的波特率。

控制寄存器 CTL0掌管着主控制电路,GD32的 SPI 模块的协议设置(时钟极性、相位等)就是由它来制定的。而控制寄存器 CTL1则用于设置各种中断使能。

最后为 NSS 引脚,这个引脚扮演着 SPI 协议中的SS 片选信号线的角色,如果我们把 NSS 引脚配置为硬件自动控制,SPI 模块能够自动判别它能否成为 SPI 的主机,或自动进入 SPI 从机模式。但实际上我们用得更多的是由软件控制某些 GPIO 引脚单独作为SS信号,这个 GPIO 引脚可以随便选择。
通常SPI通过4个引脚与外部器件相连:

● MISO:主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
● MOSI:主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
● SCK: 串口时钟,作为主设备的输出,从设备的输入。
● NSS: 从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。从设备的NSS引脚可以由主设备的一个标准I/O引脚来驱动。

22.3 SPI工作原理

22.3.1 (NSS)输入输出管理

 (NSS)输出管理
对于每个SPI的NSS可以输入,也可以输出。所谓输入,就是NSS的电平信号给自己,所谓输出,就是将NSS的电平信号发送出去,给从机。配置为输出,还是不输出,我们可以通过SPI_CTL1寄存器的NSSDRV位。当NSSDRV=1时,并且SPI处于主模式控制时(MSTMOD=1),NSS就输出低电平,也就是拉低,因此当其他SPI设备的NSS引脚与它相连,必然接收到低电平,则片选成功,都成为从设备了。

 (NSS)输入管理
NSS软件模式:
 SPI主机:
需要设置SPI_CTL0寄存器的SWNSSEN=1和SWNSS=1,SWNSSEN=1是为了使能软件管理,NSS有内部和外部引脚。这时候外部引脚留作他用(可以用来作为GPIO驱动从设备的片选信号)。内部NSS引脚电平则通过SPI_CTL0寄存器的SWNSS位来驱动。SWNSS=1是为了使NSS内电平为高电平。为什么主设备的内部NSS电平要为1呢?
GD32手册上说,要保持MSTMOD=1和SPIEN=1,也就是说要保持主机模式,只有NSS接到高电平信号时,这两位才能保持置‘1’。
 SPI从机:
NSS引脚在完成字节传输之前必须连接到一个低电平信号。在软件模式下,则需要设置SPI_CR1寄存器的SWNSSEN=1(软件管理使能)和SWNSS=0.
NSS硬件模式:
对于主机,我们的NSS可以直接接到高电平.对于从机,NSS接低就可以。

22.3.2单主和单从应用

在这里插入图片描述

从上图可以看出,主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号将字节传给从机,从机也将自己的移位寄存器中的内容通过MISO信号返还给主机。这样,两个移位寄存器中下的内容就被交换,外设的写操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个,就必须发送一个空字节来引发从机的传输。

22.3.3时钟信号的相位和极性

SPI_CTL0寄存器的CKPL和CKPH位,能够组合成四种可能的时序关系。CKPL (时钟极性)位控制在没有数据传输时时钟的空闲状态电平,此位对主模式和从模式下的设备都有效。如果CKPL被清’0’,SCK引脚在空闲状态保持低电平;如果CKPL被置’1’,SCK引脚在空闲状态保持高电平。如果CKPH (时钟相位)位被置’1’,SCK时钟的第二个边沿(CPOL位为0时就是下降沿,CKPL位为’1’时就是上升沿)进行数据位的采样,数据在第二个时钟边沿被锁存。如果CKPH位被清’0’,SCK时钟的第一边沿(CPOL位为’0’时就是下降沿,CKPL位为’1’时就是上升沿)进行数据位采样,数据在第一个时钟边沿被锁存。
CKPL时钟极性和CKPH时钟相位的组合选择数据捕捉的时钟边沿。

22.3.4数据帧格式

根据SPI_CTL0寄存器中的LF位,输出数据位时可以MSB在先也可以LSB在先。根据SPI_CTL0寄存器的FF16位,每个数据帧可以是8位或是16位。所选择的数据帧格式对发送和/或接收都有效。

22.3.5 SPI主从模式工作原理

配置SPI主模式的步骤如下:

1.设置SPI_CTL0寄存器的PSC [2:0]位,来定义串行时钟波特率。
2.选择CKPL和CKPH位,定义数据传输和串行时钟间的相位关系。
3.设置FF16位来定义8或16位数据帧格式。
4.配置SPI_CTL0寄存器的LF位定义帧格式。
5.如果NSS引脚需要工作在输入模式,硬件模式中在整个数据帧传输期间应把NSS引脚连接到高电平;在软件模式中,需设置SPI_CTL0寄存器的SWNSSEN=1和SWNSS=1。如果NSS引脚工作在输出模式,则只需设置SSOE=1位。
6.设置MSTMOD=1和SPIEN=1,只当NSS引脚被连到高电平,这些位才能保持置位。

配置SPI从模式的步骤如下:

1.设置FF16位以定义数据帧格式为8位或16位。
2.定义数据传输和串行时钟之间的相位关系。
3.帧格式必须和主设备相同,MSB在前还是LSB在前取决于SPI_CTL0寄存器中的LF位。
4.硬件模式下,在完整的数据帧(8位或16位)发送过程中,NSS引脚必须为低电平。软件模式下,设置SPI_CTL0寄存器中的SWNSSEN=1,SWNSS=0。
5.MSTMOD=0位,设置SPIEN=1,使相应引脚工作于SPI模式下。

22.3.6状态标志

应用程序通过3个状态标志可以完全监控SPI总线的状态。
1.发送缓冲器空闲标志(TBE)
此标志为’1’时表明发送缓冲器为空,可以写下一个待发送的数据进入缓冲器中。当写入SPI_DATA时,TBE标志被清除。
2.接收缓冲器非空(RBNE)
此标志为’1’时表明在接收缓冲器中包含有效的接收数据。读SPI数据寄存器可以清除此标志。
3.忙(Busy)标志
TRANS标志由硬件设置与清除(写入此位无效果),此标志表明SPI通信层的状态。

22.3.7 SPI中断

SPI的相关中断标志如下:

中断事件事件标志使能控制位
发送缓冲器空标志TBETBEIE
接收缓冲器非空标志RBNERBNEIE
主模式失效事件CONFERRERRIE
溢出错误RXORERR
CRC错误标志CRCERR

22.4硬件连接

GD25Q16BS是兆易创新推出的一款 SPI 接口的 NOR Flash 芯片,其存储空间为 16Mbit,相当于2M 字节。

GD25Q16BS可以支持 SPI 的模式 0 和模式 3,也就是 CKPL=0/CKPH=0和CKPL=1/CKPH=1这两种模式。

GD25Q16BS芯片支持 standard spi,Dual/Quad I/O SPI。

GD25Q16BS的擦写周期多达5W 次,具有10年的数据保存期限,支持电压为1.65~3.6V,GD25Q16BS支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到133Mhz(双输出时相当于266Mhz,四输出时相当于532M)。

GD25Q16BS内部有一个“SPI Command & Control Logic”,可以通过 SPI 接口向其发送指令,从而执行相应操作。

【注】
①、Flash 写入数据时和 EEPROM 类似,不能跨页写入,一次最多写入一页,GD25Q16BS的一页是 256 字节。写入数据一旦跨页,必须在写满上一页的时候,等待 Flash 将数据从缓存搬移到非易失区,重新再次往里写。

②、Flash 有一个特点,就是可以将 1 写成 0,但是不能将 0 写成 1,要想将 0 写成 1,必须进行擦除操作。因此通常要改写某部分空间的数据,必须首先进行一定物理存储空间擦除,最小的擦除空间,通常称之为扇区,扇区擦除就是将这整个扇区每个字节全部变成 0xFF。
我的开发板选用的Flash是GD25Q16BS,容量为2M,挂载在SPI0上,如下图所示。

在这里插入图片描述

22.5 SPI具体代码实现

首先是SPI的硬件初始化。

/*brief      initialize SPI1 GPIO and parameterparam[in]  noneparam[out] noneretval     none
*/
void spi_flash_init(void)
{spi_parameter_struct spi_init_struct;rcu_periph_clock_enable(RCU_GPIOA);rcu_periph_clock_enable(RCU_GPIOB);rcu_periph_clock_enable(RCU_AF);rcu_periph_clock_enable(RCU_SPI0);/* SPI0_CLK(PA5), SPI0_MISO_IO1(PA6), SPI0_MOSI_IO0(PA7) GPIO pin configuration */gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7);/* SPI0_CS(PB1) GPIO pin configuration */gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);/* chip select invalid */SPI_FLASH_CS_HIGH();/* SPI0 parameter config */spi_init_struct.trans_mode           = SPI_TRANSMODE_FULLDUPLEX; /*SPI receive and send data at fullduplex communication*/spi_init_struct.device_mode          = SPI_MASTER; /* SPI as master*/spi_init_struct.frame_size           = SPI_FRAMESIZE_8BIT; /* SPI frame size is 8 bits*/spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; /*SPI clock polarity is low level and phase is first edge*/spi_init_struct.nss                  = SPI_NSS_SOFT; /* SPI NSS control by sofrware */spi_init_struct.prescale             = SPI_PSC_32;  /* SPI clock prescale factor is 32 */spi_init_struct.endian               = SPI_ENDIAN_MSB; /* SPI transmit way is big endian: transmit MSB first */spi_init(SPI0, &spi_init_struct);/* enable SPI0 */spi_enable(SPI0);
}

SPI的硬件初始化最重要的函数就是spi_init ()。

void spi_init(uint32_t spi_periph, spi_parameter_struct *spi_struct)

其中SPI参数配置的结构体为spi_parameter_struct;。

/* SPI and I2S parameter struct definitions */
typedef struct {uint32_t device_mode;                                                       /*!< SPI master or slave */uint32_t trans_mode;                                                        /*!< SPI transfer type */uint32_t frame_size;                                                        /*!< SPI frame size */uint32_t nss;                                                               /*!< SPI NSS control by hardware or software */uint32_t endian;                                                            /*!< SPI big endian or little endian */uint32_t clock_polarity_phase;                                              /*!< SPI clock phase and polarity */uint32_t prescale;                                                          /*!< SPI prescaler factor */
} spi_parameter_struct;

spi_parameter_struct结构体成员变量如下:

  • trans_mode用来设置 SPI 的通信方式,可以选择为半双工,全双工,以及串行发和串行收方式,这里设置的全双工(SPI_TRANSMODE_FULLDUPLEX)。
  • device_mode用来设置 SPI 的主从模式。SCK 的时序是由通讯中的主机产生的。若被配置为从机模式,GD32的 SPI 外设将接受外来的 SCK 信号。
  • frame_size为 8 位还是 16 位帧格式选择项。
  • clock_polarity_phase 用来设置时钟极性与设置时钟相位,就是选择在串行同步时钟的第几个跳变沿(上升或下降)数据被采样。
  • nss设置NSS 信号由硬件(NSS 管脚)还是软件控制。可以选择为硬件模式(SPI_NSS_HARD)与软件模式(SPI_NSS_SOFT),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
  • prescale设置 SPI 波特率预分频值决定 SPI 的时钟的参数,从不分频道 256 分频 8 个可选值。2-156,凡是2的几次方都可以。
  • endian设置数据传输顺序是 MSB 位在前还是 LSB 位在前

SPI Flash的读写操作如下:

/*brief      read a byte from the SPI flashparam[in]  noneparam[out] noneretval     byte read from the SPI flash
*/
uint8_t spi_flash_read_byte(void)
{return(spi_flash_send_byte(DUMMY_BYTE));
}/*brief      send a byte through the SPI interface and return the byte received from the SPI busparam[in]  byte: byte to sendparam[out] noneretval     the value of the received byte
*/
uint8_t spi_flash_send_byte(uint8_t byte)
{/* loop while data register in not emplty */while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE));/* send byte through the SPI0 peripheral */spi_i2s_data_transmit(SPI0, byte);/* wait to receive a byte */while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE));/* return the byte read from the SPI bus */return(spi_i2s_data_receive(SPI0));
}

发送数据前要等待发送缓冲区为空,靠TBE标志判断,所以开始的while循环是等待发送缓冲区为空,同时,等待接收缓冲区是否有数据,靠RBNE标志来判断,把接收缓冲区的数据作为返回值返回。由于发送和接收是同时进行的,而且要接收一个数据时必须在有效的SCK下,而只有发送数据才能产生有效的SCK,所以接收数据的函数时在发送数据的函数的基础上,将发送的数据设置为Dummy_Byte假数据来骗取有效的SCK。

SPI Flash读写Buffer操作如下:

/*brief      write block of data to the flashparam[in]  pbuffer: pointer to the bufferparam[in]  write_addr: flash's internal address to writeparam[in]  num_byte_to_write: number of bytes to write to the flashparam[out] noneretval     none
*/
void spi_flash_buffer_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write)
{uint8_t num_of_page = 0, num_of_single = 0, addr = 0, count = 0, temp = 0;addr          = write_addr % SPI_FLASH_PAGE_SIZE;count         = SPI_FLASH_PAGE_SIZE - addr;num_of_page   = num_byte_to_write / SPI_FLASH_PAGE_SIZE;num_of_single = num_byte_to_write % SPI_FLASH_PAGE_SIZE;/* write_addr is SPI_FLASH_PAGE_SIZE aligned */if(0 == addr){/* num_byte_to_write < SPI_FLASH_PAGE_SIZE */if(0 == num_of_page){spi_flash_page_write(pbuffer, write_addr, num_byte_to_write);}else{/* num_byte_to_write >= SPI_FLASH_PAGE_SIZE */while(num_of_page--){spi_flash_page_write(pbuffer, write_addr, SPI_FLASH_PAGE_SIZE);write_addr += SPI_FLASH_PAGE_SIZE;pbuffer += SPI_FLASH_PAGE_SIZE;}spi_flash_page_write(pbuffer, write_addr, num_of_single);}}else{/* write_addr is not SPI_FLASH_PAGE_SIZE aligned */if(0 == num_of_page){/* (num_byte_to_write + write_addr) > SPI_FLASH_PAGE_SIZE */if(num_of_single > count){temp = num_of_single - count;spi_flash_page_write(pbuffer, write_addr, count);write_addr += count;pbuffer += count;spi_flash_page_write(pbuffer, write_addr, temp);}else{spi_flash_page_write(pbuffer, write_addr, num_byte_to_write);}}else{/* num_byte_to_write >= SPI_FLASH_PAGE_SIZE */num_byte_to_write -= count;num_of_page = num_byte_to_write / SPI_FLASH_PAGE_SIZE;num_of_single = num_byte_to_write % SPI_FLASH_PAGE_SIZE;spi_flash_page_write(pbuffer, write_addr, count);write_addr += count;pbuffer += count;while(num_of_page--){spi_flash_page_write(pbuffer, write_addr, SPI_FLASH_PAGE_SIZE);write_addr += SPI_FLASH_PAGE_SIZE;pbuffer += SPI_FLASH_PAGE_SIZE;}if(0 != num_of_single){spi_flash_page_write(pbuffer, write_addr, num_of_single);}}}
}/*brief      read a block of data from the flashparam[in]  pbuffer: pointer to the buffer that receives the data read from the flashparam[in]  read_addr: flash's internal address to read fromparam[in]  num_byte_to_read: number of bytes to read from the flashparam[out] noneretval     none
*/
void spi_flash_buffer_read(uint8_t *pbuffer, uint32_t read_addr, uint16_t num_byte_to_read)
{/* select the flash: chip slect low */SPI_FLASH_CS_LOW();/* send "read from memory " instruction */spi_flash_send_byte(READ);/* send read_addr high nibble address byte to read from */spi_flash_send_byte((read_addr & 0xFF0000) >> 16);/* send read_addr medium nibble address byte to read from */spi_flash_send_byte((read_addr & 0xFF00) >> 8);/* send read_addr low nibble address byte to read from */spi_flash_send_byte(read_addr & 0xFF);/* while there is data to be read */while(num_byte_to_read--){/* read a byte from the flash */*pbuffer = spi_flash_send_byte(DUMMY_BYTE);/* point to the next location where the byte read will be saved */pbuffer++;}/* deselect the flash: chip select high */SPI_FLASH_CS_HIGH();
}

主函数代码如下:

/*brief      main functionparam[in]  noneparam[out] noneretval     none
*/
int main(void)
{//systick initsysTick_init();// led initled_init(LED1);//usart init 115200 8-N-1com_init(COM1, 115200, 0, 1);/* configure SPI and parameter */spi_flash_init();/* GD32207i-EVAL start up */printf("\n\rGD32207i-EVAL System is Starting up...\n\r");printf("\n\rGD32207i-EVAL Flash:%dK\n\r", *(__IO uint16_t *)(0x1FFFF7E0));/* get chip serial number */get_chip_serial_num();/* printf CPU unique device id */printf("\n\rGD32207i-EVAL The CPU Unique Device ID:[%X-%X-%X]\n\r", int_device_serial[2], int_device_serial[1], int_device_serial[0]);printf("\n\rGD32207i-EVAL SPI Flash:GD25Q16 configured...\n\r");/* get flash id */flash_id = spi_flash_read_id();printf("\r\nThe Flash_ID:0x%X\r\n", flash_id);/* flash id is correct */if(SFLASH_ID == flash_id){printf("\n\rWrite to tx_buffer:\r\n");/* printf tx_buffer value */for(i = 0; i < BUFFER_SIZE; i++) {tx_buffer[i] = i;printf("0x%02X ", tx_buffer[i]);if(15 == i % 16){printf("\n\r");}}printf("\r\nRead from rx_buffer:\r\n");/* erase the specified flash sector */spi_flash_sector_erase(FLASH_WRITE_ADDRESS);/* write tx_buffer data to the flash */spi_flash_buffer_write(tx_buffer, FLASH_WRITE_ADDRESS, 256);delay_ms(10);/* read a block of data from the flash to rx_buffer */spi_flash_buffer_read(rx_buffer, FLASH_READ_ADDRESS, 256);   /* printf rx_buffer value */for(i = 0; i < BUFFER_SIZE; i ++){printf("0x%02X ", rx_buffer[i]);if(15 == i % 16){printf("\n\r");}}if(ERROR == memory_compare(tx_buffer, rx_buffer, 256)) {printf("\n\rErr:Data Read and Write aren't Matching.\n\r");is_successful = 1;}/* spi qspi flash test passed */if(0 == is_successful){printf("\n\rSPI-GD25Q16 Test Passed!\n\r");}}else{/* spi flash read id fail */printf("\n\rSPI Flash: Read ID Fail!\n\r");}while(1){led_toggle(LED1);delay_ms(1000);}
}

首先对SPI进行初始化,然后就极性FLASH的读取,完整代码请参看源码。

22.6实验现象

在电脑端打开串口调试助手工具,设置参数为115200 8-N-1。下载完程序之后,在串口调试助手窗口可接收到信息。

在这里插入图片描述



欢迎访问我的网站

BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的知乎


资源获取方式

1.关注公众号[嵌入式实验楼]
2.在公众号回复关键词[GD32开发实战指南]获取资料提取码

相关文章:

《嵌入式 – GD32开发实战指南》第22章 SPI

开发环境&#xff1a; MDK&#xff1a;Keil 5.30 开发板&#xff1a;GD32F207I-EVAL MCU&#xff1a;GD32F207IK 22.1 SPI简介 SPI&#xff0c;是Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的…...

一个优质软件测试工程师的简历应该有的样子(答应我一定要收藏起来)

个人简历 基本信息 姓 名&#xff1a;xxx 性 别&#xff1a; 女 年 龄&#xff1a;24 现住 地址&#xff1a; 深圳 测试 经验&#xff1a;3年 学 历&#xff1a;本科 联系 电话&#xff1a;18xxxxxxxx 邮 箱&#xff1a;xxxxl163.com 求职意向 应聘岗位&#xff1a;软件…...

C++ 浅谈之 STL Deque

C 浅谈之 STL Deque HELLO&#xff0c;各位博友好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 这里是 C 浅谈系列&#xff0c;收录在专栏 C 语言中 &#x1f61c;&#x1f61c;&#x1f61c; 本系列阿呆将记录一些 C 语言重要的语法特性 &#x1f3c3;&a…...

Koa2-项目中的基本应用

文章目录安装配置koa2配置nodemon,热更新我们的项目中间件什么是中间件&#x1f47b;洋葱模型路由中间件连接数据库 - mysql后端允许跨域处理请求getpostputdelete后续会继续更新安装配置koa2 &#x1f47b;安装 koa2 npm i koa2 -s&#x1f47b;在package.json 配置,当然是在…...

Flask入门(2):配置

目录2.Flask配置2.1 直接写入主脚本2.2 系统环境变量2.3 单独的配置文件2.4 多个配置类2.5 Flask内置配置2.Flask配置 我们都知道&#xff0c;Flask应用程序肯定是需要各种各样的配置。来满足我们不同的需求的&#xff0c;这样可以使我们的应用程序更加灵活。比如可以根据需要…...

Linux--fork

一、fork入门知识 fork&#xff08;&#xff09;函数通过系统调用创建一个与原来进程几乎完全相同的进程&#xff0c;也就是两个进程可以做完全相同的事&#xff0c;但如果初始参数或者传入的变量不同&#xff0c;两个进程也可以做不同的事。可以简单地说fork()的作用就是创建一…...

计算机组成原理(一)

1.了解计算机硬件的发展和软件的发展历程&#xff1b; 硬件&#xff1a;   电子管时代&#xff08;1946-1959&#xff09;&#xff1a;电子管、声汞延迟线、磁鼓   晶体管时代&#xff08;1959-1964&#xff09;&#xff1a;晶体管、磁芯   中、小规模集成电路时代&#…...

【SpringBoot】实现Async异步任务

1. 环境准备 在 Spring Boot 入口类上配置 EnableAsync 注解开启异步处理。 创建任务抽象类 AbstractTask&#xff0c;并分别配置三个任务方法 doTaskOne()&#xff0c;doTaskTwo()&#xff0c;doTaskThree()。 public abstract class AbstractTask {private static Random r…...

Node =>Express学习

1.Express 能做什么 能快速构建web网站的服务器 或 Api接口的服务期 Web网站服务器&#xff0c;专门对外提供Web网页资源的服务器Api接口服务器&#xff1a;专门对外提供API接口的服务器 2.安装 在项目所处的目录中&#xff0c;运行以下命令&#xff0c;简装到项目中了 npm …...

QT基础入门【布局篇】消除控件之间的间隔

一、相关参数 layoutLeftMargin: layout内的布局距离边框左端的距离。 layoutTopMargin: layout内的布局距离边框顶端的距离。 layoutRightMargin: layout内的布局距离边框右端的距离。 layoutBottomMargin: layout内的布局距离边框底端的距离。 layoutHorizontalSpacing: layo…...

vue脚手架 element-ui spring boot 实现图片上传阿里云 并保存到数据库

一.阿里云 注册登陆就不讲了&#xff0c;登陆进去后如下操作 1. 进入对象存储OSS 创建一个新的Bucket 随后点击新建的bucket 2.去访问RAM 前往RAM控制台 3.去创建用户 4.创建密匙 5.随后返回RAM控制台 给用户增加权限&#xff0c;文件上传所需权限&#xff0c;需要带含有…...

【FPGA】Verilog:组合电路 | 3—8译码器 | 编码器 | 74LS148

前言&#xff1a;本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载 示例&#xff1a;编码/译码器的应用 ​ 功能特性&#xff1a; 采用 Xilinx Artix-7 XC7A35T芯片 配置方式&#xff1a;USB-JTAG/SPI Flash 高达100MHz 的内部时钟速度 存储器&…...

GLP-1类药物研发进展-销售数据-上市药品前景分析

据一项2021 年的报告发现&#xff0c;当 GLP-1 类似物用于治疗 2 型糖尿病时&#xff0c;全因死亡率降低了 12%&#xff0c;它们不仅降糖效果显著&#xff0c;同时还兼具减重、降压、改善血脂谱等作用。近几年&#xff0c;随着GLP-1R激动剂类药物市场规模不断增长&#xff0c;美…...

C++远程监控系统接收端- RevPlayMDIChildWnd.cpp

void CRevPlayWnd::InitMultiSock() { int RevBuf; int status; BOOL bFlag; CString ErrMsg; SOCKADDR_IN stLocalAddr; SOCKADDR_IN stDestAddr; SOCKET hNewSock; int RevLensizeof(RevBuf); //创建一个IP组播套接字 MultiSock W…...

QT之OpenGL深度测试

QT之OpenGL深度测试1. 深度测试概述1. 1 提前深度测试1.2 深度测试相关函数2. 深度测试精度2.1 深度冲突3. Demo4. 参考1. 深度测试概述 在OpenGL中深度测试(Depth Testing)是关闭的&#xff0c;此时在渲染图形时会产生一种现象后渲染的会把最先渲染的遮挡住。而在启用深度测试…...

用LCR测试仪测试无线充电系统中的线圈

宽阻抗范围用来表征电感和质量因数– 高精度 DCR 测量– 制造环节快速测量– 大量夹具可供选择智能终端上不断增加新功能&#xff0c;电池寿命成为用户最头痛的问题之一。相比便携式电源和电缆供电而言&#xff0c;无线充电技术因其方便性和多功能性获得了很大的关注&#xff0…...

华为、南卡和漫步者蓝牙耳机怎么选?国产高性价比蓝牙耳机推荐

随着蓝牙耳机的快速发展&#xff0c;现如今使用蓝牙耳机的人也越来越多。其中&#xff0c;日益增多的国产蓝牙耳机品牌也逐渐被大众认识、认可。目前一些热销的国产蓝牙耳机&#xff0c;如华为、南卡和漫步者等都是大家比较熟知的品牌。那么&#xff0c;这三个品牌哪个性价比高…...

MySQl学习(从入门到精通12)

MySQl学习&#xff08;从入门到精通12&#xff09;第 15 章_存储过程与函数1. 存储过程概述1. 1 理解1. 2 分类2. 创建存储过程2. 1 语法分析2. 2 代码举例3. 调用存储过程3. 1 调用格式3. 2 代码举例3. 3 如何调试4. 存储函数的使用4. 1 语法分析4. 2 调用存储函数4. 3 代码举…...

08讲 | 基于STM32单片机NBIOT定位实战项目

前言 绘制基于 STM32 单片机的 NBIOT 实战开发板。 文章目录前言一、原理图1、绘制1&#xff09;电源供电a、USB 转 TTL 电路b、锂电池充电管理电路c、3.3V电压转换电路d、一键开关机电路2&#xff09;单片机最小系统3&#xff09;ADC电压转换电路4&#xff09;NBIOT 模组串口电…...

提取接近竖直物体(粗定位)

由于项目的需要提取图像之中的一个接近于竖直的物体&#xff0c;一般的方法是进行图像分割&#xff0c;分割方式使用什么OTSU方式以及hsv方法等等。但是项目中使用的相机是黑白相机&#xff0c;会受到一定的限制。因此想到的是使用线条提取方式。线条提取方式之中最好的方法是使…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

Go语言多线程问题

打印零与奇偶数&#xff08;leetcode 1116&#xff09; 方法1&#xff1a;使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

MySQL:分区的基本使用

目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区&#xff08;Partitioning&#xff09;是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分&#xff08;分区&#xff09;可以独立存储、管理和优化&#xff0c;…...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...