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; //下一个扇区可以写完了
        }
    }; 
}
相关推荐
憧憬一下1 小时前
Linux内核早期打印机制与RS485通信技术
arm开发·嵌入式硬件·嵌入式·linux驱动开发
电子工程师UP学堂2 小时前
电子应用设计方案-37:智能鼠标系统方案设计
人工智能·单片机·嵌入式硬件·计算机外设
Q23553688722 小时前
100V降压恒流芯片SL2516D 内置MOS管 支持15W功率输出 电动车照明
单片机·嵌入式硬件
石板小湫2 小时前
46 基于单片机的烧水壶系统设计
单片机·嵌入式硬件
lucy1530275107912 小时前
【青牛科技】BISS0001高性能的传感信号处理集成电路芯片,广泛用于安防、自控等领域能
科技·单片机·智能家居·信号处理·安防·工控主板
白天看海12 小时前
40 基于单片机的温湿度检测判断系统
单片机·嵌入式硬件
电子设计师12 小时前
45 基于单片机的信号选择与温度变化
单片机·嵌入式硬件
zcb84964437112 小时前
27 基于51单片机的方向盘模拟系统
嵌入式硬件·51单片机·proteus·方向盘
test猿13 小时前
电与计算机的关系
单片机·嵌入式硬件·物联网
编程圈子15 小时前
STM32 HAL库开发学习3.STM32启动浅析
stm32·嵌入式硬件·学习