SC-F001 way better logging and parameters. not integrated yet but.
This commit is contained in:
1
managed_components/esp-idf-lib__i2cdev/.ackrc
Normal file
1
managed_components/esp-idf-lib__i2cdev/.ackrc
Normal file
@@ -0,0 +1 @@
|
||||
--ignore-dir=build
|
||||
17
managed_components/esp-idf-lib__i2cdev/.astylerc
Normal file
17
managed_components/esp-idf-lib__i2cdev/.astylerc
Normal file
@@ -0,0 +1,17 @@
|
||||
--align-reference=name
|
||||
--attach-classes
|
||||
--attach-classes
|
||||
--attach-namespaces
|
||||
--convert-tabs
|
||||
--exclude=build
|
||||
--exclude=common
|
||||
--exclude=managed_components
|
||||
--ignore-exclude-errors
|
||||
--indent-switches
|
||||
--indent=spaces=4
|
||||
--keep-one-line-statements
|
||||
--max-continuation-indent=120
|
||||
--pad-header
|
||||
--pad-oper
|
||||
--style=allman
|
||||
--unpad-paren
|
||||
66
managed_components/esp-idf-lib__i2cdev/.clang-format
Normal file
66
managed_components/esp-idf-lib__i2cdev/.clang-format
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: WebKit
|
||||
AlignConsecutiveMacros: true
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: true
|
||||
AfterControlStatement: Always
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: false
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
ColumnLimit: 200
|
||||
CompactNamespaces: true
|
||||
Cpp11BracedListStyle: false
|
||||
FixNamespaceComments: true
|
||||
IndentCaseLabels: true
|
||||
IndentWidth: 4
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: Inner
|
||||
PointerAlignment: Right
|
||||
ReflowComments: true
|
||||
SortIncludes: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeInheritanceColon: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Auto
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
1
managed_components/esp-idf-lib__i2cdev/.component_hash
Normal file
1
managed_components/esp-idf-lib__i2cdev/.component_hash
Normal file
@@ -0,0 +1 @@
|
||||
11c08f9e1a7d346b5dd763196dc2567cf2209ae49042402c2c2d296624601c14
|
||||
28
managed_components/esp-idf-lib__i2cdev/.eil.yml
Normal file
28
managed_components/esp-idf-lib__i2cdev/.eil.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
name: i2cdev
|
||||
description: ESP-IDF I2C master thread-safe utilities
|
||||
version: 2.0.8
|
||||
groups:
|
||||
- common
|
||||
code_owners:
|
||||
- UncleRus
|
||||
depends:
|
||||
- driver
|
||||
- freertos
|
||||
- esp_idf_lib_helpers
|
||||
thread_safe: yes
|
||||
targets:
|
||||
- esp32
|
||||
- esp8266
|
||||
- esp32s2
|
||||
- esp32c3
|
||||
- esp32s3
|
||||
- esp32c2
|
||||
- esp32c6
|
||||
- esp32h2
|
||||
- esp32p4
|
||||
- esp32c5
|
||||
- esp32c61
|
||||
license: MIT
|
||||
copyrights:
|
||||
- name: UncleRus
|
||||
year: 2018
|
||||
7
managed_components/esp-idf-lib__i2cdev/.gitignore
vendored
Normal file
7
managed_components/esp-idf-lib__i2cdev/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
examples/**/sdkconfig
|
||||
examples/**/sdkconfig.old
|
||||
examples/**/build/
|
||||
examples/**/dependencies.lock
|
||||
docs/_*/
|
||||
docs/doxygen.log
|
||||
*.swp
|
||||
3
managed_components/esp-idf-lib__i2cdev/.gitmodules
vendored
Normal file
3
managed_components/esp-idf-lib__i2cdev/.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "common"]
|
||||
path = common
|
||||
url = https://github.com/esp-idf-lib/common.git
|
||||
45
managed_components/esp-idf-lib__i2cdev/CMakeLists.txt
Normal file
45
managed_components/esp-idf-lib__i2cdev/CMakeLists.txt
Normal file
@@ -0,0 +1,45 @@
|
||||
# ESP-IDF CMake component for i2cdev library
|
||||
set(req driver freertos esp_idf_lib_helpers)
|
||||
|
||||
# ESP-IDF version detection for automatic driver selection
|
||||
# Check for manual override via Kconfig
|
||||
if(CONFIG_I2CDEV_USE_LEGACY_DRIVER)
|
||||
set(USE_LEGACY_DRIVER TRUE)
|
||||
message(STATUS "i2cdev: Manual override - using legacy driver (CONFIG_I2CDEV_USE_LEGACY_DRIVER=y)")
|
||||
elseif(NOT DEFINED IDF_VERSION_MAJOR)
|
||||
# In case older ESP-IDF versions that don't define IDF_VERSION_MAJOR
|
||||
set(USE_LEGACY_DRIVER TRUE)
|
||||
message(STATUS "i2cdev: IDF_VERSION_MAJOR not defined, using legacy driver")
|
||||
elseif(IDF_VERSION_MAJOR LESS 5)
|
||||
set(USE_LEGACY_DRIVER TRUE)
|
||||
message(STATUS "i2cdev: ESP-IDF v${IDF_VERSION_MAJOR}.x detected, using legacy driver")
|
||||
elseif(IDF_VERSION_MAJOR EQUAL 5 AND IDF_VERSION_MINOR LESS 3)
|
||||
set(USE_LEGACY_DRIVER TRUE)
|
||||
message(STATUS "i2cdev: ESP-IDF v${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR} detected, using legacy driver")
|
||||
else()
|
||||
set(USE_LEGACY_DRIVER FALSE)
|
||||
message(STATUS "i2cdev: ESP-IDF v${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR} detected, using new i2c_master driver")
|
||||
endif()
|
||||
|
||||
# Conditionally set the source file based on version detection or Kconfig override
|
||||
if(USE_LEGACY_DRIVER)
|
||||
set(SRCS "i2cdev_legacy.c")
|
||||
message(STATUS "i2cdev: Compiling with legacy I2C driver (i2cdev_legacy.c)")
|
||||
else()
|
||||
set(SRCS "i2cdev.c")
|
||||
message(STATUS "i2cdev: Compiling with new I2C master driver (i2cdev.c)")
|
||||
endif()
|
||||
|
||||
# Register the component
|
||||
idf_component_register(SRCS ${SRCS}
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ${req})
|
||||
|
||||
|
||||
# include common cmake file for components
|
||||
set(ESP_IDF_LIB_CMAKE ${CMAKE_CURRENT_LIST_DIR}/common/cmake/esp-idf-lib.cmake)
|
||||
if(EXISTS ${ESP_IDF_LIB_CMAKE})
|
||||
include(${ESP_IDF_LIB_CMAKE})
|
||||
else()
|
||||
message(WARNING "${ESP_IDF_LIB_CMAKE} not found")
|
||||
endif()
|
||||
65
managed_components/esp-idf-lib__i2cdev/Kconfig
Normal file
65
managed_components/esp-idf-lib__i2cdev/Kconfig
Normal file
@@ -0,0 +1,65 @@
|
||||
menu "I2C Device Library"
|
||||
|
||||
config I2CDEV_USE_LEGACY_DRIVER
|
||||
bool "Use Legacy I2C Driver API"
|
||||
default n
|
||||
help
|
||||
Select this option to use the older ESP-IDF I2C driver API (driver/i2c.h)
|
||||
instead of the newer driver API (driver/i2c_master.h).
|
||||
|
||||
This is automatically determined by the build system based on your ESP-IDF version.
|
||||
For ESP-IDF versions prior to v5.3, the legacy driver will be used automatically.
|
||||
You can manually override this setting if needed.
|
||||
|
||||
config I2CDEV_AUTO_ENABLE_PULLUPS
|
||||
bool "Automatically enable internal I2C pullups when not configured"
|
||||
default n
|
||||
depends on !IDF_TARGET_ESP8266
|
||||
help
|
||||
When enabled, internal pullup resistors are automatically enabled
|
||||
when both sda_pullup_en and scl_pullup_en are false (default state).
|
||||
|
||||
Useful for development and prototyping. Disable for production
|
||||
systems with external pullups to avoid interference.
|
||||
|
||||
Considerations:
|
||||
- May increase power consumption slightly
|
||||
- Could interfere with carefully tuned external pullups
|
||||
- Not recommended for battery-powered applications
|
||||
|
||||
Note: This option only affects the modern i2cdev driver (ESP32 family).
|
||||
Legacy driver behavior is unchanged for compatibility.
|
||||
|
||||
config I2CDEV_DEFAULT_SDA_PIN
|
||||
int "Default I2C SDA pin"
|
||||
default 21
|
||||
help
|
||||
Default SDA pin for I2C devices.
|
||||
|
||||
config I2CDEV_DEFAULT_SCL_PIN
|
||||
int "Default I2C SCL pin"
|
||||
default 22
|
||||
help
|
||||
Default SCL pin for I2C devices.
|
||||
|
||||
config I2CDEV_MAX_DEVICES_PER_PORT
|
||||
int "Maximum number of devices per I2C port"
|
||||
default 8
|
||||
help
|
||||
Maximum number of devices that can be registered on a single I2C port.
|
||||
|
||||
config I2CDEV_TIMEOUT
|
||||
int "I2C transaction timeout, milliseconds"
|
||||
default 1000
|
||||
range 10 5000
|
||||
|
||||
config I2CDEV_NOLOCK
|
||||
bool "Disable the use of mutexes"
|
||||
default n
|
||||
help
|
||||
Attention! After enabling this option, all I2C device
|
||||
drivers will become non-thread safe.
|
||||
Use this option if you need to access your I2C devices
|
||||
from interrupt handlers.
|
||||
|
||||
endmenu
|
||||
19
managed_components/esp-idf-lib__i2cdev/Kconfig.i2c
Normal file
19
managed_components/esp-idf-lib__i2cdev/Kconfig.i2c
Normal file
@@ -0,0 +1,19 @@
|
||||
config EXAMPLE_I2C_MASTER_SCL
|
||||
int "SCL GPIO Number"
|
||||
default 5 if IDF_TARGET_ESP8266
|
||||
default 6 if IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C5 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32C61
|
||||
default 19 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
default 4 if IDF_TARGET_ESP32H2
|
||||
default 4 if IDF_TARGET_ESP32P4
|
||||
help
|
||||
GPIO number for I2C Master clock line.
|
||||
|
||||
config EXAMPLE_I2C_MASTER_SDA
|
||||
int "SDA GPIO Number"
|
||||
default 4 if IDF_TARGET_ESP8266
|
||||
default 5 if IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C5 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32C61
|
||||
default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
default 3 if IDF_TARGET_ESP32H2
|
||||
default 3 if IDF_TARGET_ESP32P4
|
||||
help
|
||||
GPIO number for I2C Master data line.
|
||||
21
managed_components/esp-idf-lib__i2cdev/LICENSE
Normal file
21
managed_components/esp-idf-lib__i2cdev/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus)
|
||||
|
||||
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.
|
||||
30
managed_components/esp-idf-lib__i2cdev/README.md
Normal file
30
managed_components/esp-idf-lib__i2cdev/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# esp-idf-lib/i2cdev
|
||||
|
||||
[](https://github.com/esp-idf-lib/i2cdev/actions/workflows//build.yml)
|
||||
[](https://github.com/esp-idf-lib/i2cdev/actions/workflows//build-docs.yml)
|
||||
[](https://github.com/esp-idf-lib/i2cdev/actions/workflows//validate-component.yml)
|
||||
|
||||
ESP-IDF I2C master thread-safe utilities.
|
||||
|
||||
* [Documentation](https://esp-idf-lib.github.io/i2cdev/)
|
||||
* [Repository](https://github.com/esp-idf-lib/i2cdev)
|
||||
* [Issues](https://github.com/esp-idf-lib/i2cdev/issues)
|
||||
* [Discussions and questions](https://github.com/esp-idf-lib/core/discussions)
|
||||
* [Component page at the ESP Component Registry](https://components.espressif.com/components/esp-idf-lib/i2cdev)
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
idf.py add-dependency esp-idf-lib/i2cdev
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For questions and discussions about the component, please use
|
||||
[Discussions](https://github.com/esp-idf-lib/core/discussions)
|
||||
at [esp-idf-lib/core](https://github.com/esp-idf-lib/core).
|
||||
|
||||
## Contributing
|
||||
|
||||
Please read [CONTRIBUTING.md](https://github.com/esp-idf-lib/core/blob/main/CONTRIBUTING.md)
|
||||
at [esp-idf-lib/core](https://github.com/esp-idf-lib/core).
|
||||
18
managed_components/esp-idf-lib__i2cdev/bin/eil-q
Normal file
18
managed_components/esp-idf-lib__i2cdev/bin/eil-q
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "pathname"
|
||||
require "yaml"
|
||||
|
||||
# A simple CLI to get values in .eil.yml
|
||||
|
||||
key = ARGV.shift
|
||||
project_root = Pathname.new(File.expand_path(__FILE__)).parent.parent
|
||||
eil_file = project_root / ".eil.yml"
|
||||
doc = YAML.safe_load(File.read eil_file)
|
||||
|
||||
case key
|
||||
when "copyright_string"
|
||||
puts doc["copyrights"].map { |e| "#{e['year']}, #{e['name']}" }.join(", ")
|
||||
else
|
||||
puts doc[key]
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
# Set common build flags but enable them only when the build is in our CI,
|
||||
# making them optional. The idea is, fail when compiled in our CI but just
|
||||
# warn in any other cases. Code that compiles with one toolchain warning-free
|
||||
# may not do so with another toolchain, which creates a project dependency on
|
||||
# specific toolchain vendors and versions.
|
||||
#
|
||||
# Define flags that may cause failures here.
|
||||
if (DEFINED ENV{ESP_IDF_LIB_CI})
|
||||
set(ESP_IDF_LIB_CI_FLAGS
|
||||
-Werror=unused-variable
|
||||
-Werror=unused-function
|
||||
-Werror=write-strings
|
||||
-Werror
|
||||
)
|
||||
endif()
|
||||
|
||||
# Set common build flags. Mandatory.
|
||||
#
|
||||
# Define flags that do not cause failures here.
|
||||
set(ESP_IDF_LIB_FLAGS
|
||||
-Wextra
|
||||
-Wwrite-strings
|
||||
-Wunused-variable
|
||||
-Wunused-function
|
||||
-Wreturn-type
|
||||
)
|
||||
|
||||
# When COMPONENT_LIB is INTERFACE_LIBRARY, or a header-only library, do not
|
||||
# set the flags.
|
||||
get_target_property(COMPONENT_TYPE ${COMPONENT_LIB} TYPE)
|
||||
if(NOT COMPONENT_TYPE STREQUAL "INTERFACE_LIBRARY")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE
|
||||
${ESP_IDF_LIB_FLAGS}
|
||||
${ESP_IDF_LIB_CI_FLAGS}
|
||||
)
|
||||
endif()
|
||||
29
managed_components/esp-idf-lib__i2cdev/component.mk
Normal file
29
managed_components/esp-idf-lib__i2cdev/component.mk
Normal file
@@ -0,0 +1,29 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS = .
|
||||
|
||||
ifdef CONFIG_IDF_TARGET_ESP8266
|
||||
COMPONENT_DEPENDS = esp8266 freertos esp_idf_lib_helpers
|
||||
# ESP8266 RTOS SDK auto-detects all .c files, so use COMPONENT_OBJS to override
|
||||
# This prevents both i2cdev.c and i2cdev_legacy.c from being compiled
|
||||
COMPONENT_OBJS := i2cdev_legacy.o
|
||||
COMPONENT_SRCDIRS := .
|
||||
else
|
||||
COMPONENT_DEPENDS = driver freertos esp_idf_lib_helpers
|
||||
# For ESP32 family, check for manual override first
|
||||
ifdef CONFIG_I2CDEV_USE_LEGACY_DRIVER
|
||||
COMPONENT_SRCS = i2cdev_legacy.c
|
||||
else
|
||||
# Check if version variables are available, fallback to legacy if not
|
||||
ifdef IDF_VERSION_MAJOR
|
||||
ifeq ($(shell test $(IDF_VERSION_MAJOR) -lt 5 && echo 1),1)
|
||||
COMPONENT_SRCS = i2cdev_legacy.c
|
||||
else ifeq ($(shell test $(IDF_VERSION_MAJOR) -eq 5 -a $(IDF_VERSION_MINOR) -lt 3 && echo 1),1)
|
||||
COMPONENT_SRCS = i2cdev_legacy.c
|
||||
else
|
||||
COMPONENT_SRCS = i2cdev.c
|
||||
endif
|
||||
else
|
||||
# Version variables not available - fallback to legacy driver for safety
|
||||
COMPONENT_SRCS = i2cdev_legacy.c
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@@ -0,0 +1,7 @@
|
||||
# The following four lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(i2c_scanner)
|
||||
@@ -0,0 +1,5 @@
|
||||
#V := 1
|
||||
PROJECT_NAME := i2c_scanner
|
||||
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,31 @@
|
||||
# I2C bus scanner
|
||||
|
||||
## What it does
|
||||
|
||||
This example scans the i2c bus in a loop and prints out a table with the addresses of the found i2c devices.
|
||||
|
||||
## Wiring
|
||||
|
||||
Connect `SCL` and `SDA` pins to the following pins with appropriate pull-up
|
||||
resistors.
|
||||
|
||||
| Name | Description | Defaults |
|
||||
|------|-------------|----------|
|
||||
| `CONFIG_EXAMPLE_I2C_MASTER_SCL` | GPIO number for `SCL` | "5" for `esp8266`, "6" for `esp32c3`, "19" for `esp32`, `esp32s2`, and `esp32s3` |
|
||||
| `CONFIG_EXAMPLE_I2C_MASTER_SDA` | GPIO number for `SDA` | "4" for `esp8266`, "5" for `esp32c3`, "18" for `esp32`, `esp32s2`, and `esp32s3` |
|
||||
|
||||
## Example output
|
||||
|
||||
Three devices found on a bus: 0x38, 0x60 and 0x77
|
||||
|
||||
```
|
||||
0 1 2 3 4 5 6 7 8 9 a b c d e f
|
||||
00: -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
30: -- -- -- -- -- -- -- -- 38 -- -- -- -- -- -- --
|
||||
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
60: 60 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
70: -- -- -- -- -- -- -- 77
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,7 @@
|
||||
menu "I2C scanner configuration"
|
||||
config EXAMPLE_I2C_CLOCK_HZ
|
||||
int "I2C clock frequency, Hz"
|
||||
default 100000
|
||||
|
||||
rsource "../../../Kconfig.i2c"
|
||||
endmenu
|
||||
@@ -0,0 +1 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS = . include/
|
||||
@@ -0,0 +1,7 @@
|
||||
dependencies:
|
||||
esp-idf-lib/esp_idf_lib_helpers:
|
||||
version: '*'
|
||||
esp-idf-lib/i2cdev:
|
||||
version: '*'
|
||||
description: default
|
||||
version: 1.0.0
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <i2cdev.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void task(void *ignore)
|
||||
{
|
||||
i2c_dev_t dev = { 0 };
|
||||
dev.cfg.sda_io_num = CONFIG_EXAMPLE_I2C_MASTER_SDA;
|
||||
dev.cfg.scl_io_num = CONFIG_EXAMPLE_I2C_MASTER_SCL;
|
||||
#if HELPER_TARGET_IS_ESP32
|
||||
dev.cfg.master.clk_speed = CONFIG_EXAMPLE_I2C_CLOCK_HZ; // 100kHz
|
||||
#endif
|
||||
while (1)
|
||||
{
|
||||
esp_err_t res;
|
||||
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\n");
|
||||
printf("00: ");
|
||||
for (uint8_t addr = 3; addr < 0x78; addr++)
|
||||
{
|
||||
if (addr % 16 == 0)
|
||||
printf("\n%.2x:", addr);
|
||||
|
||||
dev.addr = addr;
|
||||
res = i2c_dev_probe(&dev, I2C_DEV_WRITE);
|
||||
|
||||
if (res == 0)
|
||||
printf(" %.2x", addr);
|
||||
else
|
||||
printf(" --");
|
||||
}
|
||||
printf("\n\n");
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
// Init i2cdev library
|
||||
ESP_ERROR_CHECK(i2cdev_init());
|
||||
// Start task
|
||||
xTaskCreate(task, "i2c_scanner", configMINIMAL_STACK_SIZE * 3, NULL, 5, NULL);
|
||||
}
|
||||
897
managed_components/esp-idf-lib__i2cdev/i2cdev.c
Normal file
897
managed_components/esp-idf-lib__i2cdev/i2cdev.c
Normal file
@@ -0,0 +1,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;
|
||||
}
|
||||
381
managed_components/esp-idf-lib__i2cdev/i2cdev.h
Normal file
381
managed_components/esp-idf-lib__i2cdev/i2cdev.h
Normal file
@@ -0,0 +1,381 @@
|
||||
/**
|
||||
* @file i2cdev.h
|
||||
* @defgroup i2cdev i2cdev
|
||||
* @{
|
||||
*
|
||||
* ESP-IDF I2C master thread-safe functions for communication with I2C slave
|
||||
*
|
||||
* This implementation uses the newer ESP-IDF I2C master driver (v5.0+).
|
||||
* For ESP-IDF versions using the legacy I2C driver, use i2cdev_legacy.c instead.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* ============================================================================
|
||||
* OPTIONAL I2C PULLUP AUTO-CONFIGURATION
|
||||
* ============================================================================
|
||||
*
|
||||
* This library can optionally enable internal I2C pullups when no explicit
|
||||
* pullup configuration is provided. Feature is DISABLED by default for
|
||||
* backward compatibility (CONFIG_I2CDEV_AUTO_ENABLE_PULLUPS=n).
|
||||
*
|
||||
* Optional auto-pullup (CONFIG_I2CDEV_AUTO_ENABLE_PULLUPS=y):
|
||||
* - If both pullup flags are false (not set/default state), automatically enables internal pullups
|
||||
* - Only available on ESP32 family (modern driver)
|
||||
* - Legacy driver always uses explicit configuration
|
||||
*
|
||||
*
|
||||
* Example - Enable internal pullups:
|
||||
* i2c_dev_t sensor = {
|
||||
* .port = I2C_NUM_0,
|
||||
* .addr = 0x48,
|
||||
* .cfg = {
|
||||
* .sda_io_num = GPIO_NUM_21,
|
||||
* .scl_io_num = GPIO_NUM_22,
|
||||
* .sda_pullup_en = true, // Enable internal pullups
|
||||
* .scl_pullup_en = true, // Enable internal pullups
|
||||
* .master.clk_speed = 400000
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
#ifndef __I2CDEV_H__
|
||||
#define __I2CDEV_H__
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/i2c.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_idf_lib_helpers.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
// Define missing types for older ESP-IDF versions
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0)
|
||||
typedef enum
|
||||
{
|
||||
I2C_ADDR_BIT_LEN_7 = 0, /*!< I2C 7bit address for slave mode */
|
||||
I2C_ADDR_BIT_LEN_10, /*!< I2C 10bit address for slave mode */
|
||||
} i2c_addr_bit_len_t;
|
||||
#endif
|
||||
|
||||
// Definition for I2CDEV_MAX_STRETCH_TIME
|
||||
#if HELPER_TARGET_IS_ESP8266
|
||||
#define I2CDEV_MAX_STRETCH_TIME 0xffffffff
|
||||
#else
|
||||
#include <soc/i2c_reg.h> // For I2C_TIME_OUT_VALUE_V, etc.
|
||||
#if defined(I2C_TIME_OUT_VALUE_V)
|
||||
#define I2CDEV_MAX_STRETCH_TIME I2C_TIME_OUT_VALUE_V
|
||||
#elif defined(I2C_TIME_OUT_REG_V)
|
||||
#define I2CDEV_MAX_STRETCH_TIME I2C_TIME_OUT_REG_V
|
||||
#else
|
||||
#define I2CDEV_MAX_STRETCH_TIME 0x00ffffff
|
||||
#endif
|
||||
#endif /* HELPER_TARGET_IS_ESP8266 */
|
||||
|
||||
#ifndef CONFIG_I2CDEV_TIMEOUT
|
||||
#define CONFIG_I2CDEV_TIMEOUT 1000 // Default 1 second timeout
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_I2CDEV_NOLOCK
|
||||
#define CONFIG_I2CDEV_NOLOCK 0 // Enable locking by default
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_I2CDEV_MAX_DEVICES_PER_PORT
|
||||
#define CONFIG_I2CDEV_MAX_DEVICES_PER_PORT 8 // Maximum devices per I2C port
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_I2CDEV_DEFAULT_SDA_PIN
|
||||
#define CONFIG_I2CDEV_DEFAULT_SDA_PIN 21 // Default SDA pin
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_I2CDEV_DEFAULT_SCL_PIN
|
||||
#define CONFIG_I2CDEV_DEFAULT_SCL_PIN 22 // Default SCL pin
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_FREERTOS_HZ
|
||||
#define CONFIG_FREERTOS_HZ 100 // Default value in most ESP-IDF configs
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_LOG_MAXIMUM_LEVEL
|
||||
#define CONFIG_LOG_MAXIMUM_LEVEL 3 // INFO level as default
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief I2C transaction type for legacy probe
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
I2C_DEV_WRITE = 0, /**< Write operation for probe */
|
||||
I2C_DEV_READ /**< Read operation for probe */
|
||||
} i2c_dev_type_t;
|
||||
|
||||
/**
|
||||
* I2C device descriptor
|
||||
*
|
||||
* This structure supports both legacy ESP-IDF I2C driver and modern i2c_master driver.
|
||||
*
|
||||
* @note INITIALIZATION CHECKLIST - Set these fields before calling i2c_dev_create_mutex():
|
||||
*
|
||||
* ┌─── REQUIRED (Set by user) ───────────────────────────────────────────┐
|
||||
* │ - dev->port - I2C port number (e.g., I2C_NUM_0) │
|
||||
* │ - dev->addr - Device I2C address (e.g., 0x48) │
|
||||
* │ - dev->cfg.sda_io_num - SDA pin (-1 = use Kconfig default) │
|
||||
* │ - dev->cfg.scl_io_num - SCL pin (-1 = use Kconfig default) │
|
||||
* │ - dev->cfg.master.clk_speed - Clock speed in Hz (e.g., 400000) │
|
||||
* └──────────────────────────────────────────────────────────────────────┘
|
||||
*
|
||||
* ┌─── OPTIONAL (Set by user if needed) ─────────────────────────────────┐
|
||||
* │ - dev->addr_bit_len - Address format (defaults to 7-bit) - NEW │
|
||||
* │ - dev->cfg.sda_pullup_en - Enable internal SDA pullup │
|
||||
* │ - dev->cfg.scl_pullup_en - Enable internal SCL pullup │
|
||||
* │ - dev->timeout_ticks - Legacy driver timeout (legacy only) │
|
||||
* └──────────────────────────────────────────────────────────────────────┘
|
||||
*
|
||||
* ┌─── AUTO-POPULATED (library fills these) ─────────────────────────────┐
|
||||
* │ - dev->mutex - Device mutex handle │
|
||||
* │ - dev->dev_handle - I2C device handle (modern driver) - NEW │
|
||||
* │ - dev->sda_pin - Actual SDA pin used by bus │
|
||||
* │ - dev->scl_pin - Actual SCL pin used by bus │
|
||||
* └──────────────────────────────────────────────────────────────────────┘
|
||||
*
|
||||
* @note BACKWARD COMPATIBILITY DESIGN:
|
||||
* The custom 'cfg' structure mimics ESP-IDF's deprecated i2c_config_t layout
|
||||
* to maintain zero-change compatibility with existing device drivers.
|
||||
* ESP-IDF ≥5.2 deprecated i2c_config_t and split it into separate bus/device
|
||||
* configs, but this library preserves the familiar field paths like:
|
||||
* dev->cfg.sda_io_num, dev->cfg.scl_io_num, dev->cfg.master.clk_speed
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
// ═══ Core Device Identity (REQUIRED) ═══
|
||||
i2c_port_t port; //!< I2C port number (e.g., I2C_NUM_0)
|
||||
uint16_t addr; //!< Device I2C address (e.g., 0x48 for 7-bit)
|
||||
i2c_addr_bit_len_t addr_bit_len; //!< Address format: I2C_ADDR_BIT_LEN_7 (default) or I2C_ADDR_BIT_LEN_10
|
||||
|
||||
// ═══ Library Internal State (AUTO-POPULATED) ═══
|
||||
SemaphoreHandle_t mutex; //!< Device mutex - Created by i2c_dev_create_mutex()
|
||||
void *dev_handle; //!< Device handle - Modern driver only, created lazily (when actual I2C operation is performed)
|
||||
int sda_pin; //!< Actual SDA pin used - Populated after port setup
|
||||
int scl_pin; //!< Actual SCL pin used - Populated after port setup
|
||||
|
||||
// ═══ Legacy Driver Compatibility ═══
|
||||
uint32_t timeout_ticks; //!< Clock stretching timeout - Legacy driver only
|
||||
|
||||
// ═══ User Configuration (REQUIRED) ═══
|
||||
// Configuration structure with i2c_config_t compatible field layout.
|
||||
struct
|
||||
{
|
||||
gpio_num_t sda_io_num; //!< Desired SDA pin (-1 = use Kconfig default)
|
||||
gpio_num_t scl_io_num; //!< Desired SCL pin (-1 = use Kconfig default)
|
||||
uint8_t sda_pullup_en; //!< Enable internal SDA pullup (optional)
|
||||
uint8_t scl_pullup_en; //!< Enable internal SCL pullup (optional)
|
||||
uint32_t clk_flags; //!< Bitwise of ``I2C_SCLK_SRC_FLAG_**FOR_DFS**`` for clk source choice
|
||||
struct
|
||||
{
|
||||
uint32_t clk_speed; //!< Clock speed in Hz
|
||||
} master; //!< Master-specific config (mimics old i2c_config_t.master)
|
||||
} cfg; //!< Configuration set by device drivers (i2c_config_t compatible layout)
|
||||
} i2c_dev_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize I2C subsystem (port mutexes and internal states)
|
||||
*
|
||||
* @note This should be called once at the beginning of your application
|
||||
* before any I2C devices are initialized.
|
||||
*
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t i2cdev_init(void);
|
||||
|
||||
/**
|
||||
* @brief Release I2C subsystem (deletes all devices, buses, and mutexes)
|
||||
*
|
||||
* @note Call this when no more I2C operations will be performed
|
||||
* to clean up resources.
|
||||
*
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t i2cdev_done(void);
|
||||
|
||||
/**
|
||||
* @brief Create mutex for device descriptor and register device
|
||||
*
|
||||
* @note IMPORTANT: Before calling this function, you must properly initialize the i2c_dev_t
|
||||
* structure with device address, port, and pin settings. For ESP-IDF legacy driver,
|
||||
* set pins in dev->cfg.sda_io_num and dev->cfg.scl_io_num. For newer ESP-IDF version,
|
||||
* either method is compatible. See the structure documentation for details.
|
||||
*
|
||||
* @param dev Pointer to device descriptor
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t i2c_dev_create_mutex(i2c_dev_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Delete mutex for device descriptor and perform device cleanup
|
||||
*
|
||||
* @note This function performs cleanup tasks including removing the device from the
|
||||
* I2C bus, deregistering it, and deleting its mutex.
|
||||
*
|
||||
* @param dev Pointer to device descriptor
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Take device mutex
|
||||
*
|
||||
* @param dev Pointer to device descriptor
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t i2c_dev_take_mutex(i2c_dev_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Give device mutex
|
||||
*
|
||||
* @param dev Pointer to device descriptor
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t i2c_dev_give_mutex(i2c_dev_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Check the availability of a device on the I2C bus (New Driver) - legacy's i2c_dev_probe function equivalent.
|
||||
*
|
||||
* This function attempts to communicate with the I2C device to see if it ACKs.
|
||||
* It is non-intrusive; if the device is found, any temporary setup for
|
||||
* the check is torn down. Uses the new I2C driver logic.
|
||||
*
|
||||
* @param dev Pointer to the device descriptor. Pins and address must be configured.
|
||||
* @return `ESP_OK` if the device ACKs (is present), an error code otherwise.
|
||||
*/
|
||||
esp_err_t i2c_dev_check_present(const i2c_dev_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Check the availability of a device on the I2C bus (Legacy Driver).
|
||||
*
|
||||
* Issue an operation of `operation_type` to the I2C device then stops.
|
||||
* Primarily for use with the legacy i2cdev_legacy.c implementation.
|
||||
*
|
||||
* @param dev Device descriptor.
|
||||
* @param operation_type Operation type (I2C_DEV_WRITE or I2C_DEV_READ).
|
||||
* @return `ESP_OK` if device is available for the specified operation type.
|
||||
*/
|
||||
esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type);
|
||||
|
||||
/**
|
||||
* @brief Read from device
|
||||
*
|
||||
* @param dev Pointer to device descriptor
|
||||
* @param[in] out_data Data to write before reading (can be NULL if out_size is 0)
|
||||
* @param out_size Size of data to write
|
||||
* @param[out] in_data Buffer to store data read
|
||||
* @param in_size Number of bytes to read
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* @brief Write to device
|
||||
*
|
||||
* @param dev Pointer to device descriptor
|
||||
* @param[in] out_reg Register address to write to (can be NULL if out_reg_size is 0)
|
||||
* @param out_reg_size Size of register address
|
||||
* @param[in] out_data Data to write (can be NULL if out_size is 0)
|
||||
* @param out_size Size of data to write
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* @brief Read from device register (8-bit register address)
|
||||
*
|
||||
* @param dev Pointer to device descriptor
|
||||
* @param reg Command to write before reading
|
||||
* @param[out] data Buffer to store data
|
||||
* @param size Number of bytes to read
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, void *data, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Write to device register (8-bit register address)
|
||||
*
|
||||
* @param dev Pointer to device descriptor
|
||||
* @param reg Command to write before writing data
|
||||
* @param data Buffer with data to write
|
||||
* @param size Number of bytes to write
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *data, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Take device mutex with error checking
|
||||
*/
|
||||
#define I2C_DEV_TAKE_MUTEX(dev) \
|
||||
do \
|
||||
{ \
|
||||
esp_err_t __ = i2c_dev_take_mutex(dev); \
|
||||
if (__ != ESP_OK) \
|
||||
return __; \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
/**
|
||||
* @brief Give device mutex with error checking
|
||||
*/
|
||||
#define I2C_DEV_GIVE_MUTEX(dev) \
|
||||
do \
|
||||
{ \
|
||||
esp_err_t __ = i2c_dev_give_mutex(dev); \
|
||||
if (__ != ESP_OK) \
|
||||
return __; \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
/**
|
||||
* @brief Execute operation, assuming mutex is held. Gives mutex ONLY on error.
|
||||
*/
|
||||
#define I2C_DEV_CHECK(dev, X) \
|
||||
do \
|
||||
{ \
|
||||
esp_err_t ___ = X; /* Execute operation */ \
|
||||
if (___ != ESP_OK) \
|
||||
{ \
|
||||
/* Give mutex ONLY if error occurred */ \
|
||||
i2c_dev_give_mutex(dev); \
|
||||
return ___; \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
/**
|
||||
* @brief Execute operation, assuming mutex is held. Gives mutex ONLY on error, logs error.
|
||||
*/
|
||||
#define I2C_DEV_CHECK_LOGE(dev, X, msg, ...) \
|
||||
do \
|
||||
{ \
|
||||
esp_err_t ___ = X; /* Execute operation */ \
|
||||
if (___ != ESP_OK) \
|
||||
{ \
|
||||
/* Give mutex ONLY if error occurred */ \
|
||||
i2c_dev_give_mutex(dev); \
|
||||
ESP_LOGE(TAG, msg, ##__VA_ARGS__); \
|
||||
return ___; \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/**@}*/
|
||||
|
||||
#endif /* __I2CDEV_H__ */
|
||||
793
managed_components/esp-idf-lib__i2cdev/i2cdev_legacy.c
Normal file
793
managed_components/esp-idf-lib__i2cdev/i2cdev_legacy.c
Normal file
@@ -0,0 +1,793 @@
|
||||
/*
|
||||
* 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 "esp_idf_lib_helpers.h" // For HELPER_TARGET_IS_ESP32 etc.
|
||||
#include "i2cdev.h" // Common header
|
||||
#include <driver/i2c.h> // Legacy I2C driver
|
||||
#include <esp_log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <inttypes.h>
|
||||
#include <sdkconfig.h>
|
||||
#if !HELPER_TARGET_IS_ESP8266
|
||||
#include <soc/clk_tree_defs.h> // For APB_CLK_FREQ
|
||||
#endif
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "i2cdev_legacy";
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SemaphoreHandle_t lock;
|
||||
i2c_config_t config; // Use legacy config struct
|
||||
bool installed;
|
||||
uint32_t ref_count;
|
||||
i2c_dev_t *devices[CONFIG_I2CDEV_MAX_DEVICES_PER_PORT]; // Track devices registered on this port
|
||||
} i2c_port_state_t;
|
||||
|
||||
static i2c_port_state_t states[I2C_NUM_MAX] = { 0 };
|
||||
|
||||
#if CONFIG_I2CDEV_NOLOCK
|
||||
#define SEMAPHORE_TAKE(port)
|
||||
#else
|
||||
#define SEMAPHORE_TAKE(port) \
|
||||
do \
|
||||
{ \
|
||||
if (!xSemaphoreTake(states[port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT))) \
|
||||
{ \
|
||||
ESP_LOGE(TAG, "Could not take port mutex %d", port); \
|
||||
return ESP_ERR_TIMEOUT; \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
#endif
|
||||
|
||||
#if CONFIG_I2CDEV_NOLOCK
|
||||
#define SEMAPHORE_GIVE(port)
|
||||
#else
|
||||
#define SEMAPHORE_GIVE(port) \
|
||||
do \
|
||||
{ \
|
||||
if (!xSemaphoreGive(states[port].lock)) \
|
||||
{ \
|
||||
ESP_LOGE(TAG, "Could not give port mutex %d", port); \
|
||||
return ESP_FAIL; \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Register an I2C device for tracking and resource management
|
||||
*
|
||||
* This function adds a device to the port's tracking array, which helps with:
|
||||
* - Monitoring which devices are active on each port
|
||||
* - Proper cleanup when the system shuts down
|
||||
* - Diagnostics and debugging
|
||||
*
|
||||
* Each port can track up to CONFIG_I2CDEV_MAX_DEVICES_PER_PORT devices.
|
||||
*
|
||||
* @param dev Device descriptor to register
|
||||
* @return ESP_OK if registration succeeded, or an error code
|
||||
*/
|
||||
static esp_err_t register_device(i2c_dev_t *dev)
|
||||
{
|
||||
if (!dev || dev->port >= I2C_NUM_MAX)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
if (!states[dev->port].lock)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
|
||||
esp_err_t ret = ESP_ERR_NO_MEM;
|
||||
|
||||
// Take the mutex directly instead of using the macro
|
||||
if (xSemaphoreTake(states[dev->port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE)
|
||||
{
|
||||
ESP_LOGE(TAG, "[0x%02x at %d] Could not take port mutex for registration", dev->addr, dev->port);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
// Search for an empty slot in the device tracking array
|
||||
for (int i = 0; i < CONFIG_I2CDEV_MAX_DEVICES_PER_PORT; i++)
|
||||
{
|
||||
if (states[dev->port].devices[i] == NULL)
|
||||
{
|
||||
// Found empty slot - register the device here
|
||||
states[dev->port].devices[i] = dev;
|
||||
ESP_LOGV(TAG, "[0x%02x at %d] Registered device in slot %d", dev->addr, dev->port, i);
|
||||
ret = ESP_OK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// All slots full - this will still allow communication but prevents automatic cleanup
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "[0x%02x at %d] No free slots to register device", dev->addr, dev->port);
|
||||
}
|
||||
|
||||
// Release the mutex
|
||||
if (!xSemaphoreGive(states[dev->port].lock))
|
||||
{
|
||||
ESP_LOGE(TAG, "[0x%02x at %d] Could not give port mutex after registration", dev->addr, dev->port);
|
||||
// If can't give the mutex, that's a serious error that overrides the registration result
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deregister a device and update reference counting
|
||||
*
|
||||
* This function:
|
||||
* 1. Removes the device from the port's tracking array
|
||||
* 2. Decrements the port's reference count
|
||||
* 3. Cleans up the I2C driver if this was the last device on the port
|
||||
*
|
||||
* This is called during device cleanup to ensure proper resource management.
|
||||
*
|
||||
* @param dev Device descriptor to deregister
|
||||
*/
|
||||
static void deregister_device(i2c_dev_t *dev)
|
||||
{
|
||||
if (!dev || dev->port >= I2C_NUM_MAX)
|
||||
return;
|
||||
|
||||
// Don't use macros that return values since this is a void function
|
||||
if (states[dev->port].lock)
|
||||
{
|
||||
if (xSemaphoreTake(states[dev->port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE)
|
||||
{
|
||||
ESP_LOGE(TAG, "[0x%02x at %d] Could not take port mutex for deregistration", dev->addr, dev->port);
|
||||
return; // Cannot proceed without lock
|
||||
}
|
||||
|
||||
// Find the device in the tracking array
|
||||
for (int i = 0; i < CONFIG_I2CDEV_MAX_DEVICES_PER_PORT; i++)
|
||||
{
|
||||
if (states[dev->port].devices[i] == dev)
|
||||
{
|
||||
// Clear this slot
|
||||
states[dev->port].devices[i] = NULL;
|
||||
ESP_LOGV(TAG, "[0x%02x at %d] Deregistered device from slot %d", dev->addr, dev->port, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Manage reference counting for this port
|
||||
if (states[dev->port].ref_count > 0)
|
||||
{
|
||||
states[dev->port].ref_count--;
|
||||
ESP_LOGD(TAG, "[Port %d] Decremented ref_count to %" PRIu32, dev->port, states[dev->port].ref_count);
|
||||
|
||||
// If this was the last device using this port, clean up the driver
|
||||
if (states[dev->port].ref_count == 0 && states[dev->port].installed)
|
||||
{
|
||||
ESP_LOGI(TAG, "[Port %d] Last device removed, uninstalling driver", dev->port);
|
||||
i2c_driver_delete(dev->port);
|
||||
states[dev->port].installed = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Release the mutex
|
||||
if (!xSemaphoreGive(states[dev->port].lock))
|
||||
{
|
||||
ESP_LOGE(TAG, "[Port %d] Could not give port mutex after deregistration", dev->port);
|
||||
// Can't do much about this error except log it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t i2cdev_init()
|
||||
{
|
||||
memset(states, 0, sizeof(states));
|
||||
|
||||
#if !CONFIG_I2CDEV_NOLOCK
|
||||
for (int i = 0; i < I2C_NUM_MAX; i++)
|
||||
{
|
||||
states[i].lock = xSemaphoreCreateMutex();
|
||||
if (!states[i].lock)
|
||||
{
|
||||
ESP_LOGE(TAG, "Could not create port mutex %d", i);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t i2cdev_done()
|
||||
{
|
||||
ESP_LOGV(TAG, "Cleaning up I2C subsystem (legacy)...");
|
||||
for (int i = 0; i < I2C_NUM_MAX; i++)
|
||||
{
|
||||
if (!states[i].lock)
|
||||
continue;
|
||||
|
||||
if (states[i].installed)
|
||||
{
|
||||
SEMAPHORE_TAKE(i);
|
||||
|
||||
// First, clean up any devices still registered on this port
|
||||
for (int j = 0; j < CONFIG_I2CDEV_MAX_DEVICES_PER_PORT; j++)
|
||||
{
|
||||
if (states[i].devices[j] != NULL)
|
||||
{
|
||||
i2c_dev_t *dev = states[i].devices[j];
|
||||
ESP_LOGW(TAG, "[Port %d] Device 0x%02x still registered during cleanup", i, dev->addr);
|
||||
states[i].devices[j] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
i2c_driver_delete(i);
|
||||
states[i].installed = false;
|
||||
states[i].ref_count = 0;
|
||||
|
||||
SEMAPHORE_GIVE(i);
|
||||
}
|
||||
#if !CONFIG_I2CDEV_NOLOCK
|
||||
vSemaphoreDelete(states[i].lock);
|
||||
#endif
|
||||
states[i].lock = NULL;
|
||||
}
|
||||
ESP_LOGV(TAG, "I2C subsystem cleanup finished (legacy).");
|
||||
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);
|
||||
|
||||
// Initialize device pins to -1 to ensure consistent pattern with new driver
|
||||
if (dev->sda_pin == 0 && dev->scl_pin == 0)
|
||||
{
|
||||
dev->sda_pin = -1;
|
||||
dev->scl_pin = -1;
|
||||
ESP_LOGD(TAG, "[0x%02x at %d] Initialized pins to -1", dev->addr, dev->port);
|
||||
}
|
||||
|
||||
if (dev->mutex)
|
||||
{
|
||||
ESP_LOGW(TAG, "[0x%02x at %d] Device mutex already exists", dev->addr, dev->port);
|
||||
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_FAIL;
|
||||
}
|
||||
|
||||
// Register device for tracking
|
||||
esp_err_t reg_res = register_device(dev);
|
||||
if (reg_res != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "[0x%02x at %d] Could not register device: %s", dev->addr, dev->port, esp_err_to_name(reg_res));
|
||||
// Continue anyway since this is not critical
|
||||
}
|
||||
#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", dev->addr, dev->port);
|
||||
|
||||
// Deregister and update ref counts
|
||||
deregister_device(dev);
|
||||
|
||||
// Delete mutex if exists
|
||||
if (dev->mutex)
|
||||
{
|
||||
vSemaphoreDelete(dev->mutex);
|
||||
dev->mutex = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGV(TAG, "[0x%02x at %d] Device mutex was NULL", 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] Taking mutex", dev->addr, dev->port);
|
||||
|
||||
if (!dev->mutex)
|
||||
{
|
||||
ESP_LOGE(TAG, "[0x%02x at %d] Attempt to take NULL mutex!", dev->addr, dev->port);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!xSemaphoreTake(dev->mutex, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)))
|
||||
{
|
||||
ESP_LOGE(TAG, "[0x%02x at %d] Could not take device mutex (timeout %d ms)", dev->addr, dev->port, CONFIG_I2CDEV_TIMEOUT);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
#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 mutex", dev->addr, dev->port);
|
||||
|
||||
if (!dev->mutex)
|
||||
{
|
||||
ESP_LOGE(TAG, "[0x%02x at %d] Attempt to give NULL mutex!", dev->addr, dev->port);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!xSemaphoreGive(dev->mutex))
|
||||
{
|
||||
ESP_LOGE(TAG, "[0x%02x at %d] Could not give device mutex", dev->addr, dev->port);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
inline static bool cfg_equal(const i2c_config_t *a, const i2c_config_t *b)
|
||||
{
|
||||
bool clock_equal;
|
||||
#ifdef CONFIG_IDF_TARGET_ESP8266
|
||||
clock_equal = (a->clk_stretch_tick == b->clk_stretch_tick);
|
||||
#else
|
||||
clock_equal = (a->master.clk_speed == b->master.clk_speed);
|
||||
#endif
|
||||
|
||||
return a->mode == b->mode && a->scl_io_num == b->scl_io_num && a->sda_io_num == b->sda_io_num && a->scl_pullup_en == b->scl_pullup_en && a->sda_pullup_en == b->sda_pullup_en && clock_equal;
|
||||
// Note: Ignoring clk_flags for comparison as it might not be consistently set by users
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure and initialize the I2C port for a device
|
||||
*
|
||||
* This function is responsible for:
|
||||
* 1. Determining which pins to use (from device or config)
|
||||
* 2. Validating pin configuration
|
||||
* 3. Installing/configuring the I2C driver if not already done
|
||||
* 4. Managing reference counting for the port
|
||||
* 5. Setting up clock stretching timeout
|
||||
*
|
||||
* This is a critical function that must succeed before any I2C operations
|
||||
* can be performed with a device.
|
||||
*
|
||||
* @param dev Device descriptor with configuration info
|
||||
* @return ESP_OK on success, or an error code on failure
|
||||
*/
|
||||
static esp_err_t i2c_setup_port(i2c_dev_t *dev)
|
||||
{
|
||||
if (!dev)
|
||||
{
|
||||
ESP_LOGE(TAG, "Device is NULL");
|
||||
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;
|
||||
}
|
||||
|
||||
// Pin Selection Logic:
|
||||
// Pins are taken from dev->cfg.xyz_io_num.
|
||||
// If -1, Kconfig defaults are used.
|
||||
gpio_num_t sda_pin; // Effective SDA pin to be used
|
||||
gpio_num_t scl_pin; // Effective SCL pin to be used
|
||||
|
||||
if (dev->cfg.sda_io_num == (gpio_num_t) -1)
|
||||
{
|
||||
sda_pin = (gpio_num_t)CONFIG_I2CDEV_DEFAULT_SDA_PIN;
|
||||
}
|
||||
else
|
||||
{
|
||||
sda_pin = dev->cfg.sda_io_num;
|
||||
}
|
||||
|
||||
if (dev->cfg.scl_io_num == (gpio_num_t) -1)
|
||||
{
|
||||
scl_pin = (gpio_num_t)CONFIG_I2CDEV_DEFAULT_SCL_PIN;
|
||||
}
|
||||
else
|
||||
{
|
||||
scl_pin = dev->cfg.scl_io_num;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "[0x%02x at %d] Based on cfg: sda_cfg=%d, scl_cfg=%d. Effective pins for setup: SDA=%d, SCL=%d", dev->addr, dev->port, dev->cfg.sda_io_num, dev->cfg.scl_io_num, sda_pin, scl_pin);
|
||||
|
||||
// Perform basic validation of effective pins
|
||||
if (sda_pin < 0 || scl_pin < 0)
|
||||
{
|
||||
ESP_LOGE(TAG, "[0x%02x at %d] Invalid effective SDA/SCL pins (%d, %d). Check Kconfig defaults if cfg pins were -1.", dev->addr, dev->port, sda_pin, scl_pin);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (sda_pin == scl_pin)
|
||||
{
|
||||
ESP_LOGE(TAG, "[0x%02x at %d] Effective SDA and SCL pins cannot be the same (%d).", dev->addr, dev->port, sda_pin);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Initialize common fields
|
||||
i2c_config_t legacy_cfg = { .mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = sda_pin, // Use locally determined pins
|
||||
.scl_io_num = scl_pin, // Use locally determined pins
|
||||
.sda_pullup_en = dev->cfg.sda_pullup_en ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE,
|
||||
.scl_pullup_en = dev->cfg.scl_pullup_en ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE
|
||||
};
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP8266
|
||||
// ESP8266 uses clk_stretch_tick instead of master.clk_speed
|
||||
// Clock speed will be handled during driver installation
|
||||
uint32_t desired_speed = dev->cfg.master.clk_speed > 0 ? dev->cfg.master.clk_speed : 400000;
|
||||
ESP_LOGD(TAG, "Final I2C config for port %d: SDA=%d, SCL=%d, speed=%lu (ESP8266)", dev->port, legacy_cfg.sda_io_num, legacy_cfg.scl_io_num, (unsigned long)desired_speed);
|
||||
#else
|
||||
// ESP32 family uses master.clk_speed
|
||||
legacy_cfg.master.clk_speed = dev->cfg.master.clk_speed > 0 ? dev->cfg.master.clk_speed : 400000;
|
||||
ESP_LOGD(TAG, "Final I2C config for port %d: SDA=%d, SCL=%d, speed=%lu", dev->port, legacy_cfg.sda_io_num, legacy_cfg.scl_io_num, (unsigned long)legacy_cfg.master.clk_speed);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||
legacy_cfg.clk_flags = 0;
|
||||
#endif
|
||||
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
// Part 1: Driver Installation / Reconfiguration
|
||||
if (!cfg_equal(&legacy_cfg, &states[dev->port].config) || !states[dev->port].installed)
|
||||
{
|
||||
ESP_LOGD(TAG, "[0x%02x at %d] Reconfiguring I2C driver", dev->addr, dev->port);
|
||||
|
||||
if (states[dev->port].installed)
|
||||
{
|
||||
ESP_LOGD(TAG, "Uninstalling previous I2C driver configuration for port %d", dev->port);
|
||||
i2c_driver_delete(dev->port);
|
||||
states[dev->port].installed = false;
|
||||
states[dev->port].ref_count = 0;
|
||||
}
|
||||
|
||||
vTaskDelay(1);
|
||||
|
||||
// Target-specific driver installation/configuration sequence
|
||||
#if HELPER_TARGET_IS_ESP32 || HELPER_TARGET_IS_ESP32S2 || HELPER_TARGET_IS_ESP32S3 || HELPER_TARGET_IS_ESP32C3 || HELPER_TARGET_IS_ESP32C6
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
|
||||
ESP_LOGD(TAG, "Using IDF >= 5.1.0 driver install order for ESP32 family");
|
||||
err = i2c_driver_install(dev->port, legacy_cfg.mode, 0, 0, 0);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
err = i2c_param_config(dev->port, &legacy_cfg);
|
||||
}
|
||||
#else
|
||||
ESP_LOGD(TAG, "Using IDF < 5.1.0 driver install order for ESP32 family");
|
||||
err = i2c_param_config(dev->port, &legacy_cfg);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
err = i2c_driver_install(dev->port, legacy_cfg.mode, 0, 0, 0);
|
||||
}
|
||||
#endif
|
||||
#elif HELPER_TARGET_IS_ESP8266
|
||||
ESP_LOGD(TAG, "Using ESP8266 specific driver installation");
|
||||
legacy_cfg.clk_stretch_tick = dev->timeout_ticks ? dev->timeout_ticks : I2CDEV_MAX_STRETCH_TIME;
|
||||
err = i2c_driver_install(dev->port, legacy_cfg.mode);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
err = i2c_param_config(dev->port, &legacy_cfg);
|
||||
}
|
||||
// ESP8266 note: Clock speed is not directly configurable through i2c_config_t
|
||||
// The desired speed was: %lu Hz", desired_speed
|
||||
#else
|
||||
// If legacy mode is off, and target detection fails, this avoids a compile error.
|
||||
// The legacy driver just won't support any target in this case.
|
||||
ESP_LOGW(TAG, "i2cdev_legacy.c: No specific target (ESP32/ESP32-S2/ESP32-S3/ESP32-C3/ESP32-C6/ESP8266) detected "
|
||||
"for driver installation. Legacy driver might be inactive or misconfigured.");
|
||||
err = ESP_ERR_NOT_SUPPORTED; // Indicate that setup can't proceed.
|
||||
#endif
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to install/configure I2C driver for port %d: %d (%s)", dev->port, err, esp_err_to_name(err));
|
||||
states[dev->port].installed = false; // Ensure state reflects failure
|
||||
return err;
|
||||
}
|
||||
|
||||
memcpy(&states[dev->port].config, &legacy_cfg, sizeof(i2c_config_t));
|
||||
states[dev->port].installed = true;
|
||||
states[dev->port].ref_count++;
|
||||
|
||||
dev->sda_pin = legacy_cfg.sda_io_num;
|
||||
dev->scl_pin = legacy_cfg.scl_io_num;
|
||||
|
||||
ESP_LOGD(TAG, "I2C driver successfully installed/reconfigured on port %d, ref_count=%" PRIu32, dev->port, states[dev->port].ref_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
states[dev->port].ref_count++;
|
||||
ESP_LOGV(TAG, "I2C driver already installed on port %d with matching config, ref_count=%" PRIu32, dev->port, states[dev->port].ref_count);
|
||||
|
||||
dev->sda_pin = states[dev->port].config.sda_io_num;
|
||||
dev->scl_pin = states[dev->port].config.scl_io_num;
|
||||
}
|
||||
|
||||
// Part 2: Timeout Configuration (ESP32 family specific hardware timeout)
|
||||
#if HELPER_TARGET_IS_ESP32 || HELPER_TARGET_IS_ESP32S2 || HELPER_TARGET_IS_ESP32S3 || HELPER_TARGET_IS_ESP32C3 || HELPER_TARGET_IS_ESP32C6
|
||||
int current_timeout_hw;
|
||||
err = i2c_get_timeout(dev->port, ¤t_timeout_hw);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to get HW timeout for port %d: %d (%s)", dev->port, err, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
uint32_t timeout_ticks_val = dev->timeout_ticks ? dev->timeout_ticks : I2CDEV_MAX_STRETCH_TIME;
|
||||
if (timeout_ticks_val != (uint32_t)current_timeout_hw)
|
||||
{
|
||||
ESP_LOGV(TAG, "Port %d: Updating HW timeout from %d to %" PRIu32 " ticks", dev->port, current_timeout_hw, timeout_ticks_val);
|
||||
err = i2c_set_timeout(dev->port, timeout_ticks_val);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to set HW timeout for port %d: %d (%s)", dev->port, err, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
ESP_LOGD(TAG, "HW Timeout: ticks = %" PRIu32 " (%" PRIu32 " usec) on port %d", timeout_ticks_val, timeout_ticks_val / 80, dev->port);
|
||||
}
|
||||
#endif
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type)
|
||||
{
|
||||
if (!dev)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
|
||||
SEMAPHORE_TAKE(dev->port);
|
||||
|
||||
esp_err_t res = i2c_setup_port((i2c_dev_t *)dev);
|
||||
if (res == ESP_OK)
|
||||
{
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, dev->addr << 1 | (operation_type == I2C_DEV_READ ? 1 : 0), true);
|
||||
// Alternative Write-style probe for better device compatibility
|
||||
// many devices don't respond well to blind read probes.
|
||||
// i2c_master_write_byte(cmd, dev->addr << 1 | 0, true); // Force write bit (0)
|
||||
i2c_master_stop(cmd);
|
||||
|
||||
res = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT));
|
||||
|
||||
i2c_cmd_link_delete(cmd);
|
||||
}
|
||||
|
||||
SEMAPHORE_GIVE(dev->port);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
SEMAPHORE_TAKE(dev->port);
|
||||
|
||||
// Use a local status variable to track errors
|
||||
esp_err_t err = i2c_setup_port((i2c_dev_t *)dev);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
// Only create a command handle if setup was successful
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
|
||||
if (out_data && out_size)
|
||||
{
|
||||
// Write phase - typically used to specify a register address
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, dev->addr << 1, true); // Addr + Write bit (0)
|
||||
i2c_master_write(cmd, (void *)out_data, out_size, true);
|
||||
}
|
||||
|
||||
// Read phase - get data from the device
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (dev->addr << 1) | 1, true); // Addr + Read bit (1)
|
||||
i2c_master_read(cmd, in_data, in_size,
|
||||
I2C_MASTER_LAST_NACK); // NACK the last byte to signal end
|
||||
i2c_master_stop(cmd);
|
||||
|
||||
// Execute the command
|
||||
err = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT));
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "i2c_master_cmd_begin failed for read: %d (%s)", err, esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Always delete the command handle
|
||||
i2c_cmd_link_delete(cmd);
|
||||
}
|
||||
|
||||
// Always release the semaphore before returning
|
||||
SEMAPHORE_GIVE(dev->port);
|
||||
return err;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
SEMAPHORE_TAKE(dev->port);
|
||||
|
||||
// Use a local status variable to track errors
|
||||
esp_err_t err = i2c_setup_port((i2c_dev_t *)dev);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
// Only create a command handle if setup was successful
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, dev->addr << 1, true);
|
||||
|
||||
// Write register address/command if provided
|
||||
if (out_reg && out_reg_size)
|
||||
{
|
||||
i2c_master_write(cmd, (void *)out_reg, out_reg_size, true);
|
||||
}
|
||||
|
||||
// Write data if provided
|
||||
if (out_data && out_size)
|
||||
{
|
||||
i2c_master_write(cmd, (void *)out_data, out_size, true);
|
||||
}
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
|
||||
// Execute the command
|
||||
err = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT));
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "i2c_master_cmd_begin failed for write: %d (%s)", err, esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Always delete the command handle
|
||||
i2c_cmd_link_delete(cmd);
|
||||
}
|
||||
|
||||
// Always release the semaphore before returning
|
||||
SEMAPHORE_GIVE(dev->port);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *out_data, size_t out_size)
|
||||
{
|
||||
if (!dev || !out_data || !out_size)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
|
||||
SEMAPHORE_TAKE(dev->port);
|
||||
|
||||
// Use a local status variable to track errors
|
||||
esp_err_t err = i2c_setup_port((i2c_dev_t *)dev);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
// Only create a command handle if setup was successful
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (dev->addr << 1) | I2C_MASTER_WRITE, true); // Addr + Write bit
|
||||
i2c_master_write_byte(cmd, reg, true); // Register address
|
||||
if (out_data && out_size)
|
||||
{
|
||||
i2c_master_write(cmd, (void *)out_data, out_size, true); // Data to write
|
||||
}
|
||||
i2c_master_stop(cmd);
|
||||
|
||||
// Execute the command
|
||||
err = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT));
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "i2c_master_cmd_begin failed for write_reg: %d (%s)", err, esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Always delete the command handle
|
||||
i2c_cmd_link_delete(cmd);
|
||||
}
|
||||
|
||||
// Always release the semaphore before returning
|
||||
SEMAPHORE_GIVE(dev->port);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, void *in_data, size_t in_size)
|
||||
{
|
||||
if (!dev || !in_data || !in_size)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
|
||||
SEMAPHORE_TAKE(dev->port);
|
||||
|
||||
// Use a local status variable to track errors
|
||||
esp_err_t err = i2c_setup_port((i2c_dev_t *)dev);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
// Only create a command handle if setup was successful
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (dev->addr << 1) | I2C_MASTER_WRITE, true);
|
||||
i2c_master_write_byte(cmd, reg, true);
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (dev->addr << 1) | I2C_MASTER_READ, true);
|
||||
i2c_master_read(cmd, in_data, in_size, I2C_MASTER_LAST_NACK);
|
||||
i2c_master_stop(cmd);
|
||||
|
||||
// Execute the command
|
||||
err = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT));
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "i2c_master_cmd_begin failed for read_reg: %d (%s)", err, esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Always delete the command handle
|
||||
i2c_cmd_link_delete(cmd);
|
||||
}
|
||||
|
||||
// Always release the semaphore before returning
|
||||
SEMAPHORE_GIVE(dev->port);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Implementation of i2c_dev_check_present (updated version of i2c_dev_probe) using legacy I2C driver
|
||||
esp_err_t i2c_dev_check_present(const i2c_dev_t *dev)
|
||||
{
|
||||
if (!dev)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
|
||||
ESP_LOGV(TAG, "[0x%02x at %d] Checking device presence (legacy driver)...", dev->addr, dev->port);
|
||||
|
||||
// Use the exact same pattern as i2c_dev_probe with WRITE operation to ensure consistent behavior
|
||||
return i2c_dev_probe(dev, I2C_DEV_WRITE);
|
||||
}
|
||||
30
managed_components/esp-idf-lib__i2cdev/idf_component.yml
Normal file
30
managed_components/esp-idf-lib__i2cdev/idf_component.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
dependencies:
|
||||
esp-idf-lib/esp_idf_lib_helpers:
|
||||
version: '*'
|
||||
description: ESP-IDF I2C master thread-safe utilities
|
||||
discussion: https://github.com/esp-idf-lib/core/discussions
|
||||
documentation: https://esp-idf-lib.github.io/i2cdev/
|
||||
files:
|
||||
exclude:
|
||||
- docs/**/*
|
||||
issues: https://github.com/esp-idf-lib/i2cdev/issues
|
||||
license: MIT
|
||||
maintainers:
|
||||
- Ruslan V. Uss (@UncleRus) <unclerus@gmail.com>
|
||||
repository: git://github.com/esp-idf-lib/i2cdev.git
|
||||
repository_info:
|
||||
commit_sha: abf0ebc8f0f826373e9a9ee07cf96727a49ed87b
|
||||
path: .
|
||||
targets:
|
||||
- esp32
|
||||
- esp32c2
|
||||
- esp32c3
|
||||
- esp32c5
|
||||
- esp32c6
|
||||
- esp32c61
|
||||
- esp32h2
|
||||
- esp32p4
|
||||
- esp32s2
|
||||
- esp32s3
|
||||
url: https://github.com/esp-idf-lib/core
|
||||
version: 2.0.8
|
||||
Reference in New Issue
Block a user