SPI协议——读取外部SPI Flash ID

简介:

单片机型号: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的一些总结,如有错误还请指正,随时欢迎大家找我交流!!!

  • 28
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: SPI(Serial Peripheral Interface)是一种串行外设接口协议,常用于芯片之间进行通信。Verilog是一种硬件描述语言,用于设计和仿真数字电路。 要使用Verilog进行SPI读取Flash ID,首先需要编写一个SPI主设备的模块。该模块包含SPI总线控制器和Flash设备接口。在SPI总线控制器中,需要实现SPI协议的时序和通信规则,包括片选信号的生成、时钟信号的同步和数据的传输控制。Flash设备接口负责与Flash芯片进行通信,包括命令的发送和接收、数据的读取和写入。 通过SPI协议读取Flash ID的过程如下: 1. 选择Flash设备:在片选信号的激活期间,将SPI总线的片选信号置为低电平,使得Flash设备准备接受命令。 2. 发送Flash ID读取命令:向Flash设备发送读取Flash ID的命令,该命令指示Flash设备将Flash ID的数据传送到SPI总线上。 3. 接收Flash ID:等待Flash设备将Flash ID的数据传送到SPI总线,然后通过数据线接收该数据,并存储到适当的寄存器中。 4. 取消Flash设备选择:在片选信号的非激活期间,将SPI总线的片选信号恢复为高电平,结束Flash设备的选择。 通过以上步骤,SPI主设备可以成功读取Flash ID。 需要注意的是,上述过程只涵盖了读取Flash ID的基本步骤,实际的Verilog代码编写还需要考虑时序要求、数据校验等细节。根据具体的芯片型号和SPI协议,可能还需要进行一些参数配置和状态转换。因此,在实际应用中,还需要根据具体情况进行设计和实现。 ### 回答2: SPI(Serial Peripheral Interface)是一种通信协议,它可以用于与外部设备进行数据交换。在Verilog中,我们可以使用SPI读取FlashID。 首先,我们需要定义SPI总线的时钟信号、数据输入信号、数据输出信号和片选信号。接下来,我们需要编写SPI控制器的模块。 SPI控制器的模块设计如下: 1. 定义所有输入和输出端口。 2. 使用一个寄存器来保存要发送的数据。 3. 使用一个计数器来追踪要发送的位数。 4. 根据时钟信号的上升沿将数据位发送到MISO(Master In Slave Out)线上。 5. 在时钟信号的下降沿读取MOSI(Master Out Slave In)线上的数据位。 6. 当所有位都发送和接收完毕后,将数据保存到OUTPUT端口。 代码实现如下: ```verilog module spi_flashid ( input wire clk, // 时钟信号 input wire cs, // 片选信号 input wire cpol, // 时钟极性 input wire cpha, // 时钟相位 output wire [7:0] id // Flash ID ); reg [23:0] tx_data; reg [7:0] rx_data; reg [3:0] count; reg enable; always @(posedge clk) begin if (enable) begin if (cs == 1'b0) begin if (count < 24) begin if (count < 8) tx_data[count] <= spi_data[count]; else tx_data[count] <= 8'b0; rx_data[count] <= spi_data[count - 8]; count <= count + 1; end else enable <= 1'b0; end else enable <= 1'b0; end else begin if (cs == 1'b1 && cpol == 1'b1) enable <= 1'b1; else if (cs == 1'b0 && cpol == 1'b0) enable <= 1'b1; end end assign id = rx_data; endmodule ``` 在这个SPI控制器模块中,我们将FlashID保存在8位的输出端口id中。时钟信号被用来同步数据的传输。片选信号cs与时钟极性cpol和时钟相位cpha一起用来启用和禁用SPI通信。 ### 回答3: SPI (Serial Peripheral Interface)是一种常用于芯片间通信的协议。在Verilog中,我们可以使用SPI协议读取FlashID。下面是一个简单的Verilog代码示例,实现了SPI读取Flash ID的功能。 ```verilog module spi_flash ( input wire clk, // 时钟信号 input wire reset, // 复位信号 output wire [23:0] flash_id // Flash ID信号,共24位 ); reg [7:0] cmd_channel; // 命令通道,8位 reg [23:0] data_channel; // 数据通道,24位 reg [3:0] state; // 状态机变量 // 初始化 initial begin cmd_channel = 8'h03; // 读取FlashID的命令 state = 4'h0; // 状态机初始状态 end always @(posedge clk or posedge reset) begin if (reset) begin // 复位时将状态机恢复初始状态 state <= 4'h0; data_channel <= 24'h0; end else begin case(state) 4'h0: begin // 发送命令 data_channel <= {7'h0, cmd_channel}; state <= 4'h1; end 4'h1: begin // 接收FlashID data_channel <= {7'h0, flash_id[23:8]}; state <= 4'h2; end 4'h2: begin // 完成读取 data_channel <= 24'h0; state <= 4'h0; end endcase end end assign flash_id = data_channel; // 输出Flash ID endmodule ``` 在上述代码中,我们定义了一个`spi_flash`模块,其中包含一个时钟信号`clk`、一个复位信号`reset`和一个输出的Flash ID信号`flash_id`。我们使用一个简单的4位状态机来管理SPI协议读取过程。初始化时,状态机处于初始状态。在每个时钟周期的上升沿,状态机按照以下步骤进行工作: - 状态0:发送命令。将命令放入数据通道,并准备切换到下一个状态。 - 状态1:接收Flash ID。根据Flash ID的位宽,将数据通道中的数据放入对应的位置,并准备切换到下一个状态。 - 状态2:完成读取。将数据通道清零,并准备切换到初始状态。 最后,我们将数据通道的值赋给Flash ID信号输出。 这样,通过使用这段Verilog代码,我们可以实现在SPI协议读取FlashID

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值