Wiznet makers

ronpang

Published November 11, 2025 © Apache License 2.0 (Apache-2.0)

140 UCC

81 WCC

32 VAR

0 Contests

1 Followers

0 Following

Original Link

WAV audio playback terminal based on W55MH32

WAV audio playback terminal based on W55MH32

COMPONENTS
PROJECT DESCRIPTION

1. Introduction


In real-world scenarios such as smart homes and industrial alarms, updating device voice content often involves the traditional "firmware modification → re-flashing" process—a cumbersome procedure.

To address this pain point, we offer a WAV audio playback terminal solution based on the W55MH32 Ethernet microcontroller. The device can wirelessly transmit WAV audio files, eliminating the need for complex firmware flashing. Voice content updates are completed simply by sending the file to a host computer. This solution reduces development and maintenance costs while enhancing the flexibility of device voice content, making it suitable for scenarios requiring dynamic voice adjustments.

2. W55MH32L-EVB


The W55MH32 was chosen from among many MCUs because it provides comprehensive hardware support for embedded audio network terminal applications:

High network communication integration: The chip integrates a full hardware TCP/IP protocol stack, MAC, and PHY (physical layer). Compared to solutions relying on software protocol stacks, this reduces the development workload for underlying protocol processing, allowing developers to focus more on application-layer functions (such as audio transmission logic) and improving development efficiency.

Solid Storage and Computing Foundation: Built-in 1024KB FLASH and 96KB SRAM meet the needs of program storage and future iterations, ensuring stability when running functions such as audio playback and preventing stuttering due to insufficient storage.

Practical Audio Output Configuration: Provides a hardware I2S (Inter-IC Sound) controller that works in conjunction with DMA (Direct Memory Access) to achieve efficient digital audio transmission. Combined with an external power amplifier module, the audio output quality is superior to common PWM analog solutions (reducing noise interference).

Main Control Performance Adaptable to Requirements: Employs a Cortex-M3 core with a maximum clock speed of 216MHz, providing basic performance support for audio decoding and simple business logic processing, meeting the computing needs of typical voice playback scenarios.

Developer-Friendly: The official website provides abundant example code (including Keil configuration and burning tutorials), lowering the entry barrier for developers.

3. Project Environment


Hardware Preparation

  • W55MH32L-EVB Module
  • Several DuPont wires
  • Switch or Router
  • W25Q64 Module
  • One network cable
  • MAX98357 Module
  • Speaker
     

Software Environment


4. Hardware Connection and Solution
 

Hardware Connection


W55MH32L-EVB_3.3V ---> w25q64_VCC
W55MH32L-EVB_GND ---> w25q64_GND
W55MH32L-EVB_PA4 ---> w25q64_CS
W55MH32L-EVB_PA5 ---> w25q64_SCK
W55MH32L-EVB_PA6 ---> w25q64_MISO
W55MH32L-EVB_PA7 ---> w25q64_MOSI

W55MH32L-EVB_3.3V ---> MAX98357_VCC
W55MH32L-EVB_GND ---> MAX98357_GND
W55MH32L-EVB_PB3 ---> MAX98357_CLK
W55MH32L-EVB_PB5 ---> MAX98357_DIN
W55MH32L-EVB_PA15 ---> MAX98357_WS

Scheme Diagram


5. Host Computer Implementation

Downloading Qt using the official source is slow; you can access a domestic mirror source to download it. After the download is complete, open PowerShell in the current directory of the file and enter the following command to quickly download and install Qt.

installer.exe --mirror https://mirrors.tuna.tsinghua.edu.cn/qt

or

installer.exe --mirror http://mirrors.ustc.edu.cn/qtproject/


The UI interface is shown below. Users can easily import WAV format files stored locally through intuitive file browsing.

When a user selects an audio file from the playlist and clicks the "Send" button, the host computer automatically executes the following process: First, it reads the audio file and obtains its size and name; then, it reads the data sequentially and transmits it to the microcontroller via TCP. A pop-up window will indicate successful transmission, providing a clear overview of the transmission status. The main host computer code and test audio can be obtained via this link.

6. Program Function Implementation

TCP Client Function Implementation
The TCP client is used for data interaction with the host computer. After a successful connection, the host computer sends the file length to the client. The client receives this and sends the file length back. The host computer receives the file length, verifies it, and after confirming its correctness, completes the handshake and begins sending file data. A state machine is used to process data during the data reception process. The main code is as follows:

static uint8_t tcps_buf[2048] = {0};
// 接受字节总长度
uint32_t receive_size = 0;
// 文件大小
uint32_t file_size = 0;
// 接收状态
uint8_t receive_state = first_receive;
/**
* @brief   tcp server,receive audio file
* @return  value for SOCK_ERRORs,return 1:no error
*/
int32_t tcps_run()
{
   int32_t ret;
   uint16_t size = 0;
   uint8_t destip[4];
   uint8_t length_buf[4];
   switch (getSn_SR(TCPS_SOCKET_ID))
   {
   case SOCK_ESTABLISHED:
       if (getSn_IR(TCPS_SOCKET_ID) & Sn_IR_CON)
       {
           getSn_DIPR(TCPS_SOCKET_ID, destip);
           // printf("%d:Connected - %d.%d.%d.%d : %d\r\n", TCPS_SOCKET_ID, destip[0], destip[1], destip[2], destip[3], destport);
           setSn_IR(TCPS_SOCKET_ID, Sn_IR_CON);
       }
       if ((size = getSn_RX_RSR(TCPS_SOCKET_ID)) > 0) // Don't need to check SOCKERR_BUSY because it doesn't not occur.
       {
           if (size > DATA_BUF_SIZE)
               size = DATA_BUF_SIZE;
           ret = recv(TCPS_SOCKET_ID, tcps_buf, size);
           if (ret <= 0)
               return ret; // check SOCKERR_BUSY & SOCKERR_XXX. For showing the occurrence of SOCKERR_BUSY.
           size = (uint16_t)ret;
           tcps_buf[size] = 0x00;
           if (size > DATA_BUF_SIZE)
           {
               printf("receive data is too long\r\n");
               while (1)
               {
               }
           }
           switch (receive_state)
           {
           case first_receive:
               // 获取文件长度并回传
               file_size = (tcps_buf[0] << 24) | (tcps_buf[1] << 16) | (tcps_buf[2] << 8) | tcps_buf[3];
               length_buf[0] = (file_size >> 24) & 0xFF;
               length_buf[1] = (file_size >> 16) & 0xFF;
               length_buf[2] = (file_size >> 8) & 0xFF;
               length_buf[3] = file_size & 0xFF;
               ret = send(TCPS_SOCKET_ID, length_buf, 4);
               if (ret < 0)
               {
                   close(TCPS_SOCKET_ID);
                   return ret;
               }
               // 关闭文件,停止播放音频
               close_file();
               audio_stop();
               receive_state = receiving;
               break;
           case receiving:
               receive_size += size;
               // 接收完成,切换状态
               if (receive_size == file_size)
               {
                   flash_write(tcps_buf, size, 1, "xie.wav");
                   receive_state = first_receive;
                   receive_size = 0;
                   return 100;
               }
               else
                   flash_write(tcps_buf, size, 0, "xie.wav");
               break;
           default:
               break;
           }
       }
       break;
   case SOCK_CLOSE_WAIT:
       if ((ret = disconnect(TCPS_SOCKET_ID)) != SOCK_OK)
           return ret;
       printf("%d:Socket Closed\r\n", TCPS_SOCKET_ID);
       break;
   case SOCK_INIT:
       printf("%d:Listen, TCP server loopback, port [%d]\r\n", TCPS_SOCKET_ID, TCPC_SOCKET_PORT);
       if ((ret = listen(TCPS_SOCKET_ID)) != SOCK_OK)
           return ret;
       break;
   case SOCK_CLOSED:
       if ((ret = socket(TCPS_SOCKET_ID, Sn_MR_TCP, TCPC_SOCKET_PORT, 0x00)) != TCPS_SOCKET_ID)
           return ret;
       printf("%d:Socket opened\r\n", TCPS_SOCKET_ID);
       // 如果不处于起始状态,则出现异常,重新置位
       if (receive_state != first_receive)
       {
           receive_size = 0;
           receive_state = first_receive;
       }
       break;
   default:
       break;
   }
   return 1;
}


Flash Storage Implementation 

A fully functional voice player must be able to flexibly manage audio resources. To achieve this, we did not use proprietary or fixed storage formats, but instead chose a standardized, file-based storage solution widely proven in the embedded field. We selected the W25Q64 SPI Flash and used the FatFs file system. The 1MB FLASH capacity of the W55MH32 is sufficient to accommodate the FatFs source code. The main code for file read/write is as follows:

uint8_t file_open_flag = 0;
uint8_t new_file_open_flag = 0;
FIL fnew;     /* 文件对象 */
FATFS fs;     /* FatFs 文件系统对象 */
uint8_t flash_write(uint8_t *buff, u32 size, u8 end_flag, u8 *filename)
{
   FRESULT res_flash;
   uint32_t written_size = 0;
   // 打开文件
   if (!file_open_flag)
   {
       res_flash = f_open(&fnew, (char *)filename, FA_CREATE_ALWAYS | FA_WRITE);
       if (res_flash != FR_OK)
       {
           printf("4error code:%d\r\n", res_flash);
           while (1)
               ;
       }
       file_open_flag = 1;
   }
   // 写入文件
   res_flash = f_write(&fnew, buff, size, &written_size);
   if (res_flash != FR_OK)
   {
       printf("3error code:%d\r\n", res_flash);
       while (1)
       {
       }
   }
   // 关闭文件
   if (end_flag)
   {
       f_close(&fnew);
       file_open_flag = 0;
   }
   return 0;
}
int flash_read(uint8_t *buff, u32 size, u8 *filename)
{
   FRESULT res_flash;
   uint32_t read_size = 0;
   // 参数检查
   if (buff == NULL || filename == NULL)
   {
       printf("Invalid parameters\r\n");
       return -1;
   }
   // 打开文件
   if (!file_open_flag)
   {
       res_flash = f_open(&fnew, (char *)filename, FA_READ);
       if (res_flash != FR_OK)
       {
           printf("2error code:%d\r\n", res_flash);
           while (1)
               ;
       }
       file_open_flag = 1;
   }
   // 读取文件
   res_flash = f_read(&fnew, buff, size, &read_size);
   if (res_flash == FR_OK)
   {
       if (read_size <= 0)
       {
           // 可能已到达文件末尾
           if (f_eof(&fnew))
           {
               f_close(&fnew);
               file_open_flag = 0;
               return -1;
           }
       }
       else
           return read_size;
   }
   else
   {
       printf("!! 读取文件失败:%d\r\n", res_flash);
       while (1)
           ;
   }
   return 0;
}
uint8_t close_file()
{
   if (f_close(&fnew) == FR_OK)
   {
       file_open_flag = 0;
       return 0;
   }
   else
       return 1;
}

Audio Playback Functionality

The W55MH32 chip itself possesses a professional "built-in sound card"—a hardware I2S (Inter-IC Sound) controller. We fully activate and utilize this professional audio interface, rather than employing PWM or ordinary GPIO analog circuits with limited precision and susceptibility to interference.

The W55MH32's hardware I2S peripheral, in conjunction with DMA, ensures that the entire audio data transfer process requires minimal CPU intervention. The DATA_Processing function handles data movement and format conversion, while DMA interrupts seamlessly switch buffers, resulting in extremely low CPU usage. PCM data is automatically and continuously transferred from memory to the I2S bus, ensuring exceptionally smooth signal output and low latency. This means that every note you hear originates from the original digital recording, maximizing the reproduction of sound detail and realism.

The I2S code uses double buffers to store WAV data, resulting in smoother sound without stuttering. The main I2S code is as follows:

#define DATA_LEN 2048
#define BufferSize (DATA_LEN / 2)
uint8_t DATA[DATA_LEN];
uint8_t I2S3_Buffer_Tx[BufferSize];
uint8_t I2S3_Buffer_Tx2[BufferSize];
uint8_t changeFlag = 0;
uint8_t Flag;
void DATA_Processing(void)
{
   uint32_t i;
   uint8_t res;
   // 读取文件
   res = flash_read(DATA, DATA_LEN, "xie.wav");
   // 检查返回值
   if (res == -1)
   {
       printf("file read completly!\r\n");
   }
   if (changeFlag)
   {
       for (i = 0; i < DATA_LEN / 2; i++)
       {
           I2S3_Buffer_Tx2[i] = DATA[2 * i] << 8 | DATA[2 * i + 1];
       }
   }
   else
   {
       for (i = 0; i < DATA_LEN / 2; i++)
       {
           I2S3_Buffer_Tx[i] = DATA[2 * i] << 8 | DATA[2 * i + 1];
       }
   }
}
void IIS_Configuration(void)
{
   I2S_InitTypeDef I2S_InitStructure;
   GPIO_InitTypeDef GPIO_InitStructure;
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
   GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; // WS
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
   GPIO_Init(GPIOA, &GPIO_InitStructure);
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5; // SCK   SDATA
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
   GPIO_Init(GPIOB, &GPIO_InitStructure);
   SPI_I2S_DeInit(SPI3);
   I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx;
   I2S_InitStructure.I2S_Standard = I2S_Standard_Phillips;
   I2S_InitStructure.I2S_DataFormat = I2S_DataFormat_16b;
   I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq_8k;
   I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
   I2S_Init(SPI3, &I2S_InitStructure);
   SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE);
   I2S_Cmd(SPI3, ENABLE);
}
void DMA_Configuration(void)
{
   DMA_InitTypeDef DMA_InitStructure;
   RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
   DMA_DeInit(DMA2_Channel2);
   DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI3->DR;
   if (changeFlag)
       DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)I2S3_Buffer_Tx2;
   else
       DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)I2S3_Buffer_Tx;
   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
   DMA_InitStructure.DMA_BufferSize = BufferSize;
   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
   DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
   DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
   DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
   DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
   DMA_Init(DMA2_Channel2, &DMA_InitStructure);
   DMA_ITConfig(DMA2_Channel2, DMA_IT_TC, ENABLE);
   /* Enable SPI1 DMA TX request */
   DMA_Cmd(DMA2_Channel2, DISABLE);
}
void DMA2_Channel2_IRQHandler(void)
{
   if (DMA_GetITStatus(DMA2_IT_TC2) == SET)
   {
       DMA_ClearITPendingBit(DMA2_IT_TC2);
       DMA_ClearFlag(DMA2_FLAG_TC2);
       DMA_Cmd(DMA2_Channel2, DISABLE);
       Flag = 1;
   }
}
void i2s_init(void)
{
   // 初始化i2s
   IIS_Configuration();
   // 初始化DMA
   DMA_Configuration();
   // 初始化中断
   NVIC_Configuration();
}
void i2s_loop(void)
{
   // 如果标志位置1,则DMA传输结束,需要更新DMA数组并重启DMA
   if (Flag == 1)
   {
       Flag = 0;
       printf("IIS DMA data transmission completed!to update data. \n");
       // 重新启动DMA
       DMA_Configuration();
       DMA_Cmd(DMA2_Channel2, ENABLE);
       changeFlag = !changeFlag;
       DATA_Processing();
   }
}
unsigned char audio_play()
{
   // 读取数据并进行处理
   Flag = 1;
   DATA_Processing();
   i2s_loop();
}
void audio_stop()
{
   // 关闭文件,停止DMA
   close_file();
   DMA_Cmd(DMA2_Channel2, DISABLE);
}

 

7. Function Verification 

Function verification can be viewed at the link. When the serial port assistant displays "receive success! audio play", it indicates that data reception is complete and playback has begun. The display of "file read complete!" indicates playback has finished. The audio will loop, so you will see "file read complete!" printed at intervals.

8. Conclusion 

This WAV audio playback terminal based on the W55MH32 provides a relatively simple solution for updating voice content in embedded devices by combining hardware integration (network communication, audio interface) and software functions (remote transmission, Flash storage, playback control).

Currently, the solution has achieved remote transmission and playback of WAV files. Future plans include gradually expanding functionality, such as:

Adding software decoding support from MP3 to WAV (compatible with more audio formats);

Open-sourcing more technical details (MP3 decoding algorithm, deep application of FatFs).

If you are interested in this solution or would like to learn more about the technical details (such as hardware connection, software code logic), please leave a comment. We look forward to exploring more possibilities for embedded voice applications with you!

———————————————— Copyright Notice: This article is an original article by CSDN blogger "Playing with Ethernet," and is licensed under CC 4.0 BY-SA. Please include the original source link and this statement when reprinting.

Original link: https://blog.csdn.net/2301_81684513/article/details/154290059

Documents
Comments Write