当前位置: 代码迷 >> 综合 >> STM32F4-SPI
  详细解决方案

STM32F4-SPI

热度:76   发布时间:2024-02-09 00:43:53.0

SPI 简介
SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola首先在其 MC68HCXX 系列处理器上定义的。SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32F4 也有 SPI 接口。下面我们看看 SPI 的内部简明图(图 30.1.1).
在这里插入图片描述
SPI 接口一般使用 4 条线通信:
MISO 主设备数据输入,从设备数据输出。
MOSI 主设备数据输出,从设备数据输入。
SCLK 时钟信号,由主设备产生。
CS 从设备片选信号,由主设备控制。

从图中可以看出,主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器写入一个字节来发起一次传输。寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。
SPI 主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。
SPI 总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果 CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果 CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI 主模块和与之通信的外设备时钟相位和极性应该一致。
不同时钟相位下的总线数据传输时序如图 32.1.1 所示:
在这里插入图片描述
STM32F429 的 SPI 功能很强大,SPI 时钟最高可以到 45Mhz,支持 DMA,可以配置为 SPI协议或者 I2S 协议(支持全双工 I2S)。
本章,我们将使用 STM32F429 的 SPI 来读取外部 SPI FLASH 芯片(W25Q256),实现类似上节的功能。这里对 SPI 我们只简单介绍一下 SPI 的使用,STM32F429 的 SPI 详细介绍请参考《STM32F4xx 中文参考手册》第 721 页,27 节。然后我们再介绍下 SPI FLASH 芯片。
这节,我们使用 STM32F429 的 SPI5 的主模式,下面就来看看 SPI5 部分的设置步骤吧。SPI 相关的库函数和定义分布在文件 stm32f4xx_hal_spi.c 以及头文件 stm32f4xx_hal_spi.h 中。STM32 的主模式配置步骤如下:
1)配置相关引脚的复用功能,使能 SPI5 时钟。
我们要用 SPI5,第一步就要使能 SPI5 时钟和响应引脚时钟。其次要设置 SPI5 的相关引脚
为复用(AF5)输出,这样才会连接到 SPI5 上。这里我们使用的是 PF7、8、9 这 3 个(SCK.、
MISO、MOSI,CS 使用软件管理方式)
,所以设置这三个为复用 IO,复用功能为 AF5。
使能 SPI5 时钟的方法为:

__HAL_RCC_SPI5_CLK_ENABLE();

//使能 SPI5 时钟
复用 PF7,PF8,PF9 为 SPI5 引脚是通过 HAL_GPIO_Init 函数实现,代码如下:

GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
GPIO_Initure.Mode=GPIO_MODE_AF_PP;//复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP;//上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST;//快速
GPIO_Initure.Alternate=GPIO_AF5_SPI5;//复用为 SPI5
HAL_GPIO_Init(GPIOF,&GPIO_Initure);

2)初始化 SPI5,设置 SPI5 工作模式等。
这一步全部是通过 SPI5_CR1 来设置,我们设置 SPI5 为主机模式,设置数据格式为 8 位,然后通过 CPOL 和 CPHA 位来设置 SCK 时钟极性及采样方式。并设置 SPI5 的时钟频率(最大45Mhz),以及数据的格式(MSB 在前还是 LSB 在前)。在 HAL 库中初始化 SPI 的函数为:

HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);

下面我们来看看 SPI_HandleTypeDef 定义:

typedef struct __SPI_HandleTypeDef
{
SPI_TypeDef     *Instance;//基地址
SPI_InitTypeDef Init;//初始化接哦固体
uint8_t         *pTxBuffPtr;//发送缓存
uint16_t        TxXferSize;//发送数据大小
uint16_t        TxXferCount;//还剩余多少个数据要发送
uint8_t         *pRxBuffPtr;//接收缓存
uint16_t        RxXferSize;//接收数据大小
uint16_t        RxXferCount;//还剩余多少个数据要接收
DMA_HandleTypeDef *hdmatx;//DMA 发送句柄
DMA_HandleTypeDef *hdmarx;//DMA 接收句柄
void			(*RxISR)(struct __SPI_HandleTypeDef * hspi);
void			(*TxISR)(struct __SPI_HandleTypeDef * hspi);
HAL_LockTypeDef   Lock;
__IO HAL_SPI_StateTypeDef   State;
__IO uint32_t               ErrorCode;
}SPI_HandleTypeDef;

该结构体和串口句柄结构体类似,同样有 6 个成员变量和 2 个 DMA_HandleTypeDef 指针类型变量。这几个参数的作用这里我们就不做过多讲解,大家如果对 HAL 库串口通信理解了,那么这些就很好理解。这里我们主要讲解第二个成员变量 Init,它是 SPI_InitTypeDef 结构体类型,该结构体定义如下:

typedef struct
{uint32_t Mode;// 模式:主(SPI_MODE_MASTER),从(SPI_MODE_SLAVE)uint32_t Direction; //方式: 只接受模式,单线双向通信数据模式,全双工uint32_t DataSize;//8 位还是 16 位帧格式选择项uint32_t CLKPolarity; //时钟极性uint32_t CLKPhase; //时钟相位uint32_t NSS;//SS 信号由硬件(NSS 管脚)还是软件控制uint32_t BaudRatePrescaler; //设置 SPI 波特率预分频值uint32_t FirstBit;//起始位是 MSB 还是 LSBuint32_t TIMode;//帧格式 SPI motorola 模式还是 TI 模式uint32_t CRCCalculation; //硬件 CRC 是否使能uint32_t CRCPolynomial; //CRC 多项式
}SPI_InitTypeDef;

该结构体个个成员变量的含义我们已经在成员变量后面注释了,请大家参考学习。SPI 初
始化实例代码如下:

SPI5_Handler.Instance=SPI5;//SP5
SPI5_Handler.Init.Mode=SPI_MODE_MASTER;//模式:主模式
SPI5_Handler.Init.Direction=SPI_DIRECTION_2LINES; //双线模式
SPI5_Handler.Init.DataSize=SPI_DATASIZE_8BIT;//发送接收 8 位帧结构
SPI5_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //串行同步时钟空闲状态为高电平
SPI5_Handler.Init.CLKPhase=SPI_PHASE_2EDGE;//第二个跳变沿数据被采样
SPI5_Handler.Init.NSS=SPI_NSS_SOFT;//NSS 信号由硬件管理
SPI5_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;
//定义波特率预分频的值:波特率预分频值为 256
SPI5_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //指定数据传输从 MSB 位开始
SPI5_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭 TI 模式
SPI5_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;/关闭硬件 CRC
SPI5_Handler.Init.CRCPolynomial=7; //CRC 值计算的多项式
HAL_SPI_Init(&SPI5_Handler);//初始化

同样,HAL 库也提供了 SPI 初始化 MSP 回调函数 HAL_SPI_MspInit,定义如下:

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi);

关于回调函数使用,这里我们就不做过多讲解。
3)使能 SPI1。
这一步通过 SPI5_CR1 的 bit6 来设置,以启动 SPI5,在启动之后,我们就可以开始 SPI 通
讯了。使能 SPI5 的方法为:

__HAL_SPI_ENABLE(&SPI5_Handler);//使能 SPI5

4)SPI 传输数据
通信接口当然需要有发送数据和接受数据的函数,HAL 库提供的发送数据函数原型为:

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData,uint16_t Size,uint32_t Timeout);

这个函数很好理解,往 SPIx 数据寄存器写入数据 Data,从而实现发送。
HAL 库提供的接受数据函数原型为:

HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData,
uint16_t Size, uint32_t Timeout);

这个函数也不难理解,从 SPIx 数据寄存器读出接受到的数据。
前面我们讲解了 SPI 通信的原理,因为 SPI 是全双工,发送一个字节的同时接受一个字节,
发送和接收同时完成,所以 HAL 也提供了一个发送接收统一函数:

HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData,
uint8_t *pRxData, uint16_t Size, uint32_t Timeout);

该函数发送一个字节的同时负责接收一个字节。
5)设置 SPI 传输速度
SPI 初始化结构体 SPI_InitTypeDef 有一个成员变量是 BaudRatePrescaler,该成员变量用来设置 SPI 的预分频系数,从而决定了 SPI 的传输速度。但是 HAL 库并没有提供单独的 SPI 分频系数修改函数,如果我们需要在程序中不时的修改速度,那么我们就要通过设置 SPI 的 CR1 寄存器来修改,具体实现方法请参考后面软件设计小节相关函数。
SPI5 的使用就介绍到这里,接下来介绍一下 W25Q128。W25Q128 是华邦公司推出的大容量 SPI FLASH 产品, W25Q128 的容量为 128Mb,该系列还有 W25Q80/16/32/64 等。 ALIENTEK所选择的 W25Q128 容量为 128Mb,也就是 16M 字节。
W25Q128 将 16M 的容量分为 256 个块(Block),每个块大小为 64K 字节,每个块又分为16 个扇区(Sector)
,每个扇区 4K 个字节。W25Q128 的最小擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。这样我们需要给 W25Q128 开辟一个至少 4K 的缓存区,这样对 SRAM 要求比较高,要求芯片必须有 4K 以上 SRAM 才能很好的操作。
W25Q128 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V,W25Q128 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 80Mhz(双输出时相当于 160Mhz,四输出时相当于 320M),更多的 W25Q128 的介绍,请参考 W25Q128 的DATASHEET。
硬件设计
本章实验功能简介:开机的时候先检测 W25Q256 是否存在,然后在主循环里面检测两个
按键,其中 1 个按键(KEY1)用来执行写入 W25Q256 的操作,另外一个按键(KEY0)用来
执行读出操作,在 LCD 模块上显示相关信息。同时用 DS0 提示程序正在运行。
所要用到的硬件资源如下:

  1. 指示灯 DS0
  2. KEY0 和 KEY1 按键
  3. LCD 模块
  4. SPI
  5. W25Q256
    这里只介绍 W25Q256 与 STM32F429 的连接,板上的 W25Q256 是直接连在 STM32F429
    的 SPI5 上的,连接关系如图 32.2.1 所示:
    在这里插入图片描述
    软件设计
    打开我们光盘的 SPI 实验工程,可以看到我们加入了 spi.c,flash.c 文件以及头文件 spi.h 和
    flash.h,同时引入了库函数文件 stm32f4xx_hal_spi.c 文件以及头文件 stm32f4xx_hal_spi.h。
    打开 spi.c 文件,看到如下代码:
SPI_HandleTypeDef SPI5_Handler; //SPI 句柄
//以下是 SPI 模块的初始化代码,配置成主机模式
//SPI 口初始化
//这里针是对 SPI5 的初始化
void SPI5_Init(void)
{SPI5_Handler.Instance=SPI5;//SP5SPI5_Handler.Init.Mode=SPI_MODE_MASTER; //设置 SPI 工作模式,设置为主模式SPI5_Handler.Init.Direction=SPI_DIRECTION_2LINES; // SPI 设置为双线模式SPI5_Handler.Init.DataSize=SPI_DATASIZE_8BIT; // PI 发送接收 8 位帧结构SPI5_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //同步时钟空闲状态为高电平SPI5_Handler.Init.CLKPhase=SPI_PHASE_2EDGE;//同步时钟第 2 个跳变沿数据被采样SPI5_Handler.Init.NSS=SPI_NSS_SOFT; //NSS 信号由硬件(NSS 管脚)控制SPI5_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;//定义波特率预分频的值:波特率预分频值为 256SPI5_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB;//指定数据传输从 MSB 位开始SPI5_Handler.Init.TIMode=SPI_TIMODE_DISABLE;//关闭 TI 模式SPI5_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE; //关闭 CRCSPI5_Handler.Init.CRCPolynomial=7; //CRC 值计算的多项式HAL_SPI_Init(&SPI5_Handler);//初始化__HAL_SPI_ENABLE(&SPI5_Handler); //使能 SPI5SPI5_ReadWriteByte(0Xff); //启动传输}//SPI5 底层驱动,时钟使能,引脚配置//此函数会被 HAL_SPI_Init()调用//hspi:SPI 句柄void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi){GPIO_InitTypeDef GPIO_Initure;__HAL_RCC_GPIOF_CLK_ENABLE();__HAL_RCC_SPI5_CLK_ENABLE();519//使能 GPIOF 时钟//使能 SPI5 时钟GPIO_Initure.Pin=GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9; //PF7,8,9GPIO_Initure.Mode=GPIO_MODE_AF_PP;//复用推挽输出GPIO_Initure.Pull=GPIO_PULLUP;//上拉GPIO_Initure.Speed=GPIO_SPEED_FAST;//快速GPIO_Initure.Alternate=GPIO_AF5_SPI5;//复用为 SPI5HAL_GPIO_Init(GPIOF,&GPIO_Initure);}//SPI 速度设置函数//SPI 速度=fAPB1/分频系数//@ref SPI_BaudRate_Prescaler:SPI_BAUDRATEPRESCALER_2~//SPI_BAUDRATEPRESCALER_2 256//fAPB1 时钟一般为 45Mhz:void SPI5_SetSpeed(u8 SPI_BaudRatePrescaler){assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性__HAL_SPI_DISABLE(&SPI5_Handler); //关闭 SPISPI5_Handler.Instance->CR1&=0XFFC7; //位 3-5 清零,用来设置波特率SPI5_Handler.Instance->CR1|=SPI_BaudRatePrescaler;//设置 SPI 速度__HAL_SPI_ENABLE(&SPI5_Handler); //使能 SPI}//SPI5 读写一个字节//TxData:要写入的字节返回值:读取到的字节u8 SPI5_ReadWriteByte(u8 TxData){u8 Rxdata;HAL_SPI_TransmitReceive(&SPI5_Handler,&TxData,&Rxdata,1, 1000);return Rxdata; //返回收到的数据
}

此部分代码主要初始化 SPI,这里我们选择的是 SPI5,所以在 SPI5_Init 函数里面,其相关的操作都是针对 SPI5 的,其初始化主要是通过函数 HAL_SPI_Init 来实现的,初始化之后同时开启 SPI5。在初始化之后,我们就可以开始使用 SPI5 了,这里特别注意,SPI 初始化函数的最后有一个启动传输,这句话最大的作用就是维持 MOSI 为高电平,而且这句话也不是必须的,可以去掉。
在 SPI5_Init 函数里面,我们把 SPI5 的频率设置成了最低(90Mhz,256 分频),而在外部我们可以随时通过函数 SPI5_SetSpeed 来设置 SPI5 的速度。函数 SPI5_ReadWriteByte 则主要通过调用 HAL 库函数 HAL_SPI_TransmitReceive 来实现数据的发送和接收。
接下来我们来看看 w25qxx.c 文件内容。由于篇幅所限,详细代码,这里就不贴出了。我们仅介绍几个重要的函数,首先是 W25QXX_Read 函数,该函数用于从 W25Q128 的指定地址读出指定长度的数据。其代码如下:

//读取 SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大 65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{u16 i;W25QXX_CS=0;//使能器件SPI5_ReadWriteByte(W25X_ReadData); //发送读取命令if(W25QXX_TYPE==W25Q256) //如果是 W25Q256 地址为 4 字节的,要发送最高 8 位{SPI5_ReadWriteByte((u8)((ReadAddr)>>24));}SPI5_ReadWriteByte((u8)((ReadAddr)>>16)); //发送 24bit 地址SPI5_ReadWriteByte((u8)((ReadAddr)>>8));SPI5_ReadWriteByte((u8)ReadAddr);for(i=0;i<NumByteToRead;i++){pBuffer[i]=SPI5_ReadWriteByte(0XFF);//循环读数}W25QXX_CS=1;
}

由于 W25Q256 支持以任意地址(但是不能超过 W25Q256 的地址范围)开始读取数据,所以,这个代码相对来说就比较简单了,在发送 32 位地址(25Q256 及以上型号有 32 位地址,其他型号只有 24 位地址)之后,程序就可以开始循环读数据了,其地址会自动增加的,不过要注意,不能读的数据超过了 W25Q256 的地址范围哦!否则读出来的数据,就不是你想要的数据了。
有读的函数,当然就有写的函数了,接下来,我们介绍 W25QXX_Write 这个函数,该函数的作用与W25QXX_Flash_Read 的作用类似,不过是用来写数据到 W25Q256 里面的,代码如下:

//写 SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大 65535)
u8 W25QXX_BUFFER[4096];
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{u32 secpos;u16 secoff,secremain,i;u8 * W25QXX_BUF;W25QXX_BUF=W25QXX_BUFFER;secpos=WriteAddr/4096;//扇区地址secoff=WriteAddr%4096;//在扇区内的偏移secremain=4096-secoff;//扇区剩余空间大小if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于 4096 个字节while(1){W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容for(i=0;i<secremain;i++)//校验数据{if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除}if(i<secremain)//需要擦除{W25QXX_Erase_Sector(secpos);//擦除这个扇区for(i=0;i<secremain;i++)//复制{W25QXX_BUF[i+secoff]=pBuffer[i];}W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间.if(NumByteToWrite==secremain)break;//写入结束了else//写入未结束{secpos++;//扇区地址增 1secoff=0;//偏移位置为 0pBuffer+=secremain; //指针偏移WriteAddr+=secremain;//写地址偏移NumByteToWrite-=secremain;//字节数递减if(NumByteToWrite>4096)secremain=4096; //下一个扇区还是写不完else secremain=NumByteToWrite;//下一个扇区可以写完了}};
}

该函数可以在 W25Q256 的任意地址开始写入任意长度(必须不超过 W25Q256 的容量)的数据。我们这里简单介绍一下思路:先获得首地址(WriteAddr)所在的扇区,并计算在扇区内的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循环,直到写入结束。这里我们还定义了一个 W25QXX_BUFFER 的全局变量,用于擦除时缓存扇区内的数据。
其他的代码就比较简单了,我们这里不介绍了。对于头文件 w25qxx.h,这里面就定义了一些与 W25Q128 操作相关的命令和函数(部分省略了),这些命令在 W25Q128 的数据手册上都有详细的介绍,感兴趣的读者可以参考该数据手册。
最后,我们看看 main 函数,代码如下:

//要写入到 W25Q16 的字符串数组
const u8 TEXT_Buffer[]={"Apollo STM32F4 SPI TEST"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{u8 key;u16 i=0;u8 datatemp[SIZE];u32 FLASH_SIZE;HAL_Init();//初始化 HAL 库Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz...//此处省略部分代码W25QXX_Init();//W25QXX 初始化while(W25QXX_ReadID()!=W25Q256)//检测不到 W25Q256{LCD_ShowString(30,150,200,16,16,"W25Q256 Check Failed!");delay_ms(500);LCD_ShowString(30,150,200,16,16,"Please Check!");delay_ms(500);LED0=!LED0;//DS0 闪烁}LCD_ShowString(30,150,200,16,16,"W25Q256 Ready!");FLASH_SIZE=32*1024*1024; //FLASH 大小为 32M 字节POINT_COLOR=BLUE;//设置字体为蓝色while(1){key=KEY_Scan(0);if(key==KEY1_PRES)//KEY1 按下,写入 W25Q128{LCD_Fill(0,170,239,319,WHITE);//清除半屏LCD_ShowString(30,170,200,16,16,"Start Write W25Q256....");W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE);//从倒数第 100 个地址处开始,写入 SIZE 长度的数据LCD_ShowString(30,170,200,16,16,"W25Q256 Write Finished!");//提示传送完成}if(key==KEY0_PRES)//KEY0 按下,读取字符串并显示{LCD_ShowString(30,170,200,16,16,"Start Read W25Q256.... ");W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE);//从倒数第 100 个地址处开始,读出 SIZE 个字节LCD_ShowString(30,170,200,16,16,"The Data Readed Is: "); //提示传送完成LCD_ShowString(30,190,200,16,16,datatemp); //显示读到的字符串}i++;delay_ms(10);if(i==20){LED0=!LED0;//提示系统正在运行i=0;}}
}

在这里插入图片描述
伴随 DS0 的不停闪烁,提示程序在运行。程序在开机的时候会检测 W25Q256 是否存在,如果不存在则会在 LCD 模块上显示错误信息,同时 DS0 慢闪。大家可以通过跳线帽把 PF7 和PF8 短接就可以看到报错了。