1. 程式人生 > >STM32F0系列用timer控制ADC取樣,然後DMA傳輸到Memory或者DAC

STM32F0系列用timer控制ADC取樣,然後DMA傳輸到Memory或者DAC

uint16_t  RegularConvData_Tab[32];

void ADC1_DMA_Config(void)

{
  ADC_InitTypeDef     ADC_InitStructure;
  GPIO_InitTypeDef    GPIO_InitStructure;
  DMA_InitTypeDef   DMA_InitStructure;
  /* ADC1 DeInit */  
  ADC_DeInit(ADC1);
  
  /* GPIOC Periph clock enable */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
  
   /* ADC1 Periph clock enable */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
  
  /* DMA1 clock enable */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);
  
  /* Configure ADC Channel2 as analog input */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 ;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  
  /* DMA1 Channel1 Config */
  DMA_DeInit(DMA1_Channel1);
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RegularConvData_Tab;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  DMA_InitStructure.DMA_BufferSize = 32;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);
  /* DMA1 Channel1 enable */
  DMA_Cmd(DMA1_Channel1, ENABLE);

  /*DMA傳輸完成中斷使能*/

  DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE)

  /* ADC DMA request in circular mode */
  ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular);
  
  /* Enable ADC_DMA */
  ADC_DMACmd(ADC1, ENABLE);  
  
  /* Initialize ADC structure */
  ADC_StructInit(&ADC_InitStructure);
  
  /* Configure the ADC1 in continous mode withe a resolutuion equal to 12 bits  */
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; 
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Backward;
  ADC_Init(ADC1, &ADC_InitStructure); 


  /* Convert the ADC1 Channel 1 with 71.5 Cycles as sampling time */ 
  ADC_ChannelConfig(ADC1, ADC_Channel_2 , ADC_SampleTime_71_5Cycles);   
  
  /* ADC Calibration */
  ADC_GetCalibrationFactor(ADC1);
  
  /* Enable ADC1 */
  ADC_Cmd(ADC1, ENABLE);     
  
  /* Wait the ADCEN falg */
  while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADEN)); 
  
  /* ADC1 regular Software Start Conv */ 
  ADC_StartOfConversion(ADC1);

}

void DMA1_Channel1_IRQHandler()
{
uint8_t i = 0;
if(DMA_GetITStatus(DMA1_IT_TC1) != RESET)
{
DMA_ClearITPendingBit(DMA1_IT_TC1);
for(i=0;i<32;i++)
{
printf("%d\n",(RegularConvData_Tab[i]-2048));
}
}
}

首先貼段程式碼,這是ADC和DMA的初始化函式。上面程式碼實現的功能是ADC取樣之後通過DMA方式把取樣資料放入Memory ,即程式中定義的陣列RegularConvData_Tab[32]。

幾點需要注意的引數:

1、DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RegularConvData_Tab;

2、DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//因為陣列大小是32,所以選擇MemoryInc

3、DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;    ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular);//這兩個函式對DMA的轉換模式設定要一致

4、 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //啟動ADC的迴圈轉換模式 

5、 ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//ADC的觸發方式是軟體觸發,因為Software starts ADC Conversions by setting

       ADSTART=1,即ADC_StartOfConversion(ADC1);  所以外部觸發電平上升沿或者下降沿引數設為ADC_ExternalTrigConvEdge_None

6、ADC_ChannelConfig(ADC1, ADC_Channel_2 , ADC_SampleTime_71_5Cycles);  //設定ADC的取樣時間 

解析:上述設定ADC clock 為3M,GPIOA2作為作為AD取樣的模擬輸入,GPIOA2對映到ADC的Channel2。首先是ADC取樣頻率f0的計算方法,設ADC的轉換時間是T,由以上設定可得T=(71.5+12.5)*ADC clock。又因為ADC_InitStructure.ADC_ContinuousConvMode = ENABLE,使能了ADC迴圈轉換模式。所以f0=1/T=35.7KHz。所以在這種ADC continuousConvMode模式下,改變ADC clock 和ADC 取樣時間,可以得到想要的ADC取樣頻率。但往往由於ADC clock 和 ADC_SampleTime不能很好的配,總得不到想要的任意ADC取樣頻率,於是想到了用TIMER來設定ADC取樣頻率。上述觸發了DMA傳輸完成中斷,在中斷裡列印由ADC取樣後放到數組裡的數值。

用TIMER來控制ADC取樣頻率,我想得到16KHz的ADC取樣頻率,所以我的定時器要設定為62us定時一次。

#define ADC1_TIM_PRESCALER  5

#define ADC1_TIM_CLKDIV            0  

#define ADC1_TIM_TIME_US         62

void TIM15_Init(void) 
{
    __IO uint32_t KEY_TIM_CLKValuekHz = 0; /* Contains the frequency input of KEY_TIM in Khz */
    __IO uint16_t TimeIntCount = 0;
   
   TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
   /* Get frequency input of Decode_TIM in Khz */
   KEY_TIM_CLKValuekHz = CM0_TIM_GetCounterCLKValue() / ((ADC1_TIM_PRESCALER + 1)*1000);
   /* Calc the TIM count */
  TimeIntCount = (KEY_TIM_CLKValuekHz * ADC1_TIM_TIME_US)/1000; 

   /* Decode_TIM Configuration */
  TIM_DeInit(ADC1_TIM); 
  TIM_TimeBaseStructure.TIM_Period = TimeIntCount;
  TIM_TimeBaseStructure.TIM_Prescaler = ADC1_TIM_PRESCALER;       
  TIM_TimeBaseStructure.TIM_ClockDivision = ADC1_TIM_CLKDIV;    
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;   
  TIM_TimeBaseInit(ADC1_TIM, (TIM_TimeBaseInitTypeDef *)&TIM_TimeBaseStructure);
  /* Clear Decode_TIM Capture compare interrupt pending bit */
  TIM_ClearITPendingBit(ADC1_TIM, TIM_IT_Update);
  TIM_SelectOutputTrigger(ADC1_TIM,TIM_TRGOSource_Update);
  /* Disable Decode_TIM counter */
  TIM_Cmd(ADC1_TIM, ENABLE);
}

void ADC1_DMA_Config(void)//PA2
{
   ADC_InitTypeDef     ADC_InitStructure;
  GPIO_InitTypeDef    GPIO_InitStructure;
   DMA_InitTypeDef   DMA_InitStructure;
  /* ADC1 DeInit */  
  ADC_DeInit(ADC1);
  
  /* GPIOA Periph clock enable */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
  
   /* ADC1 Periph clock enable */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
  
  /* DMA1 clock enable */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);
  
  /* Configure ADC Channel2 as analog input */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 ;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

   /* DMA1 Channel1 Config */
  DMA_DeInit(DMA1_Channel1);
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RegularConvData_Tab;

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//read from peripheral

  DMA_InitStructure.DMA_BufferSize = 32;

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA_Mode_Circular DMA_Mode_Normal
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);
  /* DMA1 Channel1 enable */
  DMA_Cmd(DMA1_Channel1, ENABLE);
  DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
  /* ADC DMA request in circular mode */
  ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular);
  
  /* Enable ADC_DMA */
  ADC_DMACmd(ADC1, ENABLE);  
  
  /* Initialize ADC structure */
  ADC_StructInit(&ADC_InitStructure);
  
  /* Configure the ADC1 in continous mode withe a resolutuion equal to 12 bits  */
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;

  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T15_TRGO;
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Backward;//from channel 16 to 0
  ADC_Init(ADC1, &ADC_InitStructure); 

  /* Convert the ADC1 Channel 2 with 71.5 Cycles as sampling time PCLK/4*/ 
  ADC_ChannelConfig(ADC1, ADC_Channel_2 , ADC_SampleTime_71_5Cycles); 


  /* ADC Calibration */
  ADC_GetCalibrationFactor(ADC1);
  /* Enable ADC1 */
  ADC_Cmd(ADC1, ENABLE);     
  
  /* Wait the ADCEN falg */
  while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADEN)); 
  
  /* ADC1 regular Software Start Conv */ 
  ADC_StartOfConversion(ADC1);
}

解析:上述是由TIMER來控制ADC取樣的設定。首先是timer15的初始化,62.5us定時一次,其中要設定timer的更新事件作為觸發輸出,以此來觸                 發 ADC。TIM_SelectOutputTrigger(ADC1_TIM,TIM_TRGOSource_Update); 函式ADC1_DMA_Config(void)中主要是更改ADC部分,其中有三個引數要改:ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//將ADC迴圈模式給DISABLE掉,因為由timer觸發就不用使能。否則取樣頻率不對,這一點困了我好長時間ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;//ADC由timer的上升沿觸發, ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T15_TRGO;//設定ADC的觸發源是Timer15,這樣設定之後,每次由timer的更新事件觸發一次ADC,不用設定timer的中斷事件,DMA每傳輸完成一次就觸發DMA傳輸完成中斷,在中斷裡就可以測得32次ADC取樣時間t1,經過示波器抓取波形和計算得到 t1/32=62。

當用DMA方式把ADC取樣值傳到DAC時,即實現DMA從外設到外設的傳輸

void DAC_Config(void)
{
  DAC_InitTypeDef    DAC_InitStructure;
  GPIO_InitTypeDef   GPIO_InitStructure;

  /* Enable GPIOA clock */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
  
  /* Configure PA.04 (DAC_OUT1) in analog mode -------------------------*/
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AN;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* Enable DAC clock */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
  
  /* DAC channel1 Configuration */
  DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
  DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;

  /* DAC Channel1 Init */
  DAC_Init(DAC_Channel_1, &DAC_InitStructure);
  
  /* Enable DAC Channel1 */
  DAC_Cmd(DAC_Channel_1, ENABLE);

}

void ADC1_DMA_Config(void)//PA2
{
  ADC_InitTypeDef     ADC_InitStructure;
  GPIO_InitTypeDef    GPIO_InitStructure;
  DMA_InitTypeDef      DMA_InitStructure;
  /* ADC1 DeInit */  
  ADC_DeInit(ADC1);
  
  /* GPIOA Periph clock enable */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
  
   /* ADC1 Periph clock enable */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
  
  /* DMA1 clock enable */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);
  
  /* Configure ADC Channel2 as analog input */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 ;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

 
  /* DMA1 Channel1 Config */
  DMA_DeInit(DMA1_Channel1);
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(&(DAC->DHR12R1));
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//read from peripheral
  DMA_InitStructure.DMA_BufferSize = 1;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA_Mode_Circular DMA_Mode_Normal
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);

  /* DMA1 Channel1 enable */
  DMA_Cmd(DMA1_Channel1, ENABLE);

  DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
  /* ADC DMA request in circular mode */
  ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular);
  
  /* Enable ADC_DMA */
  ADC_DMACmd(ADC1, ENABLE);  
  
  /* Initialize ADC structure */
  ADC_StructInit(&ADC_InitStructure);
  
  /* Configure the ADC1 in continous mode withe a resolutuion equal to 12 bits  */
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T15_TRGO;
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Backward;
  ADC_Init(ADC1, &ADC_InitStructure); 


  /* Convert the ADC1 Channel 2 with 71.5 Cycles as sampling time PCLK/4*/ 
  ADC_ChannelConfig(ADC1, ADC_Channel_2 , ADC_SampleTime_71_5Cycles); 

  /* ADC Calibration */
  ADC_GetCalibrationFactor(ADC1);
  /* Enable ADC1 */
  ADC_Cmd(ADC1, ENABLE);     
  
  /* Wait the ADCEN falg */
  while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADEN)); 
  
  /* ADC1 regular Software Start Conv */ 
  ADC_StartOfConversion(ADC1);
}

上述程式碼是設定GPIOA4作為DAC的輸出引腳,使能DAC的Channel 1。ADC_DMA部分,要改動的是隻是DMA部分,有幾個引數要改動

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(&(DAC->DHR12R1));
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;

DMA_InitStructure.DMA_BufferSize = 1;

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

解析:因為此時DMA要實現從外設到外設的傳輸,所以將DMA_InitStructure.DMA_MemoryBaseAddr這個引數賦予DAC->DHR12R1暫存器,因為DAC轉換時資料的傳輸方向 是DAC->DHR12R1===>>DHRx===>DAC->DORx==>數模轉換器。因為都是傳到DAC->DHR12R1暫存器,所以DMA_MemoryInc DISABLE。因為這時我觸發了DMA傳 輸完成中斷,設定DMA_BufferSize為1,是想實時知道從ADC傳到DAC->DHR12R1裡的值。還有就是引數DMA_InitStructure.DMA_MemoryDataSize,此時應該賦值

DMA _MemoryDataSize_HalfWord,而不是DMA_PeripheralDataSize_HalfWord。我在除錯中一開始出錯就出在這個引數的設定上。

其他總結:

1、我用的晶片是STMF050X系列,其ADC外部觸發事件有:TIM1_TRGO,TIM2_TRGO,TIM3_TRGO,TIM15_TRGO。

雖然手冊中說只有STMF051X系列才有TIM15, 但是我用TIMER15來設定ADC取樣頻率,仍然能用。

2、雖然STMF050x6 datasheet中沒有講到DAC,但是真正實驗了,F050X6的DAC可以用

3、雖然STMF050x6 datasheet中介紹DMA時沒有說支援peripheral to peripheral ,即實現從外設到外設的傳輸,

但試驗後也是可以實現從ADC到DAC資料傳輸的。

4、ADC取樣頻率的計算,對於上面提到的三種傳輸方式。

方式一:ADC迴圈轉換,將轉換後的資料放入放入Memory ,即程式中定義的陣列。

方式二:ADC轉換由timer控制,將轉換後的資料放入程式中定義的陣列(同上)。

方式三:ADC轉換由timer控制,將轉換後的資料放入DAC的某個暫存器裡。

上面三種方式,關於ADC取樣頻率f的計算就兩種模式,一是方式一的方法:採用AD迴圈轉換方式,那麼在AD配置裡計算的AD轉換時間T就有用了,f=1/T。另外一種就是ADC用timer控制後,迴圈轉換方式被Disable了,那麼此時的f就應該是timer出發時間的倒數了!!!