STM32定時器觸發ADC多通道連續取樣,DMA快取結果

helesheng發表於2022-01-28
STM32的ADC使用非常靈活,取樣觸發方面:既支援軟體觸發,定時器或其他硬體電路自動觸發,也支援轉換完成後自動觸發下一通道/輪轉換。轉換結果儲存方面:既支援軟體讀取和轉存,也支援DMA自動儲存轉換結果。STM32書籍介紹的最多的是“軟體觸發 + 查詢法讀取轉換結果的方式”,對採集溫度、溼度這樣近乎直流的訊號而言,這種方法足夠應付。但當應用需要提升A/D轉換的取樣率時,這種做法就逐漸無法滿足求了:1、軟體需要通過頻繁的查詢或中斷來確定在取樣間隔時到達時及時觸發下一輪A/D轉換,處理器的其他工作被頻繁打斷,軟體開發難度增加。2、定時器定時中斷/查詢法的定時精度/抖動時間大概是處理器執行數條指令的時間,對於STM32而言,當取樣率大於10KSPS以後,取樣定時抖動造成訊雜比降低將使A/D的訊雜比降低到與10位A/D相當(具體數學分析詳見本人之前博文:(https://www.cnblogs.com/helesheng/p/8880492.html)。
STM32提供兩種方法來解決這個問題:方法一:讓ADC不停歇的連續進行轉換,轉換結果則通過DMA直接搬運到記憶體中。由於ADC進行一次轉換的時間可以由ADC時鐘CLKADC頻率和取樣保持時間精確確定,這種方法有效的降低了轉換間隔時間的孔徑抖動,提高了訊雜比,尤其適合200KSPS以上的高取樣率。有興趣的讀者可以參考我在博文:https://www.cnblogs.com/helesheng/p/8880492.html中給出的程式碼。由於無法連續調節CLKADC頻率和取樣保持時間,但這種方法的缺陷是無法連續調節取樣率,如20.05KSPS,44.1KSPS等常用但非整數的取樣率是無法被產生的。本文介紹的方法二,是由定時器3的TRGO訊號直接出發A/D轉換的方法可以有效的在低於200KSPS以下時實現取樣率的連續調節。由於使用定時器硬體直接出發A/D轉換,無需軟體參與,這種方法也能夠有效的避免取樣間隔的孔徑抖動。另外,為了避免軟體頻繁參與轉換結果讀取造成的執行效率降低和開發難度增大問題,本文提供了通過DMA讀取自動連續轉換結果的程式碼。
以下原創內容歡迎網友轉載,但請註明出處: https://www.cnblogs.com/helesheng
 
一、ADC模組配置
 1 RCC_ADCCLKConfig(RCC_PCLK2_Div8);   //設定ADC分頻因子8 72M/8=9,ADC最大時間不能超過14M  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC1工作在獨立模式
 2 
 3 ADC_InitStructure.ADC_ScanConvMode = ENABLE;//模數轉換工作在掃描模式(多通道)
 4 ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//ADC工作在非連續模式
 5 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;//定時器3的TRGO觸發轉換
 6 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//ADC資料右對齊
 7 ADC_InitStructure.ADC_NbrOfChannel = 2;//轉換的ADC通道的數目為2
 8 ADC_Init(ADC1, &ADC_InitStructure);//要把以下引數初始化ADC_InitStructure
 9 ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_13Cycles5);//ADC1通道6 ,取樣時間為13.5個週期 
10 ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 2, ADC_SampleTime_13Cycles5);//ADC1通道7 ,取樣時間為13.5個週期 
11 ADC_DMACmd(ADC1, ENABLE);  //使能ADC1的DMA傳輸方式
12 ADC_Cmd(ADC1, ENABLE);//使能ADC1
13 ADC_ResetCalibration(ADC1);//重置ADC1的校準暫存器
14 while(ADC_GetResetCalibrationStatus(ADC1));
15 ADC_StartCalibration(ADC1); //開始校準ADC1
16 while(ADC_GetCalibrationStatus(ADC1)); //等待校準完成
17 ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能ADC1軟體轉換 
其中值得注意的是:
1、ADC被配置為由外部訊號觸發,而觸發訊號是TIM3產生的TRGO。注意STM32不支援其他定時器的TRGO作為ADC的觸發源。
2、ADC被配置為非連續工作模式( ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;)所謂“連續工作模式”,就是前面提到了通過配置每次A/D轉換時間實現取樣定時的工作方式。如果該模式被使能,就意味著ADC會在被一次觸發後就逐通道、不停歇的進行連續的轉換,而不會等到下次定時器觸發訊號TRGO再啟動A/D轉換。
3、ADC被配置為多通道掃描模式(ADC_InitStructure.ADC_ScanConvMode = ENABLE;),這樣ADC會在每次被TIM3觸發後依次完成規則通道組中每個通道的一輪轉換
二、DMA的配置
 1   DMA_DeInit(DMA1_Channel1);
 2   DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//傳輸的源頭地址
 3   DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADCConvertedValue;//目標地址
 4   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外設作源頭
 5   DMA_InitStructure.DMA_BufferSize = 2000;//資料長度為2000
 6   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設地址暫存器不遞增
 7   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//記憶體地址遞增
 8   DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外設傳輸以位元組為單位
 9   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//記憶體以字為單位
10   DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//迴圈模式
11   DMA_InitStructure.DMA_Priority = DMA_Priority_High;//4優先順序之一的(高優先)
12   DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非記憶體到記憶體
13   DMA_Init(DMA1_Channel1, &DMA_InitStructure);//根據以上引數初始化DMA_InitStructure
14 
15   DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);//配置DMA1通道1傳輸完成中斷 
16   DMA_Cmd(DMA1_Channel1, ENABLE);//使能DMA1
其中值得注意的是:
1、DMA被配置為外設到記憶體方式,每次傳輸的資料寬度是半字(16位)。
2、緩衝長度為2000個半字(DMA_InitStructure.DMA_BufferSize = 2000;),由於前面配置了兩個通道,緩衝區將一次能存放1000次取樣的結果。
2、使用迴圈模式(DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;),使能了DMA1中斷(DMA_Init(DMA1_Channel1, &DMA_InitStructure);)這樣將在完成1000次取樣後觸發DMA中斷,以便軟體一次性讀取這1000次轉換的結果。而下一次ADC轉換完成後結果將從頭覆蓋這個緩衝區的內容。
與之配套的中斷控制器配置程式如下:
1 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
2 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//搶佔優先順序設定為1
3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//子優先順序設定為1
4 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//中斷使能
5 NVIC_Init(&NVIC_InitStructure);//按指定引數初始化中斷     
其中搶佔優先順序和子優先順序可以根據應用實際需要確定。
DMA中斷服務程式如下:
 1 void DMA1_Channel1_IRQHandler(void)
 2 {    
 3     if(DMA_GetITStatus(DMA1_IT_TC1))//判斷通道1是否傳輸完成
 4      {
 5         DMA_ClearITPendingBit(DMA1_IT_TC1);    //清除通道1傳輸完成標誌位
 6 
 7     ////////////此處應編寫程式碼從DMA指向的記憶體區讀走資料,否則可能被覆蓋//////////
 8 
 9 
10      }
11 }
三、TIM3配置
TIM3配置程式碼如下:
 1 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能TIM3時鐘
 2 
 3 TIM_TimeBaseStructure.TIM_Period = 1000-1;
 4 TIM_TimeBaseStructure.TIM_Prescaler = 72-1;
 5 TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;      //取樣分頻TIM_CKD_DIV1
 6 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;     //向上計數
 7 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
 8 
 9 TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);//設定輸出TRGO訊號
10 TIM_Cmd(TIM3, ENABLE);
其中值得注意的是:
1、週期暫存器和預分頻暫存器配置後,將TIM3的溢位頻率確定為1KHz。而這些值可以根據應用需要的取樣率自己確定。
2、TIM3溢位後將輸出TRGO訊號(TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);)。
 
 更多關於STM32定時器和ADC的高階使用方法,歡迎大家購買我的新書《基於STM32的嵌入式系統原理及應用》(科學出版社出版 ISBN:9787030697974)或我的B站賬號“何樂生0

 

 

 

 

 

 

 

 

 

 

 
 
 
 
 
 

相關文章