Skip to content

Commit 0ed48f9

Browse files
authored
feat(tla2528/ads7138): Add calibrate function to TLA2528 and ADS7138 (#413)
* feat(tla2528): Add `calibrate` function to `TLA2528` * Add new calibrate function to TLA2528, with option to automatically calibrate on construction (default = false). * Update example to better support hardware to test / show use of other TLA addresses - automatically finding the TLA on the I2C bus. * Update example to test calibration function and print out the calibration time for reference. Allows users to gain better precision in adc measurements by calibrating the sensor. Build and run `tla2528/example` on ESP32-S3 with TLA2528 at address 0x16 on I2C pins 17,18. * update example; apply change to ads7138 as well
1 parent a34e74a commit 0ed48f9

File tree

4 files changed

+155
-4
lines changed

4 files changed

+155
-4
lines changed

components/ads7138/example/main/ads7138_example.cpp

+8-1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,14 @@ extern "C" void app_main(void) {
107107
.log_level = espp::Logger::Verbosity::WARN,
108108
});
109109

110+
// calibrate the ADC
111+
std::error_code ec;
112+
ads.calibrate(ec);
113+
if (ec) {
114+
logger.error("error calibrating: {}", ec.message());
115+
return;
116+
}
117+
110118
// create the gpio event queue
111119
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
112120
// setup gpio interrupts for mute button
@@ -163,7 +171,6 @@ extern "C" void app_main(void) {
163171
});
164172
alert_task->start();
165173

166-
std::error_code ec;
167174
// configure the alert pin
168175
ads.configure_alert(espp::Ads7138::OutputMode::PUSH_PULL, espp::Ads7138::AlertLogic::ACTIVE_LOW,
169176
ec);

components/ads7138/include/ads7138.hpp

+47
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ class Ads7138 : public BasePeripheral<> {
153153
BasePeripheral::read_fn read; ///< Function to read from the ADC
154154
bool auto_init = true; ///< Automatically initialize the ADC on construction. If false,
155155
///< initialize() must be called before any other functions.
156+
bool auto_calibrate =
157+
false; ///< Automatically calibrate the ADC on construction. If false, calibrate() should be
158+
///< called to increase the accuracy of the ADC.
159+
///< @note Can only be set to true if auto_init is also true, otherwise it will be
160+
///< ignored.
156161
espp::Logger::Verbosity log_level{espp::Logger::Verbosity::WARN}; ///< Verbosity for the logger.
157162
};
158163

@@ -180,6 +185,13 @@ class Ads7138 : public BasePeripheral<> {
180185
initialize(ec);
181186
if (ec) {
182187
logger_.error("Error initializing ADC: {}", ec.message());
188+
return;
189+
}
190+
if (config.auto_calibrate) {
191+
calibrate(ec);
192+
if (ec) {
193+
logger_.error("Error calibrating ADC: {}", ec.message());
194+
}
183195
}
184196
}
185197
}
@@ -194,6 +206,41 @@ class Ads7138 : public BasePeripheral<> {
194206
*/
195207
void initialize(std::error_code &ec) { init(config_, ec); }
196208

209+
/// @brief Calibrate the ADC
210+
/// This function will set the calibration bit in the GENERAL_CFG register
211+
/// to start the calibration process and will wait for the calibration to
212+
/// complete (CAL bit in GENERAL_CFG register to be cleared).
213+
/// @param ec Error code to set if an error occurs.
214+
/// @param poll_interval The time to wait between polls for the calibration
215+
/// to complete.
216+
/// @param timeout The time to wait for the calibration to complete before
217+
/// returning an error.
218+
void calibrate(std::error_code &ec,
219+
std::chrono::milliseconds poll_interval = std::chrono::milliseconds(10),
220+
std::chrono::milliseconds timeout = std::chrono::milliseconds(100)) {
221+
std::lock_guard<std::recursive_mutex> lock(base_mutex_);
222+
logger_.info("Starting calibration");
223+
set_bits_(Register::GENERAL_CFG, CAL, ec);
224+
if (ec) {
225+
return;
226+
}
227+
// wait for the calibration to complete
228+
auto start_time = std::chrono::steady_clock::now();
229+
while (read_one_(Register::GENERAL_CFG, ec) & CAL) {
230+
std::this_thread::sleep_for(poll_interval);
231+
if (ec) {
232+
return;
233+
}
234+
auto now = std::chrono::steady_clock::now();
235+
if (now - start_time > timeout) {
236+
logger_.error("Calibration timed out");
237+
ec = std::make_error_code(std::errc::timed_out);
238+
return;
239+
}
240+
}
241+
logger_.info("Calibration complete");
242+
}
243+
197244
/**
198245
* @brief Communicate with the ADC to get the analog value for the channel
199246
* and return it.

components/tla2528/example/main/tla2528_example.cpp

+53-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <algorithm>
12
#include <chrono>
23
#include <sdkconfig.h>
34
#include <vector>
@@ -21,18 +22,53 @@ extern "C" void app_main(void) {
2122
.port = I2C_NUM_0,
2223
.sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO,
2324
.scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO,
25+
.sda_pullup_en = GPIO_PULLUP_ENABLE,
26+
.scl_pullup_en = GPIO_PULLUP_ENABLE,
2427
});
2528

29+
std::vector<uint8_t> found_addresses;
30+
for (uint8_t address = 0; address < 128; address++) {
31+
logger.debug("probing address {:#02x}", address);
32+
if (i2c.probe_device(address)) {
33+
found_addresses.push_back(address);
34+
}
35+
}
36+
// print out the addresses that were found
37+
logger.info("Found devices at addresses: {::#02x}", found_addresses);
38+
39+
if (found_addresses.size() == 0) {
40+
logger.error("No devices found!");
41+
return;
42+
}
43+
44+
// make sure one of the TLA addresses is in the list, and use one if it was found
45+
//
46+
// NOTE: the TLA2528 has 8 addresses, so we need to check for them all
47+
// 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17
48+
//
49+
// Example: Address pin is connected via 11k to ADC_DECAP, so the default
50+
// address of 0x10 becomes 0x16: espp::Tla2528::DEFAULT_ADDRESS | 0x06
51+
const std::vector<uint8_t> tla_addresses{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};
52+
uint8_t tla_address = 0;
53+
auto it = std::find_first_of(tla_addresses.begin(), tla_addresses.end(),
54+
found_addresses.begin(), found_addresses.end());
55+
if (it != tla_addresses.end()) {
56+
tla_address = *it;
57+
} else {
58+
logger.error("No TLA2528 found!");
59+
return;
60+
}
61+
62+
logger.info("Found TLA2528 at address {:#02x}", tla_address);
63+
2664
static std::vector<espp::Tla2528::Channel> channels = {
2765
espp::Tla2528::Channel::CH0, espp::Tla2528::Channel::CH1, espp::Tla2528::Channel::CH2,
2866
espp::Tla2528::Channel::CH3, espp::Tla2528::Channel::CH4, espp::Tla2528::Channel::CH5,
2967
espp::Tla2528::Channel::CH6, espp::Tla2528::Channel::CH7};
3068

3169
// make the actual tla class
3270
espp::Tla2528 tla(espp::Tla2528::Config{
33-
// Address pin is connected via 11k to ADC_DECAP, so the default address
34-
// of 0x10 becomes 0x16
35-
.device_address = espp::Tla2528::DEFAULT_ADDRESS | 0x06,
71+
.device_address = tla_address,
3672
.mode = espp::Tla2528::Mode::AUTO_SEQ,
3773
.analog_inputs = channels,
3874
.digital_inputs = {},
@@ -46,6 +82,20 @@ extern "C" void app_main(void) {
4682
.log_level = espp::Logger::Verbosity::WARN,
4783
});
4884

85+
// calibrate the ADC
86+
//
87+
// NOTE: this is not strictly necessary, but improves the accuracy of the
88+
// ADC.
89+
auto cal_start_us = esp_timer_get_time();
90+
std::error_code ec;
91+
tla.calibrate(ec);
92+
auto cal_end_us = esp_timer_get_time();
93+
if (ec) {
94+
logger.error("error calibrating TLA2528: {}", ec.message());
95+
return;
96+
}
97+
logger.info("calibration took {} us", cal_end_us - cal_start_us);
98+
4999
// make the task which will get the raw data from the I2C ADC
50100
fmt::print("%time (s), ntc (mV), x (mV), y (mV)\n");
51101
auto tla_read_task_fn = [&tla](std::mutex &m, std::condition_variable &cv) {

components/tla2528/include/tla2528.hpp

+47
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ class Tla2528 : public BasePeripheral<> {
131131
BasePeripheral::read_fn read; ///< Function to read from the ADC
132132
bool auto_init = true; ///< Automatically initialize the ADC on construction. If false,
133133
///< initialize() must be called before any other functions.
134+
bool auto_calibrate =
135+
false; ///< Automatically calibrate the ADC on construction. If false, calibrate() should be
136+
///< called to increase the accuracy of the ADC.
137+
///< @note Can only be set to true if auto_init is also true, otherwise it will be
138+
///< ignored.
134139
espp::Logger::Verbosity log_level{espp::Logger::Verbosity::WARN}; ///< Verbosity for the logger.
135140
};
136141

@@ -162,6 +167,13 @@ class Tla2528 : public BasePeripheral<> {
162167
initialize(ec);
163168
if (ec) {
164169
logger_.error("Error initializing ADC: {}", ec.message());
170+
return;
171+
}
172+
if (config.auto_calibrate) {
173+
calibrate(ec);
174+
if (ec) {
175+
logger_.error("Error calibrating ADC: {}", ec.message());
176+
}
165177
}
166178
}
167179
}
@@ -176,6 +188,41 @@ class Tla2528 : public BasePeripheral<> {
176188
*/
177189
void initialize(std::error_code &ec) { init(config_, ec); }
178190

191+
/// @brief Calibrate the ADC
192+
/// This function will set the calibration bit in the GENERAL_CFG register
193+
/// to start the calibration process and will wait for the calibration to
194+
/// complete (CAL bit in GENERAL_CFG register to be cleared).
195+
/// @param ec Error code to set if an error occurs.
196+
/// @param poll_interval The time to wait between polls for the calibration
197+
/// to complete.
198+
/// @param timeout The time to wait for the calibration to complete before
199+
/// returning an error.
200+
void calibrate(std::error_code &ec,
201+
std::chrono::milliseconds poll_interval = std::chrono::milliseconds(10),
202+
std::chrono::milliseconds timeout = std::chrono::milliseconds(100)) {
203+
std::lock_guard<std::recursive_mutex> lock(base_mutex_);
204+
logger_.info("Starting calibration");
205+
set_bits_(Register::GENERAL_CFG, CAL, ec);
206+
if (ec) {
207+
return;
208+
}
209+
// wait for the calibration to complete
210+
auto start_time = std::chrono::steady_clock::now();
211+
while (read_one_(Register::GENERAL_CFG, ec) & CAL) {
212+
std::this_thread::sleep_for(poll_interval);
213+
if (ec) {
214+
return;
215+
}
216+
auto now = std::chrono::steady_clock::now();
217+
if (now - start_time > timeout) {
218+
logger_.error("Calibration timed out");
219+
ec = std::make_error_code(std::errc::timed_out);
220+
return;
221+
}
222+
}
223+
logger_.info("Calibration complete");
224+
}
225+
179226
/**
180227
* @brief Communicate with the ADC to get the analog value for the channel
181228
* and return it.

0 commit comments

Comments
 (0)