简介:
单片机型号:stm32l431rct6
SPI Flash型号:W25Q32JVSSIQ
使用软件:CubeIDE
1. W25Q32JVSSIQ简介
我们通过SPI协议来读取 SPI Flash的厂商ID和芯片独一无二的ID,查数据的芯片手册可以看到如下重要点:
1.1 芯片所支持的模式
我们知道SPI协议支持四种协议模式,该芯片手册中规定W25Q32JV的标准工作模式是 SPI模式 0(CPOL = 0,CPHA = 0)。
1.2 芯片具体ID
由下图可知芯片的厂商ID为0xEF,芯片ID为0x15。
由标准指令集我们可以看出 我们得到厂商和芯片ID需要发送六个字节的数据,分别是:0x90、Dummy_Byte、0x00以及两个其他数据;对于初学者来说,可能不解为什么接收数据前要发送一个任意数据到从机,那是因为在全双工模式下,发送和接收数据是同步进行的,即你发送数据给从机的同时,从机也会发送数据给你,而这个接收到的数据有效还是无效,完全是看相应芯片所制定的协议,但一定会发送,所以可以通过接收从机数据的方式来判断数据是否已发送完毕;而接收数据因为从机没有时钟信号,需要主机提供,所以通过给从机发送数据(任意数)的方式提供时钟信号,正如前面所述,发送一个数据意味着接收到一个数据,将接收到的数据保存即可。这就是两个“其他数据”存在的意义。
2. CubeIDE配置
2.1 使能SPI1,配置相关参数
我的硬件原理图是这样的:
我们进行简单的数据传输无需开启CRC校验 ,NSS即片选信号我们选择软件,不要通过硬件,否则会造成错误
配置完成后,MISO、MOSI都已经自动配置完成,现在需要我们配置CS/SS片选信号,在我的原理图中他是PA4
注意:PA4即CS片选信号要设置成输出模式。
我们在配置完成后,CubeIDE软件会自动帮我们生成代码,这里不做过多展示,如果大家用的不是自动生成那么可以自行到网上寻找资源查看具体的配置(网上资源很多的哦)
3. 代码编写
3.1 spi_flash.c代码编写
在spi_flash.c中我们主要做的三件事:
1.定义片选,在低电平时候开始允许通信,高电平时候结束通信;
2.编写发送一字节的函数(其实发送函数也相当于接收,因为SPI协议是同发同收的);
3.编写读取ID的函数
3.1.1 片选定义
// 片选引脚定义
#define CS_PIN GPIO_PIN_4
#define CS_PORT GPIOA
// 拉低 CS 信号
#define cs_low() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET)
// 拉高 CS 信号
#define cs_high() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET)
3.1.2 发送一字节的函数
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
uint8_t r_data = 0;
HAL_StatusTypeDef status;
if( (status = HAL_SPI_TransmitReceive(&hspi1, &byte, &r_data, 1, SPI_TIMEOUT)) != HAL_OK )
{
if (status == HAL_ERROR)
{
printf("SPI transmission error!\n");
}
else if (status == HAL_TIMEOUT)
{
printf("SPI transmission timeout!\n");
}
else if (status == HAL_BUSY)
{
printf("SPI is busy!\n");
}
return 0;
}
return r_data;
}
3.1.3 编写读取ID的函数
uint32_t SPI_FLASH_ReadId(void)
{
uint32_t id = 0;
uint8_t idBytes[2] = {0};
cs_low(); // 拉低片选
SPI_FLASH_SendByte(0x90); // 发送读取ID命令
// 读取2个字节的数据
SPI_FLASH_SendByte(Dummy_Byte);//不在乎读到数据
SPI_FLASH_SendByte(Dummy_Byte);//不在乎读到数据
SPI_FLASH_SendByte(0x00);
idBytes[0] = SPI_FLASH_SendByte(Dummy_Byte);//厂商ID
idBytes[1] = SPI_FLASH_SendByte(Dummy_Byte);//芯片ID
cs_high(); // 拉高片选
id = (idBytes[0] << 8) | idBytes[1];
printf("ID Bytes: %02X %02X\n", idBytes[0], idBytes[1]);
printf("Combined ID: 0x%04X\n", id);
return id;
}
3.2 main()函数编写
id = SPI_FLASH_ReadId();
printf("id is 0x%04X\n", id);
4. 运行结果
上述代码烧录运行之后,就会打印出现在的效果,头文件只需添加相应的函数以及引入一些头文件即可,这里不做过多展示
5. 补充:
5.1 遇到的问题
5.1.1 HAL_SPI_TransmitReceive()函数返回超时
返回超时代表通信没有建立成功,笔者在确定片选信号成功拉低、拉高之后,修改了时钟频率(改为了上面设置的32)、关闭了CRC校验(原来我是打开的),但是笔者认为CRC影响不大,应该是时钟频率的问题,如果有读者懂得这个的话,欢迎留言哦。
5.1.2 HAL_SPI_TransmitReceive()函数阻塞
函数原型:HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout)
笔者最开始给的延时很大,就导致函数阻塞很久,大家在书写的时候可以适当给,需要注意的是延时的单位是毫秒。
5.2 补充知识点
5.2.1 SPI协议四个模式
模式0:CPOL=0,CPHA=0;SCK串行时钟线空闲时是低电平,数据在SCK时钟上升沿被采样,在时钟下降沿切换;
模式1:CPOL=1,CPHA=0;SCK串行时钟线空闲时是低电平,数据在SCK时钟下降沿被采样,在时钟上升沿切换;
模式2:CPOL=1,CPHA=0;SCK串行时钟空闲是高电平,数据在SCK时钟下降沿被采样,在上升沿切换;
模式3:CPOL=1,CPHA=1,SCK串行时钟线空闲是高电平,数据在SCK时钟的上升沿采样,在下降沿切换;
具体的可以看笔者关于SPI协议的具体介绍文章:单片机通信协议——SPI协议_主从设备 master设备 slave设备-CSDN博客
5.2.1 Dummy_Byte字节存在的意义
Dummy_Byte我们通常定义其为0xFF或者0x00,意思是任意的数据,SPI协议在通信的时候 通过给从机高低电平来告诉他我们要进行的操作,想要的数据,以为动作有很多,所以要区分不同命令,所以就通过Dummy_Byte来区分。
以上就是笔者关于读取SPI Flash芯片ID的一些总结,如有错误还请指正,随时欢迎大家找我交流!!!