STM32F407 SPI配置和时序图讲解(二)

上节讲了SPI的基本配置,这节主要讲解如何看时序图,SPI数据到底是如何传输的。
SPI初始化后,就可以开始向对象发送数据了,但是要发送数据给W25Q128模块,需要按照它的时序图来发送(个人用的是W25Q128模块

W25Q128模块简介
W25Q128是一款常见的串行闪存存储器模块,属于Winbond Electronics生产的产品系列之一。以下是关于W25Q128模块的一些基本信息:
存储容量:W25Q128模块的存储容量为128Mb(兆比特)或16MB(兆字节)。它可以存储大量的数据,如代码、配置文件、图像、音频等。
SPI接口:W25Q128模块使用SPI(Serial Peripheral Interface)接口进行数据通信。SPI是一种常用的串行通信协议,可以实现高速的数据传输。
供电电压:W25Q128模块通常工作在3.3V的供电电压下,但也支持1.8V的供电电压。在使用时需要根据实际情况选择适当的供电电压。
特性:W25Q128模块具有快速的读写速度和低功耗特性。它支持多种读写模式,包括普通读写、高速读写、扇区擦除、块擦除等。

什么是时序图
时序图(Timing diagram)是一种用于描述系统内部或组件之间时序关系的图形表示方法。它展示了信号、事件或数据在时间轴上的传输和交互情况,帮助我们理解系统各个组件之间的时序行为。
时序图通常由水平的时间轴垂直的信号线组成。时间轴表示时间的流逝,从左到右逐渐增加。信号线代表电平、状态或数据的变化。通过在时间轴上绘制信号线的高低变化、起始时间和持续时间等信息,时序图能够清晰地展示不同组件之间的交互和同步。
时序图可以用于多种场景,如硬件电路设计软件系统调试通信协议分析等。它有助于可视化和验证系统的时序约束、响应时间、并发操作、数据流动和交互行为,帮助开发人员了解系统执行的顺序以及事件之间的依赖关系

上述只是简单介绍一下这个模块和时序图,下面开始正题!
每一款模块都会给你原理图和数据手册以及时序图

以下是W25Q128获取厂商ID的时序图

在这里插入图片描述
这是两段时序图,因为一张图放不下,截取两部分,可以仔细观察一下看不看得懂,下面是拼接解析之后的时序图
在这里插入图片描述
第一步:先看是谁的时序图,这是W25Q128的时序图,W25Q128相对于stm32芯片来说是从机,所以最左边四个引脚要搞清楚是什么,再stm32芯片中如何配置
第二步:再看数据如何传输的(高位先出还是地位先出)这对下面编程来说很重要,对于这幅时序图来说是高位先出(MSB)(看图可以找到MSB,左上)
第三步:看第一段,各个引脚的开始状态是什么,高电平还是低电平
第四步:看第二段,片选引脚拉低,开始工作,时钟线开始变化
第五步:看第三段,主机(stm32)开始发送指令(每个指令都不相同),这里可以看到查询厂商ID指令为:0x09(小图也标注了它的二进制),这就是数据的传输过程。
注释:为何看出来是主机发送,因为数据变化的MOSI(主输出从输入)这一根数据线,而MISO这跟数据线没有变化,只是起始电平,目前不管它
第六步:看第四段,主机发送一个24位地址值,每个指令的地址值不一样,具体看时序图
第七步:看第五段,主机随便发送数据(随便发不用管是什么,因为从机不会处理这些数据)发这些数据就是为了保持同步(SPI为高速串行全双工同步通信),我们发送这些数据的同时,从机就会给我们反发(因为现在MISO这跟数据线开始变化了)厂商号和设备号
第八步:看第六段,片选引脚拉高,回到空闲状态通信结束

现在看懂了,来看看代码如何编写

//SPI发送/接收1字节数据函数(自己对SPI接收和发送库函数做的简单封装)
uint8_t Spi_Send_And_Recv_Byte_Data(uint8_t byte)
{
	//判断SR寄存器(状态寄存器)1---->发送缓冲区为空(具体查看寄存器手册)
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
	
	//通过spi发送数据
	SPI_I2S_SendData(SPI1,byte);	//STM32F4系列 SPI标准发送库函数
	
	//再次判断SR寄存器
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);
	
	//返回spi接收值
	return SPI_I2S_ReceiveData(SPI1);	//STM32F4系列 SPI的标准接收库函数
	
}


//读取厂商ID和设备ID
void Read_W25Q128_ID(uint8_t *M_ID,uint8_t *D_ID)
{
	//cs引脚拉低开始通信
	GPIO_PIN_NSS(0);	//自己定义的片选宏(不会宏定义的私发我,我亲自教,我不相信学到SPI还有人不会C语言宏定义)
	
	//发送获取ID号指令
	Spi_Send_And_Recv_Byte_Data(0x90);	//读取设备号ID指令(0x90)
	
	//发送24位地址(因为SPI发送函数只能发一个字节,所以分三次发送)
	//记住这个是高位先出,所以要先发高位,这些细节我在上面时序图解析全部提出来了,注意看!
	uint32_t Address = 0x000000;//要发送的地址值
	Spi_Send_And_Recv_Byte_Data((uint8_t)Address>>16);	//发送高八位
	Spi_Send_And_Recv_Byte_Data((uint8_t)Address>>8);	//发送中间八位
	Spi_Send_And_Recv_Byte_Data((uint8_t)Address);		//发送低八位
	
	//接收设备号(发一些无用数据是为了保持通信同步)
	*M_ID = Spi_Send_And_Recv_Byte_Data(0xFF);//这里就是发送一些无用数据,从机给我们反发ID号
	*D_ID = Spi_Send_And_Recv_Byte_Data(0xFF);//这里就是发送一些无用数据,从机给我们反发ID号
	
	//cs拉高电平,结束通信
	GPIO_PIN_NSS(1);
}

上面所有的细节和需要主义的地方我都标注清楚了
是不是很简单,只要看得懂原理图,写代码就是严格按照步骤来写就好

下面再来几张时序图,这次只有简单的解析,看得懂的可以看看每张时序图的代码步骤,看不懂的小伙伴可以继续看看上面的获取厂商ID的具体解析

解除写保护时序图
在这里插入图片描述
代码如下:

/*开启/关闭写使能(我把解除写保护和添加写保护封装在一起了,
虽然上面只有一张时序图,但是大家应该也能猜出添加写保护是什么样的)*/
void Spi_Write_Cmd(FunctionalState NewState)
{
	//cs拉低,通信开始
	GPIO_PIN_NSS(0);
	
	//判断开始写使能还是关闭
	  assert_param(IS_FUNCTIONAL_STATE(NewState));
	  if (NewState != DISABLE)
	  {
		  //发送 使能写权限 指令
		  Spi_Send_And_Recv_Byte_Data(0x06);
	  }
	  else
	  {
		  //发送 不使能写权限 指令 
		  Spi_Send_And_Recv_Byte_Data(0x04);
	  }
	
	//cs拉高,通信结束
	GPIO_PIN_NSS(1);
}

很简单,下面看看发送数据的时序图(看不清的可以点开放大看,支持下载)
在这里插入图片描述
看看代码:

//写入数据函数(页编程)
void Page_Program(uint32_t Addr,uint8_t *data,uint8_t size)
{
	//cs拉低,通信开始
	GPIO_PIN_NSS(0);
	
	//发送写指令
	Spi_Send_And_Recv_Byte_Data(0x02);
	
	//发送24位地址
	Spi_Send_And_Recv_Byte_Data((uint8_t)(Addr>>16));
	Spi_Send_And_Recv_Byte_Data((uint8_t)(Addr>>8));
	Spi_Send_And_Recv_Byte_Data((uint8_t)Addr);
	
	//发送要写入的数据
	for(int i=0; i<size; i++)
		Spi_Send_And_Recv_Byte_Data(data[i]);

	//查看状态寄存器是否BUSY位(这个就是看具体的寄存器,这里想教会大家的是如何看懂时序图)
	//BUSY位在最低位,与上0x01则,如果是0则写入完成,退出循环
	while(Read_Status_Register1() & 0x01);	

	printf("Write data finsh\n");
	
	//cs拉高,通信结束
	GPIO_PIN_NSS(1);
}

最后留一张接收数据的时序图,个人解析过,大家也可以看看,这次没有代码,大家可以看看按照逻辑会不会自己写出步骤,代码就是调用函数罢了
在这里插入图片描述
代码就没必要看了,会步骤就好了,能自己看懂就可以了!
只要认真看了本章内容,就算是一个新手也能看得懂时序图,如果对您有什么帮助,请留下免费的赞,谢谢!!!
这几天会继续更新其他类型的时序图如何查看!!!