我们在使用STM32 的ADC采集外部的电压时,难免会遇到同时采集多路数据,今天就和大家分享一下,如何使用STM32cube MX同时采集多路ADC的数据。
一:方案设计目标:使用 ADC 外设转换多个通道。ADC 转换在扫描序列中连续执行。
为了方便调试,这里我们同时采集一路外部输入电压,两路内部ADC的电压;即ADC 配置为单转换模式,来自 SW 触发器。ADC 组常规(所有 STM32 器件的 ADC 上可用的默认组)的定序器配置为转换 3 个通道:1 个来自 GPIO 的通道,2 个内部通道:内部电压基准 VrefInt 和温度传感器。
为了不占用CPU资源,本节我们使用DMA的方式来采集ADC的数据;DMA 配置为以循环模式在 RAM 内存中以大小为 3 的元素数组(每个通道的转换数据一个数组地址)传输转换数据。
二:STM32cube MX 软件配置如下所示:
如上图所示:这里我们使用的通道分别是 ADC1的通道8、内部电压值和内部温度值;
这里为了方便使用其他的方法,采集ADC值,这里我们使用DMA的方式采集;DMA配置如下所示:
ADC配置代码如下所示:
hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; hadc1.Init.LowPowerAutoWait = DISABLE; hadc1.Init.LowPowerAutoPowerOff = DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.NbrOfConversion = 3; hadc1.Init.DiscontinuousConvMode = ENABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; hadc1.Init.DMAContinuousRequests = ENABLE; hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; hadc1.Init.SamplingTimeCommon1 = ADC_SAMPLETIME_79CYCLES_5; hadc1.Init.SamplingTimeCommon2 = ADC_SAMPLETIME_160CYCLES_5; hadc1.Init.OversamplingMode = DISABLE; hadc1.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_HIGH; if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); } /** Configure Regular Channel */ sConfig.Channel = ADC_CHANNEL_8; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_1; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } /** Configure Regular Channel */ sConfig.Channel = ADC_CHANNEL_VREFINT; sConfig.Rank = ADC_REGULAR_RANK_2; sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_2; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } /** Configure Regular Channel */ sConfig.Channel = ADC_CHANNEL_TEMPSENSOR; sConfig.Rank = ADC_REGULAR_RANK_3; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); }
这里我们需要配置一下DMA的软件代码:
if(hadc->Instance==ADC1) { /* USER CODE BEGIN ADC1_MspInit 0 */ /* USER CODE END ADC1_MspInit 0 */ /** Initializes the peripherals clocks */ PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC; PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_SYSCLK; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { Error_Handler(); } /* Peripheral clock enable */ __HAL_RCC_ADC_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**ADC1 GPIO Configuration PA4 ------> ADC1_IN8 */ GPIO_InitStruct.Pin = GPIO_PIN_4; 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 = DMA1_Channel1; hdma_adc1.Init.Request = DMA_REQUEST_ADC; 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_LOW; if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1); /* ADC1 interrupt Init */ HAL_NVIC_SetPriority(ADC_COMP1_2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(ADC_COMP1_2_IRQn);
主程序函数采集如下所示:
if(ubDmaTransferStatus == 1) { uhADCxConvertedData_VrefAnalog_mVolt = VDDA_APPLI; /* Computation of ADC conversions raw data to physical values */ /* using LL ADC driver helper macro. */ uhADCxConvertedData_VoltageGPIO_mVolt = __LL_ADC_CALC_DATA_TO_VOLTAGE(uhADCxConvertedData_VrefAnalog_mVolt, uhADCxConvertedData[0], LL_ADC_RESOLUTION_12B); uhADCxConvertedData_VrefInt_mVolt = __LL_ADC_CALC_DATA_TO_VOLTAGE(uhADCxConvertedData_VrefAnalog_mVolt, uhADCxConvertedData[1], LL_ADC_RESOLUTION_12B); hADCxConvertedData_Temperature_DegreeCelsius = __LL_ADC_CALC_TEMPERATURE(uhADCxConvertedData_VrefAnalog_mVolt, uhADCxConvertedData[2], LL_ADC_RESOLUTION_12B); temperate=(float)hADCxConvertedData_Temperature_DegreeCelsius*(3.3/4096); //电压值 temperate=(1.43-temperate)/0.0043+25; //转换为温度值 /* Update status variable of DMA transfer */ ubDmaTransferStatus = 0; /* Toggle LED 4 times */ tmp_index = 4*2; while(tmp_index != 0) { BSP_LED_Toggle(LED4); HAL_Delay(LED_BLINK_FAST); tmp_index--; } HAL_Delay(500); /* Delay to highlight toggle sequence */ }
ADC中断处理函数:当ADC数据转换完成,触发完成中断,在主程序内调用
/** * @brief DMA transfer complete callback * @note This function is executed when the transfer complete interrupt * is generated * @retval None */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { /* Update status variable of DMA transfer */ ubDmaTransferStatus = 1; }
整个项目执行过程解释如下:
ADC 每秒在每个转换开始时依次在扫描序列的 3 个通道之间执行一个通道转换(启用不连续模式)。
注意:如果禁用了不连续模式,则整个扫描序列将从一个转换开始转换为突发。
每个序列秩的 ADC 转换数据由 DMA 传输到数组 “uhADCxConvertedData” 中:
uhADCxConvertedData[0]:ADC 通道设置在序列秩 1 上(GPIO 作为模拟输入)
uhADCxConvertedData[1]:ADC 通道设置在序列秩 2 (VrefInt) 上
uhADCxConvertedData[2]:ADC 通道设置在序列秩 3(温度传感器)上
当 sequence 完成时,它从头开始: sequence 中的第一个通道,第一个数组地址中的数据传输(回滚)。
使用 LL ADC 驱动程序帮助程序宏将 ADC 转换原始数据计算为物理值:
连接到模拟电压电源 Vdda 的模拟参考电压值 (Vref+)(单位:mV)
GPIO 引脚上的电压值(其上映射为 ADC 通道,cf 引脚下方)(单位:mV)
内部电压基准 VrefInt 的值(单位:mV)
温度值(单位:摄氏度)
注: 模拟基准电压 (Vref+) 是根据内部电压基准 VrefInt 的 ADC 转换计算的,并用于计算其他转换数据。此电压应对应于文字 “VDDA_APPLI” 的值。当应用程序中的电压 Vref+ 值未知时,可以执行此过程。(在本例中,情况并非如此,因为目标板由 LDO 稳压器提供已知的恒定电压值“VDDA_APPLI”)。在 Vref+ 连接到 Vdd 的典型情况下,它允许推导出 Vdd 值。