Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
Esp32HardwareI2C.cxx
Go to the documentation of this file.
1
36#if defined(ESP_PLATFORM)
37
38#include "Esp32HardwareI2C.hxx"
39// stropts.h must be included before i2c.h
40#include "stropts.h"
41#include "i2c.h"
42#include "i2c-dev.h"
43#include "sdkconfig.h"
45#include "utils/logging.h"
47
48#include <algorithm>
49
50#if CONFIG_VFS_SUPPORT_TERMIOS
51// remove defines added by arduino-esp32 core/esp32/binary.h which are
52// duplicated in sys/termios.h which may be included by esp_vfs.h
53#undef B110
54#undef B1000000
55#endif // CONFIG_VFS_SUPPORT_TERMIOS
56#include <esp_vfs.h>
57#include <string>
58#include <sys/errno.h>
59
60namespace openmrn_arduino
61{
62
63extern "C"
64{
65
74static ssize_t i2c_vfs_write(void *ctx, int fd, const void *buf, size_t size)
75{
76 HASSERT(ctx != NULL);
77 Esp32HardwareI2C *i2c = reinterpret_cast<Esp32HardwareI2C *>(ctx);
78 return i2c->write(fd, buf, size);
79}
80
89static ssize_t i2c_vfs_read(void *ctx, int fd, void *buf, size_t size)
90{
91 HASSERT(ctx != NULL);
92 Esp32HardwareI2C *i2c = reinterpret_cast<Esp32HardwareI2C *>(ctx);
93 return i2c->read(fd, buf, size);
94}
95
107static int i2c_vfs_open(void *ctx, const char *path, int flags, int mode)
108{
109 HASSERT(ctx != NULL);
110 Esp32HardwareI2C *i2c = reinterpret_cast<Esp32HardwareI2C *>(ctx);
111 return i2c->open(path, flags, mode);
112}
113
123static int i2c_vfs_close(void *ctx, int fd)
124{
125 HASSERT(ctx != NULL);
126 Esp32HardwareI2C *i2c = reinterpret_cast<Esp32HardwareI2C *>(ctx);
127 return i2c->close(fd);
128}
129
138static int i2c_vfs_ioctl(void *ctx, int fd, int cmd, va_list args)
139{
140 HASSERT(ctx != NULL);
141 Esp32HardwareI2C *i2c = reinterpret_cast<Esp32HardwareI2C *>(ctx);
142 return i2c->ioctl(fd, cmd, args);
143}
144
155static int i2c_vfs_fcntl(void *ctx, int fd, int cmd, int arg)
156{
157 HASSERT(ctx != NULL);
158 return 0;
159}
160
161} // extern "C"
162
163Esp32HardwareI2C::Esp32HardwareI2C(const char * const path)
164 : path_(path)
165{
166}
167
168Esp32HardwareI2C::~Esp32HardwareI2C()
169{
170 for (size_t idx = 0; idx < SOC_I2C_NUM; idx++)
171 {
172 if (i2cInitialized_[idx])
173 {
174 ESP_ERROR_CHECK(i2c_driver_delete(static_cast<i2c_port_t>(idx)));
175 }
176 }
177 if (vfsInitialized_)
178 {
179 ESP_ERROR_CHECK(esp_vfs_unregister(path_));
180 }
181}
182
183void Esp32HardwareI2C::hw_init(const gpio_num_t sda, const gpio_num_t scl,
184 const uint32_t bus_speed, const i2c_port_t port)
185{
186 if (!i2cInitialized_[port])
187 {
188 i2c_config_t i2c_config = {};
189 i2c_config.mode = I2C_MODE_MASTER;
190 i2c_config.sda_io_num = sda;
191 i2c_config.sda_pullup_en = GPIO_PULLUP_ENABLE;
192 i2c_config.scl_io_num = scl;
193 i2c_config.scl_pullup_en = GPIO_PULLUP_ENABLE;
194 i2c_config.master.clk_speed = bus_speed;
195
196 LOG(INFO,
197 "[I2C] Initializing I2C%d using SDA:%d, SCL:%d, "
198 "bus-speed: %" PRIu32, port, sda, scl, bus_speed);
199 ESP_ERROR_CHECK(i2c_param_config(port, &i2c_config));
200 ESP_ERROR_CHECK(i2c_driver_install(port, I2C_MODE_MASTER,
201 I2C_SLAVE_RX_BUF_SIZE, I2C_SLAVE_TX_BUF_SIZE, I2C_ISR_FLAGS));
202
203 i2cInitialized_[port] = true;
204 }
205 else
206 {
207 LOG_ERROR("[I2C] I2C%d has already been initialized!", port);
208 }
209
210 if (!vfsInitialized_)
211 {
212 esp_vfs_t vfs = {};
213 vfs.write_p = i2c_vfs_write;
214 vfs.read_p = i2c_vfs_read;
215 vfs.open_p = i2c_vfs_open;
216 vfs.close_p = i2c_vfs_close;
217 vfs.fcntl_p = i2c_vfs_fcntl;
218 vfs.ioctl_p = i2c_vfs_ioctl;
219 vfs.flags = ESP_VFS_FLAG_CONTEXT_PTR;
220 ESP_ERROR_CHECK(esp_vfs_register(path_, &vfs, this));
221
222 vfsInitialized_ = true;
223 }
224}
225
226void Esp32HardwareI2C::scan(const i2c_port_t port)
227{
228 // Scan the I2C bus and dump the output of devices that respond
229 std::string scanresults =
230 " 0 1 2 3 4 5 6 7 8 9 a b c d e f\n"
231 "00: ";
232 scanresults.reserve(256);
233
234 HASSERT(i2cInitialized_[port]);
235 for (uint8_t addr = 0; addr < 0x7F; addr++)
236 {
237 if (addr % 16 == 0)
238 {
239 scanresults.append(StringPrintf("\n%02x: ", addr));
240 }
241 i2c_cmd_handle_t cmd = i2c_cmd_link_create();
242 i2c_master_start(cmd);
243 i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
244 i2c_master_stop(cmd);
245 esp_err_t ret = i2c_master_cmd_begin(port, cmd, I2C_SCAN_TIMEOUT);
246 i2c_cmd_link_delete(cmd);
247 if (ret == ESP_OK)
248 {
249 scanresults.append(StringPrintf("%02x ", addr));
250 }
251 else if (ret == ESP_ERR_TIMEOUT)
252 {
253 scanresults.append("?? ");
254 }
255 else
256 {
257 scanresults.append("-- ");
258 }
259 }
260 LOG(INFO, scanresults.c_str());
261}
262
263ssize_t Esp32HardwareI2C::write(int fd, const void *buf, size_t size)
264{
265 uint8_t address;
266 i2c_port_t port;
267
268 {
269 AtomicHolder h(this);
270
271 auto entry = std::find_if(devices_.begin(), devices_.end(),
272 [fd](const auto &device)
273 {
274 return device.fd == fd;
275 });
276
277 if (entry == devices_.end())
278 {
279 // file handle not found, return an error.
280 errno = EBADF;
281 return -EBADF;
282 }
283 else if (entry->fd < 0)
284 {
285 // no address has been defined for this file handle, return an error.
286 errno = EINVAL;
287 return -EINVAL;
288 }
289 address = entry->address;
290 port = entry->port;
291 }
292
293 esp_err_t res = ESP_ERROR_CHECK_WITHOUT_ABORT(
294 i2c_master_write_to_device(port, address, (uint8_t *)buf, size,
295 I2C_OP_TIMEOUT));
296
297 if (res == ESP_ERR_TIMEOUT)
298 {
299 return -ETIMEDOUT;
300 }
301 else if (res == ESP_ERR_INVALID_STATE)
302 {
303 return -EINVAL;
304 }
305 else if (res != ESP_OK)
306 {
307 return -EIO;
308 }
309 return size;
310}
311
312ssize_t Esp32HardwareI2C::read(int fd, void *buf, size_t size)
313{
314 uint8_t address;
315 i2c_port_t port;
316
317 {
318 AtomicHolder h(this);
319
320 auto entry = std::find_if(devices_.begin(), devices_.end(),
321 [fd](const auto &device)
322 {
323 return device.fd == fd;
324 });
325
326 if (entry == devices_.end())
327 {
328 // file handle not found, return an error.
329 errno = EBADF;
330 return -EBADF;
331 }
332 else if (entry->fd < 0)
333 {
334 // no address has been defined for this file handle, return an error.
335 errno = EINVAL;
336 return -EINVAL;
337 }
338 address = entry->address;
339 port = entry->port;
340 }
341
342
343 esp_err_t res = ESP_ERROR_CHECK_WITHOUT_ABORT(
344 i2c_master_read_from_device(port, address, (uint8_t *)buf, size,
345 I2C_OP_TIMEOUT));
346
347 if (res == ESP_ERR_TIMEOUT)
348 {
349 return -ETIMEDOUT;
350 }
351 else if (res == ESP_ERR_INVALID_STATE)
352 {
353 return -EINVAL;
354 }
355 else if (res != ESP_OK)
356 {
357 return -EIO;
358 }
359 return size;
360}
361
362int Esp32HardwareI2C::open(const char *path, int flags, int mode)
363{
364 std::string path_str = path;
365 i2c_device_t new_dev =
366 {
367 .port = I2C_NUM_0,
368 .address = -1,
369 .fd = 0,
370 };
371
372 if (path_str.back() == '0')
373 {
374 new_dev.port = I2C_NUM_0;
375 }
376#if SOC_I2C_NUM > 1
377 else if (path_str.back() == '1')
378 {
379 new_dev.port = I2C_NUM_1;
380 }
381#endif // SOC_I2C_NUM > 1
382 else
383 {
384 LOG_ERROR("[I2C] Unsupported I2C path: %s", path);
385 return -ENOENT;
386 }
387
388 if (!i2cInitialized_[new_dev.port])
389 {
390 LOG_ERROR("[I2C] Uninitialized I2C path: %s", path);
391 return -ENODEV;
392 }
393
394 // scan existing devices to find a unique file handle number to return to
395 // the caller. The file handle starts with zero and is set to the maximum
396 // file handle found plus one. When a file handle is reclaimed there will
397 // be gaps in the file handles which will not be reclaimed until entries
398 // with higher file handle numbers are also reclaimed.
399 {
400 AtomicHolder h(this);
401
402 if (!devices_.empty())
403 {
404 for (auto &entry: devices_)
405 {
406 if (entry.fd >= new_dev.fd)
407 {
408 new_dev.fd = entry.fd + 1;
409 }
410 }
411 }
412 devices_.push_back(new_dev);
413 }
414
415 LOG(INFO, "[I2C] Using fd: %d (I2C%d) for %s", new_dev.fd, new_dev.port,
416 path);
417
418 return new_dev.fd;
419}
420
421int Esp32HardwareI2C::close(int fd)
422{
423 AtomicHolder h(this);
424
425 auto entry = std::find_if(devices_.begin(), devices_.end(),
426 [fd](const auto &device)
427 {
428 return device.fd == fd;
429 });
430
431 // only delete the device if has been found.
432 if (entry != devices_.end())
433 {
434 devices_.erase(entry);
435 }
436 return 0;
437}
438
439int Esp32HardwareI2C::ioctl(int fd, int cmd, va_list args)
440{
441 AtomicHolder h(this);
442 HASSERT(IOC_TYPE(cmd) == I2C_MAGIC);
443
444 auto entry = std::find_if(devices_.begin(), devices_.end(),
445 [fd](const auto &device)
446 {
447 return device.fd == fd;
448 });
449 if (entry == devices_.end())
450 {
451 errno = EBADF;
452 return -EBADF;
453 }
454
455 switch(static_cast<unsigned int>(cmd))
456 {
457 default:
458 return -EINVAL;
459 case I2C_SLAVE:
460 entry->address = static_cast<int>(va_arg(args, int));
461 return 0;
462 case I2C_RDWR:
463 struct i2c_rdwr_ioctl_data *data =
464 reinterpret_cast<struct i2c_rdwr_ioctl_data *>(va_arg(args, uintptr_t));
465 return transfer_messages(entry->port, data->msgs, data->nmsgs);
466 }
467
468 return 0;
469}
470
471int Esp32HardwareI2C::transfer_messages(
472 const i2c_port_t port, struct i2c_msg *msgs, int num)
473{
474 int total_len = 0;
475 esp_err_t res = ESP_OK;
476
477 for (int idx = 0; idx < num; idx++)
478 {
479 struct i2c_msg *msg = msgs + idx;
480 if (msg->flags & I2C_M_RD)
481 {
482 res =
483 ESP_ERROR_CHECK_WITHOUT_ABORT(
484 i2c_master_read_from_device(port, msg->addr,
485 (uint8_t *)msg->buf, msg->len, I2C_OP_TIMEOUT));
486 }
487 else
488 {
489 res =
490 ESP_ERROR_CHECK_WITHOUT_ABORT(
491 i2c_master_write_to_device(port, msg->addr,
492 (uint8_t *)msg->buf, msg->len, I2C_OP_TIMEOUT));
493 }
494 if (res == ESP_ERR_TIMEOUT)
495 {
496 return -ETIMEDOUT;
497 }
498 else if (res == ESP_ERR_INVALID_STATE)
499 {
500 return -EINVAL;
501 }
502 else if (res != ESP_OK)
503 {
504 return -EIO;
505 }
506 total_len += msg->len;
507 }
508 return total_len;
509}
510
511} // namespace openmrn_arduino
512
513#endif // ESP_PLATFORM
See OSMutexLock in os/OS.hxx.
Definition Atomic.hxx:153
Esp32HardwareI2C(const char *const path="/dev/i2c")
Constructor.
#define IOC_TYPE(_num)
Decode ioctl type.
#define LOG(level, message...)
Conditionally write a message to the logging output.
Definition logging.h:99
static const int INFO
Loglevel that is printed by default, reporting some status information.
Definition logging.h:57
#define LOG_ERROR(message...)
Shorthand for LOG(LEVEL_ERROR, message...). See LOG.
Definition logging.h:124
#define HASSERT(x)
Checks that the value of expression x is true, else terminates the current process.
Definition macros.h:138