一、创建一个新项目
1、打开ESP—IDF插件
2、新建文件
3、选择例程
一直下滑,知道看见下图中的选项
二、例程代码分析
首先,因为是例程代码,代码中有大量的条件编译。大家看的时候可以分块去看
1、ADC初始化
adc_oneshot_unit_handle_t adc1_handle;
定义了一个ADC_handle的变量,这个变量的作用是作为一个句柄
句柄(handle)在软件开发中是一种常见的抽象机制,用于管理和引用底层资源或对象。在处理硬件资源如ADC(模数转换器)时,句柄提供了一种方便且安全的方式来操作这些资源。
再具体的介绍大家自己在网上搜索一下吧,我在这里就不过多陈述了
adc_oneshot_unit_init_cfg_t init_config1 = {
.unit_id = ADC_UNIT_1,
};
adc_oneshot_unit_init_cfg_t
是一个用于配置ADC单次转换模式的结构体类型。
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
创建一个新的ADC1单元并获取句柄。
ESP_ERROR_CHECK的作用
是检查函数的返回值,在返回值错误时终止程序
adc_oneshot_new_unit
这个函数的作用:
1、创建新的ADC单元:
- 该函数会为一个新的ADC单元分配必要的资源,并将其配置为单次转换模式。
- 这个过程可能包括初始化底层硬件、设置默认配置以及分配内存等。
2、返回句柄:
- 函数成功后,会返回一个
adc_oneshot_unit_handle_t
类型的句柄。这个句柄是用来管理和操作新创建的ADC单元的唯一标识符。 - 通过这个句柄,可以调用其他API函数来进行进一步的配置、启动转换、读取结果等操作。
3、错误处理:
- 如果在创建过程中发生错误(例如资源不足、硬件故障等),函数可能会返回一个错误码,而不是有效的句柄。开发者可以通过检查返回值来判断是否成功创建了ADC单元。
ADC Init代码模块
//-------------ADC1 Init---------------//
adc_oneshot_unit_handle_t adc1_handle;
adc_oneshot_unit_init_cfg_t init_config1 = {
.unit_id = ADC_UNIT_1,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
2、ADC配置
typedef struct {
adc_atten_t atten; ///< ADC attenuation
adc_bitwidth_t bitwidth; ///< ADC conversion result bits
} adc_oneshot_chan_cfg_t;
在这里主要配置了ADC通道的位宽(分辨率)和衰减(增益)
其中的位宽和衰减的值大家自己跳转看一下吧
位宽(Bitwidth)
- 成员:
bitwidth
- 类型:通常是
adc_bitwidth_t
类型的枚举值。 - 作用:指定ADC转换结果的位数,即ADC的分辨率。常见的位宽有12位、10位等。
- 示例:
ADC_BITWIDTH_DEFAULT
通常是一个预定义的枚举值,表示默认的位宽。在ESP32中,默认的位宽通常是12位。
衰减(Attenuation)
- 成员:
atten
- 类型:通常是
adc_atten_t
类型的枚举值。 - 作用:设置输入信号的衰减(增益),以适应ADC的输入范围。通过衰减,可以扩展ADC的测量范围,允许测量更高电压的信号。
- 示例:
EXAMPLE_ADC_ATTEN
可能是一个预定义的枚举值,用于示例代码中的衰减设置。常见的衰减值包括ADC_ATTEN_DB_0
、ADC_ATTEN_DB_2_5
、ADC_ATTEN_DB_6
和ADC_ATTEN_DB_11
等。
adc_oneshot_config_channel
以通道初始配置函数为例
给大家解析一下,其他地方的配置函数逻辑都是相同的,不同的就是参数了
handle && config
这是前面传过来的两个参数“句柄”和“通道基本参数配置信息”,这里是通过逻辑与的方式验证这两个指针是否有空,
ESP_ERR_INVALID_ARG
错误码,如果前面的逻辑与结果为0了,那么就返回这个错误码交由传参函数处理
TAG, "invalid argument: null pointer"
TAG,日志标签,一般都在文件的最前面定义了,大家可以自行查看
"invalid argument: null pointer",这个就是要打印出来的字符串,下面圈出来的是打印函数
大家跳转一下自己看看
ESP_RETURN_ON_FALSE(handle && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
这里是判断“衰减参数值”是否有效,看他是否小于常量 4,如果不是的话,就不属于有效值
衰减参数的数值
SOC_ADC_ATTEN_NUM代表的数值
ESP_RETURN_ON_FALSE(config->atten < SOC_ADC_ATTEN_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid attenuation");
下面这两个就是大同小异了,大家自己体会吧。加油!!
ESP_RETURN_ON_FALSE(((config->bitwidth >= SOC_ADC_RTC_MIN_BITWIDTH && config->bitwidth <= SOC_ADC_RTC_MAX_BITWIDTH) || config->bitwidth == ADC_BITWIDTH_DEFAULT), ESP_ERR_INVALID_ARG, TAG, "invalid bitwidth");
ESP_RETURN_ON_FALSE(channel < SOC_ADC_CHANNEL_NUM(handle->unit_id), ESP_ERR_INVALID_ARG, TAG, "invalid channel");
adc_oneshot_config_channel函数
esp_err_t adc_oneshot_config_channel(adc_oneshot_unit_handle_t handle, adc_channel_t channel, const adc_oneshot_chan_cfg_t *config)
{
ESP_RETURN_ON_FALSE(handle && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(config->atten < SOC_ADC_ATTEN_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid attenuation");
ESP_RETURN_ON_FALSE(((config->bitwidth >= SOC_ADC_RTC_MIN_BITWIDTH && config->bitwidth <= SOC_ADC_RTC_MAX_BITWIDTH) || config->bitwidth == ADC_BITWIDTH_DEFAULT), ESP_ERR_INVALID_ARG, TAG, "invalid bitwidth");
ESP_RETURN_ON_FALSE(channel < SOC_ADC_CHANNEL_NUM(handle->unit_id), ESP_ERR_INVALID_ARG, TAG, "invalid channel");
s_adc_io_init(handle->unit_id, channel);
adc_oneshot_hal_ctx_t *hal = &(handle->hal);
adc_oneshot_hal_chan_cfg_t cfg = {
.atten = config->atten,
.bitwidth = (config->bitwidth == ADC_BITWIDTH_DEFAULT) ? SOC_ADC_RTC_MAX_BITWIDTH : config->bitwidth,
};
portENTER_CRITICAL(&rtc_spinlock);
adc_oneshot_hal_channel_config(hal, &cfg, channel);
if (handle->ulp_mode) {
adc_oneshot_hal_setup(hal, channel);
}
portEXIT_CRITICAL(&rtc_spinlock);
return ESP_OK;
}
ADC Config代码模块
//-------------ADC1 Config---------------//
adc_oneshot_chan_cfg_t config = {
.bitwidth = ADC_BITWIDTH_DEFAULT,
.atten = EXAMPLE_ADC_ATTEN,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, EXAMPLE_ADC1_CHAN0, &config));
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, EXAMPLE_ADC1_CHAN1, &config));
3、ADC 校准初始化
adc_cali_handle_t adc1_cali_chan0_handle = NULL;
定义用来包含是否校准成功的标志的句柄,如果成功就接收“TRUE”,如果不成功继续保持NULL
bool do_calibration1_chan0 = example_adc_calibration_init(ADC_UNIT_1, EXAMPLE_ADC1_CHAN0, EXAMPLE_ADC_ATTEN, &adc1_cali_chan0_handle);
“2、ADC配置”中是配置通道的参数,这里是初始化通道的参数
这个是example_adc_calibration_init函数,这个函数的基本逻辑是,判断曲性校准和线性校准哪一个宏定义为1就执行哪个,如果都为一就优先曲性校准,如果都没有就默认校准失败
最下面有三种根据校准情况所对应的三种打印具体解析可以看后面“7、ADC校准函数”
ADC Calibration Init代码模块
//-------------ADC1 Calibration Init---------------//
adc_cali_handle_t adc1_cali_chan0_handle = NULL;
adc_cali_handle_t adc1_cali_chan1_handle = NULL;
bool do_calibration1_chan0 = example_adc_calibration_init(ADC_UNIT_1, EXAMPLE_ADC1_CHAN0, EXAMPLE_ADC_ATTEN, &adc1_cali_chan0_handle);
bool do_calibration1_chan1 = example_adc_calibration_init(ADC_UNIT_1, EXAMPLE_ADC1_CHAN1, EXAMPLE_ADC_ATTEN, &adc1_cali_chan1_handle);
4、ADC2条件编译
条件编译,若果此处为1,则编译ADC2,如果为0 则不进行编译
5、while(1)中的ADC读取
框1和框2都是ADC1进行读取的过程、框3是ADC2进行读取的过程
while (1) {
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, EXAMPLE_ADC1_CHAN0, &adc_raw[0][0]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN0, adc_raw[0][0]);
if (do_calibration1_chan0) {
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_chan0_handle, adc_raw[0][0], &voltage[0][0]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Cali Voltage: %d mV", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN0, voltage[0][0]);
}
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, EXAMPLE_ADC1_CHAN1, &adc_raw[0][1]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN1, adc_raw[0][1]);
if (do_calibration1_chan1) {
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_chan1_handle, adc_raw[0][1], &voltage[0][1]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Cali Voltage: %d mV", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN1, voltage[0][1]);
}
vTaskDelay(pdMS_TO_TICKS(1000));
#if EXAMPLE_USE_ADC2
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_ADC2_CHAN0, &adc_raw[1][0]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", ADC_UNIT_2 + 1, EXAMPLE_ADC2_CHAN0, adc_raw[1][0]);
if (do_calibration2) {
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc2_cali_handle, adc_raw[1][0], &voltage[1][0]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Cali Voltage: %d mV", ADC_UNIT_2 + 1, EXAMPLE_ADC2_CHAN0, voltage[1][0]);
}
vTaskDelay(pdMS_TO_TICKS(1000));
#endif //#if EXAMPLE_USE_ADC2
}
6、资源释放模块 //Tear Down
这里主要的作用是释放之前的ADC资源和ADC校准资源,这里都是对句柄直接操作的,这也是句柄的一个作用
//Tear Down
ESP_ERROR_CHECK(adc_oneshot_del_unit(adc1_handle));
if (do_calibration1_chan0) {
example_adc_calibration_deinit(adc1_cali_chan0_handle);
}
if (do_calibration1_chan1) {
example_adc_calibration_deinit(adc1_cali_chan1_handle);
}
如果使用了ADC2,就必须也对此进行资源释放
7、ADC校准函数 对应着“3、ADC校准初始化”
adc_cali_handle_t handle = NULL;
这里定义定义了一个句柄
esp_err_t ret = ESP_FAIL;
bool calibrated = false;
这个变量用来记录 从ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);得到的返回值:
然后根据ret的状态值,决定给 calibrated 的赋值,大家自己在代码中跳转一下,看看各种标示被定义的状态值,以此来更加熟悉运行原理
接下来就是上图中的两种校准模式了,一种曲性一种线性,主要流程就是线配置,然后初始化,和stm32中是一样的其实,stm32中的ADC也是先定义一个指针,指向各种基本配置,然后进行Init初始化,之后进行校准。大概的原理都是一样的,只是换了一种编译环境,换了一套代码。
ADC校准函数模块
/*---------------------------------------------------------------
ADC Calibration
---------------------------------------------------------------*/
static bool example_adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
{
adc_cali_handle_t handle = NULL;
esp_err_t ret = ESP_FAIL;
bool calibrated = false;
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
if (!calibrated) {
ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting");
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = unit,
.chan = channel,
.atten = atten,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
if (ret == ESP_OK) {
calibrated = true;
}
}
#endif
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
if (!calibrated) {
ESP_LOGI(TAG, "calibration scheme version is %s", "Line Fitting");
adc_cali_line_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
if (ret == ESP_OK) {
calibrated = true;
}
}
#endif
*out_handle = handle;
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Calibration Success");
} else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) {
ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");
} else {
ESP_LOGE(TAG, "Invalid arg or no memory");
}
return calibrated;
}
8、反初始化和释放校准的函数
详细解释
1、条件编译:
- 该函数使用了条件编译来决定使用哪种校准方案进行反初始化。
- 如果定义了
ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
,则执行曲线拟合校准方案的反初始化。 - 如果定义了
ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
,则执行线性拟合校准方案的反初始化。
2、日志记录:
- 使用
ESP_LOGI
宏记录一条信息日志,指示正在注销哪种校准方案。 - 日志内容为
"deregister %s calibration scheme"
,其中%s
会被替换为具体的校准方案名称(如 "Curve Fitting" 或 "Line Fitting")。
3、删除校准方案:
曲线拟合校准方案:
ESP_ERROR_CHECK(adc_cali_delete_scheme_curve_fitting(handle));
- 调用
adc_cali_delete_scheme_curve_fitting
函数来删除曲线拟合校准方案,并释放相关资源。 handle
是之前创建的校准句柄。ESP_ERROR_CHECK
宏用于检查函数调用是否成功。如果返回值不是ESP_OK
,则会记录错误信息并终止程序。
线性拟合校准方案:
ESP_ERROR_CHECK(adc_cali_delete_scheme_line_fitting(handle));
- 调用
adc_cali_delete_scheme_line_fitting
函数来删除线性拟合校准方案,并释放相关资源。 handle
是之前创建的校准句柄。ESP_ERROR_CHECK
宏用于检查函数调用是否成功。如果返回值不是ESP_OK
,则会记录错误信息并终止程序。
具体作用
- 反初始化校准方案:根据支持的校准方案(曲线拟合或线性拟合),删除相应的校准方案并释放相关资源。
- 日志记录:记录注销校准方案的信息,便于调试和跟踪。
- 错误处理:使用
ESP_ERROR_CHECK
宏确保操作成功,否则记录错误信息并终止程序。
反初始化和校准代码模块
static void example_adc_calibration_deinit(adc_cali_handle_t handle)
{
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
ESP_LOGI(TAG, "deregister %s calibration scheme", "Curve Fitting");
ESP_ERROR_CHECK(adc_cali_delete_scheme_curve_fitting(handle));
#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
ESP_LOGI(TAG, "deregister %s calibration scheme", "Line Fitting");
ESP_ERROR_CHECK(adc_cali_delete_scheme_line_fitting(handle));
#endif
}