STM32-SPI和W25Q64

本内容基于江协科技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引脚
  • 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入

image.png

  • SCK时钟线由主机控制,对主机来说,时钟线为输出,对所有从机来说,时钟线为输入;
  • MOSI:主机是MO,主机输出,从机输入。数据通过MOSI线由主机输出,从机输入。
  • MISO:主机输入从机输出。三个从机通过MISO输出,主机通过MISO输入。当从机的SS引脚是高电平时,它的MISO引脚,必须切换为高阻态,相当于引脚断开,不输出任何电平。这样就可以防止,一条线有多个输出,而导致的电平冲突问题。在SS为低电平时,MISO才允许变为推挽输出。
  • SS线低电平有效。

1.3 移位示意图

image.png

  • 移位寄存器有一个时钟输入端,SPI一般都是高位先行的,所以,每来一个时钟,移位寄存器都会向左进行移位。移位寄存器的时钟源是由主机提供的,这里叫做波特率发生器,它产生的时钟驱动主机的移位寄存器进行移位。同时,这个时钟也通过SCK引脚进行输出,接到从机的移位寄存器里。
  • 主机移位寄存器左边移出去的数据通过MOSI引脚,输入到从机移位寄存器的右边。从机移位寄存器左边移出去的数据,通过MISO引脚,输入到主机移位寄存器的右边。
  • 波特率发生器时钟的上升沿,所有移位寄存器向左移动一位,移出去的位放到引脚上。波特率发生器时钟的下降沿,引脚上的位,采样输入到移位寄存器的最低位。
  • SPI通信的基础是交换一个字节,可以实现发送一个字节、接收一个字节、发送同时接收一个字节。

1.4 SPI时序基本单元

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

image.png

  • 交换一个字节(模式0)

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

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

    • CPOL(Clock Polarity)时钟极性
    • CPHA(Clock Phase)时钟相位:决定是第一个时钟采样移入还是第二个时钟采样移入,
      image.png
  • 交换一个字节(模式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引脚的电平不确定。
      image.png
  • 交换一个字节(模式2)

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

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

image.png

  • 交换一个字节(模式3)
  • CPOL=1:空闲状态时,SCK为高电平
  • CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

image.png

1.5 SPI时序

1.5.1 发送指令

  • 发送指令
  • 向SS指定的设备,发送指令(0x06)。0x06是写使能指令,

SPI采用指令码加读写数据的模型。SPI起始后,第一个交换发送给从机的数据一般叫做指令码,在从机中,对应的会定义一个指令集,当需要发送什么指令时,就可以在起始后第一个字节,发送指令集里面的数据,这样就能指导从机完成相应的功能了。不同的指令,可以有不同的数据个数。有的指令,只需要一个字节的指令码就可以完成。而有的指令,后面就需要再跟要读写的数据。

image.png

1.5.2 指定地址写

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

image.png

1.5.3 指定地址读

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

image.png

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 硬件电路

image.png

  • CS:低电平有效。
  • WP:写保护,配合内部的寄存器配置,可以实现硬件的写保护。低电平有效,WP接低电平,保护住,不让写;WP接高电平,不保护,可以写。
  • C1:滤波;R1和D1电源指示灯,接通电源就亮。

2.3 W25Q64框图

image.png

  • 存储器以字节为单位,每个字节都有唯一的地址。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框图

image.png

  • 移位寄存器:右边的数据低位一位一位地从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基本结构

image.png

3.3 主模式全双工连续传输

image.png
示例使用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 非连续传输

image.png
模式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 软件 / 硬件波形对比

image.png

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);



相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 19:20:05       51 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 19:20:05       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 19:20:05       44 阅读
  4. Python语言-面向对象

    2024-07-10 19:20:05       55 阅读

热门阅读

  1. 软件工程需求之:业务需求与用户需求

    2024-07-10 19:20:05       15 阅读
  2. 学习数据库的增删改查

    2024-07-10 19:20:05       17 阅读
  3. oracle 数据更新procedure 模板

    2024-07-10 19:20:05       20 阅读
  4. 【LeetCode 0050】【分治/递归】求x的n次方

    2024-07-10 19:20:05       20 阅读
  5. Qt图形编辑类使用总结—正在编辑中

    2024-07-10 19:20:05       12 阅读