1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/esp32/periph/adc.c
2023-04-17 07:32:48 +02:00

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");
}