[STM32CubeMX learning] SPI read and write W25Q16

1. SPI bus

        SPI is divided into master-slave working mode, usually there is one master device and one or more slave devices, in this article, MCU is the master, and W25Q16 is the slave.

SPI communication has the following four lines:

MISO: Master device data input, slave device data output.
MOSI: master device data output, slave device data input.
SCLK: clock signal, generated by the master device.
CS: Slave device chip select signal, controlled by the master device, low level is selected.

        SPI can send and receive serial data at the same time. When the host sends a data, the slave also returns its own data to the host. In this way, the data of both parties is exchanged. When the host controls the peripheral, write operations and read operations are done synchronously. If only the write operation is performed, the master simply ignores the received byte; conversely, if the master wants to read a byte from the slave, it must send a null byte to trigger the transfer from the slave.

The characteristics of SPI: the high bit is sent first, and there are four working modes in total.

CPOL (clock polarity): specifies the level of the idle state of the SCK clock signal (0-low level, 1-high level)
CPHA (clock phase): specifies whether the data is read on the rising or falling edge of the SCK clock Sampling (0-start sampling on the first clock edge, 1-start sampling on the second clock edge)

Mode 0: CPOL=0, CPHA =0 SCK idle is low level, data is sampled (extract data) on the rising edge of SCK
Mode 1: CPOL=0, CPHA =1 SCK idle is low level, data is The falling edge is sampled (extract data)
mode 2: CPOL=1, CPHA =0 SCK idle is high level, data is sampled on the falling edge of SCK (extract data)
mode 3: CPOL=1, CPHA =1 SCK idle is High level, data is sampled on the rising edge of SCK (extract data)

2. Use STM32CubeMX to configure related pins

 

3. Realize four modes of software simulation SPI

spi.h

#ifndef __SPI_H
#define __SPI_H

#include "main.h"

#define MOSI_H  HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_SET)  
#define MOSI_L  HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_RESET)  
#define SCK_H   HAL_GPIO_WritePin(SCLK_GPIO_Port, SCLK_Pin, GPIO_PIN_SET)  
#define SCK_L   HAL_GPIO_WritePin(SCLK_GPIO_Port, SCLK_Pin, GPIO_PIN_RESET)  
#define MISO    HAL_GPIO_ReadPin(MISO_GPIO_Port, MISO_Pin) 
#define F_CS_H   HAL_GPIO_WritePin(F_CS_GPIO_Port, F_CS_Pin, GPIO_PIN_SET)  
#define F_CS_L   HAL_GPIO_WritePin(F_CS_GPIO_Port, F_CS_Pin, GPIO_PIN_RESET) 

uint8_t SOFT_SPI_RW_MODE0(uint8_t write_dat);
uint8_t SOFT_SPI_RW_MODE1(uint8_t write_dat);
uint8_t SOFT_SPI_RW_MODE2(uint8_t write_dat);
uint8_t SOFT_SPI_RW_MODE3(uint8_t write_dat);

uint8_t SPI2_ReadWriteByte(uint8_t TxData);

#endif

 spi.c

#include "spi.h"

/*spi延时函数,微秒*/
static void spi_delay(uint16_t time)
{    
   uint16_t i=0;  
   while(time--)
   {
      i=10;  
      while(i--) ;    
   }
}

//CPOL:规定了SCK时钟信号空闲状态的电平(0-低电平,1-高电平)
//CPHA:规定了数据是在SCK时钟的上升沿还是下降沿被采样(0-第一个时钟边沿开始采样,1-第二个时钟边沿开始采样)

//模式0:CPOL=0,CPHA =0  SCK空闲为低电平,数据在SCK的上升沿被采样(提取数据)
//模式1:CPOL=0,CPHA =1  SCK空闲为低电平,数据在SCK的下降沿被采样(提取数据)
//模式2:CPOL=1,CPHA =0  SCK空闲为高电平,数据在SCK的下降沿被采样(提取数据)
//模式3:CPOL=1,CPHA =1  SCK空闲为高电平,数据在SCK的上升沿被采样(提取数据)

/* CPOL = 0, CPHA = 0 */
uint8_t SOFT_SPI_RW_MODE0(uint8_t write_dat)
{
  uint8_t i,read_dat = 0;
  SCK_L; 
  for(i=0;i<8;i++)
  {
    if(write_dat&0x80)
      MOSI_H;  
    else                    
      MOSI_L;  
    write_dat <<= 1;
    spi_delay(1);	
    SCK_H; 
    read_dat <<= 1;  
    
    if(MISO) 
      read_dat++; 
    spi_delay(1);
    SCK_L; 
    __nop();
  }	
  return read_dat;
}
 
/* CPOL=0,CPHA=1 */
uint8_t SOFT_SPI_RW_MODE1(uint8_t write_dat) 
{
  uint8_t i,read_dat = 0;
  SCK_L;
	for(i=0;i<8;i++)
	{
		SCK_H;     
		if(write_dat&0x80)
			MOSI_H; 
		else      
			MOSI_L;
		write_dat <<= 1;
		spi_delay(1);
		SCK_L; 
		read_dat <<= 1;  
 
		if(MISO)
			read_dat++;  
		spi_delay(1);
	}
	return read_dat;   
}
 
/* CPOL=1,CPHA=0 */
uint8_t SOFT_SPI_RW_MODE2(uint8_t write_dat) 
{
  uint8_t i,read_dat = 0; 
  SCK_H; 
	for(i=0;i<8;i++)  
	{
		if(write_dat&0x80)
			MOSI_H; 
		else      
			MOSI_L;   
		write_dat <<= 1;   
		spi_delay(1);
		SCK_L;    
		read_dat <<= 1;  
 
		if(MISO)
			read_dat++;   
		spi_delay(1);
		SCK_H; 
	}
	return read_dat;
}
 
/* CPOL = 1, CPHA = 1 */
uint8_t SOFT_SPI_RW_MODE3(uint8_t write_dat)
{
  uint8_t i,read_dat = 0;
  SCK_H; 
  for(i=0;i<8;i++)
  {
    SCK_L; 
    if(write_dat&0x80)
      MOSI_H;  
    else                    
      MOSI_L;  
    write_dat <<= 1;
    spi_delay(1);	
    SCK_H; 
    read_dat <<= 1;  
    
    if(MISO) 
      read_dat++; 
    spi_delay(1);
    __nop();
  }
  return read_dat;
}

//SPI2 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
uint8_t SPI2_ReadWriteByte(uint8_t TxData)
{
  uint8_t Rxdata;
  Rxdata = SOFT_SPI_RW_MODE3(TxData);//使用模式3
  return Rxdata;		
}

4. Realize the read and write functions of W25Q16

Refer to Punctual Atomic Routine

w25q16.h

#ifndef __W25Q16_H
#define __W25Q16_H	

#include "main.h" 

extern uint8_t W25QXX_BUFFER[4096];

//W25X16读写指令表
#define W25X_WriteEnable		0x06 
#define W25X_WriteDisable		0x04 
#define W25X_ReadStatusReg		0x05 
#define W25X_WriteStatusReg		0x01 
#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 

uint16_t  W25QXX_ReadID(void);//读取FLASH ID

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_PowerDown(void);//进入掉电模式
void W25QXX_WAKEUP(void);//唤醒

#endif

w25q16.c

#include "w25q16.h"
#include "spi.h"

//容量为16M bit,2M byte,共有32个块,512个扇区 
//4Kbytes为一个扇区,16个扇区为1个块												   

//读取SPI_FLASH的状态寄存器
//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
uint8_t W25QXX_ReadSR(void)   
{  
	uint8_t byte=0;   
	F_CS_L;//使能器件   
	SPI2_ReadWriteByte(W25X_ReadStatusReg);    //发送读取状态寄存器命令    
	byte=SPI2_ReadWriteByte(0Xff);   //读取一个字节  
	F_CS_H;//取消片选     
	return byte;   
} 

//写SPI_FLASH状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void W25QXX_Write_SR(uint8_t sr)   
{   
	F_CS_L; //使能器件   
	SPI2_ReadWriteByte(W25X_WriteStatusReg);   //发送写取状态寄存器命令    
	SPI2_ReadWriteByte(sr);  //写入一个字节  
	F_CS_H;//取消片选     
}   

//等待空闲
void W25QXX_Wait_Busy(void)   
{   
	while((W25QXX_ReadSR()&0x01)==0x01);   //等待BUSY位清空
} 

//SPI_FLASH写使能	
//将WEL置位   
void W25QXX_Write_Enable(void)   
{
	F_CS_L;//使能器件   
  SPI2_ReadWriteByte(W25X_WriteEnable);   //发送写使能  
  F_CS_H;//取消片选     
} 

//SPI_FLASH写禁止	
//将WEL清零  
void W25QXX_Write_Disable(void)   
{  
	F_CS_L;//使能器件   
  SPI2_ReadWriteByte(W25X_WriteDisable);   //发送写禁止指令    
  F_CS_H;//取消片选     
} 			   

//读取芯片ID W25X16的ID:0XEF14
uint16_t W25QXX_ReadID(void)
{
	uint16_t Temp = 0;	  
	F_CS_L;   //使能器件   
	SPI2_ReadWriteByte(0x90);//发送读取ID命令	    
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00); 	 			   
	Temp|=SPI2_ReadWriteByte(0xFF)<<8;  
	Temp|=SPI2_ReadWriteByte(0xFF);	 
  F_CS_H;//取消片选     
	return Temp;
} 

//读取SPI FLASH  
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)   
{ 
 	uint16_t i;    												    
	F_CS_L;//使能器件   
  SPI2_ReadWriteByte(W25X_ReadData);//发送读取命令   
  SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>16));  //发送24bit地址    
  SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>8));   
  SPI2_ReadWriteByte((uint8_t)ReadAddr);   
  for(i=0;i<NumByteToRead;i++)
	{ 
    pBuffer[i]=SPI2_ReadWriteByte(0XFF);   //循环读数  
  }
  F_CS_H;//取消片选     
}  

//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!	 
void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
 	uint16_t i;  
  W25QXX_Write_Enable();  //SET WEL 
	F_CS_L;   //使能器件   
  SPI2_ReadWriteByte(W25X_PageProgram);    //发送写页命令   
  SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>16));//发送24bit地址    
  SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>8));   
  SPI2_ReadWriteByte((uint8_t)WriteAddr);   
  for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);//循环写数  
  F_CS_H;//取消片选     
	W25QXX_Wait_Busy();//等待写入结束
} 

//无检验写SPI FLASH 
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能 
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{ 			 		 
	uint16_t pageremain;	   
	pageremain=256-WriteAddr%256; //单页剩余的字节数		 	    
	if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节
	while(1)
	{	   
		W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
		if(NumByteToWrite==pageremain)break;//写入结束了
	 	else //NumByteToWrite>pageremain
		{
			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;    

	secpos=WriteAddr/4096;//扇区地址 0~511 for w25x16
	secoff=WriteAddr%4096;//在扇区内的偏移
	secremain=4096-secoff;//扇区剩余空间大小   

	if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节
	while(1) 
	{	
		W25QXX_Read(W25QXX_BUFFER,secpos*4096,4096);//读出整个扇区的内容
		for(i=0;i<secremain;i++)//校验数据
		{
			if(W25QXX_BUFFER[secoff+i]!=0XFF)break;//需要擦除  	  
		}
		if(i<secremain)//需要擦除
		{
			W25QXX_Erase_Sector(secpos);//擦除这个扇区
			for(i=0;i<secremain;i++)//复制
			{
				W25QXX_BUFFER[i+secoff]=pBuffer[i];	  
			}
			W25QXX_Write_NoCheck(W25QXX_BUFFER,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;//下一个扇区可以写完了
		}	 
	};	 	 
}

//擦除整个芯片
//整片擦除时间:
//W25X16:25s 
//W25X32:40s 
//W25X64:40s 
//等待时间超长...
void W25QXX_Erase_Chip(void)   
{                                             
  W25QXX_Write_Enable();  //SET WEL 
  W25QXX_Wait_Busy();   
  F_CS_L;//使能器件   
  SPI2_ReadWriteByte(W25X_ChipErase);   //发送片擦除命令  
  F_CS_H;//取消片选     
	W25QXX_Wait_Busy();//等待芯片擦除结束
}   

//擦除一个扇区
//Dst_Addr:扇区地址 0~511 for w25x16
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(uint32_t Dst_Addr)   
{   
	Dst_Addr*=4096;
  W25QXX_Write_Enable();  //SET WEL 	 
  W25QXX_Wait_Busy();   
  F_CS_L;//使能器件   
  SPI2_ReadWriteByte(W25X_SectorErase);   //发送扇区擦除指令 
  SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>16));  //发送24bit地址    
  SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>8));   
  SPI2_ReadWriteByte((uint8_t)Dst_Addr);  
  F_CS_H;//取消片选     
  W25QXX_Wait_Busy();//等待擦除完成
}   

//进入掉电模式
void W25QXX_PowerDown(void)   
{ 
  F_CS_L;//使能器件   
  SPI2_ReadWriteByte(W25X_PowerDown);   //发送掉电命令  
  F_CS_H;//取消片选     
  Delay_Us(3);//等待TPD  
}   

//唤醒
void W25QXX_WAKEUP(void)   
{  
  F_CS_L;//使能器件   
  SPI2_ReadWriteByte(W25X_ReleasePowerDown);   //send W25X_PowerDown command 0xAB    
  F_CS_H;//取消片选     
  Delay_Us(3); //等待TPD  
}   

5. Verification

Add the necessary variables

const uint8_t TEXT_Buffer[]={"W25Q16 TEST"};//要写入到W25Q16的字符串数组
#define SIZE sizeof(TEXT_Buffer)
uint16_t W25QXX_TYPE;//定义我们使用的flash芯片型号		
uint32_t FLASH_SIZE=2*1024*1024; //FLASH 大小为2M字节
uint8_t datatemp[SIZE];

Loop read and write verification in the main function

  W25QXX_TYPE = W25QXX_ReadID();  
  printf("W25QXX_TYPE == %04x\r\n",W25QXX_TYPE);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */   
    printf("\r\nStart Write W25Q16....\r\n");
    W25QXX_Write((uint8_t*)TEXT_Buffer,FLASH_SIZE-100,SIZE); //从倒数第100个地址处开始,写入SIZE长度的数据
    printf("W25Q16 Write Finished!\r\n");//提示传送完成

    Delay_Ms(1000);
   
    printf("\r\nStart Read W25Q16....\r\n");
    W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE);  //从倒数第100个地址处开始,读出SIZE个字节
    printf("The Data Readed Is:  ");//提示传送完成
    printf("%s\r\n",datatemp);//显示读到的字符串

    Delay_Ms(1000); 
  }
  /* USER CODE END 3 */

After compiling and downloading, check it in the serial port assistant

 It can be seen that the ID of the chip is 0xef14, and the loop read and write is also successful.

Guess you like

Origin blog.csdn.net/weixin_46183891/article/details/124903252