这章节我们学习SPI基本函数:器件ID的读取、数据读取、8bit数据发送和接收、块擦除。
预备知识:
对于SPI总线我们要知道:
- SPI总线的CLK是由主机产生;所以与FLASH通信,SPI的总线的CLK由STM32来产生。
- SPI总线总是以CS拉低为开始通信,拉高为结束通信。
- SPI总线时钟CLK的产生,是在CS拉低的同时,SPI必须是在发送数据的状态下,CLK才会持续有,不然是不会产生CLK的。
- 与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。
图1 SPI框图
从图1,我们看到发送的数据,首先放到发送缓存区,然后数据就会从发送缓冲区输送到位移寄存器,直到接收缓冲区最后发送出去。所以如果我们检测发送缓冲区,那么数据有可能在位移缓冲区或者接收缓存区,所以我们检测接收缓存区是否为空,就知道前面数据是否发送完毕,然后同时接收数据,然后返回接收到数据。
有了这个函数W25x16_SPI_SendByte(unsigned char Send_data);我们就可以先完成很多功能,比如JEDEC_ID的读取,在这之前我们先看下图2:
图2 W25X16 常见命令
我们先看最后一行的 JEDEC_ID的读取,第一列是命令名称,然后是对应的命令代码0x9f,然后后面BYTE2,BYTE3, BYTE4,在这如果带括号表示是FLASH从机返回的值,没有括号的是需要主机发送的值,例如(M0~M7)是我们需要接收的,但是dummy是需要我们主机发送的,这里的dummy表示发送任意值。
那么我们再看下JEDEC_ID的命令发送时序,如图3
图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