STM32驱动FLASH(W25Q64)

2024-04-03  

1. 硬件连接

W25Q64 将 8M 的容量分为 128 个块(Block) ,每个块大小为 64K 字节 ,每个块又分为 16个扇区(Sector) ,每个扇区 4K 个字节 。

W25Q64 的 最少擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。 操作需要给 W25Q64 开辟一个至少 4K 的缓存区,对 SRAM 要求比较高,要求芯片必须有 4K 以上 SRAM 才能很好的操作。

图片

W25Q64 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V ,W25Q64 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 80Mhz(双输出时相当于 160Mhz,四输出时相当于 320M)。

1.1 硬件连接

与 STM32 的引脚连接如下:这里是使用SPI1配置。

图片

1.jpg

STM32 的 SPI 功能很强大, SPI 时钟最多可以到 18Mhz,支持 DMA,可以配置为 SPI 协议或者 I2S 协议(仅大容量型号支持)。

1.2 SPI 通讯的通讯时序

SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。

我们以读取 FLASH 的状态寄存器的时序图分析一下,时序图也是书写软件模拟时序的依据。

图片

如上图,我们知道书写 FLASH (来自华邦 W25X 手册)支持的是模式 0 (CPOL = 0 && CPHA == 0) 和 模式 3(CPOL = 1 && CPHA == 1)

CS、SCK、MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSI 与 MISO 的信号只在 CS 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。

1.2.1. 通讯的起始和停止信号

在上图,CS 信号线由高变低,为 SPI 通讯的起始信号。CS 是每个从机各自独占的信号线,当从机在自己的 CS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。当 CS 信号由低变高,为 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。

1.2.2. 数据有效性

SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。

图片

MOSI 及 MISO 数据线在 SCK 的每个时钟周期传输一位数据。数据传输时,MSB 先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI 通讯设备之间使用同样的协定,一般都会采用图中的 MSB 先行模式。

观察上图,可知模式 0 和 3 都是在上升沿读取数据。

示例:FLASH 读取 JEDEC_ID (0x9F),SPI 模式 0,,频率 f = 1MHz。

图片

读取 JEDEC_ID 时,FLASH 回复的一个字:0xC8。

图片

1.2.3 STM32 SPI外设

STM32 的 SPI 外设可用作通讯的主机及从机,支持最高的 SCK 时钟频率为 f pclk / 2 (STM32F103 型号的芯片默认 f pclk1 为 72MHz,f pclk2 为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、双线单向以及单线模式。

SPI架构:

图片

通讯引脚 :

SPI 的所有硬件架构都从上图中左 MOSI、MISO、SCK及 NSS 线展开的。

STM32 芯片有多个 SPI 外设,它们的 SPI 通讯信号引出到不同的 GPIO 引脚上,使用时必须配置到这些指定的引脚。

2. 软件配置

这里使用 STM32 的 SPI1 的主模式,SPI 相关的库函数和定义分布在文件 stm32f10x_spi.c 以及头文件 stm32f10x_spi.h 中。

2.1 配置相关引脚的复用功能

第一步就要 使能 SPI1 的时钟 , SPI1 的时钟通过 APB2ENR 的第 12 位来设置。其次要设置 SPI1 的相关引脚为 复用输出 ,这样才会连接到 SPI1 上否则这些 IO 口还是默认的状态,也就是标准输入输出口。这里我们使用的是 PA5、 PA6、 PA7 这 3 个(SCK、 MISO、 MOSI、CS 使用软件管理方式),所以设置这三个为 复用 IO 。


宏定义:


#define SPIM1_GPIO_PORT        GPIOA


#define SPIM1_CLK_IO    (GPIO_Pin_5)

#define SPIM1_MISO_IO    (GPIO_Pin_6)

#define SPIM1_MOSI_IO    (GPIO_Pin_7)


#define FLASH_CS_IO             (GPIO_Pin_2)

#define FLASH_CS_0()            (GPIO_ResetBits(SPIM1_GPIO_PORT, FLASH_CS_IO))      

#define FLASH_CS_1()             (GPIO_SetBits(SPIM1_GPIO_PORT, FLASH_CS_IO))


#define RCC_PCLK_SPIM1_GPIO     RCC_APB2Periph_GPIOA

#define RCC_PCLK_SPIM1_HD       RCC_APB2Periph_SPI1

IO 配置:


//--------------------------------------------------------------------------------------------------------

//    函 数 名: spi_gpio_init

//    功能说明: SPI 硬件IO初始化

//    形    参:     spi_chl:SPIM 通道

//    返 回 值: 无

//    日    期: 2020-03-12

//    备    注:采用 Unix like 方式

//    作    者: by 霁风AI

//--------------------------------------------------------------------------------------------------------

void spi_gpio_init(uint8_t spi_chl)

{

    GPIO_InitTypeDef gpio_config_init;


    if (spi_chl == 1)

    {

        RCC_APB2PeriphClockCmd(RCC_PCLK_SPIM1_GPIO, ENABLE);        //开启SPIM1 GPIO时钟、


//        gpio_config_init.GPIO_Pin       = SPIM1_CLK_IO | SPIM1_MISO_IO | SPIM1_MOSI_IO; //SPIM1_CLK_IO IO初始化

        gpio_config_init.GPIO_Pin       = SPIM1_CLK_IO | SPIM1_MOSI_IO;

        gpio_config_init.GPIO_Mode      = GPIO_Mode_AF_PP;  //复用推挽输出

        gpio_config_init.GPIO_Speed     = GPIO_Speed_50MHz;


        GPIO_Init(SPIM1_GPIO_PORT, &gpio_config_init);


        gpio_config_init.GPIO_Pin       = SPIM1_MISO_IO;    //SPIM1_MISO_IO IO初始化

        gpio_config_init.GPIO_Mode      = GPIO_Mode_IN_FLOATING;  //MISO浮空输入

        gpio_config_init.GPIO_Speed     = GPIO_Speed_50MHz;

        GPIO_Init(SPIM1_GPIO_PORT, &gpio_config_init);


        GPIO_SetBits(SPIM1_GPIO_PORT, SPIM1_CLK_IO | SPIM1_MISO_IO | SPIM1_MOSI_IO);    //IO初始状态都设置为高电平

    }       

}

2.2 初始化 SPI1,设置 SPI1 工作模式

接下来初始化 SPI1,设置 SPI1 为主机模式,设置数据格式为 8 位,然设置 SCK 时钟极性及采样方式。并设置 SPI1 的时钟频率(最大 18Mhz),以及数据的格式(MSB 在前还是 LSB 在前)。这在库函数中是通过 SPI_Init 函数来实现。


函数原型:


void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);

第一个参数是 SPI 标号,第二个参数结构体类型 SPI_InitTypeDef 为相关属性设置。


SPI_InitTypeDef 的定义如下:


typedef struct

{

uint16_t SPI_Direction;

uint16_t SPI_Mode;

uint16_t SPI_DataSize;

uint16_t SPI_CPOL;

uint16_t SPI_CPHA;

uint16_t SPI_NSS;

uint16_t SPI_BaudRatePrescaler;

uint16_t SPI_FirstBit;

uint16_t SPI_CRCPolynomial;

}SPI_InitTypeDef;

1.jpg


初始化的范例格式为:


//--------------------------------------------------------------------------------------------------------

//    函 数 名: spi_master_init

//    功能说明: SPI 硬件配置参数初始化

//    形    参:     spi_chl:SPIM 通道

//    返 回 值: 无

//    日    期: 2020-03-12

//    备    注:采用 Unix like 方式

//    作    者: by 霁风AI

//--------------------------------------------------------------------------------------------------------

void spi_master_init(uint8_t spi_chl)

{

    SPI_InitTypeDef  spi_config_init;

#if 1  

    if(spi_chl == 1)

    {   

        spi_flash_gpio_init();  //spi flash cs 初始化

//        sd_gpio_init(); //spi sd cs 初始化

//        nrf24l01_gpio_init();//spi nrf24l01 cs 初始化


        spi_gpio_init(1);   //spi gpio 初始化


        RCC_APB2PeriphClockCmd(RCC_PCLK_SPIM1_HD, ENABLE);  //SPI1时钟使能


        spi_config_init.SPI_Direction           = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工

        spi_config_init.SPI_Mode                = SPI_Mode_Master;      //设置SPI工作模式:设置为主SPI

        spi_config_init.SPI_DataSize            = SPI_DataSize_8b;      //设置SPI的数据大小:SPI发送接收8位帧结构

        spi_config_init.SPI_CPOL                = SPI_CPOL_Low;     //选择了串行时钟的稳态:空闲时钟低

        spi_config_init.SPI_CPHA                = SPI_CPHA_1Edge;   //数据捕获(采样)于第1个时钟沿

        spi_config_init.SPI_NSS                 = SPI_NSS_Soft;//SPI_NSS_Soft;      //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制

        spi_config_init.SPI_BaudRatePrescaler   = SPI_BaudRatePrescaler_256;        //定义波特率预分频的值:波特率预分频值为256

        spi_config_init.SPI_FirstBit            = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始

        spi_config_init.SPI_CRCPolynomial       = 7;    //CRC值计算的多项式


        SPI_Init(SPI1, &spi_config_init);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器


        SPI_Cmd(SPI1, ENABLE); //使能SPI外设


//        spi_master_send_recv_byte(1, 0xFF); //启动传输  


    }

#endif

}

2.3 SPI 传输数据

通信接口需要有发送数据和接受数据的函数,固件库提供的发送数据函数原型为:


void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);

往 SPIx 数据寄存器写入数据 Data,从而实现发送。


固件库提供的接受数据函数原型为:


uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;

这从 SPIx 数据寄存器读出接收到的数据。


收发单个字节数据:


//--------------------------------------------------------------------------------------------------------

//    函 数 名: spi_master_send_recv_byte

//    功能说明: SPI 收发数据

//    形    参:     spi_chl:SPIM 通道

//                send_byte:发送的数据

//    返 回 值: 无

//    日    期: 2020-03-14

//    备    注:采用 Unix like 方式

//    作    者: by 霁风AI

//--------------------------------------------------------------------------------------------------------

uint8_t spi_master_send_recv_byte(uint8_t spi_chl, uint8_t spi_byte)

{        

    uint8_t time = 0;


    if (spi_chl == 1)               

    {

        while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位

        {

            time++;

            if(time >200)

            {

                return false;

            }

        }             

        SPI_I2S_SendData(SPI1, spi_byte); //通过外设SPIx发送一个数据


        time = 0;


        while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位

        {

            time++;

            if(time >200)

            {

                return false;

            }

        }                               

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。