Article Directory
1. SPI serial communication protocol
1.1 Introduction to SPI communication protocol
SPI is a serial peripheral interface, which was first defined by Motorola on its MC68HCxx series processors. SPI is a high-speed, full-duplex, synchronous communication bus, and it only occupies four wires in the management of the chip, which saves the pins of the chip, and at the same time saves space for the layout of the PCB and provides convenience.
1.2 SPI working principle
The communication principle of SPI is very simple. It works in a master-slave mode. This mode usually has a master device and one or more slave devices . At least 4 wires are required. In fact, 3 wires are also possible (the chip select signal can be directly grounded. , selected by default). The 4 lines of SPI are MOSI master output slave device input), MISO master device input slave device output), CK (clock signal), CS (chip select signal, active low).
1.3 SPI characteristics
- Three-wire full-duplex synchronous transmission
- 8-bit or 16-bit transmission frame format selection
- master or slave operation
- 8 master mode baud rate prescaler coefficients
- Programmable clock polarity and phase
- Programmable data order, MSB first or LSB first
- Dedicated transmit and receive flags for triggerable interrupts
- SPI bus busy status flag
- 1-byte transmit and receive buffers supporting DMA function to generate transmit and receive requests
2. Introduction of W25Q128FV chip
2.1 Introduction to the basic parameters of the chip
W25QXX series memory chips are serial Flash memory based on SPI interface, mainly including W25Q64, W25Q128 and so on. The chip supports working voltage between 2.7~3.6V, the current is less than 5mA in normal operation, and less than 1uA in power-off state. The maximum programming speed is 104MHz. Support SPI and QSPI read and write methods.
- W25Q128 has 256 bytes per page. A page is the smallest readable and writable unit, as well as the smallest unit of programming and erasing.
- The W25Q128 has a block size of 64KB. A block is a larger unit of storage consisting of multiple pages, each block typically containing 256 pages.
- If a certain data in a page or block needs to be changed or erased, the entire page or block must be written or erased.
- The sector size of W25Q128 is 4KB. A sector is a subunit of a block, and each block contains 16 sectors. Changing or erasing data in one sector will not affect data in other sectors.
- A byte is the smallest addressable unit. A byte is composed of 8 binary bits and can store a character or a number.
2.2 Chip pin introduction
- Chip pin diagram
- pin name
Pin No. | Pin name | Functional description |
---|---|---|
1 | /CS | Chip select input, selected when low level, unselected when high level |
2 | TO(IO1) | Data output (data input and output 1) |
3 | /WP(IO2) | Write protection input (data input and output 2), low level write protection |
4 | GND | land |
5 | DI(IO0) | input input (data input output 0) |
6 | CLK | serial clock |
7 | /HOLD(IO3) | Data hold (data input and output 3) active low |
8 | VCC | Power positive |
- Pin functions of different SPI modes
Pin functions in standard SPI mode | CS | FROM | DP | WP | HOLD |
---|---|---|---|---|---|
Pin function in double SPI mode | CS | 100 | IO1 | WP | HOLD |
Pin function in quadruple SPI mode | CS | 100 | IO1 | IO2 | IO3 |
2.3 Technical manual and more information
Click to visit the official website
3. The connection circuit of the onboard Flash of the development board
1. It can be seen from the circuit diagram that the power supply voltage of the chip is 3.3V
2. The data line of the chip is 4 lines, connected to SPI1
the bus
3. Chip read and write enable use software chip select
- Chip and MCU pin correspondence table
SPI1_SCK | Clock line PB3 |
---|---|
F_CS | Chip selection (low level selection) PB14 |
SPI1_MISO | Host input slave output PB4 |
SPI1_MOSI | Master output slave input PB5 |
4. Test preparation
- Based on
STM32F407ZET6
the punctual atom development board (Flash chip is in the upper left cornerW25Q128FV
)
- Install
windows
the system and installCubemx
theKeil MDK
computer
5. Initialize the on-chip peripheral SPI1
Due to hardware reasons, it needs to be modified to correspond to hardware pins
5.1 Initialize SPI1
-
Initialize to full-duplex mode (because read and write operations are required)
-
Directly ignore the pins automatically configured by the software, and manually set the corresponding pins of the hardware to the
SPI1
corresponding pins
- The clock source is set to an external high-speed clock
【重要】
Check the frequency of the onboard crystal oscillator of the development board (set according to the crystal frequency of your own development board), so set the frequency of the input clock to 8Hz, and finally set the frequency to a maximum of 168MHz after frequency division
- Clock tree setting, after frequency division and multiplication
SPI1
Parameterization for the bus
- Configuration parameter description
Mode | Full-Duplex Mater full-duplex mode master mode |
---|---|
Frame Format | Motorola (Motorola) |
Data Size | 8 Bit (available from the technical manual of the chip) |
First Bit | High bit first: MSB First (check the chip's technical manual) |
Prescaler(For BaudRate) | Can be set to the highest rate: 42.0MBts/s divided by 2 (check the technical manual of the chip) |
Clock Polarity | Clock polarity is rising edge: Low (check the technical manual of the chip) |
Clock Phase | The clock phase is 0: 1 Edge (check the technical manual of the chip) |
CRC Calucation | Do not set CRC check: Disable |
NSS Signal Type | Set software chip selection: SoftWare |
MSB | MSB stands for "Most Significant Bit", the most significant bit, which is the highest-valued bit in a binary number, usually on the left. In multi-byte data transfers, the MSB is usually the byte transmitted first. |
---|---|
LSB | LSB stands for "Least Significant Bit", the least significant bit, which is the lowest-weight bit in a binary number, usually on the right. In multi-byte data transfers, the LSB is usually the last byte transferred. |
Clock Polarity和Clock Phase
The specific setting method needs to be adjusted according to the device and application scenario
. In this test, due toW25Q128
the read-write mode support of the chipMode 0(0,0)
andMode3(1,1)
mode 0 was selected as the read-write mode
model | CPOL (polarity) | CPHA (phase) | SCK clock when idle | sampling time |
---|---|---|---|---|
mode 0 | 0 | 0 | low level | odd edge |
mode 1 | 0 | 1 | low level | even edge |
mode 2 | 1 | 0 | high level | odd edge |
mode 3 | 1 | 1 | high level | even edge |
Mode 0 and Mode 3 are commonly used. After testing, it can be read and written correctly when set to mode 0 or mode 3.
5.2 Set chip select pin PB14
5.3 Configure the serial port printing mode
Refer to the previous configuration article to set up
5.4 Set to generate Keil-MDK code files
6. Read and write chip JEDEC ID
6.1 Chip technical manual and process of reading JEDEC ID
Receive three bytes of data on the SPI bus, respectively indicating the manufacturer ID, device type, and capacity
- Common types of memory chips and their Jedec IDs
chip type | Eater ID |
---|---|
SST25VF016B_ID | 0xBF2541 |
MX25L1606E_ID | 0xC22015 |
W25Q64BV_ID(BV JV FV) | 0xEF4017 |
W25Q128_ID | 0xEF4018 |
Read the JEDEC ID of W25Q128 through the SPI bus, and integrate the JEDEC ID into a 32-bit value. The specific process is as follows:
sf_SetCS
Enable the chip select signal of the W25Q128 chip by calling the function.- Put the read
ID命令(0x9F)
into the send bufferg_spiTxBuf
andg_spiLen
set the send buffer length to 4 (including a read ID command and 3 address bytes). - Call
bsp_spiTransfer
the function to send the read ID command, and receive 3 bytes of ID data from the DO pin, and store the received data in theg_spiRxBuf
receive buffer. g_spiRxBuf
Read 3 bytes of ID data from the receive buffer and store them inid1、id2和id3
variables respectively.- Disable the chip select signal of the W25Q128 chip by calling
sf_SetCS
the function. - The 3-byte ID data is integrated into a 32-bit value uiID, in which id1 occupies the upper 16 bits, id2 occupies the middle 8 bits, and id3 occupies the lower 8 bits.
- Return the integrated 32-bit ID value.
6.2 Write JEDEC ID code for reading chip
The code is from Anfulai Electronics
[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字节