完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:正点原子STM32mini开发板
2)摘自《正点原子STM32 不完全手册(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第二十五章 SPI 实验 本章我们将向大家介绍 STM32 的 SPI 功能。在本章中,我们将使用 STM32 自带的 SPI 来 实现对外部 FLASH(W25Q64)的读写,并将结果显示在 TFTLCD 模块上。本章分为如下几个 部分: 25.1 SPI 简介 25.2 硬件设计 25.3 软件设计 25.4 下载验证 25.1 SPI 简介 SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola 首先在其 MC68HCXX 系列处理器上定义的。SPI 接口主要应用在 EEPROM,FLASH,实时时 钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工, 同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局 上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信 协议,STM32 也有 SPI 接口。 SPI 接口一般使用 4 条线通信: MISO 主设备数据输入,从设备数据输出。 MOSI 主设备数据输出,从设备数据输入。 SCLK 时钟信号,由主设备产生。 CS 从设备片选信号,由主设备控制。 SPI 主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可 编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。 SPI 总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串 行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果 CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同步时钟的空闲状态为高电 平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果 CPHA=0, 在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果 CPHA=1,在串行同步时钟 的第二个跳变沿(上升或下降)数据被采样。SPI 主模块和与之通信的外设备时钟相位和极性 应该一致。 不同时钟相位下的总线数据传输时序如图 25.1.1 所示: 图 25.1.1 不同时钟相位下的总线传输时序(CPHA=0/1) STM32 的 SPI 功能很强大,SPI 时钟最多可以到 18Mhz,支持 DMA,可以配置为 SPI 协 议或者 I2S 协议(仅大容量型号支持)。 本章,我们将使用 STM32 的 SPI 来读取外部 SPI FLASH 芯片(W25Q64),实现类似上节 的功能。这里对 SPI 我们只简单介绍一下 SPI 的使用,STM32 的 SPI 详细介绍请参考《STM32 参考手册》第 457 页,23 节。然后我们再介绍下 SPI FLASH 芯片。 这节,我们使用 STM32 的 SPI1 的主模式,下面就来看看 SPI1 部分的设置步骤吧。SPI 相 关的库函数和定义分布在文件 stm32f1xx_spi.c 以及头文件 stm32f1xx_spi.h 中。STM32 的主模 式配置步骤如下: 1)配置相关引脚的复用功能,使能 SPI1 时钟。 我们要用 SPI1,第一步就要使能 SPI1 的时钟,SPI1 的时钟通过 APB1ENR 的第 14 位来设 置。其次要设置 SPI1 的相关引脚为复用输出,这样才会连接到 SPI1 上否则这些 IO 口还是默认 的状态,也就是标准输入输出口。这里我们使用的是 PA5,6,7 这 3 个(SCK.、MISO、MOSI, CS 使用软件管理方式),所以设置这三个为复用功能 IO。 使能 SPI1 时钟的方法为: __HAL_RCC_SPI1_CLK_ENABLE(); //使能 SPI1 时钟 复用 PA5,6,7 为 SPI1 引脚通过 HAL_GPIO_Init 函数实现,代码如下: GPIO_Initure.Pin=GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7; GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //快速 HAL_GPIO_Init(GPIOA,&GPIO_Initure); 2)设置 SPI1 工作模式。 这一步全部是通过 SPI1_CR1 来设置,我们设置 SPI1 为主机模式,设置数据格式为 8 位, 然后通过 CPOL 和 CPHA 位来设置 SCK 时钟极性及采样方式。并设置 SPI1 的时钟频率(最大 18Mhz),以及数据的格式(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; /发送数据大小 __IO uint16_t TxXferCount; //还剩余多少个数据要发送 uint8_t *pRxBuffPtr; //接收缓存 uint16_t RxXferSize; //接收数据大小 __IO uint16_t RxXferCount; //还剩余多少个数据要接收 void (*RxISR)(struct __SPI_HandleTypeDef *hspi); void (*TxISR)(struct __SPI_HandleTypeDef *hspi); DMA_HandleTypeDef *hdmatx; //DMA 发送句柄 DMA_HandleTypeDef *hdmarx; //DMA 接收句柄 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 初 始化实例代码如下: SPI1_Handler.Instance= SPI1; // SPI1 SPI1_Handler.Init.Mode=SPI_MODE_MASTER; //设置 SPI 工作模式,设置为主模式 SPI1_Handler.Init.Direction=SPI_DIRECTION_2LINES; //设置 SPI 单向或者双向的数据模式:SPI 设置为双线模式 SPI1_Handler.Init.DataSize=SPI_DATASIZE_8BIT; //设置 SPI 的数据大小:SPI 发送接收 8 位帧结构 SPI1_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //串行同步时钟的空闲状态为高电平 SPI1_Handler.Init.CLKPhase=SPI_PHASE_2EDGE; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样 SPI1_Handler.Init.NSS=SPI_NSS_SOFT; //NSS 信号由硬件(NSS 管脚)还是软件 //(使用 SSI 位)管理:内部 NSS 信号有 SSI 位控制 SPI1_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256; //定义波特率预分频的值:波特率预分频值为 256 SPI1_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //指定数据传输从 MSB 位还是 LSB 位开始:数据传输从 MSB 位开始 SPI1_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭 TI 模式 SPI1_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE; //关闭硬件 CRC 校验 SPI1_Handler.Init.CRCPolynomial=7; //CRC 值计算的多项式 HAL_SPI_Init(&SPI1_Handler);//初始化 同样,HAL 库也提供了 SPI 初始化 MSP 回调函数 HAL_SPI_MspInit,定义如下: void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi); 关于回调函数使用,这里我们就不做过多讲解。 3)使能 SPI1。 这一步通过 SPI1_CR1 的 bit6 来设置,以启动 SPI1,在启动之后,我们就可以开始 SPI 通 讯了。库函数使能 SPI1 的方法为: __HAL_SPI_ENABLE(&SPI1_Handler); //使能 SPI1 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 寄 存器来修改,具体实现方法请参考后面软件设计小节相关函数。 SPI1 的使用就介绍到这里,接下来介绍一下 W25Q64。W25Q64 是华邦公司推出的大容量 SPI FLASH 产品,W25Q64 的容量为 64Mb,该系列还有 W25Q80/16/32 等。MiniSTM32 V3.0 开发板所选择的 W25Q64 容量为 64Mb,也就是 8M 字节。 W25Q64 将 8M 的容量分为 128 个块(Block),每个块大小为 64K 字节,每个块又分为 16 个扇区(Sector),每个扇区 4K 个字节。W25Q64 的最少擦除单位为一个扇区,也就是每次必 须擦除 4K 个字节。这样我们需要给 W25Q64 开辟一个至少 4K 的缓存区,这样对 SRAM 要求 比较高,要求芯片必须有 4K 以上 SRAM 才能很好的操作。 W25Q64 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V, W25Q64 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 80Mhz(双输出 时相当于 160Mhz,四输出时相当于 320M),更多的 W25Q64 的介绍,请参考 W25Q64 的 DATASHEET。 25.2 硬件设计 本章实验功能简介:开机的时候先检测 W25Q64 是否存在,然后在主循环里面检测两个按 键,其中 1 个按键(WK_UP)用来执行写入 W25Q64 的操作,另外一个按键(KEY0)用来执 行读出操作,在 TFTLCD 模块上显示相关信息。同时用 DS0 提示程序正在运行。 所要用到的硬件资源如下: 1) 指示灯 DS0 2) WK_UP 和 KEY0 按键 3) TFTLCD 模块 4) SPI 5) W25Q64 这里只介绍 W25Q64 与 STM32 的连接,板上的 W25Q64 是直接连在 STM32 的 SPI1 上的, 连接关系如图 25.2.1 所示: 图 25.2.1 STM32 与 W25Q64 连接电路图 注意到图中,还有 NRF_CS/SD_CS 等片选信号,他们和 W25Q64 一样,都是使用的 SPI1, 也就是说这三个器件,共用一个 SPI,所以在使用的时候,必须分时复用(通过片选控制)。 25.3 软件设计 打开我们光盘的 SPI 实验工程,可以看到我们加入了 spi.c, w25qxx.c 文件以及头文件 spi.h 和 w25qxx.h,同时引入了库函数文件 stm32f1xx_hal_spi.c 文件以及头文件 stm32f1xx_hal_spi.h。 打开 spi.c 文件,看到如下代码: SPI_HandleTypeDef SPI1_Handler; //SPI1 句柄 //以下是 SPI 模块的初始化代码,配置成主机模式 //SPI 口初始化 //这里针是对 SPI1 的初始化 void SPI1_Init(void) { SPI1_Handler.Instance=SPI1; //SPI1 SPI1_Handler.Init.Mode=SPI_MODE_MASTER; //设置 SPI 工作模式,设置为主模式 SPI1_Handler.Init.Direction=SPI_DIRECTION_2LINES; //设置 SPI 单向或者双向的数据模式:SPI 设置为双线模式 SPI1_Handler.Init.DataSize=SPI_DATASIZE_8BIT; //设置 SPI 的数据大小:SPI 发送接收 8 位帧结构 SPI1_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //串行同步时钟的空闲状态为高电平 SPI1_Handler.Init.CLKPhase=SPI_PHASE_2EDGE; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样 SPI1_Handler.Init.NSS=SPI_NSS_SOFT; //NSS 信号由硬件(NSS 管脚)还是软件(使 //用 SSI 位)管理:内部 NSS 信号有 SSI 位控制 SPI1_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256; //定义波特率预分频的值:波特率预分频值为 256 SPI1_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //指定数据传输从 MSB 位还是 LSB 位开始:数据传输从 MSB 位开始 SPI1_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭 TI 模式 SPI1_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE; //关闭硬件 CRC 校验 SPI1_Handler.Init.CRCPolynomial=7; //CRC 值计算的多项式 HAL_SPI_Init(&SPI1_Handler); //初始化 __HAL_SPI_ENABLE(&SPI1_Handler); //使能 SPI1 SPI1_ReadWriteByte(0Xff); //启动传输 } //SPI5 底层驱动,时钟使能,引脚配置 //此函数会被 HAL_SPI_Init()调用 //hspi:SPI 句柄 void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi) { GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_GPIOA_CLK_ENABLE(); //使能 GPIOA 时钟 __HAL_RCC_SPI1_CLK_ENABLE(); //使能 SPI1 时钟 //PA5,6,7 GPIO_Initure.Pin=GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7; GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //快速 HAL_GPIO_Init(GPIOA,&GPIO_Initure); } //SPI 速度设置函数 //SPI 速度=fAPB1/分频系数 //fAPB1 时钟一般为 42Mhz: void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler) { assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性 __HAL_SPI_DISABLE(&SPI1_Handler); //关闭 SPI SPI1_Handler.Instance->CR1&=0XFFC7; //位 3-5 清零,用来设置波特率 SPI1_Handler.Instance->CR1|=SPI_BaudRatePrescaler;//设置 SPI 速度 __HAL_SPI_ENABLE(&SPI1_Handler); //使能 SPI } //SPI1 读写一个字节 //TxData:要写入的字节 //返回值:读取到的字节 u8 SPI1_ReadWriteByte(u8 TxData) { u8 Rxdata; HAL_SPI_TransmitReceive(&SPI1_Handler,&TxData,&Rxdata,1, 1000); return Rxdata; //返回收到的数据 } 此部分代码主要初始化 SPI,这里我们选择的是 SPI1,所以在 SPI1_Init 函数里面,其相关 的操作都是针对 SPI1 的,其初始化步骤和我们上面介绍的一样。在初始化之后,我们就可以开 始使用 SPI1 了,这里特别注意,SPI 初始化函数的最后有一个启动传输,这句话最大的作用就 是维持 MOSI 为高电平,而且这句话也不是必须的,可以去掉。 在 SPI1_Init 函数里面,把 SPI1 的频率设置成了最低(72Mhz,256 分频)。在外部函数里 面,我们通过 SPI1_SetSpeed 来设置 SPI1 的速度,而我们的数据发送和接收则是通过 SPI1_ReadWriteByte 函数来实现的。 接下来我们来看看 w25qxx.c 文件内容。由于篇幅所限,详细代码,这里就不贴出了。我们 仅介绍几个重要的函数,首先是 W25QXX_Read 函数,该函数用于从 W25Q64 的指定地址读出 指定长度的数据。其代码如下: //读取 SPI FLASH //在指定地址开始读取指定长度的数据 //pBuffer:数据存储区 //ReadAddr:开始读取的地址(24bit) //NumByteToRead:要读取的字节数(最大 65535) void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead) { u16 i; W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_ReadData); //发送读取命令 if(W25QXX_TYPE==W25Q256) //如果是 W25Q256 的话地址为 4 字节,发送最高 8 位 { SPI1_ReadWriteByte((u8)((ReadAddr)>>24)); } SPI1_ReadWriteByte((u8)((ReadAddr)>>16)); //发送 24bit 地址 SPI1_ReadWriteByte((u8)((ReadAddr)>>8)); SPI1_ReadWriteByte((u8)ReadAddr); for(i=0;i pBuffer=SPI1_ReadWriteByte(0XFF); //循环读数 } W25QXX_CS=1; } 由于 W25Q64 支持以任意地址(但是不能超过 W25Q64 的地址范围)开始读取数据,所以, 这个代码相对来说就比较简单了,在发送 24 位地址之后,程序就可以开始循环读数据了,其地 址会自动增加的,不过要注意,不能读的数据超过了 W25Q64 的地址范围哦!否则读出来的数 据,就不是你想要的数据了。 有读的函数,当然就有写的函数了,接下来,我们介绍 W25QXX_Write 这个函数,该函数 的作用与 W25QXX_Flash_Read 的作用类似,不过是用来写数据到 W2564 里面的,代码如下: //写 SPI FLASH //在指定地址开始写入指定长度的数据 //该函数带擦除操作! //pBuffer:数据存储区 WriteAddr:开始写入的地址(24bit) //NumByteToWrite:要写入的字节数(最大 65535) u8 W25QXX_BUFFER[4096]; void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) { u32 secpos; u16 secoff; u16 secremain; u16 i; u8 * W25QXX_BUF; W25QXX_BUF=W25QXX_BUFFER; secpos=WriteAddr/4096;//扇区地址 secoff=WriteAddr%4096;//在扇区内的偏移 secremain=4096-secoff;//扇区剩余空间大小 //printf("ad:%X,nb:%Xrn",WriteAddr,NumByteToWrite);//测试用 if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于 4096 个字节 while(1) { W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容 for(i=0;i if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除 } if(i W25QXX_Erase_Sector(secpos);//擦除这个扇区 for(i=0;i { W25QXX_BUF[i+secoff]=pBuffer; } 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; //下一个扇区可以写完了 } }; } 该函数可以在 W25Q64 的任意地址开始写入任意长度(必须不超过 W25Q64 的容量)的数 据。我们这里简单介绍一下思路:先获得首地址(WriteAddr)所在的扇区,并计算在扇区内的 偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是否 要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定长 度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度 的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循 环,直到写入结束。这里我们还定义了一个 W25QXX_BUFFER 的全局变量,用于擦除时缓存 扇区内的数据。 其他的代码就比较简单了,我们这里不介绍了。对于头文件 w25qxx.h,这里面就定义了一 些与 W25Q64 操作相关的命令和函数(部分省略了),这些命令在 W25Q64 的数据手册上都有 详细的介绍,感兴趣的读者可以参考该数据手册。 最后,我们看看 main 函数,代码如下: //要写入到 W25Q64 的字符串数组 const u8 TEXT_Buffer[]={"MiniSTM32 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(RCC_PLL_MUL9); //设置时钟,72M delay_init(72); //初始化延时函数 uart_init(115200); //初始化串口 usmart_dev.init(84); //初始化 USMART LED_Init(); //初始化 LED KEY_Init(); //初始化按键 LCD_Init(); //初始化 LCD FSMC 接口 W25QXX_Init(); //W25QXX 初始化 POINT_COLOR=RED; LCD_ShowString(30,50,200,16,16,"Mini STM32"); LCD_ShowString(30,70,200,16,16,"SPI TEST"); LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(30,110,200,16,16,"2019/11/15"); LCD_ShowString(30,130,200,16,16,"KEY1:Write KEY0:Read");//显示提示信息 while(W25QXX_ReadID()!=W25Q64) //检测不到 W25Q256 { LCD_ShowString(30,150,200,16,16,"W25Q64 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,"W25Q64 Ready!"); FLASH_SIZE=32*1024*1024; //FLASH 大小为 32M 字节 POINT_COLOR=BLUE; //设置字体为蓝色 while(1) { key=KEY_Scan(0); if(key==KEY1_PRES)//KEY1 按下,写入 W25Q64 { LCD_Fill(0,170,239,319,WHITE);//清除半屏 LCD_ShowString(30,170,200,16,16,"Start Write W25Q64...."); W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE); //从倒数第 100 个地址处开始,写入 SIZE 长度的数据 LCD_ShowString(30,170,200,16,16,"W25Q64 Write Finished!");//提示传送完成 } if(key==KEY0_PRES)//KEY0 按下,读取字符串并显示 { LCD_ShowString(30,170,200,16,16,"Start Read W25Q64.... "); 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; } } } 这部分代码和 IIC 实验那部分代码大同小异,我们就不多说了,实现的功能就和 IIC 差不 多,不过此次写入和读出的是 SPI FLASH,而不是 EEPROM。 最后,我们将 W25QXX_ReadID 和 W25QXX_Erase_Chip 两个函数加入 usmart 控制,这样 我们便可以通过 usmart 调用 W25QXX_ReadID 函数,来读取 SPI FLASH 的 ID,也可以调用 W25QXX_Erase_Chip 函数,实现对整个 SPI FLASH 的擦除(一般情况下不建议擦除整个 SPI FLASH,因为这将导致字库和综合例程所需的系统文件全部丢失)。 25.4 下载验证 在代码编译成功之后,我们通过下载代码到 ALIENTEK MiniSTM32 开发板上,通过先按 WK_UP 按键写入数据,然后按 KEY0 读取数据,得到如图 25.4.1 所示: 图 25.4.1 SPI 实验程序运行效果图 伴随 DS0 的不停闪烁,提示程序在运行。程序在开机的时候会检测 W25Q64 是否存在,如 果不存在则会在 TFTLCD 模块上显示错误信息,同时 DS0 慢闪。大家可以通过跳线帽把 PA5 和 PA6 短接就可以看到报错了。 |
|
相关推荐
|
|
1330 浏览 1 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
1263 浏览 3 评论
2430 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
1416 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
1847 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-30 20:13 , Processed in 0.521116 second(s), Total 65, Slave 47 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号