ESP32_IDF学习(8)--ADC
时间:2023-06-06 02:37:00
ADC
ADC通道
ESP32集成了2个SAR(逐次逼近寄存器)ADC,支持18个测量通道(模拟启用引脚)。
支持这些渠道:
ADC1:
- 8个通道:gpio32 - gpio39
ADC2:
- 10个通道:gpio0, gpio2, gpio4, gpio12 - gpio15, goio25 - gpio27
ADC衰减
Vref是ESP32 ADC测量输入电压的参考电压。ESP32 ADC可以测量从0 V到Vref模拟电压。在不同的芯片中,Vref中位数是1.1V。转换大于Vref的电压,输入电压在输入到ADC之前可以被衰减。衰减选项有四种,衰减越高,可测输入电压越高。
衰减 | 可测输入电压范围 |
---|---|
ADC_ATTEN_DB_0 |
100 mV ~ 950 mV |
ADC_ATTEN_DB_2_5 |
100 mV ~ 1250 mV |
ADC_ATTEN_DB_6 |
150 mV ~ 1750 mV |
ADC_ATTEN_DB_11 |
150 mV ~ 2450 mV |
ADC转换
ADC转换是将输入的模拟电压转换为数字值。ADC驱动器APls提供的ADC转换结果是原始数据。在单读模式下,ESP32 ADC原始结果的分辨率为12位。
adc1_get_raw()
adc2_get_raw()
要根据ADC本公式可用于计算电压的原始结果:
V o u t = D o u t ? V m a x / D m a x Vout=Dout*Vmax/Dmax Vout=Dout?Vmax/Dmax
其中
变量 | 作用 |
---|---|
Vout | 代表电压的数字输出结果。 |
Dout | ADC读取原始数字的结果 |
Vmax | 最大可测输入模拟电压 |
Dmax | 输出ADC原始数字读取结果的最大值为4095,连续读取模式为4095。 |
对于带有eFuse ADC可使用校准板esp_adc_cal_raw_to_voltage()
以获得校准的转换结果。这些结果代表实际电压(单位:mV)。这些数据不需要通过公式转换。如果没有eFuse ADC使用校准板ADC校准API,警告。
ADC的局限性
- ADC一些引脚被用作捆绑引脚(GPIO 0、2、15),不能随意使用。这就是下面官方开发的套件中的情况。
- ESP32 DevKitC:不能使用外部自动编程电路GPIO0。
- ESP-WROVER-KIT:由于外部连接的目的不同,不能使用GPIO 0、2、4和15。
- 由于ADC2模块也被Wi-Fi当它们一起使用时,只有一个可以获得优先权,这意味着
adc2_get_raw()
直到Wi-Fi停下来,反之亦然。
使用驱动程序
这两个ADC单元支持低频采样操作的单读模式。
未连接到任何信号的引脚ADC读数是随机的。
ADC单读模式
读数前要对ADC进行配置。
- 对于ADC通过调用函数
adc1_config_width()
和adc1_config_channel_atten()
配置所需的精度和衰减。 - 对于ADC2,通过
adc2_config_channel_atten()
配置衰减。ADC每次读数时都会配置2的读数宽度。
根据通道进行衰减配置,见adc1_channel_t
和adc2_channel_t
,作为上述函数的参数设置。
然后就可以用了adc1_get_raw()
和adc2_get_raw()
来读取ADC转换结果。ADC2的读宽应视为adc2_get_raw()
设置参数,而不是配置函数。
也可以调用专用函数
这个API提供了方便的方法来配置ADC1,以便从ULP读取数据。要做到这一点,需要调用函数adc1_ulp_enable()
,然后如上所述设置精度和衰减。
还有一个特定的函数adc_vref_to_gpio()
用于将内部参考电压传送到一个GPIO引脚。它在校准ADC读数时非常方便,这将在ADC校准部分讨论。
减少噪音
ESP32 ADC对噪声很敏感,导致ADC读数的巨大差异。根据使用情况,用户可以在使用中的ADC输入焊盘上连接一个旁路电容(例如,一个100nF的陶瓷电容),以尽量减少噪声。此外,也可以使用多采样来进一步减轻噪声的影响。
ADC校准
esp_adc_cal/include/esp_adc_cal.h API提供了一些函数来修正由于芯片之间ADC参考电压(Vref)的变化而导致的测量电压的差异。根据设计,ADC参考电压是1100mV,但是在不同的ESP32中,真正的参考电压可能在1000mV到1200mV之间。
使用该API纠正ADC读数涉及在给定的衰减下对其中一个ADC进行特征分析,以获得考虑到ADC参考电压差异的特性曲线(ADC-电压曲线)。特性曲线的形式是
y = c o e f f a ∗ x + c o e f f b y = coeff_a * x + coeff_b y=coeffa∗x+coeffb
,用于将ADC读数转换为mV的电压。特性曲线的计算基于校准值,这些校准值可以存储在eFuse中或由用户提供。
校准值
校准值用于生成特性曲线,以说明特定ESP32芯片的ADC参考电压的变化。目前,ESP32上有3个校准值的来源。这些校准值的可用性将取决于ESP32芯片/模块的类型和生产日期。
- 两点值代表每个ADC在150mV和850mV的读数。
BLOCK3
在工厂校准过程中测量这些值并将其烧入eFuse 。 - eFuse 参考电压表示真实的ADC参考电压。
BLOCK0
在工厂校准期间测量该值并将其烧入eFuse 。 - 默认参考电压是在表征过程中用户提供的ADC参考电压估计值作为参数。如果两点或eFuse Vref值不可用,则将使用默认参考电压。
eFuse Vref的单独测量和烧写已经适用于2018年第一周/之后生产的ESP32-D0WD和ESP32-D0WDQ6芯片。这类芯片可以通过日期代码在012018年/以后来识别(见下图的第4行)。
如果你想购买带校准的芯片或模块,请与分销商或Espressif直接确认。
如果你无法检查日期代码(即芯片可能被封装在罐装模块内,等等),你仍然可以通过运行带有adc_info参数的espefuse.py工具来验证eFuse Vref是否存在
$IDF_PATH/components/esptool_py/esptool/espefuse.py --port /COM8 adc_info (window)
$IDF_PATH/components/esptool_py/esptool/espefuse.py --port /dev/ttyUSB0 adc_info (Linux)
在window下用ESP32板的端口名称替换/dev/ttyUSB0。
一个有特定eFuse Vref值编程的芯片(在本例中为1093 mV)将被报告如下:
ADC VRef calibration: 1093 mV
在下面的另一个例子中,eFuse的Vref没有被编程:
ADC VRef calibration: None (1100 mV nominal)
对于一个有两点校准的芯片,信息看起来类似于:
ADC VRef calibration: 1149 mV
ADC readings stored in efuse BLK3:
ADC1 Low reading (150 mV): 306
ADC1 High reading (850 mV): 3153
ADC2 Low reading (150 mV): 389
ADC2 High reading (850 mV): 3206
执行.py脚本一般执行会直接退出无法看到输出值,故可以这样使用即可打印出输出信息:
python -i espefuse.py --port COM8 adc_info
详细代码
// ADC所接的通道 GPIO34 if ADC1 = ADC1_CHANNEL_6
#define ADC1_TEST_CHANNEL ADC1_CHANNEL_6
// ADC斜率曲线
static esp_adc_cal_characteristics_t *adc_chars;
// 参考电压
#define DEFAULT_VREF 3300 //使用adc2_vref_to_gpio()获得更好的估计值
void check_efuse(void)
{
//检查TP是否烧入eFuse
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
printf("eFuse Two Point: Supported\n");
} else {
printf("eFuse Two Point: NOT supported\n");
}
//检查Vref是否烧入eFuse
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
printf("eFuse Vref: Supported\n");
} else {
printf("eFuse Vref: NOT supported\n");
}
}
void adc_init(void)
{
adc1_config_width(ADC_WIDTH_BIT_12);// 12位分辨率
adc1_config_channel_atten(ADC1_TEST_CHANNEL, ADC_ATTEN_DB_11);// 电压输入衰减
adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t)); // 为斜率曲线分配内存
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars);
// print_char_val_type(val_type);
}
void app_main(void)
{
uint32_t read_raw;
check_efuse();
adc_init();
while(1){
read_raw = adc1_get_raw(ADC1_TEST_CHANNEL);// 采集ADC原始值//这里可以多次采样取平均值
uint32_t voltage = esp_adc_cal_raw_to_voltage(read_raw, adc_chars);//通过一条斜率曲线把读取adc1_get_raw()的原始数值转变成了mV
printf("ADC原始值: %d 转换电压值: %dmV\n", read_raw, voltage);
vTaskDelay(1000 / portTICK_RATE_MS);
}
}