当前位置: 首页 > news >正文

基于W55MH32的WAV音频播放终端

  • 前言
  • W55MH32L-EVB
  • 项目环境
  • 硬件连接和方案
  • 上位机实现
  • 程序功能实现
    • tcp客户端功能实现
    • flash存储功能实现
    • 音频播放功能实现
  • 功能验证
  • 结语

1.前言

在智能家居、工业告警等实际场景中,设备的语音内容更新常面临“修改固件→重新烧录”的传统模式——流程繁琐。

针对这一痛点,我们推出基于W55MH32以太网单片机的WAV音频播放终端方案。设备可实现​​无线传输WAV音频文件​​,无需复杂的固件烧录流程,只需通过上位机发送文件,即可完成语音内容更新。该方案降低了开发维护成本,同时提升了设备语音内容的灵活性,适用于需要动态调整语音的场景。

2.W55MH32L-EVB

在众多MCU中选择W55MH32,是因为其在嵌入式音频网络终端应用中能提供较为全面的硬件支持:

  • 网络通信集成度高​​:芯片内置全硬件TCP/IP协议栈、MAC及PHY(物理层),相比依赖软件协议栈的方案,减少了底层协议处理的开发工作量,开发者可更专注于应用层功能(如音频传输逻辑),提升开发效率。
  • ​存储与算力基础扎实​​:内置1024KB FLASH和96KB SRAM,能够满足程序存储和未来迭代需求,保障设备在运行音频播放等功能时的稳定性,避免因存储不足导致的卡顿。
  • ​音频输出配置实用​​:提供硬件I2S(Inter-IC Sound)控制器,与DMA(直接存储器访问)协同工作,可实现数字音频的高效传输,配合外部功放模块输出音频,音质优于常见的PWM模拟方案(减少杂音干扰)。
  • ​​主控性能适配需求​​:采用Cortex-M3内核,主频最大可达216MHz,为音频解码和简单业务逻辑处理提供基本的性能支持,满足常规语音播放场景的计算需求。
  • ​​开发友好​​:官方提供丰富的官方例程(含Keil配置与烧录教程),降低了开发者的入门门槛。W55MH32L-EVB开发板

3.项目环境

硬件准备

  • W55MH32L-EVB 模块
  • 杜邦线若干
  • 交换机或路由器
  • w25q64模块
  • 一根网线
  • max98357模块
  • 喇叭

软件环境

  • 例程链接:https://www.w5500.com/w55mh32.html
  • WIZ UartTool
  • Keil5
  • Qt

4.硬件连接和方案

硬件连接

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

方案图示

在这里插入图片描述

5.上位机实现

Qt的下载使用官方源速度较慢,可访问国内镜像源下载。下载完成后,在文件当前目录打开powershell,输入下面命令,可以快速下载安装Qt。

installer.exe --mirror https://mirrors.tuna.tsinghua.edu.cn/qt或installer.exe --mirror http://mirrors.ustc.edu.cn/qtproject/

UI界面如下,用户可以通过直观的文件浏览,轻松导入本地存储的wav格式文件。
在这里插入图片描述

当用户选中播放列表中的音频并点击"发送"按钮时,上位机会自动执行以下流程:首先,读取音频文件并获取文件大小,名称;接着,依次读取数据并通过tcp传输给单片机,传输完成后,会有弹窗提示传输完成。直观了解传输状态。上位机主要代码和测试音频可通过该链接获取。

6.程序功能实现

tcp客户端功能实现

tcp客户端用于与上位机数据交互,在连接成功后,上位机会发送文件长度到客户端,客户端接收后会回传文件长度,上位机接收到后与文件长度校验无误后握手结束,开始发送文件数据。接收数据过程中使用状态机处理数据。主要代码如下:

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;}elseflash_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存储功能实现

一个功能完备的语音播放器,必须能够灵活管理音频资源。为了实现这一目标,我们没有采用私有或固化的存储格式,而是选择了一个在嵌入式领域广受验证的标准化、文件化存储方案。我们选择了W25Q64 SPI Flash,并使用FatFs文件系统,W55MH32的1M FLASH大容量足以容纳下FatFs源码。文件读写主要代码如下:

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;}}elsereturn 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;}elsereturn 1;
}

音频播放功能实现

W55MH32芯片本身就拥有一位专业的“内置声卡”——硬件I2S(Inter-IC Sound)控制器。我们充分激活并利用了这一专业音频接口,而非采用精度有限、易受干扰的PWM或普通GPIO模拟。

W55MH32的硬件I2S外设与DMA的配合,使得整个音频数据传输过程无需CPU频繁干预。DATA_Processing函数进行数据搬移与格式转换,DMA中断无缝切换缓冲区,CPU占用率极低。PCM数据被自动、不间断地从内存送至I2S总线,确保了信号输出的极致流畅与低延迟。这意味着,您听到的每一个音符,都源于最原始的数字记录,最大限度地还原了声音的细节与真实。

I2S代码使用双缓冲区来存放wav数据,这样声音会更流畅,不会出现卡顿的情况。I2S主要代码如下:

#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; // WSGPIO_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   SDATAGPIO_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;elseDMA_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)
{// 初始化i2sIIS_Configuration();// 初始化DMADMA_Configuration();// 初始化中断NVIC_Configuration();
}void i2s_loop(void)
{// 如果标志位置1,则DMA传输结束,需要更新DMA数组并重启DMAif (Flag == 1){Flag = 0;printf("IIS DMA data transmission completed!to update data. \n");// 重新启动DMADMA_Configuration();DMA_Cmd(DMA2_Channel2, ENABLE);changeFlag = !changeFlag;DATA_Processing();}
}unsigned char audio_play()
{// 读取数据并进行处理Flag = 1;DATA_Processing();i2s_loop();
}void audio_stop()
{// 关闭文件,停止DMAclose_file();DMA_Cmd(DMA2_Channel2, DISABLE);
}

7.功能验证

功能的验证可前往链接观看,当串口助手出现"receive success ! audio play"证明数据接收完毕,开始播放。出现"file read completly!“则播放完毕,音频会循环播放,因此可以看到间隔一段时间就会打印"file read completly!”。

8.结语

基于W55MH32的WAV音频播放终端,通过硬件集成(网络通信、音频接口)与软件功能(远程传输、Flash存储、播放控制)的结合,为嵌入式设备的语音内容更新提供了一种相对简便的解决方案

目前方案已实现WAV文件的远程传输与播放,后续计划逐步扩展功能,例如:

  • 增加MP3到WAV的软解码支持(兼容更多音频格式);
  • 开源更多技术细节(MP3解码算法、FatFs深度应用)

如果您对该方案感兴趣,或希望了解更多技术细节(如硬件连接、软件代码逻辑),欢迎评论留言。我们期待与您共同探索嵌入式语音应用的更多可能性!

http://www.dtcms.com/a/561659.html

相关文章:

  • 网站排名如何上升游戏网站开发计划书案例目录
  • 公司制作网站网络服务提供者知道网络用户利用其网络服务侵害
  • 谷歌网站收录入口三亚招聘网
  • Python游戏开发入门:Pygame实战
  • 识别H265
  • 建设工程项目在哪个网站查询室内展厅设计公司
  • 怎么做招聘网站赚钱广州网站app制作公司
  • 比特币市场机构化浪潮 XBIT Wallet MEXC钱包打破区域交易壁垒
  • Chainlink: 架起链上链下计算的桥梁
  • 在网上帮做图片的网站南宁电子推广网站
  • 同一家公司可以做几个网站吗自己电脑做网站要下载
  • Ubuntu20.04中如何更换为清华的镜像源
  • Linux之Shell脚本--字符串的拼接
  • ubuntu对docker的常用命令
  • 手表网站免费设计惠州做网站的公司哪家好
  • 基于Llama3.2与LlamaIndex:实现简单的文档检索RAG系统
  • 关于解决hexo博客中无法使用特定letax公式的问题
  • LLVM专栏目录页
  • 做游戏网站需要哪些许可华为外包一般能干多久
  • C++—string(2):string类的模拟实现及底层剖析
  • 建个大型网站要多少钱模板网字体库
  • 上海网站建设服务多少钱没有网站 淘宝客
  • 如何查看网站的死链接中企动力提供网站建设
  • 你的第一个 Linux 系统程序:从进度条开始
  • 企业网站域名在哪申请网站搭建徐州百都网络搭建
  • Linux同步机制:POSIX 信号量 与 SystemV信号量 的 对比
  • Vim实用技巧补充1
  • UEC++屏幕打印输出Debug信息
  • 相电流采样电阻对电流噪声影响
  • 怎么用AI制作三宫格图片,附“山的后面是什么”同款提示词