Keil MDK STM32系列(六) 基於抽象外設庫HAL的ADC模數轉換


Keil MDK STM32系列

配置 ADC

  • 模式: 如果只啟用了一個ADC, 這里只能配置為Independent mode
  • 時鍾分頻: 這個選項是ADC的預分頻器, 可設置為2/4/6/8, 決定了一個ADC時鍾周期. 加入設置為2, 由於ADC是掛載在APB2總線(84M)上, 所以一個ADC時鍾便是84 * M/2=42M
  • 分辨率: 最高為12位分辨率, 分辨率越高轉換時間越長
  • 數據對齊方式: 如果選擇12位分辨率, 右對齊, 得到的結果最大便是4096.
  • 掃描模式: 轉換完一個通道會不會繼續轉換下一個通道
  • 連續轉換模式: 使能的話轉換將連續進行
  • 不連續轉換模式: 當使能多個轉換通道時, 可單獨設置不連續轉換通道.
  • DMA連續請求: 是否連續請求DMA.
  • EOC標志設置: 當有多個轉換通道時, 是每轉換完一個通道設置一次EOC標志還是所有通道都轉換完設置一次EOC標志.
  • 轉換的通道數:
  • 觸發模式: 可選擇軟件觸發, 外部觸發或定時器事件觸發
  • 秩序列表: 設置轉換周期數和轉換順序
  • 注入通道設置
  • 窗口看門狗模式

配置 ADC 為主動請求模式

while (1)
{
  /*##-1- Start the conversion process #######################################*/  
  HAL_ADC_Start(&hadc1);

  /*##-2- Wait for the end of conversion #####################################*/  
   /*  Before starting a new conversion, you need to check the current state of 
              the peripheral; if it’s busy you need to wait for the end of current
              conversion before starting a new one.
              For simplicity reasons, this example is just waiting till the end of the 
              conversion, but application may perform other tasks while conversion 
              operation is ongoing. */
  HAL_ADC_PollForConversion(&hadc1, 50);

  /* Check if the continous conversion of regular channel is finished */  
  if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC)) 
  {
      /*##-3- Get the converted value of regular channel  ######################*/
      AD_Value = HAL_ADC_GetValue(&hadc1);
      printf("MCU Temperature : %.1f¡æ\r\n",((AD_Value*3300/4096-760)/2.5+25));
  }
  HAL_Delay(1000);
}

配置 ADC 為多通道連續掃描DMA模式

  • 開ADC的IN0/IN1兩個通道

    • 在Pinout圖上, 將PA0和PA1設為ADC1_IN0和ADC2_IN1
  • 配置時鍾

  • ADC1相關配置

  • ADCs_Common_Settings

    • Mode: Independent mode
  • ADC_Settings

    • Clock Prescaler: PCLK2 divided by 4 可以在時鍾配置頁看到PCLK2的值
    • Resolution: 12bits (15 ADC Clock cycles) 采樣精度12bit, 此時每次采樣需要15個時鍾周期, 8bit對應11個時鍾周期
    • Data Alignment: Right alignment
    • Scan Conversion Mode: Enabled
    • Continuous Conversion Mode: Enabled --> for DMA
    • Discontinuous Conversion Mode: Disabled
    • DMA Continuous Requests: Enabled
    • End Of Conversion Selection: EOC flag at the end of single channel conversion
  • ADC_Regular_ConversionMode

    • Number of Conversion: 2 --> 2 channels
    • External Trigger Conversion Source: Regular Conversion launched by software
    • External Trigger Conversion Edge: None
    • Rank: 1: Choose channel 0
    • Rank: 2: Choose channel 1
  • ADC_Injected_ConversionMode

    • Number of Conversions: 0
  • DMA相關配置

  • ADC1

    • Stream: DMA2 Stream 4
    • Direction: Peripheral To Memory
    • Priority: High
  • DMA Request Settings

    • Mode: Circular
    • Increment Address: Memory
    • Datawidth: Peripheral->Half Word, Memory->Half Word
  • NVIC Settings

    • ADC1 global interrupt: Enabled unchecked
    • DMA2 stream4 global interrupt: Enabled checked

ADC+DMA配置, 體現在代碼上的變化

  1. stm32f4xx_hal_conf.h 去掉了ADC的注釋
#define HAL_ADC_MODULE_ENABLED
  1. stm32f4xx_it.h 增加了方法聲明
void DMA2_Stream4_IRQHandler(void);
  1. stm32f4xx_it.c 增加了對應的typeDef和方法定義
extern DMA_HandleTypeDef hdma_adc1;

void DMA2_Stream4_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdma_adc1);
}
  1. stm32f4xx_hal_msp.c
extern DMA_HandleTypeDef hdma_adc1;

void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hadc->Instance==ADC1)
  {
    __HAL_RCC_ADC1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**ADC1 GPIO Configuration
    PA0-WKUP     ------> ADC1_IN0
    PA1     ------> ADC1_IN1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* ADC1 DMA Init */
    /* ADC1 Init */
    hdma_adc1.Instance = DMA2_Stream4;
    hdma_adc1.Init.Channel = DMA_CHANNEL_0;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc1.Init.Mode = DMA_CIRCULAR;
    hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1);
  }

}

void HAL_ADC_MspDeInit(ADC_HandleTypeDef* hadc)
{
  if(hadc->Instance==ADC1)
  {
    /* Peripheral clock disable */
    __HAL_RCC_ADC1_CLK_DISABLE();

    /**ADC1 GPIO Configuration
    PA0-WKUP     ------> ADC1_IN0
    PA1     ------> ADC1_IN1
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0|GPIO_PIN_1);

    /* ADC1 DMA DeInit */
    HAL_DMA_DeInit(hadc->DMA_Handle);
  }

}
  1. main.c
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;


static void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ENABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 2;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = 2;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  __HAL_RCC_DMA2_CLK_ENABLE();

  HAL_NVIC_SetPriority(DMA2_Stream4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);
}


最后, 在main.c中增加用於存儲DMA數據的數組, 將數組地址傳給HAL_ADC_Start_DMA()開啟DMA傳輸就可以得到數據了.

DMA數組大小和中斷的問題

數組的大小與HAL_ADC_Start_DMA()方法第三個參數length一致, 這里length代表的是數據的個數. 在設置這個大小時, 如果開啟了DMAx_Streamx_IRQn的中斷, 要考慮sConfig.SamplingTime指定的采樣時間不能太短, 太短的話會一直卡在中斷里(因為中斷什么都不做也需要時間). 這個與SYSCLK大小無關, 在兩個通道采樣時

  • 如果這里指定的值為ADC_SAMPLETIME_3CYCLES, 這個數組大小至少為6, 如果等於4采樣循環會卡住
  • 如果指定的值為ADC_SAMPLETIME_15CYCLES, 這個數組大小至少為4
  • 如果指定的值為ADC_SAMPLETIME_28CYCLES, 數組大小可以為2

如果不需要使用DMA中斷, 可以在 MX_DMA_Init()方法中, 將HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);這句注釋掉或者改成HAL_NVIC_DisableIRQ(DMA2_Stream4_IRQn);指定禁用它, 這個數組就可以設到最小(和采樣通道數一致)了.

uint16_t ADC_Value[6];

main(void) {
   HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_Value, 6);    // Enable DMA transfer
   while (1)
  {
    printf("%d %d %d %d\r\n", 
      ADC_Value[0], ADC_Value[1], ADC_Value[2], ADC_Value[3]);
    HAL_Delay(100);
  }
}

DMA中斷處理回調

查看代碼可以看到, 在stm32f4xxx_hal_dma.h中, 定義的 DMA_HandleTypeDef 類型中, 包含了幾個對應中斷的處理方法

typedef struct __DMA_HandleTypeDef
{
  DMA_Stream_TypeDef         *Instance;                                                        /*!< Register base address                  */
  DMA_InitTypeDef            Init;                                                             /*!< DMA communication parameters           */ 
  HAL_LockTypeDef            Lock;                                                             /*!< DMA locking object                     */  
  __IO HAL_DMA_StateTypeDef  State;                                                            /*!< DMA transfer state                     */
  void                       *Parent;                                                          /*!< Parent object state                    */ 
  void                       (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);         /*!< DMA transfer complete callback         */
  void                       (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);     /*!< DMA Half transfer complete callback    */
  void                       (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma);       /*!< DMA transfer complete Memory1 callback */
  void                       (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);   /*!< DMA transfer Half complete Memory1 callback */
  void                       (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);        /*!< DMA transfer error callback            */
  void                       (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma);        /*!< DMA transfer Abort callback            */  
  __IO uint32_t              ErrorCode;                                                        /*!< DMA Error code                          */
  uint32_t                   StreamBaseAddress;                                                /*!< DMA Stream Base Address                */
  uint32_t                   StreamIndex;                                                      /*!< DMA Stream Index                       */
}DMA_HandleTypeDef;

其中處理接收完成的方法是 XferCpltCallback , 這個在 stm32f4xx_hal_adc.c 中, 被指定為相應的靜態方法

stm32f4xx_hal_adc.c

HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)
{
  //...
  /* Set the DMA transfer complete callback */
  hadc->DMA_Handle->XferCpltCallback = ADC_DMAConvCplt;

對應不同外設, 指定的方法是不同的, 例如對於uart, stm32f4xx_hal_uart.c中指定的是

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  //...
  /* Set the UART DMA transfer complete callback */
  huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt;

對於ADC, 再進一步在ADC_DMAConvCplt()方法中定義了處理方法

static void ADC_DMAConvCplt(DMA_HandleTypeDef *hdma)   
{
  //...
    /* Conversion complete callback */
#if (USE_HAL_ADC_REGISTER_CALLBACKS == 1)
    hadc->ConvCpltCallback(hadc);
#else
    HAL_ADC_ConvCpltCallback(hadc);
#endif /* USE_HAL_ADC_REGISTER_CALLBACKS */
  }
  else /* DMA and-or internal error occurred */
  {
    if ((hadc->State & HAL_ADC_STATE_ERROR_INTERNAL) != 0UL)
    {
      /* Call HAL ADC Error Callback function */
#if (USE_HAL_ADC_REGISTER_CALLBACKS == 1)
      hadc->ErrorCallback(hadc);
#else
      HAL_ADC_ErrorCallback(hadc);
#endif /* USE_HAL_ADC_REGISTER_CALLBACKS */
    }
  else
  {
      /* Call DMA error callback */
      hadc->DMA_Handle->XferErrorCallback(hdma);
    }
  }
}

所以, 開發時只需要定義 HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) 和 HAL_ADC_ErrorCallback(ADC_HandleTypeDef* hadc)方法, 就能處理DMA傳輸完成的中斷

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM