diff --git a/components/esp_adc_cal/Kconfig b/components/esp_adc_cal/Kconfig new file mode 100644 index 0000000000..fd7c93e564 --- /dev/null +++ b/components/esp_adc_cal/Kconfig @@ -0,0 +1,27 @@ +menu "ADC-Calibration" + +config ADC_CAL_EFUSE_TP_ENABLE + bool "Use Two Point Values" + default "y" + help + Some ESP32s have Two Point calibration values burned into eFuse BLOCK3. + This option will allow the ADC calibration component to characterize the + ADC-Voltage curve using Two Point values if they are available. + +config ADC_CAL_EFUSE_VREF_ENABLE + bool "Use eFuse Vref" + default "y" + help + Some ESP32s have Vref burned into eFuse BLOCK0. This option will allow + the ADC calibration component to characterize the ADC-Voltage curve using + eFuse Vref if it is available. + +config ADC_CAL_LUT_ENABLE + bool "Use Lookup Tables" + default "y" + help + This option will allow the ADC calibration component to use Lookup Tables + to correct for non-linear behavior in 11db attenuation. Other attenuations + do not exhibit non-linear behavior hence will not be affected by this option. + +endmenu # ADC-Calibration diff --git a/components/esp_adc_cal/esp_adc_cal.c b/components/esp_adc_cal/esp_adc_cal.c index d07c3792bd..d0748a018f 100644 --- a/components/esp_adc_cal/esp_adc_cal.c +++ b/components/esp_adc_cal/esp_adc_cal.c @@ -13,99 +13,410 @@ // limitations under the License. #include +#include "esp_types.h" #include "driver/adc.h" - +#include "soc/efuse_reg.h" +#include "esp_err.h" +#include "assert.h" #include "esp_adc_cal.h" -static const esp_adc_cal_lookup_table_t *table_ptrs[4] = {&esp_adc_cal_table_atten_0, - &esp_adc_cal_table_atten_1, - &esp_adc_cal_table_atten_2, - &esp_adc_cal_table_atten_3}; +/* ----------------------------- Configuration ------------------------------ */ -uint32_t get_adc_vref_from_efuse() +#ifdef CONFIG_ADC_CAL_EFUSE_TP_ENABLE +#define EFUSE_TP_ENABLED 1 +#else +#define EFUSE_TP_ENABLED 0 +#endif + +#ifdef CONFIG_ADC_CAL_EFUSE_VREF_ENABLE +#define EFUSE_VREF_ENABLED 1 +#else +#define EFUSE_VREF_ENABLED 0 +#endif + +#ifdef CONFIG_ADC_CAL_LUT_ENABLE +#define LUT_ENABLED 1 +#else +#define LUT_ENABLED 0 +#endif +/* + * By default, ESP32s that have Two Point values burned to BLOCK3 will also + * set the EFUSE_BLK3_PART_RESERVE flag to indicate this. However, some ESP32s + * with Two Point values do not set this flag. Set the following definition + * to 0 if this is the case. + */ +#define CHECK_BLK3_FLAG 1 +/* + * By default, Vref is burned into eFuse of ESP32s in Sign-Magnitude format. + * However some chips have Vref burned in two's complement format. Set the following + * definition to 1 if this is the case. + */ +#define VREF_FORMAT 0 + +/* ------------------------------ eFuse Access ----------------------------- */ + +#define BLK3_RESERVED_REG EFUSE_BLK0_RDATA4_REG + +#define VREF_REG EFUSE_BLK0_RDATA4_REG +#define VREF_MASK 0x1F +#define VREF_STEP_SIZE 7 +#define VREF_OFFSET 1100 + +#define TP_REG EFUSE_BLK3_RDATA3_REG +#define TP_LOW1_OFFSET 278 +#define TP_LOW2_OFFSET 421 +#define TP_LOW_MASK 0x7F +#define TP_LOW_VOLTAGE 150 +#define TP_HIGH1_OFFSET 3265 +#define TP_HIGH2_OFFSET 3406 +#define TP_HIGH_MASK 0x1FF +#define TP_HIGH_VOLTAGE 850 +#define TP_STEP_SIZE 4 + +/* ----------------------- Raw to Voltage Constants ------------------------- */ + +#define LIN_COEFF_A_SCALE 65536 +#define LIN_COEFF_A_ROUND (LIN_COEFF_A_SCALE/2) + +#define LUT_VREF_LOW 1000 +#define LUT_VREF_HIGH 1200 +#define LUT_ADC_STEP_SIZE 64 +#define LUT_POINTS 20 +#define LUT_LOW_THRESH 2880 +#define LUT_HIGH_THRESH (LUT_LOW_THRESH + LUT_ADC_STEP_SIZE) +#define ADC_12_BIT_RES 4096 + +#define ADC_CAL_CHECK(cond, ret) ({ \ + if(!(cond)){ \ + return ret; \ + } \ +}) + +/* ------------------------ Characterization Constants ---------------------- */ + +static const uint32_t adc1_tp_atten_scale[4] = {65504, 86975, 120389, 224310}; +static const uint32_t adc2_tp_atten_scale[4] = {65467, 86861, 120416, 224708}; +static const uint32_t adc1_tp_atten_offset[4] = {0, 1, 27, 54}; +static const uint32_t adc2_tp_atten_offset[4] = {0, 9, 26, 66}; + +static const uint32_t adc1_vref_atten_scale[4] = {57431, 76236, 105481, 196602}; +static const uint32_t adc2_vref_atten_scale[4] = {57236, 76175, 105678, 197170}; +static const uint32_t adc1_vref_atten_offset[4] = {75, 78, 107, 142}; +static const uint32_t adc2_vref_atten_offset[4] = {63, 66, 89, 128}; + +//20 Point lookup tables, covering ADC readings from 2880 to 4096, step size of 64 +static const uint32_t lut_adc1_low[LUT_POINTS] = {2240, 2297, 2352, 2405, 2457, 2512, 2564, 2616, 2664, 2709, + 2754, 2795, 2832, 2868, 2903, 2937, 2969, 3000, 3030, 3060}; +static const uint32_t lut_adc1_high[LUT_POINTS] = {2667, 2706, 2745, 2780, 2813, 2844, 2873, 2901, 2928, 2956, + 2982, 3006, 3032, 3059, 3084, 3110, 3135, 3160, 3184, 3209}; +static const uint32_t lut_adc2_low[LUT_POINTS] = {2238, 2293, 2347, 2399, 2451, 2507, 2561, 2613, 2662, 2710, + 2754, 2792, 2831, 2869, 2904, 2937, 2968, 2999, 3029, 3059}; +static const uint32_t lut_adc2_high[LUT_POINTS] = {2657, 2698, 2738, 2774, 2807, 2838, 2867, 2894, 2921, 2946, + 2971, 2996, 3020, 3043, 3067, 3092, 3116, 3139, 3162, 3185}; + +/* ----------------------- EFuse Access Functions --------------------------- */ + +static bool check_efuse_vref() { - //TODO: Replaced with read to eFuse once ATE confirms location of 5 bits - return 0; + //Check if Vref is burned in eFuse + return (REG_GET_FIELD(VREF_REG, EFUSE_RD_ADC_VREF) != 0) ? true : false; } -void esp_adc_cal_get_characteristics(uint32_t v_ref, +static bool check_efuse_tp() +{ + //Check if Two Point values are burned in eFuse + if (CHECK_BLK3_FLAG && (REG_GET_FIELD(BLK3_RESERVED_REG, EFUSE_RD_BLK3_PART_RESERVE) == 0)) { + return false; + } + //All TP cal values must be non zero + if ((REG_GET_FIELD(TP_REG, EFUSE_RD_ADC1_TP_LOW) != 0) && + (REG_GET_FIELD(TP_REG, EFUSE_RD_ADC2_TP_LOW) != 0) && + (REG_GET_FIELD(TP_REG, EFUSE_RD_ADC1_TP_HIGH) != 0) && + (REG_GET_FIELD(TP_REG, EFUSE_RD_ADC2_TP_HIGH) != 0)) { + return true; + } else { + return false; + } +} + +static inline int decode_bits(uint32_t bits, uint32_t mask, bool is_twos_compl) +{ + int ret; + if (bits & (~(mask >> 1) & mask)) { //Check sign bit (MSB of mask) + //Negative + if (is_twos_compl) { + ret = -(((~bits) + 1) & (mask >> 1)); //2's complement + } else { + ret = -(bits & (mask >> 1)); //Sign-magnitude + } + } else { + //Positive + ret = bits & (mask >> 1); + } + return ret; +} + +static uint32_t read_efuse_vref() +{ + //eFuse stores deviation from ideal reference voltage + uint32_t ret = VREF_OFFSET; //Ideal vref + uint32_t bits = REG_GET_FIELD(VREF_REG, EFUSE_ADC_VREF); + ret += decode_bits(bits, VREF_MASK, VREF_FORMAT) * VREF_STEP_SIZE; + return ret; //ADC Vref in mV +} + +static uint32_t read_efuse_tp_low(adc_unit_t adc_num) +{ + //ADC reading at 150mV stored in two's complement format + uint32_t ret; + uint32_t bits; + + if (adc_num == ADC_UNIT_1) { + ret = TP_LOW1_OFFSET; + bits = REG_GET_FIELD(TP_REG, EFUSE_RD_ADC1_TP_LOW); + } else { + ret = TP_LOW2_OFFSET; + bits = REG_GET_FIELD(TP_REG, EFUSE_RD_ADC2_TP_LOW); + } + ret += decode_bits(bits, TP_LOW_MASK, true) * TP_STEP_SIZE; + return ret; //Reading of ADC at 150mV +} + +static uint32_t read_efuse_tp_high(adc_unit_t adc_num) +{ + //ADC reading at 850mV stored in two's complement format + uint32_t ret; + uint32_t bits; + + if (adc_num == ADC_UNIT_1) { + ret = TP_HIGH1_OFFSET; + bits = REG_GET_FIELD(TP_REG, EFUSE_RD_ADC1_TP_HIGH); + } else { + ret = TP_HIGH2_OFFSET; + bits = REG_GET_FIELD(TP_REG, EFUSE_RD_ADC2_TP_HIGH); + } + ret += decode_bits(bits, TP_HIGH_MASK, true) * TP_STEP_SIZE; + return ret; //Reading of ADC at 850mV +} + +/* ----------------------- Characterization Functions ----------------------- */ + +static void characterize_using_two_point(adc_unit_t adc_num, + adc_atten_t atten, + uint32_t high, + uint32_t low, + uint32_t *coeff_a, + uint32_t *coeff_b) +{ + const uint32_t *atten_scales; + const uint32_t *atten_offsets; + + if (adc_num == ADC_UNIT_1) { //Using ADC 1 + atten_scales = adc1_tp_atten_scale; + atten_offsets = adc1_tp_atten_offset; + } else { //Using ADC 2 + atten_scales = adc2_tp_atten_scale; + atten_offsets = adc2_tp_atten_offset; + } + //Characterize ADC-Voltage curve as y = (coeff_a * x) + coeff_b + uint32_t delta_x = high - low; + uint32_t delta_v = TP_HIGH_VOLTAGE - TP_LOW_VOLTAGE; + //Where coeff_a = (delta_v/delta_x) * atten_scale + *coeff_a = (delta_v * atten_scales[atten] + (delta_x / 2)) / delta_x; //+(delta_x/2) for rounding + //Where coeff_b = high_v - ((delta_v/delta_x) * high_x) + atten_offset + *coeff_b = TP_HIGH_VOLTAGE - ((delta_v * high + (delta_x / 2)) / delta_x) + atten_offsets[atten]; +} + +static void characterize_using_vref(adc_unit_t adc_num, + adc_atten_t atten, + uint32_t vref, + uint32_t *coeff_a, + uint32_t *coeff_b) +{ + const uint32_t *atten_scales; + const uint32_t *atten_offsets; + + if (adc_num == ADC_UNIT_1) { //Using ADC 1 + atten_scales = adc1_vref_atten_scale; + atten_offsets = adc1_vref_atten_offset; + } else { //Using ADC 2 + atten_scales = adc2_vref_atten_scale; + atten_offsets = adc2_vref_atten_offset; + } + //Characterize ADC-Voltage curve as y = (coeff_a * x) + coeff_b + //Where coeff_a = (vref/4096) * atten_scale + *coeff_a = (vref * atten_scales[atten]) / (ADC_12_BIT_RES); + *coeff_b = atten_offsets[atten]; +} + +/* ------------------------ Conversion Functions --------------------------- */ + +static uint32_t calculate_voltage_linear(uint32_t adc_reading, uint32_t coeff_a, uint32_t coeff_b) +{ + //Where voltage = coeff_a * adc_reading + coeff_b + return (((coeff_a * adc_reading) + LIN_COEFF_A_ROUND) / LIN_COEFF_A_SCALE) + coeff_b; +} + +//Only call when ADC reading is above threshold +static uint32_t calculate_voltage_lut(uint32_t adc, uint32_t vref, const uint32_t *low_vref_curve, const uint32_t *high_vref_curve) +{ + //Get index of lower bound points of LUT + uint32_t i = (adc - LUT_LOW_THRESH) / LUT_ADC_STEP_SIZE; + + //Let the X Axis be Vref, Y axis be ADC reading, and Z be voltage + int x2dist = LUT_VREF_HIGH - vref; //(x2 - x) + int x1dist = vref - LUT_VREF_LOW; //(x - x1) + int y2dist = ((i + 1) * LUT_ADC_STEP_SIZE) + LUT_LOW_THRESH - adc; //(y2 - y) + int y1dist = adc - ((i * LUT_ADC_STEP_SIZE) + LUT_LOW_THRESH); //(y - y1) + + //For points for bilinear interpolation + int q11 = low_vref_curve[i]; //Lower bound point of low_vref_curve + int q12 = low_vref_curve[i + 1]; //Upper bound point of low_vref_curve + int q21 = high_vref_curve[i]; //Lower bound point of high_vref_curve + int q22 = high_vref_curve[i + 1]; //Upper bound point of high_vref_curve + + //Bilinear interpolation + //Where z = 1/((x2-x1)*(y2-y1)) * ( (q11*x2dist*y2dist) + (q21*x1dist*y2dist) + (q12*x2dist*y1dist) + (q22*x1dist*y1dist) ) + int voltage = (q11 * x2dist * y2dist) + (q21 * x1dist * y2dist) + (q12 * x2dist * y1dist) + (q22 * x1dist * y1dist); + voltage += ((LUT_VREF_HIGH - LUT_VREF_LOW) * LUT_ADC_STEP_SIZE) / 2; //Integer division rounding + voltage /= ((LUT_VREF_HIGH - LUT_VREF_LOW) * LUT_ADC_STEP_SIZE); //Divide by ((x2-x1)*(y2-y1)) + return (uint32_t)voltage; +} + +static inline uint32_t interpolate_two_points(uint32_t y1, uint32_t y2, uint32_t x_step, uint32_t x) +{ + //Interpolate between two points (x1,y1) (x2,y2) between 'lower' and 'upper' separated by 'step' + return ((y1 * x_step) + (y2 * x) - (y1 * x) + (x_step / 2)) / x_step; +} + +/* ------------------------- Public API ------------------------------------- */ + +esp_err_t esp_adc_cal_check_efuse(esp_adc_cal_value_t source) +{ + if (source == ESP_ADC_CAL_VAL_EFUSE_TP) { + return (check_efuse_tp()) ? ESP_OK : ESP_ERR_NOT_SUPPORTED; + } else if (source == ESP_ADC_CAL_VAL_EFUSE_VREF) { + return (check_efuse_vref()) ? ESP_OK : ESP_ERR_NOT_SUPPORTED; + } else { + return ESP_ERR_INVALID_ARG; + } +} + +esp_adc_cal_value_t esp_adc_cal_characterize(adc_unit_t adc_num, + adc_atten_t atten, + adc_bits_width_t bit_width, + uint32_t default_vref, + esp_adc_cal_characteristics_t *chars) +{ + //Check parameters + assert((adc_num == ADC_UNIT_1) || (adc_num == ADC_UNIT_2)); + assert(chars != NULL); + assert(bit_width < ADC_WIDTH_MAX); + + //Check eFuse if enabled to do so + bool efuse_tp_present = check_efuse_tp(); + bool efuse_vref_present = check_efuse_vref(); + esp_adc_cal_value_t ret; + + if (efuse_tp_present && EFUSE_TP_ENABLED) { + //Characterize based on Two Point values + uint32_t high = read_efuse_tp_high(adc_num); + uint32_t low = read_efuse_tp_low(adc_num); + characterize_using_two_point(adc_num, atten, high, low, &chars->coeff_a, &chars->coeff_b); + ret = ESP_ADC_CAL_VAL_EFUSE_TP; + } else if (efuse_vref_present && EFUSE_VREF_ENABLED) { + //Characterize based on eFuse Vref + uint32_t vref = read_efuse_vref(); + characterize_using_vref(adc_num, atten, vref, &chars->coeff_a, &chars->coeff_b); + ret = ESP_ADC_CAL_VAL_EFUSE_VREF; + } else { + //Characterized based on default Vref + characterize_using_vref(adc_num, atten, default_vref, &chars->coeff_a, &chars->coeff_b); + ret = ESP_ADC_CAL_VAL_DEFAULT_VREF; + } + + //Initialized remaining fields + chars->adc_num = adc_num; + chars->atten = atten; + chars->bit_width = bit_width; + chars->vref = (efuse_vref_present) ? read_efuse_vref() : default_vref; + //Initialize fields for lookup table if necessary + if (LUT_ENABLED && atten == ADC_ATTEN_DB_11) { + chars->low_curve = (adc_num == ADC_UNIT_1) ? lut_adc1_low : lut_adc2_low; + chars->high_curve = (adc_num == ADC_UNIT_1) ? lut_adc1_high : lut_adc2_high; + } else { + chars->low_curve = NULL; + chars->high_curve = NULL; + } + return ret; +} + +uint32_t esp_adc_cal_raw_to_voltage(uint32_t adc_reading, const esp_adc_cal_characteristics_t *chars) +{ + assert(chars != NULL); + + //Scale adc_rading if not 12 bits wide + adc_reading = (adc_reading << (ADC_WIDTH_BIT_12 - chars->bit_width)); + if (adc_reading > ADC_12_BIT_RES - 1) { + adc_reading = ADC_12_BIT_RES - 1; //Set to 12bit res max + } + + if (LUT_ENABLED && (chars->atten == ADC_ATTEN_DB_11) && (adc_reading >= LUT_LOW_THRESH)) { //Check if in non-linear region + //Use lookup table to get voltage in non linear portion of ADC_ATTEN_DB_11 + uint32_t lut_voltage = calculate_voltage_lut(adc_reading, chars->vref, chars->low_curve, chars->high_curve); + if (adc_reading <= LUT_HIGH_THRESH) { //If ADC is transitioning from linear region to non-linear region + //Linearly interpolate between linear voltage and lut voltage + uint32_t linear_voltage = calculate_voltage_linear(adc_reading, chars->coeff_a, chars->coeff_b); + return interpolate_two_points(linear_voltage, lut_voltage, LUT_ADC_STEP_SIZE, (adc_reading - LUT_LOW_THRESH)); + } else { + return lut_voltage; + } + } else { + return calculate_voltage_linear(adc_reading, chars->coeff_a, chars->coeff_b); + } +} + +esp_err_t esp_adc_cal_get_voltage(adc_channel_t channel, + const esp_adc_cal_characteristics_t *chars, + uint32_t *voltage) +{ + //Check parameters + ADC_CAL_CHECK(chars != NULL, ESP_ERR_INVALID_ARG); + ADC_CAL_CHECK(voltage != NULL, ESP_ERR_INVALID_ARG); + + int adc_reading; + if (chars->adc_num == ADC_UNIT_1) { + //Check channel is valid on ADC1 + ADC_CAL_CHECK((adc1_channel_t)channel < ADC1_CHANNEL_MAX, ESP_ERR_INVALID_ARG); + adc_reading = adc1_get_raw(channel); + } else { + //Check channel is valid on ADC2 + ADC_CAL_CHECK((adc2_channel_t)channel < ADC2_CHANNEL_MAX, ESP_ERR_INVALID_ARG); + if (adc2_get_raw(channel, chars->bit_width, &adc_reading) != ESP_OK) { + return ESP_ERR_TIMEOUT; //Timed out waiting for ADC2 + } + } + *voltage = esp_adc_cal_raw_to_voltage((uint32_t)adc_reading, chars); + return ESP_OK; +} + +/* ------------------------ Deprecated API --------------------------------- */ + +void esp_adc_cal_get_characteristics(uint32_t vref, adc_atten_t atten, adc_bits_width_t bit_width, esp_adc_cal_characteristics_t *chars) { - chars->v_ref = v_ref; - chars->table = table_ptrs[atten]; - chars->bit_width = bit_width; - if (v_ref >= ADC_CAL_LOW_V_REF) { - chars->gain = ((chars->v_ref - ADC_CAL_LOW_V_REF) - * chars->table->gain_m) - + chars->table->gain_c; - chars->offset = (((chars->v_ref - ADC_CAL_LOW_V_REF) - * chars->table->offset_m) - + chars->table->offset_c - + ((1 << ADC_CAL_OFFSET_SCALE) / 2)) - >> ADC_CAL_OFFSET_SCALE; //Bit shift to cancel 2^10 multiplier - chars->ideal_offset = (((ADC_CAL_IDEAL_V_REF - ADC_CAL_LOW_V_REF) - * chars->table->offset_m) - + chars->table->offset_c - + ((1 << ADC_CAL_OFFSET_SCALE) / 2)) //Rounding - >> ADC_CAL_OFFSET_SCALE; - } else { //For case where v_ref is smaller than low bound resulting in negative - chars->gain = chars->table->gain_c - - ((ADC_CAL_LOW_V_REF - chars->v_ref) - * chars->table->gain_m); - chars->offset = (chars->table->offset_c - - ((chars->v_ref - ADC_CAL_LOW_V_REF) - * chars->table->offset_m) - + ((1 << ADC_CAL_OFFSET_SCALE) / 2)) //Rounding - >> ADC_CAL_OFFSET_SCALE; //Bit shift to cancel 2^10 multiplier - chars->ideal_offset = (chars->table->offset_c - - ((ADC_CAL_IDEAL_V_REF - ADC_CAL_LOW_V_REF) - * chars->table->offset_m) - + ((1 << ADC_CAL_OFFSET_SCALE) / 2)) //Rounding - >> ADC_CAL_OFFSET_SCALE; - } -} - -static uint32_t esp_adc_cal_interpolate_round(uint32_t lower, uint32_t upper, - uint32_t step, uint32_t point) -{ - //Interpolate 'point' between 'lower' and 'upper' seperated by 'step' - return ((lower * step) - (lower * point) + (upper * point) + (step / 2)) / step; -} - -uint32_t esp_adc_cal_raw_to_voltage(uint32_t adc, - const esp_adc_cal_characteristics_t *chars) -{ - //Scale ADC to 12 bit width (0 to 4095) - adc <<= (ADC_WIDTH_BIT_12 - chars->bit_width); - uint32_t i = (adc >> chars->table->bit_shift); //find index for lut voltages - //Refernce LUT to obtain voltage using index - uint32_t voltage = esp_adc_cal_interpolate_round(chars->table->voltage[i], - chars->table->voltage[i + 1], - (1 << chars->table->bit_shift), - adc - (i << chars->table->bit_shift)); - /* - * Apply Gain, scaling(bit shift) and offset to interpolated voltage - * v_true = (((v_id - off_id)*gain)*scaling) + off_true - */ - if (voltage > chars->ideal_offset) { - voltage = (voltage - chars->ideal_offset) * chars->gain; - voltage += (1 << ADC_CAL_GAIN_SCALE) / 2; //For rounding when scaled - voltage >>= ADC_CAL_GAIN_SCALE; - voltage += chars->offset; - } else { //For case where voltage is less than ideal offset leading to negative value - voltage = ((chars->ideal_offset - voltage) * chars->gain); - voltage += (1 << ADC_CAL_GAIN_SCALE) / 2; //For rounding when scaled - voltage >>= ADC_CAL_GAIN_SCALE; - voltage = chars->offset - voltage; - } - - return voltage; + assert(chars != NULL); + esp_adc_cal_characterize(ADC_UNIT_1, atten, bit_width, vref, chars); } uint32_t adc1_to_voltage(adc1_channel_t channel, const esp_adc_cal_characteristics_t *chars) { - return esp_adc_cal_raw_to_voltage((uint32_t)adc1_get_raw(channel), chars); + assert(chars != NULL); + uint32_t voltage = 0; + esp_adc_cal_get_voltage((adc_channel_t)channel, chars, &voltage); + return voltage; } + diff --git a/components/esp_adc_cal/esp_adc_cal_lookup_tables.c b/components/esp_adc_cal/esp_adc_cal_lookup_tables.c deleted file mode 100644 index 111f75c4b0..0000000000 --- a/components/esp_adc_cal/esp_adc_cal_lookup_tables.c +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "esp_adc_cal.h" - -/** - * Mean error of 219 modules: 3.756418mV - * Max error of 219 modules: 26.314087mV - * Mean of max errors of 219 modules: 7.387282mV - */ -const esp_adc_cal_lookup_table_t esp_adc_cal_table_atten_0 = { - .gain_m = 56, - .gain_c = 59928, - .offset_m = 91, - .offset_c = 52798, - .bit_shift = 7, - .voltage = { - 54, 90, 120, 150, 180, 209, 241, 271, - 301, 330, 360, 391, 421, 450, 480, 511, - 541, 571, 601, 630, 660, 690, 720, 750, - 780, 809, 839, 870, 900, 929, 959, 988, - 1018 - } -}; - -/** - * Mean error of 219 modules: 4.952441mV - * Max error of 219 modules: 38.235321mV - * Mean of max errors of 219 modules: 9.718749mV - */ -const esp_adc_cal_lookup_table_t esp_adc_cal_table_atten_1 = { - .gain_m = 57, - .gain_c = 59834, - .offset_m = 108, - .offset_c = 54733, - .bit_shift = 7, - .voltage = { - 60, 102, 143, 184, 223, 262, 303, 343, - 383, 423, 463, 503, 543, 583, 623, 663, - 703, 742, 782, 823, 862, 901, 942, 981, - 1022, 1060, 1101, 1141, 1180, 1219, 1259, 1298, - 1338 - } -}; - -/** - * Mean error of 219 modules: 6.793558mV - * Max error of 219 modules: 51.435440mV - * Mean of max errors of 219 modules: 13.083121mV - */ -const esp_adc_cal_lookup_table_t esp_adc_cal_table_atten_2 = { - .gain_m = 56, - .gain_c = 59927, - .offset_m = 154, - .offset_c = 71995, - .bit_shift = 7, - .voltage = { - 82, 138, 194, 250, 305, 360, 417, 473, - 529, 584, 639, 696, 751, 806, 861, 917, - 971, 1026, 1081, 1136, 1192, 1246, 1301, 1356, - 1411, 1466, 1522, 1577, 1632, 1687, 1743, 1799, - 1855 - } -}; - -/** - * Mean error of 219 modules: 13.149460mV - * Max error of 219 modules: 97.102951mV - * Mean of max errors of 219 modules: 35.538924mV - */ -const esp_adc_cal_lookup_table_t esp_adc_cal_table_atten_3 = { - .gain_m = 33, - .gain_c = 62214, - .offset_m = 610, - .offset_c = 108422, - .bit_shift = 7, - .voltage = { - 110, 221, 325, 430, 534, 637, 741, 845, - 947, 1049, 1153, 1256, 1358, 1461, 1565, 1670, - 1774, 1878, 1983, 2088, 2192, 2293, 2393, 2490, - 2580, 2665, 2746, 2820, 2885, 2947, 3007, 3060, - 3107 - } -}; - diff --git a/components/esp_adc_cal/include/esp_adc_cal.h b/components/esp_adc_cal/include/esp_adc_cal.h index adb6598496..af611357b4 100644 --- a/components/esp_adc_cal/include/esp_adc_cal.h +++ b/components/esp_adc_cal/include/esp_adc_cal.h @@ -20,131 +20,130 @@ extern "C" { #endif #include +#include "esp_err.h" #include "driver/adc.h" -/** @cond */ -#define ADC_CAL_GAIN_SCALE 16 -#define ADC_CAL_OFFSET_SCALE 10 - -#define ADC_CAL_IDEAL_V_REF 1100 //In mV -#define ADC_CAL_LOW_V_REF 1000 -#define ADC_CAL_HIGH_V_REF 1200 -#define ADC_CAL_MIN 0 -#define ADC_CAL_MAX 4095 -/** @endcond */ +/** + * @brief Type of calibration value used in characterization + */ +typedef enum { + ESP_ADC_CAL_VAL_EFUSE_VREF = 0, /**< Characterization based on reference voltage stored in eFuse*/ + ESP_ADC_CAL_VAL_EFUSE_TP = 1, /**< Characterization based on Two Point values stored in eFuse*/ + ESP_ADC_CAL_VAL_DEFAULT_VREF = 2, /**< Characterization based on default reference voltage*/ +} esp_adc_cal_value_t; /** - * @brief Structure storing Lookup Table + * @brief Structure storing characteristics of an ADC * - * The Lookup Tables (LUT) of a given attenuation contains 33 equally spaced - * points. The Gain and Offset curves are used to find the appopriate gain and - * offset factor given a reference voltage v_ref. - * - * @note A seperate LUT is provided for each attenuation and are defined in - * esp_adc_cal_lookup_tables.c + * @note Call esp_adc_cal_characterize() to initialize the structure */ typedef struct { - uint32_t gain_m; /**> EFUSE_RD_XPD_SDIO_REG_S; result.tieh = (efuse_reg & EFUSE_RD_SDIO_TIEH_M) >> EFUSE_RD_SDIO_TIEH_S; - // in this case, DREFH/M/L are also set from EFUSE - result.drefh = (efuse_reg & EFUSE_RD_SDIO_DREFH_M) >> EFUSE_RD_SDIO_DREFH_S; - result.drefm = (efuse_reg & EFUSE_RD_SDIO_DREFM_M) >> EFUSE_RD_SDIO_DREFM_S; - result.drefl = (efuse_reg & EFUSE_RD_SDIO_DREFL_M) >> EFUSE_RD_SDIO_DREFL_S; + //DREFH/M/L eFuse are used for EFUSE_ADC_VREF instead. Therefore tuning + //will only be available on older chips that don't have EFUSE_ADC_VREF + if(REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG ,EFUSE_RD_BLK3_PART_RESERVE) == 0){ + //BLK3_PART_RESERVE indicates the presence of EFUSE_ADC_VREF + // in this case, DREFH/M/L are also set from EFUSE + result.drefh = (efuse_reg & EFUSE_RD_SDIO_DREFH_M) >> EFUSE_RD_SDIO_DREFH_S; + result.drefm = (efuse_reg & EFUSE_RD_SDIO_DREFM_M) >> EFUSE_RD_SDIO_DREFM_S; + result.drefl = (efuse_reg & EFUSE_RD_SDIO_DREFL_M) >> EFUSE_RD_SDIO_DREFL_S; + } return result; } diff --git a/docs/_static/adc-noise-graph.jpg b/docs/_static/adc-noise-graph.jpg new file mode 100644 index 0000000000..3220c2464f Binary files /dev/null and b/docs/_static/adc-noise-graph.jpg differ diff --git a/docs/_static/adc-vref-graph.jpg b/docs/_static/adc-vref-graph.jpg new file mode 100644 index 0000000000..4be77bf8b5 Binary files /dev/null and b/docs/_static/adc-vref-graph.jpg differ diff --git a/docs/api-reference/peripherals/adc.rst b/docs/api-reference/peripherals/adc.rst index 3bad7b24b8..fa6a6497c9 100644 --- a/docs/api-reference/peripherals/adc.rst +++ b/docs/api-reference/peripherals/adc.rst @@ -85,56 +85,87 @@ The value read in both these examples is 12 bits wide (range 0-4095). .. _adc-api-adc-calibration: +Minimizing Noise +---------------- + +The ESP32 ADC can be sensitive to noise leading to large discrepancies in ADC readings. To minimize noise, users may connect a 0.1uF capacitor to the ADC input pad in use. Multisampling may also be used to further mitigate the effects of noise. + +.. figure:: ../../_static/adc-noise-graph.jpg + :align: center + :alt: ADC noise mitigation + + Graph illustrating noise mitigation using capacitor and multisampling of 64 samples. + ADC Calibration --------------- -The :component_file:`esp_adc_cal/include/esp_adc_cal.h` API provides functions to correct for differences in measured voltages caused by non-ideal ADC reference voltages in ESP32s. The ideal ADC reference voltage is 1100 mV however the reference voltage of different ESP32s can range from 1000 mV to 1200 mV. +The :component_file:`esp_adc_cal/include/esp_adc_cal.h` API provides functions to correct for differences in measured voltages caused by variation of ADC reference voltages (Vref) between chips. Per design the ADC reference voltage is 1100mV, however the true reference voltage can range from 1000mV to 1200mV amongst different ESP32s. -Correcting the measured voltage using this API involves referencing a lookup table of voltages. The voltage obtained from the lookup table is then scaled and shifted by a gain and offset factor that is based on the ADC's reference voltage. This is done with function :cpp:func:`esp_adc_cal_get_characteristics`. +.. figure:: ../../_static/adc-vref-graph.jpg + :align: center + :alt: ADC reference voltage comparison + + Graph illustrating effect of differing reference voltages on the ADC voltage curve. -The reference voltage of the ADCs can be routed to certain GPIOs and measured manually using the ADC driver’s :cpp:func:`adc2_vref_to_gpio` function. +Correcting ADC readings using this API involves characterizing one of the ADCs at a given attenuation to obtain a characteristics curve (ADC-Voltage curve) that takes into account the difference in ADC reference voltage. The characteristics curve is in the form of ``y = coeff_a * x + coeff_b`` and is used to convert ADC readings to voltages in mV. Calculation of the characteristics curve is based on calibration values which can be stored in eFuse or provided by the user. -Example of Reading Calibrated Values ------------------------------------- +Calibration Values +^^^^^^^^^^^^^^^^^^ -Reading the ADC and obtaining a result in mV:: +Calibration values are used to generate characteristic curves that account for the unique ADC reference voltage of a particular ESP32. There are currently three sources of calibration values. The availability of these calibration values will depend on the type and production date of the ESP32 chip/module. - #include - #include +**Two Point** values represent each of the ADCs’ readings at 150mV and 850mV. These values are measured and burned into eFuse ``BLOCK3`` during factory calibration. + +**eFuse Vref** represents the true ADC reference voltage. This value is measured and burned into eFuse ``BLOCK0`` during factory calibration. + +**Default Vref** is an estimate of the ADC reference voltage provided by the user as a parameter during characterization. If Two Point or eFuse Vref values are unavailable, **Default Vref** will be used. + +Application Example +^^^^^^^^^^^^^^^^^^^ + +For a full example see esp-idf: :example:`peripherals/adc` + +Characterizing an ADC at a particular attenuation:: + + #include "driver/adc.h" + #include "esp_adc_cal.h" ... - #define V_REF 1100 // ADC reference voltage - - // Configure ADC - adc1_config_width(ADC_WIDTH_12Bit); - adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_11db); - - // Calculate ADC characteristics i.e. gain and offset factors - esp_adc_cal_characteristics_t characteristics; - esp_adc_cal_get_characteristics(V_REF, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, &characteristics); - - // Read ADC and obtain result in mV - uint32_t voltage = adc1_to_voltage(ADC1_CHANNEL_6, &characteristics); - printf("%d mV\n",voltage); - - -Routing ADC reference voltage to GPIO, so it can be manually measured and entered in function :cpp:func:`esp_adc_cal_get_characteristics`:: + + //Characterize ADC at particular atten + esp_adc_cal_characteristics_t *adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t)); + esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars); + //Check type of calibration value used to characterize ADC + if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { + printf("eFuse Vref"); + } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { + printf("Two Point"); + } else { + printf("Default"); + } - #include - #include - #include +Reading an ADC then converting the reading to a voltage:: + #include "driver/adc.h" + #include "esp_adc_cal.h" + + ... + uint32_t reading = adc1_get_raw(ADC1_CHANNEL_5); + uint32_t voltage = esp_adc_cal_raw_to_voltage(reading, adc_chars); + +Routing ADC reference voltage to GPIO, so it can be manually measured (for **Default Vref**):: + + #include "driver/adc.h" + ... esp_err_t status = adc2_vref_to_gpio(GPIO_NUM_25); - if (status == ESP_OK){ + if (status == ESP_OK) { printf("v_ref routed to GPIO\n"); - }else{ + } else { printf("failed to route v_ref\n"); } -An example of using the ADC driver and obtaining calibrated measurements is available in esp-idf: :example:`peripherals/adc` - GPIO Lookup Macros ------------------ diff --git a/examples/peripherals/adc/main/adc1_example_main.c b/examples/peripherals/adc/main/adc1_example_main.c index eca1e4aa29..00de0ab8bf 100644 --- a/examples/peripherals/adc/main/adc1_example_main.c +++ b/examples/peripherals/adc/main/adc1_example_main.c @@ -10,42 +10,83 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/queue.h" #include "driver/gpio.h" #include "driver/adc.h" -#include "esp_system.h" #include "esp_adc_cal.h" -/*Note: Different ESP32 modules may have different reference voltages varying from - * 1000mV to 1200mV. Use #define GET_VREF to route v_ref to a GPIO - */ -#define V_REF 1100 -#define ADC1_TEST_CHANNEL (ADC1_CHANNEL_6) //GPIO 34 -//#define V_REF_TO_GPIO //Remove comment on define to route v_ref to GPIO +#define DEFAULT_VREF 1100 //Use adc2_vref_to_gpio() to obtain a better estimate +#define NO_OF_SAMPLES 64 //Multisampling -void app_main(void) +static esp_adc_cal_characteristics_t *adc_chars; +static const adc_channel_t channel = ADC_CHANNEL_6; //GPIO34 if ADC1, GPIO14 if ADC2 +static const adc_atten_t atten = ADC_ATTEN_DB_0; +static const adc_unit_t unit = ADC_UNIT_1; + +static void check_efuse() { -#ifndef V_REF_TO_GPIO - //Init ADC and Characteristics - esp_adc_cal_characteristics_t characteristics; - adc1_config_width(ADC_WIDTH_BIT_12); - adc1_config_channel_atten(ADC1_TEST_CHANNEL, ADC_ATTEN_DB_0); - esp_adc_cal_get_characteristics(V_REF, ADC_ATTEN_DB_0, ADC_WIDTH_BIT_12, &characteristics); - uint32_t voltage; - while(1){ - voltage = adc1_to_voltage(ADC1_TEST_CHANNEL, &characteristics); - printf("%d mV\n",voltage); + //Check TP is burned into 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"); + } + + //Check Vref is burned into 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"); + } +} + +static void print_char_val_type(esp_adc_cal_value_t val_type) +{ + if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { + printf("Characterized using Two Point Value\n"); + } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { + printf("Characterized using eFuse Vref\n"); + } else { + printf("Characterized using Default Vref\n"); + } +} + +void app_main() +{ + //Check if Two Point or Vref are burned into eFuse + check_efuse(); + + //Configure ADC + if (unit == ADC_UNIT_1) { + adc1_config_width(ADC_WIDTH_BIT_12); + adc1_config_channel_atten(channel, atten); + } else { + adc2_config_channel_atten((adc2_channel_t)channel, atten); + } + + //Characterize ADC + adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t)); + esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars); + print_char_val_type(val_type); + + //Continuously sample ADC1 + while (1) { + uint32_t adc_reading = 0; + //Multisampling + for (int i = 0; i < NO_OF_SAMPLES; i++) { + if (unit == ADC_UNIT_1) { + adc_reading += adc1_get_raw((adc1_channel_t)channel); + } else { + int raw; + adc2_get_raw((adc2_channel_t)channel, ADC_WIDTH_BIT_12, &raw); + adc_reading += raw; + } + } + adc_reading /= NO_OF_SAMPLES; + //Convert adc_reading to voltage in mV + uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars); + printf("Raw: %d\tVoltage: %dmV\n", adc_reading, voltage); vTaskDelay(pdMS_TO_TICKS(1000)); } -#else - //Get v_ref - esp_err_t status; - status = adc2_vref_to_gpio(GPIO_NUM_25); - if (status == ESP_OK){ - printf("v_ref routed to GPIO\n"); - }else{ - printf("failed to route v_ref\n"); - } - fflush(stdout); -#endif } + +