MCU中的ADC应用

ADC

文章目录

知识:

1、分辨率,精度,通道,模式,对齐

分辨率:可配置,stm320b0最高12bit

通道:可配置,看使用的哪个pin,配置哪个通道,stm320b0有19个通道,16个外部,3个内部

精度:

模式:single,continuous,sacn,discontinuous mode

扫描模式:当开始转换后,会根据ADC通道数量(ADC_InitTypeDef.ADC_NbrOfChannel) 按顺序进行N次转换,全部转换完成后设置 EOC(规则组转换结束) 标志位

非扫描模式:当开始转换后,仅会对规则组位置一的通道进行1次转换,转换完成设置 EOC 标志位

单次转换:在开始转换后,仅仅对规则组整组进行一次转换

连续转换:在开始转换后,会循环对规则组整组进行转换

间断模式:在开始转换后,进行 N 次转换后停下,并记录当前位置,当下次开始转换时按顺序下去。

复制代码
如果将ADC转换比喻为使用音乐软件听歌的话

ADC_RegularChannelConfig 就是为歌单增加歌曲并设置歌曲的序列
ADC_InitTypeDef.ADC_NbrOfChannel 就是歌单中歌曲的数量

扫描模式 就是 播放整个歌单的全部歌曲
非扫描模式 就是只播放歌单的第一首歌曲

单次转换 就是只播放一次 歌单中全部歌曲(扫描模式) / 歌单的第一首歌曲(非扫描模式)
连续转换 就是循环播放 歌单中全部歌曲(扫描模式) / 歌单的第一首歌曲(非扫描模式)

扫描模式&单次转换 = 歌曲中全部歌曲按顺序全部播放一次
非扫描模式&单次转换 = 只播放一次歌单的第一首歌曲
扫描模式&连续转换 = 列表循环
非扫描模式&连续转换 = 单曲循环

间断模式 就是一次听 N 首歌曲,并记下听到第几首了,下次接着听下去,当歌单全部歌曲听完后再回到第一首

对齐:可配置数据16bit存储是左对齐还是右对齐

启动:1、软件设置启动 2、硬件触发,定时器TRG触发或者GPIO pin input触发

2、时钟,分频,采样时间,转换时间

采样时间是闭合然后断开外部模拟信号的时间间隔,通过闭合和断开来给内部电容充电,电容的电量作为转换的电压。如1.5cycles就是1.5个时钟周期,闭合到断开的时间间隔是1.5个时钟周期,就是给电容充电的时间。

转换时间是adc的12bit的分辨率需要每个bit比较一次,需要12个时钟周期,+0.5个时钟周期,需要12.5个时钟周期来对内部的充电电容的电量进行逐次比较,最终比较出2边电压相等,bit数就是模拟转数字的值。

2个时钟源,一般选择ADC asynchronous clock

ADC conversion time: 0.4 µs for 12-bit resolution (2.5Msps), faster conversion times can be obtained by lowering resolution.

Programmable sampling time

Each channel can choose one out of two sampling times configured in SMP12:0 and

SMP22:0 bitfields, through SMPSELx bits in ADC_SMPR register.

The total conversion time is calculated as follows:

tCONV = Sampling time + 12.5 x ADC clock cycles

Example:

With ADC_CLK = 16 MHz and a sampling time of 1.5 ADC clock cycles:

tCONV = 1.5 + 12.5 = 14 ADC clock cycles = 0.875 µs

3、adc 信号脚

4、ADC校准:

ADC有一个内置的校准模式,能大幅减少因内部电容器组的变化而造成的准精度误差。因此建议每次上电后都执行一次校准。

在 Stm32F10xxx参考手册(2009中文版本) 中ADC章节有这样一句话:

启动校准前,ADC必须处于关电状态(ADON='0')超过至少两个ADC时钟周期

事实上,是ST公司的描写错误,而在官网中找到的 2021 版本中已经被更正为

原文:Before starting a calibration, the ADC must have been in power-on state (ADON bit = '1') for at least two ADC clock cycles.

翻译:在开始校准之前,ADC必须处于通电状态(ADON位="1") 至少两个ADC时钟周期。

STM32:

HAL_TIM_Base_Start(&htim1); 作用是闭合定时器的时基单元,开始计时。

HAL_ADC_Start(&hadc1); 作用是闭合打通外部触发信号,如果配置是软件触发,则触发一次软件触发。

1、adc peripheral device init

初始化3要素:时钟,adc结构体,gpio,或者加上dma

1.1、最简单模式,一次启动检测,一次读取
复制代码
ADC_HandleTypeDef hadc1;

/**
* @brief ADC MSP Initialization
* This function configures the hardware resources used in this example
* @param hadc: ADC handle pointer
* @retval None
*/
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
  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
    PA1     ------> ADC1_IN1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN ADC1_MspInit 1 */

  /* USER CODE END ADC1_MspInit 1 */
  }

}

/**
  * @brief ADC1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */

  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.LowPowerAutoPowerOff = DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc1.Init.SamplingTimeCommon1 = ADC_SAMPLETIME_1CYCLE_5;
  hadc1.Init.SamplingTimeCommon2 = ADC_SAMPLETIME_1CYCLE_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_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_1;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */
    HAL_ADC_MspInit(&hadc1);
  /* USER CODE END ADC1_Init 2 */

  ADC_Enable(&hadc1);
}

rt_uint32_t AdcMicLed_Get(void)
{
    HAL_ADC_Start(&hadc1);
    /* Wait for the ADC to convert */
    HAL_ADC_PollForConversion(&hadc1, 100);
    
    return (rt_uint32_t)HAL_ADC_GetValue(&hadc1);
}

extern rt_uint32_t AdcMicLed_Get(void);
void IAdcMicLed_thread_entry(void *parameter)
{
    rt_uint32_t adc_data = 0;
    while (1)
    {
        adc_data = AdcMicLed_Get();
        LOG_D("IAdcMicLed_thread_entry adc_data=%ld\r\n", adc_data);
        rt_thread_mdelay(RT_TICK_PER_SECOND*2);   
    }
}
测试可读
1.2、定时器触发ADC转换模式:使用定时器如1ms固定间隔去转换adc值
定时器3要素:1、先使能RCC,放初始化后面再使能会无效 2、初始化结构体,配置时间间隔 3、启动定时器:HAL_TIM_Base_Start(&htim1);,尤其1和3容易忽略,代码与DMA一起看
1.3、DMA模式

使用cubemx生成代码:

注意点:HAL_ADC_Start_DMA 启动后再启动HAL_ADC_Start(&hadc1);开始转换,否则DMA会不工作。
停止时也是先停止ADC再停止DMA。
复制代码
/**
  * @brief TIM1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM1_Init(void)
{

  /* USER CODE BEGIN TIM1_Init 0 */
    __HAL_RCC_TIM1_CLK_ENABLE();
  /* USER CODE END TIM1_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM1_Init 1 */

  /* USER CODE END TIM1_Init 1 */
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 64000-1;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 1000-1;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;//TIM_AUTORELOAD_PRELOAD_ENABLE;//TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */
  
    HAL_TIM_Base_Start(&htim1);
  /* USER CODE END TIM1_Init 2 */
}

/**
* @brief ADC MSP Initialization
* This function configures the hardware resources used in this example
* @param hadc: ADC handle pointer
* @retval None
*/
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
  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
    PA1     ------> ADC1_IN1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN ADC1_MspInit 1 */

  /* USER CODE END ADC1_MspInit 1 */
  }
}

static void MX_ADC1_DMA_Init(void)
{
    __HAL_RCC_DMA1_CLK_ENABLE();
    /* ADC1 DMA Init */
    /* ADC1 Init */
    hdma_adc1.Instance = DMA1_Channel1;
    hdma_adc1.Init.Request = DMA_REQUEST_ADC1;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc1.Init.MemInc = DMA_MINC_DISABLE;//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_MEDIUM;
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(&hadc1,DMA_Handle,hdma_adc1);
    /* DMA interrupt init */
    /* DMA1_Channel1_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
    
}

/**
  * @brief This function handles DMA1 channel 1 interrupt.
  */
void DMA1_Channel1_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel1_IRQn 0 */

  /* USER CODE END DMA1_Channel1_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_adc1);
  /* USER CODE BEGIN DMA1_Channel1_IRQn 1 */

  /* USER CODE END DMA1_Channel1_IRQn 1 */
}

/**
  * @brief ADC1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */
    MX_TIM1_Init();
  /* USER CODE END ADC1_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */

  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.LowPowerAutoPowerOff = DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;//ENABLE;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T1_TRGO2;//ADC_SOFTWARE_START;//ADC_EXTERNALTRIG_T1_TRGO2;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc1.Init.DMAContinuousRequests = ENABLE;//ENABLE;
  hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
  hadc1.Init.SamplingTimeCommon1 = ADC_SAMPLETIME_160CYCLES_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_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_1;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */
    HAL_ADC_MspInit(&hadc1);
  /* USER CODE END ADC1_Init 2 */
  MX_ADC1_DMA_Init();

  ADC_Enable(&hadc1);
  
//   HAL_ADC_Start(&hadc1);
//   HAL_ADCEx_Calibration_Start(&hadc1);
}

rt_uint32_t AdcMicLed_Get(void)
{
    HAL_ADC_Start(&hadc1);
    return 0;
    /* Wait for the ADC to convert */
//    HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);

//    return (rt_uint32_t)HAL_ADC_GetValue(&hadc1);
}

void AdcMicLed_DmaStart(uint32_t *pData, uint32_t Length)
{
    HAL_ADC_Start_DMA(&hadc1,pData,Length);
//    HAL_TIM_Base_Start(&htim1);
   HAL_ADC_Start(&hadc1);
    // /* Wait for the ADC to convert */
    // HAL_ADC_PollForConversion(&hadc1, 100);
}
复制代码

问题点:

1、为什么没有声音时读到的是1.6V左右

因为mic咪头输出的电压是上下波动的,有负压,一般为±100mv,ADC 在没有VREF-的情况下采集的都是正压,故外围电路会将给到ADC的电压偏置到正压,即将0V偏置到3.3V的一半,即1.65V,再加上放大电路,调整上下幅度到0-3.3V

2、定时器1ms间隔启动ADC采样发现取值都是满电压4095,采样时间延长至100ms会读到大概3000左右,延长至1s读到2100左右。或者说采集到的都是满电压4095,播放声音的时候测到低电压。

原因是硬件上的芯片脚位VREF+ 没有接3.3V,悬空了导致ADC没有参考电压。将VREF+接上VDDA,问题解决。

相关推荐
醉逍遥_祥1 小时前
Linux进程与NuttX任务(Linux Processes vs NuttX Tasks)
linux·单片机·嵌入式软件
xxwxx__1 小时前
51单片机电机控制全攻略:直流电机、舵机与步进电机
c语言·单片机·嵌入式硬件·51单片机
xuxie9911 小时前
start9 霍尔双相编码器
单片机
兆龙电子单片机设计11 小时前
【STM32项目开源】STM32单片机语音分类垃圾桶-蓝牙APP
stm32·单片机·嵌入式硬件·物联网·开源·自动化·毕业设计
零涂毕业设计12 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
阿龙的工作记录14 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
ye1501277745514 小时前
220V转5V1A隔离芯片WT5602
单片机·嵌入式硬件·其他·硬件工程
武汉万象奥科14 小时前
AGV导航控制器方案 | 基于HD-RK3576系列核心板的算控融合一体化平台
arm开发·嵌入式硬件·arm核心板·单板机