1 /*
2 * SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <stdint.h>
8 #include "assert.h"
9 #include "esp_types.h"
10 #include "esp_err.h"
11 #include "esp_check.h"
12 #include "esp_heap_caps.h"
13 #include "esp_efuse.h"
14 #include "esp_efuse_table.h"
15 #include "esp_efuse_rtc_table.h"
16 #include "hal/adc_types.h"
17 #include "soc/efuse_periph.h"
18 #include "soc/soc_caps.h"
19 #include "esp_adc/adc_cali_scheme.h"
20 #include "adc_cali_interface.h"
21
22 const __attribute__((unused)) static char *TAG = "adc_cali";
23
24 /* ------------------------ Characterization Constants ---------------------- */
25 // coeff_a and coeff_b are actually floats
26 // they are scaled to put them into uint32_t so that the headers do not have to be changed
27 static const int coeff_a_scaling = 65536;
28 static const int coeff_b_scaling = 1024;
29
30
31 /* -------------------- Characterization Helper Data Types ------------------ */
32 typedef struct {
33 int adc_calib_high;
34 int adc_calib_low;
35 } adc_calib_data_ver1_t;
36
37 typedef struct {
38 int adc_calib_high; // the reading of adc ...
39 int adc_calib_high_voltage; // ... at this voltage (mV)
40 } adc_calib_data_ver2_t;
41
42 typedef struct {
43 char version_num;
44 adc_unit_t unit_id;
45 adc_atten_t atten_level;
46 union {
47 adc_calib_data_ver1_t ver1;
48 adc_calib_data_ver2_t ver2;
49 } efuse_data;
50 } adc_calib_parsed_info_t;
51
52
53 /* ------------------------ Context Structure--------------------------- */
54 typedef struct {
55 adc_unit_t unit_id; ///< ADC unit
56 adc_atten_t atten; ///< ADC attenuation
57 uint32_t coeff_a; ///< Gradient of ADC-Voltage curve
58 uint32_t coeff_b; ///< Offset of ADC-Voltage curve
59 } cali_chars_line_fitting_t;
60
61
62 /* ----------------------- Characterization Functions ----------------------- */
63 static bool prepare_calib_data_for(adc_unit_t unit_id, adc_atten_t atten, adc_calib_parsed_info_t *parsed_data_storage);
64 /**
65 * (Used in V1 of calibration scheme)
66 * The Two Point calibration measures the reading at two specific input voltages, and calculates the (assumed linear) relation
67 * between input voltage and ADC response. (Response = A * Vinput + B)
68 * A and B are scaled ints.
69 * @param high The ADC response at the higher voltage of the corresponding attenuation (600mV, 800mV, 1000mV, 2000mV).
70 * @param low The ADC response at the lower voltage of the corresponding attenuation (all 250mV).
71 *
72 */
73 static void characterize_using_two_point(adc_unit_t unit_id,
74 adc_atten_t atten,
75 uint32_t high,
76 uint32_t low,
77 uint32_t *coeff_a,
78 uint32_t *coeff_b);
79 /*
80 * Estimate the (assumed) linear relationship btwn the measured raw value and the voltage
81 * with the previously done measurement when the chip was manufactured.
82 * */
83 static bool calculate_characterization_coefficients(const adc_calib_parsed_info_t *parsed_data, cali_chars_line_fitting_t *ctx);
84
85
86 /* ------------------------ Interface Functions --------------------------- */
87 static esp_err_t cali_raw_to_voltage(void *arg, int raw, int *voltage);
88
89
90 /* ------------------------- Public API ------------------------------------- */
adc_cali_create_scheme_line_fitting(const adc_cali_line_fitting_config_t * config,adc_cali_handle_t * ret_handle)91 esp_err_t adc_cali_create_scheme_line_fitting(const adc_cali_line_fitting_config_t *config, adc_cali_handle_t *ret_handle)
92 {
93 esp_err_t ret = ESP_OK;
94 ESP_RETURN_ON_FALSE(config && ret_handle, ESP_ERR_INVALID_ARG, TAG, "invalid arg: null pointer");
95 ESP_RETURN_ON_FALSE(config->unit_id < SOC_ADC_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid ADC unit");
96 ESP_RETURN_ON_FALSE(config->atten < SOC_ADC_ATTEN_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid ADC attenuation");
97 //S2 Oneshot read only supports 13 bits, DMA read only supports 12 bits
98 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");
99 // current version only accepts encoding ver 1 and ver 2.
100 uint8_t adc_encoding_version = esp_efuse_rtc_table_read_calib_version();
101 ESP_RETURN_ON_FALSE(((adc_encoding_version == 1) || (adc_encoding_version == 2)), ESP_ERR_NOT_SUPPORTED, TAG, "Calibration required eFuse bits not burnt");
102
103 adc_cali_scheme_t *scheme = (adc_cali_scheme_t *)heap_caps_calloc(1, sizeof(adc_cali_scheme_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
104 ESP_RETURN_ON_FALSE(scheme, ESP_ERR_NO_MEM, TAG, "no mem for adc calibration scheme");
105
106 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);
107 ESP_GOTO_ON_FALSE(chars, ESP_ERR_NO_MEM, err, TAG, "no memory for the calibration characteristics");
108
109 scheme->raw_to_voltage = cali_raw_to_voltage;
110 scheme->ctx = chars;
111
112 adc_calib_parsed_info_t efuse_parsed_data = {0};
113 bool success = prepare_calib_data_for(config->unit_id, config->atten, &efuse_parsed_data);
114 assert(success);
115 success = calculate_characterization_coefficients(&efuse_parsed_data, chars);
116 assert(success);
117 ESP_LOGD(TAG, "adc%d (atten leven %d) calibration done: A:%"PRId32" B:%"PRId32"\n", config->unit_id, config->atten, chars->coeff_a, chars->coeff_b);
118 chars->unit_id = config->unit_id;
119 chars->atten = config->atten;
120
121 *ret_handle = scheme;
122
123 return ESP_OK;
124
125 err:
126 if (scheme) {
127 heap_caps_free(scheme);
128 }
129 return ret;
130 }
131
adc_cali_delete_scheme_line_fitting(adc_cali_handle_t handle)132 esp_err_t adc_cali_delete_scheme_line_fitting(adc_cali_handle_t handle)
133 {
134 ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
135
136 heap_caps_free(handle->ctx);
137 handle->ctx = NULL;
138
139 heap_caps_free(handle);
140 handle = NULL;
141
142 return ESP_OK;
143 }
144
145
146 /* ------------------------ Interface Functions --------------------------- */
cali_raw_to_voltage(void * arg,int raw,int * voltage)147 static esp_err_t cali_raw_to_voltage(void *arg, int raw, int *voltage)
148 {
149 //pointers are checked in the upper layer
150
151 cali_chars_line_fitting_t *ctx = arg;
152 *voltage = raw * ctx->coeff_a / coeff_a_scaling + ctx->coeff_b / coeff_b_scaling;
153
154 return ESP_OK;
155 }
156
157
158 /* ----------------------- Characterization Functions ----------------------- */
prepare_calib_data_for(adc_unit_t unit_id,adc_atten_t atten,adc_calib_parsed_info_t * parsed_data_storage)159 static bool prepare_calib_data_for(adc_unit_t unit_id, adc_atten_t atten, adc_calib_parsed_info_t *parsed_data_storage)
160 {
161 int version_num = esp_efuse_rtc_table_read_calib_version();
162 int tag;
163 parsed_data_storage->version_num = version_num;
164 parsed_data_storage->unit_id = unit_id;
165 parsed_data_storage->atten_level = atten;
166 switch (version_num) {
167 case 1:
168 // note: use the unit_id as in hal, which start from 0.
169 tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V1_PARAM_VLOW);
170 parsed_data_storage->efuse_data.ver1.adc_calib_low = esp_efuse_rtc_table_get_parsed_efuse_value(tag, false);
171 tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V1_PARAM_VHIGH);
172 parsed_data_storage->efuse_data.ver1.adc_calib_high = esp_efuse_rtc_table_get_parsed_efuse_value(tag, false);
173 break;
174 case 2:
175 tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V2_PARAM_VHIGH);
176 parsed_data_storage->efuse_data.ver2.adc_calib_high = esp_efuse_rtc_table_get_parsed_efuse_value(tag, false);
177 switch (parsed_data_storage->atten_level) {
178 case ADC_ATTEN_DB_0:
179 parsed_data_storage->efuse_data.ver2.adc_calib_high_voltage = 600;
180 break;
181 case ADC_ATTEN_DB_2_5:
182 parsed_data_storage->efuse_data.ver2.adc_calib_high_voltage = 800;
183 break;
184 case ADC_ATTEN_DB_6:
185 parsed_data_storage->efuse_data.ver2.adc_calib_high_voltage = 1000;
186 break;
187 case ADC_ATTEN_DB_12:
188 parsed_data_storage->efuse_data.ver2.adc_calib_high_voltage = 2000;
189 break;
190 default:
191 break;
192 }
193 break;
194 default:
195 // fall back to case 1 with zeros as params.
196 parsed_data_storage->version_num = 1;
197 tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V1_PARAM_VLOW);
198 parsed_data_storage->efuse_data.ver1.adc_calib_high = esp_efuse_rtc_table_get_parsed_efuse_value(tag, true);
199 tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V1_PARAM_VHIGH);
200 parsed_data_storage->efuse_data.ver1.adc_calib_low = esp_efuse_rtc_table_get_parsed_efuse_value(tag, true);
201 break;
202 }
203 return true;
204 }
205
206 /**
207 * (Used in V1 of calibration scheme)
208 * The Two Point calibration measures the reading at two specific input voltages, and calculates the (assumed linear) relation
209 * between input voltage and ADC response. (Response = A * Vinput + B)
210 * A and B are scaled ints.
211 * @param high The ADC response at the higher voltage of the corresponding attenuation (600mV, 800mV, 1000mV, 2000mV).
212 * @param low The ADC response at the lower voltage of the corresponding attenuation (all 250mV).
213 *
214 */
characterize_using_two_point(adc_unit_t unit_id,adc_atten_t atten,uint32_t high,uint32_t low,uint32_t * coeff_a,uint32_t * coeff_b)215 static void characterize_using_two_point(adc_unit_t unit_id,
216 adc_atten_t atten,
217 uint32_t high,
218 uint32_t low,
219 uint32_t *coeff_a,
220 uint32_t *coeff_b)
221 {
222 // once we have recovered the reference high(Dhigh) and low(Dlow) readings, we can calculate a and b from
223 // the measured high and low readings
224 static const uint32_t v_high[] = {600, 800, 1000, 2000};
225 static const uint32_t v_low = 250;
226 *coeff_a = coeff_a_scaling * (v_high[atten] - v_low) / (high - low);
227 *coeff_b = coeff_b_scaling * (v_low * high - v_high[atten] * low) / (high - low);
228 }
229
230 /*
231 * Estimate the (assumed) linear relationship btwn the measured raw value and the voltage
232 * with the previously done measurement when the chip was manufactured.
233 * */
calculate_characterization_coefficients(const adc_calib_parsed_info_t * parsed_data,cali_chars_line_fitting_t * ctx)234 static bool calculate_characterization_coefficients(const adc_calib_parsed_info_t *parsed_data, cali_chars_line_fitting_t *ctx)
235 {
236 switch (parsed_data->version_num) {
237 case 1:
238 ESP_LOGD(TAG, "Calib V1, low%dmV, high%dmV\n", parsed_data->efuse_data.ver1.adc_calib_low, parsed_data->efuse_data.ver1.adc_calib_high);
239
240 characterize_using_two_point(parsed_data->unit_id, parsed_data->atten_level,
241 parsed_data->efuse_data.ver1.adc_calib_high, parsed_data->efuse_data.ver1.adc_calib_low,
242 &(ctx->coeff_a), &(ctx->coeff_b));
243 break;
244 case 2:
245 ESP_LOGD(TAG, "Calib V2, volt%dmV\n", parsed_data->efuse_data.ver2.adc_calib_high);
246 ctx->coeff_a = coeff_a_scaling * parsed_data->efuse_data.ver2.adc_calib_high_voltage /
247 parsed_data->efuse_data.ver2.adc_calib_high;
248 ctx->coeff_b = 0;
249 break;
250 default:
251 return false;
252 break;
253 }
254 return true;
255 }
256