## SPI 战舰 F103ZET6 对外部 FLASH(W25Q128)的读写 PB13、14、15 这 3 个(SCK.、MISO、MOSI,CS 使用软件管理方式) SPI2 SPI2 为主机模式,设置数据格式为 8 位,然后通过 CPOL 和 CPHA 位来设置 SCK 时钟极性及采样方式。 并设置 SPI2 的时钟频率(最大18Mhz),以及数据的格式(MSB 在前还是 LSB 在前) SPI1_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //串行同步时钟的空闲状态为高电平 SPI1_Handler.Init.CLKPhase=SPI_PHASE_2EDGE; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样 PB12 F_CS N W25Q128 的片选信号 ***注意地址 16bit 24bit 32bit*** W25Q256的话地址为4字节 ***写注意事项*** 可以调用不检查写 如果不确认是否写过,需要读出来判断 续预先封装以上函数 ### 配置 SPI2 为主机模式, 设置数据格式为 8 位 CPOL 串行同步时钟的空闲状态为高电平 CPHA 第二个跳变沿(上升或下降)数据被采样 并设置 SPI2 的时钟频率(最大18Mhz),以及数据的格式(MSB 在前还是 LSB 在前) 左键点击PB12 选择为GPIO输出, 设置PB12 输出高 推挽。 如果选 SPI2_NSS 则为硬件控制了 在左侧引脚配置处勾选SPI进行配置 在SPI模式处勾选“Full-Duplex”选择全双工模式 在SPI参数配置处进行参数配置:SPI时钟、SPI数据参数以及勾选”NVIC Settings”使能中断 ### 库函数 库函数 参考 stm32f1xx_hal_spi.h 包含 polling IT DMA 三种模式,以及事件处理 ### 结构体如何写入 EEPROM ### W25Q W25Q128存储大小为128M-bit=16MB,可编程位(地址)为Flash_Size=16*1024*1024=16777216 B。 W25Q128包含256个块、每个块(64KB)16个扇区(4096个扇区)、每个扇区(4KB)有16页、每一页有256个字节(Byte)。 写数据:一次最多写一页不能跨页写入;擦除:可以选择擦除一个扇区(4KB)、擦除半个块(32KB)、擦除一个块(64KB)、擦除整个芯片。 Flash 有一个特点,就是可以将 1 写成 0,但是不能将 0 写成 1,要想将 0 写成 1,必须进行擦除操作。如果要改变数据,就需要先擦除后写数据。 可以理解为将W25Q128看成一本电子书,这本书有256个章节,每个章节有16个小节,每个小节有16页,每页有256个字。 读取数据:可以从指定位置开始一直读完这本书。 写入数据:一次最多只能写一页256个字,不能翻页写,需要等待上一页写完才能翻页。写入数据前需要保证写入的位置是擦除状态,才能正确写入数据,因为只能1写成0,不能0写成1。 擦除操作:有几种选择,可以每个小节、半个章节、整个章节、一本书进行擦除 ### 代码 对AT24CXX 不同型号的封装 ```C #ifndef __W25QXX_H__ #define __W25QXX_H__ #include "stm32f1xx_hal.h" //25系列FLASH芯片厂商与容量代号(厂商代号EF) #define W25Q80 0XEF13 #define W25Q16 0XEF14 #define W25Q32 0XEF15 #define W25Q64 0XEF16 #define W25Q128 0XEF17 #define W25Q256 0XEF18 #define EX_FLASH_ADD 0x000000 //W25Q128的地址是24位宽 extern uint16_t W25QXX_TYPE;//定义W25QXX芯片型号 extern SPI_HandleTypeDef hspi2; // //指令表 #define W25X_WriteEnable 0x06 #define W25X_WriteDisable 0x04 #define W25X_ReadStatusReg1 0x05 #define W25X_ReadStatusReg2 0x35 #define W25X_ReadStatusReg3 0x15 #define W25X_WriteStatusReg1 0x01 #define W25X_WriteStatusReg2 0x31 #define W25X_WriteStatusReg3 0x11 #define W25X_ReadData 0x03 #define W25X_FastReadData 0x0B #define W25X_FastReadDual 0x3B #define W25X_PageProgram 0x02 #define W25X_BlockErase 0xD8 #define W25X_SectorErase 0x20 #define W25X_ChipErase 0xC7 #define W25X_PowerDown 0xB9 #define W25X_ReleasePowerDown 0xAB #define W25X_DeviceID 0xAB #define W25X_ManufactDeviceID 0x90 #define W25X_JedecDeviceID 0x9F #define W25X_Enable4ByteAddr 0xB7 #define W25X_Exit4ByteAddr 0xE9 uint8_t SPI2_ReadWriteByte(uint8_t TxData);//SPI2总线底层读写 void W25QXX_CS(uint8_t a);//W25QXX片选引脚控制 uint8_t W25QXX_Init(void);//初始化W25QXX函数 uint16_t W25QXX_ReadID(void);//读取FLASH ID uint8_t W25QXX_ReadSR(uint8_t regno);//读取状态寄存器 void W25QXX_4ByteAddr_Enable(void);//使能4字节地址模式 void W25QXX_Write_SR(uint8_t regno,uint8_t sr);//写状态寄存器 void W25QXX_Write_Enable(void);//写使能 void W25QXX_Write_Disable(void);//写保护 void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//无检验写SPI FLASH void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead);//读取flash void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//写入flash void W25QXX_Erase_Chip(void);//整片擦除 void W25QXX_Erase_Sector(uint32_t Dst_Addr);//扇区擦除 void W25QXX_Wait_Busy(void);//等待空闲 void W25QXX_PowerDown(void);//进入掉电模式 void W25QXX_WAKEUP(void);//唤醒 #endif ``` ```C #include "W25Qxx.h" #include "main.h" uint16_t W25QXX_TYPE=W25Q128;//默认是W25Q128 //4Kbytes为一个Sector //16个扇区为1个Block //W25Q128 //容量为16M字节,共有128个Block,4096个Sector //SPI2总线读写一个字节 //参数是写入的字节,返回值是读出的字节 #define W25Q128_CS_GPIO_Port W25Q_SPI2_NSS_GPIO_Port #define W25Q128_CS_Pin W25Q_SPI2_NSS_Pin //定义片选 uint8_t SPI2_ReadWriteByte(uint8_t TxData) { uint8_t Rxdata;//定义一个变量Rxdata HAL_SPI_TransmitReceive(&hspi2,&TxData,&Rxdata,1,1000);//调用固件库函数收发数据 return Rxdata;//返回收到的数据 } void W25QXX_CS(uint8_t a)//软件控制函数(0为低电平,其他值为高电平) { if(a==0)HAL_GPIO_WritePin(W25Q128_CS_GPIO_Port, W25Q128_CS_Pin, GPIO_PIN_RESET); else HAL_GPIO_WritePin(W25Q128_CS_GPIO_Port, W25Q128_CS_Pin, GPIO_PIN_SET); } //初始化SPI FLASH的IO口 uint8_t W25QXX_Init(void) { uint8_t temp;//定义一个变量temp W25QXX_CS(1);//0片选开启,1片选关闭 W25QXX_TYPE = W25QXX_ReadID();//读取FLASH ID. if(W25QXX_TYPE == W25Q256)//SPI FLASH为W25Q256时才用设置为4字节地址模式 { temp = W25QXX_ReadSR(3);//读取状态寄存器3,判断地址模式 if((temp&0x01)==0)//如果不是4字节地址模式,则进入4字节地址模式 { W25QXX_CS(0);//0片选开启,1片选关闭 SPI2_ReadWriteByte(W25X_Enable4ByteAddr);//发送进入4字节地址模式指令 W25QXX_CS(1);//0片选开启,1片选关闭 } } if(W25QXX_TYPE==W25Q256||W25QXX_TYPE==W25Q128||W25QXX_TYPE==W25Q64 ||W25QXX_TYPE==W25Q32||W25QXX_TYPE==W25Q16||W25QXX_TYPE==W25Q80) return 0; else return 1;//如果读出ID是现有型号列表中的一个,则识别芯片成功! } //读取W25QXX的状态寄存器,W25QXX一共有3个状态寄存器 //状态寄存器1: //BIT7 6 5 4 3 2 1 0 //SPR RV TB BP2 BP1 BP0 WEL BUSY //SPR:默认0,状态寄存器保护位,配合WP使用 //TB,BP2,BP1,BP0:FLASH区域写保护设置 //WEL:写使能锁定 //BUSY:忙标记位(1,忙;0,空闲) //默认:0x00 //状态寄存器2: //BIT7 6 5 4 3 2 1 0 //SUS CMP LB3 LB2 LB1 (R) QE SRP1 //状态寄存器3: //BIT7 6 5 4 3 2 1 0 //HOLD/RST DRV1 DRV0 (R) (R) WPS (R) (R) //regno:状态寄存器号,范:1~3 //返回值:状态寄存器值 uint8_t W25QXX_ReadSR(uint8_t regno) { uint8_t byte=0,command=0; switch(regno) { case 1: command=W25X_ReadStatusReg1;//读状态寄存器1指令 break; case 2: command=W25X_ReadStatusReg2;//读状态寄存器2指令 break; case 3: command=W25X_ReadStatusReg3;//读状态寄存器3指令 break; default: command=W25X_ReadStatusReg1;//读状态寄存器1指令 break; } W25QXX_CS(0);//0片选开启,1片选关闭 SPI2_ReadWriteByte(command);//发送读取状态寄存器命令 byte=SPI2_ReadWriteByte(0Xff);//读取一个字节 W25QXX_CS(1);//0片选开启,1片选关闭 return byte;//返回变量byte } //写W25QXX状态寄存器 void W25QXX_Write_SR(uint8_t regno,uint8_t sr) { uint8_t command=0; switch(regno) { case 1: command=W25X_WriteStatusReg1;//写状态寄存器1指令 break; case 2: command=W25X_WriteStatusReg2;//写状态寄存器2指令 break; case 3: command=W25X_WriteStatusReg3;//写状态寄存器3指令 break; default: command=W25X_WriteStatusReg1; break; } W25QXX_CS(0);//0片选开启,1片选关闭 SPI2_ReadWriteByte(command);//发送写取状态寄存器命令 SPI2_ReadWriteByte(sr);//写入一个字节 W25QXX_CS(1);//0片选开启,1片选关闭 } //W25QXX写使能 //将WEL置位 void W25QXX_Write_Enable(void) { W25QXX_CS(0);//0片选开启,1片选关闭 SPI2_ReadWriteByte(W25X_WriteEnable);//发送写使能 W25QXX_CS(1);//0片选开启,1片选关闭 } //W25QXX写禁止 //将WEL清零 void W25QXX_Write_Disable(void) { W25QXX_CS(0);//0片选开启,1片选关闭 SPI2_ReadWriteByte(W25X_WriteDisable);//发送写禁止指令 W25QXX_CS(1);//0片选开启,1片选关闭 } //读取芯片ID //高8位是厂商代号(本程序不判断厂商代号) //低8位是容量大小 //0XEF13型号为W25Q80 //0XEF14型号为W25Q16 //0XEF15型号为W25Q32 //0XEF16型号为W25Q64 //0XEF17型号为W25Q128(目前洋桃2号开发板使用128容量芯片) //0XEF18型号为W25Q256 uint16_t W25QXX_ReadID(void) { uint16_t Temp = 0; W25QXX_CS(0);//0片选开启,1片选关闭 SPI2_ReadWriteByte(0x90);//发送读取ID命令 SPI2_ReadWriteByte(0x00); SPI2_ReadWriteByte(0x00); SPI2_ReadWriteByte(0x00); Temp|=SPI2_ReadWriteByte(0xFF)<<8; Temp|=SPI2_ReadWriteByte(0xFF); W25QXX_CS(1);//0片选开启,1片选关闭 return Temp; } //读取SPI FLASH //在指定地址开始读取指定长度的数据 //pBuffer:数据存储区 //ReadAddr:开始读取的地址(24bit) //NumByteToRead:要读取的字节数(最大65535) void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead) { uint16_t i; W25QXX_CS(0);//0片选开启,1片选关闭 SPI2_ReadWriteByte(W25X_ReadData);//发送读取命令 if(W25QXX_TYPE==W25Q256)//如果是W25Q256的话地址为4字节的,要发送最高8位 { SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>24)); } SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>16));//发送24bit地址 SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>8)); SPI2_ReadWriteByte((uint8_t)ReadAddr); for(i=0;i>24)); } SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>16));//发送24bit地址 SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>8)); SPI2_ReadWriteByte((uint8_t)WriteAddr); for(i=0;ipageremain { pBuffer+=pageremain; WriteAddr+=pageremain; NumByteToWrite-=pageremain; //减去已经写入了的字节数 if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节 else pageremain=NumByteToWrite; //不够256个字节了 } }; } //写SPI FLASH //在指定地址开始写入指定长度的数据 //该函数带擦除操作! //pBuffer:数据存储区 //WriteAddr:开始写入的地址(24bit) //NumByteToWrite:要写入的字节数(最大65535) uint8_t W25QXX_BUFFER[4096]; void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite) { uint32_t secpos; uint16_t secoff; uint16_t secremain; uint16_t i; uint8_t* W25QXX_BUF; W25QXX_BUF=W25QXX_BUFFER; secpos=WriteAddr/4096;//扇区地址 secoff=WriteAddr%4096;//在扇区内的偏移 secremain=4096-secoff;//扇区剩余空间大小 //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用 if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节 while(1) { W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容 for(i=0;i4096)secremain=4096;//下一个扇区还是写不完 else secremain=NumByteToWrite;//下一个扇区可以写完了 } }; } //擦除整个芯片 //等待时间超长... void W25QXX_Erase_Chip(void) { W25QXX_Write_Enable();//SET WEL W25QXX_Wait_Busy();//等待忙状态 W25QXX_CS(0);//0片选开启,1片选关闭 SPI2_ReadWriteByte(W25X_ChipErase);//发送片擦除命令 W25QXX_CS(1);//0片选开启,1片选关闭 W25QXX_Wait_Busy();//等待芯片擦除结束 } //擦除一个扇区 //Dst_Addr:扇区地址 根据实际容量设置 //擦除一个扇区的最少时间:150ms void W25QXX_Erase_Sector(uint32_t Dst_Addr) { Dst_Addr*=4096; W25QXX_Write_Enable();//SET WEL W25QXX_Wait_Busy(); W25QXX_CS(0);//0片选开启,1片选关闭 SPI2_ReadWriteByte(W25X_SectorErase);//发送扇区擦除指令 if(W25QXX_TYPE==W25Q256)//如果是W25Q256的话地址为4字节的,要发送最高8位 { SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>24)); } SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>16));//发送24bit地址 SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>8)); SPI2_ReadWriteByte((uint8_t)Dst_Addr); W25QXX_CS(1);//0片选开启,1片选关闭 W25QXX_Wait_Busy();//等待擦除完成 } //等待空闲 void W25QXX_Wait_Busy(void) { while((W25QXX_ReadSR(1)&0x01)==0x01);//等待BUSY位清空 } //进入掉电模式 void W25QXX_PowerDown(void) { W25QXX_CS(0);//0片选开启,1片选关闭 SPI2_ReadWriteByte(W25X_PowerDown);//发送掉电命令 0xB9 W25QXX_CS(1);//0片选开启,1片选关闭 delay_us(3);//等待TPD } //唤醒 void W25QXX_WAKEUP(void) { W25QXX_CS(0);//0片选开启,1片选关闭 SPI2_ReadWriteByte(W25X_ReleasePowerDown);//发送电源唤醒指令 0xAB W25QXX_CS(1);//0片选开启,1片选关闭 delay_us(3);//等待TRES1 } ```