Skip to content

Commit a41c6da

Browse files
committed
Add readAnalogMilliVoltsRX/TX with calibration and cache ADC handles
1 parent f82cd4c commit a41c6da

5 files changed

Lines changed: 237 additions & 23 deletions

File tree

src/M5UnitComponent.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,11 @@ bool Component::readAnalogRX(uint16_t& v)
341341
return adapter()->readAnalogRX(v) == m5::hal::error::error_t::OK;
342342
}
343343

344+
bool Component::readAnalogMilliVoltsRX(uint32_t& mv)
345+
{
346+
return adapter()->readAnalogMilliVoltsRX(mv) == m5::hal::error::error_t::OK;
347+
}
348+
344349
bool Component::pulseInRX(uint32_t& duration, const int state, const uint32_t timeout_us)
345350
{
346351
return adapter()->pulseInRX(duration, state, timeout_us) == m5::hal::error::error_t::OK;
@@ -371,6 +376,11 @@ bool Component::readAnalogTX(uint16_t& v)
371376
return adapter()->readAnalogTX(v) == m5::hal::error::error_t::OK;
372377
}
373378

379+
bool Component::readAnalogMilliVoltsTX(uint32_t& mv)
380+
{
381+
return adapter()->readAnalogMilliVoltsTX(mv) == m5::hal::error::error_t::OK;
382+
}
383+
374384
bool Component::pulseInTX(uint32_t& duration, const int state, const uint32_t timeout_us)
375385
{
376386
return adapter()->pulseInTX(duration, state, timeout_us) == m5::hal::error::error_t::OK;

src/M5UnitComponent.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,13 +448,15 @@ class Component {
448448
bool readDigitalRX(bool& high);
449449
bool writeAnalogRX(const uint16_t v);
450450
bool readAnalogRX(uint16_t& v);
451+
bool readAnalogMilliVoltsRX(uint32_t& mv);
451452
bool pulseInRX(uint32_t& duration, const int state, const uint32_t timeout_us = 1000000);
452453

453454
bool pinModeTX(const gpio::Mode m);
454455
bool writeDigitalTX(const bool high);
455456
bool readDigitalTX(bool& high);
456457
bool writeAnalogTX(const uint16_t v);
457458
bool readAnalogTX(uint16_t& v);
459+
bool readAnalogMilliVoltsTX(uint32_t& mv);
458460
bool pulseInTX(uint32_t& duration, const int state, const uint32_t timeout_us = 1000000);
459461
///@endcond
460462

src/m5_unit_component/adapter_base.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ class Adapter {
8484
{
8585
return m5::hal::error::error_t::UNKNOWN_ERROR;
8686
}
87+
virtual m5::hal::error::error_t readAnalogMilliVoltsRX(uint32_t&)
88+
{
89+
return m5::hal::error::error_t::UNKNOWN_ERROR;
90+
}
8791
virtual m5::hal::error::error_t pulseInRX(uint32_t&, const int, const uint32_t)
8892
{
8993
return m5::hal::error::error_t::UNKNOWN_ERROR;
@@ -109,6 +113,10 @@ class Adapter {
109113
{
110114
return m5::hal::error::error_t::UNKNOWN_ERROR;
111115
}
116+
virtual m5::hal::error::error_t readAnalogMilliVoltsTX(uint32_t&)
117+
{
118+
return m5::hal::error::error_t::UNKNOWN_ERROR;
119+
}
112120
virtual m5::hal::error::error_t pulseInTX(uint32_t&, const int, const uint32_t)
113121
{
114122
return m5::hal::error::error_t::UNKNOWN_ERROR;
@@ -199,6 +207,10 @@ class Adapter {
199207
{
200208
return _impl->readAnalogRX(v);
201209
}
210+
inline m5::hal::error::error_t readAnalogMilliVoltsRX(uint32_t& mv)
211+
{
212+
return _impl->readAnalogMilliVoltsRX(mv);
213+
}
202214
inline m5::hal::error::error_t pulseInRX(uint32_t& duration, const int state, const uint32_t timeout_us)
203215
{
204216
return _impl->pulseInRX(duration, state, timeout_us);
@@ -224,6 +236,10 @@ class Adapter {
224236
{
225237
return _impl->readAnalogTX(v);
226238
}
239+
inline m5::hal::error::error_t readAnalogMilliVoltsTX(uint32_t& mv)
240+
{
241+
return _impl->readAnalogMilliVoltsTX(mv);
242+
}
227243
inline m5::hal::error::error_t pulseInTX(uint32_t& duration, const int state, const uint32_t timeout_us)
228244
{
229245
return _impl->pulseInTX(duration, state, timeout_us);

src/m5_unit_component/adapter_gpio.cpp

Lines changed: 188 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
1616
#pragma message "Using RMT v2,Oneshot"
1717
#include <esp_adc/adc_oneshot.h>
18+
#include <esp_adc/adc_cali.h>
19+
#include <esp_adc/adc_cali_scheme.h>
1820
#else
1921
#pragma message "Using RMT v1"
2022
#include <driver/adc.h>
23+
#include <esp_adc_cal.h>
2124
#endif
2225

2326
// ADC_ATTEN_DB_12 was introduced in ESP-IDF v4.4.7 / v5.1.3
@@ -330,6 +333,70 @@ int gpio_to_adc12(const int8_t pin)
330333
namespace m5 {
331334
namespace unit {
332335

336+
AdapterGPIOBase::GPIOImpl::~GPIOImpl()
337+
{
338+
release_adc_resources();
339+
}
340+
341+
void AdapterGPIOBase::GPIOImpl::release_adc_resources()
342+
{
343+
#if defined(M5_UNIT_UNIFIED_USING_ADC_ONESHOT)
344+
if (_cali_handle) {
345+
auto cali = static_cast<adc_cali_handle_t>(_cali_handle);
346+
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
347+
adc_cali_delete_scheme_curve_fitting(cali);
348+
#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
349+
adc_cali_delete_scheme_line_fitting(cali);
350+
#endif
351+
_cali_handle = nullptr;
352+
_cached_cali_channel = -1;
353+
}
354+
if (_adc_handle) {
355+
adc_oneshot_del_unit(static_cast<adc_oneshot_unit_handle_t>(_adc_handle));
356+
_adc_handle = nullptr;
357+
_cached_adc_unit = -1;
358+
}
359+
#endif
360+
}
361+
362+
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::ensure_adc_handle(const gpio_num_t pin)
363+
{
364+
#if defined(M5_UNIT_UNIFIED_USING_ADC_ONESHOT)
365+
const auto ch = gpio_to_adc_channel(pin);
366+
if (ch < 0) {
367+
return m5::hal::error::error_t::INVALID_ARGUMENT;
368+
}
369+
int8_t needed_unit = (ch < 10) ? 0 : 1;
370+
371+
if (_adc_handle && _cached_adc_unit == needed_unit) {
372+
return m5::hal::error::error_t::OK;
373+
}
374+
375+
// ADC unit changed — release everything and recreate
376+
release_adc_resources();
377+
378+
adc_unit_t unit = (needed_unit == 0) ? ADC_UNIT_1 : ADC_UNIT_2;
379+
adc_oneshot_unit_handle_t handle{};
380+
#if defined(CONFIG_IDF_TARGET_ESP32C6)
381+
adc_oneshot_unit_init_cfg_t init_config = {
382+
.unit_id = unit, .clk_src = ADC_DIGI_CLK_SRC_DEFAULT, .ulp_mode = ADC_ULP_MODE_DISABLE};
383+
#else
384+
adc_oneshot_unit_init_cfg_t init_config = {
385+
.unit_id = unit, .clk_src = ADC_RTC_CLK_SRC_DEFAULT, .ulp_mode = ADC_ULP_MODE_DISABLE};
386+
#endif
387+
388+
if (adc_oneshot_new_unit(&init_config, &handle) != ESP_OK) {
389+
return m5::hal::error::error_t::UNKNOWN_ERROR;
390+
}
391+
392+
_adc_handle = handle;
393+
_cached_adc_unit = needed_unit;
394+
return m5::hal::error::error_t::OK;
395+
#else
396+
return m5::hal::error::error_t::OK;
397+
#endif
398+
}
399+
333400
namespace gpio {
334401

335402
uint8_t calculate_rmt_clk_div(uint32_t apb_freq_hz, uint32_t tick_ns)
@@ -416,37 +483,28 @@ m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::read_analog(uint16_t& value,
416483

417484
#if defined(M5_UNIT_UNIFIED_USING_ADC_ONESHOT)
418485
// ESP-IDF 5.x
419-
adc_unit_t unit = (ch < 10) ? ADC_UNIT_1 : ADC_UNIT_2;
420-
adc_channel_t channel = static_cast<adc_channel_t>((ch < 10) ? ch : (ch - 10));
421-
422-
adc_oneshot_unit_handle_t adc_handle{};
423-
#if defined(CONFIG_IDF_TARGET_ESP32C6)
424-
adc_oneshot_unit_init_cfg_t init_config = {
425-
.unit_id = unit, .clk_src = ADC_DIGI_CLK_SRC_DEFAULT, .ulp_mode = ADC_ULP_MODE_DISABLE};
426-
#else
427-
adc_oneshot_unit_init_cfg_t init_config = {
428-
.unit_id = unit, .clk_src = ADC_RTC_CLK_SRC_DEFAULT, .ulp_mode = ADC_ULP_MODE_DISABLE};
429-
#endif
430-
431-
if (adc_oneshot_new_unit(&init_config, &adc_handle) != ESP_OK) {
432-
return m5::hal::error::error_t::UNKNOWN_ERROR;
486+
auto err = ensure_adc_handle(pin);
487+
if (err != m5::hal::error::error_t::OK) {
488+
return err;
433489
}
434490

491+
auto adc_handle = static_cast<adc_oneshot_unit_handle_t>(_adc_handle);
492+
adc_channel_t channel = static_cast<adc_channel_t>((ch < 10) ? ch : (ch - 10));
493+
435494
adc_oneshot_chan_cfg_t chan_config = {
436495
.atten = M5_ADC_ATTEN_DB, // 0~3.3V
437496
.bitwidth = ADC_BITWIDTH_DEFAULT // 12bit
438497
};
439498

440-
auto ret = m5::hal::error::error_t::UNKNOWN_ERROR;
441-
if (adc_oneshot_config_channel(adc_handle, channel, &chan_config) == ESP_OK) {
442-
int raw{};
443-
if (adc_oneshot_read(adc_handle, channel, &raw) == ESP_OK) {
444-
value = static_cast<uint16_t>(raw);
445-
ret = m5::hal::error::error_t::OK;
446-
}
499+
if (adc_oneshot_config_channel(adc_handle, channel, &chan_config) != ESP_OK) {
500+
return m5::hal::error::error_t::UNKNOWN_ERROR;
501+
}
502+
int raw{};
503+
if (adc_oneshot_read(adc_handle, channel, &raw) != ESP_OK) {
504+
return m5::hal::error::error_t::UNKNOWN_ERROR;
447505
}
448-
adc_oneshot_del_unit(adc_handle);
449-
return ret;
506+
value = static_cast<uint16_t>(raw);
507+
return m5::hal::error::error_t::OK;
450508

451509
#else
452510
// ESP-IDF 4.x
@@ -471,6 +529,113 @@ m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::read_analog(uint16_t& value,
471529
#endif
472530
}
473531

532+
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::read_analog_millivolts(uint32_t& millivolts, const gpio_num_t pin)
533+
{
534+
millivolts = 0;
535+
536+
const auto ch = gpio_to_adc_channel(pin);
537+
if (ch < 0) {
538+
return m5::hal::error::error_t::INVALID_ARGUMENT;
539+
}
540+
#if !defined(SOC_ADC_PERIPH_NUM) || SOC_ADC_PERIPH_NUM <= 1
541+
if (ch >= 10) {
542+
M5_LIB_LOGE("Not support ADC2");
543+
return m5::hal::error::error_t::NOT_IMPLEMENTED;
544+
}
545+
#endif
546+
547+
#if defined(M5_UNIT_UNIFIED_USING_ADC_ONESHOT)
548+
// ESP-IDF 5.x: Use adc_cali for calibrated millivolt reading
549+
auto err = ensure_adc_handle(pin);
550+
if (err != m5::hal::error::error_t::OK) {
551+
return err;
552+
}
553+
554+
auto adc_handle = static_cast<adc_oneshot_unit_handle_t>(_adc_handle);
555+
adc_channel_t channel = static_cast<adc_channel_t>((ch < 10) ? ch : (ch - 10));
556+
557+
adc_oneshot_chan_cfg_t chan_config = {
558+
.atten = M5_ADC_ATTEN_DB, // 0~3.3V
559+
.bitwidth = ADC_BITWIDTH_DEFAULT // 12bit
560+
};
561+
562+
if (adc_oneshot_config_channel(adc_handle, channel, &chan_config) != ESP_OK) {
563+
return m5::hal::error::error_t::UNKNOWN_ERROR;
564+
}
565+
566+
// Ensure calibration handle for this channel
567+
if (_cached_cali_channel != ch) {
568+
// Release old cali handle if any
569+
if (_cali_handle) {
570+
auto old_cali = static_cast<adc_cali_handle_t>(_cali_handle);
571+
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
572+
adc_cali_delete_scheme_curve_fitting(old_cali);
573+
#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
574+
adc_cali_delete_scheme_line_fitting(old_cali);
575+
#endif
576+
_cali_handle = nullptr;
577+
}
578+
579+
adc_unit_t unit = (_cached_adc_unit == 0) ? ADC_UNIT_1 : ADC_UNIT_2;
580+
adc_cali_handle_t cali_handle{};
581+
bool cali_ok{};
582+
583+
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
584+
adc_cali_curve_fitting_config_t cali_config = {
585+
.unit_id = unit,
586+
.chan = channel,
587+
.atten = M5_ADC_ATTEN_DB,
588+
.bitwidth = ADC_BITWIDTH_DEFAULT,
589+
};
590+
cali_ok = (adc_cali_create_scheme_curve_fitting(&cali_config, &cali_handle) == ESP_OK);
591+
#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
592+
adc_cali_line_fitting_config_t cali_config = {
593+
.unit_id = unit,
594+
.atten = M5_ADC_ATTEN_DB,
595+
.bitwidth = ADC_BITWIDTH_DEFAULT,
596+
};
597+
cali_ok = (adc_cali_create_scheme_line_fitting(&cali_config, &cali_handle) == ESP_OK);
598+
#endif
599+
600+
if (cali_ok) {
601+
_cali_handle = cali_handle;
602+
_cached_cali_channel = ch;
603+
}
604+
}
605+
606+
int raw{};
607+
if (adc_oneshot_read(adc_handle, channel, &raw) != ESP_OK) {
608+
return m5::hal::error::error_t::UNKNOWN_ERROR;
609+
}
610+
611+
if (_cali_handle) {
612+
int mv{};
613+
if (adc_cali_raw_to_voltage(static_cast<adc_cali_handle_t>(_cali_handle), raw, &mv) == ESP_OK) {
614+
millivolts = static_cast<uint32_t>(mv);
615+
return m5::hal::error::error_t::OK;
616+
}
617+
}
618+
619+
// Fallback: uncalibrated estimate
620+
millivolts = static_cast<uint32_t>(raw * 3100 / 4095);
621+
return m5::hal::error::error_t::OK;
622+
623+
#else
624+
// ESP-IDF 4.x: Use esp_adc_cal for calibrated millivolt reading
625+
uint16_t raw{};
626+
auto err = read_analog(raw, pin);
627+
if (err != m5::hal::error::error_t::OK) {
628+
return err;
629+
}
630+
631+
adc_unit_t unit = (ch < 10) ? ADC_UNIT_1 : ADC_UNIT_2;
632+
esp_adc_cal_characteristics_t chars{};
633+
esp_adc_cal_characterize(unit, M5_ADC_ATTEN_DB, ADC_WIDTH_BIT_12, 1100, &chars);
634+
millivolts = esp_adc_cal_raw_to_voltage(raw, &chars);
635+
return m5::hal::error::error_t::OK;
636+
#endif
637+
}
638+
474639
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::pulse_in(uint32_t& duration, const gpio_num_t pin, const int state,
475640
const uint32_t timeout_us)
476641
{

src/m5_unit_component/adapter_gpio.hpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ class AdapterGPIOBase : public Adapter {
4646
GPIOImpl(const int8_t rx_pin, const int8_t tx_pin) : _rx_pin{(gpio_num_t)rx_pin}, _tx_pin{(gpio_num_t)tx_pin}
4747
{
4848
}
49+
virtual ~GPIOImpl() override;
50+
4951
inline gpio_num_t rx_pin() const
5052
{
5153
return _rx_pin;
@@ -85,6 +87,10 @@ class AdapterGPIOBase : public Adapter {
8587
{
8688
return read_analog(v, rx_pin());
8789
}
90+
inline virtual m5::hal::error::error_t readAnalogMilliVoltsRX(uint32_t& mv) override
91+
{
92+
return read_analog_millivolts(mv, rx_pin());
93+
}
8894

8995
inline virtual m5::hal::error::error_t pulseInRX(uint32_t& duration, const int state,
9096
const uint32_t timeout_us = 30000) override
@@ -113,6 +119,10 @@ class AdapterGPIOBase : public Adapter {
113119
{
114120
return read_analog(v, tx_pin());
115121
}
122+
inline virtual m5::hal::error::error_t readAnalogMilliVoltsTX(uint32_t& mv) override
123+
{
124+
return read_analog_millivolts(mv, tx_pin());
125+
}
116126
inline virtual m5::hal::error::error_t pulseInTX(uint32_t& duration, const int state,
117127
const uint32_t timeout_us = 30000) override
118128
{
@@ -125,12 +135,23 @@ class AdapterGPIOBase : public Adapter {
125135
m5::hal::error::error_t read_digital(const gpio_num_t pin, bool& high);
126136
m5::hal::error::error_t write_analog(const gpio_num_t pin, const uint16_t value);
127137
m5::hal::error::error_t read_analog(uint16_t& value, const gpio_num_t pin);
138+
m5::hal::error::error_t read_analog_millivolts(uint32_t& millivolts, const gpio_num_t pin);
128139
m5::hal::error::error_t pulse_in(uint32_t& duration, const gpio_num_t pin, const int state,
129140
const uint32_t timeout_us);
130141

131142
protected:
143+
m5::hal::error::error_t ensure_adc_handle(const gpio_num_t pin);
144+
void release_adc_resources();
145+
132146
gpio_num_t _rx_pin{(gpio_num_t)-1}, _tx_pin{(gpio_num_t)-1};
133147
gpio::adapter_config_t _adapter_cfg{};
148+
149+
#if defined(M5_UNIT_UNIFIED_USING_ADC_ONESHOT)
150+
void* _adc_handle{}; // adc_oneshot_unit_handle_t
151+
void* _cali_handle{}; // adc_cali_handle_t
152+
int8_t _cached_adc_unit{-1}; // 0=ADC1, 1=ADC2, -1=uninitialized
153+
int8_t _cached_cali_channel{-1}; // cached calibration channel, -1=uninitialized
154+
#endif
134155
};
135156
//
136157
explicit AdapterGPIOBase(GPIOImpl* impl);

0 commit comments

Comments
 (0)