mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
274 lines
8.7 KiB
C
274 lines
8.7 KiB
C
/*
|
|
* Copyright (C) 2022 Gunar Schorcht
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser General
|
|
* Public License v2.1. See the file LICENSE in the top level directory for more
|
|
* details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup cpu_esp32
|
|
* @ingroup drivers_periph_adc
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Low-level ADC driver implementation
|
|
*
|
|
* All ESP32x SoCs have two SAR ADC units each. However, these have
|
|
* functionalities as well as specific properties that vary between the
|
|
* ESP32x SoC and therefore require different handling for each ESP32x SoC.
|
|
* This is already taken into account in the high-level API of the ESP-IDF.
|
|
* To avoid having to reimplement these specifics and the different handling,
|
|
* the high-level API of the ESP-IDF is used directly for the ADC peripherals.
|
|
*
|
|
* @author Gunar Schorcht <gunar@schorcht.net>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <assert.h>
|
|
|
|
#include "board.h"
|
|
#include "periph/adc.h"
|
|
|
|
#include "adc_arch.h"
|
|
#include "adc_arch_private.h"
|
|
#include "esp_common.h"
|
|
#include "gpio_arch.h"
|
|
|
|
#include "driver/adc.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
/* forward declarations of internal functions */
|
|
static bool _adc_conf_check(void);
|
|
static void _adc1_ctrl_init(void);
|
|
static void _adc2_ctrl_init(void);
|
|
|
|
/* external variable declarations */
|
|
extern const gpio_t _gpio_rtcio_map[];
|
|
|
|
/*
|
|
* Structure for mapping RIOT's ADC resolutions to ESP-IDF resolutions
|
|
* of the according ESP32x SoC.
|
|
*/
|
|
typedef struct {
|
|
adc_bits_width_t res; /* used ESP-IDF resolution */
|
|
unsigned shift; /* bit shift number for results */
|
|
} _adc_esp_res_map_t;
|
|
|
|
/*
|
|
* Table for resolution mapping
|
|
*/
|
|
_adc_esp_res_map_t _adc_esp_res_map[] = {
|
|
#if defined(CPU_FAM_ESP32)
|
|
{ .res = ADC_WIDTH_BIT_9, .shift = 3 }, /* ADC_RES_6BIT */
|
|
{ .res = ADC_WIDTH_BIT_9, .shift = 1 }, /* ADC_RES_8BIT */
|
|
{ .res = ADC_WIDTH_BIT_10, .shift = 0 }, /* ADC_RES_10BIT */
|
|
{ .res = ADC_WIDTH_BIT_12, .shift = 0 }, /* ADC_RES_12BIT */
|
|
{ .res = ADC_WIDTH_MAX }, /* ADC_RES_14BIT */
|
|
{ .res = ADC_WIDTH_MAX }, /* ADC_RES_16BIT */
|
|
#elif SOC_ADC_MAX_BITWIDTH == 12
|
|
{ .res = ADC_WIDTH_BIT_12, .shift = 6 }, /* ADC_RES_6BIT */
|
|
{ .res = ADC_WIDTH_BIT_12, .shift = 4 }, /* ADC_RES_8BIT */
|
|
{ .res = ADC_WIDTH_BIT_12, .shift = 2 }, /* ADC_RES_10BIT */
|
|
{ .res = ADC_WIDTH_BIT_12, .shift = 0 }, /* ADC_RES_12BIT */
|
|
{ .res = ADC_WIDTH_MAX }, /* ADC_RES_14BIT */
|
|
{ .res = ADC_WIDTH_MAX }, /* ADC_RES_16BIT */
|
|
#elif SOC_ADC_MAX_BITWIDTH == 13
|
|
{ .res = ADC_WIDTH_BIT_13, .shift = 7 }, /* ADC_RES_6BIT */
|
|
{ .res = ADC_WIDTH_BIT_13, .shift = 5 }, /* ADC_RES_8BIT */
|
|
{ .res = ADC_WIDTH_BIT_13, .shift = 3 }, /* ADC_RES_10BIT */
|
|
{ .res = ADC_WIDTH_BIT_13, .shift = 1 }, /* ADC_RES_12BIT */
|
|
{ .res = ADC_WIDTH_MAX }, /* ADC_RES_14BIT */
|
|
{ .res = ADC_WIDTH_MAX }, /* ADC_RES_16BIT */
|
|
#endif
|
|
};
|
|
|
|
static bool _adc_module_initialized = false;
|
|
|
|
static inline void _adc1_ctrl_init(void)
|
|
{
|
|
/* nothing to do for the moment */
|
|
}
|
|
|
|
static inline void _adc2_ctrl_init(void)
|
|
{
|
|
/* nothing to do for the moment */
|
|
}
|
|
|
|
int adc_init(adc_t line)
|
|
{
|
|
DEBUG("[adc] line=%u\n", line);
|
|
|
|
if (line >= ADC_NUMOF) {
|
|
return -1;
|
|
}
|
|
|
|
if (!_adc_module_initialized) {
|
|
/* do some configuration checks */
|
|
if (!_adc_conf_check()) {
|
|
return -1;
|
|
}
|
|
_adc_module_initialized = true;
|
|
}
|
|
|
|
/* get the RTCIO pin number for the given GPIO defined as ADC channel */
|
|
uint8_t rtcio = _gpio_rtcio_map[adc_channels[line]];
|
|
|
|
/* check whether the GPIO is avalid ADC channel pin */
|
|
if (rtcio == RTCIO_NA) {
|
|
return -1;
|
|
}
|
|
|
|
/* check whether the pin is not used for other purposes */
|
|
if (gpio_get_pin_usage(_adc_hw[rtcio].gpio) != _GPIO) {
|
|
LOG_TAG_ERROR("adc", "GPIO%d is used for %s and cannot be used as "
|
|
"ADC input\n", _adc_hw[rtcio].gpio,
|
|
gpio_get_pin_usage_str(_adc_hw[rtcio].gpio));
|
|
return -1;
|
|
}
|
|
|
|
if (_adc_hw[rtcio].adc_ctrl == ADC_UNIT_1) {
|
|
/* ensure compatibility of given adc_channel_t with adc1_channel_t */
|
|
assert(_adc_hw[rtcio].adc_channel < (adc_channel_t)ADC1_CHANNEL_MAX);
|
|
/* initialize the ADC1 unit if needed */
|
|
_adc1_ctrl_init();
|
|
/* set the attenuation and configure its associated GPIO pin mux */
|
|
adc1_config_channel_atten((adc1_channel_t)_adc_hw[rtcio].adc_channel,
|
|
ADC_ATTEN_DB_11);
|
|
}
|
|
else if (_adc_hw[rtcio].adc_ctrl == ADC_UNIT_2) {
|
|
/* ensure compatibility of given adc_channel_t with adc2_channel_t */
|
|
assert(_adc_hw[rtcio].adc_channel < (adc_channel_t)ADC2_CHANNEL_MAX);
|
|
/* initialize the ADC2 unit if needed */
|
|
_adc2_ctrl_init();
|
|
/* set the attenuation and configure its associated GPIO pin mux */
|
|
adc2_config_channel_atten((adc2_channel_t)_adc_hw[rtcio].adc_channel,
|
|
ADC_ATTEN_DB_11);
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
|
|
/* set pin usage type */
|
|
gpio_set_pin_usage(_adc_hw[rtcio].gpio, _ADC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32_t adc_sample(adc_t line, adc_res_t res)
|
|
{
|
|
DEBUG("[adc] line=%u res=%u\n", line, res);
|
|
|
|
if (_adc_esp_res_map[res].res == ADC_WIDTH_MAX) {
|
|
return -1;
|
|
}
|
|
|
|
uint8_t rtcio = _gpio_rtcio_map[adc_channels[line]];
|
|
int raw;
|
|
|
|
if (_adc_hw[rtcio].adc_ctrl == ADC_UNIT_1) {
|
|
adc1_config_width(_adc_esp_res_map[res].res);
|
|
/* ensure compatibility of given adc_channel_t with adc1_channel_t */
|
|
assert(_adc_hw[rtcio].adc_channel < (adc_channel_t)ADC1_CHANNEL_MAX);
|
|
raw = adc1_get_raw((adc1_channel_t)_adc_hw[rtcio].adc_channel);
|
|
if (raw < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
else if (_adc_hw[rtcio].adc_ctrl == ADC_UNIT_2) {
|
|
/* ensure compatibility of given adc_channel_t with adc2_channel_t */
|
|
assert(_adc_hw[rtcio].adc_channel < (adc_channel_t)ADC2_CHANNEL_MAX);
|
|
if (adc2_get_raw((adc2_channel_t)_adc_hw[rtcio].adc_channel,
|
|
_adc_esp_res_map[res].res, &raw) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return raw >> _adc_esp_res_map[res].shift;
|
|
}
|
|
|
|
int adc_set_attenuation(adc_t line, adc_atten_t atten)
|
|
{
|
|
DEBUG("[adc] line=%u atten=%u\n", line, atten);
|
|
|
|
uint8_t rtcio = _gpio_rtcio_map[adc_channels[line]];
|
|
|
|
assert(rtcio != RTCIO_NA);
|
|
|
|
if (_adc_hw[rtcio].adc_ctrl == ADC_UNIT_1) {
|
|
/* ensure compatibility of given adc_channel_t with adc1_channel_t */
|
|
assert(_adc_hw[rtcio].adc_channel < (adc_channel_t)ADC1_CHANNEL_MAX);
|
|
return adc1_config_channel_atten((adc1_channel_t)_adc_hw[rtcio].adc_channel, atten);
|
|
}
|
|
else if (_adc_hw[rtcio].adc_ctrl == ADC_UNIT_2) {
|
|
/* ensure compatibility of given adc_channel_t with adc2_channel_t */
|
|
assert(_adc_hw[rtcio].adc_channel < (adc_channel_t)ADC2_CHANNEL_MAX);
|
|
return adc2_config_channel_atten((adc2_channel_t)_adc_hw[rtcio].adc_channel, atten);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int adc_line_vref_to_gpio(adc_t line, gpio_t gpio)
|
|
{
|
|
uint8_t rtcio_vref = _gpio_rtcio_map[adc_channels[line]];
|
|
uint8_t rtcio_out = _gpio_rtcio_map[gpio];
|
|
|
|
/* both the ADC line and the GPIO for the output must be ADC channels */
|
|
assert(rtcio_vref != RTCIO_NA);
|
|
assert(rtcio_out != RTCIO_NA);
|
|
/* avoid compilation problems with NDEBUG defined */
|
|
(void)rtcio_out;
|
|
|
|
/* the GPIO for the output must be a channel of ADC2 */
|
|
assert(_adc_hw[rtcio_out].adc_ctrl == ADC_UNIT_2);
|
|
/* given ADC line has to be a channel of ADC2 */
|
|
assert(_adc_hw[rtcio_vref].adc_ctrl == ADC_UNIT_2);
|
|
|
|
esp_err_t res = ESP_OK;
|
|
|
|
if (_adc_hw[rtcio_vref].adc_ctrl == ADC_UNIT_1) {
|
|
res = adc_vref_to_gpio(ADC_UNIT_1, gpio);
|
|
}
|
|
else if (_adc_hw[rtcio_vref].adc_ctrl == ADC_UNIT_2) {
|
|
res = adc_vref_to_gpio(ADC_UNIT_2, gpio);
|
|
}
|
|
if (res != ESP_OK) {
|
|
LOG_TAG_ERROR("adc", "Could not route Vref of ADC line %d to GPIO%d\n",
|
|
line, gpio);
|
|
return -1;
|
|
}
|
|
else {
|
|
LOG_TAG_ERROR("adc", "Vref of ADC%d can now be measured at GPIO %d\n",
|
|
_adc_hw[rtcio_vref].adc_ctrl, gpio);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static bool _adc_conf_check(void)
|
|
{
|
|
for (unsigned i = 0; i < ADC_NUMOF; i++) {
|
|
if (_gpio_rtcio_map[adc_channels[i]] == RTCIO_NA) {
|
|
LOG_TAG_ERROR("adc", "GPIO%d cannot be used as ADC line\n",
|
|
adc_channels[i]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void adc_print_config(void)
|
|
{
|
|
printf("\tADC\t\tpins=[ ");
|
|
#if defined(ADC_GPIOS)
|
|
for (unsigned i = 0; i < ADC_NUMOF; i++) {
|
|
printf("%d ", adc_channels[i]);
|
|
}
|
|
#endif /* defined(ADC_GPIOS) */
|
|
printf("]\n");
|
|
}
|