《嵌入式 – 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的相关中断标志如下:
中断事件 | 事件标志 | 使能控制位 |
---|---|---|
发送缓冲器空标志 | TBE | TBEIE |
接收缓冲器非空标志 | RBNE | RBNEIE |
主模式失效事件 | CONFERR | ERRIE |
溢出错误 | 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
开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 22.1 SPI简介 SPI,是Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的…...
一个优质软件测试工程师的简历应该有的样子(答应我一定要收藏起来)
个人简历 基本信息 姓 名:xxx 性 别: 女 年 龄:24 现住 地址: 深圳 测试 经验:3年 学 历:本科 联系 电话:18xxxxxxxx 邮 箱:xxxxl163.com 求职意向 应聘岗位:软件…...
C++ 浅谈之 STL Deque
C 浅谈之 STL Deque HELLO,各位博友好,我是阿呆 🙈🙈🙈 这里是 C 浅谈系列,收录在专栏 C 语言中 😜😜😜 本系列阿呆将记录一些 C 语言重要的语法特性 🏃&a…...
Koa2-项目中的基本应用
文章目录安装配置koa2配置nodemon,热更新我们的项目中间件什么是中间件👻洋葱模型路由中间件连接数据库 - mysql后端允许跨域处理请求getpostputdelete后续会继续更新安装配置koa2 👻安装 koa2 npm i koa2 -s👻在package.json 配置,当然是在…...
Flask入门(2):配置
目录2.Flask配置2.1 直接写入主脚本2.2 系统环境变量2.3 单独的配置文件2.4 多个配置类2.5 Flask内置配置2.Flask配置 我们都知道,Flask应用程序肯定是需要各种各样的配置。来满足我们不同的需求的,这样可以使我们的应用程序更加灵活。比如可以根据需要…...
Linux--fork
一、fork入门知识 fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。可以简单地说fork()的作用就是创建一…...
计算机组成原理(一)
1.了解计算机硬件的发展和软件的发展历程; 硬件: 电子管时代(1946-1959):电子管、声汞延迟线、磁鼓 晶体管时代(1959-1964):晶体管、磁芯 中、小规模集成电路时代&#…...
【SpringBoot】实现Async异步任务
1. 环境准备 在 Spring Boot 入口类上配置 EnableAsync 注解开启异步处理。 创建任务抽象类 AbstractTask,并分别配置三个任务方法 doTaskOne(),doTaskTwo(),doTaskThree()。 public abstract class AbstractTask {private static Random r…...
Node =>Express学习
1.Express 能做什么 能快速构建web网站的服务器 或 Api接口的服务期 Web网站服务器,专门对外提供Web网页资源的服务器Api接口服务器:专门对外提供API接口的服务器 2.安装 在项目所处的目录中,运行以下命令,简装到项目中了 npm …...
QT基础入门【布局篇】消除控件之间的间隔
一、相关参数 layoutLeftMargin: layout内的布局距离边框左端的距离。 layoutTopMargin: layout内的布局距离边框顶端的距离。 layoutRightMargin: layout内的布局距离边框右端的距离。 layoutBottomMargin: layout内的布局距离边框底端的距离。 layoutHorizontalSpacing: layo…...
vue脚手架 element-ui spring boot 实现图片上传阿里云 并保存到数据库
一.阿里云 注册登陆就不讲了,登陆进去后如下操作 1. 进入对象存储OSS 创建一个新的Bucket 随后点击新建的bucket 2.去访问RAM 前往RAM控制台 3.去创建用户 4.创建密匙 5.随后返回RAM控制台 给用户增加权限,文件上传所需权限,需要带含有…...
【FPGA】Verilog:组合电路 | 3—8译码器 | 编码器 | 74LS148
前言:本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载 示例:编码/译码器的应用 功能特性: 采用 Xilinx Artix-7 XC7A35T芯片 配置方式:USB-JTAG/SPI Flash 高达100MHz 的内部时钟速度 存储器&…...
GLP-1类药物研发进展-销售数据-上市药品前景分析
据一项2021 年的报告发现,当 GLP-1 类似物用于治疗 2 型糖尿病时,全因死亡率降低了 12%,它们不仅降糖效果显著,同时还兼具减重、降压、改善血脂谱等作用。近几年,随着GLP-1R激动剂类药物市场规模不断增长,美…...
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)是关闭的,此时在渲染图形时会产生一种现象后渲染的会把最先渲染的遮挡住。而在启用深度测试…...
用LCR测试仪测试无线充电系统中的线圈
宽阻抗范围用来表征电感和质量因数– 高精度 DCR 测量– 制造环节快速测量– 大量夹具可供选择智能终端上不断增加新功能,电池寿命成为用户最头痛的问题之一。相比便携式电源和电缆供电而言,无线充电技术因其方便性和多功能性获得了很大的关注࿰…...
华为、南卡和漫步者蓝牙耳机怎么选?国产高性价比蓝牙耳机推荐
随着蓝牙耳机的快速发展,现如今使用蓝牙耳机的人也越来越多。其中,日益增多的国产蓝牙耳机品牌也逐渐被大众认识、认可。目前一些热销的国产蓝牙耳机,如华为、南卡和漫步者等都是大家比较熟知的品牌。那么,这三个品牌哪个性价比高…...
MySQl学习(从入门到精通12)
MySQl学习(从入门到精通12)第 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)电源供电a、USB 转 TTL 电路b、锂电池充电管理电路c、3.3V电压转换电路d、一键开关机电路2)单片机最小系统3)ADC电压转换电路4)NBIOT 模组串口电…...
提取接近竖直物体(粗定位)
由于项目的需要提取图像之中的一个接近于竖直的物体,一般的方法是进行图像分割,分割方式使用什么OTSU方式以及hsv方法等等。但是项目中使用的相机是黑白相机,会受到一定的限制。因此想到的是使用线条提取方式。线条提取方式之中最好的方法是使…...
程序环境和预处理
目录一、程序的翻译环境和执行环境二、编译链接2.1 翻译环境2.2 编译2.2.1 预处理2.2.2 编译2.2.3 汇编2.3 链接2.4 结果三、运行环境四、预处理详解4.1 #define4.1.1 #define定义标识符4.1.2 #define定义宏4.1.3 #define 替换规则4.1.4 #和##4.1.5 带副作用的宏参数4.1.6 宏和…...
财报解读:业务复苏迹象明显,中国中免能否重写增长神话?
2月3日,中国中免披露2022年度业绩快报,2022年总营收为544.63亿元,同比下降19.52%;实现归属于上市公司股东的净利润50.25亿元,同比下降47.95%。来源:中国中免2022年度业绩快报业绩近乎腰斩,但从长…...
macOS中虚拟机桥接模式分配静态ip
1.首先使用dhclient命令,在局域网中分配一个C类地址。 2.获得地址后,输入ifconfig,查看分配的地址。 3.然后编辑vi /etc/sysconfig/network-scripts/ifcfg-en***文件 在该配置文件中编辑,设置ONBOOTyes,而后添加静态配…...
prometheus increase函数统计得到小数
今天发现prometheus的increase函数得到了小数,研究一下源码,以下是rate/increase/delta 对应的计算函数https://github.com/prometheus/prometheus/blob/d77b56e88e3d554a499e22d2073812b59191256c/promql/functions.go#L55// extrapolatedRate is a uti…...
C++学习记录——유 类和对象(3)
文章目录1、赋值运算符重载1、运算符重载1、理解2、运算符重载实例2、赋值运算符重载2、日期类的实现1、加减函数1、加函数2、减函数2、前/后置--重载3.两个日期相减其他1、流插入2、流提取日期类的整体实现代码: https://gitee.com/kongqizyd/start-some-c-codes-for-learning…...
基于Hi3861平台的OpenHarmony程序是如何启动运行的
一、前言 在继续后面课程的内容讲解前,我们要知道在H3861平台上编写的代码到底是如何启动的,这一点很重要。 先分析HelloWorld程序的启动运行流程,并顺便讲解OpenHarmony在H3861平台的,系统是从哪里启动的。 反着推导函数之间具体…...
2023彻底解决Typora使用iPic微博图床失效问题
一、问题描述用Typora搭配iPic图床使用,最近csdn图片显示不出来用浏览器打开图片显示403,这里原因是微博图床出问题了导致的而使用iPic其他图床则需要一直付费,那有没有一劳永逸的解决所有问题呢?二、旧图恢复首先怎么找回旧图&am…...
Revit中添加水平仰视平面图及水平剖面
一、 Revit中如何添加水平仰视平面图 在Revit平面视图中视角是俯视视角,但是在一些特殊的情况下,我们可能需要创建仰视视角的平面视图,例如我们需要向上看天花板的灯具布置的时候。 1.下面讲一下如何在添加仰视平面视图的方法。如图1在模型中…...
Python 循环语句
Python的循环语句,程序在一般情况下是按顺序执行的。编程语言提供了各种控制结构,允许更复杂的执行路径。循环语句允许我们执行一个语句或语句组多次,下面是在大多数编程语言中的循环语句的一般形式:Python 提供了 for 循环和 whi…...
使用 ThreeJS 实现第一个三维场景(详)
文章目录参考描述index.html三维场景的基本实现导入 ThreeJS准备工作场景摄像机视锥体正交摄像机透视摄像机渲染器后续处理将摄像机添加至场景中移动摄像机设置画布尺寸将渲染器创建的画布添加到 HTML 元素中渲染物体结构材质合成将物体添加至场景中代码总汇执行效果动画reques…...
龙岗区最新通告/提高seo关键词排名
goldengate for sqlserver 日志暴涨的解决办法 gg for sqlserver 2008 R2 开启后,sqlserver的日志会出现暴涨的情况,解决办法如下: 1.先确认是否数据库设置问题导致 如: 1)查看ggerror中,是否有报错! 2)…...
政务网站建设管理工作总结/网络营销比较好的企业
解决方法: sudo apt-get install -f 转载于:https://www.cnblogs.com/wulinmenghuantejing/p/8378005.html...
专业建站开发/网络营销培训课程
注:以下内容适用于WPF、C#编码。 最近对异步编程产生了较大的兴趣,所以写出来和感兴趣的朋友一起分享! 1、关于Dispatcher 调度类: 提供用于管理线程的工作项队列的服务。 通常,WPF 应用程序从两个线程开始&#…...
audio player wordpress 使用方法/bt磁力种子
一、background-attachment属性在CSS中,使用背景附件属性background-attachment可以设置背景图像是随对象滚动还是固定不动。语法:background-attachment:scroll/fixed;说明:background-attachment 属性只有2个属性值。scroll表示背景图像随对…...
祥云建站平台/营销型企业网站有哪些平台
Consignment 寄存一般是指卖方把货物存放在买方所属仓库,消耗后结帐。库存水平控制和货物的物理管理都由买方负责。这是目前很多大卖场通行的做法。典型的VMI一般也 是指卖方把货物存放在买方附近的仓库(可以是买方、卖方或者第三方仓库)&…...
自己电脑做的网站如何映射到公网/在线优化工具
在系统管理或者数据库管理中,经常要周期性的执行某一个命令或者SQL语句。对于linux系统熟悉的人都知道linux的cron计划任务,能很方便地实现定期运行指定命令的功能。Mysql在5.1以后推出了事件调度器(Event Scheduler),和linux的cron功能一样&…...