大概6-7年前,在網上看到過一篇用STM32F1的DMA控制GPIO輸出高速數字波形的帖子。覺得很有意思,就自己試了試:控制GPIO輸出波形翻轉的速度最高只能達到3-4MHz,且容易受到STM32F1的APB2匯流排其他裝置讀寫的影響,輸出的方波不穩定。由於問題較多,對高速實時性提升不大,感覺基本不實用,就沒有再進一步研究。
前幾天在研究STM32F4和STM32F1的區別時,發現STM32F4進行了兩項升級:1、把GPIO連線從APB2匯流排改到AHB匯流排上,極大的改善了GPIO輸入、輸出的實時性和時序控制能力。2、增加DMA stream的概念(參見拙作https://www.cnblogs.com/helesheng/p/18167026),使得DMA傳輸請求的來源更明確。
DMA控制器的上述升級,使得STM32F1上比較“雞肋”的DMA高速並行同步傳輸能力得到了較大提升,具備了一定的實用性。我嘗試用STM32F407VE的DMA2在TIM1的觸發配合下,實現了對並行介面的流水線型ADC的控制和讀取。理論讀取速度可達40MSPS以上,實測讀取速度可超過AD9200E的理論上限——20MSPS。設計思路、程式和電路如下,供大家參考和指正。
以下原創內容歡迎網友轉載,但請註明出處:https://www.cnblogs.com/helesheng
一、DMA控制高速同步並行資料傳輸的原理分析
本文基於DMA的GPIO高速並行讀寫程式,主要利用了STM32F4系列DMA的以下關鍵特性:
1)STM32的DMA傳輸可分為外設和記憶體之間,以及記憶體之間兩種傳輸模式。這兩種模式的最重要區別是:外設和記憶體之前的DMA傳輸必須由其他外部請求訊號觸發;而記憶體和記憶體之間的DMA傳輸則不會等待其他觸發,它會在上一次傳輸進行完之後自動進行下一次傳輸。 在STM32的嵌入式系統中,除去少數只追求傳輸頻寬的純資料傳輸任務以外,大部分與硬體相關的DMA傳輸,還要要求在恰當的時刻完成傳輸。以本文要完成的高速A/D資料讀取為例,讀寫資料的時刻必須收到取樣間隔的嚴格控制。也就要求由定時器TIM來實現取樣間隔的定時,即由TIM產生DMA傳輸的請求訊號,而資料則透過DMA在GPIO資料暫存器和記憶體之間進行傳輸。
2)外設和記憶體之間的DMA傳輸的請求訊號,可以是本次DMA傳輸的源或目的的外設,也可以是其他外設。STM32F4的DMA傳輸請求訊號由DMA的流(stream)和請求訊號(或稱通道channel)共同決定;但傳輸源和目的外設種類卻由源和目的地址決定。二者不能混為一談。
3)STM32F4的兩個DMA控制器的兩個埠被指定為特定的資料來源。具體連線關係如下圖所示。以GPIO為例,STM32F4把它連線到AHB1匯流排上,由下圖可知連線到AHB1外設的只有藍色和黑色兩種顏色的線,也就意味著,左側的兩個DMA控制器中只有連線了藍色和黑色的DMA2可以實現與GPIO之間的DMA傳輸。
圖1 STM32F4系列兩個DMA控制器埠(源和目標)連線的外設/儲存器種類
透過查詢STM32F4的資料手冊中的DMA請求訊號表可知,可用於請求DMA2的定時器只有TIM1和TIM8兩個高階定時器。我選擇了TIM1的更新事件TIM1_UP來請求DMA傳輸。
圖2 STMF4系列DMA2控制器的外設傳輸請求表
另外,控制高速ADC還要求單次DMA傳輸耗時要小於取樣間隔,而STM32F4把GPIO連線到AHB1匯流排的意義也就在於此——相比之前將GPIO連線到APB2匯流排的STM32F1系列,STM32F4將能夠更快速的對GPIO進行讀寫,從而提高與所控制ADC的資料讀取速率。
最後,流水線型ADC還需要一個取樣同步時鐘;由於資料讀取也是在該時鐘的同步下進行,自然只能由TIM1時基部分同時產生該時鐘。一種合理的解決辦法是用TIM1的輸出比較(OC)功能電路來產生。這也意味著該時鐘只能由TIM1的某個通道(CHx)產生,從而只能在某些管腳上輸出,這一點必須在硬體設計時加以注意。
二、高速並行介面ADC讀取的程式和電路設計
1、硬體設計
下圖是我採用的ADI公司的標稱轉換速率為20MSPS的流水線型A/D轉換器——AD9200E(10bits解析度)的工作時序圖。
圖3 AD9200的讀取時序
可以發現,資料的更新發生在上升沿後25ns左右。如果輸出比較OC電路採用時基計數的前段輸出高電平,比較翻轉的後輸出低電平的模式,就會使得輸出PWM訊號的上升沿發生在TIM1更新時。而如前所述STM32F4的DMA2資料傳輸則發生在TIM1更新事件後,這就有由於高速數位電路的競爭與冒險造成讀取時序不收斂。
我曾在某論壇看到過有人對AD9226做類似嘗試,僅在16MSPS以上時就出現取樣點讀取錯誤的問題(https://blog.csdn.net/cusichidouren/article/details/126002742),我猜測就是由於這個原因。合理的解決方案其實也不復雜:對OC電路輸出的PWM訊號反相,使其在下降沿時觸發DMA2傳輸請求。幸運的是STM32的OC輸出支援負邏輯的PWM輸出,不需要附加進一步的閘電路。具體配置程式碼請參見軟體設計部分。
具體GPIO選擇方面,我用了PE0~16號埠來實現對AD9200的控制和讀取。其中,PE口中PE11管腳可以配置為TIM1的通道2(CH2),可以作為AD9200的轉換時鐘(INPUT CLOCK)。而AD9200的10根資料線則用PE0~9負責讀取,低位對齊的做法也有利於後續的資料讀取和整理。AD9200的鉗位控制(CLAMP)、溢位指示(OTR)、低功耗待機(STBY)等管腳則連線到PE口的其他管腳。原理圖太簡單,這裡就不貼出來了,放一張實物圖。注意:AD9200的模擬驅動應使用一個高壓擺率的寬頻運放,我使用了低成本的AD8052。
圖4 實驗系統實物圖
2、軟體設計
正如本文前面“原理分析”介紹的,DMA傳輸的通道、流、資料來源/目標、傳輸請求訊號如下圖所示。
圖5 DMA工作原理示意圖
DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2時鐘使能 DMA_DeInit(DMA2_Stream5); //如果用TIM1更新觸發,則使用DMA2_S5_CH6 while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE){}//等待DMA可配置 /* 配置 DMA Stream */ DMA_InitStructure.DMA_Channel = DMA_Channel_6; //通道選擇為6通道 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(GPIOE->IDR)); DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)DST_DATA; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//DMA_DIR_MemoryToMemory;//儲存器到外設模式 DMA_InitStructure.DMA_BufferSize = SAMPLE_LEN;//資料傳輸量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外設資料長度:16位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//儲存器資料長度:16位 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用迴圈模式 DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;//高優先順序 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//儲存器突發單次傳輸 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外設突發單次傳輸 DMA_Init(DMA2_Stream5, &DMA_InitStructure);////如果用TIM1更新觸發,則使用DMA2_S5_CH6 //開啟DMA中斷 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel=DMA2_Stream5_IRQn; // NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x00; //搶佔優先順序1 NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子優先順序3 NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure); DMA_ITConfig(DMA2_Stream5,DMA_IT_TC,ENABLE); //
其中DMA2_Stream5的Channel_6是TIM1更新事件可以請求的DMA流,DST_DATA則是指向DMA快取轉換結果資料的片上SRAM區域的指標。DMA中斷則用於在中斷服務程式中讀取轉換結果(當然本程式對讀取效率沒有要求,因此也沒有使用該中斷服務程式功能)。
TIM1則需要一方面向外產生AD9200的轉換工作時鐘,另一方面用自動重灌的更新訊號觸發DMA2的資料傳輸(如圖5所示),因此TIM1的配置程式碼如下。
1 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; 2 TIM_OCInitTypeDef TIM_OCInitStructure; 3 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE); ///使能TIM1時鐘 4 TIM_DeInit(TIM1); 5 TIM_TimeBaseInitStructure.TIM_Period = 8-1; //自動重灌載值 6 TIM_TimeBaseInitStructure.TIM_Prescaler= 1-1; //定時器分頻 7 TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上計數模式 8 TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 9 TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStructure);//初始化TIM1時基 10 ////初始化TMR1的PWM輸出,作為同步時鐘///// 11 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; 12 TIM_OCInitStructure.TIM_OutputState =TIM_OutputState_Enable;// TIM_OutputState_Enable;//正向輸出使能 13 TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;//反向輸出禁止 14 //輸出極性:TIM輸出比較極性低,保證在下降沿之後,讀取或寫出資料 15 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; 16 TIM_OCInitStructure.TIM_Pulse = 0; 17 TIM_OC2Init(TIM1, &TIM_OCInitStructure); //根據T指定的引數初始化外設TIM9 OC2 18 TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); //使能TIM1在CCR2上的預裝載暫存器 19 TIM_CCxCmd(TIM1, TIM_Channel_2, TIM_CCx_Enable);//禁止TIM1-CH2 輸出 需要單獨使用 TIM1-CH2 時 此處因設定為 禁止TIM1-CH2N 輸出 20 TIM_ARRPreloadConfig(TIM1,ENABLE);//ARPE使能 21 TIM_Cmd(TIM1, ENABLE); //使能TIM1 22 TIM_CtrlPWMOutputs(TIM1, ENABLE); 23 TIM_SetCompare2(TIM1, 4); 24 //選擇DMA觸發訊號 25 TIM_DMACmd(TIM1, TIM_DMA_Update, ENABLE);//如果用TIM1更新觸發,則使用DMA2_S5_CH6
其中,自動重灌值8-1決定了AD9200的轉換時鐘為168/8=21MHz,這略微超過了AD9200手冊規定的上線20MHz。主要原因是高速ADC比較貴,我手頭有的只有AD9200,希望測試到它的工作極限。實測效果顯示AD9200在21MSPS轉換速度下還是能穩定工作點,如果用本方案做實際專案,還是要注意這個問題。
輸出比較OC暫存器的值被設定為4,以保證輸出的AD9200轉換時鐘佔空比為1:1。
相關管腳的配置程式碼如下:
1 GPIO_InitTypeDef GPIO_InitStructure; 2 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB時鐘 3 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA時鐘 4 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOE時鐘 5 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);//使能GPIOC時鐘 6 //PWM管腳輸出ADC工作時鐘 7 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //GPIOE11 8 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//複用功能 9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度100MHz 10 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推輓複用輸出 11 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//上拉 12 GPIO_Init(GPIOE,&GPIO_InitStructure); 13 GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_TIM1); //GPIOE11複用位定時器1 14 //AD9200的數字輸入,透過GPIO來讀取 15 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9; 16 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通輸入模式 17 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz 18 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//浮空輸入 19 GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIO 20 ///PE15作為AD9200待機模式控制端,高電平進入待機模式 21 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; 22 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通輸出模式 23 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推輓輸出 24 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz 25 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉 26 GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIO 27 GPIO_ResetBits(GPIOE,GPIO_Pin_15);//低電平輸出ad9200進入正常工作模式 28 //led管腳輸出 29 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;//LED0和LED1對應IO口 30 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通輸出模式 31 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推輓輸出 32 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz 33 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉 34 GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO 35 GPIO_ResetBits(GPIOB,GPIO_Pin_8 | GPIO_Pin_9);// 36 //按鍵輸入管腳 37 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5; 38 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通輸入模式 39 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz 40 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉 41 GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIO
其中最關鍵的是用於讀取AD9200轉換結果的PE0-9,共10個管腳,它們被配置為普通浮空輸入模式,內部的GPIO資料暫存器IDR根據外部輸入更新的速率就是AHB1匯流排的工作時鐘84MHz。這也限制了使用這種基於DMA和GPIO讀取並行資料的方法的頻寬上限。
PE11複用功能中有作為TIM1CH2的功能,將其複用給TIM1:GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_TIM1);
主程式的功能時等待按鍵,並在按鍵後將DMA的目標SRAM中的內容傳送給PC顯示,其程式碼如下所示:
1 extern unsigned short DST_DATA[SAMPLE_LEN]; 2 int main(void) 3 { 4 unsigned short i; 5 delay_init(168); //初始化延時函式 6 Pin_Init(); //初始化LED埠 7 uart_init(115200);//串列埠初始化配置 8 DMA_Config();//初始化DMA控制器 9 USART_GetFlagStatus(USART1, USART_FLAG_TC); //這句可以防止第一個位元組丟失 10 while(1) 11 { 12 if(KEYD == 0) 13 { 14 delay_ms(20); 15 if(KEYD == 0) 16 { 17 DMA_Cmd(DMA2_Stream5, ENABLE); //啟動DMA傳輸 18 delay_ms(5); 19 for(i = 0 ; i < SAMPLE_LEN ; i++) 20 { 21 f2c.float_data = (DST_DATA[i] & 0x03ff) * 2.00 / 1024 ; 22 USART1_Putc(f2c.char_data[0]); 23 USART1_Putc(f2c.char_data[1]); 24 USART1_Putc(f2c.char_data[2]); 25 USART1_Putc(f2c.char_data[3]); 26 //固定內容的幀尾輸出 27 USART1_Putc(0x00); 28 USART1_Putc(0x00); 29 USART1_Putc(0x80); 30 USART1_Putc(0x7F); 31 } 32 LED0 = 0;//滅燈表示資料傳輸完成 33 } 34 while(KEYD == 0);//等待到按鍵釋放 35 delay_ms(10); 36 } 37 delay_ms(1); 38 } 39 }
為了方便的檢視DMA讀取的資料是否正確,我將資料用UART口傳送到PC端開源的VOFA+軟體上,通行的VOFA+資料格式為JustFloat。因此首先把資料從10位的二進位制數轉換為浮點數後,將IEEE754格式的單精度浮點數在分解為4個位元組後由函式USART1_Putc();發出。浮點數分解為四個位元組的方式是採用聯合體(union)f2c來實現。f2c的定義如下:
1 union float2char//浮點數向可傳送的位元組轉換的結構體 2 { 3 unsigned char char_data[4]; 4 float float_data; 5 }f2c;
3、遇到的幾個“坑”
儘管STM32F4是非常成熟的MCU產品線,但本文所述的“基於DMA的高速並行GPIO讀寫”並不是常見的功能,因此在除錯過程中我還是遇到並克服了一些問題。個人覺得是晶片本身以及標準外設庫的一些小問題造成的,但也可能是由於我才疏學淺、考慮不周的原因。羅列與此,供大家參考和指正。
1)官方提供的標準外設庫高速外部晶振頻率不匹配問題
我使用了ST官方提供的標準外設庫作為開發平臺,其中配置的高速外部晶振HSE的頻率(HSE_VALUE)為25MHz。而我實際使用的晶振為8MHz,這除了導致UART通訊的波特率不準之外,更重要的是還會導致TIM1輸出比較OC電路輸出的時脈頻率不對。在韌體庫stm32f4xx.h中搜尋宏“HSE_VALUE”,將其改為8000000即可解決問題。
2)PA8管腳複用為TIM1_CH1輸出比較功能時無輸出的問題
我最初進行硬體設計時,曾想用管腳PA8複用的TIM1_CH1功能輸出AD9200所需的轉換時鐘。但折騰了很長時間都無法讓PA8管腳輸出所需時鐘(PWM)訊號,在網上搜尋後發現問題是STM32晶片的一個“頑疾”(www.openedv.com/posts/list/49738.htm)——只要使能複用在PA8、PA8、PA10等管腳上的USART1功能,就會導致PA8上的TIM1_CH1無法輸出PWM訊號。修補這個BUG也不困難:我改成使用TIM1_CH2(在PE11管腳上)輸出PWM波,就很好的解決了這個問題。
三、測試結果
下圖所示的是VOFA+軟體顯示的採集訊號
圖6 AD9200採集的訊號波形(取樣率為21MS,輸入正弦訊號為1MHz,取樣長度為512點)
相關問題分析如下:
四、DMA控制高速並行ADC/DAC的弊端和問題
1、取樣觸發訊號問題及其解決辦法
1) 用STM32這樣的MCU代替FPGA來控制高速ADC,最大的問題在於MCU軟體的實時性遠遠趕不上硬體控制的FPGA。例如,前面提供的主程式程式碼中,用檢測按鍵的方式觸發DMA實現取樣。顯然無論是檢測按鍵的程式的時間精度還是程式呼叫外設庫啟動DMA傳輸的時間精度都遠遠低於ADC取樣的100ns數量級的時間精度。致使取樣觸發訊號的實時性只能滿足對“時間平穩訊號”分析的需要,無法達到對非平穩訊號進行時域分析的需求。
2)另外,從圖6中可以發現訊號剛開始的一段訊號是混亂的,造成混亂的原因有二:
其一:在高速傳輸條件下,在傳輸剛開始的一段時間STM32的DMA控制器無法及時的響應傳輸請求,從而造成DMA只能在TIM1的取樣請求已經發出一段時間後才讀取ADC的輸出資料,結果自然不正確。
其二:流水線型ADC的資料傳輸和取樣值之間存在延遲。從圖3給出的時序圖也可以發現,當前讀取的資料是四個時鐘之前“潛伏”在ADC的流水線中的,從而造成了緩衝區中開始一段的訊號錯誤。
以上兩個原因,都可以透過丟掉緩衝器中開始的一段資料的方法掩蓋,但這無疑也是對取樣觸發訊號的實時性的進一步降低。
2、TIM1輸出取樣時鐘抖動問題及其解決辦法
下圖是用20G取樣率的示波器DSOX6004A採集到的TIM1_CH2(PE11)輸出的取樣時鐘訊號。
圖7 TIM1的PWM功能產生的取樣時鐘的孔徑抖動(觸發後100us處)
為了測試取樣時鐘訊號的孔徑抖動情況,我將觀察窗對準取樣觸發後100us的地方(觸發-取樣延遲如圖中紅色圈內資料所示),可以發現該處時鐘上升沿的抖動達到了5ns左右(圖中示波器橫軸每個為5ns,如圖中黃色圈所示)。這表明TIM1的OC模組產生的取樣時鐘孔徑抖動品質較差,大大降低了取樣訊號的訊雜比。
為解決這個問題,可以使用片外模擬鎖相環PLL輸出的時鐘訊號作為ADC的取樣時鐘。至於STM32F407的TIM1_CH2則由輸出比較模式(OC)變為輸入捕獲模式(IC),由外部鎖相環產生的時鐘訊號作為TIM1_CH2的捕獲(IC)物件。 下圖是我使用單獨的模擬鎖相環PLL晶片Si5351產生相同的21MHz訊號,同樣在取樣觸發後100us的地方觀察時鐘抖動情況。可以明顯的看到鎖相環晶片產生的時鐘的孔徑抖動效能明顯由於定時器輸出比較模式輸出的時鐘。
圖8 模擬鎖相環晶片Si5351產生的取樣時鐘的孔徑抖動(觸發後100us處)
3、同步傳輸速率被限制在10MSPS左右
從本質上講,DMA是與CPU核心共享片上的匯流排資源的,當使用DMA高速傳輸並行資料時必然擠佔CPU讀取指令和資料的匯流排時間。如果DMA傳輸的是SRAM中的資料,某一筆傳輸由於匯流排被佔用而延期並不會影響傳輸整體的正確性。但對於ADC和DAC這樣的高速資料傳輸,某一筆資料的延遲就有可能造成取樣的錯誤。
經過測試,我整體的感覺是:即使把DMA傳輸資料的優先順序設為非常高(DMA_Priority_VeryHigh),且在採集期間不執行中斷服務(ISR)等可能打斷DMA的程式,當把DMA同步傳輸速率提升到10MSPS以上就很難保證每一筆傳輸的可靠實時性了。當然在極限情況下,同步傳輸速率是可以達到40MSPS的,但不建議大家在產品中使用。
4、單端數字訊號,抗干擾能力弱於差分資料線
當數字訊號在PCB上傳輸的速率達到10MSPS以上時,STM32中使用的單端3.3V CMOS在很多情況下就有可能出現傳輸錯誤,一般的解決方案是使用LVDS等差分傳輸標準。但STM32F4系列中沒有類似硬體配置,導致同步傳輸速率達到10MSPS以上時系統的抗干擾能力和傳輸正確率都會有所下降。