1. 首页
  2. 技术文章

STM32F10x学习笔记–硬件SPI通讯FLASH第二篇之SPI接口W25x16的ID读取

这章节我们学习SPI基本函数:器件ID的读取、数据读取、8bit数据发送和接收、块擦除。

预备知识:

对于SPI总线我们要知道:

  1. SPI总线的CLK是由主机产生;所以与FLASH通信,SPI的总线的CLK由STM32来产生。
  2. SPI总线总是以CS拉低为开始通信,拉高为结束通信。
  3. SPI总线时钟CLK的产生,是在CS拉低的同时,SPI必须是在发送数据的状态下,CLK才会持续有,不然是不会产生CLK的。
  4. 与FLASH通信SPI配置的是全双工模式,所以,数据在发送的同时,会接收数据。也就是说发送和接收是在同时进行的。

那么下来我们首先看下,数据发送和接收的库函数,

SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);

SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

SPI_TypeDef* SPIx这个函数的参数首先是SPI的端口,这里是SPI1。对于发送函数还有uint16_t Data这个参数。因为我们初始化的时候,我们定义了SPI通信是以8bit方式一帧进行收发,所以我们不管发送还是接收每一次都是1个字节。所以,代码如下:

unsigned char  W25x16_SPI_SendByte(unsigned char Send_data)
{
 unsigned char	 recevie_data;
 /*先判断发送缓冲区有无数据,也就是说发送总线是否繁忙*/
 while(SPI_I2S_GetFlagStatus(SPI1 , SPI_I2S_FLAG_TXE) == RESET)	//SPI_I2S_FLAG_TXE=0 是 说明非空  反之 =1  说明发送完毕
  {
    ;	//等待发送完毕
  }	   
 SPI_I2S_SendData(SPI1,Send_data);
 while(SPI_I2S_GetFlagStatus(SPI1 , SPI_I2S_FLAG_RXNE) == RESET)//SPI_I2S_FLAG_TXE=0 是 说明非空  反之 =1  说明发送完毕
 {
  ;	//等待发送完毕
  }
 recevie_data = SPI_I2S_ReceiveData(SPI1); 
 return  recevie_data;
}

这段代码,需要理解的是两个标志位的判断,第一个标志位的判断是判断SPI_I2S_FLAG_TXE,从库函数介绍我们知道,SPI_I2S_FLAG_TXE: Transmit buffer empty flag.检测发送缓存区是否空。我们在发送数据前,检测这个标志位,等待标志从RESET到SET就表示缓存区以发空,然后我们就可以进行发送数据了。
那么后面为什么,又有接收函数recevie_data = SPI_I2S_ReceiveData(SPI1)呢,前面我们知道SPI配置的是全双工模式,所以,数据在发送的同时,会接收数据。也就是说发送和接收是在同时进行的。在接收前,先检测SPI_I2S_FLAG_RXNE这个标志,为什么要检测RXNE而不是前面TXNE呢,我们先看下图,图1。
STM32F10x学习笔记--硬件SPI通讯FLASH第二篇之SPI接口W25x16的ID读取

图1 SPI框图

从图1,我们看到发送的数据,首先放到发送缓存区,然后数据就会从发送缓冲区输送到位移寄存器,直到接收缓冲区最后发送出去。所以如果我们检测发送缓冲区,那么数据有可能在位移缓冲区或者接收缓存区,所以我们检测接收缓存区是否为空,就知道前面数据是否发送完毕,然后同时接收数据,然后返回接收到数据。

有了这个函数W25x16_SPI_SendByte(unsigned char Send_data);我们就可以先完成很多功能,比如JEDEC_ID的读取,在这之前我们先看下图2:

STM32F10x学习笔记--硬件SPI通讯FLASH第二篇之SPI接口W25x16的ID读取

图2 W25X16 常见命令

我们先看最后一行的 JEDEC_ID的读取,第一列是命令名称,然后是对应的命令代码0x9f,然后后面BYTE2,BYTE3, BYTE4,在这如果带括号表示是FLASH从机返回的值,没有括号的是需要主机发送的值,例如(M0~M7)是我们需要接收的,但是dummy是需要我们主机发送的,这里的dummy表示发送任意值。

那么我们再看下JEDEC_ID的命令发送时序,如图3

STM32F10x学习笔记--硬件SPI通讯FLASH第二篇之SPI接口W25x16的ID读取

图3 JEDEC_ID命令时序图

我们从图3看到数据发送从CS拉低开始,然后发送0x9F命令,然后连续接收三个字节数据,这里说明下这里的ID0~ID15,是个整体代表了对应W25X16是0x3015和单独的ID0~ID7的0x14无关,手册上写的很明白我们这个发送对应的命令是9F,第二个对应的命令是AB或90(对应手册P16),这点要搞明白。具体代码如下:

unsigned int W25x16_READ_JEDEC_ID(void)
{
 unsigned  int JEDEC_ID;//定义4个字节  用于存放数据
 unsigned int DATA_H,DATA_M,DATA_L;// 根据datasheet JEDECID 为 0xef  和0x3015 所以是3个字节  
 SPI_W25x16_CS_L; //cs拉低  开始SPI
 W25x16_SPI_SendByte(0x9f); //发送09H命令
 DATA_H = W25x16_SPI_SendByte(Dummy_Data); //发送任意值   用于保证SPI的CLK正常运行,以便于接收数据
 DATA_M = W25x16_SPI_SendByte(Dummy_Data); //发送任意值   用于保证SPI的CLK正常运行,以便于接收数据
 DATA_L = W25x16_SPI_SendByte(Dummy_Data); //发送任意值   用于保证SPI的CLK正常运行,以便于接收数据

 DATA_H = DATA_H <<16;	  //向左位移16位  
 DATA_M = DATA_M <<8;	 //向左位移8位  
 JEDEC_ID = DATA_H | DATA_M | DATA_L;//组合在一起
	
 SPI_W25x16_CS_H; //停止总线
 return JEDEC_ID ;
}

我们知道FLASH的JEDEC_ID由EF +3015组成,3个字节,所以需要定义4个字节变量。CS拉低,发送数据命令0x9f,后连续接收3个字节数据,使用W25x16_SPI_SendByte(Dummy_Data)函数,是因为我们说过,SPI为全双工,发送同时可以接收数据,而且为了保证 CLK正常保持,我们必须发送数据的状态下才行。所以这里发送Dummy_Data,来获取接收值,这里的Dummy_Data为任意值,但是为了和常用名命令区分开,定义为0xff。如果不明白为什么可以发送任意值,我们可以看下图3,在接收数据同时DIO线都是XXX,这代表了忽略发送值,也就是任意值。发送就是为了保证CLK可以正常运行。因此,第一次获取数据左移2个字节,第二个接收到数据左移1个字节,最后3个组合在一起组成我们要的结果。
如果读取的结果和手册上一致,那么说明器件通信成功。FLASH还有其他ID可以读取,比如AB和90命令,大家可以自己去尝试,今天这章节就到这里。下一章节,我们学习FLASH数据写入和读取

原创文章,作者:小峰,如若转载,请注明出处:http://www.wfblog.com/archives/434.html

联系我们

400-800-8888

在线咨询:点击这里给我发消息

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息