SPI Communication with W25Q128 Flash on STM32 using HAL Library
Project Overviwe
Main MCU: STM32F429IGTX, SPI slave device: W25Q128 NOR Flash
Expected Behavior
After development board reset, flash_id ok is printed over UART. When the KEY0 button is pressed, the string flash test is written to address 0 of W25Q128, then read back and printed to the serial console.
STM32CubeMX Configuration
- Enable RCC and HSE, set SYSCLK and HCLK to 180MHz, leave other clock settings as default
- Configure PA4 (KEY0 input pin) as GPIO_Input mode
- Enable and configure USART1 for serial output
- Enable and configure SPI2 peripheral
- Configure PB12 (software-controlled chip select) as GPIO_Output with default high level
- Enable microLIB in project settings to support
printfoutput
SPI Byte Transfer Wrapper
uint8_t spi2_transfer_byte(uint8_t tx_byte)
{
uint8_t rx_byte = 0;
HAL_SPI_TransmitReceive(&hspi2, &tx_byte, &rx_byte, 1, 2000);
return rx_byte;
}
W25Q128 Driver Implementation
w25q128.c
#include "w25q128.h"
// Read value from status register 1
uint8_t w25q_read_status(void)
{
uint8_t status = 0;
// Pull CS low to start transaction
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
// Send read status command (0x05)
spi2_transfer_byte(0x05);
status = spi2_transfer_byte(0xFF);
// Pull CS high to end transaction
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
return status;
}
// Read manufacturer and device ID
uint16_t w25q_read_jedec_id(void)
{
uint16_t device_id = 0;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
// Send read ID command (0x90)
spi2_transfer_byte(0x90);
spi2_transfer_byte(0x00);
spi2_transfer_byte(0x00);
spi2_transfer_byte(0x00);
// Read 2-byte ID
device_id |= (spi2_transfer_byte(0xFF) << 8);
device_id |= spi2_transfer_byte(0xFF);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
return device_id;
}
// Read multiple bytes from flash memory
void w25q_read_bytes(uint8_t *output_buf, uint32_t read_addr, uint16_t length)
{
uint16_t i = 0;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
// Send read data command (0x03)
spi2_transfer_byte(0x03);
spi2_transfer_byte((read_addr >> 16) & 0xFF);
spi2_transfer_byte((read_addr >> 8) & 0xFF);
spi2_transfer_byte(read_addr & 0xFF);
// Read all requested bytes
for(i = 0; i < length; i++)
{
output_buf[i] = spi2_transfer_byte(0xFF);
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
}
// Wait for flash to finish internal operation
void w25q_wait_not_busy(void)
{
// Wait until busy bit (bit 0) is cleared
while((w25q_read_status() & 0x01) == 0x01);
}
// Enable write/erase operations
void w25q_write_enable(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
// Send write enable command (0x06)
spi2_transfer_byte(0x06);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
}
// Erase 4KB sector containing target address
void w25q_erase_sector(uint32_t target_addr)
{
// Align address to 4KB sector boundary
target_addr = (target_addr / 4096) * 4096;
w25q_wait_not_busy();
w25q_write_enable();
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
// Send sector erase command (0x20)
spi2_transfer_byte(0x20);
spi2_transfer_byte((target_addr >> 16) & 0xFF);
spi2_transfer_byte((target_addr >> 8) & 0xFF);
spi2_transfer_byte(target_addr & 0xFF);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
}
// Program data to flash page
void w25q_page_program(uint8_t *input_buf, uint32_t write_addr, uint16_t len)
{
uint16_t i = 0;
w25q_wait_not_busy();
w25q_write_enable();
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
// Send page program command (0x02)
spi2_transfer_byte(0x02);
spi2_transfer_byte((write_addr >> 16) & 0xFF);
spi2_transfer_byte((write_addr >> 8) & 0xFF);
spi2_transfer_byte(write_addr & 0xFF);
// Send input data
for(i = 0; i < len; i++)
{
spi2_transfer_byte(input_buf[i]);
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
}
// Top level write function: erase sector and write input data
void w25q_write_data(uint8_t *input_buf, uint32_t write_addr, uint16_t len)
{
w25q_erase_sector(write_addr);
w25q_page_program(input_buf, write_addr, len);
}
w25q128.h
#ifndef __W25Q128_H
#define __W25Q128_H
#include "spi.h"
#define W25Q128_ID 0xEF16
extern uint8_t spi2_transfer_byte(uint8_t tx_byte);
uint8_t w25q_read_status(void);
uint16_t w25q_read_jedec_id(void);
void w25q_read_bytes(uint8_t *output_buf, uint32_t read_addr, uint16_t length);
void w25q_wait_not_busy(void);
void w25q_write_enable(void);
void w25q_erase_sector(uint32_t target_addr);
void w25q_page_program(uint8_t *input_buf, uint32_t write_addr, uint16_t len);
void w25q_write_data(uint8_t *input_buf, uint32_t write_addr, uint16_t len);
#endif
Main Application Code
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2022 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 "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "w25q128.h"
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
uint8_t tx_text[] = "flash test";
uint8_t rx_buffer[sizeof(tx_text)];
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI2_Init();
MX_USART1_UART_Init();
uint16_t flash_id = w25q_read_jedec_id();
if(flash_id != W25Q128_ID)
{
printf("Flash ID error: 0x%x\r\n", flash_id);
}
else
{
printf("flash_id ok\r\n");
}
GPIO_PinState key_state;
while (1)
{
key_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4);
if(key_state == GPIO_PIN_RESET)
{
HAL_Delay(50); // Button debounce
if(key_state == GPIO_PIN_RESET)
{
w25q_write_data(tx_text, 0, sizeof(tx_text));
w25q_read_bytes(rx_buffer, 0, sizeof(tx_text));
printf("Read data: %s\r\n", rx_buffer);
}
}
}
}
// Redirect printf output to USART1
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 15;
RCC_OscInitStruct.PLL.PLLN = 216;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
if (HAL_PWREx_EnableOverDrive() != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif /* USE_FULL_ASSERT */
Note: You must enable the microLIB option in your project settings for
printfto work correctly.