STM32F407 uses TIM DMA DAC to play WAV music in FLAH

Article directory

  • 1 Design ideas
  • 2 DAC introduction
  • 3 Implementation code
    • 3.1 Timer configuration
    • 3.2 DAC configuration
      • 3.2.1 **DAC dual-channel 8-bit loop playback configuration**
      • 3.2.2 **DAC dual-channel 8-bit loop playback configuration**
      • 3.2.3 Double buffer area to realize audio playback
      • 3.2.4 DMA interrupt configuration
    • 3.3 WAV file analysis
  • 4. Audio file generation method
  • 5. How to burn audio to flash

1 Design ideas

In some scenarios where alarm sounds need to be broadcast, of course, a file system can be installed, and then the audio files can be stored and read in the form of files. This is not only cumbersome but also takes up system resources.

Therefore, it is better to store the generated music WAV file in flash, and then read the audio data in the flash into the flash through stm32, analyze it, and transfer it to the DAC through DMA to convert it into an analog signal output. This example uses two channels of DAC1 and DAC2 to achieve dual channel output.
flash model W25Q128FV
Chip: stm32f407ZGT
The approximate steps are as follows

  1. Audio data storage: We first save the processed audio data in external Flash to ensure that the audio content can be safely stored. These data can be pre-generated alarm sounds or other sounds that need to be played.

  2. Flash reading: When an alarm event is triggered, the STM32F407 microcontroller will communicate with the external Flash through SPI (Serial Peripheral Interface) to read the stored audio data. By reading byte by byte or in blocks, we can effectively read audio data from Flash into the microcontroller.

  3. DAC Conversion: Once the audio data is read, it is fed into the input channel of a digital-to-analog converter (DAC). These DAC channels, such as DAC1 and DAC2, convert digital audio data into an analog signal that can be amplified and played back in speakers or other audio output devices.

  4. Dual-channel output: By using both channels DAC1 and DAC2 at the same time, we can achieve dual-channel analog output. This means that we can control the audio data of the left and right channels separately, thereby creating a more realistic and shocking sound effect when playing the alarm tone.

2 Introduction to DAC

DAC (Digital-to-Analog Converter, digital-to-analog converter) is used to convert digital signals into analog signals. It converts digital data into corresponding analog voltage or current output for use in analog circuits. DACs are widely used in many fields, such as audio processing, communication systems, control systems, etc.
DAC working block diagram is shown below

Pin Description

The analog output signal is our audio output port
The STM32F407 microcontroller has 2 DAC channels (we are using the stm32f407ZGT6), each channel has an output pin. The two DAC channels are DAC1 and DAC2, and they each have an output pin.
in:
The output pin of DAC1 is PA4
The output pin of DAC2 is PA5
These pins allow you to generate the corresponding analog signal via an analog voltage output to drive the speaker to play music.

3 Implementation code

Implementation process:
1. A timer TIM6 is configured to trigger the conversion of the DAC. By adjusting the timer configuration, a specific sampling rate can be achieved.
2. Realize that 8-bit/16-bit dual-channel audio data is sent to the DAC for output. It uses DMA to implement loop playback of data.
3. Implement analysis of WAV files
Note: Flash reading and writing is no longer displayed here. You can view the source code I uploaded.

3.1 Timer configuration

Configuring a timer can periodically generate a trigger signal that indicates when audio sampling and conversion should occur. Through precise timer configuration, a fixed sampling rate can be achieved, ensuring that audio data is transmitted at a consistent rate.

/**
 * @brief TIM6 Configuration
 * @param adoption frequency
 * @retval None
 */
static void TIM6_Config(uint32_t sample_rate)
{<!-- -->
    /* Reset the timer to its default state */
    TIM_DeInit(TIM6);
/*
Set the trigger frequency of the timer to be consistent with the wav adoption rate
Twav=(1/84Mhz)*(84M/fwav)=1/fwav
*/
    TIM_TimeBaseStructInit( & amp;TIM_TimeBaseStructure);
    TIM_TimeBaseStructure.TIM_Period = 84000000 / sample_rate;
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM6, & amp;TIM_TimeBaseStructure);

    /* Select TIM6 output trigger source */
    TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);

    TIM_Cmd(TIM6, ENABLE);
}

3.2 DAC configuration

3.2.1 DAC dual-channel 8-bit loop playback configuration

/**
 * @brief DAC dual-channel 8-bit loop playback configuration
 * @param buffer unsigned 8-bit dual-channel audio data, sampling rate 24000Hz
 * @param size data length (total number of bytes)
 * @retval None
 */
static void DAC_Dual8bitCircular_Config(const void *buffer, uint32_t size)
{<!-- -->
    /* DAC configuration */
    DAC_DeInit();
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;
    DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
    DAC_Init(DAC_Channel_1, & DAC_InitStructure);
    DAC_Init(DAC_Channel_2, & DAC_InitStructure);

    DAC_ClearFlag(DAC_Channel_1, DAC_FLAG_DMAUDR);
    DAC_ClearFlag(DAC_Channel_2, DAC_FLAG_DMAUDR);
    DAC_ITConfig(DAC_Channel_1, DAC_IT_DMAUDR, ENABLE);
    DAC_ITConfig(DAC_Channel_2, DAC_IT_DMAUDR, ENABLE);

    /* DMA1_Stream5 channel7 **************************************/
    /* We use dual-channel DAC mode and only need to receive a Request from one DAC channel, so only configure the DMA of DAC channel1*/
    DMA_DeInit(DMA1_Stream5);
    DMA_InitStructure.DMA_Channel = DMA_Channel_7;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & amp;DAC->DHR8RD;
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer;
    DMA_InitStructure.DMA_BufferSize = size >> 1;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA1_Stream5, & DMA_InitStructure);

    /* Enable DMA1_Stream5 */
    DMA_Cmd(DMA1_Stream5, ENABLE);

    /* Enable DAC Channel */
    DAC_Cmd(DAC_Channel_1, ENABLE);
    DAC_Cmd(DAC_Channel_2, ENABLE);

    /* Enable DMA for DAC Channel1 */
    DAC_DMACmd(DAC_Channel_1, ENABLE);
}

3.2.2 DAC dual-channel 8-bit loop playback configuration

/**
 * @brief DAC dual-channel 16-bit (high 12 bits) loop playback configuration
 * @param buffer unsigned 16-bit (high 12 bits) dual-channel audio data, sampling rate 24000Hz
 * @param size data length (total number of bytes)
 * @retval None
 */
static void DAC_Dual16bitCircular_Config(const void *buffer, uint32_t size)
{<!-- -->

    /* DAC configuration */
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;
    DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
    DAC_Init(DAC_Channel_1, & DAC_InitStructure);
    DAC_Init(DAC_Channel_2, & DAC_InitStructure);

    /* DMA1_Stream5 channel7 **************************************/
    /* We use dual-channel DAC mode and only need to receive a Request from one DAC channel, so only configure the DMA of DAC channel1*/
    DMA_DeInit(DMA1_Stream5);
    DMA_InitStructure.DMA_Channel = DMA_Channel_7;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & amp;DAC->DHR12LD;
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer;
    DMA_InitStructure.DMA_BufferSize = size >> 2;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA1_Stream5, & DMA_InitStructure);

    /* Enable DMA1_Stream5 */
    DMA_Cmd(DMA1_Stream5, ENABLE);

    /* Enable DAC Channel */
    DAC_Cmd(DAC_Channel_1, ENABLE);
    DAC_Cmd(DAC_Channel_2, ENABLE);

    /* Enable DMA for DAC Channel1 */
    DAC_DMACmd(DAC_Channel_1, ENABLE);
}

3.2.3 Double buffer area to realize audio playback

/**
 * @breif Enable dual-channel DAC output
 * @note
 * @param buffer_0 buffer 0
 * @param buffer_1 buffer 1
 * @param sample_rate sampling rate // frequency
 * @param buffer_size single buffer length
 * @param bits_per_sample sampling depth 8 or 16
 */
void DAC_Stereo_DualBuffering_Start(const uint8_t *buffer_0, const uint8_t *buffer_1, uint32_t sample_rate,
                                    uint32_t buffer_size, int bits_per_sample)
{<!-- -->
    TIM6_Config(sample_rate);//Configure the clock according to the sampling frequency

    /* DAC channel Configuration */
DAC_DMACmd(DAC_Channel_1, DISABLE);
    DAC_DeInit();//Restart DAC (when you finish playing one audio and then play another, you need to reset it)
    DMA_DeInit(DMA1_Stream5);
\t
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;
    DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;//Do not use the wave generated by the DAC itself
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
    DAC_Init(DAC_Channel_1, & DAC_InitStructure);
    DAC_Init(DAC_Channel_2, & DAC_InitStructure);

    /* DMA1_Stream5 channel7 configuration ******************************************/
    /* We use dual-channel DAC mode and only need to receive a Request from one DAC channel, so only configure the DMA of DAC channel1*/
    DMA_InitStructure.DMA_Channel = DMA_Channel_7;
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer_0;
    if (bits_per_sample == 8)
    {<!-- -->
        DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & amp;DAC->DHR8RD;
        DMA_InitStructure.DMA_BufferSize = buffer_size >> 1; // 8bit dual channel, one sample 2 bytes
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    } else {<!-- -->
        DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & amp;DAC->DHR12LD;
        DMA_InitStructure.DMA_BufferSize = buffer_size >> 2; // 16bit dual channel, one sample 4 bytes
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
    }
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    //Double buffering configuration
    DMA_DoubleBufferModeConfig(DMA1_Stream5, (uint32_t)buffer_1, DMA_Memory_0);
    DMA_DoubleBufferModeCmd(DMA1_Stream5, ENABLE);

    DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_TCIF5);//Clear the transfer completion interrupt flag
    DMA_ITConfig(DMA1_Stream5, DMA_IT_TC, ENABLE);//The DMA transfer completes the trip interrupt, in order to realize the double buffer

    DMA_Init(DMA1_Stream5, & DMA_InitStructure);

    /* Enable DMA1_Stream5 */
    DMA_Cmd(DMA1_Stream5, ENABLE);

    /* Enable DAC Channel */
    DAC_Cmd(DAC_Channel_1, ENABLE);
    DAC_Cmd(DAC_Channel_2, ENABLE);

    /* Enable DMA for DAC Channel1 */
    DAC_DMACmd(DAC_Channel_1, ENABLE);
}

This function is used for double-buffering audio playback. It accepts pointers to two buffers (buffer_0 and buffer_1), as well as other parameters such as sample rate, buffer size and sample depth.

3.2.4 DMA interrupt configuration

void DMA1_Stream5_IRQHandler()
{<!-- -->
    if (DMA_GetITStatus(DMA1_Stream5, DMA_IT_TCIF5))
    {<!-- -->
        DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_TCIF5);
        if (DAC_DMA_TransmitComplete_Callback)
        {<!-- -->
            DAC_DMA_TransmitComplete_Callback(DMA1_Stream5);//Read new data
        }
    }
if(DMA_GetFlagStatus(DMA1_Stream5,DMA_FLAG_TCIF5)!=RESET)//Wait for DMA transfer to complete
    {<!-- -->
        DMA_Cmd(DMA1_Stream5, DISABLE); //Close DMA to prevent data during processing
        DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//Clear the DMA2_Steam7 transfer completion flag
        DMA_SetCurrDataCounter(DMA2_Stream5, 4);
        DMA_Cmd(DMA2_Stream5, ENABLE); //Open DMA
    }
}

3.3 WAV file analysis

Let’s first look at the storage of wav in flash

As shown in the figure above, all audio files are connected at the end. You only need to record the first address of the file. The sample code is as shown in the figure below:

#define WAV_KEYPADTONE 0x00000000
#define WAV_TRUMPET 0x00004E4C
#define WAV_CH_STARTUP 0x000070DC
#define WAV_CH_SHUTDOWN 0x00027FC0
#define WAV_CH_JOYSTICK_MIDDLE 0x0002CE82
#define WAV_CH_JOYSTICK_BROKEN 0x00040EAC
#define WAV_CH_BATTERY_LOW 0x00052622
#define WAV_CH_BATTERY_DEAD 0x00068E82
#define WAV_CH_OUT_OF_AERA 0x000900EC
#define WAV_CH_EXCEED_INCLINATION_ANGLE 0x000A4B16
#define WAV_CH_VEHICLE_LOCKED 0x000C9BC0
#define WAV_CH_VEHICLE_LOCK 0x000F042A
#define WAV_CH_VEHICLE_UNLOCK 0x000F8B56
#define WAV_EN_STARTUP 0x00102002
#define WAV_EN_SHUTDOWN 0x0011A1FC
#define WAV_EN_JOYSTICK_MIDDLE 0x001242CA
#define WAV_EN_JOYSTICK_BROKEN 0x0014C76C
#define WAV_EN_BATTERY_LOW 0x00166DBE
#define WAV_EN_BATTERY_DEAD 0x00195620
#define WAV_EN_OUT_OF_AERA 0x001DEB48
#define WAV_EN_EXCEED_INCLINATION_ANGLE 0x00205B10
#define WAV_EN_VEHICLE_LOCKED 0x00238098
#define WAV_EN_VEHICLE_LOCK 0x00262372
#define WAV_EN_VEHICLE_UNLOCK 0x0026C11E
#define WAV_CN_APP2 0x002767CA
#define WAV_CN_APP1 0x00280E76

The wav file header description is shown in the figure below

File Header: WAV files start with a fixed-length file header, which is used to describe the basic information of the entire file. This file header usually occupies the first 44 bytes of the file and includes the following key information:

Unit block Description
File identifier (RIFF flag) 4 bytes, indicating the file type, usually “RIFF”.
File Size (File Size) 4 bytes, indicating the size of the entire file (including file header and audio data).
File format (Format) 4 bytes, indicating the file format, usually “WAVE”.
Data block identifier (“fmt” flag) 4 bytes, indicating the beginning of the format block.
Format Chunk Size (Format Chunk Size) 4 bytes, indicating the size of the format chunk.
Audio Format (Audio Format) 2 bytes, indicating the format of audio data, the common one is PCM (Pulse Code Modulation) format.
Number of Channels 2 bytes, indicating the number of channels, such as Mono or Stereo ).
Sample Rate (Sample Rate) 4 bytes, indicating the number of samples sampled per second.
Data transfer rate (Byte Rate) 4 bytes, indicating the number of bytes transmitted per second.
Data block alignment (Block Align) 2 bytes, indicating the number of bytes of each sampling frame.
Quantization bits (Bits per Sample) 2 bytes, indicating the number of bits per sample, the common ones are 8 bits and 16 bits wait.
Data block identifier (“data” flag) 4 bytes, indicating the beginning of the audio data block.
Audio data size (Data Size) 4 bytes, indicating the size of the audio data block.

Each wav file data needs to be parsed according to the above format. According to the file header, I can know how big the audio data is. Through the way wav is stored in flash, we know the first address of the file. The file header can be read through the first address, and the audio c length can be specified by parsing the file header.

The audio loading function is as follows:

//Read audio data from flash to buffer
static void wave_flash_to_buffer(uint8_t *buffer)
{<!-- -->
    int length_to_read;//The length that needs to be read from flash
    // Determine how many bytes should be read from flash
    if (player_status.segments_remaining == 0)
        return;
    else if (player_status.segments_remaining == 1)
        // If it is the last segment, read the remaining audio length
        length_to_read = player_status.data_size - player_status.player_offset;//The length to be read = audio data length - the position that has been read
    else
        length_to_read = player_status.read_buffer_size;

    //Read audio data from flash
    sFLASH_ReadBuffer(buffer, player_status.data_base + player_status.player_offset, length_to_read);
    player_status.player_offset + = length_to_read;
    player_status.segments_remaining--;

    //The audio format is unsigned 8-bit/signed 16-bit PCM and needs to be converted to unsigned
    if (player_status.bits_per_sample == 16)
    {<!-- -->
        /**
         * int16_t data is shifted to uint16_t, just + = 0x8000, equivalent to ^= 0x8000
         * Because the data is in little endian order, only the high-order address of every 2 bytes is required ^= 0x80
         */
        for (int i = 1; i < length_to_read; i + = 2)
        {<!-- -->
            buffer[i] ^= 0x80;
        }
    }

    if (player_status.num_channels == 1)
    {<!-- -->
        //The audio format is mono and needs to be copied to dual channel
        if (player_status.bits_per_sample == 16)
        {<!-- -->
            for (int i = player_status.read_buffer_size - 2; i >= 0; i -= 2)
            {<!-- -->
                buffer[i * 2] = buffer[i * 2 + 2] = buffer[i];
                buffer[(i + 1) * 2 - 1] = buffer[(i + 1) * 2 + 1] = buffer[i + 1];
            }
        }
        else
        {<!-- -->//8-bit depth single channel copied to dual channel
            for (int i = player_status.read_buffer_size - 1; i >= 0; --i)
            {<!-- -->
                buffer[i << 1] = buffer[(i << 1) | 1] = buffer[i];
            }
        }
        // Fill the part of the buffer with no data to 0x80, which is closer to the middle
        memset(buffer + length_to_read * 2, 0x80, WAVE_BUFFER_SIZE - length_to_read * 2);//After converting to dual channel, the remaining length of the buffer = total length - the number of data bytes read (single channel) * 2
    }
    else
    {<!-- -->
        // Fill the part of the buffer with no data to 0x80
        memset(buffer + length_to_read, 0x80, WAVE_BUFFER_SIZE - length_to_read); //If dual-channel data is read, the remaining length of the buffer = total length - the number of data bytes read in (dual-channel)
    }
}

The audio playback function is as follows:

/**
 * @brief Play audio in flash
 * @note The audio format is mono/dual channel wav format, and the sampling depth is unsigned 8-bit / signed 16-bit
 * @param wav_addr The address of the sound in flash
 */
void wave_play(uint32_t wave_addr)
{<!-- -->
    int header_length;

    //Set the interrupt callback function of DMA in DAC
    DAC_DMA_TransmitComplete_Callback = WAVE_DMA_TransmitComplete_Callback;

    // init player_status
    player_status.player_offset = 0;
    player_status.ready_to_stop = false;
    player_status.wav_base = wave_addr;
player_status.memory_index_for_new_data = -1;

    // Read and parse the wav header
    sFLASH_ReadBuffer(wave_header, wave_addr, WAVE_HEADER_SIZE);
    header_length = WAVE_Parsing( & amp;wave_format, wave_header);
    if (header_length < 0 || wave_format.DataSize == 0)
    {<!-- -->//Determine whether there is an error in parsing the wav format. <0 means an error, and >0 is the header length.
        while (1)
            ;
    }
//Start address of audio data = wav start address + header length
    player_status.data_base = wave_addr + header_length;

    player_status.sample_rate = wave_format.SampleRate;
    player_status.data_size = wave_format.DataSize;
    player_status.num_channels = wave_format.NumChannels;
    player_status.block_align = wave_format.BlockAlign;
    player_status.bits_per_sample = wave_format.BitsPerSample;

/*
WAVE_BUFFER_SIZE: The size of the buffer opened in memory
When it is mono-channel data, because the output is dual-channel, the read data will be copied again during output. Therefore, only half the size of the buffer can be read from flash, that is, WAVE_BUFFER_SIZE >> 1
*/
    player_status.read_buffer_size = (player_status.num_channels == 2) ? (WAVE_BUFFER_SIZE) : (WAVE_BUFFER_SIZE >> 1);

    player_status.segments_remaining = (player_status.data_size - 1u) / player_status.read_buffer_size + 1;//Integer division is rounded up

    //Put the first two pieces of data into the buffer
    if (player_status.segments_remaining == 1)
    {<!-- -->
        wave_flash_to_buffer(wave_buffer[0]);
        player_status.ready_to_stop = true;
    }
    else
    {<!-- -->
        wave_flash_to_buffer(wave_buffer[0]);
        wave_flash_to_buffer(wave_buffer[1]);
    }

    player_status.playing = true;
    // Start DAC
    DAC_Stereo_DualBuffering_Start(wave_buffer[0], wave_buffer[1], player_status.sample_rate, WAVE_BUFFER_SIZE,
                                   player_status.bits_per_sample);
}

4. Audio file generation method

It is too troublesome to burn audio files into flash one by one. All audio files can be spliced together.
Just record the first address. I provide a small plug-in developed by myself. See code attachment.

5. How to burn audio to flash

**Method 1:**Use STM32CubeProgrammer software to burn
This software does not provide firmware support for W25Q128FV model FLASH, you can write one yourself (see the attachment for relevant code)
.
Place the firmware you made in the STM32Cube\STM32CubeProgrammer\bin\ExternalLoader directory of the software

Then select the firmware.

See the attachment for firmware and source code.

**Method 2:** Use Punctual Atom’s P100 to program.