前言
ADC即模擬數(shù)字轉(zhuǎn)換器(Analog-to-digital converter)是用于將模擬形式的連續(xù)信號轉(zhuǎn)換為數(shù)字形式的離散信號的一類設(shè)備。一個(gè)模擬數(shù)字轉(zhuǎn)換器可以提供信號用于測量。與之相對的設(shè)備成為數(shù)字模擬轉(zhuǎn)換器。
例如溫度、壓力、聲音或者圖像等,需要轉(zhuǎn)換成更容易儲存、處理和發(fā)射的數(shù)字形式。那就可以用到ADC了
提示:以下是本篇文章正文內(nèi)容,下面案例可供參考
一、ESP32 ADC相關(guān)介紹
一些 ADC2 引腳用作捆綁引腳(GPIO 0、2、15),因此不能自由使用。
ESP32 DevKitC:由于外部自動(dòng)編程電路,GPIO 0 無法使用。
ESP-WROVER-KIT:GPIO 0、2、4 和 15 不能使用,因?yàn)橛糜诓煌康牡耐獠窟B接。
由于 ADC2 模塊也被 Wi-Fi 使用,因此它們一起使用時(shí)只有一個(gè)可以搶占,這意味著adc2_get_raw()可能會被阻塞直到 Wi-Fi 停止
ESP32 集成了 2 個(gè) SAR(逐次逼近寄存器)ADC,總共支持 18 個(gè)測量通道(模擬使能引腳)。
ADC1,8通道:GPIO32 - GPIO39
ADC2,10個(gè)通道:GPIO0、GPIO2、GPIO4、GPIO12 - GPIO15、GOIO25 - GPIO27
以下介紹通過ADC1進(jìn)行介紹
2、ADC衰減
Vref 是 ESP32 ADC 內(nèi)部用于測量輸入電壓的參考電壓。ESP32 ADC 可以測量從 0 V 到 Vref 的模擬電壓。在不同的芯片中,Vref 不同,中位數(shù)為 1.1 V。為了轉(zhuǎn)換大于 Vref 的電壓,可以在輸入 ADC 之前對輸入電壓進(jìn)行衰減。有 4 種可用的衰減選項(xiàng),衰減越高,可測量的輸入電壓就越高。
3、ADC 轉(zhuǎn)換
ADC 轉(zhuǎn)換是將輸入模擬電壓轉(zhuǎn)換為數(shù)字值。ADC 驅(qū)動(dòng)程序 API (adc1_get_raw()
)提供的 ADC 轉(zhuǎn)換結(jié)果是原始數(shù)據(jù)。Single Read 模式下 ESP32 ADC 原始結(jié)果的分辨率為 12 位。
如果根據(jù)ADC采集的原始數(shù)據(jù)來計(jì)算電壓那可以用Vout(輸出電壓)=Dout(輸出的數(shù)據(jù))*Vmax(最大測量電壓)/Dmax(最大輸出數(shù)據(jù))
ps:如果是帶有ADC校準(zhǔn)位的板子可以直接調(diào)用esp_adc_cal_raw_to_voltage()
來直接讀取輸出電壓,單位為mv
4、ADC 單次讀取
在配置好位寬adc1_config_width()
和衰減adc1_config_channel_atten()
之后就可以直接調(diào)用adc1_get_raw(),
讀取結(jié)果了;值得說明的是官方也提供給了從ULP直接讀取ADC1通道的結(jié)果,但是這個(gè)要在配置的時(shí)候調(diào)用adc1_ulp_enable()
來使能ULP
二、使用步驟
1.接口函數(shù)介紹
(1)設(shè)置通道衰減
esp_err_t adc1_config_channel_atten(adc1_channel_t channel,adc_atten_t atten )
第一個(gè)參數(shù)是設(shè)置ADC通道,第二個(gè)參數(shù)是設(shè)置衰減等級;默認(rèn)衰減是0dB;通過官方給的衰減設(shè)置可以看到,衰減設(shè)置的越大,那么可采集的電壓范圍就越大(通過對輸入電壓進(jìn)行衰減)
返回值:
ESP_OK 成功
ESP_ERR_INVALID_ARG 參數(shù)錯(cuò)誤
(2)配置ADC的捕獲位寬
esp_err_t adc1_config_width( adc_bits_width_t width_bit )
參數(shù):ADC1 的位捕獲寬度
返回值:
ESP_OK 成功
ESP_ERR_INVALID_ARG 參數(shù)錯(cuò)誤
(3)讀取單個(gè)通道上的ADC數(shù)據(jù)
int adc1_get_raw( adc1_channel_t channel)
返回
-1:參數(shù)錯(cuò)誤
其他:ADC1 通道讀數(shù)。
參數(shù)
channel: ADC1 通道讀取
(4)初始化數(shù)字ADC
esp_err_t adc_digi_initialize(const adc_digi_init_config_t *init_config )
參數(shù):數(shù)字ADC的初始化配置adc_digi_init_config_t
在這個(gè)結(jié)構(gòu)體里面;
返回:
-
- ESP_ERR_INVALID_ARG 參數(shù)錯(cuò)誤
-
- ESP_ERR_NOT_FOUND 沒有找到中斷空閑
-
- ESP_ERR_NO_MEM 內(nèi)存不足
-
- ESP_OK 成功
結(jié)構(gòu)體中參數(shù)依次是
1)轉(zhuǎn)換后的數(shù)據(jù)可以存儲的最大長度。
2)在一個(gè)中斷中可以轉(zhuǎn)換的數(shù)據(jù)字節(jié)數(shù)
3)待初始化的ADC1通道列表。
4)需要初始化的ADC2通道列表。
- ESP_OK 成功
(5)通過 DMA 從數(shù)字 ADC 讀取字節(jié)。
esp_err_t adc_digi_read_bytes(uint8_t *buf, uint32_t length_max, uint32_t *out_length, uint32_t timeout_ms);
[out] buf:從 ADC 讀取的緩沖區(qū)。
[in] length_max:從 ADC 讀取的預(yù)期數(shù)據(jù)長度。
[out] out_length:從 ADC 讀取的數(shù)據(jù)的實(shí)際長度。
[in] timeout_ms:等待數(shù)據(jù)的時(shí)間,以毫秒為單位。
返回
ESP_ERR_INVALID_STATE 驅(qū)動(dòng)狀態(tài)無效。通常這意味著 ADC 采樣率快于任務(wù)處理率。
ESP_ERR_TIMEOUT 操作超時(shí)
ESP_OK 成功
(6)啟動(dòng)數(shù)字ADC和 DMA
esp_err_t adc_digi_start(void);
返回
ESP_ERR_INVALID_STATE 驅(qū)動(dòng)狀態(tài)無效。
ESP_OK 成功
(6)設(shè)置數(shù)字控制器
esp_err_t adc_digi_controller_configure(const adc_digi_configuration_t *config);
返回值:
ESP_ERR_INVALID_STATE 驅(qū)動(dòng)狀態(tài)無效。
ESP_ERR_INVALID_ARG 如果參數(shù)組合無效。
ESP_OK 成功
參數(shù)為adc_digi_configuration_t 結(jié)構(gòu)體
結(jié)構(gòu)體中參數(shù)依次是
1)限制ADC轉(zhuǎn)換的時(shí)間,轉(zhuǎn)換完成后是否停止
2)限制ADC轉(zhuǎn)換觸發(fā)器上限1-255
3)ADC通道數(shù)
4)初始化結(jié)構(gòu)體配置參數(shù)
5)采樣頻率611Hz ~ 83333Hz
6)ADC DMA傳輸模式:選擇ADC1、ADC2、ADC1與ADC2、ADC1與ADC2輪流
7)ADC DMA傳輸轉(zhuǎn)換格式:12BIT轉(zhuǎn)換/11BIT轉(zhuǎn)換
對于設(shè)置的輸出格式來說如果設(shè)置輸出12BIT的數(shù)據(jù),那么輸出的16位中存放格式是[15:12] 通道,[11:0]12 位 ADC 數(shù)據(jù) 。注意:對于單次轉(zhuǎn)換模式。
如果設(shè)置的數(shù)是11BIT的數(shù)據(jù)那么存放內(nèi)容就是[15]adc 單元,[14:11]通道,[10:0]11 位 ADC 數(shù)據(jù) 。注意:對于多或交替轉(zhuǎn)換模式。
(7)檢查 ADC 校準(zhǔn)值是否燒入 eFuse。
esp_err_t esp_adc_cal_check_efuse( esp_adc_cal_value_t value_type )
檢查 ADC 參考電壓或兩點(diǎn)值是否已燒到當(dāng)前 ESP32 的 eFuse
返回:
ESP_OK:eFuse 支持校準(zhǔn)模式
ESP_ERR_NOT_SUPPORTED:錯(cuò)誤,eFuse 值未燒入
ESP_ERR_INVALID_ARG:錯(cuò)誤,無效參數(shù)(ESP_ADC_CAL_VAL_DEFAULT_VREF)
參數(shù):
value_type:校準(zhǔn)值的類型(ESP_ADC_CAL_VAL_EFUSE_VREF 或 ESP_ADC_CAL_VAL_EFUSE_TP)
(8)以特定衰減表征 ADC,
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 )
返回:
ESP_ADC_CAL_VAL_EFUSE_VREF:用于表征的 eFuse Vref
ESP_ADC_CAL_VAL_EFUSE_TP:用于表征的兩點(diǎn)值(僅在線性模式下)
ESP_ADC_CAL_VAL_DEFAULT_VREF:用于表征的默認(rèn) Vref
參數(shù):
[in] adc_num:ADC_UNIT_1 或 ADC_UNIT_2
[in] atten: 衰減
[in] bit_width: ADC的位寬配置
[in] default_vref:默認(rèn) ADC 參考電壓,單位 mV
[out] chars: 指向用于存儲 ADC 特征的空結(jié)構(gòu)的指針
(9)將 ADC 讀數(shù)轉(zhuǎn)換為以 mV 為單位的電壓。
uint32_t esp_adc_cal_raw_to_voltage(uint32_t adc_reading, const esp_adc_cal_characteristics_t *chars);
在調(diào)用此函數(shù)之前必須初始化特征結(jié)構(gòu)(調(diào)用 esp_adc_cal_characterize())
返回:
電壓 (mV)
參數(shù):
[in] adc_reading: ADC 讀數(shù)
[in] chars: 指向包含 ADC 特征的初始化結(jié)構(gòu)的指針
(10)直接獲取通道以 mV 為單位的電壓。
esp_err_t esp_adc_cal_get_voltage(adc_channel_t channel, const esp_adc_cal_characteristics_t *chars, uint32_t *voltage);
這個(gè)函數(shù)同(9)函數(shù)一樣都是得到電壓,但是(9)傳參是ADC的讀數(shù),本函數(shù)傳參是ADC通道
參數(shù)
[in] channel:要讀取的 ADC 通道
[in] chars: 指向已初始化 ADC 特征結(jié)構(gòu)的指針
[out] voltage:存儲轉(zhuǎn)換電壓的指針
返回
ESP_OK:ADC 讀取并轉(zhuǎn)換為 mV
ESP_ERR_INVALID_ARG:由于參數(shù)無效導(dǎo)致的錯(cuò)誤
ESP_ERR_INVALID_STATE:讀取結(jié)果無效。嘗試再次閱讀。
2.代碼示例
ADC+DMA
my_adc.c
#include "my_adc.h"
#if CONFIG_IDF_TARGET_ESP32S2
static uint16_t adc1_chan_mask = BIT(0);
static uint16_t adc2_chan_mask = 0;
static adc_channel_t channel[1] = {ADC1_CHANNEL_0};
#endif
#if CONFIG_IDF_TARGET_ESP32
static uint16_t adc1_chan_mask = BIT(0);//
static uint16_t adc2_chan_mask = 0;
static adc_channel_t channel[1] = {ADC1_CHANNEL_0};
#endif
static void continuous_adc_init(uint16_t adc1_chan_mask, uint16_t adc2_chan_mask, adc_channel_t *channel, uint8_t channel_num)
{
adc_digi_init_config_t adc_dma_config = {
.max_store_buf_size = 1024,//可以傳輸?shù)淖止?jié)大小
.conv_num_each_intr = TIMES,//一個(gè)中斷可以轉(zhuǎn)換的字節(jié)數(shù)
.adc1_chan_mask = adc1_chan_mask,//待初始化的通道列表
.adc2_chan_mask = adc2_chan_mask,
};
ESP_ERROR_CHECK(adc_digi_initialize(&adc_dma_config));
adc_digi_configuration_t dig_cfg = {
.conv_limit_en = ADC_CONV_LIMIT_EN,//限制ADC轉(zhuǎn)換的次數(shù),在轉(zhuǎn)換conv_limit_num之后就會停止,選擇是否使能
.conv_limit_num = 250,//設(shè)置ADC觸發(fā)器的上限
.sample_freq_hz = 10 * 1000,//ADC采樣頻率
.conv_mode = ADC_CONV_MODE,//選擇DMA轉(zhuǎn)換模式,選擇使用哪個(gè)ADC或者都使用
.format = ADC_OUTPUT_TYPE,//設(shè)置數(shù)據(jù)輸出格式
};
adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0};//將使用ADC通道的配置列表
dig_cfg.pattern_num = channel_num;
for (int i = 0; i < channel_num; i++) {
uint8_t unit = GET_UNIT(channel[i]);
uint8_t ch = channel[i] & 0x7;//ADC通道,總共0-7八個(gè)通道,只看后三位
adc_pattern[i].atten = ADC_ATTEN_DB_11;//衰減
adc_pattern[i].channel = ch;//通道
adc_pattern[i].unit = unit;//索引
adc_pattern[i].bit_width = SOC_ADC_DIGI_MAX_BITWIDTH;//設(shè)置位寬為最大寬度
ESP_LOGI(TAG, "adc_pattern[%d].atten is :%x", i, adc_pattern[i].atten);
ESP_LOGI(TAG, "adc_pattern[%d].channel is :%x", i, adc_pattern[i].channel);
ESP_LOGI(TAG, "adc_pattern[%d].unit is :%x", i, adc_pattern[i].unit);
}
dig_cfg.adc_pattern = adc_pattern;
ESP_ERROR_CHECK(adc_digi_controller_configure(&dig_cfg));
}
bool check_valid_data(const adc_digi_output_data_t *data)//判斷數(shù)據(jù)是不是有效的
{
const unsigned int unit = data->type2.unit;
if (unit > 2) {
return false;
}
if (data->type2.channel >= SOC_ADC_CHANNEL_NUM(unit)) {
return false;//判斷通道數(shù)是不是配置多了
}
return true;
}
void my_adc_dma_init(void)
{
continuous_adc_init(adc1_chan_mask, adc2_chan_mask, channel, sizeof(channel) / sizeof(adc_channel_t));//初始化配置
}
main.c文章來源:http://www.zghlxwxcb.cn/news/detail-417841.html
#include "my_adc.h"
// #define LEDC_MAX_DUTY (8191)
// #define LEDC_FADE_TIME (1000)
void app_main(void)
{
esp_err_t ret;
uint32_t ret_num = 0;
uint8_t result[TIMES] = {0};
memset(result, 0xcc, TIMES);//把數(shù)組result里面的times個(gè)值都初始化成0xcc=204;
// continuous_adc_init(adc1_chan_mask, adc2_chan_mask, channel, sizeof(channel) / sizeof(adc_channel_t));//初始化配置
my_adc_dma_init();
adc_digi_start();//啟動(dòng)轉(zhuǎn)換
while (1)
{
ret = adc_digi_read_bytes(result, TIMES, &ret_num, ADC_MAX_DELAY);//讀取轉(zhuǎn)換數(shù)據(jù),數(shù)據(jù)存在result里面,預(yù)期是256個(gè),實(shí)際是ret_num個(gè)。轉(zhuǎn)換超時(shí)時(shí)間限制是最大
//判斷adc_digi_read_bytes返回的參數(shù),成功、驅(qū)動(dòng)設(shè)備未安裝(殘陽速率大于任務(wù)處理速率)、超時(shí)
if (ret == ESP_OK || ret == ESP_ERR_INVALID_STATE)
{
if (ret == ESP_ERR_INVALID_STATE)
{
/**
* 對于普通的只打印任務(wù)處理速率還是很快的
*
* @note 1
* Issue:
* As an example, we simply print the result out, which is super slow. Therefore the conversion is too
* fast for the task to handle. In this condition, some conversion results lost.
*
* Reason:
* When this error occurs, you will usually see the task watchdog timeout issue also.
* Because the conversion is too fast, whereas the task calling `adc_digi_read_bytes` is slow.
* So `adc_digi_read_bytes` will hardly block. Therefore Idle Task hardly has chance to run. In this
* example, we add a `vTaskDelay(1)` below, to prevent the task watchdog timeout.
*
* Solution:
* Either decrease the conversion speed, or increase the frequency you call `adc_digi_read_bytes`
*/
printf("ERROR:請降低轉(zhuǎn)換速度,或者增加調(diào)用adc_digi_read_bytes的頻率\n" );
}
for (int i = 0; i < ret_num; i += ADC_RESULT_BYTE)
{
adc_digi_output_data_t *p = (void*)&result[i]; //把result中的數(shù)據(jù)傳到adc_digi_output_data_t中,(void*)就是可以把數(shù)據(jù)轉(zhuǎn)換成任意類型的數(shù)據(jù)
#if CONFIG_IDF_TARGET_ESP32
ESP_LOGI(TAG, "Unit: %d, Channel: %d, Value: %x", 1, p->type1.channel, p->type1.data);//如果選擇的是ESP32那么輸出使用的哪個(gè)ADC的哪個(gè)通道以及那種類型的數(shù)據(jù)
#else
if (ADC_CONV_MODE == ADC_CONV_BOTH_UNIT || ADC_CONV_MODE == ADC_CONV_ALTER_UNIT) //判斷ADC轉(zhuǎn)換模式時(shí)ADC1、2,或者是輪流轉(zhuǎn)換
{
//判斷數(shù)據(jù)是否有效
if (check_valid_data(p))
{
ESP_LOGI(TAG, "Unit: %d,_Channel: %d, Value: %x", p->type2.unit+1, p->type2.channel, p->type2.data);//輸出有效數(shù)據(jù)
} else
{
// abort();
ESP_LOGI(TAG, "Invalid data [%d_%d_%x]", p->type2.unit+1, p->type2.channel, p->type2.data);//輸出數(shù)據(jù)無效
}
}
#if CONFIG_IDF_TARGET_ESP32S2
else if (ADC_CONV_MODE == ADC_CONV_SINGLE_UNIT_2)
{
ESP_LOGI(TAG, "Unit: %d, Channel: %d, Value: %x", 2, p->type1.channel, p->type1.data);
} else if (ADC_CONV_MODE == ADC_CONV_SINGLE_UNIT_1)
{
ESP_LOGI(TAG, "Unit: %d, Channel: %d, Value: %d", 1, p->type1.channel, p->type1.data);
}
#endif //#if CONFIG_IDF_TARGET_ESP32S2
#endif
}
//See `note 1`
vTaskDelay(1);//程序運(yùn)行太快,防止任務(wù)看門狗超時(shí)
} else if (ret == ESP_ERR_TIMEOUT) {
/**
* ``ESP_ERR_TIMEOUT``: If ADC conversion is not finished until Timeout, you'll get this return error.
* Here we set Timeout ``portMAX_DELAY``, so you'll never reach this branch.
*/
ESP_LOGW(TAG, "No data, increase timeout or reduce conv_num_each_intr");
vTaskDelay(1000);
}
}
adc_digi_stop();
ret = adc_digi_deinitialize();
assert(ret == ESP_OK);
}
總結(jié)
完整代碼工程在這里:代碼文章來源地址http://www.zghlxwxcb.cn/news/detail-417841.html
到了這里,關(guān)于esp32學(xué)習(xí)筆記(4)——adc的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!