Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

SPI Communication with W25Q128 Flash on STM32 using HAL Library

Tech 1

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 printf output

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 printf to work correctly.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.