STM32标准库-FLASH

FLASH模仿EEPROM

STM32本身没有自带EEPROM,但是自带了FLASH存储器。

STM32F103ZET6自带 1M字节的FLASH空间,和 128K+64K的SRAM空间。

STM32F4 的 SPI 功能很强大,SPI 时钟最高可以到 37.5Mhz,支持 DMA,可以配置为 SPI协议或者 I2S 协议(支持全双工 I2S)。

SPI简介

SPI,串行外围设备接口。主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线。
SPI内部结构简明图

SPI 接口一般使用 4 条线通信:

MISO 主入从出。

MOSI 主出从入。

SCLK 时钟信号,由主设备产生。

CS 从设备片选信号,由主设备控制。

主机和从机都有一个移位寄存器,主机通过向自己的SPI串行寄存器写入一个字节来发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机将自己移位寄存器的内容通过MISO信号线返回给主机。这样,两个移位寄存器的内容被交换。

外设的写操作和读操作是同步完成的,如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

SPI总线有四种工作方式,通过配置时钟极性(CPOL)和相位(CPHA)实现。CPOL决定时钟空闲状态电平,CPHA选择数据传输的采样时刻。主从设备需保持时钟配置一致。

SPI1主模式配置步骤

1)配置相关引脚的复用功能,使能 SPI1 时钟。

启用SPI1需两步:

首先使能SPI1时钟,通过APB2ENR的第12位设置;

然后将PB3、4、5配置为复用输出AF5,分别对应SCK、MISO、MOSI,CS由软件管理。

cpp 复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能 SPI1 时钟

复用 PB3,PB4,PB5 为 SPI1 引脚的方法为:

cpp 复制代码
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3 复用为 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4 复用为 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5 复用为 SPI1

同时我们设置相应的引脚模式为复用功能模式:

cpp 复制代码
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能

2)初始化 SPI1,设置 SPI1 工作模式等。

通过SPI1_CR1寄存器配置SPI1:

设为主机模式,

数据格式8位,

设置SCK时钟极性及采样方式(CPOL/CPHA),

并调整时钟频率至最大37.5MHz,

同时确定数据格式(MSB或LSB在前)。

cpp 复制代码
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);

typedef struct
{
 uint16_t SPI_Direction;//通信方式,半双工、全双工
 uint16_t SPI_Mode;     //设置主从模式。例如主机模式SPI_Mode_Master
 uint16_t SPI_DataSize; //8/16位帧格式选择。8位传输则SPI_DataSize_8b。
 uint16_t SPI_CPOL;     //时钟极性。决定同步时钟空闲状态的电平。
 uint16_t SPI_CPHA;     //采样方式。在串行同步时钟的第几个跳变沿采集信号。SPI_CPHA_2Edge位为第2个跳变沿。
 uint16_t SPI_NSS;      //设置NSS信号由硬件控制还是软件控制。SPI_NSS_Soft软件控制
 uint16_t SPI_BaudRatePrescaler; //波特率预分频值。有2到256分频共八个值可选。
                                 //初始化的时候我们选择 256 分频值
                                 //SPI_BaudRatePrescaler_256,传输速度为 84M/256=328.125KHz。
//数据传输顺序是MSB在前还是LSB在前。MSB为最高有效位,LSB最低有效位。
 uint16_t SPI_FirstBit;         
 uint16_t SPI_CRCPolynomial; //CRC校验多项式,大于1即可。
}SPI_InitTypeDef;

使能SPI1

cpp 复制代码
SPI_Cmd(SPI1, ENABLE); //使能 SPI1 外设

SPI 传输数据

cpp 复制代码
//往SPIx数据寄存器写入数据,实现发送
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
cpp 复制代码
//从SPI的数据寄存器读数据
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

查看 SPI 传输状态

SPI传输过程中经常要判断数据是否传输完成,发送区是否为空等等状态。

cpp 复制代码
//检查SPI1的接收缓冲区非空
SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE);

W25Q128简介

W25Q128将 16M的容量分为 256个块,每个块64K字节。

一个块又分为 16个扇区,每个扇区 4K字节。

最小擦除单位为一个扇区,也就是 4K字节。

W25Q128支持不超过存储范围的任意地址开始读取数据,在发送24位地址之后程序就可以开始循环读数据,其地址会自动增加。

cpp 复制代码
//读取 SPI FLASH 
//在指定地址开始读取指定长度的数据
//NumByteToRead:要读取的字节数(最大 65535)
void W25QXX_Read(u8* pBuffer,      //数据存储区  
                 u32 ReadAddr,     //开始读取的地址(24bit)
                 u16 NumByteToRead) 
{ 
    u16 i; 
    W25QXX_CS=0; //使能器件 
    SPI1_ReadWriteByte(W25X_ReadData); //发送读取命令 
    SPI1_ReadWriteByte((u8)((ReadAddr)>>16)); //发送 24bit 地址 
    SPI1_ReadWriteByte((u8)((ReadAddr)>>8)); 
    SPI1_ReadWriteByte((u8)ReadAddr); 
    for(i=0;i<NumByteToRead;i++)
    { 
        pBuffer[i]=SPI1_ReadWriteByte(0XFF); //循环读数,主机发送空字节 
    }
    W25QXX_CS=1; 
}

正点原子提供的FLASH写入函数支持在 W25Q128的任意地址写入不超过容量的任意长度。

/4096获取扇区地址。

%4096获取在扇区内的偏移。

4096-扇区偏移 = 扇区剩余空间大小。

在SPI Flash写入时,需判断扇区是否需擦除,即检查目标区域是否全为0xFF。

因Flash写入前需擦除(置位为1),擦除后扇区全为0xFF。若区域非全0xFF,则需擦除;否则可直接写入,因未擦除位写0无效。

cpp 复制代码
//写 SPI FLASH 
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区 WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大 65535) 
u8 W25QXX_BUFFER[4096];
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) 
{ 
    u32 secpos;
    u16 secoff; u16 secremain; u16 i; 
    u8 * W25QXX_BUF; 
    W25QXX_BUF=W25QXX_BUFFER; 
    secpos=WriteAddr/4096;//扇区地址 
    secoff=WriteAddr%4096;//在扇区内的偏移
    secremain=4096-secoff;//扇区剩余空间大小
    //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
    if(NumByteToWrite<=secremain) //写入的字节数小于扇区剩余字节
        secremain=NumByteToWrite; //不大于 4096 个字节
    while(1) 
    {
        W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容
        for(i=0;i<secremain;i++)//校验数据,遍历整个扇区,判断是否都是0xFF
        {
            if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除
        }
        if(i<secremain)//需要擦除
        {
            W25QXX_Erase_Sector(secpos);//擦除这个扇区
            for(i=0;i<secremain;i++) //复制
            {
                W25QXX_BUF[i+secoff]=pBuffer[i]; 
            }
            W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区
        }
    else 
    {
        W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//已擦除的,直接写
    }
    if(NumByteToWrite==secremain) 
        break;//写入结束了
    else//写入未结束
    {
        secpos++; //扇区地址增 1
        secoff=0; //偏移位置为 0 
        pBuffer+=secremain; //指针偏移
        WriteAddr+=secremain; //写地址偏移 
        NumByteToWrite-=secremain; //字节数递减
        //如果要写入的字节数
        if(NumByteToWrite>4096)
            secremain=4096;//下一个扇区还是写不完
        else 
            secremain=NumByteToWrite; //下一个扇区可以写完了
        }
    }; 
}
相关推荐
DS小龙哥几秒前
基于STM32设计的仓库环境监测与预警系统
stm32·单片机·嵌入式硬件
0X7844 分钟前
HC-SR04(超声波)应急使用方案
stm32·单片机
深圳市青牛科技实业有限公司 小芋圆5 小时前
GC8872 是一款带故障报告功能的刷式直流电机驱动芯片, 适用于打印机、电器、工业设备以及其他小型机器。
人工智能·科技·stm32·单片机·嵌入式硬件·机器人
可喜~可乐7 小时前
CAN总线入门指南:从原理到实践
c++·stm32·单片机·硬件工程
冰糖雪莲IO10 小时前
【江协STM32】9-4/5 USART串口数据包、串口收发HEX数据包&串口收发文本数据包
网络·stm32·嵌入式硬件
无聊到发博客的菜鸟10 小时前
STM32中的MCO
stm32·单片机·嵌入式硬件
Echo_cy_10 小时前
STM32 I2C通信外设
stm32·单片机·嵌入式硬件
半个番茄10 小时前
STM32 : PWM 基本结构
stm32·单片机·嵌入式硬件
小猪写代码10 小时前
STM32 拓展 RTC案例1:使用闹钟唤醒待机模式 (HAL库)
stm32·嵌入式硬件·实时音视频
qq_4597300312 小时前
STM32-ADC模数转换
stm32·单片机·嵌入式硬件