-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathi2c.hpp
More file actions
319 lines (284 loc) · 10.8 KB
/
i2c.hpp
File metadata and controls
319 lines (284 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#pragma once
#include <mutex>
#include <vector>
#include <driver/i2c.h>
#include "base_component.hpp"
#include "run_on_core.hpp"
#include "i2c_format_helpers.hpp"
namespace espp {
/// @brief I2C driver
/// @details
/// This class is a wrapper around the ESP-IDF I2C driver.
///
/// \section Example
/// \snippet i2c_example.cpp i2c example
class I2c : public espp::BaseComponent {
public:
/// Configuration for I2C
struct Config {
int isr_core_id = -1; ///< The core to install the I2C interrupt on. If -1, then the I2C
/// interrupt is installed on the core that this constructor is
/// called on. If 0 or 1, then the I2C interrupt is installed on
/// the specified core.
i2c_port_t port = I2C_NUM_0; ///< I2C port
gpio_num_t sda_io_num = GPIO_NUM_NC; ///< SDA pin
gpio_num_t scl_io_num = GPIO_NUM_NC; ///< SCL pin
gpio_pullup_t sda_pullup_en = GPIO_PULLUP_DISABLE; ///< SDA pullup
gpio_pullup_t scl_pullup_en = GPIO_PULLUP_DISABLE; ///< SCL pullup
uint32_t timeout_ms = 10; ///< I2C timeout in milliseconds
uint32_t clk_speed = 400 * 1000; ///< I2C clock speed in hertz
bool auto_init = true; ///< Automatically initialize I2C on construction
espp::Logger::Verbosity log_level = espp::Logger::Verbosity::WARN; ///< Verbosity of logger
};
/// Construct I2C driver
/// \param config Configuration for I2C
explicit I2c(const Config &config)
: BaseComponent("I2C", config.log_level)
, config_(config) {
if (config.auto_init) {
std::error_code ec;
init(ec);
if (ec) {
logger_.error("auto init failed");
}
}
}
/// Destructor
~I2c() {
std::error_code ec;
deinit(ec);
if (ec) {
logger_.error("deinit failed");
}
}
/// Initialize I2C driver
void init(std::error_code &ec) {
if (initialized_) {
logger_.warn("already initialized");
ec = std::make_error_code(std::errc::protocol_error);
return;
}
logger_.debug("Initializing I2C with config: {}", config_);
std::unique_lock lock(mutex_);
i2c_config_t i2c_cfg;
memset(&i2c_cfg, 0, sizeof(i2c_cfg));
i2c_cfg.sda_io_num = config_.sda_io_num;
i2c_cfg.scl_io_num = config_.scl_io_num;
i2c_cfg.mode = I2C_MODE_MASTER;
i2c_cfg.sda_pullup_en = config_.sda_pullup_en;
i2c_cfg.scl_pullup_en = config_.scl_pullup_en;
i2c_cfg.master.clk_speed = config_.clk_speed;
auto err = i2c_param_config(config_.port, &i2c_cfg);
if (err != ESP_OK) {
logger_.error("config i2c failed {}", esp_err_to_name(err));
ec = std::make_error_code(std::errc::io_error);
return;
}
// Make copy of variables for clearer code / easier capture
auto i2c_port = config_.port;
auto install_fn = [i2c_port]() -> esp_err_t {
return i2c_driver_install(i2c_port, I2C_MODE_MASTER, 0, 0, 0);
};
err = espp::task::run_on_core(install_fn, config_.isr_core_id);
if (err != ESP_OK) {
logger_.error("install i2c driver failed {}", esp_err_to_name(err));
ec = std::make_error_code(std::errc::io_error);
return;
}
logger_.info("I2C initialized on port {}", config_.port);
initialized_ = true;
}
/// Deinitialize I2C driver
void deinit(std::error_code &ec) {
if (!initialized_) {
logger_.warn("not initialized");
// dont make this an error
ec.clear();
return;
}
std::lock_guard<std::mutex> lock(mutex_);
auto err = i2c_driver_delete(config_.port);
if (err != ESP_OK) {
logger_.error("delete i2c driver failed");
ec = std::make_error_code(std::errc::io_error);
return;
}
ec.clear();
logger_.info("I2C deinitialized on port {}", config_.port);
initialized_ = false;
}
/// Write data to I2C device
/// \param dev_addr I2C device address
/// \param data Data to write
/// \param data_len Length of data to write
/// \return True if successful
bool write(const uint8_t dev_addr, const uint8_t *data, const size_t data_len) {
if (!initialized_) {
logger_.error("not initialized");
return false;
}
logger_.debug("write {} bytes to address {:#02x}", data_len, dev_addr);
std::lock_guard<std::mutex> lock(mutex_);
auto err = i2c_master_write_to_device(config_.port, dev_addr, data, data_len,
config_.timeout_ms / portTICK_PERIOD_MS);
if (err != ESP_OK) {
logger_.error("write {:#04x} error: '{}'", dev_addr, esp_err_to_name(err));
return false;
}
return true;
}
/// Write data to I2C device
/// \param dev_addr I2C device address
/// \param data Data to write
/// \return True if successful
bool write_vector(const uint8_t dev_addr, const std::vector<uint8_t> &data) {
return write(dev_addr, data.data(), data.size());
}
/// Write to and read data from I2C device
/// \param dev_addr I2C device address
/// \param write_data Data to write
/// \param write_size Length of data to write
/// \param read_data Data to read
/// \param read_size Length of data to read
/// \return True if successful
bool write_read(const uint8_t dev_addr, const uint8_t *write_data, const size_t write_size,
uint8_t *read_data, size_t read_size) {
if (!initialized_) {
logger_.error("not initialized");
return false;
}
logger_.debug("write {} bytes and read {} bytes from address {:#02x}", write_size, read_size,
dev_addr);
std::lock_guard<std::mutex> lock(mutex_);
auto err =
i2c_master_write_read_device(config_.port, dev_addr, write_data, write_size, read_data,
read_size, config_.timeout_ms / portTICK_PERIOD_MS);
if (err != ESP_OK) {
logger_.error("write read {:#04x} error: '{}'", dev_addr, esp_err_to_name(err));
return false;
}
return true;
}
/// Write data to and read data from I2C device
/// \param dev_addr I2C device address
/// \param write_data Data to write
/// \param read_data Data to read
/// \return True if successful
bool write_read_vector(const uint8_t dev_addr, const std::vector<uint8_t> &write_data,
std::vector<uint8_t> &read_data) {
return write_read(dev_addr, write_data.data(), write_data.size(), read_data.data(),
read_data.size());
}
/// Read data from I2C device at register
/// \param dev_addr I2C device address
/// \param reg_addr Register address
/// \param data Data to read
/// \param data_len Length of data to read
/// \return True if successful
bool read_at_register(const uint8_t dev_addr, const uint8_t reg_addr, uint8_t *data,
size_t data_len) {
if (!initialized_) {
logger_.error("not initialized");
return false;
}
logger_.debug("read {} bytes from address {:#02x} at register {}", data_len, dev_addr,
reg_addr);
std::lock_guard<std::mutex> lock(mutex_);
auto err = i2c_master_write_read_device(config_.port, dev_addr, ®_addr, 1, data, data_len,
config_.timeout_ms / portTICK_PERIOD_MS);
if (err != ESP_OK) {
logger_.error("read {:#04x} at register {} error: '{}'", dev_addr, reg_addr,
esp_err_to_name(err));
return false;
}
return true;
}
/// Read data from I2C device at register
/// \param dev_addr I2C device address
/// \param reg_addr Register address
/// \param data Data to read
/// \return True if successful
bool read_at_register_vector(const uint8_t dev_addr, const uint8_t reg_addr,
std::vector<uint8_t> &data) {
return read_at_register(dev_addr, reg_addr, data.data(), data.size());
}
/// Read data from I2C device
/// \param dev_addr I2C device address
/// \param data Data to read
/// \param data_len Length of data to read
/// \return True if successful
bool read(const uint8_t dev_addr, uint8_t *data, size_t data_len) {
if (!initialized_) {
logger_.error("not initialized");
return false;
}
logger_.debug("read {} bytes from address {:#02x}", data_len, dev_addr);
std::lock_guard<std::mutex> lock(mutex_);
auto err = i2c_master_read_from_device(config_.port, dev_addr, data, data_len,
config_.timeout_ms / portTICK_PERIOD_MS);
if (err != ESP_OK) {
logger_.error("read {:#04x} error: '{}'", dev_addr, esp_err_to_name(err));
return false;
}
return true;
}
/// Read data from I2C device
/// \param dev_addr I2C device address
/// \param data Data to read
/// \return True if successful
bool read_vector(const uint8_t dev_addr, std::vector<uint8_t> &data) {
return read(dev_addr, data.data(), data.size());
}
/// Probe I2C device
/// \param dev_addr I2C device address
/// \return True if successful
/// \details
/// This function sends a start condition, writes the device address, and then
/// sends a stop condition. If the device acknowledges the address, then it is
/// present on the bus.
bool probe_device(const uint8_t dev_addr) {
bool success = false;
logger_.debug("probe device {:#02x}", dev_addr);
std::lock_guard<std::mutex> lock(mutex_);
auto cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_stop(cmd);
if (i2c_master_cmd_begin(config_.port, cmd, config_.timeout_ms / portTICK_PERIOD_MS) ==
ESP_OK) {
success = true;
}
i2c_cmd_link_delete(cmd);
return success;
}
protected:
Config config_;
bool initialized_ = false;
std::mutex mutex_;
};
} // namespace espp
// for printing the I2C::Config using fmt
template <> struct fmt::formatter<espp::I2c::Config> {
constexpr auto parse(format_parse_context &ctx) const { return ctx.begin(); }
template <typename FormatContext>
auto format(const espp::I2c::Config &c, FormatContext &ctx) const {
// print the clock speed in khz
auto clk_speed_khz = c.clk_speed / 1000;
// if it's MHz, print it as such
if (clk_speed_khz >= 1000) {
clk_speed_khz = c.clk_speed / 1000000;
return fmt::format_to(
ctx.out(),
"I2c::Config{{I2C port: {}, SDA: {}, SCL: {}, SDA pullup: {}, SCL pullup: {}, "
"timeout: {}ms, clock speed: {}MHz}}",
c.port, c.sda_io_num, c.scl_io_num, c.sda_pullup_en, c.scl_pullup_en, c.timeout_ms,
clk_speed_khz);
}
return fmt::format_to(
ctx.out(),
"I2c::Config{{I2C port: {}, SDA: {}, SCL: {}, SDA pullup: {}, SCL pullup: {}, "
"timeout: {}ms, clock speed: {}kHz}}",
c.port, c.sda_io_num, c.scl_io_num, c.sda_pullup_en, c.scl_pullup_en, c.timeout_ms,
clk_speed_khz);
}
};