串行外设接口
I2C的缺点:由于I2C开漏外加上拉电阻的电路结构,使得通信线高电平的驱动能力比较弱,这就会导致通信线由低电平变到高电平的时候,上升沿的耗时比较长,会限制I2C的最大通信速度(半双工特性导致,频繁切换输入输出模式)。
SPI:SPI传输更快,SPI协议没有严格规定最大传输速度,设计简单,SPI的硬件开销比较大,通信线的个数比较多,并且在通信过程中,经常会有资源浪费的现象
SCK-----串行时钟线,MOSI-----主机输出从机输入,MISO-----主机输入从机输出,SS-----从机选择
SPI没有应答机制
SPI所有通信线都是单端信号,它们的高低电平都是相对GND的电压差,所以所有的设备还需要共地。
SS线是低电平有效的,主机想指定谁,就把对应的SS输出线置低电平就行了
当从机的SS引脚为高电平,也就是从机未被选中时,它的MISO引脚必须切换为高阻态,高阻态相当于引脚断开,不输出任何电平,这样就可以防止一条线有多个输出而导致的电平冲突问题;在SS为低电平时,MISO才允许变为推挽输出
SPI是高位先行, 每来一个时钟,移位寄存器都会向左进行移位。
移位寄存器的时钟源是由主机提供的,这里叫作波特率发生器,它产生的时钟驱动主机的移位寄存器进行移位,同时这个时钟也通过SCK引脚进行输出,接到从机的移位寄存器里。
波特率发生器时钟的上升沿,所有移位寄存器向左移动一位,移出去的位放到引脚上,波特率发生器时钟的下降沿,引脚上的位,采样输入到移位寄存器的最低位。
SS是低电平有效
SS下降沿时,立刻触发移位输出来提供第一个数据
CPOL(Clock Polarity)时钟极性
CPHA(Clock Phase)时钟相位
还没开始通信时MISO一条直线表示为高阻态
SPI没有硬性规定MOSI的默认电平
模式0和模式1的区别就是模式0把数据变化的时机提前了
CPHA表示的是时钟相位,决定是第一个时钟采样移入还是第二个时钟采样移入,并不是规定上升沿采样还是下降沿采样的
SPI中通常采用的是指令码加读写数据的模型
SPI起始后,第一个交换发送给从机的数据,一般叫做指令码,在从机中,对应的会定义一个指令集
W25Q4芯片规定,写指令之后的字节,定义为地址高位
SPI有和I2C一样的地址指针,每读写一个字节,地址指针自动加1,如果发送一个字节之后不终止,继续发送的字节就会依次写入到后续的存储空间里
------------------------W25Q64BV介绍-------------------------------------------------------------------------------
易失性存储器一般就是SRAM、DRAM等,非易失性存储器一般就是EEPROM、Flash等,他们最主要的区别是存储的数据是否是掉电不丢失
双重SPI,在发的时候可以同时使用MOSI和MISO发送,在收的时候同时用MOSI和MISO接收,一次2位
四重SPI是多用了WP(写保护)和Hold引脚
CS,左边画了个斜杠,代表是低电平有效
WP(Write Protect),写保护,配合内部的寄存器配置,可以实现硬件的写保护,低电平有效,保护住,不让写。WP接高电平,不保护,可以写。
Hold,数据保存,低电平有效。如果在进行正常读写时,突然产生中断,然后想用SPI通信线去操控其他器件,这时如果把CS置回高电平,那时序就终止了,但如果不想终止总线,又想操作其他器件,这就可以hold引脚置低电平,芯片释放总线,但是芯片时序不会终止,它会记住当前的状态,当你操作完其他器件时,可以回过来,hold置回高电平,然后继续hold之前的时序,相当于SPI总线进了一次中断,并且在中断里,可以用SPI干别的事情
空间先划分为块block,每一块block划分为若干的扇区Sector,对于每个扇区又可以分为很多页Page。
左下角为控制逻辑,主控芯片通过SPI协议,把指令和数据发给控制逻辑,控制逻辑就会自动去操作内部电路,完成功能。控制逻辑上面有个状态寄存器,芯片是否处于忙状态、是否写使能、是否写保护,都可以在状态寄存器里体现
写控制逻辑,配合WP引脚实现硬件写保护
高电压生成器,配合Flash进行编程,掉电不丢失。比如点亮一个LED表示1,熄灭LED表示0,但如果整个系统电都没有,那1和0就无从说起了,所以要想掉电不丢失,就要我们在存储器里产生一些刻骨铭心的变化,比如一个LED,给它加很高的电压,那LED就烧坏了,我们用烧坏的LED表示1,没烧坏的LED表示0,然后再断电,烧坏的LED还是烧坏的,烧坏的状态不受有电还是没电的影响,此乃掉电不丢失。一般非易失性存储器,都需要一个比较高的电压去刺激它,需要一个高压源。
页地址锁存/计数器和字节地址锁存/计数器,用来指定地址,通过SPI总共发过来3个字节的地址。一页一共是256个字节,所以一页内的字节地址,就取决于最低的一个字节,而高位的2个字节,就对应的是页地址。所以发送的地址,前两个字节会进入到这个页地址锁存器计数器里,最后一个字节会进入到字节地址锁存器计数器里。然后,页地址,通过这个写保护的行解码,来选择我要操作哪一页。字节地址,通过列解码和256字节页缓存,来进行指定字节的读写操作。那又因为我们这个地址锁存都是有一个计数器的,所以这个地址指针在读写之后可以自动加1,这样就可以实现从指定地址开始,连续读写多个字节的目的了。
最后,右下角的256字节的页缓存区,其实是一个256字节的RAM存储器,数据读写就是通过这个RAM缓存区来进行的。我们写入数据,会先进入缓存区里,然后在时序结束后,芯片再将缓存区的数据幅值到对应的Flash里,进行永久保存。这是因为SPI写入的频率非常高,而Flash的写入需要掉电不丢失,就比较慢,所以芯片的设计思路就是,写入的数据先放在页缓存区里存着,因为缓存区是RAM,所以它的速度非常快,可以跟得上SPI总线的速度。因为缓存区是256个字节,所以写入的时序有个限制条件,就是写入的一个时序,连续写入的数据量,不能超过256字节。写完后才把数据从缓存区转移到Flash存储器里。数据从缓存器转到Flash里,需要一定的时间,所以在写入时序结束后,芯片会进入一段忙的状态,通往状态寄存器,给状态寄存器的BUSY位置1。忙的时候,芯片不会响应新的读写时序了。由于读取,只是看一下电路的状态就行了,它基本不花时间,所以读取的限制就很少了,速度也非常快。
Flash的写入和读取并不像RAM那样简单直接,RAM是指哪打哪,想在哪写就在哪写,想写多少就写多少,并且RAM是可以覆盖写入的,比如原来RAM里有个数据0xAA,之后我直接再写入一个新数据0x55,那RAM的数据就变成0x55了。
写使能的话,使用SPI发送一个写使能的指令。
Flash没有直接完全覆盖改写的能力,比如在某一个字节的存储单元里,存储了0xAA这个数据,对应的二进制位就是10101010 ,如果我直接再次在这个存储单元写入一个新的数据,比如再写入一个0x55,0x55的二进制是01010101,当这个01010101要覆盖原来的10101010时,就会受到限制,最终写入的是0x00.
擦除,可以选择整个芯片擦除,也可以选择按块擦除,或者按扇区擦除,最小的擦除单元就是一个扇区。一个扇区4096个字节。擦除时芯片也会处于忙状态,BUSY位置1。
控制和状态寄存器:
当设备正在执行页编程,页编程就是写入数据,然后扇区擦除、块擦除、整片擦除或者写状态寄存器指令时,BUSY位置1。在这期间,设备会忽略进一步的指令,当然除了读状态寄存器和擦除挂起的指令,然后是当编程、擦除、写状态寄存器指令结束后,BUSY清零来指示设备准备好了。
写使能锁存位WEL,在执行完写使能指令后,WEL置1,代表芯片可以进行写入操作了,当设备写失能时,WEL位清零。一是,上电后,芯片默认写失能;二是,在执行完以下指令之后,发送写失能,WEL就等于0,其次是页编程、扇区擦除等。在进行任何写操作之前,都得来一遍写使能,写完会自动写失能。一个写使能,只能保证后续的一条写指令可以执行。
指令集:
写使能,指令码是06,要想发送写使能指令,先起始然后交换一个字节,第一个字节是发送方向,发送0x06指令,这个指令不需要后续跟数据,直接停止。写失能是0x04。
读状态寄存器1,起始,0x05,S0是BUSY位,S1是WEL位。
页编程,就是写数据,起始,0x02,3个字节地址,1个字节数据,如果继续交换写入的话,后续的字节就从起始地址开始依次存储
扇区擦除,起始,0x20,3个字节地址,终止,发送之后,这个指定地址所在的扇区就会被整个擦除,我们一般会把这个地址对齐到扇区的首地址。
JEDEC ID,读ID号,起始,0x9F,随后连续交换读取3个字节,终止。第一个字节是厂商ID,后两个字节是设备ID,这是读取ID号的指令。
读取数据,起始,0x03,3个字节的地址,然后交换读取,这个数据就是这个地址下的数据,如果继续进行交换读取,后面数据就是从指定地址开始依次读存储的数据,这个读取没有页的限制。
------------软件SPI读写W25Q64
对于主机,时钟、主机输出和片选都是输出引脚,都配置为推挽输出模式,主机输入则配置为上拉输入
对于模式0,是先SS下降沿或SCK下降沿,再将数据移除,下降沿是触发数据移出这个动作的条件。对于硬件SPI来说,由于使用了硬件的移位寄存器电路,所以这两个动作几乎是同时发生的。而对于软件SPI来说,由于程序是一步一步执行的,就不能同时完成两个动作。
设备ID高8位,表示存储器类型,低8位表示容量
----------SPI通信外设
16位数据帧相当于连续发两个8位数据帧
SPI和I2C都采用了高位先行,而串口采用低位先行
SPI1挂载在APB2,PCLK是72M,SPI2是挂载在APB1,PCLK是36M
I2S是一种音频传输协议,一种数字音频信号传输的专用协议
图示为低位先行,框中的交叉为用来进行主从模式引脚变换,我们的SPI外设,可以做主机也可以做从机 ,LSBFIRST决定高位先行还是低位先行
和串口一样,TDR和RDR占用同一个地址,统一叫作DR
总线数据进入TDR,TDR的数据会在移位寄存器没有数据移位时立刻转入移位寄存器,开始移位,这个转入时刻,会置状态寄存器的TXE为1,表示发送寄存器空。当我们检查TXE置1后,下一个数据就可以提前写入到TDR里等候了。一旦上一个数据发完,下一个数据就可以立刻跟进,实现不间断的连续传输。移位寄存器这里一旦有数据过来了,就会自动产生时钟,将数据移出去。在移出的过程中,MISO的数据也会移入,一旦数据移出完成,数据移入也完成了。这时,移入的数据,就会整体地从移位寄存器转入到接收缓冲区RDR。这个时刻,会置状态寄存器的RXNE为1.表示接收寄存器非空。当我们检查RXNE置1后,就要尽快把数据从RDR读出来。在下一个数据到来之前,读出RDR,就可以实现连续接收。否则,如果下一个数据已经收到了,上一个数据还没从RDR读出来,那RDR的数据就会被覆盖,不能实现连续的数据流了。
波特率发生器,主要用来产生SCK时钟,内部就是一个分频器,输入时钟是PCLK,72M或36M,经过分频器之后,输出到SCK引脚。当然这里生成的时钟肯定是和移位寄存器同步的了。每产生一个周期的时钟,移入移出一个bit。
然后右边,CR1寄存器的三个位BR0、BR1、BR2,用来控制分频系数
SPE(SPI ENABLE),是SPI使能,就是SPI_Cmd函数配置的位
BR(Baud Rate)配置波特率,就是SCK时钟的频率
MSTR(Master),配置主从模式,1是主模式,0是从模式,我们一般用主模式
CPOL和CPHA,时钟极性和时钟相位,用来选择SPI的4种模式
SR状态寄存器,TXE发送寄存器空,RXNE接收寄存器非空
CR2寄存器就是一些使能位,比如中断使能,DMA使能,
NSS引脚,SS就是从机选择,低电平有效,所以前面加了个N,这里的NSS设计,可能更偏向于实现多主机模型,SS引脚直接使用一个GPIO模拟就行,具体NSS如何实现多主机功能,参考江协科技SPI通信外设19:10.
非连续传输的好处就是容易封装,好理解,好用,但是会损失一点性能;连续传输,传输更快,但是操作起来相对复杂
连续传输的流程是交错的,发送数据2,接收数据1,发送数据3,接收数据2,发送数据4,接收数据3
整个非连续流程就是,第1步,等待TXE为1,第2步,写入发送的数据至TDR,第3步,等待RXNE为1,第4步,读取RDR接收的数据,之后交换第二个字节,重复这4步
------------------------硬件SPI读写W25Q64
第一步,开启时钟,开启SPI和GPIO的时钟
第二步,初始化GPIO,其中SCK和MOSI,是由硬件外设控制的输出信号,配置为复用推挽输出;MISO是硬件外设的输入信号,配置为上拉输入,因为输入设备可以有多个,所以不存在复用输入这个东西,普通GPIO口可以输入,外设也可以输入,还有SS引脚,是软件控制的输出信号,配置为通用推挽输出。
第三步,配置SPI外设,使用一个结构体选参数即可,调用SPI_Init,比如8位/16位数据帧、高位先行/低位先行、SPI模式几、主机还是从机等等
第四步,开关控制,调用SPI_Cmd,给SPI使能
//恢复缺省配置
void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
//初始化
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
/结构体变量初始化
void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct);
//使能
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
//中断使能
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
//DMA使能
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
//写DR数据寄存器,写到TDR
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
//读DR数据寄存器,接受数据寄存器RDR
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
//NSS引脚配置
void SPI_NSSInternalSoftwareConfig(SPI_TypeDef* SPIx, uint16_t SPI_NSSInternalSoft);
void SPI_SSOutputCmd(SPI_TypeDef* SPIx, FunctionalState NewState);//8位或16位数据帧的配置
void SPI_DataSizeConfig(SPI_TypeDef* SPIx, uint16_t SPI_DataSize);
//CRC校验的配置
void SPI_TransmitCRC(SPI_TypeDef* SPIx);
void SPI_CalculateCRC(SPI_TypeDef* SPIx, FunctionalState NewState);
uint16_t SPI_GetCRC(SPI_TypeDef* SPIx, uint8_t SPI_CRC);
uint16_t SPI_GetCRCPolynomial(SPI_TypeDef* SPIx);//半双工时,双向线的方向配置
void SPI_BiDirectionalLineConfig(SPI_TypeDef* SPIx, uint16_t SPI_Direction);
//获取标志位和清楚标志位的函数
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
写入TDR的数据在TXE置1时刻进入移位寄存器,一旦移位寄存器有数据了,时序波形就会自动产生,这个波形生成,不需要我们调用什么函数让它开始传输,只需要写入数据到TDR就行。
硬件SPI,必须是发送,同时接收,要想接收,就必须得先发送,因为只有你给TDR写东西,才会触发时序的生成,如果你不发送,只调用接收函数,那时序是不会动的
TXE和RXNE标志会被自动清除,无需手动清除