ADC
文章目录
- ADC
-
- 知识:
-
- 1、分辨率,精度,通道,模式,对齐
- 2、时钟,分频,采样时间,转换时间
- [3、adc 信号脚](#3、adc 信号脚)
- 4、ADC校准:
- STM32:
-
- [1、adc peripheral device init](#1、adc peripheral device init)
-
- 1.1、最简单模式,一次启动检测,一次读取
- 1.2、定时器触发ADC转换模式:使用定时器如1ms固定间隔去转换adc值
-
- [定时器3要素:1、先使能RCC,放初始化后面再使能会无效 2、初始化结构体,配置时间间隔 3、启动定时器:HAL_TIM_Base_Start(&htim1);,尤其1和3容易忽略,代码与DMA一起看](#定时器3要素:1、先使能RCC,放初始化后面再使能会无效 2、初始化结构体,配置时间间隔 3、启动定时器:HAL_TIM_Base_Start(&htim1);,尤其1和3容易忽略,代码与DMA一起看)
- 1.3、DMA模式
-
- [注意点:HAL_ADC_Start_DMA 启动后再启动HAL_ADC_Start(&hadc1);开始转换,否则DMA会不工作。](#注意点:HAL_ADC_Start_DMA 启动后再启动HAL_ADC_Start(&hadc1);开始转换,否则DMA会不工作。)
- 停止时也是先停止ADC再停止DMA。
- 问题点:
知识:
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,问题解决。