| 
									
										
										
										
											2022-07-15 12:52:44 +08:00
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2023-10-08 10:35:52 +08:00
										 |  |  |  * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD | 
					
						
							| 
									
										
										
										
											2022-07-15 12:52:44 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: Apache-2.0 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <stdint.h>
 | 
					
						
							|  |  |  | #include "assert.h"
 | 
					
						
							|  |  |  | #include "esp_types.h"
 | 
					
						
							|  |  |  | #include "esp_err.h"
 | 
					
						
							|  |  |  | #include "esp_check.h"
 | 
					
						
							|  |  |  | #include "esp_heap_caps.h"
 | 
					
						
							|  |  |  | #include "esp_efuse.h"
 | 
					
						
							|  |  |  | #include "esp_efuse_table.h"
 | 
					
						
							|  |  |  | #include "esp_efuse_rtc_table.h"
 | 
					
						
							|  |  |  | #include "hal/adc_types.h"
 | 
					
						
							|  |  |  | #include "soc/efuse_periph.h"
 | 
					
						
							|  |  |  | #include "soc/soc_caps.h"
 | 
					
						
							|  |  |  | #include "esp_adc/adc_cali_scheme.h"
 | 
					
						
							|  |  |  | #include "adc_cali_interface.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const __attribute__((unused)) static char *TAG = "adc_cali"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* ------------------------ Characterization Constants ---------------------- */ | 
					
						
							|  |  |  | // coeff_a and coeff_b are actually floats
 | 
					
						
							|  |  |  | // they are scaled to put them into uint32_t so that the headers do not have to be changed
 | 
					
						
							|  |  |  | static const int coeff_a_scaling = 65536; | 
					
						
							|  |  |  | static const int coeff_b_scaling = 1024; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* -------------------- Characterization Helper Data Types ------------------ */ | 
					
						
							|  |  |  | typedef struct { | 
					
						
							|  |  |  |     int adc_calib_high; | 
					
						
							|  |  |  |     int adc_calib_low; | 
					
						
							|  |  |  | } adc_calib_data_ver1_t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef struct { | 
					
						
							|  |  |  |     int adc_calib_high;         // the reading of adc ...
 | 
					
						
							|  |  |  |     int adc_calib_high_voltage; // ... at this voltage (mV)
 | 
					
						
							|  |  |  | } adc_calib_data_ver2_t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef struct { | 
					
						
							|  |  |  |     char version_num; | 
					
						
							|  |  |  |     adc_unit_t unit_id; | 
					
						
							|  |  |  |     adc_atten_t atten_level; | 
					
						
							|  |  |  |     union { | 
					
						
							|  |  |  |         adc_calib_data_ver1_t ver1; | 
					
						
							|  |  |  |         adc_calib_data_ver2_t ver2; | 
					
						
							|  |  |  |     } efuse_data; | 
					
						
							|  |  |  | } adc_calib_parsed_info_t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* ------------------------ Context Structure--------------------------- */ | 
					
						
							|  |  |  | typedef struct { | 
					
						
							|  |  |  |     adc_unit_t unit_id;                     ///< ADC unit
 | 
					
						
							|  |  |  |     adc_atten_t atten;                      ///< ADC attenuation
 | 
					
						
							|  |  |  |     uint32_t coeff_a;                       ///< Gradient of ADC-Voltage curve
 | 
					
						
							|  |  |  |     uint32_t coeff_b;                       ///< Offset of ADC-Voltage curve
 | 
					
						
							|  |  |  | } cali_chars_line_fitting_t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* ----------------------- Characterization Functions ----------------------- */ | 
					
						
							|  |  |  | static bool prepare_calib_data_for(adc_unit_t unit_id, adc_atten_t atten, adc_calib_parsed_info_t *parsed_data_storage); | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  *  (Used in V1 of calibration scheme) | 
					
						
							|  |  |  |  *  The Two Point calibration measures the reading at two specific input voltages, and calculates the (assumed linear) relation | 
					
						
							|  |  |  |  *  between input voltage and ADC response. (Response = A * Vinput + B) | 
					
						
							|  |  |  |  *  A and B are scaled ints. | 
					
						
							|  |  |  |  *  @param high The ADC response at the higher voltage of the corresponding attenuation (600mV, 800mV, 1000mV, 2000mV). | 
					
						
							|  |  |  |  *  @param low The ADC response at the lower voltage of the corresponding attenuation (all 250mV). | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void characterize_using_two_point(adc_unit_t unit_id, | 
					
						
							| 
									
										
										
										
											2023-10-08 10:35:52 +08:00
										 |  |  |                                          adc_atten_t atten, | 
					
						
							|  |  |  |                                          uint32_t high, | 
					
						
							|  |  |  |                                          uint32_t low, | 
					
						
							|  |  |  |                                          uint32_t *coeff_a, | 
					
						
							|  |  |  |                                          uint32_t *coeff_b); | 
					
						
							| 
									
										
										
										
											2022-07-15 12:52:44 +08:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Estimate the (assumed) linear relationship btwn the measured raw value and the voltage | 
					
						
							|  |  |  |  * with the previously done measurement when the chip was manufactured. | 
					
						
							|  |  |  |  * */ | 
					
						
							|  |  |  | static bool calculate_characterization_coefficients(const adc_calib_parsed_info_t *parsed_data, cali_chars_line_fitting_t *ctx); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* ------------------------ Interface Functions --------------------------- */ | 
					
						
							|  |  |  | static esp_err_t cali_raw_to_voltage(void *arg, int raw, int *voltage); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* ------------------------- Public API ------------------------------------- */ | 
					
						
							|  |  |  | esp_err_t adc_cali_create_scheme_line_fitting(const adc_cali_line_fitting_config_t *config, adc_cali_handle_t *ret_handle) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     esp_err_t ret = ESP_OK; | 
					
						
							|  |  |  |     ESP_RETURN_ON_FALSE(config && ret_handle, ESP_ERR_INVALID_ARG, TAG, "invalid arg: null pointer"); | 
					
						
							|  |  |  |     ESP_RETURN_ON_FALSE(config->unit_id < SOC_ADC_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid ADC unit"); | 
					
						
							|  |  |  |     ESP_RETURN_ON_FALSE(config->atten < SOC_ADC_ATTEN_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid ADC attenuation"); | 
					
						
							|  |  |  |     //S2 Oneshot read only supports 13 bits, DMA read only supports 12 bits
 | 
					
						
							|  |  |  |     ESP_RETURN_ON_FALSE(((config->bitwidth == SOC_ADC_RTC_MAX_BITWIDTH || config->bitwidth == SOC_ADC_DIGI_MAX_BITWIDTH) || config->bitwidth == ADC_BITWIDTH_DEFAULT), ESP_ERR_INVALID_ARG, TAG, "invalid bitwidth"); | 
					
						
							|  |  |  |     // current version only accepts encoding ver 1 and ver 2.
 | 
					
						
							|  |  |  |     uint8_t adc_encoding_version = esp_efuse_rtc_table_read_calib_version(); | 
					
						
							|  |  |  |     ESP_RETURN_ON_FALSE(((adc_encoding_version == 1) || (adc_encoding_version == 2)), ESP_ERR_NOT_SUPPORTED, TAG, "Calibration required eFuse bits not burnt"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     adc_cali_scheme_t *scheme = (adc_cali_scheme_t *)heap_caps_calloc(1, sizeof(adc_cali_scheme_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); | 
					
						
							|  |  |  |     ESP_RETURN_ON_FALSE(scheme, ESP_ERR_NO_MEM, TAG, "no mem for adc calibration scheme"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cali_chars_line_fitting_t *chars = (cali_chars_line_fitting_t *)heap_caps_calloc(1, sizeof(cali_chars_line_fitting_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); | 
					
						
							|  |  |  |     ESP_GOTO_ON_FALSE(chars, ESP_ERR_NO_MEM, err, TAG, "no memory for the calibration characteristics"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     scheme->raw_to_voltage = cali_raw_to_voltage; | 
					
						
							|  |  |  |     scheme->ctx = chars; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     adc_calib_parsed_info_t efuse_parsed_data = {0}; | 
					
						
							|  |  |  |     bool success = prepare_calib_data_for(config->unit_id, config->atten, &efuse_parsed_data); | 
					
						
							|  |  |  |     assert(success); | 
					
						
							| 
									
										
										
										
											2025-07-02 15:43:23 +08:00
										 |  |  |     (void)success; | 
					
						
							| 
									
										
										
										
											2022-07-15 12:52:44 +08:00
										 |  |  |     success = calculate_characterization_coefficients(&efuse_parsed_data, chars); | 
					
						
							|  |  |  |     assert(success); | 
					
						
							| 
									
										
										
										
											2023-06-09 02:56:11 +08:00
										 |  |  |     ESP_LOGD(TAG, "adc%d (atten leven %d) calibration done: A:%" PRId32" B:%" PRId32, config->unit_id, config->atten, chars->coeff_a, chars->coeff_b); | 
					
						
							| 
									
										
										
										
											2022-07-15 12:52:44 +08:00
										 |  |  |     chars->unit_id = config->unit_id; | 
					
						
							|  |  |  |     chars->atten = config->atten; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     *ret_handle = scheme; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ESP_OK; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | err: | 
					
						
							|  |  |  |     if (scheme) { | 
					
						
							|  |  |  |         free(scheme); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | esp_err_t adc_cali_delete_scheme_line_fitting(adc_cali_handle_t handle) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     free(handle->ctx); | 
					
						
							|  |  |  |     handle->ctx = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     free(handle); | 
					
						
							|  |  |  |     handle = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ESP_OK; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* ------------------------ Interface Functions --------------------------- */ | 
					
						
							|  |  |  | static esp_err_t cali_raw_to_voltage(void *arg, int raw, int *voltage) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     //pointers are checked in the upper layer
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cali_chars_line_fitting_t *ctx = arg; | 
					
						
							| 
									
										
										
										
											2025-04-01 17:19:11 +08:00
										 |  |  |     *voltage = (raw * ctx->coeff_a / (coeff_a_scaling / coeff_b_scaling) + ctx->coeff_b) / coeff_b_scaling; | 
					
						
							| 
									
										
										
										
											2022-07-15 12:52:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return ESP_OK; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* ----------------------- Characterization Functions ----------------------- */ | 
					
						
							|  |  |  | static bool prepare_calib_data_for(adc_unit_t unit_id, adc_atten_t atten, adc_calib_parsed_info_t *parsed_data_storage) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     int version_num = esp_efuse_rtc_table_read_calib_version(); | 
					
						
							|  |  |  |     int tag; | 
					
						
							|  |  |  |     parsed_data_storage->version_num = version_num; | 
					
						
							|  |  |  |     parsed_data_storage->unit_id = unit_id; | 
					
						
							|  |  |  |     parsed_data_storage->atten_level = atten; | 
					
						
							|  |  |  |     switch (version_num) { | 
					
						
							|  |  |  |     case 1: | 
					
						
							|  |  |  |         // note: use the unit_id as in hal, which start from 0.
 | 
					
						
							|  |  |  |         tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V1_PARAM_VLOW); | 
					
						
							|  |  |  |         parsed_data_storage->efuse_data.ver1.adc_calib_low = esp_efuse_rtc_table_get_parsed_efuse_value(tag, false); | 
					
						
							|  |  |  |         tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V1_PARAM_VHIGH); | 
					
						
							|  |  |  |         parsed_data_storage->efuse_data.ver1.adc_calib_high = esp_efuse_rtc_table_get_parsed_efuse_value(tag, false); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 2: | 
					
						
							|  |  |  |         tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V2_PARAM_VHIGH); | 
					
						
							|  |  |  |         parsed_data_storage->efuse_data.ver2.adc_calib_high = esp_efuse_rtc_table_get_parsed_efuse_value(tag, false); | 
					
						
							|  |  |  |         switch (parsed_data_storage->atten_level) { | 
					
						
							|  |  |  |         case ADC_ATTEN_DB_0: | 
					
						
							|  |  |  |             parsed_data_storage->efuse_data.ver2.adc_calib_high_voltage = 600; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case ADC_ATTEN_DB_2_5: | 
					
						
							|  |  |  |             parsed_data_storage->efuse_data.ver2.adc_calib_high_voltage = 800; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case ADC_ATTEN_DB_6: | 
					
						
							|  |  |  |             parsed_data_storage->efuse_data.ver2.adc_calib_high_voltage = 1000; | 
					
						
							|  |  |  |             break; | 
					
						
							| 
									
										
										
										
											2023-10-13 16:02:45 +08:00
										 |  |  |         case ADC_ATTEN_DB_12: | 
					
						
							| 
									
										
										
										
											2022-07-15 12:52:44 +08:00
										 |  |  |             parsed_data_storage->efuse_data.ver2.adc_calib_high_voltage = 2000; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         default: | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |         // fall back to case 1 with zeros as params.
 | 
					
						
							|  |  |  |         parsed_data_storage->version_num = 1; | 
					
						
							|  |  |  |         tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V1_PARAM_VLOW); | 
					
						
							|  |  |  |         parsed_data_storage->efuse_data.ver1.adc_calib_high = esp_efuse_rtc_table_get_parsed_efuse_value(tag, true); | 
					
						
							|  |  |  |         tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V1_PARAM_VHIGH); | 
					
						
							|  |  |  |         parsed_data_storage->efuse_data.ver1.adc_calib_low = esp_efuse_rtc_table_get_parsed_efuse_value(tag, true); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  *  (Used in V1 of calibration scheme) | 
					
						
							|  |  |  |  *  The Two Point calibration measures the reading at two specific input voltages, and calculates the (assumed linear) relation | 
					
						
							|  |  |  |  *  between input voltage and ADC response. (Response = A * Vinput + B) | 
					
						
							|  |  |  |  *  A and B are scaled ints. | 
					
						
							|  |  |  |  *  @param high The ADC response at the higher voltage of the corresponding attenuation (600mV, 800mV, 1000mV, 2000mV). | 
					
						
							|  |  |  |  *  @param low The ADC response at the lower voltage of the corresponding attenuation (all 250mV). | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void characterize_using_two_point(adc_unit_t unit_id, | 
					
						
							| 
									
										
										
										
											2023-10-08 10:35:52 +08:00
										 |  |  |                                          adc_atten_t atten, | 
					
						
							|  |  |  |                                          uint32_t high, | 
					
						
							|  |  |  |                                          uint32_t low, | 
					
						
							|  |  |  |                                          uint32_t *coeff_a, | 
					
						
							|  |  |  |                                          uint32_t *coeff_b) | 
					
						
							| 
									
										
										
										
											2022-07-15 12:52:44 +08:00
										 |  |  | { | 
					
						
							|  |  |  |     // once we have recovered the reference high(Dhigh) and low(Dlow) readings, we can calculate a and b from
 | 
					
						
							|  |  |  |     // the measured high and low readings
 | 
					
						
							|  |  |  |     static const uint32_t v_high[] = {600, 800, 1000, 2000}; | 
					
						
							|  |  |  |     static const uint32_t v_low = 250; | 
					
						
							|  |  |  |     *coeff_a = coeff_a_scaling * (v_high[atten] - v_low) / (high - low); | 
					
						
							|  |  |  |     *coeff_b = coeff_b_scaling * (v_low * high - v_high[atten] * low) / (high - low); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Estimate the (assumed) linear relationship btwn the measured raw value and the voltage | 
					
						
							|  |  |  |  * with the previously done measurement when the chip was manufactured. | 
					
						
							|  |  |  |  * */ | 
					
						
							|  |  |  | static bool calculate_characterization_coefficients(const adc_calib_parsed_info_t *parsed_data, cali_chars_line_fitting_t *ctx) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     switch (parsed_data->version_num) { | 
					
						
							|  |  |  |     case 1: | 
					
						
							| 
									
										
										
										
											2023-06-09 02:56:11 +08:00
										 |  |  |         ESP_LOGD(TAG, "Calib V1, low%dmV, high%dmV", parsed_data->efuse_data.ver1.adc_calib_low, parsed_data->efuse_data.ver1.adc_calib_high); | 
					
						
							| 
									
										
										
										
											2022-07-15 12:52:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         characterize_using_two_point(parsed_data->unit_id, parsed_data->atten_level, | 
					
						
							|  |  |  |                                      parsed_data->efuse_data.ver1.adc_calib_high, parsed_data->efuse_data.ver1.adc_calib_low, | 
					
						
							|  |  |  |                                      &(ctx->coeff_a), &(ctx->coeff_b)); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 2: | 
					
						
							| 
									
										
										
										
											2023-06-09 02:56:11 +08:00
										 |  |  |         ESP_LOGD(TAG, "Calib V2, volt%dmV", parsed_data->efuse_data.ver2.adc_calib_high); | 
					
						
							| 
									
										
										
										
											2022-07-15 12:52:44 +08:00
										 |  |  |         ctx->coeff_a = coeff_a_scaling * parsed_data->efuse_data.ver2.adc_calib_high_voltage / | 
					
						
							| 
									
										
										
										
											2023-10-08 10:35:52 +08:00
										 |  |  |                        parsed_data->efuse_data.ver2.adc_calib_high; | 
					
						
							| 
									
										
										
										
											2022-07-15 12:52:44 +08:00
										 |  |  |         ctx->coeff_b = 0; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } |