2022-07-15 00:52:44 -04:00
/*
* SPDX - FileCopyrightText : 2019 - 2021 Espressif Systems ( Shanghai ) CO LTD
*
* SPDX - License - Identifier : Apache - 2.0
*/
# include <stdint.h>
# include <stdbool.h>
# include <string.h>
# include "esp_types.h"
# include "esp_err.h"
# include "esp_log.h"
# include "esp_check.h"
# include "esp_heap_caps.h"
# include "soc/soc_caps.h"
# include "esp_adc/adc_cali_scheme.h"
# include "adc_cali_interface.h"
# include "curve_fitting_coefficients.h"
2023-05-05 07:56:44 -04:00
# include "esp_private/adc_share_hw_ctrl.h"
2022-07-15 00:52:44 -04:00
# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
# include "esp_efuse_rtc_calib.h"
const __attribute__ ( ( unused ) ) static char * TAG = " adc_cali " ;
// coeff_a is actually a float number
// it is scaled to put them into uint32_t so that the headers do not have to be changed
static const int coeff_a_scaling = 65536 ;
/* -------------------- Characterization Helper Data Types ------------------ */
typedef struct {
uint32_t voltage ;
uint32_t digi ;
} adc_calib_data_ver1_t ;
typedef struct {
char version_num ;
adc_unit_t unit_id ;
adc_atten_t atten ;
union {
adc_calib_data_ver1_t ver1 ;
} ref_data ;
} adc_calib_info_t ;
/* ------------------------ Context Structure--------------------------- */
typedef struct {
uint32_t coeff_a ; ///< Gradient of ADC-Voltage curve
uint32_t coeff_b ; ///< Offset of ADC-Voltage curve
} cali_chars_first_step_t ;
typedef struct {
uint8_t term_num ; ///< Term number of the algorithm formula
const uint64_t ( * coeff ) [ COEFF_GROUP_NUM ] [ TERM_MAX ] [ 2 ] ; ///< Coeff of each term. See `adc_error_coef_atten` for details (and the magic number 2)
const int32_t ( * sign ) [ COEFF_GROUP_NUM ] [ TERM_MAX ] ; ///< Sign of each term
} cali_chars_second_step_t ;
typedef struct {
adc_unit_t unit_id ; ///< ADC unit
2023-05-05 07:56:44 -04:00
adc_channel_t chan ; ///< ADC channel
2022-07-15 00:52:44 -04:00
adc_atten_t atten ; ///< ADC attenuation
cali_chars_first_step_t chars_first_step ; ///< Calibration first step characteristics
cali_chars_second_step_t chars_second_step ; ///< Calibration second step characteristics
} cali_chars_curve_fitting_t ;
/* ----------------------- Characterization Functions ----------------------- */
static void get_first_step_reference_point ( int version_num , adc_unit_t unit_id , adc_atten_t atten , adc_calib_info_t * calib_info ) ;
static void calc_first_step_coefficients ( const adc_calib_info_t * parsed_data , cali_chars_curve_fitting_t * chars ) ;
static void calc_second_step_coefficients ( const adc_cali_curve_fitting_config_t * config , cali_chars_curve_fitting_t * ctx ) ;
static int32_t get_reading_error ( uint64_t v_cali_1 , const cali_chars_second_step_t * param , adc_atten_t atten ) ;
static esp_err_t check_valid ( const adc_cali_curve_fitting_config_t * config ) ;
/* ------------------------ 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_curve_fitting ( const adc_cali_curve_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 " ) ;
ret = check_valid ( config ) ;
if ( ret ! = ESP_OK ) {
return ret ;
}
2022-09-08 07:18:33 -04:00
// current version only accepts encoding version: `ESP_EFUSE_ADC_CALIB_VER`.
2022-07-15 00:52:44 -04:00
uint8_t adc_encoding_version = esp_efuse_rtc_calib_get_ver ( ) ;
2022-09-08 07:18:33 -04:00
ESP_RETURN_ON_FALSE ( adc_encoding_version = = ESP_EFUSE_ADC_CALIB_VER , ESP_ERR_NOT_SUPPORTED , TAG , " Calibration required eFuse bits not burnt " ) ;
2022-07-15 00:52:44 -04:00
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_curve_fitting_t * chars = ( cali_chars_curve_fitting_t * ) heap_caps_calloc ( 1 , sizeof ( cali_chars_curve_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 ;
//Prepare calibration characteristics
adc_calib_info_t calib_info = { 0 } ;
//Set first step calibration context
get_first_step_reference_point ( adc_encoding_version , config - > unit_id , config - > atten , & calib_info ) ;
calc_first_step_coefficients ( & calib_info , chars ) ;
//Set second step calibration context
calc_second_step_coefficients ( config , chars ) ;
chars - > unit_id = config - > unit_id ;
2023-05-05 07:56:44 -04:00
chars - > chan = config - > chan ;
2022-07-15 00:52:44 -04:00
chars - > atten = config - > atten ;
* ret_handle = scheme ;
return ESP_OK ;
err :
if ( scheme ) {
free ( scheme ) ;
}
return ret ;
}
esp_err_t adc_cali_delete_scheme_curve_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_curve_fitting_t * ctx = arg ;
2023-05-05 07:56:44 -04:00
# if SOC_ADC_CALIB_CHAN_COMPENS_SUPPORTED
int chan_compensation = adc_get_hw_calibration_chan_compens ( ctx - > unit_id , ctx - > chan , ctx - > atten ) ;
raw - = chan_compensation ;
/* Limit the range */
int max_val = ( 1L < < SOC_ADC_RTC_MAX_BITWIDTH ) - 1 ;
raw = raw < = 0 ? 0 :
raw > max_val ? max_val : raw ;
# endif // SOC_ADC_CALIB_CHAN_COMPENS_SUPPORTED
2022-07-15 00:52:44 -04:00
uint64_t v_cali_1 = raw * ctx - > chars_first_step . coeff_a / coeff_a_scaling + ctx - > chars_first_step . coeff_b ;
int32_t error = get_reading_error ( v_cali_1 , & ( ctx - > chars_second_step ) , ctx - > atten ) ;
* voltage = ( int32_t ) v_cali_1 - error ;
return ESP_OK ;
}
/* ----------------------- Characterization Functions ----------------------- */
//To get the reference point (Dout, Vin)
static void get_first_step_reference_point ( int version_num , adc_unit_t unit_id , adc_atten_t atten , adc_calib_info_t * calib_info )
{
2022-09-08 07:18:33 -04:00
assert ( version_num = = ESP_EFUSE_ADC_CALIB_VER ) ;
2022-07-15 00:52:44 -04:00
esp_err_t ret ;
calib_info - > version_num = version_num ;
calib_info - > unit_id = unit_id ;
calib_info - > atten = atten ;
uint32_t voltage = 0 ;
uint32_t digi = 0 ;
2023-04-27 04:53:48 -04:00
ret = esp_efuse_rtc_calib_get_cal_voltage ( version_num , unit_id , ( int ) atten , & digi , & voltage ) ;
2022-07-15 00:52:44 -04:00
assert ( ret = = ESP_OK ) ;
calib_info - > ref_data . ver1 . voltage = voltage ;
calib_info - > ref_data . ver1 . digi = digi ;
}
/*
* Estimate the ( assumed ) linear relationship btwn the measured raw value and the voltage
* with the previously done measurement when the chip was manufactured .
*/
static void calc_first_step_coefficients ( const adc_calib_info_t * parsed_data , cali_chars_curve_fitting_t * ctx )
{
ctx - > chars_first_step . coeff_a = coeff_a_scaling * parsed_data - > ref_data . ver1 . voltage / parsed_data - > ref_data . ver1 . digi ;
ctx - > chars_first_step . coeff_b = 0 ;
2022-08-25 23:28:01 -04:00
ESP_LOGV ( TAG , " Calib V1, Cal Voltage = % " PRId32 " , Digi out = % " PRId32 " , Coef_a = % " PRId32 " \n " , parsed_data - > ref_data . ver1 . voltage , parsed_data - > ref_data . ver1 . digi , ctx - > chars_first_step . coeff_a ) ;
2022-07-15 00:52:44 -04:00
}
static void calc_second_step_coefficients ( const adc_cali_curve_fitting_config_t * config , cali_chars_curve_fitting_t * ctx )
{
ctx - > chars_second_step . term_num = ( config - > atten = = 3 ) ? 5 : 3 ;
2023-05-05 07:56:44 -04:00
# if CONFIG_IDF_TARGET_ESP32C3 || SOC_ADC_PERIPH_NUM == 1
// On esp32c3, ADC1 and ADC2 share the second step coefficients
// And if the target only has 1 ADC peripheral, just use the ADC1 directly
2022-07-15 00:52:44 -04:00
ctx - > chars_second_step . coeff = & adc1_error_coef_atten ;
ctx - > chars_second_step . sign = & adc1_error_sign ;
# else
ctx - > chars_second_step . coeff = ( config - > unit_id = = ADC_UNIT_1 ) ? & adc1_error_coef_atten : & adc2_error_coef_atten ;
ctx - > chars_second_step . sign = ( config - > unit_id = = ADC_UNIT_1 ) ? & adc1_error_sign : & adc2_error_sign ;
# endif
}
static int32_t get_reading_error ( uint64_t v_cali_1 , const cali_chars_second_step_t * param , adc_atten_t atten )
{
if ( v_cali_1 = = 0 ) {
return 0 ;
}
uint8_t term_num = param - > term_num ;
int32_t error = 0 ;
uint64_t coeff = 0 ;
uint64_t variable [ term_num ] ;
uint64_t term [ term_num ] ;
memset ( variable , 0 , term_num * sizeof ( uint64_t ) ) ;
memset ( term , 0 , term_num * sizeof ( uint64_t ) ) ;
/**
* For atten0 ~ 2 :
* error = ( K0 * X ^ 0 ) + ( K1 * X ^ 1 ) + ( K2 * X ^ 2 ) ;
*
* For atten3 :
* error = ( K0 * X ^ 0 ) + ( K1 * X ^ 1 ) + ( K2 * X ^ 2 ) + ( K3 * X ^ 3 ) + ( K4 * X ^ 4 ) ;
*/
variable [ 0 ] = 1 ;
coeff = ( * param - > coeff ) [ atten ] [ 0 ] [ 0 ] ;
term [ 0 ] = variable [ 0 ] * coeff / ( * param - > coeff ) [ atten ] [ 0 ] [ 1 ] ;
error = ( int32_t ) term [ 0 ] * ( * param - > sign ) [ atten ] [ 0 ] ;
for ( int i = 1 ; i < term_num ; i + + ) {
variable [ i ] = variable [ i - 1 ] * v_cali_1 ;
coeff = ( * param - > coeff ) [ atten ] [ i ] [ 0 ] ;
term [ i ] = variable [ i ] * coeff ;
ESP_LOGV ( TAG , " big coef is %llu, big term%d is %llu, coef_id is %d " , coeff , i , term [ i ] , i ) ;
term [ i ] = term [ i ] / ( * param - > coeff ) [ atten ] [ i ] [ 1 ] ;
error + = ( int32_t ) term [ i ] * ( * param - > sign ) [ atten ] [ i ] ;
2022-08-25 23:28:01 -04:00
ESP_LOGV ( TAG , " term%d is %llu, error is % " PRId32 , i , term [ i ] , error ) ;
2022-07-15 00:52:44 -04:00
}
return error ;
}
static esp_err_t check_valid ( const adc_cali_curve_fitting_config_t * config )
{
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 " ) ;
bool available_oneshot_bitwidth = ( config - > bitwidth > = SOC_ADC_RTC_MIN_BITWIDTH & & config - > bitwidth < = SOC_ADC_RTC_MAX_BITWIDTH ) ;
bool available_dma_bitwidth = ( config - > bitwidth > = SOC_ADC_DIGI_MIN_BITWIDTH & & config - > bitwidth < = SOC_ADC_DIGI_MAX_BITWIDTH ) ;
bool default_bitwidth_mark = ( config - > bitwidth = = ADC_BITWIDTH_DEFAULT ) ;
bool available_bitwidth = ( available_oneshot_bitwidth | | available_dma_bitwidth | | default_bitwidth_mark ) ;
ESP_RETURN_ON_FALSE ( available_bitwidth , ESP_ERR_INVALID_ARG , TAG , " invalid bitwidth " ) ;
return ESP_OK ;
}
# endif //#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED