模拟数据是指在一定时间范围内连续变化的信号,该信号的取值可以是任意实数值。例如,声音、温度、压力等物理量在不同时间内的变化可以表示为模拟数据。在数字化处理中,需要将模拟数据转换为数字信号,以便于进行数字化处理和存储。
数字数据是指在一定时间范围内以离散形式表示的信号,该信号的取值只能是一组预定义的数字。例如,计算机中的数据、音频、视频等信号都是以数字形式表示的。在数字化处理中,模拟数据需要通过模数转换器(ADC)转换为数字数据。
模数转换是将模拟信号转换成数字信号的过程。它的基本原理是利用采样定理,将连续时间的模拟信号离散化,转换成一系列离散时间的采样数据。这个过程包括两个步骤:采样和量化。
采样是指在一定时间间隔内对模拟信号进行采样,将其转换为一系列数字信号。采样的时间间隔必须满足采样定理,以保证采样数据的完整性和准确性。
量化是指将采样得到的连续模拟信号转换为一系列离散的数字信号。量化的过程中,将模拟信号的幅度划分为若干个等间隔的电平,然后将每个电平对应的数字编码输出,即将模拟信号转换为数字信号。
采样:ADC的输入是模拟电压信号,为了将其转换为数字信号,需要先将模拟信号进行采样。在STM32F103单片机中,采样由外部模拟信号触发源触发,也可以由软件触发。
保持:采样后,需要将采样值保持在采样保持电容中,以便于后续的转换。在STM32F103单片机中,采样保持电容由程序配置。
转换:采样保持电容中的电压值被转换成数字量,同时进行12位的精度转换。在STM32F103单片机中,ADC可以进行单通道、扫描等模式的转换。
输出:转换完成后,数字量被存储在ADC数据寄存器中,可以由程序读取并进行后续处理
采样阶段:在ADC转换开始前,采样电路会将模拟信号进行采样,将其转化为数字信号,该阶段持续时间一般为12个时钟周期。
保持阶段:采样完成后,电荷保持电路会将采样的电荷保持在保持电容中,以保证转换过程中模拟信号的稳定性和准确性。该阶段持续时间一般为1.5个时钟周期。
转换阶段:保持电容中的电荷会被转化为数字信号,该阶段持续时间一般为12个时钟周期。
输出阶段:转换完成后,数字信号会被送至ADC数据寄存器中,等待CPU读取。
ADC的采样、保持和转换的时序是由ADC控制器内部的时序控制电路来控制的,这些时序参数可以通过相应的寄存器进行配置。
这也是最简单的一种ADC工作方式,主要就靠三个函数来完成
HAL_ADC_Start(&hadc1); // 启动ADC转换,假设使用的是ADC1
HAL_ADC_PollForConversion(&hadc1, 100); // 等待转换完成
uint32_t value = HAL_ADC_GetValue(&hadc1); // 获取ADC转换的值
使用STM32CubeMX软件来配置
自动生成的ADC配置代码
ADC_HandleTypeDef hadc1;
/* ADC1 init function */
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; //软件触发
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_10; //通道10
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(adcHandle->Instance==ADC1)
{
/* ADC1 clock enable */
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if(adcHandle->Instance==ADC1)
{
/* Peripheral clock disable */
__HAL_RCC_ADC1_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_0);
}
}
int main(void)
{
uint32_t value = 0; //定义一个value变量来接收AD转换的数据
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
HAL_ADC_Start(&hadc1);//1、开启采集与转换
while (1)
{
HAL_ADC_PollForConversion(&hadc1, 100); ///2、等待转换完成,超时时间100ms,
value = HAL_ADC_GetValue(&hadc1); ///3、获取转换结果,把结果赋值给value变量
//因为硬件电路就是一个采集电压的电路,电压与ADC的值转换公式value*3.3/4096
printf("adc_value = %.2f V:\n\r", value*3.3/4096);
HAL_Delay(1000); //一般1us左右转换就完事了,不加个延时函数,打印数据就太多了
}
}
轮询的工作方式,不管是通讯,还是ADC,都是不太好的,在单片机学习的过程中,轮询的工作方式只是方面理解原理,在实际的应用中最好还是用上中断或者DMA。
前面轮询的工作方式是:
1、开启采集与转换;
2、等待转换完成;
3、读取数据;
在等待转换完成的时候就是一直在那死等,程序控制上不应该这样,用中断,就是ADC转换完成之后,直接跳转到中断服务函数,你可以在中断服务函数中设置一个flag,或者直接在中断服务函数中就把数据给处理了(在数据量不大的情况下)。配置的过程也仅是增加了一个中断,其他的设置与轮询方式一样。
主要参与的函数
HAL_ADC_Start_IT(&hadc1);//开启ADC中断,ADC转换完成就到中断服务函数
//中断服务函数
void ADC1_2_IRQHandler(void)
{
HAL_ADC_IRQHandler(&hadc1);
}
//中断回调函数,被上面的中断服务函数所引用,在中断回调函数中就把转换完成标志置位
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
conv_flag = 1;
}
//获取结果还是要靠这个函数
HAL_ADC_GetValue(&hadc1);
配置中断,优先级就默认 0,0,实际综合性的工程中还是要根据项目实际来配置优先级的。
自动生成的ADC配置代码
#include "adc.h"
ADC_HandleTypeDef hadc1;
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_10;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(adcHandle->Instance==ADC1)
{
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/* ADC1 interrupt Init */
HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ADC1_2_IRQn); //使能中断,其他的与轮询没什么区别
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if(adcHandle->Instance==ADC1)
{
/* Peripheral clock disable */
__HAL_RCC_ADC1_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_0);
HAL_NVIC_DisableIRQ(ADC1_2_IRQn);
}
}
因为使用中断的方式进行ADC工作,转换完成之后就进入中断服务函数
//这个函数是自动生成的,只要ADC有中断就到这里了
void ADC1_2_IRQHandler(void)
{
HAL_ADC_IRQHandler(&hadc1);
}
//回调函数被上面的中断服务函数所调用,需要自己去写内容;
//可以在回调函数中直接取数据,或者处理数据(可以,单不推荐,中断服务函数要少多点事)
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
conv_flag = 1; //转换完成标志置1
//value = HAL_ADC_GetValue(&hadc1); // 获取转换结果,可以在回调函数中去数据
}
在main函数中取数据,处理数据(就是把数据打印一下,有其他的用途的就自己写代码去)
int main(void)
{
uint32_t value = 0; //定义一个变量来接收ADC的数据
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
HAL_ADC_Start_IT(&hadc1); //开启ADC采集与转换,中断的方式,转换完成就进入中断服务函数
while (1)
{
if(conv_flag == 1) //转换完成标志为1,就来取数据
{
conv_flag = 0; //把标志位清零
value = HAL_ADC_GetValue(&hadc1); // 获取转换结果
}
printf("adc_value = %.2f V:\n\r", value*3.3/4096);//打印数据
HAL_Delay(1000); //加个延时就是怕打印数据太频繁,控制工程中不要随便加延时函数,低效
}
}
STM32单片机的ADC采样支持定时器触发,就是你可以以固定的时间间隔来获得ADC的值,比如每5秒获取一下ADC的值。
ADC的配置如下
既然使用定时器来触发ADC采样,那么就需要设置一下定时器,你使用来了那个定时器,就去设置哪个定时器,本例程使用的是定时器3
使用CubeMX自动生成的代码
#include "adc.h"
ADC_HandleTypeDef hadc1;
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO; //定时器3触发输出时间
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_10;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(adcHandle->Instance==ADC1)
{
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if(adcHandle->Instance==ADC1)
{
__HAL_RCC_ADC1_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_0);
HAL_NVIC_DisableIRQ(ADC1_2_IRQn);
}
}
定时器部分的代码
#include "tim.h"
TIM_HandleTypeDef htim3;
void MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 7200;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 50000;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_ENABLE();
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_DISABLE();
}
}
在main函数中开启定时器和ADC中断,就可以按照设置的时间间隔获取ADC的值,ADC的数据在中断回调函数中去获取。
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
MX_TIM3_Init();
HAL_TIM_Base_Start(&htim3); //开启定时器3
HAL_ADC_Start_IT(&hadc1);//开启ADC中断
while (1)
{
}
}
在中断回调函数中获取ADC的值
void ADC1_2_IRQHandler(void)
{
HAL_ADC_IRQHandler(&hadc1);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
uint32_t value = 0;
value = HAL_ADC_GetValue(&hadc1); // 获取转换结果
printf("adc_value = %.2f V:\n\r", value*3.3/4096);
}
4、DMA模式
使用ADC的DMA模式,就是让ADC一直在哪里采样转换,转换完成之后就把ADC的数据通过DMA直接送到目标位置,目标位置一般是一个数组
ADC的设置如下
DMA的设置如下
对应的配置代码
#include "adc.h"
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_10;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(adcHandle->Instance==ADC1)
{
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
hdma_adc1.Instance = DMA1_Channel1;
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(adcHandle,DMA_Handle,hdma_adc1);
HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if(adcHandle->Instance==ADC1)
{
__HAL_RCC_ADC1_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_0);
HAL_DMA_DeInit(adcHandle->DMA_Handle);
HAL_NVIC_DisableIRQ(ADC1_2_IRQn);
}
}
在main文件中添加部分代码
uint16_t adc_data[100]; //定义一个数组来接收DMA传输的数据
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adc_data,100);//把数据传导adc_data,传输100个数据
while (1)
{
}
/* USER CODE END 3 */
}
多通道的ADC数据传输后续添加,主要是硬件开发板上只有一个通道。