STM32基于HAL库的ADC精讲及详解
stm32 ADC 精讲(基于HAL库)
一.ADC的介绍
首先ADC是将模拟量信号转化为数字信号,简单来说就是把一些连续信号转化为 010101。
1.原理讲解
典型的ADC叫做逐次逼近型ADC,接下来我们来分模块讲解上述电路图
上图所示,是一个 电压比较器,将待测电压Vin输入到比较器的正端上去,当正端电压大于负端电压时,在输出端输出1,反之则输出0
然后将0/1送入到计数器中,0/1决定clk是增还是减,1是增,0是减,最后将计数的结果传给DAC,DAC会输出一个电压VDAC跟vin 去进行比较。
比如一开始的时候,比较器的”-”端电压是0 ,当待测电压输入进来的时候,此时,“﹢”端电压大于”-”端电压,就输出1 ,clk计数器就+1,VDAC的电压就逐渐增大,当VDC电压增大到与vin电压大概相等的时候,放大器的“﹢”端与“-”端的电压值谁大谁小就会交替变化,因此clk就会在+和-之间交替变化,最后达到一个稳定的状态,达到稳定之后再将计数器的值传给CPU
在上图中 ,Latch就是CPU,将数据传进去之后,经CPU处理,再将数据输出出来。
接下来我们来看一个动态的过程
通过上图我们不难发现,VADC的值上升到与 待测电压Vin 的值大小相等的时候,是需要一定的时间的,这里我们来引进一个新的概念,ADC 的转换时间。
2.ADC的转换时间
转换时间我们用Tc 表示,我们从ADC的转换原理可以知道VADC上升到与vin需要的一定的时间,这个时间我们记为Tc1,然后将计数器得到的数据传给CPU进行处理的时候又需要一定的时间,这个时间我们计为Td,因此,T = Tc1+Td。
二.STM32原理图讲解
1 .ADC的主要功能
- 最大位数为12位,分辨率为2的12次方,数据格式可以选为左对齐还是右对齐,(一般我们在配置之中都是选择右对齐)
- 转换模式分为独立跟多重,我们stm32之中不止有一个ADC通道,当选择两个以上的ADC通道时,则为多重模式,选择一个ADC通道时就是独立模式。
- 单次模式,触发一次,采样一次,连续模式,只需要触发一次即可。
- F429的ADC 的时钟一定是<= 36MHZ ,T min = 0.41us,实际上一般是达不到0.41us,比如我们的主频率是180MHZ , 我们ADC的时钟频率来自APB2,对其2分频得到PCLK2 = 90MHZ ,这个时候你只能对其进行4分频,这个时候,ADC的时钟频率就是22.5MHZ,如果你对其2分频的话,这个时候ADC的时钟频率就等于45MHZ,就不符合要求了。
- 2次采样时间要隔 5- 10 clk。
- 规则组跟注入组的区别, 规则组最多可以存16个通道,CH0 – CH15,但是数据寄存器只有一个,也就是说,数据会被覆盖掉,这个时候就只能存一个通道的数据了,那又什么办法解决没有呢,答案是有的,就是使用DMA 来进行数据转运,这个我们留到后面来讲,接下来我们来说一说注入组,注入组最多可以存4个通道。有上图可知,注入组的数据寄存器有4个,因此不用担心数据覆盖问题。然后注入组是类似于中断(一般少用)。
2.基本设计规则
在配置的时候,我们要在采样率跟分辨率之间做取舍。
ADCCLK 就是从APB2分频过来的比如我们在上面讲解ADC原理的时候,配置的ADC时钟是22.5MHZ ,这个时候,ADCCLK = 1 / 22.5 MHZ = 0.4444444444us,
Tsam可以在程序中进行配置 (后面配置时会细讲)。
![在这里插入图片描述](https://i3.wp.com/img-blog.csdnimg.cn/0bf10243ea1e4b9198a0c099cc016965.png
ADC单通道:
要求进行一次ADC转换:配置为单次模式使能,扫描模式失能。这样ADC的这个通道,转换一次后,就停止转换。
要求进行连续ADC转换:配置为连续模式使能,扫描模式失能。这样ADC的这个通道,转换一次后,接着进行下一次转换,不断连续。
ADC多通道:必须是扫描模式
要求进行一次ADC转换:配置为单次模式使能,扫描模式使能。这样ADC的多个通道,按照配置的顺序依次转换一次后,就停止转换。
要求进行连续ADC转换:配置为连续模式使能,扫描模式使能。这样ADC的多个通道,按照配置的顺序依次转换一次后,接着进行下一次转换,不断连续。
因此,得出结论:扫描模式只在多通道的条件下有效,来使得各个通道按照配置循序依次转换。而单次模式无论在单通道还是多通道下只对这些或者这个通道进行一次转换,连续模式无论是在单通道还是多通道下都对这些或者这个通道不断进行连续的转换。
3.ADC多重通道
1.交替模式,简单来说,就是开启多个ADC,对同一通道进行采样,则合格最大的好久就是可以提高采样率。由上图可知,采集三个adc数据的时间,比采集一个adc数据的时间只多大概1.5倍而已,大大提高了采样率。
2.同步模式,开启多个ADC对不同通道进行采样,得到多个采样值 ,具体的ADC IO口配置得参考数据手册。
4.DMA 的讲解
- DMA 是一个公用的数据转运工具 ,可用于spi ,uart, DA ,ADC .
- 是一个自动化的数据搬运工,不占用cpu 资源。
- 使用DMA 跟不使用DMA 的区别
不使用DMA 的时候,这个时候我们需要写一个运行程序给cpu让他去读取adc的值,这里我们取一个极端的情况,假如ADC的采样频率跟CPU的频率相等,这个时候CPU的运行周期跟ADC的周期就一样,这样子CPU 就只能一直在读取ADC的值了,干不了其他的事情。
使用DMA时 ,我们只需要在主函数初始化一下DMA ,在配置ADC的时候,将ADC转换后数据用DMA 转移到一个特定的区域,这样子,我们只需要配置一下CPU 对数据储存的程序即可。
三.cubemx的配置
1.ADC的三种工作模式
1)阻塞模式(也叫查询模式)
Step1 : 启用ADC
Step2: 等待EOC标志位
Step3:读取寄存器的数据 、<– 查询环节
缺点:占用cpu的使用率
主要使用到的函数
HAL_StatusTypeDef HAL_ADC_Start (ADC_HandleTypeDefhadc); //打开ADC的转换通道
HAL_StatusTypeDef HAL_ADC_Stop (ADC_HandleTypeDefhadc) //关闭ADC
HAL_StatusTypeDef HAL_ADC_PollForConversion (ADC_HandleTypeDef*hadc,uint32_t Timeout); // 查询函数,等着ADC 转换,查询转换的标志位,TImeout为超时时间,当超过这个时间时,直接退出函数,防止程序死机。
cubemx 的主要配置
- 时钟频率的配置,PCLK2 我配置的是90MHZ,因此,这里我设置的是四分频,出来的ADCCLK = 22.5MHZ(小于36即可)
- ADC采样时间的配置
这里我们配置 Sampling time = 56 cycles .
根据 Tadc = Sam + 12Tadcclk 可得 Tadc =(12 +56 Tadcclk = 68 * (1 / 22.5) ≈3us
主要代码
- cubemx 配置生成的ADC_Init 代码
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_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
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 = 1;
hadc1.Init.DMAContinuousRequests = DISABLE;
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_56CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
- get adc_value ADC转换得到的电压 Vadc = cpu得到的计数值 * 3.3 / 4096
float get_adc_value(void)
{
HAL_ADC_Start (&hadc1);//第一步,开启ADC
HAL_ADC_PollForConversion (&hadc1,50);// 查询函数,等着ADC 转换,查询转换的标志位.
if (HAL_IS_BIT_SET (HAL_ADC_GetState(&hadc1),HAL_ADC_STATE_REG_EOC)) //判断ADC是否转换完成,判断EOC标志位
{
return HAL_ADC_GetValue(&hadc1) * 3.3 /4096; // 获取ADC的值,并转换为对应的电压
}
else return 0;
}
- 主程序
int main(void)
{
/* USER CODE BEGIN 1 */
char text [20];
/* 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_ADC1_Init();
MX_FMC_Init();
/* USER CODE BEGIN 2 */
lcd_init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
adc_value = get_adc_value();
sprintf (text, "v = %f", adc_value );// 将adc_value打印成字符串存在text里
lcd_show_string(200,400,200,200,16,text,BLACK);//显示adc_value
HAL_Delay(0);
}
/* USER CODE END 3 */
}
现象
2).中断模式
Step1 : 启用ADC,使能中断
Step2: 等待EOC自动触发中断
Step3:在中断中读取寄存器的数据
主要函数
HAL_StatusTypeDef HAL_ADC_Start_IT (ADC_HandleTypeDefhadc) //使能ADC,打开中断标志位
HAL_StatusTypeDef HAL_ADC_Stop——IT (ADC_HandleTypeDefhadc)
HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)//回调函数
cubemx 的主要配置
打开NVIC中断使能标志
其他配置与查询模式一样
主要代码
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_ADC1_Init();
MX_FMC_Init();
/* USER CODE BEGIN 2 */
lcd_init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_ADC_Start_IT(&hadc1);
sprintf (tex11, "v = %f", adc_value1 );
lcd_show_string(200,400,200,200,16,tex11,BLACK);
}
/* USER CODE END 3 */
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)//回调函数
{
adc_value1 = HAL_ADC_GetValue(&hadc1) * 3.3 /4096;
}
3)DMA 模式
首先,说一下DMA 的工作模式,DMA 的工作模式一般分为正常模式跟循环模式,循环模式就是当你的DMA缓冲区填满之后还会自动回到起始地址,配置我们一般配置为循环模式。然后我们在使用DMA 的时候一般会涉及外设地址跟存储器地址,如图
cubemx的主要配置
这里我们配置了双通道。
step 1 :使能扫描转换模式(Scan Conversion Mode),使能连续转换模式(ContinuousConversion Mode)。
step 2 : ADC规则组选择转换通道数为2(Number Of Conversion)。
step 3 :配置Rank的输入通道。设置为连续传输模式,数据长度为字。
在配置的时候存储器地址自增,因为ADC1的地址只有一个,所以外设地址我们不自增,因为ADC我们配置的时候是12位,因此我们需要选择的 Data Width 为 half Word 也就是16位数据.因为是有两个通道,所以必须开启扫描模式,然后开启连续模式,这样子只需要触发一次,ADC就能不断的采集数据。
主要代码
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_DMA_Init();
MX_ADC1_Init();
MX_FMC_Init();
/* USER CODE BEGIN 2 */
lcd_init();
HAL_ADC_Start_DMA (&hadc1,(uint32_t *)adc_value,4);//因为此函数第二位必须是32位的指针,所以这里必须强制类型转换
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
get_adc_value();
sprintf (tex12, "v2 = %f", ad2 *3.3 / 4096 );
lcd_show_string(200,400,200,200,16,tex12,BLACK);
sprintf (tex11, "v1 = %f",ad1 *3.3 / 4096 );
lcd_show_string(200,300,200,200,16,tex11,BLACK);
}
/* USER CODE END 3 */
}
/*由于DMA采用了连续传输的模式,ADC采集到的数据会不断传到到存储器中(此处即为数组ADC_Value)。
ADC采集的数据从ADC_Value[0]一直存储到ADC_Value[99],然后采集到的数据又重新存储到ADC_Value[0],
一直到ADC_Value[99]。所以ADC_Value数组里面的数据会不断被刷新。这个过程中是通过DMA控制的,不需
要CPU参与。我们只需读取ADC_Value里面的数据即可得到ADC采集到的数据。其中ADC_Value[0]为通道0(PA0)
采集的数据,ADC_Value[1]为通道1(PA1)采集的数据,ADC_Value[2]为通道0采集的数据,如此类推。数组偶
数下标的数据为通道0采集数据,数组奇数下标的数据为通道1采集数据 , 在get_adc_value中 ,我们将通道0
跟通道1采集到的数据,除以50,达到滤波的效果*/
uint32_t get_adc_value()
{
for(i =0,ad1= 0,ad2= 0; i < 100 ; )
{
ad1+= adc_value[i++];
ad2 += adc_value[i++];
}
ad1= adc_value[0];
ad2 = adc_value[1];
lcd_show_num(1,1,ad2,6,16,BLACK);
lcd_show_num(1,20,ad1,6,16,BLACK);
ad1 /= 50;
ad2 /= 50;
}
现象
2、总结
1.查询模式:查询模式下,占用CPU时间较多,CPU效率较低。
2.中断模式:相比查询模式大大释放了CPU,提高了CPU的利用率。
3.DMA模式:该模式下基本不占用CPU,能直接将ADC采集的数据存储到存储器。