• The last:【STM32Cube_17】 Using hardware SPI driver TFT-LCD (ST7789)

For a better reading experience, please go:Mculover666’s personal blog.

This article describes how to configure the HARDWARE QSPI peripheral of STM32L431RCT6 to communicate with SPI Flash (W25Q64) using STM32CubeMX.

1. Preparation

Hardware preparation

  • Development board First needs to prepare a development board, here I prepared the development board of STM32L4 (BearPi) :

  • SPI Flash

    Pai Bear developed an onboard SPI Flash, the model isW25Q64, 8 MB in size, supports a maximum operating frequency of 80 Mhz.

Software to prepare

  • Keil-mdk and the corresponding chip package need to be installed in order to compile and download the generated code;
  • Prepare a serial debugging assistant, the one I used hereSerial Port Utility;

Keil MDK and Serial Port Utility installation packages are available at the end of this article.

2. Generate MDK projects

Select the chip model

Open STM32CubeMX, open MCU selector:

Search for and select the chipSTM32L431RCT6:

Configuring the Clock Source

  • If you choose to use an external high speed clock (HSE), you need to configure RCC in the System Core;
  • If you use the default internal clock (HSI), you can skip this step.

Here I use the external clock:

Configure a serial port

Bear Pai developed onboard ST-link and virtual a serial port, the schematic diagram is as follows:

Here, I switch the switch to at-MCU mode to connect the PC serial port with USART1.

Next, configure USART1:

Configure the QSPI interface

First check the SPI Flash schematic on the Little Bear development board:

Its pin connection is as follows:

SPI Flash connection pin The corresponding pin
QUADSPI_BK1_NCS PB11
QUADSPI_BK1_CLK PB10
QUADSPI_BK1_IO0 PB1
QUADSPI_BK1_IO1 PB0

Next configure the QSPI interface:

Configuring the Clock Tree

The maximum main frequency of STM32L4 is 80M, so PLL is configured and finally enabledHCLK = 80MhzYou can:

Build project Settings

Code generation Settings

Finally set to generate a separate initialization file:

The generated code

Click GENERATE CODE to GENERATE MDK-V5 project:

3. Write, compile and download user code in MDK

Redirect the printf() function

STM32Cube_09 redirects printf function to serial port output.

4. Encapsulate SPI Flash (W25Q64) commands and underlying functions

MCU sends various commands to SPI Flash to read and write the internal registers of SPI Flash. Therefore, the bare machine driver first defines the commands to be used, and then uses the library functions provided by HAL library to encapsulate three underlying functions for easy transplantation:

  • A function that sends commands to SPI Flash
  • A function that sends data to SPI Flash
  • A function that receives data from SPI Flash

Next, start writing code ~

Macros define action commands

#define ManufactDeviceID_CMD	0x90
#define READ_STATU_REGISTER_1   0x05
#define READ_STATU_REGISTER_2   0x35
#define READ_DATA_CMD	        0x03
#define WRITE_ENABLE_CMD	    0x06
#define WRITE_DISABLE_CMD	    0x04
#define SECTOR_ERASE_CMD	    0x20
#define CHIP_ERASE_CMD	        0xc7
#define PAGE_PROGRAM_CMD        0x02
Copy the code

Encapsulate the function that sends the command (emphasis)

/** * @brief sends instructions to SPI Flash * @param Instruction -- instruction to send * @param address -- address to send * @param dummyCycles -- number of empty instruction cycles * @param instructionMode -- send instructions * @param addressMode -- send addresses * @param addressSize -- addressSize * @param dataMode -- send dataMode * @retval successfully returns HAL_OK */
HAL_StatusTypeDef QSPI_Send_Command(uint32_t instruction, 
									uint32_t address, 
									uint32_t dummyCycles, 
									uint32_t instructionMode, 
									uint32_t addressMode, 
									uint32_t addressSize, 
									uint32_t dataMode)
{
    QSPI_CommandTypeDef cmd;

    cmd.Instruction = instruction;                 	/ / instructions
    cmd.Address = address;                          / / address
    cmd.DummyCycles = dummyCycles;                  // Set the number of empty instruction cycles
    cmd.InstructionMode = instructionMode;			// Command mode
    cmd.AddressMode = addressMode;   				// Address mode
    cmd.AddressSize = addressSize;   				// Address length
    cmd.DataMode = dataMode;             			// Data schema
    cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;       	// Send instructions every time
    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // No alternate bytes
    cmd.DdrMode = QSPI_DDR_MODE_DISABLE;           	// Disable the DDR mode
    cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    
	return HAL_QSPI_Command(&hqspi, &cmd, 5000);
}
Copy the code

Encapsulates a function that sends data

/** * @brief QSPI sends a specified length of data * @param buf -- start address of the send data buffer * @param size -- number of bytes to send data * @retval returns HAL_OK */ on success
HAL_StatusTypeDef QSPI_Transmit(uint8_t* send_buf, uint32_t size)
{
    hqspi.Instance->DLR = size - 1;                         // Set the data length
    return HAL_QSPI_Transmit(&hqspi, send_buf, 5000);	    // Receive data
}
Copy the code

Encapsulates a function that receives data

/** * @brief QSPI receives a specified length of data * @param buf -- the first address of the receive data buffer * @param size -- the number of bytes to receive data * @retval returns HAL_OK */ on success
HAL_StatusTypeDef QSPI_Receive(uint8_t* recv_buf, uint32_t size)
{
    hqspi.Instance->DLR = size - 1;                       // Set the data length
    return HAL_QSPI_Receive(&hqspi, recv_buf, 5000);			// Receive data
}
Copy the code

5. Write driver program for W25Q64

Next, write the W25Q64 driver using the macro definitions and underlying functions encapsulated in the previous section:

Read the Manufacture ID and Device ID

Reading these two ids from Flash does two things:

  • Check whether the SPI Flash exists
  • You can determine the Flash model based on the ID

The operation timing sequence given in the data manual is shown as follows:

According to this sequence, the code is written as follows:

/** * @brief Read the internal ID of the Flash * @param None * @retval Successfully Return device_id */
uint16_t W25QXX_ReadID(void)
{
	uint8_t recv_buf[2] = {0};	Recv_buf [0] stores the Manufacture ID. Recv_buf [1] stores the Device ID
	uint16_t device_id = 0;
	if(HAL_OK == QSPI_Send_Command(ManufactDeviceID_CMD, 0.0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_1_LINE))
	{
        / / read ID
        if(HAL_OK == QSPI_Receive(recv_buf, 2))
        {
            device_id = (recv_buf[0] < <8) | recv_buf[1];
            return device_id;
        }
        else
        {
            return 0; }}else
	{
		return 0; }}Copy the code

Read the data

SPI Flash can read data of any length (up to 65535 bytes) at any address (32 bits) without any limitation. The timing sequence given in the data manual is as follows:

The code written according to the sequence diagram is as follows:

/** * @brief Reads SPI FLASH data * @param dat_buffer -- data store * @param start_read_addr -- Address to start reading (Max. 32bit) * @param Byte_to_read -- Number of bytes to read (Max. 65535) * @retval None */
void W25QXX_Read(uint8_t* dat_buffer, uint32_t start_read_addr, uint16_t byte_to_read)
{
	QSPI_Send_Command(READ_DATA_CMD, start_read_addr, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_1_LINE);
    QSPI_Receive(dat_buffer, byte_to_read);
}
Copy the code

Read the status register data and determine whether the Flash is busy

As mentioned above, all the operations of SPI Flash are completed by sending commands, but after Flash receives the commands, it takes a period of time to execute the operations. During this period, Flash is in the “busy” state, and the commands sent by MCU are invalid and cannot be executed. There are 2-3 status registers inside Flash. Indicates the current state of Flash. An interesting point is:

When Flash is running a command, it can no longer execute the command sent by MCU, but MCU can always read the status register, which is very easy to do, MCU can always read, and then determine whether Flash is busy:

First read the status register code as follows:

* @param reg -- status register number (1~2) * @retval Status register value */
uint8_t W25QXX_ReadSR(uint8_t reg)
{
	uint8_t cmd = 0, result = 0;	
	switch(reg)
	{
		case 1:
			/* Reads status register 1 */
			cmd = READ_STATU_REGISTER_1;
		case 2:
			cmd = READ_STATU_REGISTER_2;
		case 0:
		default:
			cmd = READ_STATU_REGISTER_1;
	}
	QSPI_Send_Command(cmd, 0.0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_24_BITS, QSPI_DATA_1_LINE);
	QSPI_Receive(&result, 1);
	
	return result;
}
Copy the code

Then write a blocking function to determine whether Flash is busy:

/** * @brief blocks waiting for Flash to be idle * @param None * @retval None */
void W25QXX_Wait_Busy(void)
{
    while((W25QXX_ReadSR(1) & 0x01) = =0x01); // Wait for the BUSY bit to clear
}
Copy the code

Write Enable/Disable

The Flash chip forbids data writing by default. Therefore, before writing data to the Flash, you must send commands to enable data writing. The sequence is as follows in the data manual:

Write the function as follows:

/** * @brief W25QXX write enable, set WEL in S1 register * @param None * @retval */
void W25QXX_Write_Enable(void)
{
    QSPI_Send_Command(WRITE_ENABLE_CMD, 0.0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);
		W25QXX_Wait_Busy();
}

/** * @brief W25QXX write block, clear WEL * @param None * @retval None */
void W25QXX_Write_Disable(void)
{
    QSPI_Send_Command(WRITE_DISABLE_CMD, 0.0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);
		W25QXX_Wait_Busy();
}
Copy the code

Erase sector

SPI Flash has the following features:

Data bits can be changed from 1 to 0, but not from 0 to 1.

Therefore, erasure must be performed before writing data to Flash, and Flash can erase at least one sector. After erasure, all data in the sector changes to 0xFF (that is, all data is 1). The sequence given in the data manual is as follows:

Write the function as follows:

/** * @brief W25QXX Erasing a sector * @param sector_addr -- Sector address set according to actual capacity * @retval None * @note blocking operation */
void W25QXX_Erase_Sector(uint32_t sector_addr)
{
    sector_addr *= 4096;	// each block has 16 sectors. The size of each sector is 4KB, which needs to be converted to the actual address
    W25QXX_Write_Enable();  // Write 0xFF to erase. Enable write
    W25QXX_Wait_Busy();		// Wait for write enablement to complete
    QSPI_Send_Command(SECTOR_ERASE_CMD, sector_addr, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_NONE);
    W25QXX_Wait_Busy();   	// Wait for sector erasing to complete
}
Copy the code

Page write operation

When writing data to the Flash chip, it is possible to write data per page because of Flash’s internal structure:

The timing of page writing is shown as follows:

Write the code as follows:

/** * @brief page write operation * @param dat -- first address of data buffer to write * @param WriteAddr -- address to write * @param byte_to_write -- number of bytes to write (0-256) * @retval none */
void W25QXX_Page_Program(uint8_t* dat, uint32_t WriteAddr, uint16_t byte_to_write)
{
	W25QXX_Write_Enable();
	QSPI_Send_Command(PAGE_PROGRAM_CMD, WriteAddr, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_1_LINE);
	QSPI_Transmit(dat, byte_to_write);
    W25QXX_Wait_Busy();
}
Copy the code

6. Test drive

Write code in main.c to test the driver:

First, define two caches:

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t dat[11] = "mculover666";
uint8_t read_buf[11] = {0};
/* USER CODE END 0 */
Copy the code

Then write code in the main function:

/* USER CODE BEGIN 2 */
printf("Test W25QXX... \r\n");
device_id = W25QXX_ReadID();
printf("device_id = 0x%04X\r\n\r\n", device_id);

/* To verify, first read the data at the address to be written */
printf("-------- read data before write -----------\r\n");
W25QXX_Read(read_buf, 5.11);
printf("read date is %s\r\n", (char*)read_buf);

/* Erases the sector */
printf("-------- erase sector 0 -----------\r\n");
W25QXX_Erase_Sector(0);

/* Write data */
printf("-------- write data -----------\r\n");
W25QXX_Page_Program(dat, 5.11);

/* Read the data again */
printf("-------- read data after write -----------\r\n");
W25QXX_Read(read_buf, 5.11);
printf("read date is %s\r\n", (char*)read_buf);
/* USER CODE END 2 */
Copy the code

The test results are as follows:

Now that we have learned how to read and write SPI Flash data using the hardware QSPI interface, the next section will describe how to read SD card data using the hardware SDMMC interface.

For more exciting articles and resources, please pay attention to my wechat official number: “McUlover666”.