本内容基于江协科技STM32视频学习之后整理而得。
文章目录
1. SPI(串行外设接口)通信
1.1 SPI通信简介
- SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
- 四根通信线:SCK(Serial Clock)串行时钟线、MOSI(Master Output Slave Input)主机输出从机输入、MISO(Master Input Slave Output)主机输入从机输出、SS(Slave Select)从机选择
- 同步,全双工(数据的发送和接收单独用一条线)
- 支持总线挂载多设备(一主多从)
1.2 硬件电路
- 所有SPI设备的SCK、MOSI、MISO分别连在一起
- 主机另外引出多条SS控制线,分别接到各从机的SS引脚
- 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
- SCK时钟线由主机控制,对主机来说,时钟线为输出,对所有从机来说,时钟线为输入;
- MOSI:主机是MO,主机输出,从机输入。数据通过MOSI线由主机输出,从机输入。
- MISO:主机输入从机输出。三个从机通过MISO输出,主机通过MISO输入。当从机的SS引脚是高电平时,它的MISO引脚,必须切换为高阻态,相当于引脚断开,不输出任何电平。这样就可以防止,一条线有多个输出,而导致的电平冲突问题。在SS为低电平时,MISO才允许变为推挽输出。
- SS线低电平有效。
1.3 移位示意图
- 移位寄存器有一个时钟输入端,SPI一般都是高位先行的,所以,每来一个时钟,移位寄存器都会向左进行移位。移位寄存器的时钟源是由主机提供的,这里叫做波特率发生器,它产生的时钟驱动主机的移位寄存器进行移位。同时,这个时钟也通过SCK引脚进行输出,接到从机的移位寄存器里。
- 主机移位寄存器左边移出去的数据通过MOSI引脚,输入到从机移位寄存器的右边。从机移位寄存器左边移出去的数据,通过MISO引脚,输入到主机移位寄存器的右边。
- 波特率发生器时钟的上升沿,所有移位寄存器向左移动一位,移出去的位放到引脚上。波特率发生器时钟的下降沿,引脚上的位,采样输入到移位寄存器的最低位。
- SPI通信的基础是交换一个字节,可以实现发送一个字节、接收一个字节、发送同时接收一个字节。
1.4 SPI时序基本单元
- 起始条件:SS从高电平切换到低电平
- 终止条件:SS从低电平切换到高电平
交换一个字节(模式0)
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
- CPOL(Clock Polarity)时钟极性
- CPHA(Clock Phase)时钟相位:决定是第一个时钟采样移入还是第二个时钟采样移入,
交换一个字节(模式1)
CPOL=0:空闲状态时,SCK为低电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
- SS为高电平时,MISO为高阻态。SS下降沿之后,从机的MISO被允许开启输出;SS上升沿之后,从机的MISO必须置回高阻态。
- SCK上升沿时,主机和从机同时移出数据,主机通过MOSI移出最高位,此时MOSI的电平就表示了主机要发送数据的B7;从机通过MISO移出最高位,MISO的电平就表示了从机要发送数据的B7,时钟运行产生下降沿,此时主机和从机同时移入数据,也就是进行数据采样,主机移出的B7进入从机移位寄存器的最低位,从机移出的B7进入主机移位寄存器的最低位。这样,一个时钟脉冲产生完毕,一个数据位传输完毕。当主机和从机完成了一个字节的数据交换后,如果主机只想交换一个字节,那这时可以置SS为高电平,结束通信。在SS的上升沿,MOSI还可以再变化一次,将MOSI置到一个默认的高电平或低电平,但MISO,从机必须置回高组态,如果主机的MISO为上拉输入的话,则MISO引脚的电平就是默认的高电平,如果主机MISO为浮空输入,则MISO引脚的电平不确定。
交换一个字节(模式2)
CPOL=1:空闲状态时,SCK为高电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
- 交换一个字节(模式3)
- CPOL=1:空闲状态时,SCK为高电平
- CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
1.5 SPI时序
1.5.1 发送指令
- 发送指令
- 向SS指定的设备,发送指令(0x06)。0x06是写使能指令,
SPI采用指令码加读写数据的模型。SPI起始后,第一个交换发送给从机的数据一般叫做指令码,在从机中,对应的会定义一个指令集,当需要发送什么指令时,就可以在起始后第一个字节,发送指令集里面的数据,这样就能指导从机完成相应的功能了。不同的指令,可以有不同的数据个数。有的指令,只需要一个字节的指令码就可以完成。而有的指令,后面就需要再跟要读写的数据。
1.5.2 指定地址写
- 指定地址写
- 向SS指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0])下,写入指定数据(Data)
1.5.3 指定地址读
- 指定地址读
- 向SS指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)
2. W25Q64
2.1 W25Q64简介
W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景
存储介质:Nor Flash(闪存)
时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
存储容量(24位地址):
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByte
W25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte
W25Q256: 256Mbit / 32MByte
2.2 硬件电路
- CS:低电平有效。
- WP:写保护,配合内部的寄存器配置,可以实现硬件的写保护。低电平有效,WP接低电平,保护住,不让写;WP接高电平,不保护,可以写。
- C1:滤波;R1和D1电源指示灯,接通电源就亮。
2.3 W25Q64框图
- 存储器以字节为单位,每个字节都有唯一的地址。W25Q64的地址宽度是24位,3个字节。24位地址,最大寻址范围是16MB,该芯片只有8MB,所以地址空间只用了一半。8MB的空间排到最后一个字节就是7F FF FF。
- 在整个空间中以64KB为一个基本单元,划分为若干的块Block,
8*1024/64=128
块。每一块再进行更细的划分,以4KB进行划分,分为16个扇区Sector。页是256个字节,一个扇区是4KB,以256个字节划分,得到4*1024/256=16页。每一行为一页,一页内的地址变化,仅限于地址的最低一个字节。 - 控制逻辑就是整个芯片的管理员,控制逻辑左边是SPI的通信引脚,这些引脚与主控芯片进行连接,主控芯片通过SPI协议,把指令和数据发给控制逻辑,控制逻辑就会自动去操作内部电路来完成想要的功能。
- 控制逻辑上面的状态寄存器,比如芯片是否处于忙状态、是否写使能、是否写保护等都可以在这个状态寄存器里体现。
- 写控制逻辑与外部的WP引脚相连,是配合WP引脚实现硬件写保护的。
- 高电压生成器:是配合Flash进行编程的,因为Flash是掉电不丢失的,所以需要一个高压源。
- 页地址锁存/计数器、字节地址锁存/计数器:用来指定地址的,
- 通过SPI总共发过来3个字节的地址。因为一页是256字节,所以一页内的字节地址就取决于最低一个字节,而高位的2个字节就对应的是页地址。
- 所以发过来的3个字节的前2个字节会进到页地址锁存计数器里,最后一个字节会进到字节地址锁存计数器里。页地址通过写保护和行解码来选择要操作哪一页。字节地址通过这个列解码和256字节页缓存来进行指定字节的读写操作。又因为地址锁存都是有一个计数器的,所以这个地址指针在读写之后可以自动加1,因此可以实现从指定地址开始,连续读写多个字节的目的。
256字节的页缓存区是一个256字节的RAM存储器,读写是通过这个RAM缓存区来进行的,写入的数据会先放到缓存区里,然后在时序结束后,芯片再将缓存区的数据复制到对应的Flash里,进行永久保存。
为什么数据要先进入缓存区呢?是因为SPI写入的频率是非常高的,
2.4 Flash操作注意事项
写入操作时:
- 写入操作前,必须先进行写使能
- 每个数据位只能由1改写为0,不能由0改写为1
- 写入数据前必须先擦除,擦除后,所有数据位变为1
- 擦除必须按最小擦除单元(4096个字节)进行
- 连续写入多字节时,最多写入一页(256字节(RAM缓存区))的数据,超过页尾位置的数据,会回到页首覆盖写入
- 写入操作结束后,芯片进入忙状态,不响应新的读写操作
读取操作时:
- 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取
2.5 手册指令集
- Write Enable 写使能:06,先起始,再交换一个字节,第一个字节是发送方向,发送0x06指令
- Write Disable写失能:04,起始,交换字节发送指令码04,终止。
- Read Status Register-1 读状态寄存器1:05,起始,交换字节发送指令码05,要读数据,继续交换字节,通过交换读取一个字节,该字节就是状态寄存器的S7-S0,S0是BUSY位,S1是WEL位,主要用来查看忙状态的
- Page Program页编程:02,就是写数据,起始、交换字节发送指令02,然后继续交换发送地址的23-16位、15-8位、7-0位,这三个字节用来指定地址,再之后,就可以写入数据D7-D0,该数据写入到刚才指定的地址下。如果继续交换写入的话,后续的字节就从起始地址开始依次存储,
Sector Erase(4KB)扇区擦除:20,起始,交换字节发送指令20,之后再交换发送3个字节的地址,终止。发送之后,这个指定地址所在的扇区就会被整个擦除。 - JEDEC ID 读取ID号:9F,起始,交换发送9F,随后继续交换读取3个字节,终止。第一个字节是厂商ID,后两个字节是设备ID,
- Read Data 读取数据:03,起始,交换发送指令03,之后交换发送3个字节的地址,再之后交换读取数据,该数据是3个字节地址下的数据。再继续读取,后面数据就是从指定地址开始依次读存储的数据。
3. SPI外设简介
- STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
- 可配置8位/16位数据帧、高位先行/低位先行
- 时钟频率:fPCLK / 分频系数 = fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
- APB2的PCLK是72MHz,APB1的PCLK是36MHz。
- 支持多主机模型、主或从操作
- 可精简为半双工/单工通信
- 支持DMA
- 兼容I2S协议
- STM32F103C8T6 硬件SPI资源:SPI1(挂载在APB2)、SPI2(挂载在APB1)
3.1 SPI框图
- 移位寄存器:右边的数据低位一位一位地从MOSI移出去,
MISO的数据一位一位地移入到左边的数据高位。 - LSBFIRST是控制低位先行还是高位先行,给0,先发送MSB即高位,给1,先发送LSB即低位
MOSI与MISO的交叉方框是主要用来进行主从模式引脚变换的。这个SPI外设可以做主机也可以做从机,做主机时,这个交叉就不用,MOSI为MO,主机输出;MISO为MI,主机输入。STM32做从机时,MOSI为SI,从机输入,走交叉路线输入到移位寄存器,MISO为SO,从机输出,也走交叉路线,移位寄存器输出到MISO。 - 接收缓冲区就是接收数据寄存器RDR,发送缓冲区实际上就是发送数据寄存器TDR。TDR和RDR占用同一个地址,统一叫做DR。当要发送一系列数据流时,数据写入发送缓冲区,当移位寄存器没有数据移位时,发送缓冲区的数据会立刻转入移位寄存器,开始移位。这个转入时刻会置状态寄存器的TXE为1,表示发送寄存器空。当检查到TXE为1时,下一个数据,就可以提前写入到TDR里了,一旦上个数据发完,下一个数据就可以立刻跟进,实现不间断的连续传输。移位寄存器一旦有数据进来了,就会自动产生时钟,将数据移出去,在移出去的过程中,MISO的数据也会移入,一旦数据移出完成,数据移入也完成。这时,移入的数据就会整体的从移位寄存器转入到接收缓冲区RDR,这个时刻会置状态寄存器的RXNE为1,表示接收寄存器非空。当检查RXNE置1后,就要尽快把数据从RDR读出来,在下一个数据到来之前,读出RDR,可以实现连续接收。否则,如果下一个数据已经收到了,上一个数据还没从RDR读出来,那么RDR的数据就会被覆盖,就不能实现连续的数据流了。
- 波特率发生器用来产生SCK时钟的,内部主要是一个分频器。
- CR1寄存器的三个位BR0、BR1、BR2,用来控制分频系数。
- SPE是SPI使能,就是SPI_Cmd函数配置的位。
- BR配置波特率,就是SCK时钟频率。
- MSTR配置主从模式,1是主模式,0是从模式。
- CPOL和CPHA用来选择SPI的4种模式。
- SR寄存器:TXE为发送寄存器空,RXNE为接收寄存器非空
- CR2寄存器:主要是使能位,如中断使能、DMA使能
- NSS:SS就是从机选择,低电平有效,所以这里前面加了个N。
3.2 SPI基本结构
3.3 主模式全双工连续传输
示例使用SPI模式3。
- SCK默认是高电平,在第一个下降沿,MOSI和MISO移出数据。上升沿移入数据。
- SS置低电平开始时序,在刚开始时,TXE为1,表示TDR空,
- 可以写入数据,指示就是软件写入0xF1至SPI_DR,0xF1就是要发送的第一个数据,写入之后TDR(发送缓冲器)变为0xF1,同时TXE=0,此时,TDR是等候区,移位寄存器才是真正的发送区。等候区TDR中的0xF1就会立刻转入移位寄存器,开始发送。转入瞬间置TXE标志为1,表示发送寄存器空。然后移位寄存器有数据了,波形就自动开始生成 (MISO/MOSI(输出)),数据F1的波形就开始产生了,在移位产生F1波形的同时,等候区TDR是空的,为了移位完成时,下一个数据能不间断地跟随,这里就要提早把下一个数据写入到TDR里等着了,
- 写入F1后,软件等待TEX=1,然后写入0xF2至SPI_DR。写入之后,TDR的内容变成F2了。然后F1数据波形产生完毕后,F2转入移位寄存器开始发送,这时TXE=1,就尽快把下一个数据F3放到TDR里等着(软件等待TEX=1,然后写入0xF3至SPI_DR).如果只想发送3个数据,F3转入移位寄存器之后,TXE=1,就不需要再继续写入了。在最后一个TXE=1之后,还要继续再等待一段时间,F3的波形才能完整发送完,等波形完全发送完之后,BUSY标志由硬件清除,这才表示波形发送完了。
- SPI是全双工,发送的同时,还有接收。
- 在第一个字节发送完成后,第一个字节的接收也完成了。接收到的数据1是A1,这时移位寄存器的数据整体转入RDR,RDR随后存储的就是A1。转入的同时RXNE标志位也置1,表示收到数据了(软件等到RXNE=1,然后从SPI_DR读出A1),接收之后,软件清除RXNE标志位,当下一个数据A2收到后,RXNE重新置1。当监测到RXNE=1时,就继续读出RDR,即第二个数据A2。在最后一个字节时序完全产生之后,数据3才能收到。一个字节波形收到后,移位寄存器的数据自动转入RDR,会覆盖原有的数据,所以读取RDR要及时。
3.4 非连续传输
模式3,SCK默认高电平。如果你检测到TXE=1了,TDR为空,就软件写入0xF1至SPI_DR,这时TDR的值变为F1,TXE变为0,目前移位寄存器也是空,F1会立刻转入移位寄存器开始发送,波形产生,并且TXE置1,表示可以把下一个数据放在TDR里候着了。等到第一个字节时序结束,也就是第一个字节接收完成,这时接收的RXNE置1,先把第一个接收到的数据读出来,之后再写入下一个字节数据(软件等待TXE=1,但是较晚写入0xF2至SPI_DR),之后数据2开始发送,等先把接收的数据2收着,再继续写入数据3。数据3时序结束后,再接收数据3置换回来的数据。
1、 等待TXE=1;2. 写入发送的数据至TDR,3. 等待RXNE=1,4. 读取RDR接收的数据。之后交换第二个字节,重复该4步。
3.5 软件 / 硬件波形对比
3.6 库函数
// 恢复缺省配置
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数据寄存器
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
// 读DR数据寄存器
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
// 可以获取TXE和RXNE标志位的状态
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);