您的位置:首页 > 新闻 > 会展 > 最好看免费观看_深圳网站seo优化排名公司_淄博网站制作_网站打开速度优化

最好看免费观看_深圳网站seo优化排名公司_淄博网站制作_网站打开速度优化

2025/7/2 2:57:28 来源:https://blog.csdn.net/m0_63690570/article/details/142867817  浏览:    关键词:最好看免费观看_深圳网站seo优化排名公司_淄博网站制作_网站打开速度优化
最好看免费观看_深圳网站seo优化排名公司_淄博网站制作_网站打开速度优化

SPI通信

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

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

SS线是用来选择和哪个从机通信,低电平有效。

同步,全双工

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

硬件电路

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

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

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

移位设计图

移位寄存器有一个时钟输入端,因为SPI是高位先行,所以每来一个时钟,移位寄存器就会向左进行移位,从机中的移位寄存器也是同理。

移位寄存器的时钟源是由主机提供的,叫波特率发生器,它产生的时钟驱动主机的移位寄存器移位,同时,这个时钟也通过SCK引脚进行输出,接到从机的移位寄存器里。主机左边移位寄存器移出的数据通过MOSI引脚,输入到从机移位寄存器的右边;从机移位寄存器左边移出的数据,通过MISO引脚,输入到主机移位寄存器的右边,这样组成一个圈。

波特率发生器时钟的上升沿,所有移位寄存器向左移动一位,移出去的位放到引脚上,波特率时钟发生器的下降沿,引脚上的位,采样输入到移位寄存器的最低位。

这种方法就是下面的模式1。

SPI时序基本单元

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

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

模式1

换一个字节(模式1)。

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

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

第一个边沿就是SCK的上升沿,主机和从机同时移出数据,主机通过MOSI移出最高位,此时MOSI的电平就表示了主机要发送数据的B7,从机通过MISO移出最高位,此时MISO表示从机要发送数据的B7。然后时钟运行,产生下降沿,此时主机和从机同时移入数据,也就是进行数据采样,这里主机移出的B7,进入从机移位寄存器的最低位,从机移出的B7,进入主机移位寄存器的最低位,这样,一个时钟脉冲产生完毕,一个数据位传输完毕。依次移出、移入,最后数据传输完成之后,MOSI电平可以任意,MISO必须置回高阻态

因为有多个从机输出连在一起,如果同时开启输出,会造成冲突。所以在SS的未被选中状态,从机的MISO引脚必须关断输出,即配置输出为高阻状态。在下图中,MISO处于中间,在SS下降沿之后,从机的MISO被允许开启输出,SS上升沿之后,从机的MISO必须置回高阻态。

 模式0

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

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

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

在第一个边沿移入数据,但是要先移出数据才能移入数据,所以在第一个上升沿之前就要提前移出数据,或者称为在第0个边沿移出,第1个边沿移入。所以在MOSI和MISO的输出是对齐SS的下降沿的。

如果要采样多个字节,在第一个字节的结束,也就是最后一个下降沿,主机放一个字节的B7,从机也放下一个字节的B7,SCK上升沿,正好接着采样第二个字节的B7

模式2

交换一个字节(模式2

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

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

模式3

交换一个字节(模式3

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

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

SPI时序

使用SPI模式0

发送指令

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

                                  

定地址写

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

第一个字节是发送指令,第二、三、四个字节是指定地址,第五个字节是写入的数据。

定地址读

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

第一个字节表示读取数据,之后三个字节表示指定的地址,因为是读取数据,所以之后要接收数据,然后通过交换数据,把从机的数据传给主机。

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

硬件电路

W25Q64框图

8MB先划分成128块(每一块64KB);然后每一块又划分成16个扇区,每一个扇区4KB;扇区里再进行划分成页,页的大小是256个字节,一个扇区是4KB,所以一个扇区可以分成16页。页就是每一行,如从000000h到0000FFh。

Status Register状态寄存器,比如芯片是否处于忙状态、是否写使能、是否写保护都可以在这个状态寄存器里体现。

Write Control Logic写控制逻辑,和外部的WP引脚相连,可以配合WP引脚实现硬件写保护的。

Page Address Latch / Counter页地址锁存/计数器,Byte Address Latch / Counter字节地址锁存/计数器,这两个地址锁存/计数器就是用来指定地址的。总共发过来3个字节地址,因为一页是256字节,所以一页内的字节地址,就取决于最低一个字节,而高位的两个字节就对应页地址。所以前两个字节会进入到页地址锁存/计数器,最后一个字节会进到字节地址锁存/计数器,然后页地址通过写保护和行解码,来选择要操作哪一页,字节地址,通过这个列解码和256字节页缓存来进行指定字节的读写操作。又因为地址锁存都是有一个计数器的,所以这个地址指针,在读写之后,可以自动加1,这样就可以实现,从指定地址开始,连续读写多个字节的目的了。

Flash操作注意事项

写入操作时:

  • 写入操作前,必须先进行写使能。就使用SPI发送一个写使能的指令。
  • 每个数据位只能由1改写为0,不能由0改写为1。所以只能通过擦除重新写入别的数据。
  • 写入数据前必须先擦除,擦除后,所有数据位变为1。
  • 擦除必须按最小擦除单元进行。
  • 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入。因为有一个页缓存区,只能写入256个字节。缓存区的原因是Flash的写入太慢了,跟不上SPI的频率,所以写入的数据会先放到RAM里暂存,等时序结束后,芯片再慢慢地把数据写入到Flash里。
  • 写入操作结束后,芯片进入忙状态,不响应新的读写操作。这段时间要把页缓存区的内容写到Flash里,在这段时间状态寄存器的BUSY为1。

读取操作时:

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

软件SPI读写W25Q64

接线图

程序流程

在发送一个0x9Fh,表示要获取ID号,之后交换字节,第一个字节是厂商ID,第二个字节是设备ID的高八位,第三个字节是设备ID的低八位。

代码

W25Q64.c

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"void W25Q64_Init(void)
{MySPI_Init();
}void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{MySPI_Start();MySPI_SwapByte(W25Q64_JEDEC_ID);*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 获取厂商ID*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 获取设备ID的高八位*DID <<= 8;*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 获取设备ID的低八位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();
}

W25Q64_Ins.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

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"uint8_t MID;
uint16_t DID;uint8_t ArrayWrite[] = {0x55, 0x66, 0x77, 0x88};
uint8_t ArrayRead[4];int main(void)
{OLED_Init();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, DID, 4);W25Q64_SectorErase(0x000000);  // 如果不擦除,0是不能改写为1的,1可以改写为0W25Q64_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){}
}

SPI通信外设

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

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

时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)。

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

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

支持DMA。

兼容I2S协议。

STM32F103C8T6 硬件SPI资源:SPI1SPI2。

SPI框图

移位寄存器右边的数据低位,一位一位地,从MOSI移出去,然后MISO的数据一位一位地移入到左边的数据高位,显然移位寄存器是一个右移的状态,图上显示的是低位先行,右下角的控制位LSBFIRST可以控制是低位先行还是高位先行,给1是低位先行,给0是高位先行。

发送缓冲区是发送数据寄存器TDR,接收缓冲区是接收数据寄存器RDR。写入DR时,数据写入到TDR,读取DR时,数据从RDR读出。

如果需要连续发送一批数据,第一个数据写入到TDR,当移位寄存器没有数据移位时,TDR的数据会立刻转入移位寄存器,开始移位,转入时刻会置状态寄存器的TXE为1,表示发送寄存器空,当TXE置1后下一个数据就可以提前写入到TDR里候着了,一旦上一个数据发完,下一个数据就可以立刻跟进,实现不间断的连续传输。

移位寄存器那里一旦有数据过来,就会自动产生时钟,把数据移出去,在移出的过程中,MISO的数据也会移入,当数据交换完成,移位进入的数据就会整体的从移位寄存器转入到接收缓冲区RDR,这个时刻会置状态寄存器的RXNE为1,表示接收寄存器非空,当检查到RXNE置1后,就要尽快把数据从RDR读出来,在下一个数据到来之前,读出RDR,就可以实现连续接收,否则RDR的数据会被覆盖。

波特率发生器是用来产生SCK时钟的,它的内部主要就是一个分频器,输入时钟是PCLK,72M或36M,经过分频器之后,输出到SCK引脚。

CR1寄存器:

LSBFIRST是控制高位先行还是低位先行;SPE是SPI使能,就是SPI_Cmd函数配置的位;BR0、BR1、BR2用来控制分频系数。;MSTR配置主从模式,1是主模式,0是从模式;CPOL和CPHA,用来选择SPI的4种模式。

SR状态寄存器:

TXE发送寄存器空,RXNE接收寄存器非空。

NSS引脚,SS就是从机选择,低电平有效,所以前面加了一个N,这个引脚更偏向于实现多主机模型。

SPI基本结构

TDR数据整体转入移位寄存器的时刻,置TXE标志位;移位寄存器数据整体转入RDR的时刻,置RXNE标志位。

波特率发生器产生时钟,输出到SCK引脚;数据控制器就看成一个管理员,它控制着所有电路的运行;最后开关控制,就是SPI_Cmd,初始化之后,给个ENABLE,使能整个外设。

主模式全双工连续传输(入门先不用)

CPOL=1,CPHA=1,使用的是SPI模式3,所以SCK默认是高电平。在SCK的第一个下降沿,MOSI和MISO移出数据,之后上升沿移入数据,依次这样进行。第二行是MOSI和MISO输出的波形,从前到后依次出现的是b0,b1,...b7,示例演示的是低位先行的模式。

首先SS置低电平,开始时序,刚开始TXE为1,表示TDR为空,可以写入数据开始传输,下面指示的第一步是软件写入0xF1至SPI_DR,0xF1就是要发送的第一个数据,写入之后,TDR变为0xF1,同时TXE变为0,表示TDR已经有数据了,然后将TDR里的数据转入到移位寄存器进行发送,转入瞬间,置TXE标志为1,表示发送寄存器空,之后,数据F1的波形就开始产生了。在移位产生F1波形的同时,等候区TDR是空的,为了移位完成时,下一个数据能不间断地跟随,这里我们就要提早把下一个数据写入到TDR里等着,写入F1之后,软件等待TXE=1,一旦TDR空了,就写入F2至SPI_DR,写入之后TDR的内容就变成F2了。之后类似。

在第一个字节发送之后,第一个字节的接收也完成了,接收的数据是A1,数据整体转入RDR,RDR随后存储的就是A1,转入的同时,RXNE标志位也置1,表示收到数据了,接收到数据之后,软件清除RXNE标志位,后面类似。

非连续传输

模式3。检测到TXE为1,TDR为空,就软件写入0xF1至SPI_DR,这时TDR的值变为F1,TXE变为0。目前移位寄存器也是空,这个F1会立刻转入移位寄存器开始发送,波形产生,TXE置回1,表示可以把下一个数据放到TDR候着了。在非连续传输这里,TXE=1了,不急着把下一个数据写进去,而是一直等待,等第一个字节时序结束,接收也完成了,这时接收的RXNE也会置1,等RXNE置1后,先把第一个接收到的数据读出来,之后再写入下一个字节数据0xF2到TDR,之后数据2开始发送,等把接收的数据2收着,再继续写入数据3到TDR。

1. 等待TXE为1。

2. 写入发送的数据至TDR

3. 等待RXNE为1

4. 读取RDR接收的数据

硬件SPI读写W25Q64

接线图

程序流程

  1. 开启时钟,开启SPI喝GPIO的时钟。
  2. 初始化GPIO口,其中SCK和MOSI,是由硬件外设控制的输出信号,配置为复用推挽输出,MISO是硬件外设的输入信号,可以配置为上拉输入,SS是软件控制的输出信号,配置为通用推挽输出,GPIO口的初始化配置。
  3. 配置SPI外设,使用一个结构体选参数即可。
  4. 开关控制,调用SPI_Cmd,给SPI使能即可。

库函数

// SPI_Init初始化
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
// SPI_Cmd外设使能
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
// 写DR数据寄存器
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
// 读DR数据寄存器
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

代码

因为只要修改底层的代码,所以这里只修改了MySPI.c。

MySPI.c:

#include "stm32f10x.h"                  // Device headervoid MySPI_W_SS(uint8_t BitValue)  // 写SS的引脚
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}void MySPI_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);// PA6是主机输入,其他三个都是推挽输出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_AF_PP;GPIO_InitStructure.GPIO_Pin = 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);SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_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;SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 选择SPI的模式,SPI主机还是从机SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_Init(SPI1, &SPI_InitStructure);SPI_Cmd(SPI1, ENABLE);MySPI_W_SS(1);
}void MySPI_Start(void) // 起始信号就是SS置低电平
{MySPI_W_SS(0);
}void MySPI_Stop(void) // 起始信号就是SS置高电平
{MySPI_W_SS(1);
}uint8_t MySPI_SwapByte(uint8_t ByteSend)
{while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);SPI_I2S_SendData(SPI1, ByteSend);while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);return SPI_I2S_ReceiveData(SPI1);
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com