Audio Processing Series Part II : Storing Audio data on external Serial Flash Memory
Storing Audio data in an SPI Flash Memory
This is the second article of the Audio Processing Project on the STM32F411 Discovery Board. This article focuses on developing the SPI driver and the Library for the W25Q64FV 64 M-bit Serial Flash Memory from Winbond. The W25Qxx series of chips are Serial NOR Flash memory devices with capacities ranging from 4 MB to 128 MB and utilize the Serial Peripheral Interface for communication with microcontrollers.
Checkout the STM32F411 Reference Manual (RM0383) and the Datasheet for W25Q64FV 64M-bit SPI Flash Memory.
Developing the SPI Driver
Since some of the SPI1 and SPI3 peripheral pins are already occupied by the JTAG interface on the STM32F411 Discovery Board, we will use the SPI2 peripheral for our communication with the external flash memory. Remember to enable the clock to the GPIO Port B and SPI2 peripheral in the RCC registers.
For driving the NSS line Low and High to Select and Deselect the Slave device, we can write functions that set the bits in the BSRR Register for GPIO Port B. Since this is not a reusable general purpose SPI driver, I decided to keep PB12 as the SPI2_NSS pin.
Since SPI is a full-duplex protocol, we have to send and receive data simultaneously and to generate clock pulses, When writing data to the SPI bus, we must also read from it to maintain the clocking mechanism necessary for synchronizing data transfer. By sending a dummy byte (0xFF) during a write operation, we effectively create a situation where the clock pulses are generated, allowing the slave device to latch the data being sent. Thus we will Write & Read dummy bytes when we have to Read & Write from the SPI bus.
The procedure is the same for Transmitting multiple bytes with a small modification required in looping through the Input Data array or Received Data array.
Designing the W25Qxx Library
The library is designed with functions to Write, Read, and Erase the Flash Memory. Writing the memory can be done maximum in blocks of 256 bytes at a time, which is also the page size. Erasing the memory is done in sectors and blocks, where each sector is 16 pages and each block is 16 sectors. For the W25Q64FV, there are 32768 pages, 2048 sectors, and 128 blocks.
The W25Q64FV along with the other chips in this series follows a similar instruction set. For resetting the memory device, first we should send an ENABLE_RESET command (66h) and then a RESET command (99h) to execute a chip reset. The READ_ID command (9Fh) returns a 24-bit hardcoded Manufacturer ID of the memory device. The device offers dual speeds for Reading in the Standard SPI connection. The memory device requires a WRITE_ENABLE command (06h) before sending a PAGE_WRITE command (02h). The ERASE_CHIP command (60h) takes the longest to execute, taking around 100 seconds. Each operation requires some time to complete. We can either wait for the BUSY bit to be reset or we can block the CPU for the amount of time listed in the Datasheet. I choose to block the CPU.
The Initialization function will initialize the SPI bus and executes the Reset Operation of the memory device. The ReadID function will return the Manufacturer ID of the memory device.
Both Read functions take the same arguments but differ only in the time taken to execute these operations. The Write function also has a similar argument list, but before each write operation the command for Enabling Write is pushed out and at the end it is Disabled. The execution is delayed for 5 milliseconds after each Page is written so as to give time for it to finish writing.
The EraseSector function erases a sector of 16 pages. The Erase32kBlock function erases either the first half of a block or the second half of a Block depending on the second argument it takes. The Erase64kBlock function will erase a block of 16 sectors. The EraseChip function takes the longest to execute.
Preparing Audio Data to be Flashed
I utilized a trimmed version of a an mp3 file as the audio data for my DSP algorithms. I first converted the MP3 file to WAV format and then transformed it into a 16-bit signed little-endian PCM file with a sampling rate of 16 kHz. Since I was using the flashrom tool to flash the W25Q memory, I decided to pad the remaining memory with zeros to achieve a total size of 8 megabytes, as the flasher would not accept an unpadded file as input. After erasing the flash memory, I wrote the padded PCM file to it. Subsequently, I read from the flash memory and played the readback file to confirm that it is the same and not corrupted.
I opted not to use this library for flashing the serial memory due to the overhead involved in preparing the audio data in advance. With this script, you only need to provide the correct input file. You can find the original and trimmed MP3 files, along with the bash script and the padded PCM file, in my GitHub repository.