之前參加了這個活動,眼瞅著馬上要截止了,趕緊急頭白臉的調(diào)兩天終于交了:

一開始想整個外接屏幕,后來硬件沒調(diào)通:

軟件 SPI 驅(qū)動,罕見的見到了 9bit 的設計,可以看到原理圖里面是少了 DC 這個引腳的:

高級捏,不過自帶的 IIC 有大問題,群里面的老哥們都遇到了,emmmm 我也沒有特別深入的調(diào),這里就寫幾個解決辦法。




居然是 SDK 的問題,Maxim 的 MCU 真是一大坨

我只能說,硬件強大固然重要,那軟件面對廣大用戶真是要好好測測了,平時我調(diào)個小外設就一會兒,這東西能搞搞兩天:


這個芯片極客使用還行,但是大規(guī)模使用還是要大大的問號的,基礎外設都不通,我都不敢想更加復雜的東西:

雖然坑多,但是一些設計真是亮瞎我的狗眼 :




值得學習的有,每個軌上面都有跳線帽:

總之板子配置是很豪華的。
這次demo做了一個分析儀,項目比較考驗數(shù)據(jù)結構的處理:
J5 Line-In → MAX9867 ADC → I2S RX DMA → I2S TX DMA → MAX9867 DAC → J6 耳機


系統(tǒng)有兩條鏈。
第一條是絕對實時的音頻鏈:J5 Line-In→MAX9867 ADC→I2S RX→DMA→I2S TX→MAX9867 DAC→J6耳機
第二條是旁路分析鏈:DMA已經(jīng)接收完成的數(shù)據(jù)→復制→波形+FFT→TFT
因為我發(fā)現(xiàn)實時沒有必要,以及顯示和音質(zhì)都有問題,所以:
┌→ FFT → TFT
ADC → DMA → 播放 ── ┤
└→ 旁路分析
因此 FFT 算慢一次、TFT少刷一幀都無所謂,但是音頻DMA絕對不能停。

MAX32690的I2S配置為:
req.wordSize = MXC_I2S_WSIZE_WORD;
req.sampleSize = MXC_I2S_SAMPLESIZE_TWENTYFOUR;
req.bitsWord = 24;
MAX9867數(shù)據(jù)手冊明確寫的是:Two 16-bit ADCs,也就是左右聲道ADC實際都是16位;數(shù)字音頻時序圖輸出的數(shù)據(jù)也是:D15,D14,…,D1,D0即16個有效數(shù)據(jù)位。
那么為什么程序可以配置成24 bit?
因為這里的24 bit更準確地說是:I2S Slot Width / Frame Format如每個聲道可以占24個BCLK周期,但其中只有16位是真正的ADC有效數(shù)據(jù),其余位用于槽位填充。
嚴謹?shù)膩碚f應該是48 kHz,16-bit ADC,有效音頻數(shù)據(jù)通過24-bit I2S slot傳輸;這個區(qū)別以后做ENOB、SNR、FFT噪聲底分析時非常重要。
(int32_t)(word << 8) >> 8
先把24位數(shù)據(jù)進行符號擴展,然后:
>> 8
取成16位數(shù)據(jù)。
如果I2S槽是:
| D15 D14 ... D1 D0 | 0 0 0 0 0 0 0 0 |
16位有效數(shù)據(jù) 填充
相當于從24-bit數(shù)字音頻容器中抽取真正的16-bit Codec數(shù)據(jù)。
MAX9867=I2S MasterMAX32690=I2S Target,MAX32690接受外部:BCLK/SCK,WS/LRCLK
MAX9867負責產(chǎn)生它們,這樣可以避免MCU作為主機時的WS相位錯位問題。

12.288MHz音頻基準給MAX9867,生成LRCLK + BCLK,最后MAX32690跟著Codec采,于是整個采樣時序由Codec掌握,MCU只是同步接收。
MAX9867數(shù)據(jù)手冊說明,Master模式下BCLK由 BSEL決定:
定義:
#define I2S_DMA_BUFFER_SIZE 64
uint32_t i2s_rx_buffer[128];
HALF0 = word 0...63
HALF1 = word 64...127
核心思想是讓 DMA 和 CPU 分別訪問不同的半緩沖區(qū)。
DMA 寫入 HALF0,CPU 處理 HALF1。,DMA 切換到 HALF1,CPU 處理 HALF0;然后DMA不斷:
寫 HALF0
↓
切到 HALF1
寫 HALF1
↓
切到 HALF0
真正關鍵的是:CPU絕對不能讀取DMA正在寫的區(qū)域。
否則會出現(xiàn)DMA正在改一個word,但是CPU同時讀取就會產(chǎn)生 torn/inconsistent frame,表現(xiàn)出來就是:數(shù)據(jù)錯位;左右聲道錯位;突然跳變;沙沙聲;FFT異常尖峰。
rxBufPtr指針這個指針不是表示“剛剛寫完哪里”,只是說明“DMA下一次要寫哪里”,如:
rxBufPtr = HALF0
代表:
HALF0 = DMA下一塊將覆蓋
HALF1 = 當前安全
因此:
safe_idx =(rx_now == i2s_rx_buffer) ? 1 : 0;
實際上就是:safe half=opposite of DMA target half,總之這是一個比較好的并發(fā)設計。
CPU先快速:
memcpy(snap_temp, safe, 256);
然后立即離開DMA共享內(nèi)存。
Buffer:
+------------+------------+
| HALF 0 | HALF 1 |
+------------+------------+
DMA寫0
CPU讀1
DMA寫1
CPU讀0
之后FFT、解包再慢慢處理:
DMA-owned SRAM
↓
快速 memcpy
↓
CPU-owned snap_temp
↓
隨便處理
這相當于建立了一個所有權邊界:DMA ownership→CPU ownership
我的代碼是:
uint32_t *rxBufPtr = i2s_rx_buffer;
但這個變量實際上同時被:DMA回調(diào)/ISR修改;主循環(huán)讀取;因此從C語言編譯器層面,最好不是普通指針,而是把指針變量本身聲明為 volatile,如:
uint32_t * volatile rxBufPtr;
兩者含義不同:
uint32_t * volatile p
↑
指針本身會異步變化
在Cortex-M4上,對齊的32位指針讀寫本身是原子的,但 volatile 主要解決編譯器可能緩存這個共享變量的問題;至于為什么沒改,是因為就是個演示 demo,匆忙交東西,沒辦法細研究。
arm_rfft_fast_f32計算最終選擇:
arm_rfft_fast_f32()
而不是Q15/Q31。
MAX32690是Cortex-M4F,自帶單精度FPU,所以對于128點FFT,用float實際上非常舒服;128點FFT對100 MHz M4F來說只是很小的計算量。
設計上的重點是FFT執(zhí)行的時候會不會阻塞實時音頻。
對于 點 DFT:
CMSIS-DSP 實現(xiàn):
arm_rfft_fast_f32(&fft_inst, fft_in, fft_out, 0);
FFT太慢;FPU影響DMA;DMA優(yōu)先級不夠;memcpy搶總線;SRAM沖突,但最終發(fā)現(xiàn)真正的問題是:
__disable_irq();
bit-bang TFT...
__enable_irq();
這是非常典型的實時系統(tǒng)問題,注意嚴格來說:
__disable_irq()不一定立即停止DMA硬件搬運。
DMA控制器仍然可以繼續(xù)工作,看下面的流程圖:
DMA完成
↓
IRQ來了
↓
但是PRIMASK=1
↓
ISR不能執(zhí)行
↓
下一塊DMA不能及時重新配置
↓
I2S FIFO eventually overrun/underrun
↓
丟樣本
↓
沙沙/爆音
修復后的邏輯變成:
TFT bit-bang進行中
↓
DMA IRQ到來
↓
CPU暫停TFT幾微秒
↓
處理DMA
↓
返回繼續(xù)bit-bang
這個操作最壞結果只是LCD某個時鐘周期被拉長了一點,但是對于SPI類接口來說,時鐘一般:有最大頻率要求;通常沒有嚴格最小頻率要求。
所以:
SCK
____| ̄|_| ̄|________| ̄|_
↑
IRQ插進來
通常只是這個bit變得更慢,并不會改變采樣順序,寧可讓屏幕慢一點,也絕對不要延誤音頻DMA
softfp配置為:
MFLOAT_ABI = softfp
LIB_CMSIS_DSP = 1
softfp 不等于“不使用FPU”,表示:浮點運算仍然可以使用M4F硬件FPU,但函數(shù)調(diào)用ABI使用兼容的軟浮點參數(shù)傳遞規(guī)則。(可以硬件算float,但ABI兼容soft)
因為SDK預編譯庫是softfp,項目也使用softfp可以避免:ABI不匹配;鏈接錯誤;參數(shù)傳遞不一致。
實時性系統(tǒng)設計要求就是數(shù)據(jù)流的處理問題,但是最大的問題是底層 API 不好用,告辭。