看野火的视频,用正点原子的板子(STM32F4探索者)做SPI实验

1. 实验目的

读取W25Q128Flash的设备ID、厂商ID,以及读写数据操作。

2. 实验流程

初始化SPI;
编写SPI发送并接收一个字节函数;
编写读取设备ID函数;
编写Flash使能函数;
编写擦除Flash指定扇区函数;
编写等待Flash内部时序操作完成函数;
编写读取Flash的内容函数;
编写向Flash中写入内容的函数;
编写main函数。

2.1 准备工作

在这里插入图片描述

在这里插入图片描述如图可以看到,PB3、PB4、PB5分别和SPI1 SCK、SPI1 MISO、SPI1 MOSI相连,F_CS片选引脚与PB14相连。要把PB3、PB4、PB5引脚复用为SPI1的三个引脚。

2.2 初始化SPI

在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/e2d9cb3d369842d381496a0623ab60e7.png
在这里插入图片描述
可以看到SPI1挂载到APB2总线上面,GPIOG、GPIOB挂载到AHB1总线上。

#define	W25QXX_CS  PBout(14)  	  //W25QXX的片选信号,W25QXX_CS  = 0; PB14引脚输出低电平。W25QXX_CS = 1; PB14引脚输出高电平
void SPI1_Init(void){
	GPIO_InitTypeDef GPIO_InitStructure;   //GPIO结构体
	SPI_InitTypeDef  SPI_InitStructure;    //SPI结构体
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);   //使能SPI时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);  //使能GPIOB时钟,PB14引脚是片选的引脚
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE); //使能GPIOG时钟
	
	//GPIOFB3,4,5初始化设置: 复用功能输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5; 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;       //复用功能
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;     //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //100MHZ高速
	GPIO_InitStructure.GPIO_PuPd =  GPIO_PuPd_UP;      //上拉
	GPIO_Init(GPIOB,&GPIO_InitStructure);              //初始化GPIO的引脚
	
	GPIO_InitStructure.GPIO_Pin  =  GPIO_Pin_14;      //PB14,CS引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;     //输出
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;    //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;      //上拉
    GPIO_Init(GPIOB, &GPIO_InitStructure);            //初始化GPIO的引脚
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;       //PG7(参考正点原子实验手册)
    GPIO_Init(GPIOG, &GPIO_InitStructure);          //初始化
	GPIO_SetBits(GPIOG,GPIO_Pin_7);  //PG7输出1,防止NRF干扰SPI FLASH的通信 
	W25QXX_CS = 1;			          //SPI FLASH不选中,开始状态,不发数据就是1
	
	//配置引脚复用映射
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3复用为SPI1
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB3复用为SPI1
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB3复用为SPI1
	
	//SPI口初始化
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;      //设置SPI工作模式:主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;  //设置SPI的数据大小: 8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;        //串行同步时钟的空闲状态为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;       //数据捕获于第二个时钟沿
	SPI_InitStructure.SPI_NSS =  SPI_NSS_Soft;         //NSS信号由软件管理
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //预分频256
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从MSB(高数据在前)位开始,配置数据先行,发送数据就需要先发送高位,再发送低位
	SPI_InitStructure.SPI_CRCPolynomial = 7;           //CRC值计算的多项式,使用CRC校验,校验位,一般不使用,这里也没有使用
	SPI_Init(SPI1, &SPI_InitStructure);                //根据指定的参数初始化外设SPIx寄存器,写入配置到寄存器
	SPI_Cmd(SPI1, ENABLE);  //使能  SPI1
}

2.3 编写SPI发送并接收一个字节函数

//下面调用了这个函数
static uint8_t SPI_TIMEOUT_UserCallback(uint8_t errorCode){  
	printf("SPI 等待超时!errorCode =%d",errorCode);
	return 0;
}
//发送并接收一个字节
uint8_t Spi_Flash_Send_Byte(uint8_t data){
	 uint16_t SPITimeout = 1000;      //超时时间
	//检测TXE标志()发送缓冲区是空的,才能发送数据,如果为空的话就会置1的
	//SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE); //@arg SPI_I2S_FLAG_TXE: Transmit buffer empty flag.
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET){    //RESET = 0;  //检测并等待至发送缓冲区为空 	  
	  if((SPITimeout--) == 0){
			return  SPI_TIMEOUT_UserCallback(0);
		}
	};
	//程序执行到此处,发送缓冲区置为空
	 SPI_I2S_SendData(SPI1,data);     //发送数据
	//检查并等待至RX缓冲区为非空 ,非空的话就代表有数据
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET){  //RESET = 0;  //检测并等待至接收缓冲区为非空,置1的话代表非空
		if((SPITimeout--) == 0){
			return  SPI_TIMEOUT_UserCallback(0);
		}
	}
	 return SPI_I2S_ReceiveData(SPI1);  //程序执行到此处,说明数据发送完毕,并接收到了一个字节数据
}
//接收一个字节数据
uint8_t SPI_FLASH_Read_Byte(void){
  return Spi_Flash_Send_Byte(0X00);  //FLASH会忽略这个数据
}

2.4 编写读取设备ID函数

在这里插入图片描述
由上图可知制造商ID是0xEF,设备ID是0x17。
在这里插入图片描述在这里插入图片描述
由上面的时序图可以看出,先要发送一个0X90,再发送一个任意字节,再发送一个任意字节,再发送一个0X00,然后接收字节是制造商ID,再接收字节是设备ID。

uint32_t SPI_Read_ID(void){
	uint16_t flash_id;
	//片选使能
    W25QXX_CS = 0;	   //CS引脚拉低
	Spi_Flash_Send_Byte(0X90); //发送命令
	SPI_FLASH_Read_Byte();
	SPI_FLASH_Read_Byte();
	SPI_FLASH_Read_Byte();    //这里就是发送的0X00			   
    flash_id |= SPI_FLASH_Read_Byte();   //随便发送一个字节,收到厂商id:0XEF
	flash_id <<= 8;           //左移8位 
	flash_id |= SPI_FLASH_Read_Byte();   //随便发送一个字节,收到厂商id:0XEF
	W25QXX_CS = 1;	         //CS引脚拉高
	return flash_id;  
}

2.5 编写Flash使能函数

在这里插入图片描述发送0X06,不用接收。

//FLASH写入使能 ,在擦除,读写函数中都要调用这个使能
void SPI_Writer_Enable(void){
	
  W25QXX_CS = 0;	             //片选使能,CS引脚拉低
	Spi_Flash_Send_Byte(0X06);   //flash写入使能,查手册得到的
	W25QXX_CS = 1;	             //CS引脚拉高
}

2.6 编写擦除Flash指定扇区函数

在这里插入图片描述
发送0X20,发送扇区地址,分三次发送扇区地址。

//擦除FLASH指定扇区
void SPI_ERASER_SECTOR(uint32_t addr){
	SPI_Writer_Enable();  //FLASH写入使能
    //uint8_t flash_8 = (addr>>16)&0XFF;  //发的23,所以移动的是16
    W25QXX_CS = 0;	   //CS引脚拉低   ,片选使能
	Spi_Flash_Send_Byte(0X20);   //发送命令
	Spi_Flash_Send_Byte((uint8_t)((addr>>16)&0XFF));     //分三次发送扇区地址
    Spi_Flash_Send_Byte((uint8_t)((addr>>8)&0XFF));   
    Spi_Flash_Send_Byte((uint8_t)(addr&0XFF));          
    W25QXX_CS = 1;	          //CS引脚拉高
}

2.7 编写等待Flash内部时序操作完成函数

在这里插入图片描述
在这里插入图片描述
发送0X05,等待返回的值是否为0,这是为1就是忙碌,0就是不忙碌。

//等待FLASH内部时序操作完成
void SPI_WaitForWriterEnd(void){
	uint8_t status_reg = 0;     //状态标志位
	W25QXX_CS = 0;	           //CS引脚拉低   ,片选使能
	Spi_Flash_Send_Byte(0X05);   //发送命令,读状态寄存器
	status_reg = Spi_Flash_Send_Byte(0X00);   //FLASH会忽略这个数据
	while(status_reg&0X01 == 1){                 //如果为1就是忙碌,0就是不忙碌
	status_reg = Spi_Flash_Send_Byte(0X00);     //一直读取状态
	}
	W25QXX_CS = 1;	          //CS引脚拉高
}

2.8 编写读取Flash的内容函数

在这里插入图片描述
发送0X03,发送扇区地址,分三次发送扇区地址,发送任意字节,返回数据。

//读取FLASH的内容
void SPI_Read_Data(uint32_t addr, uint8_t *Read_Buff,uint32_t numByteToRead){
	SPI_Writer_Enable();        //FLASH写入使能
	W25QXX_CS = 0;	            //CS引脚拉低,片选使能
	Spi_Flash_Send_Byte(0X03);  //发送命令,读状态寄存器
	Spi_Flash_Send_Byte((addr>>16)&0XFF);     //分三次发送扇区地址
    Spi_Flash_Send_Byte((addr>>8)&0XFF);   
    Spi_Flash_Send_Byte(addr&0XFF);  
	while(numByteToRead--){	
	 *Read_Buff = Spi_Flash_Send_Byte(0X00);   //返回数据
	 Read_Buff++;   //地址+1
	}
	W25QXX_CS = 1;
}

2.9 编写向Flash中写入内容的函数

在这里插入图片描述
发送0X02,发送扇区地址,分三次发送扇区地址,发送内容字节,进行数据存储。

//写入内容
void SPI_Write_Data(uint32_t addr, uint8_t *Write_Buff,uint32_t numByteToWrite){
	SPI_Writer_Enable();  //FLASH写入使能
	W25QXX_CS = 0;	            //CS引脚拉低 ,片选使能
	Spi_Flash_Send_Byte(0X02);  //发送命令,写数据的指令
	Spi_Flash_Send_Byte((addr>>16)&0XFF);     //分三次发送扇区地址
    Spi_Flash_Send_Byte((addr>>8)&0XFF);   
    Spi_Flash_Send_Byte(addr&0XFF);  
	while(numByteToWrite--){
	 Spi_Flash_Send_Byte(*Write_Buff);   //写入数据
	 Write_Buff++;                      //地址+1
	}
	W25QXX_CS = 1;		     //CS引脚拉高
}

2.10 编写main函数

uint8_t read_buff[4096];   
uint8_t write_buff[4096];
int main(void)
{
	    uint32_t value;
	    uint16_t i;
	    uint16_t j;
		LED_GPIO_Config(); 
		USART_Config();	             
		SPI1_Init();
	    value =	SPI_Read_ID();
	    printf("ID = 0X%x\r\n",value);
		SPI_ERASER_SECTOR(0);         //擦除扇区,0是代表地址
		SPI_WaitForWriterEnd();       //等待擦除扇区结束
	    for(i = 0;i < 20; i++){
				write_buff[i] = 9;       //写进去的数字不能大于256
			}
	    SPI_Write_Data(0, write_buff,20);	//只写20个字节的数据	   				
	    SPI_WaitForWriterEnd();             //等待写入扇区结束
	    SPI_Read_Data(0,read_buff,4096);    //读地址0,把读到的数据放到buff中,读4096个字节
	    for(j = 0;j<4096;j++){
		  	printf("0X%x",read_buff[j]);
				if(j%10 == 0){             //每10个数据,回车换行
					printf("\r\n");
				}
			}
			while(1){}
}  

3. 实验结果

在这里插入图片描述
可以看到制造商ID:0XEF,设备ID:0X17。
![在这里插入图片描述](https://img-blog.csdnimg.cn/81b0d6b167f34a95864b01ae289cb3d7.png
可以看到前20个字节的数据内容是0X9,后面的字节内容都是0XFF,证明写入和读取数据正确。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值