目录
单个ADC框图(F4)
ADC的电源要求
ADC时钟
ADC通道
内部通道与外部通道
规则通道与注入通道
转换顺序
规则序列
注入序列
触发源
直接写寄存器
外部事件触发
转换时间
输入时钟
采样周期
转换时间
数据寄存器
电压转换
函数讲解
轮询模式
中断模式
DMA模式
其他函数
Analog-to-Digital Converter的缩写。指模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。
典型的模拟数字转换器将模拟信号转换为表示一定比例电压值的数字信号。
简单地说就是将模拟电压值,转换成对应的肉眼可读数值
12 位 ADC 是逐次趋近型模数转换器。它具有多达 19 个复用通道,可测量来自 16 个外部 源、两个内部源和 VBAT 通道的信号。这些通道的 A/D 转换可在单次、连续、扫描或不连续 采样模式下进行。ADC 的结果存储在一个左对齐或右对齐的 16 位数据寄存器中。 ADC 具有模拟看门狗特性,允许应用检测输入电压是否超过了用户自定义的阈值上限或下限。
(本文将不介绍看门狗的用法)
● ADC 电源要求:全速运行时为 2.4 V 到 3.6 V,慢速运行时为 1.8 V
● ADC 输入范围:Vref- <= Vin <= Vref+
ADC 具有两个时钟方案:
● 用于模拟电路的时钟:ADCCLK,所有 ADC 共用 此时钟来自于经可编程预分频器分频的 APB2 时钟,该预分频器允许 ADC 在 fPCLK2/2、 /4、/6 或 /8 下工作。有关 ADCCLK 的最大值,请参见数据手册。(每一种芯片的最大值不同)
● 用于数字接口的时钟(用于寄存器读/写访问) 此时钟等效于 APB2 时钟。可以通过 RCC APB2 外设时钟使能寄存器 (RCC_APB2ENR) 分别为每个 ADC 使能/禁止数字接口时钟。
关于ADC的通道,规则手册中已经明确的给出了标注
按照不同的分类方式,STM32的ADC通道可以有一下两种分类方式:
● 内部通道+外部通道
● 规则通道+注入通道
图:野火《库开发实战指南》
规则通道:顾名思意,规则通道就是很规矩的意思,我们平时一般使用的就是这个通 道,或者应该说我们用到的都是这个通道,没有什么特别要注意的可讲。
注入,可以理解为插入,插队的意思,是一种不安分的通道。它是一种在规则通道转 换的时候强行插入要转换的一种。如果在规则通道转换过程中,有注入通道插队,那么就 要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。这点跟中断 程序很像,都是不安分的主。所以,注入通道只有在规则通道存在时才会出现。
从数据手册中我们可以看到,STM32的ADC采集可以分为单通道模式和多通道模式。其中,如果我们采用的是单通道的模式,则不需要管理通道间的转换顺序。但是当我们设计多通道采集ADC电压时,便会涉及到ADC通道的转换顺序问题。那么,由上面的介绍,我们知道,这种情况下的转换顺序就有两种,即“规则序列”与“注入序列”。
规则序列,说白了就像排队一样,在寄存器中依次填入要参与转换的通道的名字。
● 规则组由多达16个转换组成。规则通道和它们的转换顺序在ADC_SQRx寄存器中选择。规 则组中转换的总数应写入ADC_SQR1寄存器的L[3:0]位中。
● 注入组由多达4个转换组成。注入通道和它们的转换顺序在ADC_JSQR寄存器中选择。注入 组里的转换总数目应写入ADC_JSQR寄存器的L[1:0]位中
需要注意的是,只有当JL=4的时候,注入通道的转换顺序才会按照JSQ1、JSQ2、JSQ3、JSQ4的顺序执行。当JL<4时,注入通道的转换顺序恰恰相反,也就是执行顺序为:JSQ4、JSQ3、JSQ2、JSQ1。
ADC有两种触发方式:直接控制寄存器的方式和外部事件触发的方式
ADC 转换可以由 ADC 控制寄存器 2: ADC_CR2 的 ADON 这个位来控制,写 1 的时候开始转换。写0的时候停止转换。
ADC 还支持外部事件触发转换,这个触发包括内部定时 器触发和外部 IO 触发。
触发源有很多,具体选择哪一种触发源,由 ADC 控制寄存器 2:ADC_CR2 的 EXTSEL[2:0]和 JEXTSEL[2:0]位来控制。EXTSEL[2:0]用于选择规则通道的 触发源,JEXTSEL[2:0]用于选择注入通道的触发源。选定好触发源之后,触发源是否要激 活,则由 ADC 控制寄存器 2:ADC_CR2 的 EXTTRIG 和 JEXTTRIG 这两位来激活。
如果使能了外部触发事件,我们还可以通过设置 ADC 控制寄存器 2:ADC_CR2 的 EXTEN[1:0]和 JEXTEN[1:0]来控制触发极性,可以有 4 种状态,分别是:禁止触发检测、 上升沿检测、下降沿检测以及上升沿和下降沿均检测。
对于转换时间的问题,ADC的每一次信号转换都要时间,这个时间就是转换时间,转换时间由输入时钟和采样周期来决定。
由于ADC在STM32中是挂载在APB2总线上的,所以ADC得时钟是由PCLK2(72MHz)经过分频得到的,分频因子由 RCC 时钟配置寄存器RCC_CFGR 的位 15:14 ADCPRE[1:0]设置,可以是 2/4/6/8 分频。
采样周期是确立在输入时钟上的,配置采样周期可以确定使用多少个ADC时钟周期来对电压进行采样,采样的周期数可通过 ADC采样时间寄存器 ADC_SMPR1 和 ADC_SMPR2 中的 SMP[2:0]位设置,ADC_SMPR2 控制的是通道 0~9, ADC_SMPR1 控制的是通道 10~17。每个通道可以配置不同的采样周期,但最小的采样周期是1.5个周期,也就是说如果想最快时间采样就设置采样周期为1.5。
ADC 会在数个 ADCCLK 周期内对输入电压进行采样,可使用 ADC_SMPR1 和 ADC_SMPR2 寄存器中的 SMP[2:0] 位修改周期数。每个通道均可以使用不同的采样时间进行采样。 总转换时间的计算公式如下: Tconv = 采样时间 + 12 个周期 示例: ADCCLK = 30 MHz 且采样时间 = 3 个周期时: Tconv = 3 + 12 = 15 个周期 = 0.5 μs(APB2 为 60 MHz 时)
转换时间=采样时间+12.5个周期
12.5个周期是固定的,一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us。
ADC的最高转换频率可以从数据受从中查到。
模拟电压经过 ADC 转换后,是一个相对精度的数字值,如果通过串口以 16 进制打印 出来的话,可读性比较差,那么有时候我们就需要把数字电压转换成模拟电压,也可以跟 实际的模拟电压(用万用表测)对比,看看转换是否准确。
我们一般在设计原理图的时候会把 ADC 的输入电压范围设定在:0~3.3v,如果设置 ADC 为 12 位的,那么 12 位满量程对应的就是 3.3V,12 位满量程对应的数字值是:2^12。
数值 0 对应的就是 0V。如果转换后的数值为 X ,X 对应的模拟电压为 Y,那么会有这么 一个等式成立:
HAL_StatusTypeDef HAL_ADC_Start (ADC_HandleTypeDef * hadc) // 开启
HAL_StatusTypeDef HAL_ADC_Stop (ADC_HandleTypeDef * hadc) // 关闭
HAL_StatusTypeDef HAL_ADC_PollForConversion (ADC_HandleTypeDef * hadc, uint32_t Timeout) // 循环等待转换结束
HAL_StatusTypeDef HAL_ADC_PollForEvent (ADC_HandleTypeDef * hadc, uint32_t EventType, uint32_t Timeout) // 中断回调函数
HAL_StatusTypeDef HAL_ADC_Start_IT (ADC_HandleTypeDef * hadc) // 开启中断
HAL_StatusTypeDef HAL_ADC_Stop_IT (ADC_HandleTypeDef * hadc) // 结束中断
void HAL_ADC_IRQHandler (ADC_HandleTypeDef * hadc) // 中断请求处理(调用中断回调函数)
HAL_StatusTypeDef HAL_ADC_Start_DMA (ADC_HandleTypeDef * hadc, uint32_t * pData, uint32_t Length) // 开启DMA
HAL_StatusTypeDef HAL_ADC_Stop_DMA (ADC_HandleTypeDef * hadc) // 结束DMA
void HAL_ADC_ConvCpltCallback (ADC_HandleTypeDef * hadc) // DMA模式下传输完成后的回调函数
void HAL_ADC_ConvHalfCpltCallback (ADC_HandleTypeDef * hadc) // DMA模式下传输一半时的回调函数
uint32_t HAL_ADC_GetValue (ADC_HandleTypeDef * hadc) // 获得该通道的ADC的结果
HAL_ADC_StateTypeDef HAL_ADC_GetState (ADC_HandleTypeDef * hadc) // 返回通道的状态
void HAL_ADC_ErrorCallback (ADC_HandleTypeDef * hadc) // 错误回调函数
typedef struct {
uint32_t ADC_Mode; // ADC 工作模式选择
FunctionalState ADC_ScanConvMode; // ADC 扫描(多通道)或者单次(单通道)模式选择
FunctionalState ADC_ContinuousConvMode; // ADC 单次转换或者连续转换选择
uint32_t ADC_ExternalTrigConv; // ADC 转换触发信号选择
uint32_t ADC_DataAlign; // ADC 数据寄存器对齐格式
uint8_t ADC_NbrOfChannel; // ADC 采集通道数
} ADC_InitTypeDef;
typedef struct {
ADC_TypeDef *Instance; /*寄存器基地址指针*/
ADC_InitTypeDef Init; /*ADC 初始化参数结构体*/
__IO uint32_t NbrOfCurrentConversionRank; /*正在转换序列的 ADC 数目 */
DMA_HandleTypeDef *DMA_Handle; /* DMA 处理程序指针 */
HAL_LockTypeDef Lock; /*ADC 锁定对象 */
__IO uint32_t State; /*ADC 通信状态*/
__IO uint32_t ErrorCode; /*ADC 错误码 */
} ADC_HandleTypeDef;
第一步,在usart.c中对printf和scanf进行重定向
第二步,首先在main.c文件中的/* USER CODE BEGIN PD */与/* USER CODE END PD */之间生命两个变量:
uint32_t ADC_ConvertedValue = 0;
float value = 0.0;
其中ADC_ConvertedValue为uint32_t类型,因为ADC的采样值为uint32_t。将value值声明为浮点类型
然后再while(1)循环中对ADC的值进行轮询
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("ADC测试\n");
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 1000);
ADC_ConvertedValue = HAL_ADC_GetValue(&hadc1);
value = (ADC_ConvertedValue * 3.3 / 4096);
printf("%.5f\n", value);
HAL_Delay(1000);
}
/* USER CODE END 3 */
用 HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc),因此只需重写该函数进行数据处理即可。