Interfacing a Flash W25Q16JV or W25Q64JV using the SPI protocol with an STM32 Micro Controller


Introduction

In this tutorial, we shall explore interfacing a Flash W25Q16JV or W25Q64JV using the SPI protocol with an STM32 Micro Controller. Sometimes you need a little extra storage for your micro controller projects: for files, images, fonts, audio clips, etc. These IC’s are perfect for storing these kinds of small amounts of data. If you need to store data in the GigaBytes, then it’s probably more optimal to store that on an SD Card.

You can address them as a flat memory space or, if you like, format them with a filesystem like littleFS or FAT. Great for use with data-logging or storage needs where you are comfortable with performing the management yourself.

These flash memory storage IC’s have similar limitations during writing as we had in our previous tutorial STM32 – Using a 512KB SPI EEPROM 25AA040A for Storage. The SPI EEPROM was limited to writing a page sizes of 16 bytes. The W25Q series IC’s have a page size limit of 256 bytes, and this will require logic to be able to write within that page size.



Materials List


FTDI FT232RL Mini USB to TTL Serial Converter Adapter

FTDI to USB Pinout from right to left

  • Pin 1 – GND
  • Pin 2 – CTS
  • Pin 3 – VCC
  • Pin 4 – TX
  • Pin 5 – RX
  • Pin 6 – DTR
  • USB Mini – Connect to PC via USB cable

SPI Flash Breakout Boards

W25Q16JV

W25Q64JV


Schematic


Pinouts & Configurations


Exploring the Source Code

main.h

Switching the behavior of the code to work with either the W25Q16xx or the W25Q64 chip is as easy as changing line 62 in main.h to override the use of one versus the other chip.

C
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.h
  * @brief          : Header for main.c file.
  *                   This file contains the common defines of the application.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 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 __MAIN_H
#define __MAIN_H

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_hal.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */

/* USER CODE END ET */

/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */

/* USER CODE END EC */

/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */

/* USER CODE END EM */

/* Exported functions prototypes ---------------------------------------------*/

/* USER CODE BEGIN EFP */

/* USER CODE END EFP */

/* Private defines -----------------------------------------------------------*/

/* USER CODE BEGIN Private defines */

#define W25Q16

#ifdef W25Q16
#define SPI1_CS_Pin GPIO_PIN_14
#define SPI1_CS_GPIO_Port GPIOB
#endif

#ifdef W25Q64
#define SPI1_CS_Pin GPIO_PIN_1
#define SPI1_CS_GPIO_Port GPIOC
#endif

// PC1 is Blue LED which is 4017		8MB
// PB14 is Yelllow LED which is 4015	2MB

void _Error_Handler(const char *, int);
	
#define Error_Handler() _Error_Handler((const char *)__FILE__, __LINE__)
#define REDIRECT_PRINTF
#ifdef DEBUG
#define DBG(...)    printf(__VA_ARGS__)
#else
#define DBG(...)
#endif

/* USER CODE END Private defines */

#ifdef __cplusplus
}
#endif

#endif /* __MAIN_H */

main.c

Modifying the code to RUN_TESTS requires that you #define RUN_TESTS somewhere at the top of the main.c code such as within the /* USER CODE BEGIN PD */ section, as this will change the code on line 352. Otherwise the code will execute without the test code.

C
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 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"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>

#include "w25qxx.h"
#include "paragraph.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define PAGE_SIZE 4096
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
CRC_HandleTypeDef hcrc;

SPI_HandleTypeDef hspi1;

UART_HandleTypeDef huart1;

/* USER CODE BEGIN PV */

W25QXX_HandleTypeDef w25qxx; // Handler for all w25qxx operations!

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_SPI1_Init(void);
static void MX_CRC_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

#ifdef REDIRECT_PRINTF
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#endif

#ifdef REDIRECT_PRINTF
/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the USART2 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);

  return ch;
}
#endif
// Dump hex to serial console
void dump_hex(char *header, uint32_t start, uint8_t *buf, uint32_t len) {
    uint32_t i = 0;

    printf("%s\n", header);

    for (i = 0; i < len; ++i) {

        if (i % 16 == 0) {
            printf("0x%08lx: ", start);
        }

        printf("%02x ", buf[i]);

        if ((i + 1) % 16 == 0) {
            printf("\n");
        }

        ++start;
    }
}

void fill_buffer(uint8_t pattern, uint8_t *buf, uint32_t len) {
    switch (pattern) {
    case 0:
        memset(buf, 0, len);
        break;
    case 1:
        memset(buf, 0xaa, len); // 10101010
        break;
    case 2:
        for (uint32_t i = 0; i < len; ++i)
            buf[i] = i % 256;
        break;
    default:
        DBG("Programmer is a moron");
    }
}

uint8_t check_buffer(uint8_t pattern, uint8_t *buf, uint32_t len) {

    uint8_t ret = 1;

    switch (pattern) {
    case 0:
        for (uint32_t i = 0; i < len; ++i) {
            if (buf[i] != 0)
                ret = 0;
        }
        break;
    case 1:
        for (uint32_t i = 0; i < len; ++i) {
            if (buf[i] != 0xaa)
                ret = 0;
        }
        break;
    case 2:
        for (uint32_t i = 0; i < len; ++i) {
            if (buf[i] != i % 256)
                ret = 0;
        }
        break;
    default:
        DBG("Programmer is a moron");
    }

    return ret;
}

uint32_t get_sum(uint8_t *buf, uint32_t len) {
    uint32_t sum = 0;
    for (uint32_t i = 0; i < len; ++i) {
        sum += buf[i];
    }
    return sum;
}

void runTests(void) {
	  W25QXX_result_t res;

		HAL_Delay(2000);

		uint8_t buf[PAGE_SIZE]; // Buffer the size of a page

		for (uint8_t run = 0; run <= 2; ++run) {

			DBG("\n-------------\nRun %d\n", run);

			DBG("Reading first page");

			res = w25qxx_read(&w25qxx, 0, (uint8_t*) &buf, sizeof(buf));
			if (res == W25QXX_Ok) {
				dump_hex("First page at start", 0, (uint8_t*) &buf, sizeof(buf));
			} else {
				DBG("Unable to read w25qxx\n");
			}

			DBG("Erasing first page");
			if (w25qxx_erase(&w25qxx, 0, sizeof(buf)) == W25QXX_Ok) {
				DBG("Reading first page\n");
				if (w25qxx_read(&w25qxx, 0, (uint8_t*) &buf, sizeof(buf)) == W25QXX_Ok) {
					dump_hex("After erase", 0, (uint8_t*) &buf, sizeof(buf));
				}
			}

			// Create a well known pattern
			fill_buffer(run, buf, sizeof(buf));

			// Write it to device
			DBG("Writing first page\n");
			if (w25qxx_write(&w25qxx, 0, (uint8_t*) &buf, sizeof(buf)) == W25QXX_Ok) {
				// now read it back
				DBG("Reading first page\n");
				if (w25qxx_read(&w25qxx, 0, (uint8_t*) &buf, sizeof(buf)) == W25QXX_Ok) {
					//DBG("  - sum = %lu", get_sum(buf, 256));
					dump_hex("After write", 0, (uint8_t*) &buf, sizeof(buf));
				}
			}
		}

		// Let's do a stress test
		uint32_t start;
		uint32_t sectors = w25qxx.block_count * w25qxx.sectors_in_block; // Entire chip

		DBG("Stress testing w25qxx device: sectors = %lu\n", sectors);

		DBG("Doing chip erase\n");
		start = HAL_GetTick();
		w25qxx_chip_erase(&w25qxx);
		DBG("Done erasing - took %lu ms\n", HAL_GetTick() - start);

		fill_buffer(0, buf, sizeof(buf));

		DBG("Writing all zeroes %lu sectors\n", sectors);
		start = HAL_GetTick();
		for (uint32_t i = 0; i < sectors; ++i) {
			w25qxx_write(&w25qxx, i * w25qxx.sector_size, buf, sizeof(buf));
		}
		DBG("Done writing - took %lu ms\n", HAL_GetTick() - start);

		DBG("Reading %lu sectors\n", sectors);
		start = HAL_GetTick();
		for (uint32_t i = 0; i < sectors; ++i) {
			w25qxx_read(&w25qxx, i * w25qxx.sector_size, buf, sizeof(buf));
		}
		DBG("Done reading - took %lu ms\n", HAL_GetTick() - start);

		DBG("Validating buffer .... ");
		if (check_buffer(0, buf, sizeof(buf))) {
			DBG("OK\n");
		} else {
			DBG("Not OK\n");
		}

		DBG("Doing chip erase\n");
		start = HAL_GetTick();
		w25qxx_chip_erase(&w25qxx);
		DBG("Done erasing - took %lu ms\n", HAL_GetTick() - start);

		fill_buffer(1, buf, sizeof(buf));

		DBG("Writing 10101010 (0xaa) %lu sectors\n", sectors);
		start = HAL_GetTick();
		for (uint32_t i = 0; i < sectors; ++i) {
			w25qxx_write(&w25qxx, i * w25qxx.sector_size, buf, sizeof(buf));
		}
		DBG("Done writing - took %lu ms\n", HAL_GetTick() - start);

		DBG("Reading %lu sectors\n", sectors);
		start = HAL_GetTick();
		for (uint32_t i = 0; i < sectors; ++i) {
			w25qxx_read(&w25qxx, i * w25qxx.sector_size, buf, sizeof(buf));
		}
		DBG("Done reading - took %lu ms\n", HAL_GetTick() - start);

		DBG("Validating buffer ... ");
		if (check_buffer(1, buf, sizeof(buf))) {
			DBG("OK\n");
		} else {
			DBG("Not OK\n");
		}

		DBG("Erasing %lu sectors sequentially\n", sectors);
		start = HAL_GetTick();
		for (uint32_t i = 0; i < sectors; ++i) {
			w25qxx_erase(&w25qxx, i * w25qxx.sector_size, sizeof(buf));
			if ((i > 0) && (i % 100 == 0)) {
				DBG("Done %4lu sectors - total time = %3lu s\n", i, (HAL_GetTick() - start) / 1000);
			}
		}
		DBG("Done erasing - took %lu ms\n", HAL_GetTick() - start);

}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_SPI1_Init();
  MX_CRC_Init();
  /* USER CODE BEGIN 2 */

  printf("\x1b[2J\x1b[H");	// Clear the dumb terminal screen

  HAL_GPIO_WritePin (GPIOC, GPIO_PIN_1, GPIO_PIN_RESET);
  HAL_GPIO_WritePin (GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);

  HAL_Delay(1000);

  HAL_GPIO_WritePin (GPIOC, GPIO_PIN_1, GPIO_PIN_SET);
  HAL_GPIO_WritePin (GPIOB, GPIO_PIN_14, GPIO_PIN_SET);

  char readBuffer[PAGE_SIZE] = {0x00};
  W25QXX_result_t res;

  res = w25qxx_init(&w25qxx, &hspi1, SPI1_CS_GPIO_Port, SPI1_CS_Pin);
  if (res == W25QXX_Ok) {
	  DBG("W25QXX successfully initialized\n");
	  DBG("Manufacturer       = 0x%2x\n", w25qxx.manufacturer_id);
	  DBG("Device             = 0x%4x\n", w25qxx.device_id);
	  DBG("Block size         = 0x%04lx (%lu)\n", w25qxx.block_size, w25qxx.block_size);
	  DBG("Block count        = 0x%04lx (%lu)\n", w25qxx.block_count, w25qxx.block_count);
	  DBG("Sector size        = 0x%04lx (%lu)\n", w25qxx.sector_size, w25qxx.sector_size);
	  DBG("Sectors per block  = 0x%04lx (%lu)\n", w25qxx.sectors_in_block, w25qxx.sectors_in_block);
	  DBG("Page size          = 0x%04lx (%lu)\n", w25qxx.page_size, w25qxx.page_size);
	  DBG("Pages per sector   = 0x%04lx (%lu)\n", w25qxx.pages_in_sector, w25qxx.pages_in_sector);
	  DBG("Total size (in kB) = 0x%04lx (%lu)\n",
			  (w25qxx.block_count * w25qxx.block_size) / 1024, (w25qxx.block_count * w25qxx.block_size) / 1024);
	  DBG("Total size (in Bytes) = 0x%04lx (%lu)\n",
			  (w25qxx.block_count * w25qxx.block_size), (w25qxx.block_count * w25qxx.block_size));
  } else {
	  DBG("Unable to initialize w25qxx\n");
  }

#ifdef RUN_TESTS
  runTests();
#else
#define PARAGRAPH small_paragraph

  int len = strlen(PARAGRAPH);

  res = w25qxx_write(&w25qxx, (uint32_t) 0x02321, (uint8_t *) PARAGRAPH, len);

  if (res != W25QXX_Ok) {
	  Error_Handler();
  } else {
	  printf("No Errors! Completed writing %d bytes of small paragraph!\r\n", len);
  }

  res = w25qxx_read(&w25qxx, (uint32_t) 0x02321, (uint8_t *) &readBuffer, len);

  if (res != W25QXX_Ok) {
	  Error_Handler();
  } else {
	  printf("No Errors! Completed reading %d bytes of memory containing small paragraph!\r\n\r\n", len);
	  printf(readBuffer);
	  fflush(stdout);
  }
#endif
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 180;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 2;
  RCC_OscInitStruct.PLL.PLLR = 2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Activate the Over-Drive mode
  */
  if (HAL_PWREx_EnableOverDrive() != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  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 CRC Initialization Function
  * @param None
  * @retval None
  */
static void MX_CRC_Init(void)
{

  /* USER CODE BEGIN CRC_Init 0 */

  /* USER CODE END CRC_Init 0 */

  /* USER CODE BEGIN CRC_Init 1 */

  /* USER CODE END CRC_Init 1 */
  hcrc.Instance = CRC;
  if (HAL_CRC_Init(&hcrc) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN CRC_Init 2 */

  /* USER CODE END CRC_Init 2 */

}

/**
  * @brief SPI1 Initialization Function
  * @param None
  * @retval None
  */
static 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 */
  /* SPI1 parameter configuration*/
  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_8;
  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 */

}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 19200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);

  /*Configure GPIO pin : PC1 */
  GPIO_InitStruct.Pin = GPIO_PIN_1;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /*Configure GPIO pin : PB14 */
  GPIO_InitStruct.Pin = GPIO_PIN_14;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */


/**
 * @brief  This function is executed in case of error occurrence.
 * @param  file: The file name as string.
 * @param  line: The line in file as a number.
 * @retval None
 */
void _Error_Handler(const char *file, int line)
{
	/* USER CODE BEGIN Error_Handler_Debug */
	__disable_irq();
#ifdef REDIRECT_PRINTF
	char buf[80];
	sprintf(buf, "Trapped in Error_Handler().  Called from: %s, line: %d\r\n", file, line);
	printf(buf);
#endif
	/* User can add his own implementation to report the HAL error return state */
	while(1)
	{
	}
}

/* USER CODE END 4 */

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

w25qxx.h

We want to thank the contributor of this github repo for letting us use the following code: stm32-w25qxx

C
/**
 ******************************************************************************
 * @file           : w25qxx.h
 * @brief          : Minimal W25Qxx Library Header
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2022, 2023, 2024 Lars Boegild Thomsen <lbthomsen@gmail.com>
 * 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.
 *
 ******************************************************************************
 */

#ifndef W25QXX_H_
#define W25QXX_H_

#define DEBUGxxxNO

#ifdef DEBUGxxx
#define W25_DBG(...) printf(__VA_ARGS__);\
                     printf("\r\n")
#else
#define W25_DBG(...)
#endif

#define W25QXX_MANUFACTURER_GIGADEVICE 0xC8
#define W25QXX_MANUFACTURER_WINBOND 0xEF

#define W25QXX_DUMMY_BYTE         0xA5
#define W25QXX_GET_ID             0x9F
#define W25QXX_READ_DATA          0x03
#define W25QXX_WRITE_ENABLE       0x06
#define W25QXX_PAGE_PROGRAM       0x02
#define W25QXX_SECTOR_ERASE	      0x20
#define W25QXX_CHIP_ERASE         0xc7
#define W25QXX_READ_REGISTER_1    0x05

typedef struct {
#ifdef W25QXX_QSPI
    QSPI_HandleTypeDef *qspiHandle;
#else
    SPI_HandleTypeDef *spiHandle;
    GPIO_TypeDef *cs_port;
    uint16_t cs_pin;
#endif
    uint8_t manufacturer_id;
    uint16_t device_id;
    uint32_t block_size;
    uint32_t block_count;
    uint32_t sector_size;
    uint32_t sectors_in_block;
    uint32_t page_size;
    uint32_t pages_in_sector;
} W25QXX_HandleTypeDef;

typedef enum {
    W25QXX_Ok,     // 0
    W25QXX_Err,    // 1
    W25QXX_Timeout // 2
} W25QXX_result_t;

#ifdef W25QXX_QSPI
W25QXX_result_t w25qxx_init(W25QXX_HandleTypeDef *w25qxx, QSPI_HandleTypeDef *qhspi);
#else
W25QXX_result_t w25qxx_init(W25QXX_HandleTypeDef *w25qxx, SPI_HandleTypeDef *hspi, GPIO_TypeDef *cs_port, uint16_t cs_pin);
#endif
W25QXX_result_t w25qxx_read(W25QXX_HandleTypeDef *w25qxx, uint32_t address, uint8_t *buf, uint32_t len);
W25QXX_result_t w25qxx_write(W25QXX_HandleTypeDef *w25qxx, uint32_t address, uint8_t *buf, uint32_t len);
W25QXX_result_t w25qxx_erase(W25QXX_HandleTypeDef *w25qxx, uint32_t address, uint32_t len);
W25QXX_result_t w25qxx_chip_erase(W25QXX_HandleTypeDef *w25qxx);

#endif /* W25QXX_H_ */

/*
 * vim: ts=4 et nowrap
 */

w25qxx.c

C
/**
 ******************************************************************************
 * @file           : w25qxx.h
 * @brief          : Minimal W25Qxx Library Source
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2022, 2023, 2024 Lars Boegild Thomsen <lbthomsen@gmail.com>
 * 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.
 *
 * Notice!  The library does _not_ bother to check that sectors have been erased
 * before writing.
 *
 ******************************************************************************
 */

#include "main.h"
#include "w25qxx.h"
#include <string.h>

#ifdef DEBUG
#include <stdio.h>
#endif

/*
 * Internal functions
 */

/**
 * @brief  Enables CS (driving it low) of the W25Qxx
 *
 * @param  W25Qxx handle
 * @retval None
 */
static inline void cs_on(W25QXX_HandleTypeDef *w25qxx) {
    HAL_GPIO_WritePin(w25qxx->cs_port, w25qxx->cs_pin, GPIO_PIN_RESET);
}

/**
 * @brief  Disables CS (driving it high) of the W25Qxx
 *
 * @param  W25Qxx handle
 * @retval None
 */
static inline void cs_off(W25QXX_HandleTypeDef *w25qxx) {
    HAL_GPIO_WritePin(w25qxx->cs_port, w25qxx->cs_pin, GPIO_PIN_SET);
}

/**
 * @brief  Transmit data to w25qxx - ignore returned data
 *
 * @param  W25Qxx handle
 * @param  Pointer to buffer with data to transmit
 * @param  Length (in bytes) of data to be transmitted
 * @retval None
 */
W25QXX_result_t w25qxx_transmit(W25QXX_HandleTypeDef *w25qxx, uint8_t *buf, uint32_t len) {
    W25QXX_result_t ret = W25QXX_Err;
    if (HAL_SPI_Transmit(w25qxx->spiHandle, buf, len, HAL_MAX_DELAY) == HAL_OK) {
        ret = W25QXX_Ok;
    }
    return ret;
}

/*
 * Receive data from w25qxx
 */
W25QXX_result_t w25qxx_receive(W25QXX_HandleTypeDef *w25qxx, uint8_t *buf, uint32_t len) {
    W25QXX_result_t ret = W25QXX_Err;
    if (HAL_SPI_Receive(w25qxx->spiHandle, buf, len, HAL_MAX_DELAY) == HAL_OK) {
        ret = W25QXX_Ok;
    }
    return ret;
}

uint32_t w25qxx_read_id(W25QXX_HandleTypeDef *w25qxx) {
    uint32_t ret = 0;
    uint8_t buf[3];
    cs_on(w25qxx);
    buf[0] = W25QXX_GET_ID;
    if (w25qxx_transmit(w25qxx, buf, 1) == W25QXX_Ok) {
        if (w25qxx_receive(w25qxx, buf, 3) == W25QXX_Ok) {
            ret = (uint32_t) ((buf[0] << 16) | (buf[1] << 8) | (buf[2]));
        }
    }
    cs_off(w25qxx);
    return ret;
}

uint8_t w25qxx_get_status(W25QXX_HandleTypeDef *w25qxx) {
    uint8_t ret = 0;
    uint8_t buf = W25QXX_READ_REGISTER_1;
    cs_on(w25qxx);
    if (w25qxx_transmit(w25qxx, &buf, 1) == W25QXX_Ok) {
        if (w25qxx_receive(w25qxx, &buf, 1) == W25QXX_Ok) {
            ret = buf;
        }
    }
    cs_off(w25qxx);
    return ret;
}

W25QXX_result_t w25qxx_write_enable(W25QXX_HandleTypeDef *w25qxx) {
    W25_DBG("w25qxx_write_enable");
    W25QXX_result_t ret = W25QXX_Err;
    uint8_t buf[1];
    cs_on(w25qxx);
    buf[0] = W25QXX_WRITE_ENABLE;
    if (w25qxx_transmit(w25qxx, buf, 1) == W25QXX_Ok) {
        ret = W25QXX_Ok;
    }
    cs_off(w25qxx);
    return ret;
}

W25QXX_result_t w25qxx_wait_for_ready(W25QXX_HandleTypeDef *w25qxx, uint32_t timeout) {
    W25QXX_result_t ret = W25QXX_Ok;
    uint32_t begin = HAL_GetTick();
    uint32_t now = HAL_GetTick();
    while ((now - begin <= timeout) && (w25qxx_get_status(w25qxx))) {
        now = HAL_GetTick();
    }
    if (now - begin == timeout)
        ret = W25QXX_Timeout;
    return ret;
}

#ifdef W25QXX_QSPI
W25QXX_result_t w25qxx_init(W25QXX_HandleTypeDef *w25qxx, QSPI_HandleTypeDef *qhspi) {

}
#else
W25QXX_result_t w25qxx_init(W25QXX_HandleTypeDef *w25qxx, SPI_HandleTypeDef *hspi, GPIO_TypeDef *cs_port, uint16_t cs_pin) {

    W25QXX_result_t result = W25QXX_Ok;

    W25_DBG("w25qxx_init");

    w25qxx->spiHandle = hspi;
    w25qxx->cs_port = cs_port;
    w25qxx->cs_pin = cs_pin;

    cs_off(w25qxx);

    uint32_t id = w25qxx_read_id(w25qxx);
    if (id) {
        w25qxx->manufacturer_id = (uint8_t) (id >> 16);
        w25qxx->device_id = (uint16_t) (id & 0xFFFF);

        switch (w25qxx->manufacturer_id) {
        case W25QXX_MANUFACTURER_GIGADEVICE:

            w25qxx->block_size = 0x10000;
            w25qxx->sector_size = 0x1000;
            w25qxx->sectors_in_block = 0x10;
            w25qxx->page_size = 0x100;
            w25qxx->pages_in_sector = 0x10;

            switch (w25qxx->device_id) {
            case 0x6017:
                w25qxx->block_count = 0x80;
                break;
            default:
                W25_DBG("Unknown Giga Device device");
                result = W25QXX_Err;
            }

            break;
        case W25QXX_MANUFACTURER_WINBOND:

            w25qxx->block_size = 0x10000;
            w25qxx->sector_size = 0x1000;
            w25qxx->sectors_in_block = 0x10;
            w25qxx->page_size = 0x100;
            w25qxx->pages_in_sector = 0x10;

            switch (w25qxx->device_id) {
            case 0x4017:
                w25qxx->block_count = 0x80;
                break;
            case 0x4015:
                w25qxx->block_count = 0x20;
                break;
            default:
                W25_DBG("Unknown Winbond device");
                result = W25QXX_Err;
            }

            break;
        default:
            W25_DBG("Unknown manufacturer");
            result = W25QXX_Err;
        }
    } else {
        result = W25QXX_Err;
    }

    if (result == W25QXX_Err) {
        // Zero the handle so it is clear initialization failed!
        memset(w25qxx, 0, sizeof(W25QXX_HandleTypeDef));
    }

    return result;

}
#endif

W25QXX_result_t w25qxx_read(W25QXX_HandleTypeDef *w25qxx, uint32_t address, uint8_t *buf, uint32_t len) {

    W25_DBG("w25qxx_read - address: 0x%08lx, lengh: 0x%04lx", address, len);

    // Transmit buffer holding command and address
    uint8_t tx[4] = {
    W25QXX_READ_DATA, (uint8_t) (address >> 16), (uint8_t) (address >> 8), (uint8_t) (address), };

    // First wait for device to get ready
    if (w25qxx_wait_for_ready(w25qxx, HAL_MAX_DELAY) != W25QXX_Ok) {
        return W25QXX_Err;
    }

    cs_on(w25qxx);
    if (w25qxx_transmit(w25qxx, tx, 4) == W25QXX_Ok) { // size will always be fixed
        if (w25qxx_receive(w25qxx, buf, len) != W25QXX_Ok) {
            cs_off(w25qxx);
            return W25QXX_Err;
        }
    }
    cs_off(w25qxx);

    return W25QXX_Ok;
}

W25QXX_result_t w25qxx_write(W25QXX_HandleTypeDef *w25qxx, uint32_t address, uint8_t *buf, uint32_t len) {

    W25_DBG("w25qxx_write - address 0x%08lx len 0x%04lx", address, len);

    // Let's determine the pages
    uint32_t first_page = address / w25qxx->page_size;
    uint32_t last_page = (address + len - 1) / w25qxx->page_size;

    W25_DBG("w25qxx_write %lu pages from %lu to %lu", 1 + last_page - first_page, first_page, last_page);

    uint32_t buffer_offset = 0;
    uint32_t start_address = address;

    for (uint32_t page = first_page; page <= last_page; ++page) {

        uint32_t write_len = w25qxx->page_size - (start_address & (w25qxx->page_size - 1));
        write_len = len > write_len ? write_len : len;

        W25_DBG("w25qxx_write: handling page %lu start_address = 0x%08lx buffer_offset = 0x%08lx len = %04lx", page, start_address, buffer_offset, write_len);

        // First wait for device to get ready
        if (w25qxx_wait_for_ready(w25qxx, HAL_MAX_DELAY) != W25QXX_Ok) {
            return W25QXX_Err;
        }

        if (w25qxx_write_enable(w25qxx) == W25QXX_Ok) {

            uint8_t tx[4] = {
            W25QXX_PAGE_PROGRAM, (uint8_t) (start_address >> 16), (uint8_t) (start_address >> 8), (uint8_t) (start_address), };

            cs_on(w25qxx);
            if (w25qxx_transmit(w25qxx, tx, 4) == W25QXX_Ok) { // size will always be fixed
                // Now write the buffer
                if (w25qxx_transmit(w25qxx, buf + buffer_offset, write_len) != W25QXX_Ok) {
                    cs_off(w25qxx);
                    return W25QXX_Err;
                }
            }
            cs_off(w25qxx);
        }
        start_address += write_len;
        buffer_offset += write_len;
        len -= write_len;
    }

    return W25QXX_Ok;
}

W25QXX_result_t w25qxx_erase(W25QXX_HandleTypeDef *w25qxx, uint32_t address, uint32_t len) {

    W25_DBG("w25qxx_erase, address = 0x%08lx len = 0x%04lx", address, len);

    W25QXX_result_t ret = W25QXX_Ok;

    // Let's determine the sector start
    uint32_t first_sector = address / w25qxx->sector_size;
    uint32_t last_sector = (address + len - 1) / w25qxx->sector_size;

    W25_DBG("w25qxx_erase: first sector: 0x%04lx", first_sector);W25_DBG("w25qxx_erase: last sector : 0x%04lx", last_sector);

    for (uint32_t sector = first_sector; sector <= last_sector; ++sector) {

        W25_DBG("Erasing sector %lu, starting at: 0x%08lx", sector, sector * w25qxx->sector_size);

        // First we have to ensure the device is not busy
        if (w25qxx_wait_for_ready(w25qxx, HAL_MAX_DELAY) == W25QXX_Ok) {
            if (w25qxx_write_enable(w25qxx) == W25QXX_Ok) {

                uint32_t sector_start_address = sector * w25qxx->sector_size;

                uint8_t tx[4] = {
                W25QXX_SECTOR_ERASE, (uint8_t) (sector_start_address >> 16), (uint8_t) (sector_start_address >> 8), (uint8_t) (sector_start_address), };

                cs_on(w25qxx);
                if (w25qxx_transmit(w25qxx, tx, 4) != W25QXX_Ok) {
                    ret = W25QXX_Err;
                }
                cs_off(w25qxx);
            }
        } else {
            ret = W25QXX_Timeout;
        }

    }

    return ret;
}

W25QXX_result_t w25qxx_chip_erase(W25QXX_HandleTypeDef *w25qxx) {
    if (w25qxx_write_enable(w25qxx) == W25QXX_Ok) {
        uint8_t tx[1] = {
        W25QXX_CHIP_ERASE };
        cs_on(w25qxx);
        if (w25qxx_transmit(w25qxx, tx, 1) != W25QXX_Ok) {
            return W25QXX_Err;
        }
        cs_off(w25qxx);
        if (w25qxx_wait_for_ready(w25qxx, HAL_MAX_DELAY) != W25QXX_Ok) {
            return W25QXX_Err;
        }
    }
    return W25QXX_Ok;
}

/*
 * vim: ts=4 et nowrap
 */

USART1 Dumb Terminal Output

W25Q16JV

W25Q64JV


Logic Analyzer Output

These screens are only analyzer outputs for the W25Q16JV