HAL库 ADC超详细(查询、中断、DMA)

本文详细介绍了STM32 ADC的三种工作模式:查询模式、中断模式和DMA模式,以及各自的优缺点。查询模式占用CPU时间,中断模式提高CPU利用率,而DMA模式几乎不占用CPU资源。此外,还阐述了ADC的单通道和多通道转换模式,包括扫描模式和连续转换模式的配置和应用。在实际操作中,针对不同的应用场景选择合适的工作模式和转换策略,以优化系统性能。最后,提供了ADC在不同模式下的STM32CubeMX配置示例和软件实现代码片段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、ADC的三种工作方式及优缺点

1.查询模式:查询模式下,占用CUP时间较多,cup效率较低。

2.中断模式:相比查询模式大大释放了cup,提高了cup的利用率。

3.DMA模式:该模式下基本不占用cup,能直接将ADC采集的数据存储到存储器。

二、ADC的转换方式

      转换方式需要根据情况搭配使用,分为扫描模式(Scan Conversion mode)、连续转换模式(Continuous Conversion Mode)和间断模式(Discontinuous Conversion Mode)。

ADC单通道转换:

  “单次转换模式,扫描模式关闭”:只进行一次转换,不过可以持续使能ADC达到不断采集的的。

  “连续转换模式,扫描模式关闭”:使能一次ADC后,能够连续转换。

ADC多通道转换:

 单次转换模式”,扫描模式开启”:每个通道转换完一次后结束转换,但可以通过持续使能ADC不断进行多通道连续采集。

 “连续转换模式”,扫描模式开启”:每个通道转换完之后还能继续循环转换,不需要反复使能ADC。

  多通道必须开启扫描模式。



三、ADC之查询模式(阻塞模式)

1、流程:

①开启ADC:调用HAL_ADC_Start(),开启ADC。

②等待EOC标志位:调用查询函数HAL_ADC_PollForConversion(),等待ADC转化结束,CUP在这段时间内不能干其他事,所以查询方式降低了CUP的使用率。

③读取寄存器数据:调用HAL_ADC_GetValue()。

2、STM32CUBEMX配置(ADC部分)

   使用的stm32f103zet6正点原子精英版,通过打开ADC1的通道1(PA1)。

 

Data Alignment : 可选左对齐或右对齐。

Conversion Mode:由于只使用了一个通道,关闭就行。

Continuous Conversion Mode:这里关闭,我们使用软件开启ADC。

Discontinuous Conversion Mode:单通道模式间断模式自动关闭,不可选。

Enable Regular Conversions:是否使能转换,开启规则转换。

Number Of Conversion:转换的通道数,单通道当然只能是1。

External Trigger Conversion Source:选择由软件触发采集。

Rank :每个通道的编号,

      每个Rank有如下参数配置:

      Channel:所选择的通道

      Sampling Time:每次采集ADC所需要的时间(采样周期),T = 采样周期 + 12.5个周期,(其中1周期为1/ADCCLK,设置的ADC时钟频率为12M),这里我设置的ADC的采样周期 为1.5Cycles,所以转化一次总的时间T=(1.5+12.5)/12=1.167us。补充:采样周期越长,越准确,这里存在一个竞争冒险的关系。

3、软件配置

在adc.h文件添加代码:

/* USER CODE BEGIN 1 */
获取ADC的值
uint16_t Get_adc(uint16_t ch)
{
	HAL_ADC_Start(&hadc1);  //先开启ADC
	HAL_ADC_PollForConversion(&hadc1,1);//查询函数,查询EOC标志位。每次采样,CUP在这里都要 
                                       //等待采样完成才能进行下一步,这段时间CUP没有干其他 
                                       //事,所以降低了CUP使用率

	return HAL_ADC_GetValue(&hadc1);    //得到ADC的值
} 

/获取times次采样值的平均值///
uint16_t Get_ADC_Average(uint16_t ch,uint8_t times)
{
   uint32_t ADC_Sum=0;
	 uint8_t i;
   for(i=0;i<times;i++)
	 {
		 ADC_Sum+=Get_adc(ch);
		 HAL_Delay(5);
	 }
	 return ADC_Sum/times;                //对times次样品值取平均,使采样值更加准确
}
/* USER CODE END 1 */

在main..c的初始化代码段定义一个uint16_t ADC_Value1;在while中添加一下代码(串口自己配置,可参考串口通信

  /* USER CODE BEGIN 1 */
    uint16_t ADC_Value1;    //
  /* USER CODE END 1 */

 
  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
    HAL_ADCEx_Calibration_Start(&hadc1);//开启ADC之前校准一下
  /* USER CODE END 2 */
  while (1)
  {

		ADC_Value1=Get_ADC_Average(ADC_CHANNEL_1,10);//得到10次采样值的均值
		printf("ADC:%d\n",ADC_Value1);
		HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
		HAL_Delay(1);
  }

ADC查询模式小总结:在关闭间断模式的情况下,多通道采集尽量不使用ADC查询模式进行,不然采样值容易出错,如果要用一定要开启间断采样模式。

四、多通道查询模式

1.STM32CUBEMX配置

  开启ADC1的四个通道 

 2.软件配置

在main.c中添加:

int main(void)
{
  /* USER CODE BEGIN 1 */
    uint16_t ADC_Value1,i; 
	uint16_t adcbuf[4];    //四个通道采样数据存储的数组
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADCEx_Calibration_Start(&hadc1);   //开启adc前校准一下ADC
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		
		for(i=1;i<5;i++)
		{
			HAL_ADC_Start(&hadc1);   //由于没有开启连续转换,每次转换前需要软件开启adc
			HAL_ADC_PollForConversion(&hadc1,10);//轮询等待ADC转换完成
			adcbuf[i]=HAL_ADC_GetValue(&hadc1);     //将四个通道的采样值存在数组里
			printf("adc_ch%d:  %d  \n\r",i,adcbuf[i]);
		}
		
     HAL_ADC_Stop(&hadc1);//关闭ADC
		  HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
			HAL_Delay(100);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

五、ADC之中断模式(单通道)

1、流程

①启动ADC,使能中断

②等待中断触发

③在中断中读取寄存器数据

2、STM32CUBEMX配置(ADC部分)

ADC的中断模式和单通道采样一样,只是多使能了一下adc中断。

 3、软件配置

只需在while()的上面校准adc和开启ADC。 

HAL_ADCEx_Calibration_Start(&hadc1); 
HAL_ADC_Start_IT(&hadc1);

在ADC的回调函数里面添加:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)//回调函数
{
	uint16_t Adc_Value;
	Adc_Value=HAL_ADC_GetValue(&hadc1);
  printf("adc_value:%d\n",Adc_Value);
}

 六、ADC之DMA(多通道)

1、流程

启动ADC

②配置DMA缓冲区

③读取缓冲区数据

2、STM32CUBEMX配置(ADC部分)

开启ADC1的通道1和2。

 

 3、软件配置

main.c

int main(void)
{
  /* USER CODE BEGIN 1 */
   uint16_t ADC_Value1,i;
	uint16_t AD_Buf[2]={0};//两个通道采集数据存在这个数组里面
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
	 HAL_ADCEx_Calibration_Start(&hadc1);
	HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&AD_Buf,2);//开启ADC的DMA,采集的数据直接放入 
                                                     AD_Buf这个数组里,操作简单。
  /* USER CODE END 2 */
  while (1)
  {
		printf("AD1=%d\n\r",AD_Buf[0]);
        printf("AD2=%d\n\r",AD_Buf[1]);
		HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
		HAL_Delay(20);
  }
}

ADC的DMA模式小总结:DMA模式最简单,并且采样过程基本不会影响CPU,一般使用ADC的DMA模式进行采样。

<think>好的,我现在需要帮助用户理解他们提供的这个函数`double get_vol(ADC_HandleTypeDef *hadc)`,特别是里面的参数。用户之前询问过`HAL_ADC_GetValue`是否需要中断,现在可能是在进一步开发ADC相关的代码,遇到了具体的问题或者想更深入理解这个函数的实现细节。 首先,我需要分析用户提供的函数代码。函数的主要功能是启动ADC转换,读取ADC的值,然后将其转换为电压值。参数是`ADC_HandleTypeDef *hadc`,这个结构体指针应该是HAL中用来管理ADC配置的句柄。 用户的问题集中在两点:函数的功能和参数的作用。我需要详细解释这个函数每一步做了什么,特别是参数的意义。同时,用户之前对是否需要中断有疑问,可能需要在这里关联起来,解释为什么在这个函数中没有使用中断,或者是否需要考虑转换完成的状态。 接下来,我需要考虑用户可能的背景。他们可能在使用STM32HAL进行开发,对ADC的基本使用有一定了解,但可能对具体的函数调用流程和参数传递不太清楚。特别是`ADC_HandleTypeDef`这个结构体的作用,可能需要解释清楚,因为它包含了ADC的配置信息,比如通道、分辨率、时钟等。 在函数内部,首先调用了`HAL_ADC_Start(hadc)`,这里需要指出是否正确。根据之前的回答,`HAL_ADC_Start`启动转换,但如果是单次转换模式,可能需要使用`HAL_ADC_Start_IT`或者轮询转换完成状态。当前的函数直接调用`HAL_ADC_Start`后立即读取值,可能存在问题,因为转换可能还未完成,导致读取的是旧数据或无效数据。 然后,函数调用`HAL_ADC_GetValue(hadc)`获取ADC值,这里是否缺少了等待转换完成的步骤?比如使用`HAL_ADC_PollForConversion`或者中断来处理。用户之前的提问中已经了解到了这一点,所以需要指出当前代码的潜在问题,即没有等待转换完成就直接读取,可能导致数据不准确。 接下来是电压转换公式`3.3 * adc_value / 4096`,这里假设ADC是12位分辨率,参考电压是3.3V。需要确认用户是否了解这个公式的来源,以及他们的硬件配置是否符合这个假设,比如参考电压是否正确,ADC分辨率是否设置为12位。如果用户的配置不同,比如使用10位分辨率或不同的参考电压,结果会出错。 关于参数`ADC_HandleTypeDef *hadc`,需要解释这是一个指向ADC配置结构体的指针,包含了该ADC实例的所有配置信息,比如使用的ADC模块(ADC1, ADC2等)、通道、采样时间等。函数需要这个参数来知道操作哪个ADC外设,特别是在有多个ADC实例时尤为重要。 用户可能没有意识到,这个函数在多次调用时可能会有问题,比如如果前一次转换没有完成,或者ADC没有正确停止,可能会导致冲突。此外,连续启动ADC转换是否需要先停止之前的转换?HAL中的某些函数可能需要先调用停止再启动,否则会返回错误。 总结下来,用户提供的函数有几个潜在问题: 1. 缺少等待转换完成的步骤,导致可能读取旧数据。 2. 假设了特定的参考电压和分辨率,可能需要根据实际硬件调整。 3. 参数`hadc`的正确传递和使用,确保操作正确的ADC实例。 在回答中,需要逐步分解函数,解释每部分的作用,指出潜在问题,并给出改进建议。同时,结合用户之前的问题,强调是否需要中断或轮询来确保转换完成,以确保数据准确性。此外,解释参数的意义和结构体的内容,帮助用户理解如何正确使用和传递这个参数。</think>--- ### **函数解析:`double get_vol(ADC_HandleTypeDef *hadc)`** #### **1. 函数功能** 此函数用于 **通过 ADC 读取模拟信号并转换为电压值**,核心步骤如下: 1. **启动 ADC 转换**:`HAL_ADC_Start(hadc)` 2. **读取 ADC 原始值**:`HAL_ADC_GetValue(hadc)` 3. **转换为电压值**:`3.3 * adc_value / 4096` --- ### **2. 参数详解** #### **`ADC_HandleTypeDef *hadc`** - **作用**:指向 **ADC 外设配置结构体** 的指针,包含以下关键信息: - ADC 模块(如 ADC1、ADC2) - 通道配置(如采样通道、采样时间) - 工作模式(单次/连续转换、分辨率等) - **意义**: 通过此参数,函数可以操作 **指定的 ADC 外设**(例如多 ADC 系统中选择 ADC1 或 ADC2)。 --- ### **3. 代码逐行解析** #### **(1) `HAL_ADC_Start(hadc)`** - **功能**:启动 ADC 转换。 - **注意事项**: - 若 ADC 配置为 **单次转换模式**,此函数触发一次转换后自动停止。 - 若配置为 **连续转换模式**,ADC 会持续运行直到调用 `HAL_ADC_Stop`。 #### **(2) `uint32_t adc_value = HAL_ADC_GetValue(hadc)`** - **问题**: 直接调用 `HAL_ADC_GetValue` **可能读取无效数据**,因为未等待 ADC 转换完成。 - **改进建议**: 需在 `HAL_ADC_Start` 后添加 **等待转换完成** 的代码,例如: ```c if (HAL_ADC_PollForConversion(hadc, 100) == HAL_OK) { adc_value = HAL_ADC_GetValue(hadc); } ``` #### **(3) `return 3.3 * adc_value / 4096`** - **公式原理**: - **3.3V**:假设 ADC 参考电压为 3.3V(需与硬件实际电压一致)。 - **4096**:12 位 ADC 的分辨率(最大值为 $2^{12} - 1 = 4095$)。 - **计算**:电压 = 参考电压 × (原始值 / 最大分辨率)。 --- ### **4. 关键问题与改进** #### **问题 1:未等待转换完成** - **风险**:ADC 转换需要时间(通常几微秒到几十微秒),直接读取可能导致获取旧数据或随机值。 - **解决方案**: - **轮询模式**:添加 `HAL_ADC_PollForConversion`。 - **中断模式**:使用 `HAL_ADC_Start_IT` 并在中断回调中读取数据。 #### **问题 2:假设性参数** - **风险**:若实际硬件参考电压非 3.3V 或 ADC 分辨率非 12 位,计算结果会错误。 - **改进建议**: ```c #define VREF 3.3 // 根据实际参考电压修改 #define ADC_MAX 4095 // 根据实际分辨率修改(如 10 位为 1023) return VREF * adc_value / ADC_MAX; ``` #### **问题 3:未处理多通道情况** - **扩展性**:若需切换 ADC 通道,需在启动转换前配置通道: ```c ADC_ChannelConfTypeDef config = {0}; config.Channel = ADC_CHANNEL_X; // 指定通道号 config.Rank = 1; HAL_ADC_ConfigChannel(hadc, &config); ``` --- ### **5. 完整改进代码示例** ```c double get_vol(ADC_HandleTypeDef *hadc) { HAL_ADC_Start(hadc); // 启动转换 if (HAL_ADC_PollForConversion(hadc, 100) == HAL_OK) { // 等待转换完成 uint32_t adc_value = HAL_ADC_GetValue(hadc); return 3.3 * adc_value / 4096; // 转换为电压 } return -1; // 错误返回值(可自定义处理逻辑) } ``` --- ### **6. 总结** - **参数 `hadc`**:操作指定 ADC 外设的核心句柄,需确保其配置正确(通道、分辨率等)。 - **关键改进点**: - 添加转换完成等待逻辑。 - 根据硬件参数调整参考电压和分辨率。 - **扩展建议**: 若需高频采样或实时处理,建议使用 **中断模式** 或 **DMA 传输** 以提高效率。
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值