拓冰建站拓冰建站
首页 / 资讯中心 / 正文

NUCLEO-H753ZI I2S音频无声?从协议到DMA的完整排查指南

NUCLEO-H753ZI加一个I2S音频DAC烧完代码喇叭一点声音都没有。如果你也遇到这个问题第一反应是不是跟我当初一样板子坏了说实话这块板子确实不便宜但根据我在论坛和群里看到的案例I2S不出声九成以上都不是硬件损坏而是配置、接线或者时序上的问题。这篇文章就把我之前排查NUCLEO-H753ZI上I2S音频不输出的完整过程写出来从协议细节到CubeMX配置从DMA缓冲区到逻辑分析仪实测如果你手头正在折腾这块板子的I2S可以按着这个思路一路查下去。1. 项目概述先别急着换板子I2S无声问题的定位路径1.1 问题场景还原“Not transmitting I2S audio on NUCLEO-H753ZI (board broken?)”这种帖子在嵌入式社区里隔几天就能看到一条。发帖人通常已经做了不少尝试CubeMX里把I2S外设配好了HAL库的初始化代码也生成了调用了HAL_I2S_Transmit()或者HAL_I2S_Transmit_DMA()接上MAX98357A或者PCM5102A这类DAC模块结果喇叭/耳机里安静得吓人。这里其实要区分几种不一样的现象不能一刀切完全无声连底噪都没有。这个最常见问题多半出在I2S外设没有真正工作或者DAC根本没有拿到数据。有短暂的爆音或咔哒声。这通常说明BCLK和WS已经出来了数据线有问题或者DAC的上电时序没处理好。只有一边响。常见原因是WS极性/格式配置和DAC不匹配或者DAC的左右声道配置有问题。声音像“机器人说话”音调明显不对。这个往往是BCLK频率算错了或者时钟树PLL配置和实际采样率对不上。我见过不少人在帖子末尾追加一句“我确认板子没坏因为下载LED闪烁程序是正常的”——这就说明芯片和调试链路都活着问题大概率在I2S这条专项链路上。反正我的习惯是先不怀疑硬件先把软件和信号链路捋一遍。1.2 分层排查思路遇到I2S无声不要东一榔头西一棒子。我习惯把整条链路分成五层逐层确认这样定位速度快也不会漏掉低级错误。层级检查内容判断手段常见结果第一层电源、地线、杜邦线、模块供电万用表通断、眼观DAC没供电第二层时钟生成BCLK、WS、MCLK是否输出示波器、逻辑分析仪根本没有BCLK第三层外设配置引脚的AF复用、GPIO速度CubeMX配置检查引脚没选对第四层数据流DMA是否在搬运、缓冲区位置Debug查看、逻辑分析仪抓SD线DMA缓冲区放错区域第五层外部DAC模式、寄存器、MCLK需求数据手册、示波器测DAC输出模块需要MCLK没接这个顺序很重要。先从物理层开始因为物理连接出问题最快暴露、也最好修最后再怀疑外部DAC因为它涉及芯片手册、寄存器这些更繁琐的东西。按照这个顺序走一遍就算最后真的发现是板子的问题你手里也已经有完整证据链不至于在论坛里被各路网友来回追问。2. I2S协议与NUCLEO-H753ZI的音频硬件基础2.1 三根线说清楚I2S时序I2SInter-IC Sound本质上是一种串行总线协议专门用来传输数字音频数据。物理上最少三根线BCLK位时钟每一个脉冲对应一个数据位频率等于采样率 × 声道数 × 位深。WS声道选择也叫LRCK低电平表示左声道高电平表示右声道频率等于采样率。SD串行数据实际音频数据的传输线。很多DAC模块还会引出第四条线MCLK主时钟它是DAC内部Σ-Δ调制器和数字滤波器的工作时钟频率通常是采样率的256倍或者512倍例如48kHz采样率对应12.288MHz的MCLK。不是所有DAC都需要外部MCLK像PCM5102A、MAX98357A这类带内部时钟恢复的模块只接BCLK、WS、SD三根线就能工作但有些DAC芯片比如CS4344的某些用法或者需要系统时钟的编码解码芯片缺了MCLK就完全不输出。我特别想回应一下很多初学者都会搜到的一个问题“I2S主设备读取数据和从设备准备好数据都是在BCLK的上升沿吗”标准I2S协议给出的答案是发送端在每个BCLK的下降沿更新/移出数据接收端在每个BCLK的上升沿采样数据。数据变化和数据采样刻意避开了同一个边沿这样接收端能在数据稳定的中点去读取抗干扰能力更好。你可以简单理解成发送方在“下坡”的时候把数据摆上去接收方在“上坡”的时候把数据捡起来两边错开半拍谁也不会踩到谁的脚。WS的变化通常也发生在BCLK的下降沿接收端在BCLK上升沿读取WS就能知道当前这个bit属于左声道还是右声道。另外一个容易搞混的地方是“标准I2S”和“左对齐”的差异标准Philips I2S里数据比WS变化晚一个BCLK周期才开始传输也就是有一个bit的延迟左对齐格式则是在WS变化后的第一个BCLK周期就开始传数据。这两种格式只差一个时钟周期但DAC如果只支持其中一种配置错了就完全解不出有效数据。我在调试的时候经常用逻辑分析仪抓BCLK和SD两根线看数据位相对于WS的位置偏移了几个时钟周期。只要看到偏移稳定基本就能判断协议格式对不对。2.2 NUCLEO-H753ZI的I2S资源与引脚NUCLEO-H753ZI使用的是STM32H753ZIT6U这颗芯片板上没有集成音频编解码芯片这跟很多Discovery板不一样。你必须在外部接一个DAC模块然后通过排针把I2S信号引出去。在STM32H7系列上I2S是复用SPI外设实现的。比如SPI1可以配置成I2S1SPI2可以配置成I2S2SPI3可以配置成I2S3。CubeMX的外设选择界面里你找到对应的SPI外设把Mode切到“I2S”相关选项引脚就会自动变成I2S功能。有一个非常常见的坑NUCLEO-H753ZI的板子虽然把很多引脚引到了Arduino接口排针上但并不是每一个I2S外设的引脚都方便接。比如我用I2S1对应的引脚是PA4WS、PA5CK、PA6SD、PA7MCK这几个引脚正好落在Arduino排针的D10到D13附近飞线非常方便。而I2S2的引脚在PB12到PB15虽然板上也有引出但位置就比较偏。所以建议在CubeMX选外设的时候打开芯片引脚图对照NUCLEO-H753ZI的原理图可以在ST官网下载优先选那些引脚正好在Arduino排针上的I2S外设省得自己飞线到芯片引脚增加虚焊风险。另外还有一个细节I2S的BCLK工作在1.5MHz甚至3MHz以上的频率如果GPIO输出速度配置太低波形上升沿会变得很缓DAC可能采不到正确的电平。CubeMX里把I2S相关引脚GPIO Speed设置为Very High这个我在实际项目里对比过确实能改善信号质量。2.3 外接DAC模块的检查要点如果你用的是现成的DAC模块在上电之前先确认几件事第一供电。常见模块有3.3V和5V两种供电选项你要确认模块上的LDO或者稳压电路能不能接受3.3V供电。很多模块板上自带稳压5V供电更稳但如果你把开发板的3.3V引脚当成5V去接模块可能完全不工作甚至过压损坏。第二共地。开发板的地和DAC模块的地必须连在一起这是数字电路的基本要求。我见过有人只接三根信号线、不接地线结果信号完全乱套。第三MCLK需求。翻一下模块主控芯片的数据手册看看是否需要MCLK。需要的话就把CubeMX里I2S配置的Master Clock Output使能并把MCLK引脚和模块对应引脚连起来。第四杜邦线长度。I2S的BCLK是兆赫兹级别的方波信号用十几厘米甚至几十厘米的杜邦线飞线时线间电容和寄生电感会让波形严重畸变。我自己实测过超过20厘米的杜邦线在3MHz的BCLK下波形就已经没法看了。如果必须用长线可以考虑用带屏蔽的杜邦线或者直接缩短连线或者把BCLK频率降低比如用24kHz采样率先验证通路。3. 从CubeMX到HAL库的完整配置实操3.1 时钟树配置与BCLK频率计算I2S的BCLK频率直接决定了音频采样率公式很简单BCLK 采样率 × 声道数 × 每个声道的位时钟数以最常见的48kHz采样率、双声道、16bit数据为例如果使用标准16bit帧每个声道16个BCLK周期那么BCLK 48000 × 2 × 16 1,536,000 Hz 1.536MHz如果你在CubeMX里选择“16-bit data on 32-bit frame”也就是每个声道占32个BCLK周期那么BCLK就是48000 × 2 × 32 3.072MHz很多高阶DAC模块对数据格式有硬性要求比如某些I2S输入只支持32bit slot这时你就得选32位帧BCLK也会随之翻倍。在CubeMX里配置时钟树时找到Clock Configuration界面把I2S的kernel clock来源选为一个PLL输出比如PLL1 Q或者PLL2 P然后在I2S外设配置界面里填上Audio Frequency 48000。CubeMX会自动计算分频参数保证输出的BCLK频率和48kHz匹配。如果你填了48000之后CubeMX报错说明内核时钟频率没法整除出这个BCLK需要回去换一个PLL源或者调整PLL倍频。这里有一个经验在时钟树配置里I2S kernel clock最好选一个和它独立的PLL输出不要跟CPU主频强相关。H7的PLL1通常负责SYSCLK你要是硬从PLL1分频出I2S时钟改主频的时候容易把音频时钟一起带偏。3.2 I2S外设参数设置CubeMX里进入I2S外设配置页关键参数有五个每一个都值得细看Mode选择Master Transmit。你是主设备自己产生BCLK和WS。Standard默认是Philips标准对应上面说的标准I2S时序。如果你确认DAC是左对齐或者右对齐就改成对应选项。Data Format16-bit data on 16-bit frame还是16-bit data on 32-bit frame。这个必须和DAC支持格式一致。Clock Polarity即CPOL。标准Philips I2S下发送端在下降沿更新数据接收端在上升沿采样所以CPOL通常保持Low。如果设成High数据和WS会在上升沿变化接收端在下降沿采样虽然有些DAC也兼容但很多无法正常工作。Master Clock Output使能还是禁用。取决于你的DAC是否需要MCLK。生成代码后CubeMX会在i2s.c里生成类似hi2s1.Init.Standard I2S_STANDARD_PHILIPS、hi2s1.Init.DataFormat I2S_DATAFORMAT_16B、hi2s1.Init.ClockPolarity I2S_CPOL_LOW这样的初始化代码。调试的时候如果怀疑参数不对先回来核对这几个值比瞎改硬件高效得多。3.3 DMA发送与H7缓冲区陷阱用轮询方式HAL_I2S_Transmit()发送短数据没问题但音频数据是持续流轮询会占满CPU。更合理的做法是用DMA搬运让I2S外设自己在后台从内存读数据、移位输出。STM32H7系列在这里有一个特别隐蔽、很多人踩过的坑普通DMA1/DMA2无法访问DTCM RAM。H7内核有DTCMData Tightly-Coupled Memory地址在0x20000000它和内核紧耦合CPU访问速度极快但它不在DMA的AHB总线上。如果你把音频缓冲区定义成普通的全局数组而链接脚本默认把它放在DTCM区域那么DMA搬运的时候根本读不到数据I2S就只会输出BCLK和WSSD线上空白一片。解决办法是手动把音频缓冲区放到DMA可访问的内存区域比如AXI SRAM起始地址0x24000000或者D2域的SRAM0x30000000。在CubeMX生成的H7工程里如果使用GCC工具链可以用类似下面的方式__attribute__((section(.sram))) uint16_t sine_buf[128];如果你的工程链接脚本里没有.sram这个段名就需要打开.ld链接脚本看哪个执行区域映射到了0x24000000或者0x30000000然后改成对应段名。用Keil或者IAR的话可以直接在变量声明里用__attribute__((at(0x24000000)))这类语法不过不同编译器写法有差异。另外还要注意Cache一致性问题。H7的D-Cache默认可能开启DMA读取内存时直接从AXI总线读而CPU写入的数据可能还留在Cache里没刷回内存。DMA拿到的就是旧数据。解决办法是发送前调用SCB_CleanDCache_by_Addr((uint32_t *)sine_buf, sizeof(sine_buf));或者在MPU配置里把这个缓冲区所在的RAM区域设置成Non-cacheable。我在实际项目里两种方案都用过最简单粗暴的办法是调试初期直接关掉D-Cache等确认通路没问题再开Cache并处理一致性。3.4 一个最小验证程序为了排除各种干扰因素我建议先写一个最小验证程序不要上来就放音乐。目的只有一个让I2S的SD线上持续输出一段正弦波数据。如果这段数据能被逻辑分析仪抓到说明从外设到DMA路径是通的。下面是一段可以放在main.c里直接跑的核心代码我用I2S2举例#include main.h #include math.h /* 缓冲区放到AXI SRAM避开H7的DMA无法访问DTCM的坑 */ __attribute__((section(.sram))) static uint16_t sine_buf[128]; static void Fill_SineTable(uint16_t *buf, uint32_t len) { for (uint32_t i 0; i len; i) { /* 生成0~65535范围、单声道16bit正弦波 */ buf[i] (uint16_t)((sinf(2.0f * 3.1415926f * i / len) 1.0f) * 32767.0f); } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2S2_Init(); Fill_SineTable(sine_buf, 128); /* 先跑一次DMA发送观察SD线有没有数据 */ HAL_I2S_Transmit_DMA(hi2s2, sine_buf, 128); while (1) { /* 用LED翻转作为程序运行指示确认没跑飞 */ HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin); HAL_Delay(100); } }如果这段程序跑起来后逻辑分析仪在SD线上能看到规律的正弦波数据说明DMA和缓冲区都没问题。如果看不到数据先检查sine_buf的实际地址落在哪个区域再检查DMA中断有没有触发。如果只是想验证有声音也可以直接用阻塞方式循环发送while (1) { HAL_I2S_Transmit(hi2s2, sine_buf, 128, 1000); }阻塞方式不依赖DMA能帮你进一步缩小问题范围阻塞方式都能发出声音但DMA方式不行那就是DMA配置或者缓冲区地址的事和外设本身无关。4. 实测排查用示波器和逻辑分析仪找出故障点4.1 第一板斧GPIO方波测试排查之前先做一个最简单的实验验证物理连接和引脚本身是好的。临时把I2S的SCK引脚配置成普通GPIO输出模式然后在代码里翻转它HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); HAL_Delay(1);用示波器或者万用表的频率档量一下排针上有没有方波。如果方波正常说明这颗引脚经过排针、杜邦线一直到测试点都是通的如果方波没有问题出在引脚配置、杜邦线接触或者排针虚焊上。这一步做起来很快但很多人会跳过。我见过太多案例折腾了半天软件最后发现是杜邦线插在排针上没插到底接触不良。所以哪怕你觉得“肯定没问题”也花两分钟测一下。4.2 第二板斧观察BCLK和WS把I2S配置改回来跑上面的最小验证程序。用逻辑分析仪同时抓BCLK和WS两根线。正常情况BCLK上应该看到稳定连续的方波频率在1.5MHz或3MHz附近WS上应该是48kHz左右、占空比接近50%的方波。只要这两根线有波形就说明时钟树配置没有问题I2S外设已经工作在Master模式至少“声音时钟”已经产生了。如果BCLK完全没有波形检查三件事CubeMX里I2S外设是否真的初始化了看i2s.c里有没有执行HAL_I2S_Init()引脚复用是否正确AF值是不是选到了对应的SPI/I2S功能外设时钟有没有使能CubeMX生成代码通常会自动处理但如果你手动改过时钟树就要查。还有一个容易忽略的情况BCLK只在HAL_I2S_Transmit()或者HAL_I2S_Transmit_DMA()被调用之后才会输出。如果只是初始化了外设、没有任何发送动作你是看不到BCLK的。4.3 第三板斧抓SD线数据BCLK和WS都正常继续抓SD线。如果SD线上有数据你应该能看到一串和BCLK对齐的比特流并且数据内容和你填充的正弦波表对应得上。这里有一个需要细心的地方I2S数据是MSB先出的所以用逻辑分析仪按8bit或者16bit分组看的时候要注意字节序。我经常看到有人抓了一堆波形然后说“SD线上没有数据”其实只是把数据解析方式搞错了。如果SD线上确实没有任何翻转可能是DMA没有实际启动。查看HAL_I2S_Transmit_DMA()的返回值应该返回HAL_OK在Debug模式下看hi2s2.State正常是HAL_I2S_STATE_BUSY_TX。如果返回错误多半是外设状态不对或者上次传输还没结束就再次调用。加一个判断if (HAL_I2S_Transmit_DMA(hi2s2, sine_buf, 128) ! HAL_OK) { Error_Handler(); }如果返回HAL_OK但SD线上依然没数据重点检查缓冲区地址。在IDE里添加变量监视看sine_buf的地址值。如果落在0x20000000到0x20010000区间那就是DTCM区域DMA访问不到换成AXI SRAM或者D2域SRAM就好了。4.4 第四板斧确认MCLK与DAC上电时序三根信号线全部正常但DAC还是不输出声音接下来把目光移到DAC模块本身。先用示波器测DAC的供电引脚确保电压稳定。然后用万用表确认模块地和开发板地确实连通。如果你的DAC需要MCLK量一下MCLK引脚有没有12.288MHz左右的时钟。如果CubeMX里没有使能Master Clock Output或者MCLK引脚没有接对DAC内部电路不会启动输出自然无声。还需要关注DAC的上电时序。一些DAC芯片要求在供电稳定之后延迟几十毫秒再送入I2S数据否则芯片可能进入错误状态。如果你用的是带控制寄存器的DAC比如通过I2C配置音量的芯片还要确认寄存器是否初始化成功音量寄存器是否默认是静音状态。这个问题非常阴间我之前调一颗ES8388硬件、I2S全部正常就是不出声最后发现是芯片默认处于静音模式必须写寄存器解除静音。5. 常见问题与排查技巧实录5
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门