0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

GD32开发实战指南(基础篇) 第22章 SPI

嵌入式大杂烩 来源:嵌入式大杂烩 作者:嵌入式大杂烩 2023-05-24 09:04 次阅读

开发环境:

MDK:Keil 5.30

开发板:GD32F207I-EVAL

MCU:GD32F207IK

1 SPI简介

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

1.1 SPI 信号线

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

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

1684842210943wtxtixill8

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

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

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

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 的偶数边沿采样。

C:\\Users\\BruceOu\\Desktop\\111.png

我们来分析这个 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 模式。

1.3 SPI特性

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

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

2 SPI架构

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

1684842212106p6eaqgquvn

当要发送数据时,我们把数据写入发送缓冲区,硬件将会把它用移位寄存器处理后输出到 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引脚来驱动。

3 SPI工作原理

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接低就可以。

3.2 单主和单从应用

1684842212488ey8d9bek5o

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

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时钟相位的组合选择数据捕捉的时钟边沿。

3.4 数据帧格式

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

3.5 SPI主从模式工作原理

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

设置SPI_CTL0寄存器的PSC [2:0]位,来定义串行时钟波特率。

选择CKPL和CKPH位,定义数据传输和串行时钟间的相位关系。

设置FF16位来定义8或16位数据帧格式。

配置SPI_CTL0寄存器的LF位定义帧格式。

如果NSS引脚需要工作在输入模式,硬件模式中在整个数据帧传输期间应把NSS引脚连接到高电平;在软件模式中,需设置SPI_CTL0寄存器的SWNSSEN=1和SWNSS=1。如果NSS引脚工作在输出模式,则只需设置SSOE=1位。

设置MSTMOD=1和SPIEN=1,只当NSS引脚被连到高电平,这些位才能保持置位。

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

设置FF16位以定义数据帧格式为8位或16位。

定义数据传输和串行时钟之间的相位关系。

帧格式必须和主设备相同,MSB在前还是LSB在前取决于SPI_CTL0寄存器中的LF位。

硬件模式下,在完整的数据帧(8位或16位)发送过程中,NSS引脚必须为低电平。软件模式下,设置SPI_CTL0寄存器中的SWNSSEN=1,SWNSS=0。

MSTMOD=0位,设置SPIEN=1,使相应引脚工作于SPI模式下。

3.6 状态标志

应用程序通过3个状态标志可以完全监控SPI总线的状态。

1.发送缓冲器空闲标志(TBE)

此标志为’1’时表明发送缓冲器为空,可以写下一个待发送的数据进入缓冲器中。当写入SPI_DATA时,TBE标志被清除。

2.接收缓冲器非空(RBNE)

此标志为’1’时表明在接收缓冲器中包含有效的接收数据。读SPI数据寄存器可以清除此标志。

3.忙(Busy)标志

TRANS标志由硬件设置与清除(写入此位无效果),此标志表明SPI通信层的状态。

3.7 SPI中断

SPI的相关中断标志如下:

中断事件 事件标志 使能控制位
发送缓冲器空标志 TBE TBEIE
接收缓冲器非空标志 RBNE RBNEIE
主模式失效事件 CONFERR ERRIE
溢出错误 RXORERR
CRC****错误标志 CRCERR

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上,如下图所示。

1684842212747bclncfwwvf

5 SPI具体代码实现

首先是SPI的硬件初始化。

/*
   brief      initialize SPI1 GPIO and parameter
   param[in]  none
   param[out] none
   retval     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 flash
   param[in]  none
   param[out] none
   retval     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 bus
   param[in]  byte: byte to send
   param[out] none
   retval     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 flash
   param[in]  pbuffer: pointer to the buffer
   param[in]  write_addr: flash's internal address to write
   param[in]  num_byte_to_write: number of bytes to write to the flash
   param[out] none
   retval     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 flash
   param[in]  pbuffer: pointer to the buffer that receives the data read from the flash
   param[in]  read_addr: flash's internal address to read from
   param[in]  num_byte_to_read: number of bytes to read from the flash
   param[out] none
   retval     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 function
    param[in]  none
    param[out] none
    retval     none
*/
int main(void)
{
    st_bsp_usart_dev bsp_usart_dev0 = USART_DEV0_CONFIG;
    st_bsp_led_dev bsp_led_dev0 = LED_DEV0_CONFIG;

    //systick init
    sysTick_init();

    // led init
    bsp_led_init(&bsp_led_dev0);

    //usart init 115200 8-N-1
    bsp_usart_init(&bsp_usart_dev0, USART_MODE_EXTI, 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("\\n\\rThe Flash_ID:0x%X\\n\\r\\n\\r", flash_id);

    /* flash id is correct */
    if(SFLASH_ID == flash_id)
    {
        printf("\\n\\rWrite to tx_buffer:\\n\\r\\n\\r");

        /* 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("\\n\\r\\n\\rRead from rx_buffer:\\n\\r\\n\\r");

        /* 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)
    {
        bsp_led_toggle(&bsp_led_dev0);
        delay_ms(1000);
    }
}

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

6 实验现象

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

16848422131184oulwdc9in

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • mcu
    mcu
    +关注

    关注

    146

    文章

    16840

    浏览量

    349735
  • SPI
    SPI
    +关注

    关注

    17

    文章

    1686

    浏览量

    91135
  • 开发板
    +关注

    关注

    25

    文章

    4874

    浏览量

    96972
  • Cortex-M
    +关注

    关注

    2

    文章

    227

    浏览量

    29697
  • GD32
    +关注

    关注

    7

    文章

    403

    浏览量

    24088
收藏 人收藏

    评论

    相关推荐

    GD32开发实战指南(基础) 1 开发环境搭建

    开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 GD32F207I-EVAL
    的头像 发表于 05-07 23:35 1w次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>1<b class='flag-5'>章</b> <b class='flag-5'>开发</b>环境搭建

    GD32开发实战指南(基础) 4 GD32启动流程详解(Keil版)

    ,所有的一切都需要由开发者来设置,这里处理器是没有堆栈,没有中断,更没有外围设备,这些工作是需要软件来指定的,而且不同的CPU类型、不同大小的内存和不同种类的外设,其初始化工作都是不同的。本文将以GD32F207IK (基于Cortex-M3)为例进行讲解。
    的头像 发表于 05-10 09:00 1.6w次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>4<b class='flag-5'>章</b> <b class='flag-5'>GD32</b>启动流程详解(Keil版)

    GD32开发实战指南(基础) 7 定时器

    系统滴答定时器一般用来提供“心跳”作用,而GD32定时器最基本功能也是定时,可以设置不同时间长度的定时。定时器除了最基本的定时功能外,定时器与GPIO有挂钩使得它可以发挥强大的作用,比如可以输出
    的头像 发表于 05-11 09:00 1.2w次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>7<b class='flag-5'>章</b> 定时器

    GD32开发实战指南(基础) 8 定时器

    开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 PWM输出的工作原理 脉冲宽度调制(PWM) ,是英文“Pulse Wi
    的头像 发表于 05-12 22:14 7750次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>8<b class='flag-5'>章</b> 定时器

    GD32开发实战指南(基础) 11 CPU的高级代理-DMA

    或者存储器和存储器之间的高速数据传输,因而被广泛地使用。早在 8086 的应用中就已经有 Intel 的 8237 这种典型的 DMA 控制器,而 GD32的 DMA 则是以类似外设的形式添加到 Cortex 内核之外的。可以说,DMA就是CPU的高级代理,DMA大大减轻了CPU的负担。
    的头像 发表于 05-16 08:59 3780次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>11<b class='flag-5'>章</b> CPU的高级代理-DMA

    GD32开发实战指南(基础) 12 ADC

    GD32F2系列有 3 个逐次逼近型的ADC,精度为 12 位,有18个多路复用通道,可以转换来自16个外部通道和2个内部通道的模拟信号。其中ADC0 和 ADC1都有 16 个外部通道, ADC2
    的头像 发表于 05-16 09:03 1.1w次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>12<b class='flag-5'>章</b> ADC

    GD32开发实战指南(基础) 14 内部温度传感器

    GD32 有一个内部的温度传感器,可以用来测量 CPU 及周围的温度(TA)。该温度传感器在内部和 ADCx_IN16 输入通道相连接,此通道把传感器输出的电压转换成数字值。温度传感器模拟输入
    的头像 发表于 05-17 08:58 5135次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>14<b class='flag-5'>章</b> 内部温度传感器

    GD32开发实战指南(基础) 15 低功耗

    GD32的工作电压(VDD)为2.0~3.6V。通过内置的电压调节器提供所需的1.8V电源。当主电源VDD掉电后,通过VBAT脚为实时时钟(RTC)和备份寄存器提供电源。
    的头像 发表于 05-17 08:59 7919次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>15<b class='flag-5'>章</b> 低功耗

    GD32开发实战指南(基础) 16 RTC

    开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 RTC工作原理 1.1 RTC简介
    的头像 发表于 05-18 22:14 6934次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>16<b class='flag-5'>章</b> RTC

    GD32开发实战指南(基础) 17 看门狗

    开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK GD32 有两个看门狗, 一个是
    的头像 发表于 06-03 16:00 1.1w次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>17<b class='flag-5'>章</b> 看门狗

    【图书分享】《STM32库开发实战指南

    画板 22 字库及BMP图片显示 23 OV7670摄像头驱动 24
    发表于 03-13 17:01

    GD32 MCU原理及固件库开发指南》 + 初读感悟

    GD32 MCU原理固件库开发指南这本书内容丰富,囊括了GD32中的所有外设,书中首先介绍了如何使用MDK或IAR软件搭建GD32工程环境,让初学者能快速基于工程上手编程。书中主要对
    发表于 03-31 22:11

    GD32 MCU原理及固件库开发指南》+读后感

    2介绍GD32 MCU快速入门与开发平台搭建的方法,包括对软硬件开发平台、调试工具、GD32
    发表于 06-06 21:52

    GD32SPI部分需要特别注意的部分 以及STM32移植到GD32SPI需要修改的部分

    如题,本文主要分享我在将STM32代码移植到GD32上时SPI部分出现的问题,(GD32F103VET6)。业务逻辑:GD32SPI作为从机接收传感器发送的数据,传感器为主机。
    发表于 12-02 15:21 51次下载
    <b class='flag-5'>GD32</b>中<b class='flag-5'>SPI</b>部分需要特别注意的部分 以及STM32移植到<b class='flag-5'>GD32SPI</b>需要修改的部分

    GD32开发实战指南(基础) 19 程序加密

    GD32通过读取芯片唯一ID号来实现程序的保护,防止被抄袭。96位的产品唯一身份标识所提供的参考号码对任意一个GD32微控制器
    的头像 发表于 05-20 09:10 4037次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>19<b class='flag-5'>章</b> 程序加密