STM32(F103ZET6)第十八课:SPI&Flash

项目需求

本项目主要时SPI用于FLASH存储器的应用。通过SPI控制FLash进行数据的保存和删除。
在这里插入图片描述

一、SPI概要

在我们使用UART(通用串行异步通讯协议)时,因为UART没有时钟信号,速度不同,无法控制何时收发数据。非要解决这个问题的话,需要为UART传输的数据添加起始位和停止位,双方波特率还需同步,很麻烦,比较不好。于是由摩托摩拉公司牵头推出了一种新的通讯总线 SPI:串行外设接口。
SPI是一个高速、全双工、同步的串行通信总线。应用场景:OLED屏幕FLASH存储器、AD转换器
通信方式:串行同步全双工(就是数据在线上按照时间顺序一位一位的传输,发送和接收时要在通信时钟的同步下进行数据传输,且可以同时发送和接收)该总线就是利用单独的数据线(MISO和MOSI)和单独的时钟信号线(SCK)完美解决了收发端的数据完美同步。
接线:
五线制SPI:全双工通信
MOSI:主设备输出、从设备输入 TX
MISO: 主设备输入、从设备输出 RX
SCK:时钟线
CS:片选信号线 一主多从,拉低选择和哪个从机通信
GND:地线
单机通信:
在这里插入图片描述
多机通信:
在这里插入图片描述
总结:除了SS片选线需要单独备一根线和从机进行一对一的链接,其他都可以一对多,多个从机接同一个主机接口。
通信时序:
1,拉低片选信号线,决定要跟谁通信
2,操作时钟线,让MOSI和MISO准备数据(采样)
3,操作时钟线,让MOSI和MISO发送数据(接收)
4,拉高CS片选信号线,停止通信
SPI通信模式:SPI通信是一个环形传输,主机发送数据的同时,从机也会回复对应的数据。数据发送完成,其实也就接收完成了。
在这里插入图片描述
比较关键的点:发送数据时高位先发,还是低位先发,时钟的边沿,在哪个边沿上进行数据的发送,时钟的极性,时钟默认的电平。时钟的速度。
在这里插入图片描述
CPHA:时钟相位
CPHA=0时,在时钟的第一个边沿进行采样,第二个边沿进行输出
CPHA=1时,在时钟的第二个边沿进行采样,第三个边沿进行输出
CPOL:时钟极性
CPOL=0时,空闲时时钟为低电平CPOL=1时,空闲时时钟为高电平
CPHA=0,CPOL=0:SPI的模式0:上升沿采样、下降沿接受
CPHA=0,CPOL=1:SPI的模式1:下降沿采样、上升沿接受
CPHA=1,CPOL=0:SPI的模式2:下降沿采样、上升沿接受
CPHA=1,CPOL=1:SPI的模式3:上升沿采样、下降沿接受
一般能够支持SPI模式0的设备也支持SPI模式3,支持模式1的设备也支持模式2。SPI通信没有具体的协议格式,格式根据通信对象的要求来定。一般的传输数据时候采用的就是8位进行,高位先传还是低位先传,传输的速度。这些参数都需要根据我们的通信对象来进行配置。
STM32中SPI接口的框图
在这里插入图片描述
寄存器配置步骤
在这里插入图片描述
主要用到的就是SPI_CR1寄存器:
在这里插入图片描述

二、SPI配置

1.开时钟

硬件接口:
在这里插入图片描述
配置对应引脚:
在这里插入图片描述
根据引脚的开SPI2和GPIOB的时钟
在这里插入图片描述

2.配置IO口

配置GPIO:MOSI,SCK:复用推挽输出;MISO:浮空输入 CS:通用推挽输出
由于是主机所以CS当做GPIO配就行(软件模式)从机的话配硬件模式
在这里插入图片描述
最后拉高CS是因为CS处于低电平时表示开始通信了。

3.配置和使能SPI

在这里插入图片描述
在这里插入图片描述
主要配置SPI极性,相位等配置。

三、FLASH应用

本次用到的FLash为W25Q64,是华邦存储推出的一款串行FLASH存储芯片。
空间规格:
64Mbit (兆位)== 8Mbyte(兆字节)
256字节为一页、16页为一扇区、16扇区为一块
写入最小可以1字节1字节写入,删除则最少也要擦除一个扇区。
在这里插入图片描述
地址表示:
0x000000->第0个块的第0个扇区的第0个页的第0个字节
地址划分:0x000000 - 0x7FFFFF总计的地址空间
0x010111 0x023510
黄色:表示第几块
绿色:第0个扇区
蓝色:第1页
紫色:表示第几个字节(0x11 ->17)
W25Q64存储器驱动接口
在这里插入图片描述
接线方式:
CS->CS
DO->MISO
DI->MOSI
CLK->CLK

1.SPI发送数据

发送:
判断发送缓冲器空闲标志(TXE)是否为0,为0表示正在发,此时持续等待数据发完。标志为1表示之前的发送完了,空闲下来了。此时就可以开始往DR发数据。
接收:
判断接收缓冲器非空(RXNE)是否为0,为0表示接受缓冲区为空,没数据,此时持续等待数据到来。标志为1表示数据来了,此时就可以开始读DR的数据了。

/*****************************************************************
 *函 数 名 称:SPI2_SendData
 *函 数 功 能:SPI2发送数据
 *函 数 形 参:data:发送内容
 *函 数 返 回:返回收到的数据
*******************************************************************/
uint8_t SPI2_SendData(uint8_t data)
{
	uint8_t recv = 0;
	while((SPI2->SR & (0x1<<1)) == 0);
	SPI2->DR = data;
	while((SPI2->SR & (0x1<<0)) == 0);
	recv = SPI2->DR;
	return recv;
}

2.FLASH写使能

FLASH存储器,只能写入0,不能写入1,对FLASH擦除操作,其实就是将存储器指定空间全部置1.
06H:写使能:写入和擦除之前都要,发送06H
在这里插入图片描述

/*****************************************************************
 *函 数 名 称:Write_Enable
 *函 数 功 能:FLASH写使能操作
 *函 数 形 参:无
 *函 数 返 回:无
*******************************************************************/
void Write_Enable(void)
{
	//拉低CS
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);
	//发送0x06指令
	SPI2_SendData(0x06);
	//拉高CS
	GPIO_SetBits(GPIOB,GPIO_Pin_12);	
}

3.FLASH等待操作完成

在这里插入图片描述
等待操作步骤:
1.先拉低CS。
2.发送05或35指令。
3.循环往FLash发送数据(无所谓什么数据,只是为了置换出FLash状态寄存器的值),判断寄存器的第0位BUSY是否为0,为0时就代表Flash为空闲状态,可以执行其他操作。
4.判断结束就拉高CS。

/*****************************************************************
 *函 数 名 称:Flash_WaitForWriteEnd
 *函 数 功 能:FLASH等待操作完成
 *函 数 形 参:无
 *函 数 返 回:无
*******************************************************************/
void Flash_WaitForWriteEnd(void)
{
	uint8_t recv = 0;
	//拉低CS
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);
	SPI2_SendData(0x05);//发送读取指令
	do{
		recv = SPI2_SendData(0x55);//循环读取寄存器数据,0x55随便改
	}while((recv & 0x01) == SET);//判断寄存器的第0位是否是1
	//拉高CS
	GPIO_SetBits(GPIOB,GPIO_Pin_12);	
}

4.FLASH页写操作

02H(0x02):
写入数据指令,执行之前先写使能,06H
写入之前先擦除、发送02H之前先发06H指令。先拉低CS,然后发送02H,发送三个字节的地址,然后至少跟一个字节的数据,最多是256字节的数据。写入完成之后要判断一下BUSY标志位。
在这里插入图片描述
1.由于要进行写操作,所以要先进行写使能。
2.拉低CS,发送02指令。
3.发送三个字节,即24位的地址,每次发8位分3次发送。为了告知写入的位置。
4.发送数据,8位8位发,最多256。
5.调用FLash等待函数,等待写入完成。
6.写入完成后,拉高CS。

/*****************************************************************
 *函 数 名 称:Flash_PageProgram
 *函 数 功 能:FLASH页写操作
 *函 数 形 参:add:写入地址 data:写入数据 len:写入数据长度
 *函 数 返 回:无
*******************************************************************/
void Flash_PageProgram(uint32_t add, uint8_t *data,uint16_t len)
{
	//1,先写使能
	Write_Enable();
	//2,拉低CS
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);
	//3,发送命令
	SPI2_SendData(0x02);
	//4,发送三个字节地址 add = 0x123456
	SPI2_SendData((add&0xFF0000)>> 16);
	SPI2_SendData((add&0x00FF00) >> 8);
	SPI2_SendData((add&0x0000FF));
	while(len--)
	{
		SPI2_SendData(*data);
		data++;
	}
	//5,拉高CS
	GPIO_SetBits(GPIOB,GPIO_Pin_12);
	//6,等待操作完成
	Flash_WaitForWriteEnd();
}

5.FLASH读操作

03H:读取操作,先拉低CS、发送03H、发送要读取数据的地址,然后主机发送任意数据,置换出来FLASH中的数据
在这里插入图片描述
读数据步骤:
1.拉低CS,发送03指令。
2.发送3字节读取的地址。
3.随便发送1个字节的数据,返回值就是要获取的数据。
4.拉高CS。

/*****************************************************************
 *函 数 名 称:Flash_ReadData
 *函 数 功 能:FLASH读操作
 *函 数 形 参:add:读取地址 data:保存读取数据 len:读取数据长度
 *函 数 返 回:无
*******************************************************************/
void Flash_ReadData(uint32_t add, uint8_t *data,uint16_t len)
{
	//1,拉低CS
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
	//2,发送命令
	SPI2_SendData(0x03);	
	//3,发送三个字节地址 add = 0x123456
	SPI2_SendData((add&0xFF0000)>> 16);
	SPI2_SendData((add&0x00FF00) >> 8);
	SPI2_SendData((add&0x0000FF));
	while(len--)
	{
		*data = SPI2_SendData(0x55);
		data++;
	}
	//4,拉高CS
	GPIO_SetBits(GPIOB,GPIO_Pin_12);	
}

6.FLASH扇区擦除

在这里插入图片描述
擦除一个扇区步骤:
1.擦除操作实际上也是写入操作,是往Flash中写入0xFF。所以此时也要先开写使能。
2.拉低CS,发送指令20。
3.发送三个字节。
4.拉高电平,等待操作完成

/*****************************************************************
 *函 数 名 称:Flash_SectorErase
 *函 数 功 能:FLASH扇区擦除
 *函 数 形 参:add:读取地址
 *函 数 返 回:无
*******************************************************************/
void Flash_SectorErase(uint32_t add)
{
	//先写使能
	Write_Enable();
	//1,拉低CS
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
	//2,发送命令
	SPI2_SendData(0x20);	
	//3,发送三个字节地址 add = 0x123456
	SPI2_SendData(add >> 16);
	SPI2_SendData((add&0x00FF00) >> 8);
	SPI2_SendData((add&0x0000FF));	
	//4,拉高CS
	GPIO_SetBits(GPIOB,GPIO_Pin_12);		
	//5,等待忙标志变0
	Flash_WaitForWriteEnd();
}

7.读取设备ID

9FH:
读取设备ID;W25Q64固定ID为0xEF4017
在这里插入图片描述

/*****************************************************************
 *函 数 名 称:Flash_ReadID
 *函 数 功 能:FLASH读取ID
 *函 数 形 参:无
 *函 数 返 回:无
 *作       者:CYM
 *修 改 日 期:xx/xx/xx
*******************************************************************/
void Flash_ReadID(void)
{
 uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;

	//1,拉低CS
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
  SPI2_SendData(0x9F);
  Temp0 = SPI2_SendData(0x55);
  Temp1 = SPI2_SendData(0x55);
  Temp2 = SPI2_SendData(0x55);
  GPIO_SetBits(GPIOB,GPIO_Pin_12);	
  Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
	printf("FLASH_ID:%x\r\n",Temp);
}

8.全片擦除

0x60:每次擦除一片

/*****************************************************************
 *函 数 名 称:Flash_SectorAll
 *函 数 功 能:FLASH整个芯片擦除
 *函 数 形 参:add:读取地址
 *函 数 返 回:无
 *作       者:QXJ
 *修 改 日 期:xx/xx/xx
*******************************************************************/

void Flash_SectorAll(void)
{
	//先写使能
	Write_Enable();
	//等待忙标志变0
	//Flash_WaitForWriteEnd();
	//拉低CS
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
	//发送命令
	SPI2_SendData(0x60);
	//拉高CS
	GPIO_SetBits(GPIOB,GPIO_Pin_12);		
	//等待忙标志变0
	Flash_WaitForWriteEnd();
}

9.写入任意行

/*****************************************************************
 *函 数 名 称:sFLASH_WriteBuffer
 *函 数 功 能:FLASH写任意行
 *函 数 返 回:无
*******************************************************************/
void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

  Addr = WriteAddr % 256;
  count = 256 - Addr;
  NumOfPage =  NumByteToWrite / 256;//写多少页
  NumOfSingle = NumByteToWrite % 256;//写满多少页后剩余多少字节

  if (Addr == 0) //从一页的头开始写
  {
    if (NumOfPage == 0)//写入的数据小于256
    {
      Flash_PageProgram(WriteAddr,pBuffer, NumByteToWrite);
    }
    else //一页写不完,需要换页
    {
      while (NumOfPage--)//循环写入整页数据
      {
        Flash_PageProgram(WriteAddr,pBuffer, 256);//写一页操作
        WriteAddr +=  256;//地址偏移256个
        pBuffer += 256;//数据偏移256个
      }

       Flash_PageProgram(WriteAddr,pBuffer, NumOfSingle);
    }
  }
  else //从一页中间开始写
  {
    if (NumOfPage == 0) /*写入数据小于256 */
    {
      if (NumOfSingle > count) /*!< (NumByteToWrite + WriteAddr) > sFLASH_PAGESIZE */
      {
        temp = NumOfSingle - count;//计算把第一页写入完还剩余多少
			  Flash_PageProgram(WriteAddr,pBuffer, count);//先写入当前页剩余空间
        WriteAddr +=  count;
        pBuffer += count;
        Flash_PageProgram(WriteAddr,pBuffer, temp);
      }
      else//剩余空间大于要写入的数据,直接写
      {
        Flash_PageProgram(WriteAddr,pBuffer, NumByteToWrite);
      }
    }
    else //要写入数据大于256
    {
      NumByteToWrite -= count;//计算写完第一页还有多少字节
      NumOfPage =  NumByteToWrite / 256;//写完第一页还要写多少页
      NumOfSingle = NumByteToWrite % 256;//写满每一页还剩多少字节

      Flash_PageProgram(WriteAddr,pBuffer, count);//先写当前页地址空间
      WriteAddr +=  count;
      pBuffer += count;

      while (NumOfPage--)
      {
        Flash_PageProgram(WriteAddr,pBuffer, 256);
        WriteAddr +=  256;
        pBuffer += 256;
      }

      if (NumOfSingle != 0)
      {
        Flash_PageProgram(WriteAddr,pBuffer, NumOfSingle);
      }
    }
  }
}
  • 16
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值