DAC輸出)
想給STM32加個音頻播報功能第一反應是用芯片自帶的DAC。結果一查手冊手上這塊F103C8T6只有一個12位DAC兩路輸出還分別占用PA4和PA5如果這兩根引腳已經(jīng)被其他外設占用了就得另想辦法。后來試了用PWM加RC濾波當DAC用效果出乎意料地好語音提示、開機音、簡單音效完全沒問題。這篇就把整個方案的參數(shù)計算、濾波電路設計、CubeMX配置和代碼實現(xiàn)從頭到尾寫清楚給同樣想低成本給STM32加音頻輸出的朋友做個參考。這個方案適合已經(jīng)會配置定時器PWM輸出、想進一步玩音頻的嵌入式初學者也適合做項目時需要“能發(fā)聲”但不想外掛音頻芯片的開發(fā)者。核心思路就三件事把音頻樣本轉成占空比序列、用定時器PWM持續(xù)輸出這個序列、再用RC低通濾波把PWM方波“磨平”成模擬波形。下面按從理論到實操的順序拆開講。1. 整體設計與思路拆解1.1 為什么放著現(xiàn)成DAC不用非要拿PWM湊合STM32F103系列帶DAC的型號其實不多。F103C8T6雖然內(nèi)置DAC但只有一路12位分辨率輸出引腳固定在PA4另一路PA5和比較器共用。更麻煩的是12位DAC的建立時間在幾微秒級別輸出阻抗也比較高驅動后級電路基本都要加運放緩沖。用PWM做DAC的好處在于每個定時器都有至少一個PWM輸出通道引腳可選范圍大占空比本質(zhì)上就是數(shù)字量到模擬電平的映射不需要額外復用外設資源。PWM DAC的原理可以這樣理解一個周期內(nèi)PWM高電平的時間比例叫占空比經(jīng)過RC低通濾波后輸出電壓就是這個占空比對應VCC的平均值。8位分辨率對應256級占空比3.3V供電下理論最小步進約12.9mV。把音頻波形離散成256級電壓再以一定的采樣率連續(xù)刷新占空比RC濾波器輸出的就是還原后的音頻波形。1.2 這個方案能做什么、不能做什么能做的場景語音播報空氣質(zhì)量檢測儀播報數(shù)值、倒車雷達播報距離、設備狀態(tài)語音提示簡單音效按鍵音、報警聲、開機旋律低頻波形發(fā)生器正弦波、三角波、任意自定義波形輸出不太適合的場景高保真音樂播放8位分辨率底噪明顯量化臺階能聽出來44.1kHz立體聲音樂單路PWM都難應付更不要說左右聲道對頻響要求苛刻的測量儀器RC濾波的幅頻特性不夠平坦輸出阻抗又高定位很清楚這是一個“低成本、能出聲、能學原理”的方案。真正追求音質(zhì)直接上I2S加外置DAC芯片比如MAX98357A這類單芯片方案省心得多。1.3 整個系統(tǒng)由哪幾部分構成拆開看一共四個環(huán)節(jié)音頻數(shù)據(jù)準備、DMA搬運、PWM生成、濾波重建。音頻數(shù)據(jù)準備環(huán)節(jié)把WAV文件轉成C語言數(shù)組每個元素對應一個PWM占空比值。DMA搬運環(huán)節(jié)的作用是不讓CPU逐個賦值CCR寄存器而是由定時器觸發(fā)DMA自動把數(shù)組里的值搬到PWM的CCR寄存器。PWM生成環(huán)節(jié)由定時器完成CCR變化占空比就跟著變。濾波重建環(huán)節(jié)在硬件上完成RC低通網(wǎng)絡濾掉PWM載波頻率保留音頻信號。這四個環(huán)節(jié)里決定音質(zhì)好壞的關鍵在參數(shù)設計PWM頻率、采樣率、RC截止頻率三者必須匹配否則會出現(xiàn)載波泄漏、聲音發(fā)悶或者波形失真。下一節(jié)專門講計算。2. PWM DAC系統(tǒng)參數(shù)設計分辨率、采樣率和截止頻率2.1 分辨率由ARR值決定同時綁定了PWM頻率上限定時器輸出PWM時ARR決定計數(shù)周期CCR決定比較值。占空比等于CCR除以ARR加一。ARR越大可表示的占空比級數(shù)越多分辨率越高。但ARR變大意味著計數(shù)周期變長PWM頻率就降下來了。STM32F103定時器最高時鐘是72MHzPWM頻率計算公式為Fpwm 72MHz / ((PSC 1) * (ARR 1))以8位分辨率為例ARR設為255PSC設為0PWM頻率約281.25kHz。這個頻率遠高于音頻帶寬濾波后殘留的載波紋波很小。如果把分辨率提高到12位ARR等于4095同樣的PSC下PWM頻率降到約17.58kHz。這個頻率只比音頻上限高不到三倍一階RC濾波很難濾干凈至少要三級濾波才能勉強壓住載波泄漏。再往上到16位分辨率ARR等于65535PWM頻率只剩約1.1kHz已經(jīng)完全不適合做音頻了。所以這個方案的第一個經(jīng)驗認知8位分辨率是性能和實現(xiàn)難度的最佳平衡點。雖說明面上12位DAC芯片指標更好但在72MHz主頻的F103上12位PWM DAC的實際聽感反而不如8位因為載波噪聲會蓋過分辨率提升帶來的好處。F4系列主頻168MHz以上才適合跑12位PWM DAC。分辨率、PWM頻率和主頻的關系可以看這張表分辨率ARR值F10372MHzPWM頻率適合音頻8位255281.25kHz推薦濾波容易10位102370.3kHz可以濾波壓力稍大12位409517.6kHz不推薦載波難濾除16位655351.1kHz完全不適合2.2 采樣率決定音質(zhì)上限奈奎斯特定理是硬約束音頻數(shù)據(jù)刷新的速率叫采樣率通俗說就是每秒鐘更新多少次占空比。常用語音采樣率是8kHz也就是電話音質(zhì)人聲清晰度夠用但音樂高頻細節(jié)基本丟棄。16kHz采樣率的聲音明亮不少22.05kHz接近FM廣播音質(zhì)。奈奎斯特定理要求采樣率必須大于信號最高頻率的兩倍否則會產(chǎn)生混疊失真。8kHz采樣率下能還原的最高頻率是4kHz所以后級濾波器的截止頻率不宜高于4kHz否則引入的信號高頻噪聲會混進音頻帶內(nèi)。很多人會把PWM頻率和采樣率混為一談。PWM頻率是載波頻率是重復次數(shù)它決定濾波難度采樣率是音頻數(shù)據(jù)刷新的速率它決定音質(zhì)上限。兩者是獨立的參數(shù)實際使用中用兩個定時器分別控制這一點在設計時要想清楚。采樣率的具體實現(xiàn)方式用定時器TIM2產(chǎn)生更新事件觸發(fā)DMA把數(shù)組中的下一個值搬運到PWM定時器的CCR寄存器。所以TIM2的更新頻率就是采樣率。如果TIM2時鐘也是72MHzPSC設0ARR設8999采樣率正好是8kHzFs 72MHz / (9000) 8kHz想在8kHz采樣率下播放更高頻率的音頻信號數(shù)據(jù)本身就不包含高于4kHz的成分所以不用考慮。2.3 RC截止頻率計算先定音頻上限再反推R和C一階RC低通濾波器的截止頻率公式是fc 1 / (2 * π * R * C)截止頻率的含義是信號頻率等于fc時幅度衰減到原來的0.707倍約-3dB頻率繼續(xù)升高每十倍頻程衰減20dB。對PWM載波而言281.25kHz相對4kHz截止頻率有約37dB的衰減相當于幅度壓到原來的1.4%左右。這個殘留紋波對于語音播報已經(jīng)可以接受但如果要更干凈用兩級RC就能在多壓一倍。具體參數(shù)選擇我常用4.7kΩ電阻配10nF電容算下來fc 1 / (2 * 3.14 * 4700 * 10e-9) ≈ 3386Hz這個截止頻率略低于4kHz最大音頻上限會損失一點點極高頻細節(jié)但換來的是載波衰減更狠。如果播放的是16kHz采樣率的音頻截止頻率要放寬到7kHz左右比如2.2kΩ配10nF這時載波衰減就不能指望一級RC完成了需要上兩級甚至三級濾波。具體電路在下一節(jié)展開。有人會問R和C能不能隨便選。不能。R值太大會導致輸出阻抗過高后級負載阻抗一接上來截止頻率就跑偏R值太小又會讓濾波電路消耗過多電流。經(jīng)驗上4.7kΩ到10kΩ比較合適C選10nF到100nF保證截止頻率落在音頻范圍內(nèi)。2.4 三個關鍵參數(shù)的權衡主頻、數(shù)組長度和濾波復雜度設計一個實際項目時PWM頻率、采樣率、濾波電路個數(shù)是互相牽制的。PWM頻率越高濾波越容易但ARR上限被分辨率卡死采樣率越高音質(zhì)越好但DMA搬運頻繁CPU負擔增加音頻數(shù)組占用的Flash也成倍增長濾波級數(shù)越多載波越干凈但電路越復雜而且每一級都會帶來通帶衰減我自己的取舍標準是語音播報場景8kHz采樣率加8位分辨率加一級RC夠用。16kHz采樣率加8位分辨率加兩級RC效果明顯提升。如果是做產(chǎn)品原型需要播放提示音第一種方案成本最低一個電阻一個電容就搞定。實際項目中三級無源RC和兩級RC加上運放緩沖聽感差別很小所以不要盲目堆濾波級數(shù)。3. 濾波電路設計從一階RC到有源低通3.1 一階RC無源低通最簡方案搭起來就能響一階RC濾波只需要一個電阻和一個電容。PWM輸出引腳串聯(lián)電阻電容一端接電阻另一端接地輸出電壓從電容兩端取。這個電路的等效輸出阻抗就等于R值。R取4.7kΩ時后級如果直接接功放輸入端功放輸入阻抗通常有幾十千歐影響不大。但如果后級是低阻負載比如幾十歐的耳機負載輸出阻抗太高截止頻率和幅值都會嚴重漂移聲音會變得很輕很悶。所以一階RC版本建議后面接高輸入阻抗的功放模塊或者先經(jīng)過運放緩沖再輸出。PAM8403這類數(shù)字功放板輸入阻抗在幾十千歐到一百千歐以上直接接沒問題。LM386功放模塊輸入阻抗也有50kΩ左右同樣可以直連。實際測試中一級RC濾波后播放語音能聽到輕微的“沙沙”底噪類似FM廣播的靜噪聲但不影響內(nèi)容聽清楚。面包板搭法很簡單PA6引腳串4.7kΩ電阻電阻后端接10nF電容到地音頻信號從電阻電容的連接點引出接到功放輸入端。10nF電容我是用兩個4.7nF并聯(lián)湊出來的實際用標稱10nF的MLCC電容更省事。3.2 兩級RC級聯(lián)無源濾波的性價比升級一級RC濾波在截止頻率之后按每十倍頻程20dB衰減256kHz的PWM載波相對3.4kHz的截止頻率大約衰減37dB能聽出底噪但不算嚴重。還想更干凈最直接的辦法是再串聯(lián)一級RC。兩級無源RC的衰減速度接近每十倍頻程40dB。但要特別注意兩級RC并不是簡單地把第一級的輸出直接接第二級的輸入。第二級的輸入阻抗會并聯(lián)到第一級的輸出端等效改變了第一級的截止頻率。正確的做法是第一級R1和C1構成第一級低通輸出端接到第二級R2R2后面接C2到地。計算兩級等效截止頻率時不能簡單的把兩個截止頻率相同的RC疊加算成fc不變實際會向低頻偏移。需要在兩級之間加一級電壓跟隨器做隔離第一級RC輸出的信號進入運放同相輸入端運放輸出端直接連到第二級RC。運放輸入阻抗極高基本不吸取前級電流輸出阻抗極低后級RC不會反過來影響前級。這種情況下兩級RC可以獨立計算實際截止頻率更可控。用LM358做電壓跟隨器最便宜NE5532的噪聲指標更好但對這個場景差別不大。兩級RC的參數(shù)用R4.7kΩ、C10nF每級截止頻率都約3.4kHz級聯(lián)后-3dB點大約在2.5kHz左右人聲的齒音和部分高頻細節(jié)被削掉一點換來的是載波完全聽不到。對語音提示類應用音質(zhì)反而更“干凈”。如果想兩級合起來截止頻率仍在3.4kHz可以把每級截止頻率設到5kHz左右比如R3.3kΩ、C10nF這樣合起來-3dB點接近3.5kHz。3.3 有源二階低通Sallen-Key結構參數(shù)一次到位如果覺得兩級無源RC的參數(shù)不好把控可以直接上有源二階低通濾波器。Sallen-Key結構只需要一個運放四個無源器件巴特沃斯響應下Q值取0.707幅頻特性最平坦。給出完整設計方法Sallen-Key低通的截止頻率和Q值公式fc 1 / (2 * π * sqrt(R1 * R2 * C1 * C2))Q sqrt(R1 * R2 * C1 * C2) / (C2 * (R1 R2))真正手算有點繞實際工程中我一般用固定參數(shù)R1等于R2等于10kΩC1等于10nFC2等于4.7nF。代入公式fc 1 / (2 * 3.14 * sqrt(10k * 10k * 10n * 4.7n)) ≈ 4.4kHz這個參數(shù)下Q值接近0.707幅頻特性在通帶內(nèi)比較平坦截止頻率約4.4kHz正好適配8kHz采樣率的音頻。運放供電用3.3V單電源LM358這類軌到軌運放可以單電源工作輸入偏置要加個交叉偏置電阻從正相輸入端虛地。但這里輸入信號本身就是正電壓PWM濾波后的直流分量加交流信號默認在0到3.3V之間不需要額外偏置電路。用Sallen-Key的另一個好處是運放輸出阻抗極低可以直接推動耳機功放或者接長導線不用考慮阻抗匹配問題。實際聽感上有源二階濾波比無源兩級RC更通透一些可能是因為通帶衰減更小。3.4 電源去耦和功放級搭配容易被忽略的一環(huán)PWM DAC輸出的是模擬波形對電源噪聲很敏感。MCU數(shù)字電路高頻開關產(chǎn)生的噪聲會通過電源引腳耦合進PWM輸出導致音頻底噪變大。最簡單的對策是在MCU電源引腳旁邊放100nF陶瓷電容加10μF電解電容的組合。如果MCU和功放共用一個電源功放電源引腳也要單獨加100μF電解電容和100nF陶瓷電容并且功放的地線要單獨走線回到電源地不要在MCU附近共用。功放模塊的選擇LM386適合電池供電的小功率場景PAM8403適合3.7V鋰電池供電的便攜設備兩者輸入阻抗都能直接匹配RC濾波輸出。音量控制用一個10kΩ電位器放在濾波電路和功放輸入之間電位器兩端分別接信號和地中間抽頭接功放輸入。注意電位器會改變后級等效負載但10kΩ相對4.7kΩ濾波電阻已經(jīng)足夠大影響可以接受。如果你在面包板上搭建PCB布局的坑更多。輸入PWM信號線和功放輸出線盡量分開走避免平行。濾波電路的電容要盡可能靠近MCU引腳減小寄生電感。實際上面包板做這個項目完全沒問題只是高頻噪聲會多一點用屏蔽線連接功放輸入能改善一些。4. STM32固件實現(xiàn)CubeMX配置、DMA搬運和播放控制4.1 CubeMX配置兩個定時器各司其職打開STM32CubeMX芯片選STM32F103C8T6我用的HAL庫版本是1.8.0。需要配置的內(nèi)容有Clock Configuration頁面確認SYSCLK是72MHz。這里有個F103特有的坑APB1總線的定時器時鐘不是簡單的PCLK1而是PCLK1的兩倍。如果APB1預分頻設置成2定時器時鐘仍然是72MHz。CubeMX會自動計算但你在手算采樣率的時候要注意別用36MHz去算。TIM3配置為PWM Generation CH1通道1選擇PA6輸出。Parameter Settings頁面Prescaler寫0Counter Period寫255對應ARR等于2558位分辨率Pulse先寫128或占空比50%。在PWM模式下ARR的值決定了PWM頻率PWM頻率等于定時器時鐘除以ARR加一再除以PSC加一這里是72MHz除以256約281.25kHz。TIM2配置為基礎定時器觸發(fā)DMA。Prescaler寫0Counter Period寫8999這樣更新事件頻率是8kHz。關鍵步驟是在TIM2的DMA Settings頁面添加DMA請求選擇TIM2_UPDirection選擇Memory To Peripheral外設地址為TIM3的CCR寄存器地址。需要注意DMA的外設地址不要填錯是htim3.Instance-CCR也就是(htim3.Instance-CCR)。數(shù)據(jù)寬度外設和內(nèi)存都選Half Word即16位因為CCR寄存器是16位音頻數(shù)組也用uint16_t類型存放。還要在TIM3的PWM配置里把CCR預裝載使能打開。這樣DMA寫入CCR的值不會立即生效而是等下一個PWM周期開始時才裝載避免在一個PWM周期中間突然跳變產(chǎn)生毛刺。TIM3自己也開了DMA請求不要開。TIM3的DMA請求只用于更新事件而我們要用的是TIM2的更新事件來觸發(fā)DMA搬運如果兩個都開CCR會被反復寫邏輯就亂了。最后Project Manager里生成代碼Toolchain選擇MDK-ARM。4.2 音頻數(shù)據(jù)準備WAV轉成C數(shù)組的兩種方法音頻源文件先用Audacity處理。打開一個WAV文件后首先Tracks菜單里Stereo Track to Mono轉單聲道Effect菜單里Resample把采樣率降到8000HzEffect里Normalize把音量峰值歸一化到-1dB避免播放時削波。處理完File菜單Export Export as WAVEncoding選Signed 16-bit PCM。拿到16位PCM數(shù)據(jù)后需要轉成8位無符號PWM占空比值。16位PCM的范圍是-32768到327670對應靜音。8位PWM占空比范圍是0到2553.3V供電下128對應1.65V中點是靜音。轉換公式是pwm_value (pcm_sample 8) 128右移8位相當于取高8位再加128是把有符號范圍映射到無符號中點。直接用在線轉換工具也能轉但樣本多的時候還是腳本靠譜。分享一個Python腳本用pydub庫讀取WAV生成C數(shù)組我這里測試環(huán)境是Python 3.10加pydub 0.25.1from pydub import AudioSegment import numpy as np audio AudioSegment.from_wav(input.wav) audio audio.set_channels(1).set_frame_rate(8000).set_sample_width(2) samples np.array(audio.get_array_of_samples()) pwm ((samples 8) 128).astype(np.uint16) print(fconst uint16_t audio_data[{len(pwm)}] {{) for i in range(0, len(pwm), 12): row , .join(str(v) for v in pwm[i:i12]) print(f {row},) print(};)把生成的數(shù)組復制到main.c中注意數(shù)組要加const修飾存放在Flash而不是RAMF103C8T6的RAM只有20KB一段10秒8kHz采樣率的音頻數(shù)據(jù)就有8萬個樣本每個樣本占2字節(jié)16萬字節(jié)必須放在Flash里。Flash容量64KB也就是最多可以放32秒8kHz的語音音頻夠用了。如果音頻太長放不下只能外掛Flash或者用壓縮格式。另外要做淡入淡出處理。直接播放時數(shù)組的第一個值如果是128附近而停止時的CCR可能是0從一個電平跳到另一個電平就會產(chǎn)生爆音。處理方法是音頻數(shù)據(jù)開頭加16個采樣從128漸變到第一個有效值結尾也加16個采樣漸變回128爆音基本消除。這個處理在Python腳本里做fade_in np.linspace(128, pwm[0], 16, endpointFalse).astype(np.uint16) fade_out np.linspace(pwm[-1], 128, 16, endpointFalse).astype(np.uint16) pwm np.concatenate([fade_in, pwm, fade_out])4.3 主程序代碼啟動DMA、播放控制和停止邏輯CubeMX生成的代碼基礎上主循環(huán)里只需要做三件事啟動DMA播放、檢測停止按鍵、停止播放。啟動播放的核心函數(shù)HAL_TIM_Base_Start(htim2); // 啟動采樣率定時器使能更新中斷 HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); // 啟動PWM輸出 HAL_TIM_Base_Start_DMA(htim2, (uint32_t *)htim3.Instance-CCR, (uint32_t *)audio_data, AUDIO_LEN);注意HAL_TIM_Base_Start_DMA是把DMA綁定到htim2上第三個參數(shù)是外設地址第四個是內(nèi)存地址第五個是傳輸長度。這里內(nèi)存地址傳的是audio_data數(shù)組名外設地址傳的是TIM3的CCR寄存器地址代表每次TIM2更新事件觸發(fā)后DMA從audio_data取一個值寫到CCR。停止播放的處理要小心。直接調(diào)用HAL_TIM_Base_Stop_DMA會立刻停掉DMA但CCR可能停在任意值導致輸出電平突變產(chǎn)生爆音。我處理的辦法是停止前先把CCR設置到128接近中點等待幾個毫秒再停掉DMA和PWMvoid audio_stop(void) { __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 128); HAL_Delay(10); HAL_TIM_Base_Stop_DMA(htim2); HAL_TIM_PWM_Stop(htim3, TIM_CHANNEL_1); HAL_TIM_Base_Stop(htim2); }DMA設置為循環(huán)模式后播完一遍會自動從頭開始。如果想播完一遍自動停止可以關掉循環(huán)模式然后在DMA的傳輸完成中斷回調(diào)里調(diào)用停止函數(shù)void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { // 在這里可以做播放狀態(tài)標記或者音頻數(shù)據(jù)循環(huán)切換 } }如果只有一個音頻段循環(huán)播放DMA循環(huán)模式就夠了。如果要做“按一下播一段”的效果建議在DMA傳輸完成中斷里停掉DMA代碼如下void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)注意不同HAL庫版本回調(diào)函數(shù)名可能有差異老版本用HAL_TIM_PeriodElapsedCallback新版本可能更細化。我這里用的庫版本里TIM2基本定時器更新事件走的是HAL_TIM_PeriodElapsedCallback。4.4 實際運行效果從示波器到人耳搭好硬件、燒錄固件后先播放一個1kHz正弦波測試信號驗證系統(tǒng)基礎功能。示波器探頭接在RC濾波輸出端正常應該看到一個連續(xù)正弦波頻率1kHz幅度約幾百毫伏到1V之間。如果看到的是方波或者階梯波說明PWM頻率太低或者RC濾波失效。如果波形上有高頻率的毛刺說明載波沒有濾干凈需要調(diào)整截止頻率。沒有示波器的話直接接功放模塊聽音也行。播放語音時正常能聽清楚內(nèi)容背景有輕微沙沙聲。如果播放音樂能明顯聽出底噪和采樣率不夠導致的“數(shù)碼味”。我實測過播放一段8kHz采樣率的普通話語音一級RC濾波下聽感清晰底噪輕微兩級RC濾波后底噪基本消失但高頻齒音變?nèi)?。整體效果相當于老式電話的聽感對設備語音提示來說完全夠用。5. 常見問題與排查技巧實錄5.1 聲音發(fā)悶像嘴被捂住先說結論RC截止頻率設太低了。8kHz采樣率的音頻最高頻率成分就是4kHz但語音里大多數(shù)能量集中在1kHz到3kHz如果截止頻率設在2kHz以下齒音、輔音部分會被削掉聽感發(fā)悶。解決方法把RC截止頻率往上調(diào)或者檢查采樣率是不是真的8kHz。如果WAV文件本身是44.1kHz采樣率你直接重采樣到8kHz高頻信息本來就會丟失聽起來悶是正常的可以改用16kHz采樣率配合更高截止頻率。還有一種常見情況是兩級RC級聯(lián)后截止頻率不是3.4kHz而是更低。兩個截止頻率3.4kHz的RC級聯(lián)-3dB點實際約2.2kHz。解決方案是用運放隔離讓兩級RC互不影響或者把每級RC截止頻率提高到5kHz以上再級聯(lián)。5.2 播放時持續(xù)有“嘶嘶”聲或者高頻嘯叫嘶嘶聲是PWM載波泄漏。一級RC濾波對281.25kHz載波的衰減約-37dB有些人對這個量級敏感有些人不敏感。先看PWM頻率配置是否正確如果ARR設成5119位而PSC沒改PWM頻率掉到140kHz同樣RC參數(shù)下載波衰減少了6dB嘶嘶聲更明顯。兩種解法提高PWM頻率或加濾波級數(shù)。高頻嘯叫更可能是電源問題。筆記本USB口供電時開關電源紋波會直接耦合到模擬輸出。試試用電池供電或者給MCU電源和功放電源之間加LC濾波嘯叫基本消失。我遇到過的情況是把功放和MCU共用一個5V電源負載變化時功放反饋到電源的噪聲進入MCUPWM輸出被調(diào)制聽起來就是刺耳的蜂鳴。分開供電后問題徹底消失。5.3 播放開始和結束時“啪”的爆音爆音的原理是CCR寄存器從非靜音值突變到靜音值或者反向突變輸出電平瞬間跳變。首次播放時如果數(shù)組第一個值不是128附近CCR從默認0跳到第一個采樣值振鈴就會產(chǎn)生爆音。結尾同理。解決辦法有兩個。第一個是軟件層面的淡入淡出前面Python腳本里已經(jīng)寫了音頻數(shù)據(jù)兩頭各加16個漸變樣本實測能消除大部分爆音。第二個是播放啟動時序上先啟動PWM輸出再啟動DMA保證PWM已經(jīng)開始輸出穩(wěn)定電平了DMA才改變占空比。停止時把CCR先設到128延時幾十毫秒再停PWM也可以避免爆音。實際調(diào)試時還要注意DMA循環(huán)模式下播放到最后會跳回開頭如果數(shù)據(jù)首尾電平差很大每循環(huán)一次就爆一次。這就是為什么淡入淡出處理要加在數(shù)據(jù)開頭和結尾讓首尾都靠近128中點。5.4 播放速度不對聲音變快或變慢音調(diào)變高、播放變快通常是采樣率定時器配置偏高。TIM2的更新頻率就是采樣率如果預分頻算錯實際采樣率不是8kHz而是10kHz同一段音頻會在更短時間里播完音調(diào)也跟著升高。常見原因有三個第一個是時鐘樹配置不對。F103的APB1定時器時鐘在APB1預分頻不為1時自動乘2如果CubeMX里APB1設成36MHz你手算時用36MHz計算PSC和ARR實際定時器時鐘是72MHz采樣率就翻倍了。第二個是CubeMX生成的代碼里修改了PSC或ARR之后沒有重新計算。第三個是DMA搬運的數(shù)據(jù)寬度不對設置成Byte后每個樣本只搬運低8位數(shù)據(jù)量減半播放時間也跟著減半。排查方法很簡單播放一段已知時長的語音用秒表計時。假設音頻是15秒如果實際播了7.5秒說明采樣率翻倍了?;氐紺ubeMX檢查APB1定時器時鐘和TIM2參數(shù)就行。5.5 DMA只搬運一次或者完全不搬運癥狀是只聽到一個單調(diào)的PWM音調(diào)或完全沒有聲音。優(yōu)先檢查DMA配置。第一DMA請求源要選TIM2_UP這一步經(jīng)常有人選成TIM3_UP。如果選成TIM3_UPTIM2更新事件不會觸發(fā)DMACCR永遠是初值。第二DMA外設地址要寫成htim3.Instance-CCR而不是直接寫0x40000414這種硬編碼地址。有些教程給的是寄存器地址但HAL庫配置時用的外設地址是基地址加偏移直接填寄存器地址反而對不上。第三確認DMA模式選了Circular循環(huán)模式。如果選Normal普通模式DMA搬運完一遍數(shù)組就停了表現(xiàn)就是播放一次后沒聲音。如果你確實想單次播放Normal模式配合傳輸完成中斷是合理的但很多人是配置成Normal卻期望循環(huán)播放。第四確認HAL_TIM_Base_Start_DMA的第三個參數(shù)傳的是外設地址寄存器指針第四個參數(shù)是內(nèi)存地址。傳反了會導致DMA把CCR的值搬到內(nèi)存輕則數(shù)據(jù)錯誤重則觸發(fā)總線錯誤死機。5.6 最后再提一個電源問題底噪和地環(huán)路這是最容易忽略的一環(huán)。MCU開發(fā)板、功放模塊、USB供電三者之間形成的地環(huán)路會引入50Hz交流聲和開關電源噪聲。我排查了很長時間才意識到USB供電的GND和功放模塊的GND之間有幾毫伏的電位差這個差值直接疊加到音頻信號上。解決的思路是單點接地MCU的地和功放模塊的地用一根粗導線連到電源地不要在多個位置反復接地。如果項目要求嚴格模擬地濾波電容地、功放地和數(shù)字地MCU地用0Ω電阻單點連接。這個技巧在傳感器采集電路里常見音頻輸出同樣適用。寫在最后這個PWM DAC方案我從面包板搭到PCB前前后后調(diào)了一周最大的體會是不要一上來就追求高分辨率高采樣率先讓最簡配置跑通再用示波器觀察各級波形逐步優(yōu)化濾波參數(shù)。8位PWM加一級RC聽起來條件簡陋實際上播報語音的效果遠超預期底噪輕微人聲清晰已經(jīng)達到了不少商用產(chǎn)品的語音提示質(zhì)量。當然它也有明顯短板播放音樂時底噪和量化失真能清楚感知聲音“脆硬”不像真正DAC那樣圓潤。如果項目預算允許直接上I2S加DAC芯片才是音樂播放的正道。但作為理解PWM原理、DAC原理和濾波器設計的入門項目這套方案性價比極高調(diào)通了它你對定時器、DMA、RC濾波的理解會上升一個臺階。