forked from esp-idf-lib/i2cdev
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathi2cdev.c
More file actions
897 lines (803 loc) · 37.6 KB
/
i2cdev.c
File metadata and controls
897 lines (803 loc) · 37.6 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
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
/*
* The MIT License (MIT)
*
* Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file i2cdev.c
*
* ESP-IDF I2C master thread-safe functions for communication with I2C slave
*
* Copyright (C) 2018 Ruslan V. Uss <unclerus@gmail.com>
* Updated 2025 by quinkq to use newer ESP-IDF I2C master driver API
*
* MIT Licensed as described in the file LICENSE
*/
#include "i2cdev.h"
#include <driver/i2c_master.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <inttypes.h>
#include <string.h>
static const char *TAG = "i2cdev";
// Fallback definition for platforms without 10-bit address support
#ifndef I2C_ADDR_BIT_LEN_10
#define I2C_ADDR_BIT_LEN_10 1
#endif
#define I2C_DEFAULT_FREQ_HZ 400000
#define I2C_MAX_RETRIES 3
#define I2C_RETRY_BASE_DELAY_MS 20
#define I2CDEV_MAX_STACK_ALLOC_SIZE 32 // Stack allocation threshold to avoid heap fragmentation for small buffers
typedef struct
{
SemaphoreHandle_t lock; // Mutex for exclusive access to this port's state
i2c_master_bus_handle_t bus_handle; // Handle to the initialized I2C master bus
bool installed; // Flag indicating if the bus for this port has been installed
uint32_t ref_count; // Number of devices currently active on this bus port
int sda_pin_current; // Actual SDA pin the bus was initialized with
int scl_pin_current; // Actual SCL pin the bus was initialized with
} i2c_port_state_t;
static i2c_port_state_t i2c_ports[I2C_NUM_MAX] = { 0 };
static i2c_dev_t *active_devices[I2C_NUM_MAX][CONFIG_I2CDEV_MAX_DEVICES_PER_PORT] = { { NULL } };
// Helper to register a device
static esp_err_t register_device(i2c_dev_t *dev)
{
if (!dev)
return ESP_ERR_INVALID_ARG;
int port = dev->port;
if (port >= I2C_NUM_MAX)
return ESP_ERR_INVALID_ARG;
// Note: Port mutex should be held by caller
for (int i = 0; i < CONFIG_I2CDEV_MAX_DEVICES_PER_PORT; i++)
{
if (active_devices[port][i] == NULL)
{
active_devices[port][i] = dev;
ESP_LOGV(TAG, "[0x%02x at %d] Registered device in slot %d", dev->addr, port, i);
return ESP_OK;
}
}
ESP_LOGE(TAG, "[0x%02x at %d] No free slots to register device - limit reached", dev->addr, port);
return ESP_ERR_NO_MEM;
}
// Helper to deregister a device
static void deregister_device(i2c_dev_t *dev)
{
if (!dev)
return;
int port = dev->port;
if (port >= I2C_NUM_MAX)
return;
for (int i = 0; i < CONFIG_I2CDEV_MAX_DEVICES_PER_PORT; i++)
{
if (active_devices[port][i] == dev)
{
active_devices[port][i] = NULL;
ESP_LOGV(TAG, "[0x%02x at %d] Deregistered device from slot %d", dev->addr, port, i);
return;
}
}
}
esp_err_t i2cdev_init(void)
{
ESP_LOGV(TAG, "Initializing I2C subsystem...");
memset(active_devices, 0, sizeof(active_devices));
for (int i = 0; i < I2C_NUM_MAX; i++)
{
if (!i2c_ports[i].lock)
{
i2c_ports[i].lock = xSemaphoreCreateMutex();
if (!i2c_ports[i].lock)
{
ESP_LOGE(TAG, "Could not create port mutex %d", i);
return ESP_ERR_NO_MEM;
}
ESP_LOGV(TAG, "Created port mutex %d", i);
}
i2c_ports[i].installed = false;
i2c_ports[i].ref_count = 0;
i2c_ports[i].bus_handle = NULL;
i2c_ports[i].sda_pin_current = -1;
i2c_ports[i].scl_pin_current = -1;
}
ESP_LOGV(TAG, "I2C subsystem initialized.");
return ESP_OK;
}
esp_err_t i2c_dev_create_mutex(i2c_dev_t *dev)
{
#if !CONFIG_I2CDEV_NOLOCK
if (!dev)
return ESP_ERR_INVALID_ARG;
ESP_LOGV(TAG, "[0x%02x at %d] Creating device mutex...", dev->addr, dev->port);
if (dev->mutex)
{
ESP_LOGW(TAG, "[0x%02x at %d] device mutex already exists (Handle: %p)", dev->addr, dev->port, dev->mutex);
return ESP_OK; // Already created
}
dev->mutex = xSemaphoreCreateMutex();
if (!dev->mutex)
{
ESP_LOGE(TAG, "[0x%02x at %d] Could not create device mutex", dev->addr, dev->port);
return ESP_ERR_NO_MEM; // Use ESP_ERR_NO_MEM for memory allocation failures
}
ESP_LOGV(TAG, "[0x%02x at %d] Device mutex created (Handle: %p)", dev->addr, dev->port, dev->mutex);
// Register the device for cleanup tracking (under port mutex for consistency)
if (dev->port < I2C_NUM_MAX && i2c_ports[dev->port].lock)
{
if (xSemaphoreTake(i2c_ports[dev->port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) == pdTRUE)
{
esp_err_t reg_res = register_device(dev);
if (reg_res != ESP_OK)
{
ESP_LOGW(TAG, "[0x%02x at %d] Failed to register device: %s - device will work but cleanup tracking disabled", dev->addr, dev->port, esp_err_to_name(reg_res));
// Continue - device can still function without registration tracking
}
else
{
ESP_LOGV(TAG, "[0x%02x at %d] Device registered successfully for cleanup tracking", dev->addr, dev->port);
}
xSemaphoreGive(i2c_ports[dev->port].lock);
}
else
{
ESP_LOGW(TAG, "[0x%02x at %d] Could not take port mutex for device registration", dev->addr, dev->port);
// Continue - device can still function without registration tracking
}
}
// Set default address bit length if not explicitly set
if (dev->addr_bit_len != I2C_ADDR_BIT_LEN_7 && dev->addr_bit_len != I2C_ADDR_BIT_LEN_10)
{
ESP_LOGV(TAG, "[0x%02x at %d] Setting default 7-bit address format", dev->addr, dev->port);
dev->addr_bit_len = I2C_ADDR_BIT_LEN_7;
}
#else
ESP_LOGV(TAG, "[0x%02x at %d] Mutex creation skipped (CONFIG_I2CDEV_NOLOCK=1)", dev->addr, dev->port);
#endif
return ESP_OK;
}
esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev)
{
#if !CONFIG_I2CDEV_NOLOCK
if (!dev)
return ESP_ERR_INVALID_ARG;
ESP_LOGV(TAG, "[0x%02x at %d] Deleting device mutex and cleaning up resources", dev->addr, dev->port);
// Remove device from bus if handle exists
if (dev->dev_handle)
{
ESP_LOGV(TAG, "[0x%02x at %d] Removing device handle %p from bus", dev->addr, dev->port, dev->dev_handle);
esp_err_t rm_res = i2c_master_bus_rm_device((i2c_master_dev_handle_t)dev->dev_handle);
if (rm_res != ESP_OK)
{
ESP_LOGW(TAG, "[0x%02x at %d] Failed to remove device handle: %s", dev->addr, dev->port, esp_err_to_name(rm_res));
// Continue with cleanup despite error
}
dev->dev_handle = NULL;
}
// Deregister the device
deregister_device(dev);
// Update port reference count if port is valid
if (dev->port < I2C_NUM_MAX)
{
if (xSemaphoreTake(i2c_ports[dev->port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) == pdTRUE)
{
if (i2c_ports[dev->port].installed && i2c_ports[dev->port].ref_count > 0)
{
i2c_ports[dev->port].ref_count--;
ESP_LOGV(TAG, "[Port %d] Decremented ref_count to %" PRIu32, dev->port, i2c_ports[dev->port].ref_count);
// If last device on this port, delete the bus
if (i2c_ports[dev->port].ref_count == 0)
{
ESP_LOGI(TAG, "[Port %d] Last device removed, cleaning up THIS port's bus", dev->port);
// Just clean up this port's bus
if (i2c_ports[dev->port].bus_handle)
{
ESP_LOGI(TAG, "[Port %d] Deleting bus handle %p", dev->port, i2c_ports[dev->port].bus_handle);
esp_err_t del_bus_res = i2c_del_master_bus(i2c_ports[dev->port].bus_handle);
if (del_bus_res != ESP_OK)
{
ESP_LOGE(TAG, "[Port %d] Failed to delete master bus: %s", dev->port, esp_err_to_name(del_bus_res));
}
i2c_ports[dev->port].bus_handle = NULL;
}
i2c_ports[dev->port].installed = false;
i2c_ports[dev->port].sda_pin_current = -1;
i2c_ports[dev->port].scl_pin_current = -1;
}
}
xSemaphoreGive(i2c_ports[dev->port].lock);
}
else
{
ESP_LOGW(TAG, "[0x%02x at %d] Could not take port mutex for ref_count update", dev->addr, dev->port);
}
}
// Delete the mutex itself last
if (dev->mutex)
{
vSemaphoreDelete(dev->mutex);
dev->mutex = NULL;
}
else
{
ESP_LOGV(TAG, "[0x%02x at %d] Device mutex was NULL, nothing to delete", dev->addr, dev->port);
}
#endif
return ESP_OK;
}
esp_err_t i2c_dev_take_mutex(i2c_dev_t *dev)
{
#if !CONFIG_I2CDEV_NOLOCK
if (!dev)
return ESP_ERR_INVALID_ARG;
ESP_LOGV(TAG, "[0x%02x at %d] Attempting to take device mutex (Handle: %p)...", dev->addr, dev->port, dev->mutex);
if (!dev->mutex)
{
ESP_LOGE(TAG, "[0x%02x at %d] Attempt to take NULL device mutex!", dev->addr, dev->port);
return ESP_ERR_INVALID_STATE; // Mutex doesn't exist
}
TickType_t timeout_ticks = pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT);
ESP_LOGV(TAG, "[0x%02x at %d] Taking device mutex with timeout %d ms (%lu ticks)", dev->addr, dev->port, CONFIG_I2CDEV_TIMEOUT, (unsigned long)timeout_ticks);
if (!xSemaphoreTake(dev->mutex, timeout_ticks))
{
ESP_LOGE(TAG, "[0x%02x at %d] Could not take device mutex (Timeout after %d ms)", dev->addr, dev->port, CONFIG_I2CDEV_TIMEOUT);
return ESP_ERR_TIMEOUT;
}
ESP_LOGV(TAG, "[0x%02x at %d] Device mutex taken successfully.", dev->addr, dev->port);
#else
ESP_LOGV(TAG, "[0x%02x at %d] Mutex take skipped (CONFIG_I2CDEV_NOLOCK=1)", dev->addr, dev->port);
#endif
return ESP_OK;
}
esp_err_t i2c_dev_give_mutex(i2c_dev_t *dev)
{
#if !CONFIG_I2CDEV_NOLOCK
if (!dev)
return ESP_ERR_INVALID_ARG;
ESP_LOGV(TAG, "[0x%02x at %d] Giving device mutex (Handle: %p)...", dev->addr, dev->port, dev->mutex);
if (!dev->mutex)
{
ESP_LOGE(TAG, "[0x%02x at %d] Attempt to give NULL device mutex!", dev->addr, dev->port);
return ESP_ERR_INVALID_STATE;
}
if (!xSemaphoreGive(dev->mutex))
{
// This case should ideally not happen if the mutex was taken correctly
ESP_LOGE(TAG, "[0x%02x at %d] Could not give device mutex (Was it taken?) (Handle: %p)", dev->addr, dev->port, dev->mutex);
return ESP_FAIL;
}
ESP_LOGV(TAG, "[0x%02x at %d] Device mutex given successfully.", dev->addr, dev->port);
#else
ESP_LOGV(TAG, "[0x%02x at %d] Mutex give skipped (CONFIG_I2CDEV_NOLOCK=1)", dev->addr, dev->port);
#endif
return ESP_OK;
}
// i2c_setup_port: Initializes the I2C master bus for a given port if not already done.
// It uses pin configurations from dev->cfg.sda_io_num and dev->cfg.scl_io_num.
// The pins for a port are fixed after the first device initializes it.
static esp_err_t i2c_setup_port(i2c_dev_t *dev) // dev is non-const to update dev->sda_pin, dev->scl_pin
{
if (!dev)
return ESP_ERR_INVALID_ARG;
if (dev->port >= I2C_NUM_MAX)
{
ESP_LOGE(TAG, "Invalid I2C port number: %d", dev->port);
return ESP_ERR_INVALID_ARG;
}
esp_err_t res = ESP_OK;
i2c_port_state_t *port_state = &i2c_ports[dev->port];
ESP_LOGV(TAG, "[Port %d] Setup request for device 0x%02x", dev->port, dev->addr);
if (xSemaphoreTake(port_state->lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE)
{
ESP_LOGE(TAG, "[Port %d] Could not take port mutex for setup", dev->port);
return ESP_ERR_TIMEOUT;
}
if (!port_state->installed)
{
// Pin Selection Logic: Use device-specified pins, fallback to Kconfig defaults if -1
gpio_num_t sda_pin = (dev->cfg.sda_io_num == (gpio_num_t) -1) ? (gpio_num_t)CONFIG_I2CDEV_DEFAULT_SDA_PIN : dev->cfg.sda_io_num;
gpio_num_t scl_pin = (dev->cfg.scl_io_num == (gpio_num_t) -1) ? (gpio_num_t)CONFIG_I2CDEV_DEFAULT_SCL_PIN : dev->cfg.scl_io_num;
// Validate pins (basic check, gpio_is_valid_gpio could be used for more robust check)
if (sda_pin < 0 || scl_pin < 0)
{
ESP_LOGE(TAG, "[Port %d] Invalid SCL/SDA pins: SDA=%d, SCL=%d. Check driver or Kconfig defaults.", dev->port, sda_pin, scl_pin);
xSemaphoreGive(port_state->lock);
return ESP_ERR_INVALID_ARG;
}
/*
* OPTIONAL I2C PULLUP AUTO-CONFIGURATION
*
* By default: Uses whatever sda_pullup_en/scl_pullup_en you set (usually false)
*
* When CONFIG_I2CDEV_AUTO_ENABLE_PULLUPS=y: If both pullup flags are false,
* automatically change them to true to enable internal pullups (~45kΩ).
*
* Manual pullup configuration:
* - Set sda_pullup_en=true, scl_pullup_en=true for internal pullups
* - Set sda_pullup_en=false, scl_pullup_en=false for external pullups
*/
// Read user's pullup configuration (default false if not set)
bool sda_pullup = dev->cfg.sda_pullup_en;
bool scl_pullup = dev->cfg.scl_pullup_en;
#if CONFIG_I2CDEV_AUTO_ENABLE_PULLUPS
// CONFIG_I2CDEV_AUTO_ENABLE_PULLUPS=y: If user didn't configure pullups, enable them automatically
if (!sda_pullup && !scl_pullup)
{
sda_pullup = true;
scl_pullup = true;
ESP_LOGI(TAG, "[Port %d] Auto-enabling internal pullups (CONFIG_I2CDEV_AUTO_ENABLE_PULLUPS=y)", dev->port);
}
#endif
ESP_LOGI(TAG,
"[Port %d] First initialization. Configuring bus with SDA=%d, SCL=%d (Pullups "
"SCL:%d SDA:%d)",
dev->port, sda_pin, scl_pin, scl_pullup, sda_pullup);
i2c_master_bus_config_t bus_config =
{
.i2c_port = dev->port,
.sda_io_num = sda_pin,
.scl_io_num = scl_pin,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = (sda_pullup || scl_pullup),
// Bus speed is not set here. It's per-device or a global target for the bus can be set
// if desired, but i2c_master supports per-device speeds.
};
res = i2c_new_master_bus(&bus_config, &port_state->bus_handle);
if (res == ESP_OK)
{
port_state->installed = true;
port_state->ref_count = 0; // Will be incremented when a device is successfully added
port_state->sda_pin_current = sda_pin;
port_state->scl_pin_current = scl_pin;
dev->sda_pin = sda_pin; // Update dev struct with actual pins used
dev->scl_pin = scl_pin;
ESP_LOGI(TAG, "[Port %d] Successfully installed I2C master bus (Handle: %p).", dev->port, port_state->bus_handle);
}
else
{
ESP_LOGE(TAG, "[Port %d] Failed to create master bus: %d (%s)", dev->port, res, esp_err_to_name(res));
port_state->installed = false;
port_state->bus_handle = NULL;
port_state->sda_pin_current = -1;
port_state->scl_pin_current = -1;
}
}
else
{
ESP_LOGV(TAG, "[Port %d] Port already installed (SDA=%d, SCL=%d, Handle: %p).", dev->port, port_state->sda_pin_current, port_state->scl_pin_current, port_state->bus_handle);
// Pin Consistency Check: For subsequent devices, ensure pins match already-configured bus
gpio_num_t sda_desired = (dev->cfg.sda_io_num == (gpio_num_t) -1) ? (gpio_num_t)port_state->sda_pin_current : dev->cfg.sda_io_num;
gpio_num_t scl_desired = (dev->cfg.scl_io_num == (gpio_num_t) -1) ? (gpio_num_t)port_state->scl_pin_current : dev->cfg.scl_io_num;
if (sda_desired != port_state->sda_pin_current || scl_desired != port_state->scl_pin_current)
{
ESP_LOGE(TAG,
"[Port %d] Pin mismatch for device 0x%02x! Bus on SDA=%d,SCL=%d. Device wants "
"SDA=%d,SCL=%d",
dev->port, dev->addr, port_state->sda_pin_current, port_state->scl_pin_current, sda_desired, scl_desired);
res = ESP_ERR_INVALID_STATE; // Cannot change pins for an installed bus
}
else
{
dev->sda_pin = port_state->sda_pin_current; // Update dev struct with actual pins used
dev->scl_pin = port_state->scl_pin_current;
}
// ref_count is managed by i2c_setup_device when adding/removing device handles
}
xSemaphoreGive(port_state->lock);
ESP_LOGV(TAG, "[Port %d] Port setup finished with res %d.", dev->port, res);
return res;
}
// i2c_setup_device: Ensures port is set up and adds the device to the bus if not already added.
// It also registers the device in active_devices for cleanup purposes.
static esp_err_t i2c_setup_device(i2c_dev_t *dev) // dev is non-const - modifies dev->dev_handle, dev->addr_bit_len
{
if (!dev)
return ESP_ERR_INVALID_ARG;
ESP_LOGV(TAG, "[0x%02x at %d] Setting up device context...", dev->addr, dev->port);
esp_err_t res = i2c_setup_port(dev);
if (res != ESP_OK)
{
ESP_LOGE(TAG, "[0x%02x at %d] Port setup failed during device setup: %d (%s)", dev->addr, dev->port, res, esp_err_to_name(res));
return res;
}
// If addr_bit_len is not set (e.g. 0, which is invalid for i2c_addr_bit_len_t enum), default to
// 7-bit. Modified to conditionally check for I2C_ADDR_BIT_LEN_10 based on hardware support
if (dev->addr_bit_len != I2C_ADDR_BIT_LEN_7
#if SOC_I2C_SUPPORT_10BIT_ADDR
&& dev->addr_bit_len != I2C_ADDR_BIT_LEN_10
#endif
)
{
ESP_LOGD(TAG, "[0x%02x at %d] addr_bit_len not explicitly set, defaulting to 7-bit.", dev->addr, dev->port);
dev->addr_bit_len = I2C_ADDR_BIT_LEN_7;
}
// Only warn about address size if the device is actually using 10-bit addressing
if (dev->addr_bit_len == I2C_ADDR_BIT_LEN_7 && dev->addr > 0x7F)
{
ESP_LOGW(TAG,
"[0x%02x at %d] Device address > 0x7F but addr_bit_len is 7-bit. Ensure address "
"is correct.",
dev->addr, dev->port);
}
#if !defined(SOC_I2C_SUPPORT_10BIT_ADDR) || !SOC_I2C_SUPPORT_10BIT_ADDR
// On platforms without 10-bit support, force 7-bit addressing regardless of user setting
if (dev->addr_bit_len == I2C_ADDR_BIT_LEN_10)
{
ESP_LOGW(TAG, "[0x%02x at %d] 10-bit addressing not supported on this platform, forcing 7-bit mode", dev->addr, dev->port);
dev->addr_bit_len = I2C_ADDR_BIT_LEN_7;
}
#endif
if (dev->dev_handle == NULL)
{
i2c_port_state_t *port_state = &i2c_ports[dev->port];
if (xSemaphoreTake(port_state->lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE)
{
ESP_LOGE(TAG, "[0x%02x at %d] Could not take port mutex for device add", dev->addr, dev->port);
return ESP_ERR_TIMEOUT;
}
if (!port_state->installed || !port_state->bus_handle)
{
ESP_LOGE(TAG, "[0x%02x at %d] Cannot add device, bus for port %d not ready!", dev->addr, dev->port, dev->port);
xSemaphoreGive(port_state->lock);
return ESP_ERR_INVALID_STATE;
}
ESP_LOGV(TAG, "[0x%02x at %d] Adding device to bus (Bus Handle: %p)...", dev->addr, dev->port, port_state->bus_handle);
uint32_t effective_dev_speed = dev->cfg.master.clk_speed;
if (effective_dev_speed == 0)
{
ESP_LOGW(TAG,
"[0x%02x at %d] Device speed (dev->cfg.master.clk_speed) is 0, using default: "
"%" PRIu32 " Hz",
dev->addr, dev->port, (uint32_t)I2C_DEFAULT_FREQ_HZ);
effective_dev_speed = I2C_DEFAULT_FREQ_HZ;
}
i2c_device_config_t dev_config =
{
// Use the possibly modified addr_bit_len that respects hardware capabilities
.dev_addr_length = dev->addr_bit_len,
.device_address = dev->addr,
.scl_speed_hz = effective_dev_speed,
.flags.disable_ack_check = false,
};
res = i2c_master_bus_add_device(port_state->bus_handle, &dev_config, (i2c_master_dev_handle_t *)&dev->dev_handle);
if (res == ESP_OK)
{
ESP_LOGI(TAG, "[0x%02x at %d] Device added successfully (Device Handle: %p, Speed: %" PRIu32 " Hz).", dev->addr, dev->port, dev->dev_handle, effective_dev_speed);
// Increment the port reference count for each device successfully added
port_state->ref_count++;
ESP_LOGV(TAG, "[Port %d] Incremented ref_count to %" PRIu32, dev->port, port_state->ref_count);
}
else
{
ESP_LOGE(TAG, "[0x%02x at %d] Failed to add device to bus: %d (%s)", dev->addr, dev->port, res, esp_err_to_name(res));
dev->dev_handle = NULL;
}
xSemaphoreGive(port_state->lock);
}
else
{
ESP_LOGV(TAG, "[0x%02x at %d] Device handle %p already exists. Skipping add.", dev->addr, dev->port, dev->dev_handle);
res = ESP_OK;
}
ESP_LOGV(TAG, "[0x%02x at %d] Device context setup finished with res %d.", dev->addr, dev->port, res);
return res;
}
// Helper function with retry mechanism for I2C operations
static esp_err_t i2c_do_operation_with_retry(i2c_dev_t *dev, esp_err_t (*i2c_func)(i2c_master_dev_handle_t, const void *, size_t, void *, size_t, int), const void *write_buffer, size_t write_size,
void *read_buffer, size_t read_size)
{
if (!dev)
return ESP_ERR_INVALID_ARG;
esp_err_t res = ESP_FAIL;
int retry = 0;
int timeout_ms = CONFIG_I2CDEV_TIMEOUT;
ESP_LOGV(TAG, "[0x%02x at %d] Performing I2C operation (timeout %d ms)...", dev->addr, dev->port, timeout_ms);
while (retry <= I2C_MAX_RETRIES)
{
// Ensure device is set up before each attempt, in case handle became stale or bus was reset
// This is more robust if issues like bus errors or device resets occur.
res = i2c_setup_device(dev);
if (res != ESP_OK)
{
ESP_LOGE(TAG, "[0x%02x at %d] Device setup failed (Try %d): %d (%s). Retrying setup...", dev->addr, dev->port, retry, res, esp_err_to_name(res));
// No point continuing this attempt if setup fails, but the loop will retry setup.
vTaskDelay(pdMS_TO_TICKS(I2C_RETRY_BASE_DELAY_MS * (1 << (retry))));
retry++;
continue;
}
if (!dev->dev_handle)
{
ESP_LOGE(TAG,
"[0x%02x at %d] Device handle is NULL after setup (Try %d)! Cannot perform "
"operation.",
dev->addr, dev->port, retry);
// This indicates a persistent problem with adding the device to the bus.
// No point retrying the i2c_func if handle is null.
res = ESP_ERR_INVALID_STATE;
vTaskDelay(pdMS_TO_TICKS(I2C_RETRY_BASE_DELAY_MS * (1 << (retry))));
retry++;
continue;
}
ESP_LOGV(TAG, "[0x%02x at %d] Attempting I2C op (Try %d, Handle %p)", dev->addr, dev->port, retry, dev->dev_handle);
res = i2c_func(dev->dev_handle, write_buffer, write_size, read_buffer, read_size, timeout_ms);
if (res == ESP_OK)
{
ESP_LOGV(TAG, "[0x%02x at %d] I2C operation successful (Try %d).", dev->addr, dev->port, retry);
return ESP_OK;
}
ESP_LOGW(TAG, "[0x%02x at %d] I2C op failed (Try %d, Handle %p): %d (%s).", dev->addr, dev->port, retry, dev->dev_handle, res, esp_err_to_name(res));
// Only remove handle on errors that indicate handle corruption or permanent invalidity
// Don't remove on temporary errors like ESP_ERR_TIMEOUT, ESP_FAIL (NACK), etc.
bool should_remove_handle = false;
switch (res)
{
case ESP_ERR_INVALID_ARG:
// Handle was likely removed by another task or is corrupted
should_remove_handle = true;
ESP_LOGW(TAG, "[0x%02x at %d] Invalid argument error - handle may be corrupted", dev->addr, dev->port);
break;
case ESP_ERR_INVALID_STATE:
// I2C driver is in invalid state, handle likely needs recreation
should_remove_handle = true;
ESP_LOGW(TAG, "[0x%02x at %d] Invalid state error - handle may need recreation", dev->addr, dev->port);
break;
default:
// For other errors (timeout, NACK, bus busy, etc.), keep the handle
// These are usually temporary and don't require handle recreation
should_remove_handle = false;
ESP_LOGV(TAG, "[0x%02x at %d] Temporary error - keeping handle for retry", dev->addr, dev->port);
break;
}
if (should_remove_handle && dev->dev_handle)
{
ESP_LOGW(TAG, "[0x%02x at %d] Removing potentially corrupted device handle %p after permanent error", dev->addr, dev->port, dev->dev_handle);
// Try to remove the handle from the bus before nullifying
esp_err_t rm_res = i2c_master_bus_rm_device(dev->dev_handle);
if (rm_res != ESP_OK)
{
ESP_LOGW(TAG, "[0x%02x at %d] Failed to remove corrupted handle (expected): %s", dev->addr, dev->port, esp_err_to_name(rm_res));
// This is expected if the handle was already invalid - continue cleanup
}
dev->dev_handle = NULL;
}
retry++;
if (retry <= I2C_MAX_RETRIES)
{
vTaskDelay(pdMS_TO_TICKS(I2C_RETRY_BASE_DELAY_MS * (1 << retry))); // Exponential backoff
ESP_LOGW(TAG, "[0x%02x at %d] Retrying operation...", dev->addr, dev->port);
}
}
ESP_LOGE(TAG, "[0x%02x at %d] I2C operation failed after %d retries. Last error: %d (%s)", dev->addr, dev->port, I2C_MAX_RETRIES + 1, res, esp_err_to_name(res));
return res;
}
// Wrapper functions for the I2C master API to use with the retry mechanism
// i2c_do_operation_with_retry() needs a unified function signature for all I2C operations
static esp_err_t i2c_master_transmit_wrapper(i2c_master_dev_handle_t handle, const void *write_buffer, size_t write_size, void *read_buffer, size_t read_size, int timeout_ms)
{
return i2c_master_transmit(handle, write_buffer, write_size, timeout_ms);
}
static esp_err_t i2c_master_receive_wrapper(i2c_master_dev_handle_t handle, const void *write_buffer, size_t write_size, void *read_buffer, size_t read_size, int timeout_ms)
{
return i2c_master_receive(handle, read_buffer, read_size, timeout_ms);
}
static esp_err_t i2c_master_transmit_receive_wrapper(i2c_master_dev_handle_t handle, const void *write_buffer, size_t write_size, void *read_buffer, size_t read_size, int timeout_ms)
{
return i2c_master_transmit_receive(handle, write_buffer, write_size, read_buffer, read_size, timeout_ms);
}
esp_err_t i2c_dev_read(const i2c_dev_t *dev, const void *out_data, size_t out_size, void *in_data, size_t in_size)
{
if (!dev || !in_data || !in_size)
return ESP_ERR_INVALID_ARG;
ESP_LOGV(TAG, "[0x%02x at %d] i2c_dev_read called (out_size: %u, in_size: %u)", dev->addr, dev->port, out_size, in_size);
esp_err_t result = i2c_do_operation_with_retry((i2c_dev_t *)dev, // Cast to non-const for i2c_setup_device internal modifications
out_data && out_size ? i2c_master_transmit_receive_wrapper : i2c_master_receive_wrapper, out_data, out_size, in_data, in_size);
ESP_LOGV(TAG, "[0x%02x at %d] i2c_dev_read result: %s (%d)", dev->addr, dev->port, esp_err_to_name(result), result);
return result;
}
esp_err_t i2c_dev_write(const i2c_dev_t *dev, const void *out_reg, size_t out_reg_size, const void *out_data, size_t out_size)
{
if (!dev)
return ESP_ERR_INVALID_ARG;
if ((!out_reg || !out_reg_size) && (!out_data || !out_size))
return ESP_ERR_INVALID_ARG;
ESP_LOGV(TAG, "[0x%02x at %d] i2c_dev_write called (reg_size: %u, data_size: %u)", dev->addr, dev->port, out_reg_size, out_size);
esp_err_t res;
if (out_reg && out_reg_size && out_data && out_size)
{
size_t total_write_size = out_reg_size + out_size;
// Check for overflow before proceeding
if (total_write_size < out_reg_size || total_write_size < out_size)
{
ESP_LOGE(TAG, "[0x%02x at %d] Write size overflow: reg_size=%u + data_size=%u", dev->addr, dev->port, out_reg_size, out_size);
return ESP_ERR_INVALID_ARG;
}
// Use stack for small buffers to avoid heap fragmentation
if (total_write_size <= I2CDEV_MAX_STACK_ALLOC_SIZE)
{
// Use stack allocation for small buffers
uint8_t stack_buf[I2CDEV_MAX_STACK_ALLOC_SIZE];
memcpy(stack_buf, out_reg, out_reg_size);
memcpy(stack_buf + out_reg_size, out_data, out_size);
res = i2c_do_operation_with_retry((i2c_dev_t *)dev, i2c_master_transmit_wrapper, stack_buf, total_write_size, NULL, 0);
}
else
{
uint8_t *heap_buf = malloc(total_write_size);
if (!heap_buf)
{
ESP_LOGE(TAG, "[0x%02x at %d] Failed to allocate %u bytes for write", dev->addr, dev->port, total_write_size);
return ESP_ERR_NO_MEM;
}
memcpy(heap_buf, out_reg, out_reg_size);
memcpy(heap_buf + out_reg_size, out_data, out_size);
res = i2c_do_operation_with_retry((i2c_dev_t *)dev, i2c_master_transmit_wrapper, heap_buf, total_write_size, NULL, 0);
free(heap_buf); // Free buffer regardless of operation result
}
}
else if (out_reg && out_reg_size)
{
res = i2c_do_operation_with_retry((i2c_dev_t *)dev, i2c_master_transmit_wrapper, out_reg, out_reg_size, NULL, 0);
}
else if (out_data && out_size)
{
res = i2c_do_operation_with_retry((i2c_dev_t *)dev, i2c_master_transmit_wrapper, out_data, out_size, NULL, 0);
}
else
{
return ESP_ERR_INVALID_ARG; // Shouldn't reach here given the earlier check
}
ESP_LOGV(TAG, "[0x%02x at %d] i2c_dev_write result: %s (%d)", dev->addr, dev->port, esp_err_to_name(res), res);
return res;
}
esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, void *data, size_t size)
{
ESP_LOGV(TAG, "[0x%02x at %d] i2c_dev_read_reg called (reg: 0x%02x, size: %u)", dev->addr, dev->port, reg, size);
return i2c_dev_read(dev, ®, 1, data, size);
}
esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *data, size_t size)
{
ESP_LOGV(TAG, "[0x%02x at %d] i2c_dev_write_reg called (reg: 0x%02x, size: %u)", dev->addr, dev->port, reg, size);
return i2c_dev_write(dev, ®, 1, data, size);
}
esp_err_t i2c_dev_check_present(const i2c_dev_t *dev_const)
{
if (!dev_const)
return ESP_ERR_INVALID_ARG;
ESP_LOGV(TAG, "[0x%02x at %d] Probing device presence...", dev_const->addr, dev_const->port);
// Cast to non-const for i2c_setup_port (which may modify internal state)
i2c_dev_t *dev = (i2c_dev_t *)dev_const;
// Ensure the I2C port is set up before probing
esp_err_t setup_res = i2c_setup_port(dev);
if (setup_res != ESP_OK)
{
ESP_LOGE(TAG, "[0x%02x at %d] Failed to setup port for probe: %s", dev_const->addr, dev_const->port, esp_err_to_name(setup_res));
return setup_res;
}
// Now probe using the initialized bus
if (dev_const->port < I2C_NUM_MAX && i2c_ports[dev_const->port].lock)
{
if (xSemaphoreTake(i2c_ports[dev_const->port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) == pdTRUE)
{
if (i2c_ports[dev_const->port].installed && i2c_ports[dev_const->port].bus_handle)
{
// Use ESP-IDF's built-in probe function - completely non-intrusive
esp_err_t probe_res = i2c_master_probe(i2c_ports[dev_const->port].bus_handle, dev_const->addr, CONFIG_I2CDEV_TIMEOUT);
xSemaphoreGive(i2c_ports[dev_const->port].lock);
if (probe_res == ESP_OK)
{
ESP_LOGV(TAG, "[0x%02x at %d] Device probe successful - device present", dev_const->addr, dev_const->port);
return ESP_OK;
}
else
{
ESP_LOGV(TAG, "[0x%02x at %d] Device probe failed: %s", dev_const->addr, dev_const->port, esp_err_to_name(probe_res));
return probe_res;
}
}
else
{
xSemaphoreGive(i2c_ports[dev_const->port].lock);
ESP_LOGW(TAG, "[0x%02x at %d] Cannot probe - bus not ready on port %d", dev_const->addr, dev_const->port, dev_const->port);
return ESP_ERR_INVALID_STATE;
}
}
else
{
ESP_LOGE(TAG, "[0x%02x at %d] Could not take port mutex for probe", dev_const->addr, dev_const->port);
return ESP_ERR_TIMEOUT;
}
}
else
{
ESP_LOGE(TAG, "[0x%02x at %d] Invalid port or port not initialized", dev_const->addr, dev_const->port);
return ESP_ERR_INVALID_ARG;
}
}
// Compatibility wrapper for legacy code that still calls i2c_dev_probe
// The new driver implementation uses i2c_master_probe which doesn't need operation_type
esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type)
{
ESP_LOGV(TAG, "[0x%02x at %d] Legacy probe called (operation_type %d), redirecting to new implementation", dev->addr, dev->port, operation_type);
return i2c_dev_check_present(dev);
}
// Clean up function to be called at application exit
esp_err_t i2cdev_done(void)
{
esp_err_t result = ESP_OK;
ESP_LOGV(TAG, "Cleaning up I2C subsystem (i2c_master)...");
for (int i = 0; i < I2C_NUM_MAX; i++)
{
if (i2c_ports[i].lock)
{
ESP_LOGV(TAG, "[Port %d] Cleaning up port...", i);
if (xSemaphoreTake(i2c_ports[i].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE)
{
ESP_LOGE(TAG, "[Port %d] Could not take port mutex for cleanup", i);
result = ESP_FAIL;
}
else
{
if (i2c_ports[i].installed)
{
ESP_LOGV(TAG, "[Port %d] Removing active devices before deleting bus...", i);
// Remove all registered devices for this port from the bus
for (int j = 0; j < CONFIG_I2CDEV_MAX_DEVICES_PER_PORT; j++)
{
i2c_dev_t *dev_ptr = active_devices[i][j];
if (dev_ptr != NULL && dev_ptr->dev_handle != NULL)
{
ESP_LOGV(TAG, "[Port %d] Removing device 0x%02x (Handle %p)", i, dev_ptr->addr, dev_ptr->dev_handle);
esp_err_t rm_res = i2c_master_bus_rm_device(dev_ptr->dev_handle);
if (rm_res != ESP_OK)
{
ESP_LOGE(TAG, "[Port %d] Failed to remove device 0x%02x handle: %d", i, dev_ptr->addr, rm_res);
// Continue cleanup despite error
if (result == ESP_OK)
result = rm_res; // Report first error
}
dev_ptr->dev_handle = NULL;
}
}
ESP_LOGV(TAG, "[Port %d] Deleting master bus handle %p...", i, i2c_ports[i].bus_handle);
esp_err_t del_res = i2c_del_master_bus(i2c_ports[i].bus_handle);
if (del_res != ESP_OK)
{
ESP_LOGE(TAG, "[Port %d] Failed to delete I2C bus during cleanup: %d", i, del_res);
if (result == ESP_OK)
result = del_res;
}
i2c_ports[i].installed = false;
i2c_ports[i].bus_handle = NULL;
i2c_ports[i].ref_count = 0;
}
xSemaphoreGive(i2c_ports[i].lock);
} // End else (mutex taken)
ESP_LOGV(TAG, "[Port %d] Deleting port mutex...", i);
vSemaphoreDelete(i2c_ports[i].lock);
i2c_ports[i].lock = NULL;
// Clear the active device list for this port
memset(active_devices[i], 0, sizeof(active_devices[i]));
ESP_LOGV(TAG, "[Port %d] Cleanup complete.", i);
} // end if lock exists
} // end for loop
ESP_LOGV(TAG, "I2C subsystem cleanup finished with result: %d", result);
return result;
}