STM32F4-SPI

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 还是 LSB
	uint32_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;
	//SP5
	SPI5_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;//定义波特率预分频的值:波特率预分频值为 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_ENABLE(&SPI5_Handler); //使能 SPI5
	SPI5_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,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);
	}
	//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); //关闭 SPI
	SPI5_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++;//扇区地址增 1
		secoff=0;//偏移位置为 0
		pBuffer+=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 短接就可以看到报错了。

  • 1
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yhwang-hub

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值