STM32学习心得:SPI

您所在的位置:网站首页 flsh怎么读 STM32学习心得:SPI

STM32学习心得:SPI

2023-03-16 15:39| 来源: 网络整理| 查看: 265

前言 目前正在学习STM32单片机的基础知识,通过库函数实现想要的一些功能。这篇文章主要介绍的是片外的Flash的操作,Flash的型号是W25Q16DV(芯片介绍在后面),通信的方式是SPI通信。通过ST提供的库函数,实现对SPI通信的简单封装。实现Flash的驱动程序,包括驱动的初始化、写使能、读操作、写操作、擦除操作等功能。在做比如FLASH、SPI等介绍时,都是只讲一点,这篇博文主要是代码的实现。相关知识建议自行搜索学习,我就不班门弄斧,我只做抛砖引玉。 SPI简介 SPI的控制模式

        首先要牢记,在使用SPI的时候,至少需要一个主机和一个从机。但是也可以一个主机多个从机,但是不可以多主机多从机或者多主机一从机。这里说的从机和主机,其实在实际应用中多表现为MCU是主机,其他的芯片,例如ADC转换芯片、FLASH芯片、EEPROM芯片为从机。

        接着需要知道的是,SPI在通信的时候,因为是同步通信,所以需要一个时钟信号,这个时钟信号是由主机提供的。

        然后需要知道从机其实在通信的时候是处于被动的模式,当主机需要通信时会通过CS(片选信号线)选择一个芯片(唯一),然后由主机产生时钟信号。

        最后需要知道SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。

SPI的传输模式

        SPI存在四种传输模式,这四种传输模式是由SPI的时钟极性和时钟相位组成的,简单的理解就是:

        时钟极性(CKP)是时钟信号线在空闲时处于什么电平。

        时钟相位(CKE)是一个时钟周期会有2个跳变沿,而相位直接决定SPI总线从那个跳变沿开始采集数据。

SPI的信号线

        在SPI通信的过程中,使用到了4根线,分别是CS、SCLK、MOSI、MISO。

        CS:片选信号线,在主机需要和那个从机进行通信时,会把相应从机的CS片选信号线发送高电平或者低电平。这个需要看具体的芯片而定,一般情况下是发送低电平选择从机。

        SCLK:时钟信号线,时钟信号线非常重要,在整个通信过程中都会由主机产生时钟信号。

        MOSI:主设备数据输出信号线,在通信时,主设备会通过这条信号线持续的发送信号,具体发送的信号内容需要根据芯片手册而定。

        MISO:主设备数据输入信号线,(假设我们在初始化SPI的时候选择的时全双工的工作模式)那么每一个时钟周期,主机在发送数据的同时,也会由这个输入信号线接收到一个BIT的数据。

SPI工作模式【参考博文:SPI几种工作模式简介】

        双线双向全双工模式

        双线单向只收模式

        单线双向半双工发送模式

        单线双向半双工接收模式

        这里SPI只是做了很基础的介绍,具体详细的内容,比如四种模式的波形、通信的过程等,还需要读者自行查阅。我觉的学习的过程就是你不断地自行找资料、一点点学习的过程。望海涵。

Flash简介

了解Flash之前可以先大致看看经常伴随Flash出现的另一种存储器EEPROM。

EEPROM的全称是“电可擦除可编程只读存储器,即Electrically Erasable Programmable Read-Only Memory。是相对于紫外擦除的ROM来讲的。在一开始,ROM是不能编程的,出厂什么内容就永远什么内容,不灵活。后来呢,出现了PROM,可以自己写入一次,但要是写错了,只能换一片。可是人类的科技是一直都在进步的,终于出现了可多次擦除写入的EPROM,每次擦除要把芯片拿到紫外线上照一下。可是依旧不灵活方便,想一下你往单片机上下了一个程序之后发现有个地方需要加一句话,为此你要把单片机放紫外灯下照半小时,然后才能再下一次,这么折腾一天也改不了几次。最后伟大的发明,EEPROM出现了,程序猿终于可以随意的修改ROM里面的内容了。

总结:EEPROM可以随机访问和修改任何一个字节,可以往每个bit中写入0或者1。掉电后数据不丢失,可以保存100年,可以擦写100w次。具有较高的可靠性,但是电路复杂/成本也高。因此目前的EEPROM都是几十千字节到几百千字节的,绝少有超过512K的。

大概知道了一下EEPROM的发展史,我们来看看今天的主角FLASH 

FLASH属于广义上的EEPROM,因为它也是可带电擦除的ROM。但是为了区别于一般的按字节为单位的擦写的EEPROM,我们都叫它FLASH。FLASH做的改进就是擦除时不再以字节为单位,而是以块为单位,一次简化了电路,数据密度更高,降低了成本。上M的ROM一般都是FLASH。FLASH分为NOR FLASH和NAND FLASH。NOR FLASH数据线和地址线分开,可以实现RAM一样的随机寻址功能,可以读取任何一个字节。但是擦除仍要按块来擦。NAND FLASH同样是按块擦除,但是数据线和地址线复用,不能利用地址线随机寻址。读取只能按页来读取。(NAND FLASH按块来擦除,按页来读,NOR FLASH没有页)

总结:

        在读写速度上,由于NAND FLASH引脚上复用,因此读取速度比NOR FLASH慢一点,但是擦除和写入速度比NOR FLASH快很多。

        在电路设计上,NAND FLASH内部电路更简单,因此数据密度大,体积小,成本也低。因此大容量的FLASH都是NAND型的。小容量的2~12M的FLASH多是NOR型的。

        在使用寿命上,NAND FLASH的擦除次数是NOR的数倍。而且NAND FLASH可以标记坏块,从而使软件跳过坏块。NOR FLASH 一旦损坏便无法再用。

        在实际应用上, 因为NOR FLASH可以进行字节寻址,所以程序可以在NOR FLASH中运行。嵌入式系统多用一个小容量的NOR FLASH存储引导代码,用一个大容量的NAND FLASH存放文件系统和内核。

参考博文:yuanlulu-EEPROM和flash的区别

W25Q16DV简介

        W25Q16DV是一个FLASH的芯片,其实我也是看芯片手册才知道,这个16代表的意思是16MBit。因为芯片手册篇幅很大,还是英文,在这里我只介绍很小一部分的东西,具体的内容,希望读者自行下载该芯片手册查阅。(插个题外话,大家可以下载一个叫半导小芯的APP,里面可以查到很多芯片,也可以下载相应的数据手册)。

        该芯片是一个16Mbit容量的芯片,换算就是16MBit / 8Bit = 2M。1M = 1024K。1K = 1024Byte。1Byte = 8Bit,所以他的容量就是2M * 1024 * 1024 = 2,097,152Byte,换算成16进制就是0X1FFFFF,所以在操作内存的时候,地址不能超过这个。

        该芯片由页、扇区、块组成。由芯片手册可以知道,它的一页是256个Byte,也就是在写入的时候,一次最多可以写入256个字节的数据,超过了需要自行在代码中处理。而扇区和块,目前主要我是在擦除的时候用到了,代码实现是按一个扇区擦除的。该芯片规定一个扇区是4K,经过换算一个扇区有16个页;一个块由64K,经过换算一共由32个块,而一个块有16个扇区。

        关于读写和擦除的介绍,首先是读(0X03),当主机发送一个读内存的命令之后,芯片就会根据读的起始地址,然后只要产生时钟信号,就会一直往下读,理论上如果产生足够多的时钟信号,一次可以把整个芯片所有的内容读完;接着是擦除(0X20),因为FLASH规定的特性,芯片存储的电平只能从高电平转成低电平,所以每次需要写的时候都需要对相应写的空间地址进行擦除,可以是按扇区或者按块进行擦除(目前实现的是按扇区进行擦除),擦除完之后才可以进行写入;然后是写(0X02),写就是在相应的地址中进行数据的写入,需要注意的是写之前一定对写入的扇区或者块进行擦除;最后需要注意的是,在写,无论是写数据进内存还是写状态寄存器,都需要开启写使能(0X06)。

        下面放两张数据手册截的图,可进行参考。(具体细节希望读者可以下载数据手册看)

466c9c978550427b857de8f60458e286.png

49c54dd269e444c68c5f47cb778e9b9d.png

程序代码 使用过程中用到的代码 //Std_Types.h #ifndef STD_TYPES_H #define STD_RTPES_H #define E_OK 0x00u #define E_NOT_OK 0x01u typedef uint8_t Std_ReturnType; #endif SPI相关代码 //SPI_Types.h #ifndef SPI_TYPES_H #define SPI_TYPES_H typedef SPI_TypeDef* Spi_ChannelType; typedef uint8_t Spi_DataBufferType; typedef uint16_t Spi_NumberOfDataType; typedef uint32_t SPI_TimeOutCount; #endif //SPI.h #ifndef SPI_H #define SPI_H #include "stm32f10x.h" #include "stm32f10x_spi.h" #include "SPI_Types.h" #include "Std_Types.h" #define FLASH_CS_CHOOSE_DISABLE() GPIO_SetBits(FLASH_SPI_GPIO_PORT,FLASH_SPI_CS_PIN) #define FLASH_CS_CHOOSE_ENABLE() GPIO_ResetBits(FLASH_SPI_GPIO_PORT,FLASH_SPI_CS_PIN) #define SPI_FLAG_TIMEOUT ((uint32_t)0x1000) #define SPI_FLAG_TXE SPI_I2S_FLAG_TXE //发送标志位 #define SPI_FLAG_RXNE SPI_I2S_FLAG_RXNE //接收标志位 #define FLASH_SPI SPI2 #define FLASH_SPI_CLK RCC_APB1Periph_SPI2 #define FLASH_SPI_GPIO_CLK RCC_APB2Periph_GPIOB #define FLASH_SPI_GPIO_PORT GPIOB #define FLASH_SPI_GPIO_SPEED GPIO_Speed_50MHz #define FLASH_SPI_GPIO_MODE GPIO_Mode_AF_PP #define FLASH_SPI_GPIO_CS_MODE GPIO_Mode_Out_PP #define FLASH_SPI_CS_PIN GPIO_Pin_12 #define FLASH_SPI_SCK_PIN GPIO_Pin_13 #define FLASH_SPI_MISO_PIN GPIO_Pin_14 #define FLASH_SPI_MOSI_PIN GPIO_Pin_15 /* SPI初始化 */ void spiInit(); /* 先发送再接收才会产生时序,一定要注意!! 否则STM32不会产生时序,产生一下后就停止,只接受到了地址的数据 */ uint8_t readWriteByte(uint8_t data); /* 发送和接收数据 */ Std_ReturnType spiSetupSimple(Spi_ChannelType Channel , const Spi_DataBufferType *SrcDataBufferPtr , Spi_DataBufferType *DesDataBufferPtr , Spi_NumberOfDataType Length); /* 设置波特率 */ void setSpiSpeed(uint8_t SPI_BaudRatePrescaler); #endif //SPI.c #include "SPI.h" void spiInit(){ //使能SPI2和外设连接的GPIO口的时钟 RCC_APB1PeriphClockCmd(FLASH_SPI_CLK , ENABLE); RCC_APB2PeriphClockCmd(FLASH_SPI_GPIO_CLK , ENABLE); //初始化GPIOB四个引脚,注意,CS初始化模式是推挽输出 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN | FLASH_SPI_MISO_PIN | FLASH_SPI_MOSI_PIN; GPIO_InitStructure.GPIO_Speed = FLASH_SPI_GPIO_SPEED; GPIO_InitStructure.GPIO_Mode = FLASH_SPI_GPIO_MODE; GPIO_Init(FLASH_SPI_GPIO_PORT , &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN ; GPIO_InitStructure.GPIO_Mode = FLASH_SPI_GPIO_CS_MODE; GPIO_Init(FLASH_SPI_GPIO_PORT , &GPIO_InitStructure); GPIO_SetBits(FLASH_SPI_GPIO_PORT , FLASH_SPI_SCK_PIN | FLASH_SPI_MISO_PIN | FLASH_SPI_MOSI_PIN); //cs不选 FLASH_CS_CHOOSE_DISABLE(); //初始化SPI2 SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//工作模式是(双向还是单向)全双工还是半双工 SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//SPI的模式,主机模式还是从机模式 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//SPI一次传输数据的大小,8为还是16位 //mode0(0,0) or mode3(1,1) be supported SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//时钟极性,也就是时钟信号在空闲状态的时候是高还是低 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//时钟相位,数据的采集实在第二个跳变沿 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//片选引脚是使用软件还是硬件控制 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//时钟波特率的设置。目前SPI的是时钟频率是36MHZ,做256预分频 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//MSB or LSB,传输的时候,是高位在前还是低位在前 SPI_InitStructure.SPI_CRCPolynomial = 0;//CRC校验 SPI_Init(FLASH_SPI , &SPI_InitStructure); //使能SPI2 SPI_Cmd(FLASH_SPI , ENABLE); //readWriteByte(0xff);//启动传输 } void setSpiSpeed(uint8_t SPI_BaudRatePrescaler){ assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler)); FLASH_SPI->CR1&=0XFFC7; FLASH_SPI->CR1|=SPI_BaudRatePrescaler; //设置SPI2速度 SPI_Cmd(FLASH_SPI,ENABLE); } Std_ReturnType spiSetupSimple(Spi_ChannelType Channel , const Spi_DataBufferType *SrcDataBufferPtr , Spi_DataBufferType *DesDataBufferPtr , Spi_NumberOfDataType Length){ SPI_TimeOutCount timeOut = 0; for(Spi_NumberOfDataType index = 0 ; index < Length ; ++index){ timeOut = SPI_FLAG_TIMEOUT; //首先检查是否发送缓冲是否为空 while(SPI_I2S_GetFlagStatus(Channel , SPI_FLAG_TXE ) == RESET){ if((--timeOut) == 0){ //发送超时处理,暂时不实现 return E_NOT_OK; } } //发送缓冲区为空,那么可以发送数据 SPI_I2S_SendData(Channel , *(SrcDataBufferPtr + index)); while(SPI_I2S_GetFlagStatus(Channel , SPI_FLAG_RXNE ) == RESET){ if((--timeOut) == 0){ //接收超时处理,暂时不实现 return E_NOT_OK; } } *(DesDataBufferPtr+index) = SPI_I2S_ReceiveData(Channel); } return E_OK; } W25Q16DV相关代码 //CDD_W25Q16_Types.h #ifndef CDD_W25Q16_TYPES_H #define CDD_W25Q16_TYPES_H typedef uint8_t Fls_DataType; typedef uint16_t Fls_DataType16; typedef uint8_t Fls_DataBufferType; typedef uint8_t Fls_NumberOfBufferType; typedef uint32_t Fls_DataAddr; #endif //CDD_W25Q16_Cfg.h #ifndef CDD_W25Q16_CFG_H #define CDD_W25Q16_CFG_H /* 配置参数 */ #define FLASH_BUSY_TIME_OUT 10000 /* 操作指令 */ #define INS_CODE_WRSR 0x01 //写状态寄存器 #define INS_CODE_WRFL 0x02 //写内存数据 #define INS_CODE_RDFL 0x03 //读内存数据 #define INS_CODE_WRDI 0x04 //写禁止 #define INS_CODE_RDSR1 0x05 //读状态寄存器1 #define INS_CODE_RDSR2 0X35 //读状态寄存器2 #define INS_CODE_WREN 0X06 //写使能 #define INS_CODE_ERASE_SECTOR_4 0x20 //按扇区擦除 #define INS_CODE_ERASE_BLOCK_32 0x52 //按块32KB擦除 #define INS_CODE_ERASE_BLOCK_64 0xD8 //按块64KB擦除 #define INS_CODE_DEID 0X9F //获取设备的ID #define INS_CODE_DEFA 0XFF //默认数据 #endif //CDD_W25Q16.h #ifndef CDD_W25Q16_H #define CDD_W25Q16_H #include "stm32f10x.h" #include "SPI.h" #include "CDD_W25Q16_Cfg.h" #include "CDD_W25Q16_Types.h" /* 8bit(位)=1Byte(字节) 1024Byte(字节)=1KB 1024KB=1MB */ /**************************************************************************************** * 类型定义 ***************************************************************************************/ typedef enum{ fFls_Idle = 0, /* 空闲状态 */ fFls_WRData = 1, /* 正在写数据 */ fFls_WRSR = 2, /* 正在写状态寄存器 */ fFls_RData = 3, /* 正在读数据 */ fFls_RDSR = 4, /* 正在读状态寄存器*/ }f_FlsOpState; /**************************************************************************************** * 参数定义 ***************************************************************************************/ /**************************************************************************************** * 函数声明 ***************************************************************************************/ extern void Fls_W25Q16_Init(); extern void Fls_W25Q16_Device_Id(); extern Std_ReturnType Fls_W25Q16_Write_Enable(); extern Std_ReturnType Fls_W25Q16_Write_Disable(); extern Std_ReturnType Fls_W25Q16_Read_SR1(Fls_DataType* pState); extern Std_ReturnType Fls_W25Q16_Read_SR2(Fls_DataType* pState); extern Std_ReturnType Fls_W25Q16_Write_SR(const Fls_DataType stateReg1 , const Fls_DataType stateReg2); extern Std_ReturnType Fls_W25Q16_Write(Fls_DataAddr addr, Fls_NumberOfBufferType len, Fls_DataBufferType *buf); extern Std_ReturnType Fls_W25Q16_Read(Fls_DataAddr addr, Fls_NumberOfBufferType len , Fls_DataBufferType *buf); extern Std_ReturnType Fls_W25Q16_Erase_Sector_4(Fls_DataAddr addr, Fls_NumberOfBufferType len); extern Std_ReturnType Fls_W25Q16_Erase_Block_32(Fls_DataAddr addr, Fls_NumberOfBufferType len); extern Std_ReturnType Fls_W25Q16_Erase_Block_64(Fls_DataAddr addr, Fls_NumberOfBufferType len); extern Std_ReturnType Fls_W25Q16_Protect(); extern Std_ReturnType Fls_W25Q16_DisProtect(); extern Std_ReturnType Fls_W25Q16_Wait_Busy(); extern Std_ReturnType Fls_W25Q16_Wait_Busy_Time_Out(); #endif //CDD_W25Q16.c #include "CDD_W25Q16.h" /* 一次只能读取一个扇区的数据 */ Spi_DataBufferType gFlashTxCmd[4100] = {0}; Spi_DataBufferType gFlashRxData[4100] = {0}; f_FlsOpState gFlsWrState = fFls_Idle;//FLASH的状态 /** * @brief W25Q16 Flash初始化 * @param None * @retval None */ void Fls_W25Q16_Init(){ setSpiSpeed(SPI_BaudRatePrescaler_2);//设置2分频 Fls_W25Q16_Write_SR(0 ,0); } /** * @brief W25Q16 Flash 获取芯片的ID * @param None * @retval None */ void Fls_W25Q16_Device_Id(){ Std_ReturnType result; //发送的命令 gFlashTxCmd[0] = INS_CODE_DEID; gFlashTxCmd[1] = INS_CODE_DEFA; gFlashTxCmd[2] = INS_CODE_DEFA; gFlashTxCmd[3] = INS_CODE_DEFA; FLASH_CS_CHOOSE_ENABLE(); /* 使能设备 */ result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 4); FLASH_CS_CHOOSE_DISABLE(); /* 失能设备 */ if(result == E_OK){ //成功处理 }else{ //失败处理 } } /** * @brief W25Q16 Flash 芯片写使能 * @param None * @retval None */ Std_ReturnType Fls_W25Q16_Write_Enable(){ Std_ReturnType result = E_OK; /* 地址和命令 */ gFlashTxCmd[0] = INS_CODE_WREN; FLASH_CS_CHOOSE_ENABLE(); result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 1); FLASH_CS_CHOOSE_DISABLE(); return result; } /** * @brief W25Q16 Flash 芯片写禁止 * @param None * @retval None */ Std_ReturnType Fls_W25Q16_Write_Disable(){ Std_ReturnType result = E_OK; /* 地址和命令 */ gFlashTxCmd[0] = INS_CODE_WRDI; FLASH_CS_CHOOSE_ENABLE(); result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 1); FLASH_CS_CHOOSE_DISABLE(); return result; } /** * @brief W25Q16 Flash 读取状态寄存器1的数据 * @param None * @retval None */ Std_ReturnType Fls_W25Q16_Read_SR1(Fls_DataType* pState){ Std_ReturnType result = E_OK; FLASH_CS_CHOOSE_ENABLE(); /* 地址和命令 */ gFlashTxCmd[0] = INS_CODE_RDSR1; gFlashTxCmd[1] = INS_CODE_DEFA; result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 2); if(!result){ *pState = gFlashRxData[1]; }else{ *pState = 0xFF; } FLASH_CS_CHOOSE_DISABLE(); return result; } /** * @brief W25Q16 Flash 读取状态寄存器2的数据 * @param None * @retval None */ Std_ReturnType Fls_W25Q16_Read_SR2(Fls_DataType* pState){ Std_ReturnType result = E_OK; FLASH_CS_CHOOSE_ENABLE(); /* 地址和命令 */ gFlashTxCmd[0] = INS_CODE_RDSR2; gFlashTxCmd[1] = INS_CODE_DEFA; result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 2); if(!result){ *pState = gFlashRxData[1]; }else{ *pState = 0xFF; } FLASH_CS_CHOOSE_DISABLE(); return result; } /** * @brief W25Q16 Flash 写状态寄存器1和2的数据 * @param stateReg1:状态寄存器1的写入值 * @param stateReg2:状态寄存器2的写入值 * @retval None */ Std_ReturnType Fls_W25Q16_Write_SR(const Fls_DataType stateReg1 , const Fls_DataType stateReg2){ Std_ReturnType result = E_OK; /* 地址和命令 */ gFlashTxCmd[0] = INS_CODE_WRSR; gFlashTxCmd[1] = stateReg1; gFlashTxCmd[2] = stateReg2; Fls_W25Q16_Write_Enable(); FLASH_CS_CHOOSE_ENABLE(); result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 3); FLASH_CS_CHOOSE_DISABLE(); return result; } /** * @brief 往W25Q16芯片内写数据,以页为单位,每一页的大小是256Byte * @param a ddr:Flash地址 * @param len:数据长度 * @param buf:数据地址 * @retval None */ Std_ReturnType Fls_W25Q16_Write(Fls_DataAddr addr, Fls_NumberOfBufferType len, Fls_DataBufferType *buf){ Std_ReturnType result = E_NOT_OK; Fls_DataAddr pageId = addr & 0x0000FF; /* 判断写入的地址和长度是否合法 , 每一页只能写入256字节 */ if((len == 0) || (len > 256) || addr > 0x1FFFFF || (addr + len) > 0x200000 || (pageId + len) > 256 || gFlsWrState != fFls_Idle){ return result ; } Fls_W25Q16_Write_Enable();//写使能 /* 检查寄存器是否处于忙的状态 */ if(Fls_W25Q16_Wait_Busy_Time_Out() != E_OK){ return E_NOT_OK; } /* 组装命令和需要写入的数据 */ gFlashTxCmd[0] = INS_CODE_WRFL; gFlashTxCmd[1] = (uint8_t)(addr>>16); gFlashTxCmd[2] = (uint8_t)(addr>>8); gFlashTxCmd[3] = (uint8_t)(addr); for( Fls_NumberOfBufferType index = 1 ; index 4096) || addr > 0x1FFFFF || (addr + len) > 0x200000 || gFlsWrState != fFls_Idle){ return result; } /* 地址和命令 */ gFlashTxCmd[0] = INS_CODE_RDFL; gFlashTxCmd[1] = (uint8_t)(addr>>16); gFlashTxCmd[2] = (uint8_t)(addr>>8); gFlashTxCmd[3] = (uint8_t)(addr); Fls_NumberOfBufferType index = 1; for(; index 4096 || addr > 0x1FFFFF || (addr + len) > 0x200000 || gFlsWrState != fFls_Idle){ return result; } /* 不能跨扇区操作 , 这个的解释是,一个扇区是4KB, 那么假设它的起始地址是0x000000,结束的地址就是0x000FFF。 当需要擦除的地址和0x000FFF与的时候,得出的地址都是一个扇区内的某个地址, 这个地址再加上操作的长度,理论上不能大于4KB(也就是4096) */ /*sectorId = addr & 0x000FFF; if((sectorId + len) > 4096){ return result; }*/ //对地址进行扇区起始地址设置 addr &= 0XFFF000; gFlsWrState = fFls_WRData; if(Fls_W25Q16_Wait_Busy_Time_Out() != E_OK){ //检查寄存器是否处于忙的状态 return E_NOT_OK; } Fls_W25Q16_Write_Enable();//写使能 FLASH_CS_CHOOSE_ENABLE();//选择CS gFlashTxCmd[0] = INS_CODE_ERASE_SECTOR_4; gFlashTxCmd[1] = (uint8_t)(addr>>16); gFlashTxCmd[2] = (uint8_t)(addr>>8); gFlashTxCmd[3] = (uint8_t)(addr); result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 4); FLASH_CS_CHOOSE_DISABLE(); gFlsWrState = fFls_Idle; //Fls_W25Q16_Wait_Busy(); if(Fls_W25Q16_Wait_Busy_Time_Out() != E_OK){ //检查寄存器是否处于忙的状态 return E_NOT_OK; } return E_OK; } /* Std_ReturnType Fls_W25Q16_Erase_Block_32(Fls_DataAddr addr, Fls_NumberOfBufferType len){ } Std_ReturnType Fls_W25Q16_Erase_Block_64(Fls_DataAddr addr, Fls_NumberOfBufferType len){ } */ /** * @brief 查看寄存器1的读写状态 * @param None * @retval None */ Std_ReturnType Fls_W25Q16_Wait_Busy(){ //首先获取到状态寄存器1的 Fls_DataType stateReg1 = 0; if(!Fls_W25Q16_Read_SR1(&stateReg1)){ while((stateReg1 & 0x01)); return E_OK; } return E_NOT_OK; } /** * @brief 查看寄存器1的读写状态,同时定时FLASH_BUSY_TIME_OUT * @param None * @retval None */ Std_ReturnType Fls_W25Q16_Wait_Busy_Time_Out(){ //首先获取到状态寄存器1的 Fls_DataType stateReg1 = 0; uint16_t timeOut = 0; if(!Fls_W25Q16_Read_SR1(&stateReg1)){ while((stateReg1 & 0x01) && timeOut != FLASH_BUSY_TIME_OUT){ ++timeOut; } return stateReg1 && timeOut; } return E_NOT_OK; } 总结 由于刚开始接触这个,所以很多东西介绍的不是很清楚,希望读者见谅。其次希望读者在查看本篇博文的时候,要去思考,多看芯片手册,这样才能有些许收获,而不是直接拿到代码编译,然后大致浏览就算过了。接着我也知道本片博文还有很多不完善的地方,无论是排版还是内容,希望大家可以留言一起讨论学习,我也会根据大家的建议继续发布自己的一些学习心得。在代码实现上,有些东西做的还不是很完善,比如写入超过一页的数据该怎么处理,我是直接就返回E_NOT_OK,其实可以做一下分页写入操作;亦或者在擦除的时候我只实现了按扇区擦除,大家也可以根据现有代码实现按块擦除等。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3