STM32-HAL-SPI-读写W25Q128FV-JEDEC ID(1)

一、SPI串行通信协议

1.1 SPI通信协议简介

SPI是串行外设接口,是Motorola首先在其MC68HCxx系列处理器上定义的。SPI是一种高速的、全双工,同步的通信总线,并且在芯片的管教上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便。

1.2 SPI工作原理

SPI的通信原理很简单,它以主从的方式工作,这种模式通常有一个主设备和一个或者多个从设备,至少需要4根线,事实上3根也可以(片选信号可以直接接地,默认选中)。SPI的4根线是MOSI主设备输出从设备输入),MISO主设备输入从设备输出),CK(时钟信号),CS(片选信号,低电平有效)。
在这里插入图片描述

1.3 SPI特性

  • 三线全双工同步传输
  • 8位或16位传输帧格式选择
  • 主或从操作
  • 8个主模式波特率预分频系数
  • 可编程的时钟极性和相位
  • 可编程的数据顺序,MSB在前或者LSB在前
  • 可触发的中断的专用发送和接收标志
  • SPI总线忙状态标志
  • 支持DMA功能的1字节发送和接收缓冲器,产生发送和接收请求

二、W25Q128FV芯片介绍

2.1 芯片基本参数介绍

W25QXX系列存储芯片是基于SPI接口的串行Flash存储器,主要有W25Q64、W25Q128等。芯片支持工作电压在2.7~3.6V之间,在正常工作时电流小于5mA,掉电时低于1uA。最大支持104MHz的编程速度。支持SPI和QSPI读写方式。

  • W25Q128每页256个字节。页是最小的可读、可写单元,也是编程和擦除的最小单位。
  • W25Q128的块大小为64KB。块是由多个页组成的一个较大的存储单元,每个块通常包含256个页面。
  • 如果需要更改或者擦除某个页、块中的某个数据,那么就要将整个页或者块全部写入或者擦除。
  • W25Q128的扇区大小为4KB。扇区是块的子单元,每个块包含16个扇区。更改或者擦除一个扇区的数据不会影响其他的扇区的数据。
  • 字节是最小的可寻址单元,一个字节是由8个二进制位组成,可以存储一个字符或者数字。

2.2 芯片管脚介绍

  • 芯片管脚图
    在这里插入图片描述
  • 管脚的名称
管脚编号 管脚名称 功能描述
1 /CS 片选输入,为低电平时选中,高电平时未选中
2 DO(IO1) 数据输出(数据输入输出1)
3 /WP(IO2) 写保护输入(数据输入输出2),低电平写保护
4 GND
5 DI(IO0) 输入输入(数据输入输出0)
6 CLK 串行时钟
7 /HOLD(IO3) 数据保持(数据输入输出3)低电平有效
8 VCC 电源正
  • 不同的SPI模式的引脚作用
标准SPI模式下管脚功能 CS DI DP WP HOLD
双倍SPI模式下管脚功能 CS IO0 IO1 WP HOLD
四倍SPI模式下管脚功能 CS IO0 IO1 IO2 IO3

2.3 技术手册等更多信息

点击访问官网
在这里插入图片描述

三、开发板的板载Flash的连接电路

在这里插入图片描述
在这里插入图片描述
1、从电路图中可以看出,芯片的供电电压为3.3V

2、芯片的数据线为4线,接在SPI1总线上

3、芯片读写使能用的是软件片选

  • 芯片和MCU引脚对应表
SPI1_SCK 时钟线 PB3
F_CS 片选(低电平选中)PB14
SPI1_MISO 主机输入从机输出 PB4
SPI1_MOSI 主机输出从机输入PB5

四、测试准备

  • 基于STM32F407ZET6的正点原子开发板(Flash芯片在左上角W25Q128FV)

../../_images/stm32f407_explorer.png

  • 安装windows系统并安装CubemxKeil MDK的电脑

五、初始化片上外设SPI1

由于硬件原因需要修改成对应硬件的引脚

5.1 初始化SPI1

  • 初始化为全双工模式(因为要进行读写操作)

  • 直接无视软件自动配置的引脚,手动设置硬件对应的引脚为SPI1对应的引脚

在这里插入图片描述

  • 时钟源设置为外部高速时钟

在这里插入图片描述
【重要】查看开发板的板载晶振的频率(根据自己的开发板的晶振频率设置),因此设置输入的时钟的频率为8Hz,经过分频后最后设置频率为最大168MHz

  • 时钟树设置,经过分频、倍频后

在这里插入图片描述

  • 针对SPI1总线进行参数设置

在这里插入图片描述

  • 配置参数说明
Mode Full-Duplex Mater 全双工模式主模式
Frame Format Motorola(摩托罗拉)
Data Size 8 Bit( 查看芯片的技术手册可得)
First Bit 高位在前:MSB First ( 查看芯片的技术手册可得)
Prescaler(For BaudRate) 可设置为最高速率:2分频 42.0MBts/s ( 查看芯片的技术手册可得)
Clock Polarity 时钟极性为上升沿 : Low ( 查看芯片的技术手册可得)
Clock Phase 时钟相位为0 : 1 Edge( 查看芯片的技术手册可得)
CRC Calucation 不设置CRC校验:Disable
NSS Signal Type 设置软件片选:SoftWare
MSB MSB代表“Most Significant Bit”,即最高有效位,是二进制数中权值最高的位,通常位于左侧。在多字节数据传输时,MSB通常是首先传输的字节。
LSB LSB代表“Least Significant Bit”,即最低有效位,是二进制数中权值最低的位,通常位于右侧。在多字节数据传输时,LSB通常是最后传输的字节。

Clock Polarity和Clock Phase的具体设置方式需要依照设备和应用场景进行调整的
在本次测试中,由于W25Q128芯片的读写模式支持Mode 0(0,0)Mode3(1,1) 选择了模式0作为读写模式

在这里插入图片描述

芯片的数据手册第6.1章节

模式 CPOL(极性) CPHA(相位) 空闲时SCK时钟 采样时刻
模式0 0 0 低电平 奇数边沿
模式1 0 1 低电平 偶数边沿
模式2 1 0 高电平 奇数边沿
模式3 1 1 高电平 偶数边沿

一般经常用的就是模式0和模式3。经过测试,设置为模式0或者模式3均可进行正确的读写。

5.2 设置片选引脚PB14

在这里插入图片描述

5.3 配置串口打印模式

参考以前的配置文章进行设置

5.4 设置生成Keil- MDK代码文件

六、读写芯片JEDEC ID

6.1 芯片技术手册和读JEDEC ID流程

在SPI总线上接收三个字节的数据,分别表示制造商ID、设备类型、容量

在这里插入图片描述

芯片的Jedec ID指令

在这里插入图片描述

芯片的Jedec ID介绍
  • 常见的存储芯片的类型和它的Jedec ID
芯片类型 Jedec ID
SST25VF016B_ID 0xBF2541
MX25L1606E_ID 0xC22015
W25Q64BV_ID(BV JV FV) 0xEF4017
W25Q128_ID 0xEF4018

通过SPI总线读取W25Q128的JEDEC ID,并将JEDEC ID整合为一个32位的数值。具体流程如下:

  1. 通过调用sf_SetCS函数使能W25Q128芯片的片选信号。
  2. 将读取ID命令(0x9F)放入发送缓冲区g_spiTxBuf,并将发送缓冲区长度g_spiLen设置为4(包括一个读取ID命令和3个地址字节)。
  3. 调用bsp_spiTransfer函数发送读取ID命令,并从DO引脚接收3个字节的ID数据,并将接收到的数据存储在g_spiRxBuf接收缓冲区中。
  4. g_spiRxBuf接收缓冲区中读取3个字节的ID数据,并分别存储在id1、id2和id3变量中。
  5. 通过调用sf_SetCS函数禁用W25Q128芯片的片选信号。
  6. 将3个字节的ID数据整合成一个32位的数值uiID,其中id1占据高16位,id2占据中8位,id3占据低8位。
  7. 将整合后的32位ID数值返回。

6.2 编写读芯片JEDEC ID代码

代码来自安富莱电子

[bsp_flash.c]

#include "bsp_flash.h"
SFLASH_T g_tSF;  //定义结构体

/*
*********************************************************************************************************
*	函 数 名: sf_SetCS
*	功能说明: 串行FALSH片选控制函数
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void sf_SetCS(uint8_t _Level)
{
    
    
	if (_Level == 0)
	{
    
    
		bsp_SpiBusEnter();     
		SF_CS_0();
	}
	else
	{
    
    		
		SF_CS_1();	
		bsp_SpiBusExit();		
	}
}

/*
*********************************************************************************************************
*	函 数 名: sf_ReadInfo
*	功能说明: 读取器件ID,并填充器件参数
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void sf_ReadInfo(void)
{
    
    
	/* 自动识别串行Flash型号 */
	{
    
    
		g_tSF.ChipID = sf_ReadID();	/* 芯片ID */

		switch (g_tSF.ChipID)
		{
    
    
			case SST25VF016B_ID:
				strcpy(g_tSF.ChipName, "SST25VF016B");
				g_tSF.TotalSize = 2 * 1024 * 1024;	/* 总容量 = 2M */
				g_tSF.SectorSize = 4 * 1024;		/* 扇区大小 = 4K */
				break;

			case MX25L1606E_ID:
				strcpy(g_tSF.ChipName, "MX25L1606E");
				g_tSF.TotalSize = 2 * 1024 * 1024;	/* 总容量 = 2M */
				g_tSF.SectorSize = 4 * 1024;		/* 扇区大小 = 4K */
				break;

			case W25Q64BV_ID:
				strcpy(g_tSF.ChipName, "W25Q64");
				g_tSF.TotalSize = 8 * 1024 * 1024;	/* 总容量 = 8M */
				g_tSF.SectorSize = 4 * 1024;		/* 扇区大小 = 4K */
				break;
			
			case W25Q128_ID:
				strcpy(g_tSF.ChipName, "W25Q128");
				g_tSF.TotalSize = 16 * 1024 * 1024;	/* 总容量 = 8M */
				g_tSF.SectorSize = 4 * 1024;		/* 扇区大小 = 4K */
				break;			

			default:
				strcpy(g_tSF.ChipName, "Unknow Flash");
				g_tSF.TotalSize = 2 * 1024 * 1024;
				g_tSF.SectorSize = 4 * 1024;
				break;
		}
	}
}

/*
*********************************************************************************************************
*	函 数 名: sf_ReadID
*	功能说明: 读取器件ID
*	形    参:  无
*	返 回 值: 32bit的器件ID (最高8bit填0,有效ID位数为24bit)
*********************************************************************************************************
*/
uint32_t sf_ReadID(void)
{
    
    
	uint32_t uiID;
	uint8_t id1, id2, id3;

	sf_SetCS(0);							    /* 使能片选 */
	g_spiLen = 0;
	g_spiTxBuf[0] = (CMD_RDID);	  /* 发送读ID命令 0x9F */
	g_spiLen = 4;
	bsp_spiTransfer();
	
	id1 = g_spiRxBuf[1];					/* 读ID的第1个字节 */
	id2 = g_spiRxBuf[2];					/* 读ID的第2个字节 */
	id3 = g_spiRxBuf[3];					/* 读ID的第3个字节 */
	sf_SetCS(1);							    /* 禁能片选 */

	uiID = ((uint32_t)id1 << 16) | ((uint32_t)id2 << 8) | id3;   /*ID整合*/

	return uiID;
}
[bsp_flash.h]
#ifndef __BSP_FLASH_H_
#define __BSP_FLASH_H_

#include "stm32f4xx_hal.h"
#include "main.h"
#include "spi.h"
#include "string.h"

#define SF_CS_0()					F_CS_GPIO_Port->BSRR = ((uint32_t)F_CS_Pin << 16U) 
#define SF_CS_1()					F_CS_GPIO_Port->BSRR = F_CS_Pin
	  
#define CMD_AAI       0xAD  	/* AAI 连续编程指令(FOR SST25VF016B) */
#define CMD_DISWR	    0x04		/* 禁止写, 退出AAI状态 */
#define CMD_EWRSR	    0x50		/* 允许写状态寄存器的命令 */
#define CMD_WRSR      0x01  	/* 写状态寄存器命令 */
#define CMD_WREN      0x06		/* 写使能命令 */
#define CMD_READ      0x03  	/* 读数据区命令 */
#define CMD_RDSR      0x05		/* 读状态寄存器命令 */
#define CMD_RDID      0x9F		/* 读器件ID命令 */
#define CMD_SE        0x20		/* 擦除扇区命令 */
#define CMD_BE        0xC7		/* 批量擦除命令 */
#define DUMMY_BYTE    0xA5		/* 哑命令,可以为任意值,用于读操作 */

#define WIP_FLAG      0x01		/* 状态寄存器中的正在编程标志(WIP) */

typedef struct
{
    
    
	uint32_t ChipID;		/* 芯片ID */
	char ChipName[16];		/* 芯片型号字符串,主要用于显示 */
	uint32_t TotalSize;		/* 总容量 */
	uint16_t SectorSize;		/* 扇区大小 */
}SFLASH_T;

/* 定义串行Flash ID */
enum
{
    
    
	SST25VF016B_ID = 0xBF2541,
	MX25L1606E_ID  = 0xC22015,
	W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */
	W25Q128_ID     = 0xEF4018
};

void bsp_InitSFlash(void);
void sf_ReadInfo(void);
uint32_t sf_ReadID(void);
void sf_SetCS(uint8_t _Level);

extern SFLASH_T g_tSF;

#endif

[spi.c]
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    spi.c
  * @brief   This file provides code for the configuration
  *          of the SPI instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "spi.h"

/* USER CODE BEGIN 0 */
enum {
    
    
	TRANSFER_WAIT,
	TRANSFER_COMPLETE,
	TRANSFER_ERROR
};


uint32_t g_spiLen;	
uint8_t  g_spi_busy; /* SPI忙状态,0表示不忙,1表示忙 */
__IO uint32_t wTransferState = TRANSFER_WAIT;


uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/*
*********************************************************************************************************
*	                             选择DMA,中断或者查询方式
*********************************************************************************************************
*/
//#define USE_SPI_DMA    /* DMA方式  */
//#define USE_SPI_INT    /* 中断方式 */
#define USE_SPI_POLL   /* 查询方式 */

/* USER CODE END 0 */

SPI_HandleTypeDef hspi1;

/* SPI1 init function */
void MX_SPI1_Init(void)
{
    
    

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    
    
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
    
    

  GPIO_InitTypeDef GPIO_InitStruct = {
    
    0};
  if(spiHandle->Instance==SPI1)
  {
    
    
  /* USER CODE BEGIN SPI1_MspInit 0 */

  /* USER CODE END SPI1_MspInit 0 */
    /* SPI1 clock enable */
    __HAL_RCC_SPI1_CLK_ENABLE();

    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**SPI1 GPIO Configuration
    PB3     ------> SPI1_SCK
    PB4     ------> SPI1_MISO
    PB5     ------> SPI1_MOSI
    */
    GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /* USER CODE BEGIN SPI1_MspInit 1 */

  /* USER CODE END SPI1_MspInit 1 */
  }
}

void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{
    
    

  if(spiHandle->Instance==SPI1)
  {
    
    
  /* USER CODE BEGIN SPI1_MspDeInit 0 */

  /* USER CODE END SPI1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_SPI1_CLK_DISABLE();

    /**SPI1 GPIO Configuration
    PB3     ------> SPI1_SCK
    PB4     ------> SPI1_MISO
    PB5     ------> SPI1_MOSI
    */
    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5);

  /* USER CODE BEGIN SPI1_MspDeInit 1 */

  /* USER CODE END SPI1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */
/*
*********************************************************************************************************
*	函 数 名: HAL_SPI_TxRxCpltCallback,HAL_SPI_ErrorCallback
*	功能说明: SPI数据传输完成回调和传输错误回调
*	形    参: SPI_HandleTypeDef 类型指针变量
*	返 回 值: 无
*********************************************************************************************************
*/
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
    
    
	wTransferState = TRANSFER_COMPLETE;
}


void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
    
    
	wTransferState = TRANSFER_ERROR;
}

/*
*********************************************************************************************************
*	函 数 名: bsp_SpiBusEnter
*	功能说明: 占用SPI总线
*	形    参: 无
*	返 回 值: 0 表示不忙  1表示忙
*********************************************************************************************************
*/
void bsp_SpiBusEnter(void)
{
    
    
	g_spi_busy = 1;
}

/*
*********************************************************************************************************
*	函 数 名: bsp_SpiBusExit
*	功能说明: 释放占用的SPI总线
*	形    参: 无
*	返 回 值: 0 表示不忙  1表示忙
*********************************************************************************************************
*/
void bsp_SpiBusExit(void)
{
    
    
	g_spi_busy = 0;
}

/*
*********************************************************************************************************
*	函 数 名: bsp_SpiBusBusy
*	功能说明: 判断SPI总线忙,方法是检测其他SPI芯片的片选信号是否为1
*	形    参: 无
*	返 回 值: 0 表示不忙  1表示忙
*********************************************************************************************************
*/
uint8_t bsp_SpiBusBusy(void)
{
    
    
	return g_spi_busy;
}

/*
*********************************************************************************************************
*	函 数 名: bsp_spiTransfer
*	功能说明: 启动数据传输
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_spiTransfer(void)
{
    
    
	if (g_spiLen > SPI_BUFFER_SIZE)
	{
    
    
		return;
	}
	
	/* DMA方式传输 */
#ifdef USE_SPI_DMA
	wTransferState = TRANSFER_WAIT;
	
    if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)	
    {
    
    
        Error_Handler();
    }
	
	while (wTransferState == TRANSFER_WAIT)
	{
    
    
		;
	}
#endif

	/* 中断方式传输 */	
#ifdef USE_SPI_INT
	wTransferState = TRANSFER_WAIT;

    if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)	
    {
    
    
        Error_Handler();
    }
	
	while (wTransferState == TRANSFER_WAIT)
	{
    
    
		;
	}
#endif

	/* 查询方式传输 */	
#ifdef USE_SPI_POLL
	if(HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)	
	{
    
    
		Error_Handler();
	}	
#endif
}

/* USER CODE END 1 */

[spi.h]
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    spi.h
  * @brief   This file contains all the function prototypes for
  *          the spi.c file
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __SPI_H__
#define __SPI_H__

#ifdef __cplusplus
extern "C" {
    
    
#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* USER CODE BEGIN Includes */
#define	SPI_BUFFER_SIZE		(4 * 1024)			
extern uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];
extern uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
extern uint32_t g_spiLen;
extern uint8_t g_spi_busy;

/* USER CODE END Includes */

extern SPI_HandleTypeDef hspi1;

/* USER CODE BEGIN Private defines */

/* USER CODE END Private defines */

void MX_SPI1_Init(void);

/* USER CODE BEGIN Prototypes */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi);
void bsp_SpiBusEnter(void);
void bsp_SpiBusExit(void);
uint8_t bsp_SpiBusBusy(void);
void bsp_spiTransfer(void);


/* USER CODE END Prototypes */

#ifdef __cplusplus
}
#endif

#endif /* __SPI_H__ */

[main.c]

/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "bsp_flash.h"
/* USER CODE END Includes */



/* USER CODE BEGIN 2 */
sf_ReadInfo();//读取芯片的ID
/* 检测串行Flash OK */
printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);
printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
/* USER CODE END 2 */


[result]

检测到串行Flash, ID = 00EF4018, 型号: W25Q128 
容量 : 16M字节, 扇区大小 : 4096字节


猜你喜欢

转载自blog.csdn.net/sinat_41690014/article/details/130409216