笔记来源于江科协议的视频

芯片采用与stm32F103C8T6

SPI简介

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

IIC用最少的通信线,实现最多的功能,但是由于IIC开漏外加上拉电阻的结构,使得通信线高电平的驱动能力比较弱,这样通信线由低电平变为高电平的时候,上升沿耗时比较长,会限制IIC通信最大速度,所以IIC标准速度只有100Khz,快速模式也只有400Khz。

SPI传输更快,传输最大速度取决于芯片厂商的设计需求,SPI设计比较简单,SPI硬件开销比较大,通信过程中有资源浪费的情况。

•四根通信线:SCK(Serial Clock)串行时钟线、MOSI(也叫DO)(Master Output Slave Input)主机输出从机输入、MISO(也叫DI)(Master Input Slave Output)主机输入从机输出、SS(Slave Select)从机选择

•同步,全双工

•支持总线挂载多设备(一主多从),SPI仅支持一主多从,不支持多主机,SPI没有应答机制,

上图中最左边图中DI和DO怎么判断,这个芯片接在stm32上,是从机身份,这里的DI数据输入就是从机数据输入,主机数据输出MOSI,DO则对应的是从机输出,主机输入MISO,

硬件电路

左边是SPI主机,一般是stm32,下面就是挂载上主机的设备,3根从机选择线,2根数据传输线,1根时钟线,他们的高低电平都是相对于GND的电位差,所以单端信号,所有设备还需要共地,GND必须要接,上图只是没画出来,如果从机没有独立供电,主机还要接一根VCC给从机供电,SCK时钟线,MOSI主机输出,从机输入,MISO主机输入从机输出,主机另外引出多条SS控制线,分别接到各从机的SS引脚,对某个设备输出低电平,那就是选择哪个设备,通信完之后,应该把连接的线置回高电平,同一时间,主机只能选择一个设备,如果选择多个从机,则数据产生冲突,•输出引脚配置为推挽输出,高低电平均有很强的驱动能力,这会使上升沿、下降沿都很迅速,这会使速度更快一般SPI能达到Mhz。输入引脚配置为浮空或上拉输入。

SPI冲突点,MISO,主机一个输入,从机三个输出,如果三个从机都是推挽输出,必定产生冲突,在SPI协议里面,从机SS脚为高电平,代表未选中,从机的MISO引脚必须切换到高阻态,相当于引脚断开,不输出任何电平,这样就可以防止数据冲突问题。在SS为低电平时,从机MISO才能为推挽输出。

移位电路

左边是SPI主机,里面有一个8位的移位寄存器,右边是SPI从机,里面也有一个移位寄存器,主机的移位寄存器有个波特率发生器就是时钟输入端,因为SPI是高位先行,所以每来一个时钟,移位寄存器都会向左进行移位,从机中的移位寄存器也是同理,移位寄存器中的时钟源是主机提供的,波特率发生器产生的时钟驱动主机的移位寄存器,同时也通过SCK引脚输出到从机的SCK,接到从机的移位寄存器里面。

主机移位寄存器左边移出去的数据,通过MOSI输入到从机寄存器的右边,从机左边移出去的数据通过MISO输入到主机移位寄存器的右边。 时钟的上升沿所有寄存器向左移动一位,移出的一位放引脚上,时钟的下降沿,引脚上的位,采样输入到移位寄存器的最低位, 如:主机有个数据10101010,从机有数据01010101,先产生上升沿,所有的位向左移动一位,移除的数据会放在通信线上,实际是放在数据输出寄存器上,此时,MOSI数据为1,电平就是高电平,MISO数据为0,电平就是低电平,第一个上升沿就是把寄存器的最高位放在通信线上,这就是数据的输出,在下降沿时,主机和从机都会进行数据采样输入,也就是MOSI的1会采样到从机的最低位,MISO的0则会采样到主机移位寄存器的最低位,这是第一个下降沿的现象,之后的时钟也是这样操作的,一直到第8个时钟,就实现了主机和从机一个字节的数据交换。SPI的数据收发,都是基于字节交换,这个基本单元来进行的,当主机需要发送一个字节,并且需要接受一个字节时,就可以执行字节交换的时序, 如果只想发送,不想接受,依然执行交换时序,接受的数据,不用即可,如果只想接受,不想发送,仍然用交换时序,随便发送一个数据,只要接受的数据,一般在接受时,会同一发送0x00或0xFF,去和从机交换数据。

总结:SPI的基础就是交换字节,就可以实现发送一个字节、接受一个字节,和发送同时接受一个字节。SPI执行只发送或只接受时会有一些资源浪费情况。

时序单元

SS低电平有效,相当于使能位,下降沿通信的开始,上升沿通信的结束。

•CPOL=0:空闲状态时,SCK为低电平,•CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据,模式0的数据移出移入的时机会提前半个时钟,SCK在第一个边沿之前就要提前移入数据,但数据总要先移除才能移入,所以SCK在第一个边沿之前,就要提前移出数据,或者称为在第0个边沿移出,第一个边沿移入。SS下降沿开始通信,在SCK没有发生第一个上升沿的时候,就要移出数据,MOSI和MISO的输出对齐SS的下降沿,或者说SS的下降沿也作为时钟的一部分,SS下降沿移出,SCK的上升沿移入数据,这样B7就移出完毕,之后SCK下降沿,移出B6,连续8次,之后还有一个下降沿,如果主机只交换一个字节就结束,MOSI可以置会默认电平,或者不管它,MISO也会变化一次,如果不需要,MISO则会在SS的上升沿置会高阻态,如果主机想要交换多个字节,就在第一个字节的时序交换后在发送数据。

模式0对于模式1把数据变化的时机给提前了。

SPI并没有限制上升沿移位还是下降沿移位,这是为了兼容更多芯片,这里有2个可配置的位,CPOL时钟极性,CPHA时钟相位,CPOL=0:空闲状态时,SCK为低电平,CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据(采样),SS为高电平MISO为高阻态,低电平时才是高低电平,交换完数据之后,MISO必须置会高阻态。在第一个上升沿的时候,同时移出最高位,此时MOSI的电平表示了主机要发送的数据B7,从机通过MISO移除数据,此时MISO通信上的数据是从机的B7,在第一个下降沿的时候,双方寄存器进行数据采样,主机移出去的B7到从机的最低位,从机移出去的B7到主机的最低位。连续8次,主机和从机完成交换一个字节,如果主机想交换一个字节,就可以结束了,在SS的上升沿,MOSI还可以在变化一次,将MOSI置到一个默认的高电平和低电平,MISO必须置为高阻态。此时如果主机的MISO为上拉输入的话,那么MISO的引脚电平就是默认的高电平,如果主机的MISO为浮空,那么从机的MISO不确定。 如果主机还需继续交换字节,主机不必把SS置会高电平,直接重复一下,交换字节的时序即可。

•CPOL=1:空闲状态时,SCK为高电平,CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

相对于模式0,就是SCK极性取反一下。

•CPOL=1:空闲状态时,SCK为高电平,CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

相对于模式1,就是SCK极性取反一下。

时序图

第一个交换从机发送的数据,一般叫指令码,在从机中,对应的会定义一个指令集,当我们要发送什么指令时,就会在起始后第一个字节发送指令集的数据,这样就能从机完成相应的功能,不同的指令,可以有不同的数据个数,有的指令,只需要一个字节的指令码就可以完成了,如:W25Q64的写使能,写使能。而有的指令需要后面读写的数据,如:W25Q64的写数据、读数据。

上图时序图,使用的模式0。图中的MISO并没有回传数据,是高阻态,当然这个数据也没有那么重要,因为STM32的MISO是上拉输入,所以这里MISO呈现高电平,MOSI的数据根据模式0的方式进行采样,连续采样8次,因为是软件模拟的时序,所以没有贴紧SCK的下降沿,这里因为是写使能,不需要跟随数据。

主机用0x06换来了从机的0xFF,实际上从机并没有输出。

这里是指定地址写,因为W25Q64是8Mhz的存储芯片,一个字节的8位地址不够,地址是24位的,分3个字节传输。首先SS下降沿开始,MOSI空闲时是高电平,所以在下降沿,SCK第一个时钟之前,MOSI变换数据,由高电平变为低电平,然后SCK上升沿采样输入,8个时钟之后,一个字节交换完成,用0x02换来了0xFF,0x02是一条指令代表写一个数据的时序,接受的0xFF则不需要。第8个字节的下降沿是移出数据,把下一个字节的最高位放在MOSI上, 第二个字节用0x12换来了0xFF,根据W25Q64的规定,写指令之后的字节,定义为地址高位,所以这个0x12代表地址的23~16位,第三个数据就代表地址的15~8位,第四个数据代表地址的7~0位,通过三个字节的交换,24位地址就发送完毕了,从机收到的地址为0x123456,第5个数据就是要写入的数据,以上操作代表要在地址0x123456下,写入0x55这个数据。 如果不终止发送数据,继续发送字节会依次写入到后续的存储空间里面。这样就可以从指定地址写,写入多个字节。

模式0

指定地址读,向SS指定的设备,发送读指令(0x03),之后三个字节还是地址,第5个数据是随便给出的数据,从机就会在第5个字节的时序下传递数据给主机,从机传给主机的数据就是要读取的数据。继续发送数据,从机就会继续把指定地址下一个位置的数据发给主机,这样依次进行,就可以实现多字节读取的数据。

W25Q64介绍

简介

•W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景,使用的是SPI串行通信,掉电不丢失,

•存储介质:Nor Flash(闪存)

•时钟频率:80MHz / 160MHz (Dual SPI) (双重SPI等效的模式)/ 320MHz (Quad SPI)(四重SPI等效的模式)

•存储容量(24位地址)

硬件电路

左边是W25Q65的原理图,右上角是引脚图,右下角就是每个引脚定义的功能,VCC是典型的3.3V供电设备,不能直接接入5V电压,CS低电平有效,WP写保护,配合内部的寄存器配置,可以实现硬件的写保护,WP低电平有效,保护住,不让写,WP接高电平,可以写,HOLD数据保持,低电平有效,如:正常读写时,突然产生中断,然后想用SPI通信线去操作其他器件,如果直接置CS为高电平,此时通信终止,但是又不想终止总线,又想去操作其他器件,可以HOLD置低电平,芯片释放总线,但是芯片时序不会终止,它会记住当前的状态,操作完其他器件时,HOLD置会高电平,然后继续HOLD之前的时序,想当于SPI总线进了中断,还用SPI操作了其他器件。

W25Q64框图

右上角和左上角两大块是存储器的规划示意图,一整块存储空间,先划分为若干个块Block,其中每一块再划分为若干个扇区Sector,对于每个扇区,内部又可以分为很多页Page。 首先右边矩形空间是所有存储器,存储器以字节为单位,每个字节都有唯一的地址,W25Q64的地址宽度是24位,3个字节,所以能看到左下角00 00 00h,之后的空间,地址依次自增,直到最后一个字节7F FF FFh,在整个空间里,以64KB为一个基本单元,划分为若干个块,一共有128块,序号就是从序号0~序号127,在每一块内,它的地址变化范围就是低位的2个字节,每个块的起始是xx0000,结束是xxFFFF, 左边的框图则是对每一块进行更细的划分,分为多个扇区Sector,在一块里面再以4KB为一个单元,进行切分,一块里面有16个扇区,分为扇区0~扇区15,每个扇区内的范围是xx x0 00~xx xF FF,当我们在写入数据时,还会有更细的划分,就是页Page,页是对整个存储空间划分的,也可以看作在扇区里面进行划分,页的大小为256字节,一个扇区里面一共有16页,在一页中,地址范围xx xx 00 ~ xx xx FF,一页内的地址变化,仅限于地址的最低一个字节。 一整个存储空间,首先划分为若干块,对于每一块又划分为若干个扇区,对于整个空间可以划分为很多页。

左下角是SPI控制逻辑,也就是芯片内部进行地址锁存、数据读写等操作,都可以由控制逻辑自动完成,控制逻辑的左边就是SPI的通信引脚,主控芯片通过SPI协议,把指令和数据发给控制逻辑,控制逻辑就会自动取操作内部电路, 控制逻辑上面有一个状态寄存器,芯片是否处于忙状态,是否写使能、是否写保护,都可以在这个状态寄存器里体现,在往上是写控制逻辑,与外部的WP引脚相连,配合WP引脚实现写保护的。 控制逻辑右边有个高电压生成器,配合Flash进行编程的,因为Flash是掉电不丢失的,需要高电压刺激。高电压生成器下面是页地址锁存/计数器,在往下有个字节地址锁存/计数器,这两个器件是用来锁存地址的,我们寻址发送的3个字节地址,前两个字节会进到页地址锁存计数器里,最后一个字节会进到字节地址锁存计数器里,页地址通过写保护和行解码,来选则操作哪一页,字节地址通过列解码和256字节页缓存,来进行指定字节的读写操作,因为地址锁存有计数器的,所以这个地址指针,在读写之后可以自动+1,这样就可以实现从指定地址读写,连续读写多个字节的目的了,再往有大矩形框下面还有一个256字节的页缓冲区,本质上是一个256字节的RAM存储器,数据读写就是通过RAM缓冲区来进行的,写入数据,会先放到缓冲区里面,时序结束后,芯片再将缓冲区的数据复制到对应的Flash里面,因为缓冲区是256字节的,一次写入不能超过256字节,写完数据之后,芯片会把数据从缓冲区移到Flash里面,数据从缓冲区转移到Flash里,需要一定时间,所以在写入时序后,芯片会进入一段忙的状态,所以缓冲区有一根线通向状态寄存器,给状态寄存器的BUSY置1,表示芯片当时正在转运数据,忙状态,在忙的时候,芯片就不会读写新的时序了。

操作事项

写入操作时:

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

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

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

•擦除必须按最小擦除单元进行,最小的一个擦除单元是扇区,4KB,

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

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

读取操作时:

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

指令集:

0x06:写使能

0x04:写失能

0x05:读寄存器,通过交换来读取一个字节,查看忙状态,

0x02:页编程,写数据,限制256字节, 起始 0x02 地址 写入的数据

0xD8:按64KB擦除

0x52:按32KB擦除

0x20:按4KB擦除 起始 交换字节0x20 地址 终止

0xC7/0x60:整片擦除。

0x9F:读取ID号, 起始 交换字节0x9F 连续读取三个字节(第一个字节厂商ID,第二、三个字节设备ID) 终止

0x03:读取数据 起始 交换字节0x03 地址 数据(交换数据用0x00)

SPI外设

简介

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

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

•时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256),SPI的时钟其实是由PCLK得来的,fPCLK就是外部终使,Apb2的时钟72Mhz(SPI1),Apb1的时钟36Mhz(SPI2),

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

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

•支持DMA

•兼容I2S协议(音频传输协议)

•STM32F103C8T6 硬件SPI资源:SPI1、SPI2

框图

图大概可以分为两部分,左上角就是数据寄存器和移位寄存器配合的部分,剩下的右下角就是一些控制逻辑。

左上角,核心部分就是这个移位寄存器,右边的数据低位,一位一位的从MOSI移出去,然后MISO的数据,一位一位的移入左边的数据高位,显然移位寄存器是一个右移的状态,图上表示的是低位先行,对应右下角LSBFIRST控制位,这一位可以控制是低位先行还是高位先行,给0就是高位先行,给1就是低位先行,再往左看,MOSI和MISO做了个交叉,主要用于进行主从模式引脚变化的,做主机是,交叉就不用,做从机的时候就要用到交叉连接。上下两个缓冲区,实际上就是数据寄存器DR, 发送缓冲区就是发送数据寄存器TDR,上面接受缓冲区就是接受数据寄存器RDR,TDR和RDR占用一个地址,同一称DR,写入数据时数据写入TDR,读取DR时数据从RDR读取,数据寄存器和移位寄存器配合,可实现连续的数据流,如:连续发送一批数据,第一个数据写入TDR,当移位寄存器没有数据移位时,TDR的数据会立刻转入移位寄存器,开始移位,转入时刻,会置状态寄存器的TXE为1, 表示发送寄存器为空,当检查到TXE为1时,下个数据提前写入到TDR了,一旦上个数据发完,下个数据立马跟上,实现不间断的连续传输,然后移位寄存器这里一旦有数据,就自动产生时钟,将数据移出去,在移出的过程中,MISO的数据也会移入,移入的数据会整体的转入到接受缓冲区RDR,这时,会置RXNE为1,表示接受寄存器非空,需要尽快把RDR的数据读出来,在下一个数据来之前,读出RDR,就可以实现连续的接受了。

波特率发生器本质上就是个分频器,用来产生SCK时钟的,输入时钟是PCLK,72M或36M,经过分频之后,输出到SCK引脚,这里生成的时钟和移位寄存器同步,右边,CR1寄存器的三个位,BR0、BR1、BR2用来控制分频系数,SPE是SPI使能,MSTR,配置主从模式,1是主模式,0是从模式,CPOL和CPHA用来选择SPI的四种模式。

逻辑框图

核心部分是数据寄存器和移位寄存器,移位寄存器,高位移出去,通过GPIO,到MOSI,从MOSI输出,移入的数据从MISO进来,通过GPIO到移位寄存器的低位。TDR整体转入移位寄存器,会置TXE标志位,移位寄存器,整体转入RDR的时刻,置RXNE标志位。波特率发生器产生时钟输出到SCK引脚。

时序逻辑图

主模式全双工连续传输、连续传输

第一行是SCK时钟线,CPOL=1,CPHA=1使用的是SPI模式3,所以SCK默认高电平,在第一个下降沿,MOSI和MISO移出数据,之后,上升沿移入数据,第二行是MISO和MOSI输出的波形,跟随SCK时钟变化,数据依次出现,这里演示的是低位先行的模式,第三行是TXE,发送寄存器空标志位,第四行是发送缓冲器,写入SPI_DR, 第五行BSY,忙,由硬件自动设置和清除的,有数据传输时,BSY置1,没有时,置0。再往下看是输入的流程现象,第一行是MISO和MOSI输入,第二行是RXNE是接受标志位非空,最后一个是接受缓冲器读出SPI_DR。

首先SS置低电平,(上图没画),开始时序,刚开始时,TXE为1,表示TDR为空,软件写入0xF1到SPI_DR,写入之后,TDR变为0xF1,同时TXE变为0,表示TDR已经有数据了,此时TDR是等候区,移位寄存器才是发送区,刚开始没数据,在TDR的数据会直接转入移位寄存器,开始发送,转入瞬间,TXE置1,表示发送寄存器空,移位寄存器有数据了,波形自动开始生成,在转入数据的同时,TDR为空,然后就可以把第二个数据放入TDR,之后的数据也是这样流程,不想继续发送时,置TXE=1;置TXE=1之后还要等待一段时间,波形才能发送完成,然后BSY置0。

因为SPI是全双工,在第一个字节发送完成后,第一个字节也完成了接受,接受到的数据A1,这时,移位寄存器的数据整体转入RDR,RDR存储的就是A1,转入的同时RXNE也置1,表示收到了数据。然后从SPI_DR也就是RDR读出数据A1,接受完之后,软件清除RXNE标志位,当第二个数据收到之后,RXNE重新置1,检测到RXNE=1时,读出数据,在最后一个字节时序产生之后第三个数据才能收到, 一个字节收到之后,移位寄存器的数据自动转入RDR,会覆盖原有的数据,所以读取数据要立刻。

非连续传输

SPI模式3,SCK默认高电平,想发送数据时,如果检测到TXE=1,TDR为空,就软件写入0xF1到SPI_DR,这时TDR的值为F1,TXE变为0,目前移位寄存器也为空,所以F1会转到移位寄存器开始发送,并且TXE置回1,表示可以把下一个数据放在TDR里面,这里TXE置1但并不会把这个数据写进去,而是等待第一个字节时序结束,这时接受的RXNE置1,等待RXNE置1后,先把接受的第一个字节读出来,之后再写入下一个字节数据。之后的数据也是这个流程。 第一步:等待TXE为1, 第二步:写入发送的数据到TDR, 第三步:等待RXNE为1, 第四步:读取RDR接受的数据, 之后交换字节重复这四步。

代码

硬件SPI

#include "stm32f10x.h"                  // Device header



//CS引脚,低电平有效,高电平通信停止
void MySPI_W_CS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}



void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	SPI_InitTypeDef SPI_InitStructure;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//主机
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;		//128分频
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;			//第几个边沿采样
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;			//极性选择
	SPI_InitStructure.SPI_CRCPolynomial = 7;					//校沿位用不到
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;			//8位数据帧
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;		//全双工
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;			//高位先行
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;			//NSS用软件控制
	SPI_Init(SPI1,&SPI_InitStructure);
	
	SPI_Cmd(SPI1,ENABLE);
	
	
	//默认电平
	MySPI_W_CS(1);
}


//开始信号
void MySPI_Start(void)
{
	MySPI_W_CS(0);
}


//结束信号
void MySPI_Stop(void)
{
	MySPI_W_CS(1);
}

交换一个字节
//uint8_t MySPI_SwapByte(uint8_t byte)
//{
//	uint8_t i,ByteReceive=0x00;
//	
//	for(i=0;i<8;i++)
//	{
//		MySPI_W_MOSI(byte &(0x80 >> i));
//		MySPI_W_CLK(1);
//		//ByteReceive |= MySPI_R_MISO()<<(8-i);
//		if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}	
//		MySPI_W_CLK(0);
//	}
//	return ByteReceive;
//}


uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET );
	
	SPI_I2S_SendData(SPI1,ByteSend);
	
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET );
	
	return SPI_I2S_ReceiveData(SPI1);
}

软件SPI

SPI.c

#include "stm32f10x.h"                  // Device header



//CS引脚,低电平有效,高电平通信停止
void MySPI_W_CS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}

//写时钟
void MySPI_W_CLK(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);
}

//MOSI 主机输出从机输入
void MySPI_W_MOSI(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);
}

//MISO  主机读取从机数据
uint8_t MySPI_R_MISO(void)
{
	return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}

void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);

	//默认电平
	MySPI_W_CS(1);
	MySPI_W_CLK(0);	
}


//开始信号
void MySPI_Start(void)
{
	MySPI_W_CS(0);
}


//结束信号
void MySPI_Stop(void)
{
	MySPI_W_CS(1);
}

交换一个字节
//uint8_t MySPI_SwapByte(uint8_t byte)
//{
//	uint8_t i,ByteReceive=0x00;
//	
//	for(i=0;i<8;i++)
//	{
//		MySPI_W_MOSI(byte &(0x80 >> i));
//		MySPI_W_CLK(1);
//		//ByteReceive |= MySPI_R_MISO()<<(8-i);
//		if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}	
//		MySPI_W_CLK(0);
//	}
//	return ByteReceive;
//}


uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	uint8_t i, ByteReceive = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	
	for (i = 0; i < 8; i ++)						//循环8次,依次交换每一位数据
	{
		MySPI_W_MOSI(ByteSend & (0x80 >> i));		//使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
		MySPI_W_CLK(1);								//拉高SCK,上升沿移出数据
		if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}	//读取MISO数据,并存储到Byte变量
																//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
		MySPI_W_CLK(0);								//拉低SCK,下降沿移入数据
	}
	
	return ByteReceive;								//返回接收到的一个字节数据
}

W25Q64.c

#include "stm32f10x.h"                  // Device header
#include "W25Q64_Ins.h"
#include "MySPI.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);
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE) << 8;
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);	
	MySPI_Stop();	
}

//写使能
void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);			//写使能
	MySPI_Stop();
}

//W25Q64 等待忙
void W25Q64_WaitBusy(void)
{
	uint32_t Count = 10000;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	while((MySPI_SwapByte(W25Q64_DUMMY_BYTE)& 0x01) == 0x01)
	{
		Count--;
		if(Count == 0)
			break;
	}
	
	MySPI_Stop();
}


//W25Q64页编程
void W25Q64_PageProgram(uint32_t Addr,uint8_t *DataArray,uint16_t Count)
{
	
	
	uint16_t i;
	
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);		//交换发送页编程的指令
	MySPI_SwapByte(Addr>>16);
	MySPI_SwapByte(Addr>>8);
	MySPI_SwapByte(Addr);
	for(i=0;i<Count;i++)
	{
		MySPI_SwapByte(DataArray[i]);
	}
	MySPI_Stop();
	W25Q64_WaitBusy();
}

//页擦除
void W25Q64_SectorEraser(uint32_t Addr)
{
	W25Q64_WriteEnable();
	MySPI_Start();
	
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Addr>>16);
	MySPI_SwapByte(Addr>>8);
	MySPI_SwapByte(Addr);
	
	MySPI_Stop();
	
	W25Q64_WaitBusy();							//等待忙
}


void W25Q64_ReadData(uint32_t Addr,uint8_t *DataArray,uint16_t Count)
{
	uint32_t i;
	
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);
	MySPI_SwapByte(Addr>>16);
	MySPI_SwapByte(Addr>>8);
	MySPI_SwapByte(Addr);	
	for(i=0;i<Count;i++)
	{
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}	

指令集

#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

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;							//定义用于存放MID号的变量
uint16_t DID;							//定义用于存放DID号的变量

uint8_t ArrayWrite[] = {0x01, 0xBB, 0x03, 0x04};	//定义要写入数据的测试数组
uint8_t ArrayRead[4];								//定义要读取数据的测试数组

int main(void)
{
	/*模块初始化*/
	OLED_Init();						//OLED初始化
	W25Q64_Init();						//W25Q64初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	
	/*显示ID号*/
	W25Q64_ReadID(&MID, &DID);			//获取W25Q64的ID号
	OLED_ShowHexNum(1, 5, MID, 2);		//显示MID
	OLED_ShowHexNum(1, 12, DID, 4);		//显示DID
	
	/*W25Q64功能函数测试*/
	//W25Q64_SectorEraser(0x000000);					//扇区擦除
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);	//将写入数据的测试数组写入到W25Q64中
	
	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)
	{
		
	}
}

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 SPI接口学习笔记

发表评论