当前位置: 首页 > news >正文

STM32第十八课:SPIFlash

目录

  • 需求
  • 一、SPI概要
  • 二、SPI配置
    • 1.开时钟
    • 2.配置IO
    • 3.配置&使能SPI
  • 三、FLash操作函数
    • 1.SPI发送数据
    • 2.FLASH写使能
    • 3.FLASH等待操作完成
    • 4.FLASH页写操作
    • 5.FLASH读操作
    • 6.FLASH扇区擦除
  • 四、需求实现


需求

通过SPI控制FLash进行数据的保存和删除。
在这里插入图片描述


一、SPI概要

在我们使用UART(通用串行异步通讯协议)时,因为UART没有时钟信号,速度不同,无法控制何时收发数据
非要解决这个问题的话,需要为UART传输的数据添加起始位停止位,双方波特率还需同步,很麻烦,比较垃圾。
于是由摩托摩拉公司牵头推出了一种新的通讯总线 SPI:串行外设接口
SPI是一个高速、全双工、同步的串行通信总线。应用场景:OLED屏幕、FLASH存储器、AD转换器
通信方式:串行同步全双工(人话就是数据在线上按照时间顺序一位一位的传输,发送和接收时要在通信时钟的同步下进行数据传输,且可以同时发送和接收)
该总线就是利用单独的数据线(MISO和MOSI)和单独的时钟信号线(SCK)完美解决了收发端的数据完美同步。
接线
MOSI: 主设备输出、从设备输入 TX
MISO: 主设备输入、从设备输出 RX
SCK: 时钟线
CS: 片选信号线 一主多从,拉低选择和哪个从机通信
GND: 地线
单从机时:
在这里插入图片描述
多从机时:
在这里插入图片描述
总结:除了SS片选线需要单独备一根线和从机进行一对一的链接,其他都可以一对多,多个从机接同一个主机接口。
SPI通信模式:环形传输,发多少收多少。
操作逻辑:
1、用时先拉低CS。
2、开始操作发数据,收数据。
3、用完后拉高CS。

STM32中SPI接口
在这里插入图片描述

二、SPI配置

1.开时钟

打开原理图,找到引脚
在这里插入图片描述

在这里插入图片描述
根据引脚的开SPI2和GPIOB的时钟

	//1,开时钟,GPIO SPI2 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);

2.配置IO

MOSI,SCK:复用推挽输出
均需要输出高低电平,还是复用功能,所以配复用推挽输出
MISO:浮空输入
需要接收数据,没什么好说的
CS:通用推挽输出
由于是主机所以CS当做GPIO配就行(软件模式)
从机的话配硬件模式

	//2,配置IO模式GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin =GPIO_Pin_12;//CSGPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStruct);GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Pin =GPIO_Pin_13|GPIO_Pin_15;//SCK和MOSIGPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStruct);	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_InitStruct.GPIO_Pin =GPIO_Pin_14;//MISOGPIO_Init(GPIOB,&GPIO_InitStruct);	GPIO_SetBits(GPIOB,GPIO_Pin_12);//拉高CS避免通信出错

最后拉高CS是因为当CS拉低时表示开始传输信号

3.配置&使能SPI


参考手册中的步骤如下:

在这里插入图片描述
CPHA:时钟相位, CPHA=0时,在时钟的第一个边沿进行采样,第二个边沿进行输出
              CPHA=1时,在时钟的第二个边沿进行采样,第三个边沿进行输出
CPOL:时钟极性, CPOL=0时,空闲时时钟为低电平
              CPOL=1时,空闲时时钟为高电平

SPI的模式0CPHA=0,CPOL=0 上升沿采样、下降沿接受
SPI的模式1CPHA=0,CPOL=1 下降沿采样、上升沿接受
SPI的模式2CPHA=1,CPOL=0 下降沿采样、上升沿接受
SPI的模式3CPHA=1,CPOL=1 上升沿采样、下降沿接受

  一般能够支持SPI模式0的设备也支持SPI模式3,支持模式1的设备也支持模式2。SPI通信没有具体的协议格式,格式根据通信对象的要求来定。一般的传输数据时候采用的就是8位进行,高位先传还是低位先传,传输的速度。这些参数都需要根据通信对象来进行配置

	//2,配置SPISPI_InitStruct.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;//时钟分频2SPI_InitStruct.SPI_CPHA=SPI_CPHA_1Edge;//时钟相位,第一个边沿采样SPI_InitStruct.SPI_CPOL=SPI_CPOL_Low;//时钟极性 ,低电平SPI_InitStruct.SPI_CRCPolynomial=0x12;//不使用CRC校验,参数无意义SPI_InitStruct.SPI_DataSize=SPI_DataSize_8b;//数据宽度8未SPI_InitStruct.SPI_Direction=SPI_Direction_2Lines_FullDuplex;//双线全双工模式SPI_InitStruct.SPI_FirstBit=SPI_FirstBit_MSB;//先发高位SPI_InitStruct.SPI_Mode=SPI_Mode_Master;//主机模式SPI_InitStruct.SPI_NSS=SPI_NSS_Soft;//软件NSS模式SPI_Init(SPI2,&SPI_InitStruct);//3,使能SPISPI_Cmd(SPI2,ENABLE);

本此SPI配置的对象是FLash,需求如下:
在这里插入图片描述

三、FLash操作函数

  本次用到的FLash为W25Q64,是华邦存储推出的一款串行FLASH存储芯片。
空间规格:
64Mbit (兆位)== 8Mbyte(兆字节)
256字节为一页、16页为一扇区、16扇区为一块
在这里插入图片描述

地址表示:
在这里插入图片描述

1.SPI发送数据

由于SPI是环形传输,所以发多少,我们也要接收多少。
在这里插入图片描述
发送:判断发送缓冲器空闲标志(TXE)是否为0,为0表示正在发,此时持续等待数据发完。标志为1表示之前的发送完了,空闲下来了。此时就可以开始往DR发数据。
接收:判断接收缓冲器非空(RXNE)是否为0,为0表示接受缓冲区为空,没数据,此时持续等待数据到来。标志为1表示数据来了,此时就可以开始读DR的数据了。

/******************************************************************函 数 名 称:SPI2_SendData*函 数 功 能:SPI2发送数据*函 数 形 参:SPI2_SendData 发送内容*函 数 返 回:SPI2_RecvData 返回收到的数据*作       者:ZHT*修 改 日 期:xx\xx\xx*******************************************************************/
uint8_t SPI2_SendData(uint8_t SPI2_SendData)
{uint8_t SPI2_RecvData = 0;while((SPI2->SR & (0x1<<1)) == 0);SPI2->DR = SPI2_SendData;while((SPI2->SR & (0x1<<0)) == 0);SPI2_RecvData = SPI2->DR;return SPI2_RecvData;
}

2.FLASH写使能

看手册上的时序:
在这里插入图片描述
得知逻辑:
1.先拉低CS。
2.SPI发出06指令。
3.最后拉高CS。

/******************************************************************函 数 名 称:Write_Enable*函 数 功 能:FLASH写使能操作*函 数 形 参:无*函 数 返 回:无*作       者:ZHT*修 改 日 期:xx/xx/xx
*******************************************************************/
void Write_Enable(void)
{//拉低CSGPIO_ResetBits(GPIOB,GPIO_Pin_12);//发送0x06指令SPI2_SendData(0x06);//拉高CSGPIO_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等待操作完成*函 数 形 参:无*函 数 返 回:无*作       者:ZHT*修 改 日 期:xx/xx/xx
*******************************************************************/
void Flash_WaitForWriteEnd(void)
{uint8_t recv = 0;//拉低CSGPIO_ResetBits(GPIOB,GPIO_Pin_12);SPI2_SendData(0x05);//发送读取指令do{recv = SPI2_SendData(0x55);//循环读取寄存器数据,0x55随便改}while((recv & 0x01) == SET);//判断寄存器的第0位是否是1//拉高CSGPIO_SetBits(GPIOB,GPIO_Pin_12);	
}

4.FLASH页写操作

顾名思义,就是往FLash中写入一页(256Byte)的操作。
手册上的时序:
在这里插入图片描述
得出逻辑:
1.由于要进行写操作,所以要先进行写使能。
2.拉低CS,发送02指令。
3.发送三个字节,即24位的地址,每次发8位分3次发送。为了告知写入的位置。
4.发送数据,8位8位发,最多256。
5.调用FLash等待函数,等待写入完成。
6.写入完成后,拉高CS。

/******************************************************************函 数 名 称:Flash_WritePage*函 数 功 能:FLASH页写操作*函 数 形 参:Write_Local:写入地址 Write_data:写入数据 Write_len:写入数据长度*函 数 返 回:无*作       者:ZHT*修 改 日 期:xx/xx/xx
*******************************************************************/
void Flash_WritePage(uint8_t *Write_data,uint32_t Write_Local,uint16_t Write_len)
{//1,先写使能Write_Enable();//2,拉低CSGPIO_ResetBits(GPIOB,GPIO_Pin_12);//3,发送命令SPI2_SendData(0x02);//4,发送三个字节地址 add = 0x123456SPI2_SendData((Write_Local&0xFF0000)>> 16);SPI2_SendData((Write_Local&0x00FF00) >> 8);SPI2_SendData((Write_Local&0x0000FF));while(Write_len--){SPI2_SendData( *Write_data);Write_data++;}//5,拉高CSGPIO_SetBits(GPIOB,GPIO_Pin_12);//6,等待操作完成Flash_WaitForWriteEnd();
}

5.FLASH读操作

手册上的时序:
在这里插入图片描述
得出逻辑:
1.拉低CS,发送03指令。
2.发送3字节读取的地址。
3.随便发送1个字节的数据,返回值就是要获取的数据。
4.拉高CS。(千万不要等待操作完成,因为手册里没写)

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

6.FLASH扇区擦除

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

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

四、需求实现

main.c

#include "stm32f10x.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
#include "string.h"
#include "pwm.h"
#include "adc.h"
#include "su03t.h"
#include "dht11.h"
#include "kqm.h"
#include "key.h"
#include "RTC.h"
#include "bsp_lcd.h"
#include "wifi.h"
#include "aliot.h"
#include "time.h"
#include "spi.h"
#define TestAddr 0x000700
char buff1[20]; 
int main()
{NVIC_SetPriorityGrouping(5);//两位抢占两位次级Usart1_Config(); SysTick_Config(72000);SPI2_Config();Flash_ReadID();Flash_SectorErase(TestAddr);FLASH_WriteBuffer((u8*)"好饿好饿好饿~",TestAddr,300);printf("写数据后\r\n");Flash_ReadData(TestAddr,buff1,300);printf("FLASH:%s\r\n",buff1);Flash_SectorErase(TestAddr);printf("清数据后\r\n");Flash_ReadData(TestAddr,buff1,300);printf("FLASH:%s\r\n",buff1);FLASH_WriteBuffer((u8*)"好饿好饿好饿~",TestAddr,300);printf("重写数据后\r\n");Flash_ReadData(TestAddr,buff1,300);printf("FLASH:%s\r\n",buff1);while(1){	if(ledcnt[0]>=ledcnt[1])//2S一次{ledcnt[0]=0;printf("FLASH:%s\r\n",buff1);}}return 0;
}

spi.c

#include "spi.h"
#define sFLASH_SPI_PAGESIZE       0x100
/******************************************************************函 数 名 称:SPI2_Config*函 数 功 能:初始化SPI2*函 数 形 参:无*函 数 返 回:无*作       者:ZHT*修 改 日 期:xx\xx\xx*******************************************************************/
void SPI2_Config()
{
#if 0//1,开时钟,GPIO SPI2  代码简短,但不直观RCC->APB2ENR |= 1<<3;RCC->APB1ENR |= 1<<14;//2,配置IO模式GPIOB->CRH &= ~(0xFFFF<<16);GPIOB->CRH |= 0xB4B3<<16;//配置4个IO口的模式 1011GPIOB->ODR |= 0x1<<12;//拉高PB12,避免对通信产生影响//3,配置SPISPI2->CR1 |= 0x0244;//配置SPI工作模式#elseGPIO_InitTypeDef GPIO_InitStruct={0};SPI_InitTypeDef SPI_InitStruct={0};//1,开时钟,GPIO SPI2 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);//2,配置IO模式GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin =GPIO_Pin_12;//CSGPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStruct);GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Pin =GPIO_Pin_13|GPIO_Pin_15;//SCK和MOSIGPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStruct);	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_InitStruct.GPIO_Pin =GPIO_Pin_14;//MISOGPIO_Init(GPIOB,&GPIO_InitStruct);	GPIO_SetBits(GPIOB,GPIO_Pin_12);//拉高CS避免通信出错//2,配置SPISPI_InitStruct.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;//时钟分频2SPI_InitStruct.SPI_CPHA=SPI_CPHA_1Edge;//时钟相位,第一个边沿采样SPI_InitStruct.SPI_CPOL=SPI_CPOL_Low;//时钟极性 ,低电平SPI_InitStruct.SPI_CRCPolynomial=0x12;//不使用CRC校验,参数无意义SPI_InitStruct.SPI_DataSize=SPI_DataSize_8b;//数据宽度8未SPI_InitStruct.SPI_Direction=SPI_Direction_2Lines_FullDuplex;//双线全双工模式SPI_InitStruct.SPI_FirstBit=SPI_FirstBit_MSB;//先发高位SPI_InitStruct.SPI_Mode=SPI_Mode_Master;//主机模式SPI_InitStruct.SPI_NSS=SPI_NSS_Soft;//软件NSS模式SPI_Init(SPI2,&SPI_InitStruct);//3,使能SPISPI_Cmd(SPI2,ENABLE);#endif
}/******************************************************************函 数 名 称:SPI2_SendData*函 数 功 能:SPI2发送数据*函 数 形 参:SPI2_SendData 发送内容*函 数 返 回:SPI2_RecvData 返回收到的数据*作       者:ZHT*修 改 日 期:xx\xx\xx*******************************************************************/
uint8_t SPI2_SendData(uint8_t SPI2_SendData)
{uint8_t SPI2_RecvData = 0;while((SPI2->SR & (0x1<<1)) == 0);SPI2->DR = SPI2_SendData;while((SPI2->SR & (0x1<<0)) == 0);SPI2_RecvData = SPI2->DR;return SPI2_RecvData;
}/************************SPI-FLASH********************************/
/******************************************************************函 数 名 称:Write_Enable*函 数 功 能:FLASH写使能操作*函 数 形 参:无*函 数 返 回:无*作       者:ZHT*修 改 日 期:xx/xx/xx
*******************************************************************/
void Write_Enable(void)
{//拉低CSGPIO_ResetBits(GPIOB,GPIO_Pin_12);//发送0x06指令SPI2_SendData(0x06);//拉高CSGPIO_SetBits(GPIOB,GPIO_Pin_12);	
}/******************************************************************函 数 名 称:Flash_WaitForWriteEnd*函 数 功 能:FLASH等待操作完成*函 数 形 参:无*函 数 返 回:无*作       者:CYM*修 改 日 期:xx/xx/xx
*******************************************************************/
void Flash_WaitForWriteEnd(void)
{uint8_t recv = 0;//拉低CSGPIO_ResetBits(GPIOB,GPIO_Pin_12);SPI2_SendData(0x05);//发送读取指令do{recv = SPI2_SendData(0x55);//循环读取寄存器数据,0x55随便改}while((recv & 0x01) == SET);//判断寄存器的第0位是否是1//拉高CSGPIO_SetBits(GPIOB,GPIO_Pin_12);	
}/******************************************************************函 数 名 称:Flash_WritePage*函 数 功 能:FLASH页写操作*函 数 形 参:Write_Local:写入地址 Write_data:写入数据 Write_len:写入数据长度*函 数 返 回:无*作       者:CYM*修 改 日 期:xx/xx/xx
*******************************************************************/
void Flash_WritePage(uint8_t *Write_data,uint32_t Write_Local,uint16_t Write_len)
{//1,先写使能Write_Enable();//2,拉低CSGPIO_ResetBits(GPIOB,GPIO_Pin_12);//3,发送命令SPI2_SendData(0x02);//4,发送三个字节地址 add = 0x123456SPI2_SendData((Write_Local&0xFF0000)>> 16);SPI2_SendData((Write_Local&0x00FF00) >> 8);SPI2_SendData((Write_Local&0x0000FF));while(Write_len--){SPI2_SendData( *Write_data);Write_data++;}//5,拉高CSGPIO_SetBits(GPIOB,GPIO_Pin_12);//6,等待操作完成Flash_WaitForWriteEnd();
}/******************************************************************函 数 名 称:Flash_ReadData*函 数 功 能:FLASH读操作*函 数 形 参:add:读取地址 data:保存读取数据 len:读取数据长度*函 数 返 回:无*作       者:CYM*修 改 日 期:xx/xx/xx
*******************************************************************/
void Flash_ReadData(uint32_t add, uint8_t *data,uint16_t len)
{//1,拉低CSGPIO_ResetBits(GPIOB,GPIO_Pin_12);	//2,发送命令SPI2_SendData(0x03);	//3,发送三个字节地址 add = 0x123456SPI2_SendData((add&0xFF0000)>> 16);SPI2_SendData((add&0x00FF00) >> 8);SPI2_SendData((add&0x0000FF));while(len--){*data = SPI2_SendData(0x55);data++;}//4,拉高CSGPIO_SetBits(GPIOB,GPIO_Pin_12);	
}
/******************************************************************函 数 名 称:Flash_SectorErase*函 数 功 能:FLASH扇区擦除*函 数 形 参:add:读取地址*函 数 返 回:无*作       者:CYM*修 改 日 期:xx/xx/xx
*******************************************************************/
void Flash_SectorErase(uint32_t add)
{//先写使能Write_Enable();//1,拉低CSGPIO_ResetBits(GPIOB,GPIO_Pin_12);	//2,发送命令SPI2_SendData(0x20);	//3,发送三个字节地址 add = 0x123456SPI2_SendData(add >> 16);SPI2_SendData((add&0x00FF00) >> 8);SPI2_SendData((add&0x0000FF));	//4,拉高CSGPIO_SetBits(GPIOB,GPIO_Pin_12);		//5,等待忙标志变0Flash_WaitForWriteEnd();
}
/******************************************************************函 数 名 称:Flash_ReadID*函 数 功 能:FLASH读取ID*函 数 形 参:无*函 数 返 回:无*作       者:ZHT*修 改 日 期:xx/xx/xx
*******************************************************************/
void Flash_ReadID(void)
{uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;//1,拉低CSGPIO_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);
}/******************************************************************函 数 名 称:Flash_ChipClean*函 数 功 能:全片擦除*函 数 形 参:无*函 数 返 回:无*作       者:ZHT*修 改 日 期:xx\xx\xx*******************************************************************/
void Flash_ChipClean()
{//先写使能Write_Enable();//1,拉低CSGPIO_ResetBits(GPIOB,GPIO_Pin_12);	//2,发送命令SPI2_SendData(0x60);	//4,拉高CSGPIO_SetBits(GPIOB,GPIO_Pin_12);	Flash_WaitForWriteEnd();
}/******************************************************************函 数 名 称:FLASH_WriteBuffer*函 数 功 能:随意写入*函 数 形 参:uint8_t* pBuffer:指向要写入数据的缓冲区的指针。uint32_t WriteAddr:写入数据的起始地址。uint16_t NumByteToWrite:要写入的字节数。*函 数 返 回:无*作       者:ZHT*修 改 日 期:xx\xx\xx*******************************************************************/void FLASH_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 = 0x100 - Addr;NumOfPage =  NumByteToWrite / 256;NumOfSingle = NumByteToWrite % 256;if (Addr == 0) /*!< WriteAddr is sFLASH_PAGESIZE aligned  */{if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */{Flash_WritePage(pBuffer, WriteAddr, NumByteToWrite);}else /*!< NumByteToWrite > sFLASH_PAGESIZE */{while (NumOfPage--){Flash_WritePage(pBuffer, WriteAddr, 256);WriteAddr +=  256;pBuffer += 256;}Flash_WritePage(pBuffer, WriteAddr, NumOfSingle);}}else /*!< WriteAddr is not sFLASH_PAGESIZE aligned  */{if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */{if (NumOfSingle > count) /*!< (NumByteToWrite + WriteAddr) > sFLASH_PAGESIZE */{temp = NumOfSingle - count;Flash_WritePage(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;Flash_WritePage(pBuffer, WriteAddr, temp);}else{Flash_WritePage(pBuffer, WriteAddr, NumByteToWrite);}}else /*!< NumByteToWrite > sFLASH_PAGESIZE */{NumByteToWrite -= count;NumOfPage =  NumByteToWrite / 256;NumOfSingle = NumByteToWrite % 256;Flash_WritePage(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;while (NumOfPage--){Flash_WritePage(pBuffer, WriteAddr, 256);WriteAddr +=  256;pBuffer += 256;}if (NumOfSingle != 0){Flash_WritePage(pBuffer, WriteAddr, NumOfSingle);}}}
}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 % sFLASH_SPI_PAGESIZE;count = sFLASH_SPI_PAGESIZE - Addr;NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;if (Addr == 0) /*!< WriteAddr is sFLASH_PAGESIZE aligned  */{if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */{Flash_WritePage(pBuffer, WriteAddr, NumByteToWrite);}else /*!< NumByteToWrite > sFLASH_PAGESIZE */{while (NumOfPage--){Flash_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);WriteAddr +=  sFLASH_SPI_PAGESIZE;pBuffer += sFLASH_SPI_PAGESIZE;}Flash_WritePage(pBuffer, WriteAddr, NumOfSingle);}}else /*!< WriteAddr is not sFLASH_PAGESIZE aligned  */{if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */{if (NumOfSingle > count) /*!< (NumByteToWrite + WriteAddr) > sFLASH_PAGESIZE */{temp = NumOfSingle - count;Flash_WritePage(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;Flash_WritePage(pBuffer, WriteAddr, temp);}else{Flash_WritePage(pBuffer, WriteAddr, NumByteToWrite);}}else /*!< NumByteToWrite > sFLASH_PAGESIZE */{NumByteToWrite -= count;NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;Flash_WritePage(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;while (NumOfPage--){Flash_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);WriteAddr +=  sFLASH_SPI_PAGESIZE;pBuffer += sFLASH_SPI_PAGESIZE;}if (NumOfSingle != 0){Flash_WritePage(pBuffer, WriteAddr, NumOfSingle);}}}
}

spi.h

#ifndef __SPI_H
#define __SPI_H#include "stm32f10x.h"
#include "stdio.h"void SPI2_Config();
void Flash_WritePage(uint8_t *Write_data,uint32_t Write_Local,uint16_t Write_len);
void Flash_ReadData(uint32_t add, uint8_t *data,uint16_t len);
void Flash_SectorErase(uint32_t add);
void Flash_ReadID(void);
void Flash_WaitForWriteEnd(void);
void Flash_ChipClean();
void FLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
#endif

相关文章:

STM32第十八课:SPIFlash

目录 需求一、SPI概要二、SPI配置1.开时钟2.配置IO3.配置&使能SPI 三、FLash操作函数1.SPI发送数据2.FLASH写使能3.FLASH等待操作完成4.FLASH页写操作5.FLASH读操作6.FLASH扇区擦除 四、需求实现 需求 通过SPI控制FLash进行数据的保存和删除。 一、SPI概要 在我们使用UA…...

如何使用IPython的并行计算能力处理大数据

目录 引言IPython概述 什么是IPythonIPython的特点 并行计算简介 什么是并行计算并行计算的优势 IPython的并行计算功能 IPython.parallel模块IPython并行架构 IPython的安装与配置 安装IPython配置并行环境 IPython并行计算的基础 任务分发与负载均衡核心概念&#xff1a;Cli…...

前端热门面试题二

你有使用过哪些前端构建工具&#xff08;如Webpack、Gulp、Rollup&#xff09;&#xff1f;并谈谈它们的特点和优势。 在前端开发中&#xff0c;构建工具扮演着至关重要的角色&#xff0c;它们能够自动化处理各种任务&#xff0c;如代码压缩、模块打包、代码转换、静态资源管理…...

Android TabLayout+ViewPager2如何优雅的实现联动详解

一、介绍 Android开发过程中&#xff0c;我们经常会遇到滑动导航栏的做法&#xff0c;之前的做法就是我们通过ViewGroup来转动&#xff0c;然后通过大量的自定义来完成&#xff0c;将导航栏item与viewpage 滑动&#xff0c;达到业务需求 二、现实方案 通过介绍&#xff0c;我…...

k8s快速部署一个网站

1&#xff09;使用Deployment控制器部署镜像&#xff1a; kubectl create deployment web-demo --imagelizhenliang/web-demo:v1 kubectl get deployment,pods[rootk8s-matser ~]# kubectl get pods NAME READY STATUS RESTARTS A…...

期货量化交易客户端开源教学第四节——交易接口协议

指令介绍: 01----09:服务端发送到客户端指令 10----49:客户端发送操作指令 50----59:客户端与服务端通讯指令 60----99:股票接口与服务端交互指令 --------------------------------------------------- 02:商品行情 03:用户信息接收 04:用户资产信息接收 ----发送到…...

M1000 4G蓝牙网关:高速稳定,赋能物联网新体验

桂花网M1000的4G移动网络功能主要体现在以下几个方面&#xff1a; 一、高速稳定的数据传输 高速率&#xff1a;M1000支持4G移动网络&#xff0c;能够实现高速的数据传输。根据4G网络的技术标准&#xff0c;其理论上的最大下行速率可达到数百Mbps&#xff08;如TD-LTE在20MHz带…...

中国高端水果元宇宙

高档榴莲通常指的是品质上乘、口感极佳、产地知名且价格较高的榴莲品种。榴莲因其独特的风味和营养价值而被誉为“水果之王”&#xff0c;在东南亚尤其受欢迎。以下是一些被认为是高档榴莲的品种&#xff1a; 1.**猫山王榴莲&#xff08;Musang King or Mao Shan Wang&#xff…...

MySQL:库操作

1. 创建数据库 create database [if not exists] name [create_specification], [create_specification]... []内为可选的选项 create_specification: character set charset_name -- 指定数据库采用的字符集 -- 数据库未来存储数据 collate collation_name -- 指定数据库字符…...

struts2如何防止XSS脚本攻击(XSS防跨站脚本攻击过滤器)

只需要配置一个拦截器即可解决参数内容替换 一、配置web.xml <filter><filter-name>struts-xssFilter</filter-name><filter-class>*.*.filters.XssFilter</filter-class></filter><filter-mapping><filter-name>struts-xss…...

SQL基础 | NOT NULL 约束介绍

在SQL中&#xff0c;NOT NULL是一个约束条件&#xff0c;用于确保列不接受NULL值。 这个约束通常在创建表或修改表时使用&#xff0c;以确保数据的完整性和准确性。 以下是NOT NULL的一些常见用法&#xff1a; 创建表时指定NOT NULL约束&#xff1a; 当你创建一个新表时&#x…...

C语言 ——— 实用调试技巧(Visual Studio)

目录 Debug 和 Release 的区别 F10 --- 逐过程调试 & F11 --- 逐语句调试 F9 --- 新建/切换断点 & F5 --- 开始调试 shift F5 & ctrl F5 Debug 和 Release 的区别 Debug&#xff1a;通常为调试版本&#xff0c;它包含调试信息&#xff0c;并且不作任何优化…...

音频demo:使用faad2将AAC数据解码出PCM数据

1、README 前言 本demo是使用的开源项目faad2将aac数据解码成pcm数据。 a. 编译使用 faad2的编译&#xff1a;(faad2下载地址&#xff1a;https://sourceforge.net/projects/faac/files/faad2-src/faad2-2.8.0/) tar xzf faad2-2.8.8.tar.gz cd faad2-2.8.8/ ./configure …...

力扣 hot100 -- 多维动态规划

&#x1f447;woc&#xff0c;这不是最熟悉那种&#xff0c;记忆化 dfs 或者 普通的深度优先搜索&#xff1f;&#xff1f;都适用于二维地图&#x1f447; DFS&#xff08;深度优先搜索&#xff09;8种题型_dfs典型问题-CSDN博客 目录 &#x1f943;不同路径 &#x1f33c;最…...

[misc]-流量包-wireshark-icmp

wireshark打开&#xff0c;大部分都是icmp,查看data部分 提取data长度&#xff1a; tshark.exe -r 1.pcapng -T fields -e data.len > length.txt 使用python解析这个文件&#xff0c;剔除异常值&#xff0c;每8个取一个值&#xff0c;得到flag ds [] with open(length.tx…...

探索性数据分析:使用Python与Pandas库实现数据洞察

探索性数据分析&#xff1a;使用Python与Pandas库实现数据洞察 引言 在当今数据驱动的时代&#xff0c;数据分析已成为决策制定、策略规划和业务优化的关键环节。无论是商业智能、金融分析还是市场研究&#xff0c;数据分析都扮演着至关重要的角色。Pandas库作为Python生态系统…...

枚举的高阶用法之枚举里写方法以及注入spring的bean

1、前言 一般我们使用枚举都是用来定义一些常量。比如我们需要一个表示订单类(pc订单、手机订单)的常量,那我们就可以使用枚举来实现,如下: AllArgsConstructor public enum OrderTypeEnum{PC("PC", "电脑端"),PHONE("PHONE", "手机端&quo…...

游戏开发面试题2

网络游戏分为客户端和服务端&#xff0c;你能说说客户端和服务端都干了一些什么工作吗&#xff1f; 客户端&#xff08;Client&#xff09; 客户端是玩家直接交互的部分&#xff0c;主要负责用户界面、输入处理、渲染和部分逻辑处理。具体工作包括&#xff1a; 用户界面&…...

华为机试题-单车道汽车通行时间-Java

代码在最后面 1 题目描述 M&#xff08;1 ≤ M ≤ 20&#xff09;辆车需要在一条不能超车的单行道到达终点&#xff0c;起点到终点的距离为 N&#xff08;1 ≤ N ≤ 400&#xff09;。 速度快的车追上前车后&#xff0c;只能以前车的速度继续行驶&#xff0c;求最后一辆车到达…...

6-5,web3浏览器链接区块链(react+区块链实战)

6-5&#xff0c;web3浏览器链接区块链&#xff08;react区块链实战&#xff09; 6-5 web3浏览器链接区块链&#xff08;调用读写合约与metamask联动&#xff09; 6-5 web3浏览器链接区块链&#xff08;调用读写合约与metamask联动&#xff09; 这里就是浏览器端和智能合约的交…...

C# 多态性

C# 多态性 介绍 多态性是面向对象编程(OOP)的一个核心概念,它允许不同类的对象对同一消息做出响应,并产生不同的结果。在C#中,多态性主要通过继承、接口和虚方法来实现。本文将深入探讨C#中的多态性,包括其原理、实现方式以及在实际编程中的应用。 原理 多态性允许将…...

Visual Studio 安装程序无法执行修复或更新

一.问题场景 出现问题的场景&#xff1a;当你的VS已经安装但是无法在工具中下载新组件或者卸载了当时一直无法安装。 二.问题原因 如果计算机上的 Visual Studio 实例已损坏&#xff0c;则可能会出现此问题。 三.解决方法 如果之前尝试修复或更新 Visual Studio 失败&…...

C#与PLC通信——如何设置电脑IP地址

前言&#xff1a; 我们与PLC通过以太网通信时&#xff0c;首先要做的就是先设置好电脑的IP&#xff0c;这样才能实现上位机电脑与PLC之间的通信&#xff0c;并且电脑的ip地址和PLC的Ip地址要同处于一个网段&#xff0c;比如电脑的Ip地址为192.168.1.1&#xff0c;那么PLC的Ip地…...

Milvus 核心设计(1) ---- 数据一致性的等级及使用场景

目录 背景 Milvus的数据一致性 设置数据一致性等级 等级类型 PACELC定理 level 详细解释 Strong Bounded staleness Session Eventually 总结 背景 分布式上的可扩展性是个比较重要的concept。Chroma 核心之前写过了,他的最大优势在于轻量级且好用。Milvus相对Ch…...

EasyCVR视频技术:城市电力抢险的“千里眼”,助力抢险可视化

随着城市化进程的加速和电力需求的不断增长&#xff0c;电力系统的稳定运行对于城市的正常运转至关重要。然而&#xff0c;自然灾害、设备故障等因素常常导致电力中断&#xff0c;给城市居民的生活和企业的生产带来严重影响。在这种情况下&#xff0c;快速、高效的电力抢险工作…...

【Wamp】局域网设备访问WampServer | 使用域名访问Wamp | Wamp配置HTTPS

局域网设备访问WampServer 参考&#xff1a;https://www.jianshu.com/p/d431a845e5cb 修改Apache的httpd.conf文件 D:\Academic\Wamp\program\bin\apache\apache2.4.54.2\conf\httpd.conf 搜索 Require local 和Require all denied&#xff0c;改为Require all granted <…...

采用自动微分进行模型的训练

自动微分训练模型 简单代码实现&#xff1a; import torch import torch.nn as nn import torch.optim as optim# 定义一个简单的线性回归模型 class LinearRegression(nn.Module):def __init__(self):super(LinearRegression, self).__init__()self.linear nn.Linear(1, 1) …...

k8s怎么配置secret呢?

在Kubernetes中&#xff0c;配置Secret主要涉及到创建、查看和使用Secret的过程。以下是配置Secret的详细步骤和相关信息&#xff1a; ### 1. Secret的概念 * Secret是Kubernetes用来保存密码、token、密钥等敏感数据的资源对象。 * 这些敏感数据可以存放在Pod或镜像中&#x…...

算法篇 滑动窗口 leetcode 长度最小的子数组

长度最小的子数组 1. 题目描述2. 算法图分析2.1 暴力图解2.2 滑动窗口图解 3. 代码演示 1. 题目描述 2. 算法图分析 2.1 暴力图解 2.2 滑动窗口图解 3. 代码演示...

数据库作业d8

要求&#xff1a; 一备份 1 mysqldump -u root -p booksDB > booksDB_all_tables.sql 2 mysqldump -u root -p booksDB books > booksDB_books_table.sql 3 mysqldump -u root -p --databases booksDB test > booksDB_and_test_databases.sql 4 mysql -u roo…...