完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第三十章 SPI 实验 本章我们将向大家介绍 STM32F4 的 SPI 功能。在本章中,我们将使用 STM32F4 自带的 SPI 来实现对外部 FLASH(W25Q128)的读写,并将结果显示在 TFTLCD 模块上。本章分为如下 几个部分: 30.1 SPI 简介 30.2 硬件设计 30.3 软件设计 30.4 下载验证 30.1 SPI 简介 SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola 首先在其 MC68HCXX 系列处理器上定义的。SPI 接口主要应用在 EEPROM,FLASH,实时时 钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工, 同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局 上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信 协议,STM32F4 也有 SPI 接口。下面我们看看 SPI 的内部简明图(图 30.1.1): 图 30.1.1 SPI 内部结构简明图 SPI 接口一般使用 4 条线通信: MISO 主设备数据输入,从设备数据输出。 MOSI 主设备数据输出,从设备数据输入。 SCLK 时钟信号,由主设备产生。 CS 从设备片选信号,由主设备控制。 从图中可以看出,主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器 写入一个字节来发起一次传输。寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的 移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。 外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之, 若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。 SPI 主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可 编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。 SPI 总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串 行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果 CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同步时钟的空闲状态为高电 平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果 CPHA=0, 在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果 CPHA=1,在串行同步时钟 的第二个跳变沿(上升或下降)数据被采样。SPI 主模块和与之通信的外设备时钟相位和极性 应该一致。 不同时钟相位下的总线数据传输时序如图 30.1.2 所示: 图 30.1.2 不同时钟相位下的总线传输时序(CPHA=0/1) STM32F4 的 SPI 功能很强大,SPI 时钟最高可以到 37.5Mhz,支持 DMA,可以配置为 SPI 协议或者 I2S 协议(支持全双工 I2S)。 本章,我们将使用 STM32F4 的 SPI 来读取外部 SPI FLASH 芯片(W25Q128),实现类似 上节的功能。这里对 SPI 我们只简单介绍一下 SPI 的使用,STM32F4 的 SPI 详细介绍请参考 《STM32F4xx 中文参考手册》第 721 页,27 节。然后我们再介绍下 SPI FLASH 芯片。 这节,我们使用 STM32F4 的 SPI1 的主模式,下面就来看看 SPI1 部分的设置步骤吧。SPI 相关的库函数和定义分布在文件 stm32f4xx_spi.c 以及头文件 stm32f4xx_spi.h 中。STM32 的主 模式配置步骤如下: 1)配置相关引脚的复用功能,使能 SPI1 时钟。 我们要用 SPI1,第一步就要使能 SPI1 的时钟,SPI1 的时钟通过 APB2ENR 的第 12 位来设 置。其次要设置 SPI1 的相关引脚为复用(AF5)输出,这样才会连接到 SPI1 上。这里我们使用的 是 PB3、4、5 这 3 个(SCK.、MISO、MOSI,CS 使用软件管理方式),所以设置这三个为复用 IO,复用功能为 AF5。 使能 SPI1 时钟的方法为: __HAL_RCC_SPI1_CLK_ENABLE(); //使能 SPI1 时钟 复用 PB3,PB4,PB5 为 SPI1 引脚通过 HAL_GPIO_Init 函数实现,代码如下: GPIO_Initure.Pin=GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5; GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_FAST; //快速 GPIO_Initure.Alternate=GPIO_AF5_SPI1; //复用为 SPI1 HAL_GPIO_Init(GPIOB,&GPIO_Initure);2)初始化 SPI1,设置 SPI1 工作模式等。 这一步全部是通过 SPI1_CR1 来设置,我们设置 SPI1 为主机模式,设置数据格式为 8 位, 然后通过 CPOL 和 CPHA 位来设置 SCK 时钟极性及采样方式。并设置 SPI1 的时钟频率(最大 37.5Mhz),以及数据的格式(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 初 始化实例代码如下: 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); //使能 SPI14)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,ALIENTEK 探索者 STM32F407 开发板教程 378 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 的使用就介绍到这里,接下来介绍一下 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。 30.2 硬件设计 本章实验功能简介:开机的时候先检测 W25Q128 是否存在,然后在主循环里面检测两个 按键,其中 1 个按键(KEY1)用来执行写入 W25Q128 的操作,另外一个按键(KEY0)用来 执行读出操作,在 TFTLCD 模块上显示相关信息。同时用 DS0 提示程序正在运行。 所要用到的硬件资源如下: 1) 指示灯 DS0 2) KEY_UP 和 KEY1 按键 3) TFTLCD 模块 4) SPI 5) W25Q128 这里只介绍 W25Q128 与 STM32F4 的连接,板上的 W25Q128 是直接连在 STM32F4 的 SPI1 上的,连接关系如图 30.2.1 所示: 图 30.2.1 STM32F4 与 W25Q128 连接电路图 这里,我们的 F_CS 是连接在 PB14 上面的,另外要特别注意:W25Q128 和 NRF24L01 共 用 SPI1,所以这两个器件在使用的时候,必须分时复用(通过片选控制)才行。 30.3 软件设计 打开我们光盘的 SPI 实验工程,可以看到我们加入了 spi.c,flash.c 文件以及头文件 spi.h 和 flash.h,同时引入了库函数文件 stm32f4xx_hal_spi.c 文件以及头文件 stm32f4xx_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 位控制 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_GPIOB_CLK_ENABLE(); //使能 GPIOB 时钟 __HAL_RCC_SPI1_CLK_ENABLE(); //使能 SPI1 时钟 //PB3,4,5 GPIO_Initure.Pin=GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5; GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_FAST; //快速 GPIO_Initure.Alternate=GPIO_AF5_SPI1; //复用为 SPI1 HAL_GPIO_Init(GPIOB,&GPIO_Initure); } //SPI 速度设置函数 //SPI 速度=fAPB1/分频系数 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 的频率设置成了最低(84Mhz,256 分频)。在外部函数里 面,我们通过 SPI1_SetSpeed 来设置 SPI1 的速度,而我们的数据发送和接收则是通过 SPI1_ReadWriteByte 函数来实现的。 接下来我们来看看 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; //使能器件 SPI1_ReadWriteByte(W25X_ReadData); //发送读取命令 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; }由于 W25Q128 支持以任意地址(但是不能超过 W25Q128 的地址范围)开始读取数据,所 以,这个代码相对来说就比较简单了,在发送 24 位地址之后,程序就可以开始循环读数据了, 其地址会自动增加的,不过要注意,不能读的数据超过了 W25Q128 的地址范围哦!否则读出 来的数据,就不是你想要的数据了。 有读的函数,当然就有写的函数了,接下来,我们介绍 W25QXX_Write 这个函数,该函数 的作用与W25QXX_Flash_Read的作用类似,不过是用来写数据到W25Q128里面的,代码如下: //写 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; //下一个扇区可以写完了 } }; }该函数可以在 W25Q128 的任意地址开始写入任意长度(必须不超过 W25Q128 的容量)的 数据。我们这里简单介绍一下思路:先获得首地址(WriteAddr)所在的扇区,并计算在扇区内 的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是 否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定 长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长 度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此 循环,直到写入结束。这里我们还定义了一个 W25QXX_BUFFER 的全局变量,用于擦除时缓 存扇区内的数据。 其他的代码就比较简单了,我们这里不介绍了。对于头文件 w25qxx.h,这里面就定义了一 些与 W25Q128 操作相关的命令和函数(部分省略了),这些命令在 W25Q128 的数据手册上都 有详细的介绍,感兴趣的读者可以参考该数据手册。 最后,我们看看 main 函数,代码如下: //要写入到 W25Q128 的字符串数组 const u8 TEXT_Buffer[]={"Explorer STM32F4 SPI TEST"}; #define SIZE sizeof(TEXT_Buffer) int main(void) { u8 key, datatemp[SIZE]; u16 i=0; u32 FLASH_SIZE; HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhz delay_init(168); //初始化延时函数 uart_init(115200); //初始化 USART usmart_dev.init(84); //初始化 USMART LED_Init(); //初始化 LED KEY_Init(); //初始化 KEY LCD_Init(); //初始化 LCD W25QXX_Init(); //W25QXX 初始化 POINT_COLOR=RED; LCD_ShowString(30,50,200,16,16,"Explorer STM32F4"); 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,"2014/5/7"); LCD_ShowString(30,130,200,16,16,"KEY1:Write KEY0:Read"); //显示提示信息 while(W25QXX_ReadID()!=W25Q128) //检测不到 W25Q128 { LCD_ShowString(30,150,200,16,16,"W25Q128 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,"W25Q128 Ready!"); FLASH_SIZE=128*1024*1024; //FLASH 大小为 2M 字节 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 W25Q128...."); W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE); //从倒数第 100 个地址处开始,写入 SIZE 长度的数据 LCD_ShowString(30,170,200,16,16,"W25Q128 Write Finished!");//提示完成 } if(key==KEY0_PRES)//KEY0 按下,读取字符串并显示 { LCD_ShowString(30,170,200,16,16,"Start Read W25Q128.... "); 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。 30.4 下载验证 在代码编译成功之后,我们通过下载代码到 ALIENTEK 探索者 STM32F4 开发板上,通过 先按 KEY1 按键写入数据,然后按 KEY0 读取数据,得到如图 30.4.1 所示: 图 30.4.1 SPI 实验程序运行效果图 伴随 DS0 的不停闪烁,提示程序在运行。程序在开机的时候会检测 W25Q128 是否存在, 如果不存在则会在 TFTLCD 模块上显示错误信息,同时 DS0 慢闪。大家可以通过跳线帽把 PB4 和 PB5 短接就可以看到报错了。 |
|
相关推荐
|
|
使用stm32l451片子,对ad7606进行3通道100k采样值跳动问题?
792 浏览 1 评论
STM32进行DMA数据转运,为什么只有A组最后一位数据被转运到B组去?
1415 浏览 2 评论
1733 浏览 3 评论
1336 浏览 0 评论
嵌入式开发 Win11安装ST-Link 提示 设备描述符请求失败 怎么处理?
1463 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-8-21 19:58 , Processed in 0.744902 second(s), Total 63, Slave 46 queries .
Powered by 电子发烧友网
© 2015 www.ws-dc.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号