一、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
)
- 安装
windows
系统并安装Cubemx
和Keil 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作为读写模式
模式 | 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 |
---|---|
SST25VF016B_ID | 0xBF2541 |
MX25L1606E_ID | 0xC22015 |
W25Q64BV_ID(BV JV FV) | 0xEF4017 |
W25Q128_ID | 0xEF4018 |
通过SPI总线读取W25Q128的JEDEC ID,并将JEDEC ID整合为一个32位的数值。具体流程如下:文章来源:https://www.toymoban.com/news/detail-427262.html
- 通过调用
sf_SetCS
函数使能W25Q128芯片的片选信号。 - 将读取
ID命令(0x9F)
放入发送缓冲区g_spiTxBuf
,并将发送缓冲区长度g_spiLen
设置为4(包括一个读取ID命令和3个地址字节)。 - 调用
bsp_spiTransfer
函数发送读取ID命令,并从DO引脚接收3个字节的ID数据,并将接收到的数据存储在g_spiRxBuf
接收缓冲区中。 - 从
g_spiRxBuf
接收缓冲区中读取3个字节的ID数据,并分别存储在id1、id2和id3
变量中。 - 通过调用
sf_SetCS
函数禁用W25Q128芯片的片选信号。 - 将3个字节的ID数据整合成一个32位的数值uiID,其中id1占据高16位,id2占据中8位,id3占据低8位。
- 将整合后的32位ID数值返回。
6.2 编写读芯片JEDEC ID代码
代码来自安富莱电子文章来源地址https://www.toymoban.com/news/detail-427262.html
[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字节
到了这里,关于STM32-HAL-SPI-读写W25Q128FV-JEDEC ID(1)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!