当前位置: 首页 > news >正文

江协科技STM32学习笔记(第10章 SPI通信)

第10章 SPI通信

10.1 SPI通信协议

10.1.1 SPI通信

SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线;

串行外设接口;

I2C无论是软件还是软件电路,设计的都还是比较复杂的,硬件上,我们要配置为开漏外加上拉的模式;软件上有很多功能和要求,比如一根通信线兼顾数据收发、应答位的收发、寻址机制的设计等等。通过这么多的设计,使得I2C的通信性价比非常高,I2C可以在消耗最低硬件资源的情况下,实现最多的功能。在硬件上,无论挂载多少个设备,都只需要两根通信线,在软件上,数据双向通信、应答位都可以实现,如果把通信协议比做人的话,那I2C就属于精打细算、思维灵活的人,既要实现硬件上最少的通信线,又要实现软件上最多的功能。I2C经过精心的设计,也确实实现了这么多功能。缺点就是由于I2C采用开漏外加上拉电阻的电路结构,使得通信线高电平的驱动能力比较弱,这就会导致,通信线由低电平变到高电平的时候,上升沿比较长,这会限制I2C的最大通信速度,所以I2C的标准模式,只有100KHz的时钟频率,I2C的快速模式,也只有400KHz;虽然I2C协议最后又通过改进电路的方式,设计出了高速模式,可以达到3.4MHz,但是高速模式目前普及模式不是很高,所以一般情况下,我们认为I2C的时钟速度最多就是400KHz,这个速度相比较I2C而言,还是慢了很多的。

SPI的优缺点:

(1)SPI传输更快,SPI协议并没有严格规定最大传输速度,这个最大传输速度取决于芯片厂商的设计需求,比如下图第一个图所示W25Q64存储器芯片,手册里写的SPI时钟频率,最大可达80Hz

,这比STM32F1的主频还要高;

(2)其次,SPI的设计比较简单粗暴,实现的功能没有I2C那么多,所以学习起来,SPI比I2C简单很多;

(3)SPI硬件开销比较大,通信线的个数比较多,并且通信过程中,经常会有资源浪费的现象,如果继续把通信协议比作一个人的话,SPI就属于富家子弟、有钱任性这类型的人。SPI不在乎花了多少钱,只在乎任务有没有最简单、最快速的完成。

四根通信线:SCKSerial Clock)、MOSIMaster Output Slave Input)、MISOMaster Input Slave Output)、SSSlave Select);

SCK:串行时钟线

MOSI:主机输出、从机输入

MISO:主机输入、从机输出

SS:从机选择

以上是SPI通信典型的引脚名称,当然在实际情况下,这些名称可能会有别的表述方式,比如SCK,有的地方可能叫做SCLK、CLK、CK;MOSI和MISO,有的地方可能直接叫做DO(Data Output)和DI(Data Input);SS有的地方也可能叫做NSS(Not Slave Select)、CS(Chip Select)。

同步,全双工;

首先既然是同步时序,肯定就得有时钟线了,SCK引脚就是用来提供时钟信号的,数据位的输出和输入,都是在SCK的上升沿或下降沿进行的,这样,数据位的收发时刻就可以明确的确定,并且,同步时序,时钟快点慢点,或者中途暂停一会儿,都是没问题的,这就是同步时序的好处。对照I2C总线,这个SCK,就相当于I2C的SCL,两者作用相同。

之后,SPI是全双工的协议,全双工,就是数据发送和数据接收单独各占一条线,发送用发送的线路,接收用接收的线路,两者互不影响,所以这里MOSI和MISO,就是分别用于发送和接收的两条线路,MOSI线,是主机输出、从机输入,如果是主机接在这条线上,那就是MO,主机输出;如果是从机接在这条线上,就是SI,从机输入。意思就是一条通信线,如果主机接在上面配置为输出,那从机肯定得配置为输入,才能接收主机得数据,主机和从机不能同时配置为输入或输出,不然就没法通信了,所以这条MOSI就是主机向从机发送数据的线路。MISO就是主机从从机接收数据的线路,这就是全双工通信的两根通信线,这两根线,加在一起就相当于I2C总线的SDA,当然I2C是一根线兼具发送和接收,是半双工,SPI是一根发送,一根接收,是全双工。全双工的好处就是简单高效,输出线就一直输出,输入线就一直输入,数据流的方向不会改变,也不用担心发送和接收没协调好冲突了。但是坏处就是多了一根线,会有通信资源的浪费。

支持总线挂载多设备(一主多从)。

SPI仅支持一主多从,不支持多主机。这一点,SPI没有I2C强大。

I2C实现一主多从的方式是,在起始条件之后,主机必须先发送一个字节进行寻址,用来指定我要跟哪个从机进行通信,所以I2C这里,要涉及分配地址和寻址的问题,但是SPI表示,你这太麻烦了,SPI直接再开辟了一条通信线,专门用来指定我要跟哪个从机进行通信,所以这条专门用来指定从机的通信线,就是这里的SS,从机选择线。并且这个SS可能不止一条,SPI的主机表示,我有几个从机,我就开几条SS,所有从机一人一根,我需要的时候,就控制接到你那根SS线。

SPI没有应答机制的设计,发送数据就是发送,接收数据就是接收,至于对面是不是存在,SPI是不管的。

第1个图是W25Q64,是一个Flash存储器, 可以看到这个模块的引脚,和刚才说的SPI通信典型引脚名称并不一样,这里CLK就是CK、DI和DO就是MOSI和MISO,DI到底是MOSI还是MISO,要看一下这个芯片的身份,这个芯片接在STM32上,应该是从机,所以这里的DI数数据输入,就是从机的数据输入SI,对应需要接在主机的MO上,所以这里的DI就是MOSI,另一个DO就是MISO了。一般在这种始终作为从机的设备上,可能会用DI和DO的简写,像STM32这种,可以进行身份转换的设备,一般都会把MOSI、MISO的全称写完整。CS片选就是SS从机选择了。

第2个图是利用SPI通信的OLED屏幕,上面的引脚也不是标准的名称。所以这个引脚需要查一下手册,手册里有些。

第3个图是一个2.4G无线通信模块,芯片型号是NRF24L01,这个芯片使用的就是SPI通信协议,要想使用这个芯片来进行无线通信,就需要用SPI来读写这个芯片。

第4个图就是常见的MicroSD卡了,这个SD卡官方的通信协议是SDIO,但是它也是支持SPI协议的,我们可以利用这个SPI,对这个SD卡进行读写操作。

10.1.2 SPI硬件电路

所有SPI设备的SCKMOSIMISO分别连在一起;

SCK;时钟线,时钟线完全由主机掌控,所以对于主机来说,时钟线为输出,对于所有从机来说,时钟线为输入,这样主机的同步时钟,就能送到各个从机了;

MOSI:主机输出从机输入,左边是主机,所以对应MO主机输出,下面三个都是从机,所以就对应SI,从机输入;数据传输方向是,主机通过MOSI输出,所有从机通过MOSI输出。

MISO:主机输入从机输出,左边是主机对应MI,下面三个从机对应SO,数据传输方向是,三个从机通过MISO输出,主机通过MISO输入。

主机另外引出多条SS控制线,分别接到各从机的SS引脚;

主机的SS都是输出,从机的SS都是输入,SS线是低电平有效的,主机想指定谁就把对应的SS输出线置低电平就行了。比如主机初始化之后,所有的SS都输出高电平,这样就是谁也不指定,当主机需要和比如从机1进行通信了,主机就把SS1线输出低电平,这样从机1就知道主机在找我,然后主机在数据引脚进行的传输,就只有从机1会响应。其它从机的SS线是高电平,所以它们都会保持默认,当主机和从机1通信完成后,就会把SS1置回高电平,这样从机1就知道,主机结束了和我的通信。同一时间,主机只能置一个SS为低电平,只能选中一个从机否则,如果主机选中多个从机,就会导致数据冲突,这就是SPI总线选择从机的方式。

输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。

推挽输出,高低电平均有很强的驱动能力,这将使得SPI引脚信号的下降沿,非常迅速,上升沿,也非常迅速,不想I2C那样,下降沿非常迅速,但是上升沿就比较缓慢了,得益于推挽输出的驱动能力,SPI的信号变化得快,自然就能达到更高得传输速度,一般SPI信号都能轻松地达到MHz的速度级别。I2C并不是不想使用更快的推挽输出,而是I2C要使用半双工,经常要切换输入输出,另外I2C又要实现多主机的时钟同步和总线仲裁,这些功能都不允许I2C使用推挽输出,不然I2C一不小心就短路了。所以I2C选择了实现更多的功能,自然就要放弃更强的性能了。对于SPI来说,首先SPI不支持多主机,然后SPI又是全双工,SPI的输出引脚始终是输出,输入引脚始终是输入,基本不会出现冲突,所以SPI可以大胆地使用推挽输出。不过SPI还是有一个冲突点的,就是MISO引脚,在这个引脚上可以看到主机一个是输入,但是三个从机全都是输出,如果三个从机都始终是推挽输出,势必会导致冲突,所以在SPI协议里,有一条规定,就是当从机的SS引脚为高电平,也就是从机未被选中时,它的MISO引脚,必须切换为高阻态,高阻态就相当于引脚断开,不输出任何电平,这样就能防止一条线上有多个输出,而导致的电平冲突的问题了,在SS为低电平时,MISO才允许变为推挽输出,这就是SPI对这个可能的冲突做出的规定。当然这个切换过程都是在从机里,我们一般都是写主机的程序,所以我们主机的程序中,并不需要关注这个问题。

SPI主机主导整个SPI总线,主机一般都是控制器来作,比如STM32,下面的SPI从机1、2、3就是挂载在主机上的从设备,比如存储器、显示屏、通信模块、传感器等等。左边SPI主机实际上引出了6根通信线,因为有3个从机,所以SS线需要3根,再加SCK、MOSI、MISO,就是6根通信线,当然SPI所有通信线都是单端信号,它们的高低电平都是相对GND的电压差。所以单端信号,所有的设备还需要共地,这里GND的线没画出来,但是是必须要接的,如果从机没有独立供电的话,主机还需要再额外引出电源正极VCC,给从机供电,这两根电源线,VCC和GND也要注意接好。

10.1.3 移位示意图

这个移位示意图是SPI硬件电路设计的核心,只要把这个移位示意图搞懂了,无论是硬件电路还是软件时序,理解起来都会更加轻松。

SPI基本收发电路,就是使用了这样一个移位的模型。左边是SPI主机,里面有一个8位的移位寄存器,右边是SPI从机,里面也有一个8位的移位寄存器。这里移位寄存器有一个时钟输入端,因为一般SPI都是高位先行的,所以每来一个时钟,移位寄存器都会向左进行移位,从机中的移位寄存器也是同理。移位寄存器的时钟源是由主机提供的,这里叫做波特率发生器,它产生的时钟驱动主机的移位寄存器进行移位,同时这个时钟也通过SCK引脚进行输出,接到从机的移位寄存器里,之后,上面移位寄存器的接法是,主机移位寄存器左边移出去的数据,通过MOSI引脚,输入到从机移位寄存器的右边,从机移位寄存器左边移出去的数据,通过MISO引脚,输入到主机移位寄存器的右边。

首先,我们规定,波特率发生器时钟的上升沿、所有移位寄存器向左移动一位,移出去的位放在引脚上;波特率发生器时钟的下降沿,引脚上的位,采样输入到移位寄存器的最低位,接下来,假设主机有个数据10101010要发送到从机,同时从机有个数据01010101要发送到主机,那我们就可以驱动时钟,先产生一个上升沿,这时,所有的位,就会往左移动一位,从最高位移出去的数字,就会放到通信线上(实际上是放到了输出数据寄存器),可以看到,此时MOSI数据是1,所以MOSI的电平就是高电平;MISO数据是0,所以MISO的电平就是低电平,这就是第一个时钟上升沿执行的结果。就是把主机和从机中,移位寄存器的最高位,分别放到MISO和MOSI的通信线上,这就是数据的输出。

之后时钟继续运行,上升沿之后,下一个边沿就是下降沿。在下降沿时,主机和从机内,都会进行数据采样输入, 也就是MOSI的1,会采样输入到从机这里的最低位,MISO的0,会采样输入到主机这里的最低位,这就是第一个时钟结束后的现象。

时钟继续运行,同样的操作。 

8个时钟以后,就实现了主机和从机一个字节的数据交换。实际上SPI的运行过程就是这样,SPI的数据收发,都是基于字节交换,这个基本单元来进行的,当主机需要发送一个字节,并且同时需要接收一个字节时,就可以执行以下字节交换的时序,这样主机要发送的数据跑到从机,主机要从从机接收的数据,跑到主机,这就完成了发送同时接收的目的。

如果只想发送,不想接收,仍然调用交换字节的时序,发送,同时接收,只是这个接收到的数据,我们不看它就行了。如果只想接收,不想发送,也是同理,调用交换字节的时序,发送,同时接收,只是我们回随便发送一个数据,只要能把从机的数据置换过来就行了,我们读取置换过来的数据就是接收到了,随便塞过去的数据,从机也不会去看它,当然这个随便的数据不会真的随便发,一般在接收的时候,统一发送0x00或0xFF,去跟从机换数据。

10.1.4 SPI时序基本单元

起始条件:SS从高电平切换到低电平

终止条件:SS从低电平切换到高电平

数据传输的基本单元是建立在移位模型上的,并且这个模型什么时候移位?是上升沿移位还是下降沿移位?SPI并没有限定死,给了我们可以配置的选择,这样的话SPI就可以兼容更多的芯片。SPI有两个可以配置的位,分别叫做CPOL(Clock Polarity)、时钟极性CPHA(Clock Phase),每一位都可以配置为1或0,总共组合起来,就有模式0、模式1、模式2、模式3这4中模式。模式虽然多,但功能都是一样的。

实际应用中,模式0的应用是最多的。模式0和模式1的区别就在于模式0把数据变化的时机给提前了。

交换一个字节(模式0

CPOL=0:空闲状态时,SCK为低电平

CPHA=0SCK第一个边沿移入数据,第二个边沿移出数据

MISO起始和终止位高阻态。

交换一个字节(模式1

CPOL=0:空闲状态时,SCK为低电平

CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据(或叫做进行采样)

 交换一个字节(模式2)

CPOL=1:空闲状态时,SCK为高电平

CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

 交换一个字节(模式3

CPOL=1:空闲状态时,SCK为高电平

CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

10.1.5 SPI时序 

SPI中,通常使用的是指令码加读写数据的模型,这个过程就是SPI起始后,第一个交换发送给从机的数据,一般叫做指令码,在从机中,对应的会定义一个指令集,当我们需要发送什么指令时,就可以在起始后第一个字节,发送指令集里面的数据,这样就是指导从机完成相应的功能了。不同的指令,可以有不同的数据个数,有的指令,只需要一个字节的指令码就可以完成,比如W25Q64的写使能、写失能等指令。而有的指令,后面就需要再跟要读写的数据,比如W25Q64的写数据、读数据等。写数据指令后面就得跟上,我要在哪里写,我要写什么;读数据指令后面就得跟上我要在哪里读,我要读到的是什么。这就是指令码加读写数据的模型,在SPI从机的芯片手册里,都会定义好指令集,什么指令对应什么功能;什么指令后面得跟上什么数据。

发送指令

向SS指定的设备,发送指令(0x06

指定地址写

向SS指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0])下,写入指定数据(Data

指定地址读

向SS指定的设备,发送读指令(0x03), 随后在指定地址(Address[23:0])下,读取从机数据(Data

10.2 W25Q64简介  

10.2.1 W25Q64简介  

W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景;

SPI串行通信,通信引脚比较少,协议也很简单,这个芯片的硬件接线也不麻烦,就VCC、GND接上电,剩下的全都可以接GPIO,基本不需要其它电路;

存储器分为易失性存储器和非易失性存储器,易失性存储器一般就是SRAM、DRAM等,非易失性存储器一般就是E2PROM、Flash等,它们最主要的区别,简而言之,就是存储的数据是否掉电不丢失,非易失性存储器就是数据不容易丢失的存储器,也就是数据掉电不丢失。所以存储在W25Qxx芯片里的数据,在断电重启后,数据仍然保持原样。

字库存储;可以用这个数据来存储汉字字库的点阵数据,在显示某个数据之前,先读取芯片查询字库,再在显示屏上显示对应的点阵数据,这样就能让显示屏任意显示中文了。

固件程序存储就相当于直接把程序文件下载到外挂芯片里,需要执行程序的时候,直接读取外挂芯片的程序文件来执行。这就是XIP(eXecute In Place),就地执行。比如我们电脑里的BIOS固件,就可以存储在这个W25Q系列芯片里。

存储介质:Nor Flash(闪存)

Flash就是闪存存储器,像我们STM32里的程序存储器、U盘、电脑里的固态硬盘等,使用的都是Flash闪存,闪存分为Nor Flash和Nand Flash,两者各有优势和劣势,适用领域不同。

时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)

我们这个芯片使用的SPI通信,其中SPI的SCK线,就是时钟线,这个时钟线的最大频率是80MHz,这个频率相比较STM32,是非常快了。所以我们之后写程序的时候,翻转引脚,就不需要加延时了,即使不掩饰,这个GPIO的翻转频率,也不可能达到80MHz,所以可以放心使用了。

160MHz是双重SPI模式等效的频率,320MHz是四重SPI模式等效的频率。

双重SPI和四重SPI:MOSI用于发送,MISO用于接收,是全双工通信,在只发或只收的时候有资源浪费,但是在这个W25Q芯片厂商不忍心浪费,所以就对SPI做出了一些改进,就是我在发的时候,我可以同时用MOSI和MISO发送,在收的时候,也可以同时用MOSI和MISO接收,MOSI和MISO同时兼具发送和接收的功能。一个SCK时钟,同时发送或接收2位数据,这就是双重SPI模式,一个时钟收发两位,相比较一位一位的普通SPI,数据传输率就是二倍了,所以在双重SPI模式下,等效的时钟频率就是160MH。但实际的SCK频率,最大还是80MHz,只是一个时钟发两位而已。

在我们的芯片里还有两位引脚,一位是WP写保护,另一个是HOLD,这两个引脚如果不需要的话,也可以拉过来充当数据传输引脚,加上MOSI和MISO,这就可以4个数据位同时收发了。

存储容量(24位地址):

W25Q40    4Mbit / 512KByte

W25Q80    8Mbit / 1MByte

W25Q16    16Mbit / 2MByte

W25Q32    32Mbit / 4MByte

W25Q64    64Mbit / 8MByte

W25Q128  128Mbit / 16MByte

W25Q256  256Mbit / 32MByte

这个芯片使用的是24位地址,是3个字节,因为我们在进行读写的时候,肯定得把每个字节都分配一个地址,这样才能找到它们。

24位地址能够提供(2^24/1024/1024=16MB)的寻址空间。

W25Q256分为3字节地址模式和4字节地址模式,在3字节地址模式下,只能读取前16MB的数据,后面16MB,3个字节的地址够不着,要想读写到所有鵆单元,可以进入4字节地址的模式。

10.2.2 硬件电路

引脚

功能

VCCGND

电源(2.7~3.6V

CSSS

SPI片选

CLKSCK

SPI时钟

DIMOSI

SPI主机输出从机输入

DOMISO

SPI主机输入从机输出

WP

写保护

HOLD

数据保持

WP(Write Protect): 配合内部的寄存器配置,可以实现硬件的写保护,写保护低电平有效。WP接低电平,保护住,不让写,WP接高电平,不保护,可以写。

HOLD:如果在进行正常读写时, 突然产生中断,然后想用SPI通信线去操控其它器件,这时如果把CS置回高电平,那时序就终止了,但如果又不想终止总线,又想操作其它器件,这就可以HOLD引脚置低电平,这样芯片就HOLD住了。芯片释放总线,但是芯片时序也不会终止,它会记住当前的状态。当操作完其它器件时,可以回过来,HOLD置回高电平,然后继续HOLD之前的时序。相当于SPI总线进了一次中断,并且还在中断里,还可以用SPI干别的事情。

10.2.3 W25Q64框图

10.2.4 Flash操作注意事项

写入操作时:

        写入操作前,必须先进行写使能;

这是一种保护操作,防止误操作,就像手机一样,先解锁再操作。

        每个数据位只能由1改写为0,不能由0改写为1;

Flash并没有像RAM那样的直接完全覆盖改写的能力,比如在某个字节的存储单元里,存储了0xAA这个数据,对应的二进制位就是1010 1010,如果我直接在在这个存储单元写入一个新的数据,比如我再次写入一个0x55,写完之后这个存储单元里存的并不是0x5.因为0x55的二进制是0101 0101,当这个0101 0101要覆盖原来的1010 1010时,就会受到这条规定的限制,所以这里写入0101 0101之后,一次来看,最高位由原来的1改写为0是可以的,所以写入之后,新的最高位就是0,但是第二位原来是0,现在想改成1,这是不行的,所以写入之后新的第二位还是0,这样最终就会变成0x00,为了弥补这个缺陷,因为有了下一条规定。

        写入数据前必须先擦除,擦除后,所有数据位变为1;

因此,在Flash中,空白部分是0xFF。如果读取的是0xFF,那说明这部分有可能是还没有写入数据的空白空间。

        擦除必须按最小擦除单元进行;

这个应该是为了成本而做的妥协,Flash不能指定某一个字节单元进行擦除,要擦就得一片一起擦,在我们这个芯片里可以选择整个芯片一起擦除,也可以选择按块擦除或者按扇区擦除。再小就没有了,所以最小的擦除单元,是一个扇区。一个扇区是4Kb,就是4096个字节。擦除时,如果不想丢失数据,只能先把这4096个字节的数据读取出来,再把4096个字节的扇区擦掉,改写完读出来的数据之后,再把4096个字节全部写回去。实际情况下,我们还有别的方法来优化这个流程,比如,上电后,我们先把Flash的数据读出来,放到RAM里,当有数据变动时,我们统一把数据备份到Flash里。或者我把使用频繁的扇区,放在RAM里,当使用频率降低时,我再把整个扇区被分到Flash里。或者如果数据量确实非常少,只想存几个字节的参数就行了,那直接1个字节占一个扇区就行。

        连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入;

在写入的时候,一次性不能写太多,一个写入时序,最多只能写一页的数据,也就是256字节。这是因为有一个页缓存区,它只有256字节。为什么会有缓存区呢?是因为Flash的写入太慢了,跟不上SPI的频率,所以写如的数据,会先放到RAM里暂存,等时序结束之后,芯片再慢慢地把数据写入到Flash里,所以这里会有一个限制,每个时序,最多写入一页的数据。这个页缓存区,是和Flash的页对应的,必需得从页的起始位置开始,才能最大写入256字节。如果从页中间的地址开始写,那写到页尾时,这个地址就会跳回到页首,这会导致地址错乱。所以在进行多字节写入时,一定要注意这个地址范围,不能跨越页的边沿,否则会地址错乱。

        写入操作结束后,芯片进入忙状态,不响应新的读写操作。

我们的写入操作都是对缓存区进行的,等时序结束后,芯片还要搬砖一段时间,所以每次写入操作后,都有一段时间的忙状态,在这个状态下,我们不要进行新的读写操作,否则,芯片是不会相应我们的,要想知道芯片什么时候结束忙状态了。我们可以使用读状态寄存器的指令,看一下状态寄存器的BUSY位是否为1,BUSY位为0时,芯片就不忙了,我们再进行操作。

另外,这个写入操作包括上面的擦除,在发出擦除指令后,芯片也会进入忙状态,我们也得等忙状态结束后,才能进行后续操作。

读取操作时:

        直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取。

Flash作为一种掉电不丢失的存储器,为了保证掉电不丢失这个特性,同时还要保证存储容量足够大、成本足够低,所以Flash存储器会在其它地方,比如操作的便捷性等做一些妥协和让步。Flash的写入和读取并不像RAM那样简单直接,RAM是指哪打哪,想在哪写就在哪写,想写多少就写多少,并且RAM是可以覆盖写入的。比如原来RAM里有个数据0xAA,之后我直接再写入一个新的数据0x55,那RAM的数据就变成0x55了。

10.2.5 器件手册

(1)芯片引脚定义及描述

 (2)芯片系统框图

 (3)SPI操作

(4)写保护逻辑

(5)状态寄存器 

状态寄存器示意图: 

(6) 写保护配置表

(7)指令集

指令翻译
Write Enable写使能
Write Disable写失能
Read Status Register-1读状态寄存器1
Page Progam页编程
Block Erase(64KB)按64KB的块擦除
Block Erase(32KB)按32KB的块擦除
Sector Erase(4KB)扇区擦除
Chip Erase整片擦除
JEDEC ID读ID号

指令翻译
Read Data读取数据

10.3 软件SPI读写W25Q64

10.3.1 硬件电路

 10.3.2 软件部分

(1)复制《OLED显示屏》工程并改名为《软件SPI读写W25Q64》

(2)添加驱动文件

(3)MySPI.c

#include "stm32f10x.h"                  // Device header/*从机选择函数*/
void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);           //SS端接在PA4引脚上
}/*SCK控制函数*/
void MySPI_W_SCK(uint8_t BitValue)
{GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);           //SCK端接在PA5引脚上}/*MOSI控制函数*/
void MySPI_W_MOSI(uint8_t BitValue)
{GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);           //MOSI端接在PA7引脚上
}/*MISO控制函数*/
uint8_t MySPI_R_MISO(void)
{return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);                  //MISO端接在PA6引脚上,STM32读取W25Q64数据
}/*软件SPI的初始化函数*/
void MySPI_Init(void)
{	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);        GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;     //输出引脚配置为推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;        //输入引脚配置为上拉输入GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);MySPI_W_SS(1);                                    //初始化时给ss置高电平,默认不选中从机MySPI_W_SCK(0);                                   //使用模式0,默认是低电平。
}	/*起始条件函数*/
void MySPI_Start(void)
{MySPI_W_SS(0); 
}	
/*终止条件函数*/
void MySPI_Stop(void)
{MySPI_W_SS(1); 
}	
/*交换字节函数,这种方法使用掩码依次提出每一位,不会改变传入参数本身*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t i,ByteReceive = 0x00;                 //用来接收字节for(i=0;i<8;i++){MySPI_W_MOSI(ByteSend & (0x80 >> i));       //发送第i位MySPI_W_SCK(1);                           //产生上升沿,程序把MOSI总线上的数据(ByteSend & 0x80)读走if (MySPI_R_MISO()==1){ByteReceive |= (0x80>> i);}MySPI_W_SCK(0);                           //产生下降沿,主机发送下一位}return ByteReceive;
}/*交换字节函数,这种方法效率高,但是ByteSend在移位过程中改变了*/
//uint8_t MySPI_SwapByte(uint8_t ByteSend)
//{
//	uint8_t i,ByteReceive = 0x00;                 //用来接收字节
//	for(i=0;i<8;i++)
//	{
//		MySPI_W_MOSI(ByteSend & 0x80);            //发送最高位
//		ByteSend <<=1;                            //次高位向左移位,变成最高位,准备下一次发送         
//		MySPI_W_SCK(1);                           //产生上升沿,程序把MOSI总线上的数据(ByteSend & 0x80)读走
//		if (MySPI_R_MISO()==1){ByteSend |= 0x01;}
//		MySPI_W_SCK(0);                           //产生下降沿,主机发送下一位
//	}
//	return ByteReceive;
//}

(4)MySPI.h

#ifndef __MYSPI_
#define __MYSPI_void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);#endif

(5)W25Q64_lns.h

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3#define W25Q64_DUMMY_BYTE							0xFF#endif

(6)W25Q64.c

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_lns.h"/*W25Q64初始化函数*/
void W25Q64_Init(void)
{MySPI_Init();
}/*读取ID号函数*/
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)
{MySPI_Start();MySPI_SwapByte(W25Q64_JEDEC_ID);             //发送读ID号的指令*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);      //随便给从机发一个东西,没有意义,目的就是把从机的数据置换过来,获取到厂商ID*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);      //获取设备ID的高8位*DID <<= 8;*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);     //获取设备ID的低8位MySPI_Stop();
}/*写使能*/
void W25Q64_WriteEnable(void)
{MySPI_Start();MySPI_SwapByte(W25Q64_WRITE_ENABLE); MySPI_Stop();
}/*状态获取函数*/
void W25Q64_WaitBusy(void)
{uint32_t Timeout;MySPI_Start();MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);        //获取寄存器状态指令Timeout = 100000;while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)       //等待Busy状态结束{Timeout--;if(Timeout == 0){break;              //超时退出}}MySPI_Stop();
}/*页编程函数*/
void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t Count)
{uint16_t i;W25Q64_WriteEnable();                //写入操作前,都必须进行写使能MySPI_Start();MySPI_SwapByte(W25Q64_PAGE_PROGRAM);MySPI_SwapByte(Address >> 16);MySPI_SwapByte(Address >> 8);MySPI_SwapByte(Address);for(i=0;i<Count;i++){MySPI_SwapByte(DataArray[i]);}MySPI_Stop();W25Q64_WaitBusy();
}/*扇区擦除函数*/
void W25Q64_SectorErase(uint32_t Address)
{W25Q64_WriteEnable();                //写入操作前,都必须进行写使能MySPI_Start();MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);MySPI_SwapByte(Address >> 16);MySPI_SwapByte(Address >> 8);MySPI_SwapByte(Address);MySPI_Stop();W25Q64_WaitBusy();
}/*读取数据函数*/
void W25Q64_ReadData(uint32_t Address,uint8_t *DataArray,uint32_t Count)
{uint32_t i;MySPI_Start();MySPI_SwapByte(W25Q64_READ_DATA);MySPI_SwapByte(Address >> 16);MySPI_SwapByte(Address >> 8);MySPI_SwapByte(Address);for(i=0;i<Count;i++){DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);}	MySPI_Stop();
}

(7)W25Q64.h

#ifndef __W25Q64_
#define __W25Q64
void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address,uint8_t *DataArray,uint32_t Count);
#endif

(8)main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"                      // 调用延时头文件
#include "OLED.h"
#include "W25Q64.h"uint8_t MID;       //厂商ID
uint16_t DID;      //设备ID
uint8_t ArrayWrite[] = {0x01,0x02,0x03,0x04};
uint8_t ArrayRead[4];int main(void)
{OLED_Init();                                 // 初始化OLED屏幕W25Q64_Init();OLED_ShowString(1,1,"MID:   DID:");OLED_ShowString(2,1,"W:");OLED_ShowString(3,1,"R:");W25Q64_ReadID(&MID,&DID);OLED_ShowHexNum(1,5,MID,2);OLED_ShowHexNum(1,12,MID,4);
//	W25Q64_SectorErase(0x000000);
//	W25Q64_PageProgram(0x000000,ArrayWrite,4);W25Q64_ReadData(0x000000,ArrayRead,4);OLED_ShowHexNum(2,3,ArrayWrite[0],2);OLED_ShowHexNum(2,6,ArrayWrite[1],2);OLED_ShowHexNum(2,9,ArrayWrite[2],2);OLED_ShowHexNum(2,12,ArrayWrite[3],2);OLED_ShowHexNum(3,3,ArrayRead[0],2);OLED_ShowHexNum(3,6,ArrayRead[1],2);OLED_ShowHexNum(3,9,ArrayRead[2],2);OLED_ShowHexNum(3,12,ArrayRead[3],2);while(1)        {	}
}

10.4  STM32 SPI通信外设

10.4.1 SPI外设简介

STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担;

可配置8/16位数据帧、高位先行/低位先行;

最常用的是8位数据帧,高位先行。

时钟频率: f_{PCLK} / (2, 4, 8, 16, 32, 64, 128, 256);

时钟频率一般体现的是传输速度、单位是Hz或者bit/s。PSI的时钟,就是由f_{PCLK}分频得来的,

PCLK(Peripheral Clock)就是外设时钟,APB2的PCLK就是72MHz,APB1的PCLK就是36MHz。

支持多主机模型、主或从操作;

可精简为半双工/单工通信;

支持DMA;

兼容I2S协议;

音频传输协议。

STM32F103C8T6 硬件SPI资源:SPI1SPI2。

SPI1是APB2的外设,SPI2是APB1的外设。

10.4.2 SPI框图

10.4.3 SPI基本结构

核心部分就是数据寄存器和移位寄存器了, 上图所画的是左移,高位移出去,通过GPIO,到MOSI,从MOSI输出,显然就是SPI的主机,之后移入的数据,从MISO进来,通过GPIO到移位寄存器的低位,这样循环8次,就能实现主机和从机交换一个字节,然后TDR行业RDR的配合,可以实现连续的数据流。另外,TDR数据整体转入移位寄存器的时刻,置TXE标志位;移位寄存器整体转入RDR的时刻,置RXNE标志位。

10.4.4 主模式全双工连续传输

连续传输、传输更快,但是操作起来相对复杂。

10.4.5 非连续传输

10.4.6 软件/硬件波形对比

 10.5 硬件SPI读写W25Q64

10.5.1 硬件电路

10.5.2 软件部分

(1)复制《软件SPI读写W25Q64》并更改工程名为《硬件SPI读写W25Q64》 

(2)修改“MySPI.c”,其它文件不变。

#include "stm32f10x.h"                  // Device header/*从机选择函数*/
void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);           //SS端接在PA4引脚上
}/*软件SPI的初始化函数*/
void MySPI_Init(void)
{	/*初始化GPIO*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;     //输出引脚配置为推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;     //输出引脚配置为复用推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;     //输出引脚配置为上拉输入模式GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);/*初始化GPIO外设*/SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;     //指定当前设备为主机SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;       //配置双线全双工模式SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;  //配置8位数据帧SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB ;  //选择高位先行SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;   // 配置SCK的时钟频率SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;       //时钟极性空闲时默认为低电平SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;     //设置第一个边沿开始采样,上面两个参数将SPI配置成模式0SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;        //这个外设的NSS引脚一般不会用到,所以一般选择软件NSS就可以了SPI_InitStructure.SPI_CRCPolynomial = 7;         //CRC校验的默认参数SPI_Init(SPI1,&SPI_InitStructure);SPI_Cmd(SPI1,ENABLE);                            //使能SPI外设MySPI_W_SS(1);                                   //默认给SS输出高电平,不选中从机
}	/*起始条件函数*/
void MySPI_Start(void)
{MySPI_W_SS(0); 
}	
/*终止条件函数*/
void MySPI_Stop(void)
{MySPI_W_SS(1); 
}	
/*交换字节函数,这种方法使用掩码依次提出每一位,不会改变传入参数本身*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)!= SET);        //监测TXE标志位是否为1,直到其等于1,卡死几率不大SPI_I2S_SendData(SPI1,ByteSend);             //ByteSend发送到TDR,之后转运到移位寄存器,生成波形自动完成while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)!= SET);   //RXNE为1,表示大收到1个字节,同时也表示发送的时序产生完成了return SPI_I2S_ReceiveData(SPI1);            //读取RDR
}

相关文章:

江协科技STM32学习笔记(第10章 SPI通信)

第10章 SPI通信 10.1 SPI通信协议 10.1.1 SPI通信 SPI&#xff08;Serial Peripheral Interface&#xff09;是由Motorola公司开发的一种通用数据总线&#xff1b; 串行外设接口&#xff1b; I2C无论是软件还是软件电路&#xff0c;设计的都还是比较复杂的&#xff0c;硬件…...

力扣热题100_回溯_22_括号生成

文章目录 题目链接解题思路解题代码 题目链接 22. 括号生成 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[“((()))”,“(()())”,“(())()…...

【k8s】ubuntu24.04 containerd 手动从1.7.15 换为1.7.20

24.04的这个应该是apt 安装的1.7.20-1 root@k8s-master-pfsrv:~# sudo apt update && sudo apt install containerd.io -y 命中:1 http://mirrors.aliyun.com/docker-ce/linux/ubuntu noble InRelease 命中:2 https://dl.google.com/linux/chrome/deb stable InRelease…...

Java二十三种设计模式-备忘录模式(19/23)

本文深入探讨了备忘录模式&#xff0c;从定义、组成、实现到使用场景、优缺点、与其他模式的比较&#xff0c;以及最佳实践和替代方案&#xff0c;全面解析了如何在软件开发中有效地保存和恢复对象状态&#xff0c;以支持复杂的撤销操作和历史状态管理。 备忘录模式&#xff1a…...

js一些杂乱理解

js 的值类型和引用类型 引用类型:object,array,function值类型:诸如number,stringboolean,null,Undefined,Symbol js使用变量访问对象属性示例 var myDog "Hunter"; var dogs { Fido: "Mutt", Hunter: "Doberman", Snoopie: "Beagle&q…...

机器学习 之 线性回归算法

目录 线性回归&#xff1a;理解与应用 什么是线性回归&#xff1f; 一元线性回归 正态分布的重要性 多元线性回归 实例讲解 数据准备 数据分析 构建模型 训练模型 验证模型 应用模型 代码实现 线性回归&#xff1a;理解与应用 线性回归是一种广泛使用的统计方法&…...

ThreadLoad如何防止内存溢出

优质博文&#xff1a;IT-BLOG-CN 从 ThreadLocalMap看 ThreadLocal使用不当的内存泄漏问题 【1】基础概念 &#xff1a; 首先我们先看看ThreadLocalMap的类图&#xff0c;我们知道 ThreadLocal只是一个工具类&#xff0c;他为用户提供get、set、remove接口操作实际存放本地变…...

2024.8.19 学习记录 —— 作业

一、TCP机械臂测试 #include <myhead.h>#define SER_PORT 8888 // 与服务器保持一致 #define SER_IP "192.168.0.114" // 服务器ip地址int main(int argc, const char *argv[]) {// 创建文件描述符打开键盘文件int fd open("/dev/input/event1…...

Java 阿里云视频直播开发流程

首先来看一下直播效果 推流工具有很多种&#xff08;例如OBS、阿里云直播Demo推流、等等&#xff0c;我用的是芯象导播&#xff09;阿里播放器地址 一、直播基础服务概述 官方文档说明 二、直播域名配置需要两个域名&#xff08;推流域名、播流域名&#xff09; 官方文档说…...

SQLite 轻量级的嵌入式关系型数据库的替代软件

SQLite 是一个轻量级的嵌入式关系型数据库&#xff0c;由于其简单易用和跨平台的特性&#xff0c;被广泛应用于各种应用程序中。以下是一些可作为SQLite替代品的数据库软件或可视化管理工具&#xff1a; 1. **SQLiteStudio**&#xff1a;这是一个免费、开源的跨平台SQLite数据…...

Flutter-自适用高度PageView

需求 在 Flutter 中&#xff0c;PageView 是一个非常常用的组件&#xff0c;能够实现多个页面的滑动切换。然而&#xff0c;默认的 PageView 高度是固定的&#xff0c;这在展示不同高度的页面时&#xff0c;可能会导致不必要的空白或内容裁剪问题。为了使 PageView 能够根据每…...

群晖NAS本地搭建可远程交互的大型语言模型LLM聊天机器人

文章目录 前言1. 拉取相关的Docker镜像2. 运行Ollama 镜像3. 运行Chatbot Ollama镜像4. 本地访问5. 群晖安装Cpolar6. 配置公网地址7. 公网访问8. 固定公网地址 前言 本文主要分享如何在群晖NAS本地部署并运行一个基于大语言模型Llama 2的个人本地聊天机器人并结合内网穿透工具…...

TypeScript 构建工具之 webpack

在实际开发中&#xff0c;直接使用TypeScript 编译器的情况不多。 在项目中&#xff0c;需要使用构建工具对代码进行打包&#xff0c;不可能脱离项目使用TypeScript 编译器单独打包TypeScript 。 那如何将 webpack 和 TypeScript 进行集成&#xff1f; 参考文档&#xff1a; w…...

conda环境下在pycharm中调试scrapy项目

前提条件 已经创建好了conda环境已经安装好了scrapy框架项目初始化完成 编写一个爬虫脚本 import scrapyclass StackOverflowSpider(scrapy.Spider):name stackoverflowstart_urls [http://stackoverflow.com/questions?sortvotes]def parse(self, response):print("…...

contenteditable=“true“的标签限制字数的时候修改光标位置

contenteditable"true"的标签限制字数的时候修改光标位置 有时候input和textarea并不能完全满足ui需求&#xff0c;这个时候我们就用contenteditable"true"来将别的标签修改为可编辑状态&#xff0c;但当我们通过js修改了内容之后光标的位置就是一个问题&…...

51单片机-LED灯蜂鸣器数码管按键DS18B20温度传感器

LDE灯的相关程序 LED灯闪烁 LED流水灯 方法1 方法二&#xff1a; 因为P1口可以直接控制P1^0~P1^7的8个led灯&#xff0c;利用一个8位的二进制数字来进行控制即可。如果要点亮P1^0 只需要给P1口传递 1111 1110即可。 蜂鸣器的使用 什么是蜂鸣器&#xff1f; 蜂鸣器是一种一…...

笔记本一线品牌有哪些

笔记本电脑的一线品牌通常指的是在市场上具有较高市场份额、良好口碑、较强的技术实力和服务能力的品牌。根据目前的信息&#xff0c;笔记本电脑市场的一线品牌主要包括以下几个&#xff1a; 联想 (Lenovo)&#xff1a;联想在全球笔记本市场上的占有率较高&#xff0c;其产品线…...

mysql聚合函数和分组

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…...

ubuntu20.04+RealSenseD455

ubuntu20.04安装驱动双目相机RealSenseD455 安装环境安装RealSense SDK 2.0ROS包安装启动Realsense摄像头存在的 bugD455标定安装环境 系统:Ubuntu20.04 ROS:Noetic 视觉传感器:Intel RealSense D455 安装RealSense SDK 2.0 该安装有两种方式,一个是用命令安装,另一个是…...

WAF绕过技巧

WAF绕过技巧 WAF&#xff08;Web Application Firewall&#xff09;是一种安全系统&#xff0c;旨在监控和控制网络流量&#xff0c;以防止攻击&#xff0c;如SQL 注入、跨站脚本&#xff08;XSS&#xff09;和拒绝服务&#xff08;DoS&#xff09;。 WAF 可以通过多种方式绕过…...

HarmonyOS应用三之组件生命周期和参数传递

目录&#xff1a; 1、生命周期的执行顺序2、页面数据传递3、图片的读取4、数据的备份和恢复5、轮播图6、页面布局图 1、生命周期的执行顺序 /** Copyright (c) 2023 Huawei Device Co., Ltd.* Licensed under the Apache License, Version 2.0 (the "License");* yo…...

[Qt][Qt 网络][上]详细讲解

目录 0.概述1.UDP Socket1.核心API概览2.回显服务器3.回显客户端 0.概述 要使用Qt中有关网络编程的API&#xff0c;需要添加network模块 1.UDP Socket 1.核心API概览 主要的类有两个&#xff1a;QUdpSocket和QNetworkDatagramQUdpSocket表⽰⼀个UDP的socket⽂件 bind(const …...

读零信任网络:在不可信网络中构建安全系统21读后总结与感想兼导读

1. 基本信息 零信任网络&#xff1a;在不可信网络中构建安全系统 道格巴斯&#xff08;Doug Barth&#xff09; 著 人民邮电出版社,2019年8月出版 1.1. 读薄率 书籍总字数252千字&#xff0c;笔记总字数73194字。 读薄率73194252000≈29.5% 这个读薄率是最高的吧&#x…...

Java基础——注释

在开发中注释是必不可少的&#xff0c;帮助我们更好的标记阅读代码&#xff0c;下面介绍几种常用的注释方式。 一、注释种类 1. 单行注释 使用//一行代码来进行注释&#xff0c;只能注释一行内容 2. 多行注释 使用斜杠星号的方式 /*注释多行代码*/&#xff0c;注释多行代…...

Redis未授权访问漏洞利用合集

一、基本信息 靶机&#xff1a;IP:192.168.100.40 攻击机&#xff1a;IP:192.168.100.60 二、漏洞 & 过程 Redis 未授权访问漏洞利用无口令远程登录靶机 靶机 cd redis-4.0.8/src./redis-server ../redis.conf 攻击机 ./redis-cli -h 192.168.100.40 Redis 未授权访问…...

基于asp.net的在线考试系统、基于c#的在线考试管理系统

摘 要 伴随着社会以及科学技术的发展&#xff0c;互联网已经渗透在人们的身边&#xff0c;网络慢慢的变成了人们的生活必不可少的一部分&#xff0c;紧接着网络飞速的发展&#xff0c;管理系统这一名词已不陌生&#xff0c;越来越多的学校、公司等机构都会定制一款属于自己个…...

将 hugo 博客搬迁到服务器

1. 说明 在 Ubuntu 22.04 上使用 root 账号&#xff0c;创建普通账号&#xff0c;并赋予 root 权限。 演示站点&#xff1a;https://woniu336.github.io/ 魔改hugo主题: https://github.com/woniu336/hugo-magic 2. 服务器配置 建立 git 用户 adduser git安装 git sudo apt …...

【Datawhale AI夏令营第四期】 魔搭-大模型应用开发方向笔记 Task04 RAG模型 人话八股文Bakwaan_Buddy项目创空间部署

【Datawhale AI夏令营第四期】 魔搭-大模型应用开发方向笔记 Task04 RAG模型 人话八股文Bakwaan_Buddy项目创空间部署 什么是RAG&#xff1a; 我能把这个过程理解为Kimi.ai每次都能列出的一大堆网页参考资料吗&#xff1f;Kimi学了这些资料以后&#xff0c;根据这里面的信息综…...

CTF密码学小结

感觉没啥好总结的啊 基础的永远是RSA、流密码、哈希、对称密码、古典密码那一套&#xff08;密码学上过课都会&#xff09;&#xff0c;其他的就是数论的一些技巧 似乎格密码也很流行&#xff0c;以及一些奇奇怪怪的性质利用也很多 1、random设置种子后随机的性质&#xff1a…...

Vue快速入门(七)——Vue3 状态管理 - Pinia(二)

目录 六、核心概念——Getter 1、基本操作 定义getter 访问getter 2、访问其他 getter 3、向 getter 传递参数 4、访问其他 store 的 getter 使用 setup() 时的用法 使用选项式 API 的用法 使用 setup() 不使用 setup() 七、核心概念——Action 1、基本操作 定义a…...

ZooKeeper集群环境部署

1. ZooKeeper安装部署 1.1 系统要求 1.1.1 支持的平台 ZooKeeper 由多个组件组成。一些组件得到广泛支持&#xff0c;而另一些组件仅在较小的一组平台上得到支持。 客户端是 Java 客户端库&#xff0c;由应用程序用于连接到 ZooKeeper 集群。 服务器是在 ZooKeeper 集群节点…...

10 个 C# 关键字和功能

在 Stack Overflow 调查中&#xff0c;C# 语言是排名第 5 位的编程语言。它广泛用于创建各种应用程序&#xff0c;范围从桌面到移动设备再到云原生。由于有如此多的语言关键字和功能&#xff0c;对于开发人员来说&#xff0c;要跟上新功能发布的最新信息将是一项艰巨的任务。本…...

贪心算法之重叠区间问题

以下四个题都是重叠区间问题 452. 用最少数量的箭引爆气球 为了让气球尽可能重叠&#xff0c;先按照气球起始位置由大到小排序tips&#xff1a;sort默认就可以实现以上排序&#xff0c;不需要写cmp重点&#xff1a;当下一个气球的左边界不小于上一个气球的右边界时(即有重叠的…...

Python爬虫入门教程(非常详细)适合零基础小白

一、什么是爬虫&#xff1f; 1.简单介绍爬虫 爬虫的全称为网络爬虫&#xff0c;简称爬虫&#xff0c;别名有网络机器人&#xff0c;网络蜘蛛等等。 网络爬虫是一种自动获取网页内容的程序&#xff0c;为搜索引擎提供了重要的数据支撑。搜索引擎通过网络爬虫技术&#xff0c;将…...

ArcGIS Pro基础:软件的常用设置:中文语言、自动保存、默认底图

上图所示&#xff0c;在【选项】&#xff08;Options&#xff09;里找到【语言】设置&#xff0c;将语言切换为中文选项&#xff0c;记得在安装软件时&#xff0c;需要提前安装好ArcGIS语言包。 上图所示&#xff0c;在【选项】里找到【编辑】设置&#xff0c;可以更改软件默认…...

依赖注入+中央事件总线:Vue 3组件通信新玩法

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Vue篇专栏内容:Vue-依赖注入-中央事件总线 目录 中央事件总线使用 依赖注入使用 总结 中央事件总线 依赖注入…...

EasyCVR视频汇聚平台构建远程安防监控:5大亮点解析,助力安防无死角

随着科技的飞速发展&#xff0c;远程安防监控系统已经成为现代社会中不可或缺的一部分&#xff0c;无论是在小区、公共场所还是工业领域&#xff0c;安防监控都发挥着至关重要的作用。而EasyCVR作为一款功能强大的视频监控综合管理平台&#xff0c;其在构建远程安防监控系统方面…...

fastadmin安装插件报500的错误

项目场景&#xff1a; 项目新建后&#xff0c;想在本地项目中安装相关的插件&#xff0c;但是在插件管理页面点击安装的时候一直报500的错误。 问题描述 我们将项目中的调试打开&#xff0c;在application/config.php里修改 app_debug&#xff0c;将false改为true&#xff0c…...

速盾:为什么需要服务器和cdn?

在互联网时代&#xff0c;服务器和CDN&#xff08;内容分发网络&#xff09;起着非常重要的作用。它们是实现高效、稳定和可靠网络服务的关键组成部分。下面我将详细阐述为什么需要服务器和CDN。 首先&#xff0c;服务器是互联网上存储、处理和传输数据的中心枢纽。当我们在浏…...

十四、模拟实现 list 类

Ⅰ . list 基本框架的实现 01 结点的建立 为了实现链表&#xff0c;我们首先要做的应该是建立结点 为了和真正的 list 进行区分&#xff0c;我们仍然在自己的命名空间内实现 代码实现&#xff1a; namespace yxt {// 建立结点template<class T>struct ListNode{T _d…...

JavaScript简介之引入方式

JavaScript 引入方式 提问&#xff1a;CSS的引入方式&#xff1f;在学习 JavaScript 语法之前&#xff0c;我们首先要知道在哪里写 JavaScript 才行。想要在 HTML 中引入 JavaScript&#xff0c;一般有 3 种方式。 外部 JavaScript 内部 JavaScript 元素事件 JavaScript&#…...

同一台电脑上安装不同版本的nodejs(搭配VSCode)

今天拉取了一个前后端分离的项目&#xff0c;运行前端的时候&#xff0c;出现node版本不匹配的情况。 本文章将从安装node.js开始到VSCode使用进行讲解 1、去官网下载node版本 以16版本为例&#xff0c;需要哪个版本&#xff0c;就在网址上把版本号替换即可 https://nodejs.o…...

python小游戏之摇骰子猜大小

最近学习Python的随机数&#xff0c;逻辑判断&#xff0c;循环的用法&#xff0c;就想找一些练习题&#xff0c;比如小游戏猜大小&#xff0c;程序思路如下&#xff1a; 附上源代码如下&#xff1a; 摇骰子的函数&#xff0c;这个函数其实并不需要传任何参数&#xff0c;调用后…...

C++入门——12继承

1.继承 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象程序设计的层次结构&#xff0c;体现了由简…...

Python做统计图之美

Python数据分析可视化 案例效果图 import pandas as pd import matplotlib.pyplot as plt import matplotlib# 数据 data {"房型": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],"住宅类型": ["普通宅", "普通宅", "普通宅", &q…...

激光雷达点云投影到图像平面

将激光雷达点云投影到图像平面涉及几何变换和相机模型的应用。以下是该过程的基本原理&#xff1a; 1. 坐标系转换 激光雷达生成的点云通常位于激光雷达的坐标系中&#xff0c;而图像则在相机坐标系中。为了将点云投影到图像上&#xff0c;首先需要将点云从激光雷达坐标系转换…...

[python]将anaconda默认创建环境python版本设置为32位的

首先看看gpt怎么回答的 装了Anaconda。如果尚未安装&#xff0c;可以从Anaconda官网下载适合你的操作系统的安装程序&#xff0c;并按照安装向导进行安装。 二、创建32位Python环境 在Anaconda中&#xff0c;你可以通过修改环境变量来尝试切换到32位模式&#xff08;尽管这并…...

Jmeter+Influxdb+Grafana平台监控性能测试过程(三种方式)

一、Jmeter自带插件监控 下载地址&#xff1a;Install :: JMeter-Plugins.org 安装&#xff1a;下载后文件为jmeter-plugins-manager-1.3.jar&#xff0c;将其放入jmeter安装目录下的lib/ext目录&#xff0c;然后重启jmeter&#xff0c;即可。 启动Jmeter&#xff0c;测试计…...

[创业之路-135] :ERP、PDM、EDM、Git各种的用途和区别,硬件型初创公司需要哪些管理工具?

目录 前言&#xff1a; 一、ERP&#xff08;企业资源计划&#xff09; 二、PDM&#xff08;产品数据管理系统&#xff09; 三、EDM&#xff08;文档管理系统&#xff0c;有时也指电子邮件营销&#xff09; 四、Git 总结 五、硬件研发、生产型企业需要哪些管理工具&#…...

通过剪枝与知识蒸馏优化大型语言模型:NVIDIA在Llama 3.1模型上的实践与创新

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...