- 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 here
Serial 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 = 80Mhz
You 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.
- The last:【STM32Cube_17】 Using hardware SPI driver TFT-LCD (ST7789)
- Next up:【 STM32CUB-19 】 Use SDMMC interface to read and write SD card data.
For more exciting articles and resources, please pay attention to my wechat official number: “McUlover666”.