1 ADC简介
1.1 ADC 简介
12 位 ADC 是逐次趋近型模数转换器。它具有多达 19 个复用通道,可测量来自 16 个外部 源、两个内部源和 VBAT 通道的信号。这些通道的 A/D 转换可在单次、连续、扫描或不连续 采样模式下进行。ADC 的结果存储在一个左对齐或右对齐的 16 位数据寄存器中。12位 16个通道可以用外部 3个用于内部电源 和VBAT(电池电压)
ADC 具有模拟看门狗特性,允许应用检测输入电压是否超过了用户自定义的阈值上限或下限。
1.2 主要特性
● 可配置 12 位、10 位、8 位或 6 位分辨率
● 在转换结束、注入转换结束以及发生模拟看门狗或溢出事件时产生中断
● 单次和连续转换模式
● 用于自动将通道 0 转换为通道“n”的扫描模式
● 数据对齐以保持内置数据一致性
● 可独立设置各通道采样时间
● 外部触发器选项,可为规则转换和注入转换配置极性
● 不连续采样模式
● 双重/三重模式(具有 2 个或更多 ADC 的器件提供)
● 双重/三重 ADC 模式下可配置的 DMA 数据存储
● 双重/三重交替模式下可配置的转换间延迟
● ADC 转换类型(参见数据手册)
● ADC 电源要求:全速运行时为 2.4 V 到 3.6 V,慢速运行时为 1.8 V
● ADC 输入范围:VREF—<= VIN <=VREF+
● 规则通道转换期间可产生 DMA 请求
注意: VREF— 如果可用(取决于封装),则必须将其连接到 VSSA。
VREF模拟电源参考电压
1.3ADC功能
1.3.1电压输入范围
ADC 输入范围为:VREF- ≤ VIN ≤ VREF+。由 VREF-、VREF+ 、VDDA 、VSSA、这四个外部引脚决定。
我们在设计原理图的时候一般把 VSSA 和 VREF- 接地,把 VREF+ 和 VDDA 接 3V3,得到 ADC 的输
入电压范围为:0~3.3V。
如果我们想让输入的电压范围变宽,去到可以测试负电压或者更高的正电压,我们可以在外部加
一个电压调理电路,把需要转换的电压抬升或者降压到 0~3.3V,这样 ADC 就可以测量了。
1.3.2 ADC 开关控制
可通过将 ADC_CR2 寄存器中的 ADON 位置 1 来为 ADC 供电。首次将 ADON 位置 1 时,
会将 ADC 从掉电模式中唤醒。
SWSTART 或 JSWSTART 位置 1 时,启动 AD 转换。
可通过将 ADON 位清零来停止转换并使 ADC 进入掉电模式。在此模式下,ADC 几乎不耗电
(只有几 μA)。
ADC_Cmd(BAT_ADC, ENABLE); //使能指定的 BAT_ADC就是开启这个位
1.3.3 ADC 时钟
ADC 具有两个时钟方案:
● 用于模拟电路的时钟:ADCCLK,所有 ADC 共用
此时钟来自于经可编程预分频器分频的 APB2 时钟,该预分频器允许 ADC 在 fPCLK2/2、
/4、/6 或 /8 下工作。有关 ADCCLK 的最大值,请参见数据手册。
● 用于数字接口的时钟(用于寄存器读/写访问)
此时钟等效于 APB2 时钟。可以通过 RCC APB2 外设时钟使能寄存器 (RCC_APB2ENR)
分别为每个 ADC 使能/禁止数字接口时钟。
常用数字时钟接口
1.3.4通道选择
有 16 条复用通道。可以将转换分为两组:规则转换和注入转换。每个组包含一个转换序列,
该序列可按任意顺序在任意通道上完成。例如,可按以下顺序对序列进行转换:ADC_IN3、
ADC_IN8、ADC_IN2、ADC_IN2、ADC_IN0、ADC_IN2、ADC_IN2、ADC_IN15。
● 一个规则转换组最多由 16 个转换构成。必须在 ADC_SQRx 寄存器中选择转换序列的规
则通道及其顺序。规则转换组中的转换总数必须写入 ADC_SQR1 寄存器中的 L[3:0] 位。
● 一个注入转换组最多由 4 个转换构成。必须在 ADC_JSQR 寄存器中选择转换序列的注入
通道及其顺序。注入转换组中的转换总数必须写入 ADC_JSQR 寄存器中的 L[1:0] 位。
如果在转换期间修改 ADC_SQRx 或 ADC_JSQR 寄存器,将复位当前转换并向 ADC 发送一
个新的启动脉冲,以转换新选择的组
规则组就是规矩按顺序来我们平常使用的就是规则组
注入组 注入可以理解为插入的有意思,如果在规则组进行转换时注入组开启转换,那么要等注入组转换完成后规则组再转换
转换顺序 根据SQR1的[3:0]位决定通道个数,通过配置SQR3~SQR1来确定转换顺序,比如通道16在第一个转换那么配置SQ1[4:0]为16 注意这是5bit
注入组转换
注入序列寄存器 JSQR 只有一个,最多支持 4 个通道,具体多少个由 JSQR 的 JL[2:0] 决定。如果
JL 的值小于 4 的话,则 JSQR 跟 SQR 决定转换顺序的设置不一样,第一次转换的不是 JSQR1[4:0],
而是 JCQRx[4:0] ,x = (4-JL),跟 SQR 刚好相反。如果 JL=00(1 个转换),那么转换的顺序是
从 JSQR4[4:0] 开始,而不是从 JSQR1[4:0] 开始,这个要注意,编程的时候不要搞错。当 JL 等于
4 时,跟 SQR 一样。
1.3.5 转换模式
单次转换模式
在单次转换模式下,ADC 执行一次转换。CONT 位为 0 时,可通过以下方式启动此模式:
将 ADC_CR2 寄存器中的 SWSTART 位置 1(仅适用于规则通道)
● 将 JSWSTART 位置 1(适用于注入通道)
● 外部触发(适用于规则通道或注入通道)
完成所选通道的转换之后:
● 如果转换了规则通道:
— 转换数据存储在 16 位 ADC_DR 寄存器中
— EOC(转换结束)标志置 1
— EOCIE 位置 1 时将产生中断
● 如果转换了注入通道:
— 转换数据存储在 16 位 ADC_JDR1 寄存器中
— JEOC(注入转换结束)标志置 1
— JEOCIE 位置 1 时将产生中断
然后,ADC 停止
连续转换模式
在连续转换模式下,ADC 结束一个转换后立即启动一个新的转换。CONT 位为 1 时,可通过
外部触发或将 ADC_CR2 寄存器中的 SWSTRT 位置 1 来启动此模式(仅适用于规则通道)。
每次转换之后:
● 如果转换了规则通道组:
— 上次转换的数据存储在 16 位 ADC_DR 寄存器中
— EOC(转换结束)标志置 1
— EOCIE 位置 1 时将产生中断
注意: 无法连续转换注入通道。连续模式下唯一的例外情况是,注入通道配置为在规则通道之后自
动转换(使用 JAUTO 位),请参见自动注入一节。
1.3.6 时序图
如图 35 所示,ADC 在开始精确转换之前需要一段稳定时间 tSTAB。ADC 开始转换并经过 15 个
时钟周期后,EOC 标志置 1,转换结果存放在 16 位 ADC 数据寄存器中。
1.3.7 模拟看门狗
如果 ADC 转换的模拟电压低于阈值下限或高于阈值上限,则 AWD 模拟看门狗状态位会置
1。这些阈值在 ADC_HTR 和 ADC_LTR 16 位寄存器的 12 个最低有效位中进行编程。可以
使用 ADC_CR1 寄存器中的 AWDIE 位使能中断。
阈值与 ADC_CR2 寄存器中的 ALIGN 位的所选对齐方式无关。在对齐之前,会将模拟电压
与阈值上限和下限进行比较。
表 49 介绍了应如何配置 ADC_CR1 寄存器才能在一个或多个通道上使能模拟看门狗。
1.3.8 扫描模式
此模式用于扫描一组模拟通道。
通过将 ADC_CR1 寄存器中的 SCAN 位置 1 来选择扫描模式。将此位置 1 后,ADC 会扫描
在 ADC_SQRx 寄存器(对于规则通道)或 ADC_JSQR 寄存器(对于注入通道)中选择的
所有通道。为组中的每个通道都执行一次转换。每次转换结束后,会自动转换该组中的下一
个通道。如果将 CONT 位置 1,规则通道转换不会在组中最后一个所选通道处停止,而是再
次从第一个所选通道继续转换。
如果将 DMA 位置 1,则在每次规则通道转换之后,均使用直接存储器访问 (DMA) 控制器将
转换自规则通道组的数据(存储在 ADC_DR 寄存器中)传输到 SRAM。
在以下情况下,ADC_SR 寄存器中的 EOC 位置 1:
● 如果 EOCS 位清零,在每个规则组序列转换结束时
● 如果 EOCS 位置 1,在每个规则通道转换结束时
从注入通道转换的数据始终存储在 ADC_JDRx 寄存器中。
扫描是將规则组或者注入组中的全部通道都进行转换。
相当于有单次扫描和循环扫描取决于CONT状态。
1.3.9 不连续采样模式
规则组
可将 ADC_CR1 寄存器中的 DISCEN 位置 1 来使能此模式。该模式可用于转换含有 n (n 8)
个转换的短序列,该短序列是在 ADC_SQRx 寄存器中选择的转换序列的一部分。可通过写
入 ADC_CR1 寄存器中的 DISCNUM[2:0] 位来指定 n 的值。
出现外部触发时,将启动在 ADC_SQRx 寄存器中选择的接下来 n 个转换,直到序列中的所
有转换均完成为止。通过 ADC_SQR1 寄存器中的 L[3:0] 位定义总序列长度。
示例:
n = 3,要转换的通道 = 0、1、2、3、6、7、9、10
第 1 次触发:转换序列 0、1、2
第 2 次触发:转换序列 3、6、7
第 3 次触发:转换序列 9、10 并生成 EOC 事件
第 4 次触发:转换序列 0、1、2
注意: 在不连续采样模式下转换规则组时,不会出现翻转。
转换完所有子组后,下一个触发信号将启动第一个子组的转换。在上述示例中,第 4 次触发
重新转换了第 1 个子组中的通道 0、1 和 2。
注入组
可将 ADC_CR1 寄存器中的 JDISCEN 位置 1 来使能此模式。在出现外部触发事件之后,可
使用该模式逐通道转换在 ADC_JSQR 寄存器中选择的序列。
出现外部触发时,将启动在 ADC_JSQR 寄存器中选择的下一个通道转换,直到序列中的所
有转换均完成为止。通过 ADC_JSQR 寄存器中的 JL[1:0] 位定义总序列长度。
示例:
n = 1,要转换的通道 = 1、2、3
第 1 次触发:转换通道 1
第 2 次触发:转换通道 2
第 3 次触发:转换通道 3 并生成 EOC 和 JEOC 事件
第 4 次触发:通道 1
注意: 转换完所有注入通道后,下一个触发信号将启动第一个注入通道的转换。在上述示例中,
第 4 次触发重新转换了第 1 个注入通道。
不能同时使用自动注入和不连续采样模式。
不得同时为规则组和注入组设置不连续采样模式。只能针对一个组使能不连续采样模式。
所谓不连续采样指的是指定每次采样通道个数n 比如n=3每次采样转换3个通道,转换的通道顺序转换,一次不连续转换完成后当第二次触发产生进行第二次三个通道的转换,以此类推直至所有通道转换完毕产生EOC事件当下一次触发来临继续第一次的3个通道转换
1.4 数据对齐
ADC_CR2 寄存器中的 ALIGN 位用于选择转换后存储的数据的对齐方式。可选择左对齐和
右对齐两种方式,
注入通道组的转换数据将减去 ADC_JOFRx 寄存器中写入的用户自定义偏移量,因此结果可
以是一个负值。SEXT 位表示扩展的符号值。
对于规则组中的通道,不会减去任何偏移量,因此只有十二个位有效。
规则组16位寄存器中只有12位有效
也就是说使用6位左对齐时按字节对齐低位补0 但我觉得这有问题不应该是高位补零吗没做过实验
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 时)
1.6 外部触发转换和触发极性
可以通过外部事件(例如,定时器捕获、EXTI 中断线)触发转换。如果 EXTEN[1:0] 控制位
(对于行规转换)或 JEXTEN[1:0] 位(对于注入转换)不等于“0b00”,则外部事件能够
以所选极性触发转换。
注意: 可以实时更改外部触发的极性。
EXTSEL[3:0] 和 JEXTSEL[3:0] 控制位用于从 16 个可能事件中选择可触发规则组转换和注
入组转换的事件。
可通过将 ADC_CR2 寄存器中的 SWSTART(对于规则转换)或 JSWSTART(对于注入转
换)位置 1 来产生软件源触发事件。
可通过注入触发中断规则组转换。
注意: 可以实时更改触发选择。不过,当更改触发选择时,会在 1 个 APB 时钟周期的时间范围内禁
止触发检测。这是为了避免在转换期间出现意外检测。
1.7 快速转换模式
可通过降低 ADC 分辨率来执行快速转换。RES 位用于选择数据寄存器中可用的位数。每种
分辨率的最小转换时间如下:
● 12 位:3 + 12 = 15 ADCCLK 周期
● 10 位:3 + 10 = 13 ADCCLK 周期
● 8 位:3 + 8 = 11 ADCCLK 周期
● 6 位:3 + 6 = 9 ADCCLK 周期
有关DMA我这里没用到呢等用到再写
先写一个历程
我用stm32的ADC输入监控供电电源电压当低于一定值时进行报警,这里只写ADC部分
void Adc_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
//1使用数字电路时钟时钟等于APB2总线时钟
RCC_APB2PeriphClockCmd(BAT_GPIO_CLK | BAT_ADC_CLK, ENABLE); //使能 BAT_ADC 通道时钟
//2模式设置为模拟输入挤不上啦也不下啦,这里不用配置AF功能
GPIO_InitStructure.GPIO_Pin = BAT_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(BAT_GPIO_PORT, &GPIO_InitStructure); //初始化 GPIOC.4
ADC_DeInit(); //复位 BAT_ADC,将外设 BAT_ADC 的全部寄存器重设为缺省值
//4转换模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //单通道模式 扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //单次转换模式
//5、触发方式
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止外部边沿触发
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; //转换由软件而不是外部触发启动随便设置一个
//6数据对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC 数据右对齐
//7通道4第一个转换
ADC_InitStructure.ADC_NbrOfConversion =1;//顺序进行规则转换的 ADC 通道的数目
//8.分辨率
ADC_InitStructure.ADC_Resolution=ADC_Resolution_12b;//分辨率
ADC_Init(BAT_ADC, &ADC_InitStructure); //根据指定的参数初始化外设 ADCx
//9、配置单重多重模式
ADC_CommonInitStructure.ADC_Mode= ADC_Mode_Independent; //ADC 独立模式
//10.DMA配置
ADC_CommonInitStructure.ADC_DMAAccessMode=ADC_DMAAccessMode_Disabled;
//11、时钟频率我认为这个是使用模拟时钟时会用到
ADC_CommonInitStructure.ADC_Prescaler= ADC_Prescaler_Div6;
//12、采样时间
ADC_CommonInitStructure.ADC_TwoSamplingDelay=ADC_TwoSamplingDelay_20Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
//13、使能ADC
ADC_Cmd(BAT_ADC, ENABLE); //使能指定的 BAT_ADC
}
// 获得 ADC 值, ch:通道值 0~3
static uint16_t Adc_Get(uint8_t ch)
{
uint16_t timeout = 1000;
//设置指定 ADC 的规则组通道,设置它们的转化顺序和采样时间
ADC_RegularChannelConfig(BAT_ADC, ch, 1, ADC_SampleTime_144Cycles);
//通道 1,规则采样顺序值为 1,采样时间为 144 周期
ADC_SoftwareStartConv(BAT_ADC); //使能软件转换功能
while (!ADC_GetFlagStatus(BAT_ADC, ADC_FLAG_EOC) && timeout--)
; //等待转换结束
return ADC_GetConversionValue(BAT_ADC); //返回最近一次 BAT_ADC 规则组的转换结果
}
// 获得 ADC 多次测量平均值, ch:通道值 ; times:测量次数
uint16_t Adc_Get_Average(uint8_t ch, uint8_t times)
{
uint16_t temp_val = 0;
uint8_t t;
for (t = 0; t < times; t++)
{
temp_val += Adc_Get(ch);
}
if (times == 4)
{
temp_val = temp_val >> 2;//右移2位除以4 这里是整形数
}
else
{
temp_val = temp_val / times;
}
return temp_val;
}
// 获得测得原始电压值
float Adc_Get_Measure_Volotage(void)
{
uint16_t adcx;
float temp;
adcx = Adc_Get_Average(BAT_ADC_CH, 4);
temp = (float)adcx * (3.30f / 4096);
return temp;
}
// 获得实际电池分压前电压
float Adc_Get_Battery_Volotage(void)
{
float temp;
temp = Adc_Get_Measure_Volotage();
// 实际测量的值比计算得出的值低一点点。
temp = temp * 4.03f; //temp*(10+3.3)/3.3;
return temp;
}
// 查询电池电压状态,连续几秒读到低于9.6V返回0,高于9.6V返回1
uint8_t Bat_State(void)
{
if (g_bat_state)
{
Voltage_Z10 = (int) (Adc_Get_Battery_Volotage() * 10);
#if ENABLE_LOW_BATTERY_ALARM
if (Voltage_Z10 < 96)
{
Voltage_Low_Count++;
if(Voltage_Low_Count > BAT_CHECK_COUNT)
{
g_bat_state = 0;
}
}
else
{
Voltage_Low_Count = 0;
}
#endif
}
// DEBUG("BAT:%d, %d", g_bat_state, Voltage_Z10);
return g_bat_state;
}
关于if (Voltage_Z10 < 96) 比较用整型比较请参考下文
C语言布尔、整形、浮点、指针变量与”零值”比较的if语句