SC-F001 way better logging and parameters. not integrated yet but.

This commit is contained in:
Thaddeus Hughes
2025-12-13 10:57:09 -06:00
commit ac030005c3
505 changed files with 174645 additions and 0 deletions

View File

@@ -0,0 +1 @@
--ignore-dir=build

View 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

View 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

View File

@@ -0,0 +1 @@
a8049b1e609679fb54b2d57b0399dd29c4d1fda09a797edac9926f7810aa5703

View File

@@ -0,0 +1,28 @@
---
name: esp_idf_lib_helpers
description: Common support library for esp-idf-lib
version: 1.3.10
groups:
- common
code_owners:
- trombik
- UncleRus
depends:
- freertos
thread_safe: n/a
targets:
- esp32
- esp8266
- esp32s2
- esp32c3
- esp32s3
- esp32c2
- esp32c6
- esp32h2
- esp32p4
- esp32c5
- esp32c61
license: ISC
copyrights:
- name: trombik
year: 2019

View File

@@ -0,0 +1,7 @@
examples/**/sdkconfig
examples/**/sdkconfig.old
examples/**/build/
examples/**/dependencies.lock
docs/_*/
docs/doxygen.log
*.swp

View File

@@ -0,0 +1,3 @@
[submodule "common"]
path = common
url = https://github.com/esp-idf-lib/common.git

View File

@@ -0,0 +1,12 @@
idf_component_register(
INCLUDE_DIRS .
REQUIRES freertos
)
# 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()

View File

@@ -0,0 +1,13 @@
Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -0,0 +1,30 @@
# esp-idf-lib/esp_idf_lib_helpers
[![Build examples](https://github.com/esp-idf-lib/esp_idf_lib_helpers/actions/workflows//build.yml/badge.svg)](https://github.com/esp-idf-lib/esp_idf_lib_helpers/actions/workflows//build.yml)
[![Build docs](https://github.com/esp-idf-lib/esp_idf_lib_helpers/actions/workflows//build-docs.yml/badge.svg)](https://github.com/esp-idf-lib/esp_idf_lib_helpers/actions/workflows//build-docs.yml)
[![Validation](https://github.com/esp-idf-lib/esp_idf_lib_helpers/actions/workflows//validate-component.yml/badge.svg)](https://github.com/esp-idf-lib/esp_idf_lib_helpers/actions/workflows//validate-component.yml)
Common support library for esp-idf-lib.
* [Documentation](https://esp-idf-lib.github.io/esp_idf_lib_helpers/)
* [Repository](https://github.com/esp-idf-lib/esp_idf_lib_helpers)
* [Issues](https://github.com/esp-idf-lib/esp_idf_lib_helpers/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/esp_idf_lib_helpers)
## Installation
```sh
idf.py add-dependency esp-idf-lib/esp_idf_lib_helpers
```
## 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).

View 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

View File

@@ -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()

View File

@@ -0,0 +1,8 @@
COMPONENT_ADD_INCLUDEDIRS = .
ifdef CONFIG_IDF_TARGET_ESP8266
COMPONENT_DEPENDS = esp8266 freertos
else
COMPONENT_DEPENDS = freertos
endif

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#if !defined(__ESP_IDF_LIB_HELPERS__H__)
#define __ESP_IDF_LIB_HELPERS__H__
/* XXX this header file does not need to include freertos/FreeRTOS.h.
* but without it, ESP8266 RTOS SDK does not include `sdkconfig.h` in correct
* order. as this header depends on sdkconfig.h, sdkconfig.h must be included
* first. however, the SDK includes this header first, then includes
* `sdkconfig.h` when freertos/FreeRTOS.h is not explicitly included. an
* evidence can be found in `build/${COMPONENT}/${COMPONENT}.d` in a failed
* build.
*/
#include <freertos/FreeRTOS.h>
#include <esp_idf_version.h>
#if !defined(ESP_IDF_VERSION) || !defined(ESP_IDF_VERSION_VAL)
#error Unknown ESP-IDF/ESP8266 RTOS SDK version
#endif
/* Minimal supported version for ESP32, ESP32S2 */
#define HELPER_ESP32_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 5)
/* Minimal supported version for ESP8266 */
#define HELPER_ESP8266_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 0)
/* HELPER_TARGET_IS_ESP32
* 1 when the target is esp32
*/
#if defined(CONFIG_IDF_TARGET_ESP32) \
|| defined(CONFIG_IDF_TARGET_ESP32S2) \
|| defined(CONFIG_IDF_TARGET_ESP32S3) \
|| defined(CONFIG_IDF_TARGET_ESP32C2) \
|| defined(CONFIG_IDF_TARGET_ESP32C3) \
|| defined(CONFIG_IDF_TARGET_ESP32C5) \
|| defined(CONFIG_IDF_TARGET_ESP32C6) \
|| defined(CONFIG_IDF_TARGET_ESP32P4) \
|| defined(CONFIG_IDF_TARGET_ESP32C61) \
|| defined(CONFIG_IDF_TARGET_ESP32H2)
#define HELPER_TARGET_IS_ESP32 (1)
/* HELPER_TARGET_IS_ESP8266
* 1 when the target is esp8266
*/
#elif defined(CONFIG_IDF_TARGET_ESP8266)
#define HELPER_TARGET_IS_ESP8266 (1)
#else
#error BUG: cannot determine the target
#endif
#if HELPER_TARGET_IS_ESP32 && ESP_IDF_VERSION < HELPER_ESP32_MIN_VER
#error Unsupported ESP-IDF version. Please update!
#endif
#if HELPER_TARGET_IS_ESP8266 && ESP_IDF_VERSION < HELPER_ESP8266_MIN_VER
#error Unsupported ESP8266 RTOS SDK version. Please update!
#endif
/* HELPER_SPI_HOST_DEFAULT
*
* The default SPI_HOST for spi_host_device_t
*/
#if CONFIG_IDF_TARGET_ESP32
#define HELPER_SPI_HOST_DEFAULT HSPI_HOST
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
#define HELPER_SPI_HOST_DEFAULT SPI2_HOST
#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61
#define HELPER_SPI_HOST_DEFAULT SPI1_HOST
#elif CONFIG_IDF_TARGET_ESP32H2
#define HELPER_SPI_HOST_DEFAULT SPI1_HOST
#elif CONFIG_IDF_TARGET_ESP32P4
#define HELPER_SPI_HOST_DEFAULT SPI1_HOST
#endif
/* show the actual values for debugging */
#if DEBUG
#define VALUE_TO_STRING(x) #x
#define VALUE(x) VALUE_TO_STRING(x)
#define VAR_NAME_VALUE(var) #var "=" VALUE(var)
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32C3))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32H2))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32S2))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP8266))
#pragma message(VAR_NAME_VALUE(ESP_IDF_VERSION_MAJOR))
#endif
#endif

View File

@@ -0,0 +1,27 @@
#if CONFIG_IDF_TARGET_ESP32
#include <esp32/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32C2
#include <esp32c2/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32C3
#include <esp32c3/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32C5
#include <esp32c5/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32C6
#include <esp32c6/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32C61
#include <esp32c61/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32H2
#include <esp32h2/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32H4
#include <esp32h4/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32S2
#include <esp32s2/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32S3
#include <esp32s3/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32P4
#include <esp32p4/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP8266
#include <rom/ets_sys.h>
#else
#error "ets_sys: Unknown target"
#endif

View File

@@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(example_example)

View File

@@ -0,0 +1,5 @@
#V := 1
PROJECT_NAME := example_example
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,16 @@
# Example application for `example` component
## What the example does
The example does nothing but waits in a loop.
## Configuration
No configuration is available.
## Notes
This is an example application of `example`. It is intended as an example
application for new component.
The code under `main` should conform the code style.

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1 @@
COMPONENT_ADD_INCLUDEDIRS = .

View File

@@ -0,0 +1,5 @@
dependencies:
esp-idf-lib/esp_idf_lib_helpers:
version: '*'
description: default
version: 1.0.0

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) YYYY YOUR NAME HERE <user@your.dom.ain>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
#include <esp_idf_lib_helpers.h>
#include "my_local_header.h"
static char *tag = "main";
void app_main()
{
ESP_LOGI(tag, "An example log");
#if HELPER_TARGET_IS_ESP32
ESP_LOGI(tag, "the target is ESP32");
#else
ESP_LOGI(tag, "the target is not ESP32");
#endif
while (1)
{
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) YYYY YOUR NAME HERE <user@your.dom.ain>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#if !defined(__MY_LOCAL_HEADER__H__)
#define __MY_LOCAL_HEADER__H__
#endif

View File

@@ -0,0 +1 @@
# add required non-default option for the example if any

View File

@@ -0,0 +1,29 @@
dependencies: {}
description: Common support library for esp-idf-lib
discussion: https://github.com/esp-idf-lib/core/discussions
documentation: https://esp-idf-lib.github.io/esp_idf_lib_helpers/
files:
exclude:
- docs/**/*
issues: https://github.com/esp-idf-lib/esp_idf_lib_helpers/issues
license: ISC
maintainers:
- Tomoyuki Sakurai (@trombik) <y@trombik.org>
- Ruslan V. Uss (@UncleRus) <unclerus@gmail.com>
repository: git://github.com/esp-idf-lib/esp_idf_lib_helpers.git
repository_info:
commit_sha: 57bbd8f3cda9c5ad390fc3ea5585e0ad80672584
path: .
targets:
- esp32
- esp32c2
- esp32c3
- esp32c5
- esp32c6
- esp32c61
- esp32h2
- esp32p4
- esp32s2
- esp32s3
url: https://github.com/esp-idf-lib/core
version: 1.3.10

View File

@@ -0,0 +1 @@
--ignore-dir=build

View 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

View 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

View File

@@ -0,0 +1 @@
11c08f9e1a7d346b5dd763196dc2567cf2209ae49042402c2c2d296624601c14

View 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

View File

@@ -0,0 +1,7 @@
examples/**/sdkconfig
examples/**/sdkconfig.old
examples/**/build/
examples/**/dependencies.lock
docs/_*/
docs/doxygen.log
*.swp

View File

@@ -0,0 +1,3 @@
[submodule "common"]
path = common
url = https://github.com/esp-idf-lib/common.git

View 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()

View 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

View 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.

View 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.

View File

@@ -0,0 +1,30 @@
# esp-idf-lib/i2cdev
[![Build examples](https://github.com/esp-idf-lib/i2cdev/actions/workflows//build.yml/badge.svg)](https://github.com/esp-idf-lib/i2cdev/actions/workflows//build.yml)
[![Build docs](https://github.com/esp-idf-lib/i2cdev/actions/workflows//build-docs.yml/badge.svg)](https://github.com/esp-idf-lib/i2cdev/actions/workflows//build-docs.yml)
[![Validation](https://github.com/esp-idf-lib/i2cdev/actions/workflows//validate-component.yml/badge.svg)](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).

View 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

View File

@@ -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()

View 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

View File

@@ -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)

View File

@@ -0,0 +1,5 @@
#V := 1
PROJECT_NAME := i2c_scanner
include $(IDF_PATH)/make/project.mk

View File

@@ -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
```

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,7 @@
menu "I2C scanner configuration"
config EXAMPLE_I2C_CLOCK_HZ
int "I2C clock frequency, Hz"
default 100000
rsource "../../../Kconfig.i2c"
endmenu

View File

@@ -0,0 +1 @@
COMPONENT_ADD_INCLUDEDIRS = . include/

View File

@@ -0,0 +1,7 @@
dependencies:
esp-idf-lib/esp_idf_lib_helpers:
version: '*'
esp-idf-lib/i2cdev:
version: '*'
description: default
version: 1.0.0

View File

@@ -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);
}

View 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, &reg, 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, &reg, 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;
}

View 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__ */

View 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, &current_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);
}

View 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

View File

@@ -0,0 +1 @@
--ignore-dir=build

View 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

View 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

View File

@@ -0,0 +1 @@
4bbdbd82828cf1fd5c03fd07e3ea2cb0f36daf16cb3ac7219d1e5decb9ec04ee

View File

@@ -0,0 +1,28 @@
---
name: tca95x5
description: Driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus
version: 1.0.7
groups:
- gpio
code_owners: UncleRus
depends:
- i2cdev
- log
- esp_idf_lib_helpers
thread_safe: yes
targets:
- esp32
- esp8266
- esp32s2
- esp32c3
- esp32s3
- esp32c2
- esp32c6
- esp32h2
- esp32p4
- esp32c5
- esp32c61
license: BSD-3
copyrights:
- name: UncleRus
year: 2019

View File

@@ -0,0 +1,7 @@
examples/**/sdkconfig
examples/**/sdkconfig.old
examples/**/build/
examples/**/dependencies.lock
docs/_*/
docs/doxygen.log
*.swp

View File

@@ -0,0 +1,3 @@
[submodule "common"]
path = common
url = https://github.com/esp-idf-lib/common.git

View File

@@ -0,0 +1,13 @@
idf_component_register(
SRCS tca95x5.c
INCLUDE_DIRS .
REQUIRES i2cdev log esp_idf_lib_helpers
)
# 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()

View 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.

View File

@@ -0,0 +1,26 @@
Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of itscontributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,30 @@
# esp-idf-lib/tca95x5
[![Build examples](https://github.com/esp-idf-lib/tca95x5/actions/workflows//build.yml/badge.svg)](https://github.com/esp-idf-lib/tca95x5/actions/workflows//build.yml)
[![Build docs](https://github.com/esp-idf-lib/tca95x5/actions/workflows//build-docs.yml/badge.svg)](https://github.com/esp-idf-lib/tca95x5/actions/workflows//build-docs.yml)
[![Validation](https://github.com/esp-idf-lib/tca95x5/actions/workflows//validate-component.yml/badge.svg)](https://github.com/esp-idf-lib/tca95x5/actions/workflows//validate-component.yml)
Driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus.
* [Documentation](https://esp-idf-lib.github.io/tca95x5/)
* [Repository](https://github.com/esp-idf-lib/tca95x5)
* [Issues](https://github.com/esp-idf-lib/tca95x5/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/tca95x5)
## Installation
```sh
idf.py add-dependency esp-idf-lib/tca95x5
```
## 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).

View 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

View File

@@ -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()

View File

@@ -0,0 +1,2 @@
COMPONENT_ADD_INCLUDEDIRS = .
COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers

View File

@@ -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(example-tca95x5)

View File

@@ -0,0 +1,6 @@
#V := 1
PROJECT_NAME := example-tca95x5
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,13 @@
## What the example does
* Initializes the driver
* Configure a pin as input
* Registers an interrupt that logs port value of the INT pin
* Blinks, or toggles, P10 output
## Wiring
Connect an LED with an appropriate register to P10.
Connect a switch that toggle its voltage HIGH or LOW to INT GPIO. The INT GPIO
can be set in `menuconfig`. See the defaults in `Kconfig.projbuild`.

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,21 @@
menu "Example configuration"
config EXAMPLE_I2C_ADDR
hex "I2C address of TCA9555"
default 0x20
help
I2C address of TCA9555. The default is `TCA95X5_I2C_ADDR_BASE`, or
0x20. See available options in datasheet.
config EXAMPLE_INT_GPIO
int "INT GPIO Number"
default 14 if IDF_TARGET_ESP8266
default 7 if IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C5 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32C61
default 5 if IDF_TARGET_ESP32H2
default 5 if IDF_TARGET_ESP32P4
default 5 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
help
GPIO number connected to INT pin.
rsource "../../../Kconfig.i2c"
endmenu

View File

@@ -0,0 +1 @@
COMPONENT_ADD_INCLUDEDIRS = . include/

View File

@@ -0,0 +1,9 @@
dependencies:
esp-idf-lib/esp_idf_lib_helpers:
version: '*'
esp-idf-lib/i2cdev:
version: '*'
esp-idf-lib/tca95x5:
version: '*'
description: default
version: 1.0.0

View File

@@ -0,0 +1,80 @@
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <tca95x5.h>
#include <driver/gpio.h>
#include <esp_log.h>
static i2c_dev_t tca9555 = { 0 };
static QueueHandle_t gpio_queue = NULL;
static const char *TAG = "tca95x5-example";
// Interrupt handler
static void IRAM_ATTR intr_handler(void *arg)
{
gpio_num_t gpio = (gpio_num_t)arg;
xQueueSendFromISR(gpio_queue, &gpio, NULL);
}
// Interrupt event receiver
static void gpio_recv_task(void *arg)
{
gpio_num_t gpio;
while (1)
{
if (xQueueReceive(gpio_queue, &gpio, portMAX_DELAY))
{
ESP_LOGI(TAG, "GPIO interrupt on pin %d", gpio);
if (gpio != CONFIG_EXAMPLE_INT_GPIO) continue;
uint16_t val;
esp_err_t res = tca95x5_port_read(&tca9555, &val);
if (res != ESP_OK)
{
ESP_LOGE(TAG, "Error reading TCA9555: %d (%s)", res, esp_err_to_name(res));
continue;
}
ESP_LOGI(TAG, "TCA9555 port value: 0x%04x", val);
}
}
}
void main_task(void *pvParameters)
{
// Init descriptor
ESP_ERROR_CHECK(tca95x5_init_desc(&tca9555, CONFIG_EXAMPLE_I2C_ADDR, 0, CONFIG_EXAMPLE_I2C_MASTER_SDA, CONFIG_EXAMPLE_I2C_MASTER_SCL));
// Setup P00, P01 and P02 as input, others as output
ESP_ERROR_CHECK(tca95x5_port_set_mode(&tca9555, 0x0007)); // 0b0000000000000111
// Create queue
gpio_queue = xQueueCreate(5, sizeof(gpio_num_t));
// Run event receiver
xTaskCreate(gpio_recv_task, "gpio_recv_task", 4096, NULL, 5, NULL);
// Setup GPIO interrupt
gpio_set_direction(CONFIG_EXAMPLE_INT_GPIO, GPIO_MODE_INPUT);
gpio_set_intr_type(CONFIG_EXAMPLE_INT_GPIO, GPIO_INTR_NEGEDGE);
gpio_install_isr_service(0);
gpio_isr_handler_add(CONFIG_EXAMPLE_INT_GPIO, intr_handler, (void *)CONFIG_EXAMPLE_INT_GPIO);
// blink on P10
bool on = true;
while (1)
{
ESP_ERROR_CHECK(tca95x5_set_level(&tca9555, 8, on));
on = !on;
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void app_main()
{
ESP_ERROR_CHECK(i2cdev_init());
xTaskCreate(main_task, "main_task", configMINIMAL_STACK_SIZE * 6, NULL, 5, NULL);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -0,0 +1,32 @@
dependencies:
esp-idf-lib/esp_idf_lib_helpers:
version: '*'
esp-idf-lib/i2cdev:
version: '*'
description: Driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus
discussion: https://github.com/esp-idf-lib/core/discussions
documentation: https://esp-idf-lib.github.io/tca95x5/
files:
exclude:
- docs/**/*
issues: https://github.com/esp-idf-lib/tca95x5/issues
license: BSD-3
maintainers:
- Ruslan V. Uss (@UncleRus) <unclerus@gmail.com>
repository: git://github.com/esp-idf-lib/tca95x5.git
repository_info:
commit_sha: 1334d902c73251c88a70f37c271a335d6a5868e5
path: .
targets:
- esp32
- esp32c2
- esp32c3
- esp32c5
- esp32c6
- esp32c61
- esp32h2
- esp32p4
- esp32s2
- esp32s3
url: https://github.com/esp-idf-lib/core
version: 1.0.7

View File

@@ -0,0 +1,138 @@
/*
* Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of itscontributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file tca95x5.c
*
* ESP-IDF driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus
*
* Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
*
* BSD Licensed as described in the file LICENSE
*/
#include <esp_idf_lib_helpers.h>
#include "tca95x5.h"
#define I2C_FREQ_HZ 400000
#define REG_IN0 0x00
#define REG_OUT0 0x02
#define REG_CONF0 0x06
#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
#define BV(x) (1 << (x))
static esp_err_t read_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t *val)
{
CHECK_ARG(dev && val);
I2C_DEV_TAKE_MUTEX(dev);
I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, val, 2));
I2C_DEV_GIVE_MUTEX(dev);
return ESP_OK;
}
static esp_err_t write_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t val)
{
CHECK_ARG(dev);
I2C_DEV_TAKE_MUTEX(dev);
I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &val, 2));
I2C_DEV_GIVE_MUTEX(dev);
return ESP_OK;
}
///////////////////////////////////////////////////////////////////////////////
esp_err_t tca95x5_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
{
CHECK_ARG(dev && (addr & TCA95X5_I2C_ADDR_BASE));
dev->port = port;
dev->addr = addr;
dev->cfg.sda_io_num = sda_gpio;
dev->cfg.scl_io_num = scl_gpio;
#if HELPER_TARGET_IS_ESP32
dev->cfg.master.clk_speed = I2C_FREQ_HZ;
#endif
return i2c_dev_create_mutex(dev);
}
esp_err_t tca95x5_free_desc(i2c_dev_t *dev)
{
CHECK_ARG(dev);
return i2c_dev_delete_mutex(dev);
}
esp_err_t tca95x5_port_get_mode(i2c_dev_t *dev, uint16_t *mode)
{
return read_reg_16(dev, REG_CONF0, mode);
}
esp_err_t tca95x5_port_set_mode(i2c_dev_t *dev, uint16_t mode)
{
return write_reg_16(dev, REG_CONF0, mode);
}
esp_err_t tca95x5_port_read(i2c_dev_t *dev, uint16_t *val)
{
return read_reg_16(dev, REG_IN0, val);
}
esp_err_t tca95x5_port_write(i2c_dev_t *dev, uint16_t val)
{
return write_reg_16(dev, REG_OUT0, val);
}
esp_err_t tca95x5_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val)
{
uint16_t v;
CHECK(read_reg_16(dev, REG_IN0, &v));
*val = v & BV(pin) ? 1 : 0;
return ESP_OK;
}
esp_err_t tca95x5_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val)
{
uint16_t v;
I2C_DEV_TAKE_MUTEX(dev);
I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_OUT0, &v, 2));
v = (v & ~BV(pin)) | (val ? BV(pin) : 0);
I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_OUT0, &v, 2));
I2C_DEV_GIVE_MUTEX(dev);
return ESP_OK;
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of itscontributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file tca95x5.h
* @defgroup tca95x5 tca95x5
* @{
*
* ESP-IDF driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus
*
* Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
*
* BSD Licensed as described in the file LICENSE
*/
#ifndef __TCA95X5_H__
#define __TCA95X5_H__
#include <stddef.h>
#include <i2cdev.h>
#include <esp_err.h>
#ifdef __cplusplus
extern "C" {
#endif
#define TCA95X5_I2C_ADDR_BASE 0x20
/**
* @brief Initialize device descriptor
*
* Default SCL frequency is 400kHz
*
* @param dev Pointer to I2C device descriptor
* @param addr I2C address (`0b0100<A2><A1><A0>`)
* @param port I2C port number
* @param sda_gpio SDA GPIO
* @param scl_gpio SCL GPIO
* @return `ESP_OK` on success
*/
esp_err_t tca95x5_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio);
/**
* @brief Free device descriptor
* @param dev Pointer to I2C device descriptor
* @return `ESP_OK` on success
*/
esp_err_t tca95x5_free_desc(i2c_dev_t *dev);
/**
* @brief Get GPIO pins mode
*
* 0 - output, 1 - input for each bit in `val`
*
* @param dev Pointer to device descriptor
* @param[out] mode Buffer to store mode, 0 bit for P0.0 .. 15 bit for P1.7
* @return `ESP_OK` on success
*/
esp_err_t tca95x5_port_get_mode(i2c_dev_t *dev, uint16_t *mode);
/**
* @brief Set GPIO pins mode
*
* 0 - output, 1 - input for each bit in `val`
*
* @param dev Pointer to device descriptor
* @param mode Mode, 0 bit for P0.0 .. 15 bit for P1.7
* @return `ESP_OK` on success
*/
esp_err_t tca95x5_port_set_mode(i2c_dev_t *dev, uint16_t mode);
/**
* @brief Read GPIO port value
*
* @param dev Pointer to I2C device descriptor
* @param val 16-bit GPIO port value, 0 bit for P0.0 .. 15 bit for P1.7
* @return `ESP_OK` on success
*/
esp_err_t tca95x5_port_read(i2c_dev_t *dev, uint16_t *val);
/**
* @brief Write value to GPIO port
*
* @param dev Pointer to I2C device descriptor
* @param val GPIO port value, 0 bit for P0.0 .. 15 bit for P1.7
* @return ESP_OK on success
*/
esp_err_t tca95x5_port_write(i2c_dev_t *dev, uint16_t val);
/**
* @brief Read GPIO pin level
*
* @param dev Pointer to device descriptor
* @param pin Pin number, 0 for P0.0 .. 15 for P1.7
* @param[out] val `true` if pin currently in high state
* @return `ESP_OK` on success
*/
esp_err_t tca95x5_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val);
/**
* @brief Set GPIO pin level
*
* Pin must be set up as output
*
* @param dev Pointer to device descriptor
* @param pin Pin number, 0 for P0.0 .. 15 for P1.7
* @param[out] val `true` if pin currently in high state
* @return `ESP_OK` on success
*/
esp_err_t tca95x5_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val);
#ifdef __cplusplus
}
#endif
/**@}*/
#endif /* __TCA95X5_H__ */

View File

@@ -0,0 +1,38 @@
[bumpversion]
current_version = 1.20.3
commit = True
tag = True
[bumpversion:file:README.md]
search = littlefs=={current_version}
replace = littlefs=={new_version}
[bumpversion:file:idf_component.yml]
search = "{current_version}"
replace = "{new_version}"
[bumpversion:file:library.json]
search = "{current_version}"
replace = "{new_version}"
[bumpversion:file(number):include/esp_littlefs.h]
search = ESP_LITTLEFS_VERSION_NUMBER "{current_version}"
replace = ESP_LITTLEFS_VERSION_NUMBER "{new_version}"
[bumpversion:file(major):include/esp_littlefs.h]
parse = (?P<major>\d+)
serialize = {major}
search = ESP_LITTLEFS_VERSION_MAJOR {current_version}
replace = ESP_LITTLEFS_VERSION_MAJOR {new_version}
[bumpversion:file(minor):include/esp_littlefs.h]
parse = (?P<minor>\d+)
serialize = {minor}
search = ESP_LITTLEFS_VERSION_MINOR {current_version}
replace = ESP_LITTLEFS_VERSION_MINOR {new_version}
[bumpversion:file(patch):include/esp_littlefs.h]
parse = (?P<patch>\d+)
serialize = {patch}
search = ESP_LITTLEFS_VERSION_PATCH {current_version}
replace = ESP_LITTLEFS_VERSION_PATCH {new_version}

View File

@@ -0,0 +1 @@
1808d73e99168f6f3c26dd31799a248484762b3a320ec4962dec11a145f4277f

View File

@@ -0,0 +1,11 @@
build/
sdkconfig
sdkconfig.old
example/build/
example/sdkconfig
example/sdkconfig.old
example/dependencies.lock
*.DS_Store
*/.cache

View File

@@ -0,0 +1,3 @@
[submodule "main/littlefs"]
path = src/littlefs
url = https://github.com/littlefs-project/littlefs.git

View File

@@ -0,0 +1,47 @@
cmake_minimum_required(VERSION 3.10)
file(GLOB SOURCES src/littlefs/*.c)
list(APPEND SOURCES src/esp_littlefs.c src/littlefs_esp_part.c src/lfs_config.c)
if(IDF_TARGET STREQUAL "esp8266")
# ESP8266 configuration here
else()
# non-ESP8266 configuration
list(APPEND pub_requires sdmmc)
if(CONFIG_LITTLEFS_SDMMC_SUPPORT)
list(APPEND SOURCES src/littlefs_sdmmc.c)
endif()
endif()
list(APPEND pub_requires esp_partition)
list(APPEND priv_requires esptool_py spi_flash vfs)
idf_component_register(
SRCS ${SOURCES}
INCLUDE_DIRS include
PRIV_INCLUDE_DIRS src
REQUIRES ${pub_requires}
PRIV_REQUIRES ${priv_requires}
)
set_source_files_properties(
${SOURCES}
PROPERTIES COMPILE_FLAGS "-DLFS_CONFIG=lfs_config.h"
)
if(CONFIG_LITTLEFS_FCNTL_GET_PATH)
target_compile_definitions(${COMPONENT_LIB} PUBLIC -DF_GETPATH=${CONFIG_LITTLEFS_FCNTL_F_GETPATH_VALUE})
endif()
if(CONFIG_LITTLEFS_MULTIVERSION)
target_compile_definitions(${COMPONENT_LIB} PUBLIC -DLFS_MULTIVERSION)
endif()
if(CONFIG_LITTLEFS_MALLOC_STRATEGY_DISABLE)
target_compile_definitions(${COMPONENT_LIB} PUBLIC -DLFS_NO_MALLOC)
endif()
if(NOT CONFIG_LITTLEFS_ASSERTS)
target_compile_definitions(${COMPONENT_LIB} PUBLIC -DLFS_NO_ASSERT)
endif()

View File

@@ -0,0 +1,273 @@
menu "LittleFS"
config LITTLEFS_SDMMC_SUPPORT
bool "SDMMC support (requires ESP-IDF v5+)"
default n
help
Toggle SD card support
This requires IDF v5+ as older ESP-IDF do not support SD card erase.
config LITTLEFS_MAX_PARTITIONS
int "Maximum Number of Partitions"
default 3
range 1 10
help
Define maximum number of partitions that can be mounted.
config LITTLEFS_PAGE_SIZE
int "LITTLEFS logical page size"
default 256
range 256 1024
help
Logical page size of LITTLEFS partition, in bytes. Must be multiple
of flash page size (which is usually 256 bytes).
Larger page sizes reduce overhead when storing large files, and
improve filesystem performance when reading large files.
Smaller page sizes reduce overhead when storing small (< page size)
files.
config LITTLEFS_OBJ_NAME_LEN
int "Maximum object name length including NULL terminator."
default 64
range 16 1022
help
Includes NULL-terminator. If flashing a prebuilt filesystem image,
rebuild the filesystem image if this value changes.
mklittlefs, the tool that generates the image will automatically be rebuilt.
If downloading a pre-built release of mklittlefs, it was most-likely
built with LFS_NAME_MAX=32 and should not be used.
config LITTLEFS_READ_SIZE
int "Minimum size of a block read."
default 128
help
Minimum size of a block read. All read operations will be a
multiple of this value.
config LITTLEFS_WRITE_SIZE
int "Minimum size of a block write."
default 128
help
Minimum size of a block program. All write operations will be a
multiple of this value.
config LITTLEFS_LOOKAHEAD_SIZE
int "Look ahead size."
default 128
help
Look ahead size. Must be a multiple of 8.
config LITTLEFS_CACHE_SIZE
int "Cache Size"
default 512
help
Size of block caches. Each cache buffers a portion of a block in RAM.
The littlefs needs a read cache, a program cache, and one additional
cache per file. Larger caches can improve performance by storing more
data and reducing the number of disk accesses. Must be a multiple of
the read and program sizes, and a factor of the block size (4096).
config LITTLEFS_BLOCK_CYCLES
int "LittleFS wear-leveling block cycles"
default 512
range -1 1024
help
Number of erase cycles before littlefs evicts metadata logs and moves
the metadata to another block. Suggested values are in the
range 100-1000, with large values having better performance at the cost
of less consistent wear distribution.
Set to -1 to disable block-level wear-leveling.
config LITTLEFS_USE_MTIME
bool "Save file modification time"
default "y"
help
Saves timestamp on modification. Uses an additional 4bytes.
config LITTLEFS_USE_ONLY_HASH
bool "Don't store filepath in the file descriptor"
default "n"
help
Records the filepath only as a 32-bit hash in the file descriptor instead
of the entire filepath. Saves approximately `sizeof(filepath)` bytes
per file descriptor.
If enabled, functionality (like fstat) that requires the file path
from the file descriptor will not work.
In rare cases, may cause unlinking or renaming issues (unlikely) if
there's a hash collision between an open filepath and a filepath
to be modified.
config LITTLEFS_HUMAN_READABLE
bool "Make errno human-readable"
default "n"
help
Converts LittleFS error codes into human readable strings.
May increase binary size depending on logging level.
choice LITTLEFS_MTIME
prompt "mtime attribute options"
depends on LITTLEFS_USE_MTIME
default LITTLEFS_MTIME_USE_SECONDS
help
Save an additional 4-byte attribute. Options listed below.
config LITTLEFS_MTIME_USE_SECONDS
bool "Use Seconds"
help
Saves timestamp on modification.
config LITTLEFS_MTIME_USE_NONCE
bool "Use Nonce"
help
Saves nonce on modification; intended for detecting filechanges
on systems without access to a RTC.
A file who's nonce is the same as it was at a previous time has
high probability of not having been modified.
Upon file modification, the nonce is incremented by one. Upon file
creation, a random nonce is assigned.
There is a very slim chance that a file will have the same nonce if
it is deleted and created again (approx 1 in 4 billion).
endchoice
config LITTLEFS_SPIFFS_COMPAT
bool "Improve SPIFFS drop-in compatability"
default "n"
help
Enabling this feature allows for greater drop-in compatability
when replacing SPIFFS. Since SPIFFS doesn't have folders, and
folders are just considered as part of a file name, enabling this
will automatically create folders as necessary to create a file
instead of throwing an error. Similarly, upon the deletion of the
last file in a folder, the folder will be deleted. It is recommended
to only enable this flag as a stop-gap solution.
config LITTLEFS_FLUSH_FILE_EVERY_WRITE
bool "Flush file to flash after each write operation"
default "n"
help
Enabling this feature extends SPIFFS capability.
In SPIFFS data is written immediately to the flash storage when fflush() function called.
In LittleFS flush() does not write data to the flash, and fsync() call needed after.
With this feature fflush() will write data to the storage.
config LITTLEFS_OPEN_DIR
bool "Support opening directory"
default "n"
depends on !LITTLEFS_USE_ONLY_HASH && LITTLEFS_SPIFFS_COMPAT
help
Support opening directory by following APIs:
int fd = open("my_directory", O_DIRECTORY);
config LITTLEFS_FCNTL_GET_PATH
bool "Support get file or directory path"
default "n"
depends on !LITTLEFS_USE_ONLY_HASH
help
Support getting directory by following APIs:
char buffer[MAXPATHLEN];
int fd = open("my_file", flags);
fcntl(fd, F_GETPATH, buffer);
config LITTLEFS_FCNTL_F_GETPATH_VALUE
int "Value of command F_GETPATH"
default 20
depends on LITTLEFS_FCNTL_GET_PATH
help
ESP-IDF's header file "fcntl.h" doesn't support macro "F_GETPATH",
so we should define this macro here.
config LITTLEFS_MULTIVERSION
bool "Support selecting the LittleFS minor version to write to disk"
default "n"
help
LittleFS 2.6 bumps the on-disk minor version of littlefs from lfs2.0 -> lfs2.1.
This change is backwards-compatible, but after the first write with the new version,
the image on disk will no longer be mountable by older versions of littlefs.
Enabling LITTLEFS_MULTIVERSION allows to select the On-disk version
to use when writing in the form of 16-bit major version
+ 16-bit minor version. This limiting metadata to what is supported by
older minor versions. Note that some features will be lost. Defaults to
to the most recent minor version when zero.
choice LITTLEFS_DISK_VERSION
prompt "LITTLEFS_DISK_VERSION"
depends on LITTLEFS_MULTIVERSION
default LITTLEFS_DISK_VERSION_MOST_RECENT
help
See LITTLEFS_MULTIVERSION for details.
config LITTLEFS_DISK_VERSION_MOST_RECENT
bool "Write the most recent LittleFS version"
config LITTLEFS_DISK_VERSION_2_1
bool "Write LittleFS 2.1"
config LITTLEFS_DISK_VERSION_2_0
bool "Write LittleFS 2.0 (no forward-looking erase-state CRCs)"
endchoice
choice LITTLEFS_MALLOC_STRATEGY
prompt "Buffer allocation strategy"
default LITTLEFS_MALLOC_STRATEGY_DEFAULT
help
Maps lfs_malloc to ESP-IDF capabilities-based memory allocator or
disables dynamic allocation in favour of user-provided static buffers.
config LITTLEFS_MALLOC_STRATEGY_DISABLE
bool "Static buffers only"
help
Disallow dynamic allocation, static buffers must be provided by the calling application.
config LITTLEFS_MALLOC_STRATEGY_DEFAULT
bool "Default heap selection"
help
Uses an automatic allocation strategy. On systems with heap in SPIRAM, if
the allocation size does not exceed SPIRAM_MALLOC_ALWAYSINTERNAL then internal
heap allocation if preferred, otherwise allocation will be attempted from SPIRAM
heap.
config LITTLEFS_MALLOC_STRATEGY_INTERNAL
bool "Internal heap"
help
Uses ESP-IDF heap_caps_malloc to allocate from internal heap.
config LITTLEFS_MALLOC_STRATEGY_SPIRAM
bool "SPIRAM heap"
depends on SPIRAM_USE_MALLOC || SPIRAM_USE_CAPS_ALLOC
help
Uses ESP-IDF heap_caps_malloc to allocate from SPIRAM heap.
endchoice
config LITTLEFS_ASSERTS
bool "Enable asserts"
default "y"
help
Selects whether littlefs performs runtime assert checks.
config LITTLEFS_MMAP_PARTITION
bool "Memory map LITTLEFS partitions"
default "n"
help
Use esp_partition_mmap to map the partitions to memory, which can provide a significant
performance boost in some cases. Make sure the chip you're using has enough available address
space to map the partition (for the ESP32 there is 4MB available).
config LITTLEFS_WDT_RESET
bool "Reset task watchdog during flash operations"
default "n"
help
Enable calling esp_task_wdt_reset() during flash read/write/erase operations
to prevent task watchdog timeouts during long-running filesystem operations.
endmenu

View File

@@ -0,0 +1,7 @@
Copyright 2020 Brian Pugh
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.

View File

@@ -0,0 +1,28 @@
PROJECT_NAME := littlefs
EXTRA_COMPONENT_DIRS := \
$(abspath .) \
$(abspath unit_tester) \
$(IDF_PATH)/tools/unit-test-app/components/
CFLAGS += \
-Werror
include $(IDF_PATH)/make/project.mk
.PHONY: tests
tests-build:
$(MAKE) \
TEST_COMPONENTS='src'
tests:
$(MAKE) \
TEST_COMPONENTS='src' \
flash monitor;
tests-enc:
$(MAKE) \
TEST_COMPONENTS='src' \
encrypted-flash monitor;

View File

@@ -0,0 +1,267 @@
LittleFS for ESP-IDF.
# What is LittleFS?
[LittleFS](https://github.com/ARMmbed/littlefs) is a small fail-safe filesystem
for microcontrollers. We ported LittleFS to esp-idf (specifically, the ESP32)
because SPIFFS was too slow, and FAT was too fragile.
# How to Use
## ESP-IDF
There are two ways to add this component to your project
1. As a ESP-IDF managed component: In your project directory run
```
idf.py add-dependency joltwallet/littlefs==1.20.3
```
2. As a submodule: In your project, add this as a submodule to your `components/` directory.
```
git submodule add https://github.com/joltwallet/esp_littlefs.git
git submodule update --init --recursive
```
The library can be configured via `idf.py menuconfig` under `Component config->LittleFS`.
#### Example
User @wreyford has kindly provided a [demo repo](https://github.com/wreyford/demo_esp_littlefs) showing the use of `esp_littlefs`. A modified copy exists in the `example/` directory.
## PlatformIO
Add to the following line to your project's `platformio.ini` file:
```
lib_deps = https://github.com/joltwallet/esp_littlefs.git
```
Example `platformio.ini` file:
```
[env]
platform = espressif32
framework = espidf
monitor_speed = 115200
[common]
lib_deps = https://github.com/joltwallet/esp_littlefs.git
[env:nodemcu-32s]
board = nodemcu-32s
board_build.filesystem = littlefs
board_build.partitions = min_littlefs.csv
lib_deps = ${common.lib_deps}
```
Example `min_littlefs.cvs` flash partition layout:
```
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1E0000,
app1, app, ota_1, 0x1F0000,0x1E0000,
littlefs, data, littlefs, 0x3D0000,0x20000,
coredump, data, coredump, 0x3F0000,0x10000,
```
[Currently, it is required](https://github.com/platformio/platform-espressif32/issues/479) to modify `CMakeList.txt`. Add the following 2 lines to the your project's `CMakeList.txt`:
```
get_filename_component(configName "${CMAKE_BINARY_DIR}" NAME)
list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_SOURCE_DIR}/.pio/libdeps/${configName}/esp_littlefs")
```
Example `CMakeList.txt`:
```
cmake_minimum_required(VERSION 3.16.0)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
get_filename_component(configName "${CMAKE_BINARY_DIR}" NAME)
list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_SOURCE_DIR}/.pio/libdeps/${configName}/esp_littlefs")
project(my_project_name_here)
```
To configure LittleFS from PlatformIO, run the following command:
```console
$ pio run -t menuconfig
```
An entry `Component config->LittleFS` should be available for configuration. If not, check your `CMakeList.txt` configuration.
# Documentation
See the official [ESP-IDF SPIFFS documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/spiffs.html), basically all the functionality is the
same; just replace `spiffs` with `littlefs` in all function calls.
Also see the comments in `include/esp_littlefs.h`
Slight differences between this configuration and SPIFFS's configuration is in the `esp_vfs_littlefs_conf_t`:
1. `max_files` field doesn't exist since we removed the file limit, thanks to @X-Ryl669
2. `grow_on_mount` will expand an existing filesystem to fill the partition. Defaults to `false`.
* LittleFS filesystems can only grow, they cannot shrink.
### Filesystem Image Creation
At compile time, a filesystem image can be created and flashed to the device by adding the following to your project's `CMakeLists.txt` file:
```
littlefs_create_partition_image(partition_name path_to_folder_containing_files FLASH_IN_PROJECT)
```
If `FLASH_IN_PROJECT` is not specified, the image will still be generated, but you will have to flash it manually using `esptool.py`, `parttool.py`, or a custom build system target.
For example, if your partition table looks like:
```
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
graphics, data, spiffs, , 0xF0000,
```
change it to:
```
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
graphics, data, littlefs, , 0xF0000,
```
and your project has a folder called `device_graphics/`, your call should be:
```
littlefs_create_partition_image(graphics device_graphics FLASH_IN_PROJECT)
```
# Performance
Here are some naive benchmarks to give a vague indicator on performance.
Tests were performed with the following configuration:
* ESP-IDF: v4.4
* Target: ESP32
* CPU Clock: 160MHz
* Flash SPI Freq: 80MHz
* Flash SPI Mode: QIO
In these tests, FAT has a cache size of 4096, and SPIFFS has a cahce size of 256 bytes.
#### Formatting a 512KB partition
```
FAT: 549,494 us
SPIFFS: 10,715,425 us
LittleFS: 110,997 us
```
#### Writing 5 88KB files
```
FAT: 7,124,812 us
SPIFFS*: 99,138,905 us
LittleFS (cache=128): 8,261,920 us
LittleFS (cache=512 default): 6,356,247 us
LittleFS (cache=4096): 6,026,592 us
*Only wrote 374,784 bytes instead of the benchmark 440,000, so this value is extrapolated
```
In the above test, SPIFFS drastically slows down as the filesystem fills up. Below
is the specific breakdown of file write times for SPIFFS. Not sure what happens
on the last file write.
```
SPIFFS:
88000 bytes written in 2190635 us
88000 bytes written in 2190321 us
88000 bytes written in 5133605 us
88000 bytes written in 16570667 us
22784 bytes written in 73053677 us
```
#### Reading 5 88KB files
```
FAT: 5,685,230 us
SPIFFS*: 5,162,289 us
LittleFS (cache=128): 6,284,142 us
LittleFS (cache=512 default): 5,874,931 us
LittleFS (cache=4096): 5,731,385 us
*Only read 374,784 bytes instead of the benchmark 440,000, so this value is extrapolated
```
#### Deleting 5 88KB files
```
FAT: 680,358 us
SPIFFS*: 1,653,500 us
LittleFS (cache=128): 86,090 us
LittleFS (cache=512 default): 53,705 us
LittleFS (cache=4096): 27,709 us
*The 5th file was smaller, did not extrapolate value.
```
# Tips, Tricks, and Gotchas
* LittleFS operates on blocks, and blocks have a size of 4096 bytes on the ESP32.
* A freshly formatted LittleFS will have 2 blocks in use, making it seem like 8KB are in use.
* The esp32 has [flash concurrency constraints](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_flash/spi_flash_concurrency.html#concurrency-constraints-for-flash-on-spi1).
When using UART (either for data transfer or generic logging) at the same time, you *MUST* enable the following option in KConfig:
`menuconfig > Component config > Driver config > UART > UART ISR in IRAM`.
# Running Unit Tests
To flash the unit-tester app and the unit-tests, clone or symbolicly link this
component to `$IDF_PATH/tools/unit-test-app/components/littlefs`. Make sure the
folder name is `littlefs`, not `esp_littlefs`. Then, run the following:
```
cd $IDF_PATH/tools/unit-test-app
idf.py menuconfig # See notes
idf.py -T littlefs -p YOUR_PORT_HERE flash monitor
```
In `menuconfig`:
* Set the partition table to `components/littlefs/partition_table_unit_test_app.csv`
* Double check your crystal frequency `ESP32_XTAL_FREQ_SEL`; my board doesn't work with autodetect.
To test on an encrypted partition, add the `encrypted` flag to the `flash_test` partition
in `partition_table_unit_test_app.csv`. I.e.
```
flash_test, data, spiffs, , 512K, encrypted
```
Also make sure that `CONFIG_SECURE_FLASH_ENC_ENABLED=y` in `menuconfig`.
The unit tester can then be flashed via the command:
```
idf.py -T littlefs -p YOUR_PORT_HERE encrypted-flash monitor
```
# Breaking Changes
* July 22, 2020 - Changed attribute type for file timestamp from `0` to `0x74` ('t' ascii value).
* May 3, 2023 - All logging tags have been changed to a unified `esp_littlefs`.
# Acknowledgement
This code base was heavily modeled after the SPIFFS esp-idf component.

View File

@@ -0,0 +1,24 @@
#
# Component Makefile
#
COMPONENT_SRCDIRS := src src/littlefs
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_PRIV_INCLUDEDIRS := src
COMPONENT_SUBMODULES := src/littlefs
CFLAGS += \
-DLFS_CONFIG=lfs_config.h
ifdef CONFIG_LITTLEFS_FCNTL_GET_PATH
CFLAGS += \
-DF_GETPATH=$(CONFIG_LITTLEFS_FCNTL_F_GETPATH_VALUE)
endif
ifdef CONFIG_LITTLEFS_MULTIVERSION
CFLAGS += \
-DLFS_MULTIVERSION
endif

View File

@@ -0,0 +1,9 @@
# The following 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)
# Add the root of this git repo to the component search path.
set(EXTRA_COMPONENT_DIRS "../")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(demo_esp_littlefs)

View File

@@ -0,0 +1,11 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := demo_esp_littlefs
EXTRA_COMPONENT_DIRS := $(realpath ..)
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,3 @@
This example is based on [wreyford's](https://github.com/wreyford/demo_esp_littlefs) demo project.
Modifications were made so that this example project could be built as a part of CI.

View File

@@ -0,0 +1 @@
Example text to compile into a LittleFS disk image to be flashed to the ESP device.

View File

@@ -0,0 +1,7 @@
idf_component_register(SRCS "demo_esp_littlefs.c"
INCLUDE_DIRS "."
)
# Note: you must have a partition named the first argument (here it's "littlefs")
# in your partition table csv file.
littlefs_create_partition_image(littlefs ../flash_data FLASH_IN_PROJECT)

View File

@@ -0,0 +1,5 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,166 @@
/* Demo ESP LittleFS Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "esp_err.h"
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "esp_idf_version.h"
#include "esp_flash.h"
#include "esp_chip_info.h"
#include "spi_flash_mmap.h"
#include "esp_littlefs.h"
static const char *TAG = "demo_esp_littlefs";
void app_main(void)
{
printf("Demo LittleFs implementation by esp_littlefs!\n");
printf(" https://github.com/joltwallet/esp_littlefs\n");
/* Print chip information */
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
printf("This is %s chip with %d CPU cores, WiFi%s%s, ",
CONFIG_IDF_TARGET,
chip_info.cores,
(chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
(chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
printf("silicon revision %d, ", chip_info.revision);
uint32_t size_flash_chip = 0;
esp_flash_get_size(NULL, &size_flash_chip);
printf("%uMB %s flash\n", (unsigned int)size_flash_chip >> 20,
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
printf("Free heap: %u\n", (unsigned int) esp_get_free_heap_size());
printf("Now we are starting the LittleFs Demo ...\n");
ESP_LOGI(TAG, "Initializing LittleFS");
esp_vfs_littlefs_conf_t conf = {
.base_path = "/littlefs",
.partition_label = "littlefs",
.format_if_mount_failed = true,
.dont_mount = false,
};
// Use settings defined above to initialize and mount LittleFS filesystem.
// Note: esp_vfs_littlefs_register is an all-in-one convenience function.
esp_err_t ret = esp_vfs_littlefs_register(&conf);
if (ret != ESP_OK)
{
if (ret == ESP_FAIL)
{
ESP_LOGE(TAG, "Failed to mount or format filesystem");
}
else if (ret == ESP_ERR_NOT_FOUND)
{
ESP_LOGE(TAG, "Failed to find LittleFS partition");
}
else
{
ESP_LOGE(TAG, "Failed to initialize LittleFS (%s)", esp_err_to_name(ret));
}
return;
}
size_t total = 0, used = 0;
ret = esp_littlefs_info(conf.partition_label, &total, &used);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to get LittleFS partition information (%s)", esp_err_to_name(ret));
}
else
{
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
}
// Use POSIX and C standard library functions to work with files.
// First create a file.
ESP_LOGI(TAG, "Opening file");
FILE *f = fopen("/littlefs/hello.txt", "w");
if (f == NULL)
{
ESP_LOGE(TAG, "Failed to open file for writing");
return;
}
fprintf(f, "LittleFS Rocks!\n");
fclose(f);
ESP_LOGI(TAG, "File written");
// Check if destination file exists before renaming
struct stat st;
if (stat("/littlefs/foo.txt", &st) == 0)
{
// Delete it if it exists
unlink("/littlefs/foo.txt");
}
// Rename original file
ESP_LOGI(TAG, "Renaming file");
if (rename("/littlefs/hello.txt", "/littlefs/foo.txt") != 0)
{
ESP_LOGE(TAG, "Rename failed");
return;
}
// Open renamed file for reading
ESP_LOGI(TAG, "Reading file");
f = fopen("/littlefs/foo.txt", "r");
if (f == NULL)
{
ESP_LOGE(TAG, "Failed to open file for reading");
return;
}
char line[128];
char *pos;
fgets(line, sizeof(line), f);
fclose(f);
// strip newline
pos = strchr(line, '\n');
if (pos)
{
*pos = '\0';
}
ESP_LOGI(TAG, "Read from file: '%s'", line);
ESP_LOGI(TAG, "Reading from flashed filesystem example.txt");
f = fopen("/littlefs/example.txt", "r");
if (f == NULL)
{
ESP_LOGE(TAG, "Failed to open file for reading");
return;
}
fgets(line, sizeof(line), f);
fclose(f);
// strip newline
pos = strchr(line, '\n');
if (pos)
{
*pos = '\0';
}
ESP_LOGI(TAG, "Read from file: '%s'", line);
// All done, unmount partition and disable LittleFS
esp_vfs_littlefs_unregister(conf.partition_label);
ESP_LOGI(TAG, "LittleFS unmounted");
}

View File

@@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
littlefs, data, littlefs, , 0xF0000,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,
6 littlefs, data, littlefs, , 0xF0000,

View File

@@ -0,0 +1,12 @@
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_demo_esp_littlefs.csv"
#
# Serial flasher config
#
CONFIG_ESPTOOLPY_BAUD_921600B=y
CONFIG_ESPTOOLPY_COMPRESSED=y
CONFIG_ESPTOOLPY_MONITOR_BAUD_CONSOLE=y
# BOOTLOADER
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y

View File

@@ -0,0 +1,9 @@
dependencies:
idf: '>=5.0'
description: LittleFS is a small fail-safe filesystem for micro-controllers.
repository: git://github.com/joltwallet/esp_littlefs.git
repository_info:
commit_sha: 8274371dc5912196f66ac3e71dbb6291760cb8b0
path: .
url: https://github.com/joltwallet/esp_littlefs
version: 1.20.3

View File

@@ -0,0 +1 @@
littlefs-python==0.15.0

View File

@@ -0,0 +1,212 @@
#ifndef ESP_LITTLEFS_H__
#define ESP_LITTLEFS_H__
#include "sdkconfig.h"
#include "esp_err.h"
#include "esp_idf_version.h"
#include <stdbool.h>
#include "esp_partition.h"
#ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT
#include <sdmmc_cmd.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define ESP_LITTLEFS_VERSION_NUMBER "1.20.3"
#define ESP_LITTLEFS_VERSION_MAJOR 1
#define ESP_LITTLEFS_VERSION_MINOR 20
#define ESP_LITTLEFS_VERSION_PATCH 3
#ifdef ESP8266
// ESP8266 RTOS SDK default enables VFS DIR support
#define CONFIG_VFS_SUPPORT_DIR 1
#endif
#if CONFIG_VFS_SUPPORT_DIR
#define ESP_LITTLEFS_ENABLE_FTRUNCATE
#endif // CONFIG_VFS_SUPPORT_DIR
/**
*Configuration structure for esp_vfs_littlefs_register.
*/
typedef struct {
const char *base_path; /**< Mounting point. */
const char *partition_label; /**< Label of partition to use. If partition_label, partition, and sdcard are all NULL,
then the first partition with data subtype 'littlefs' will be used. */
const esp_partition_t* partition; /**< partition to use if partition_label is NULL */
#ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT
sdmmc_card_t *sdcard; /**< SD card handle to use if both esp_partition handle & partition label is NULL */
#endif
uint8_t format_if_mount_failed:1; /**< Format the file system if it fails to mount. */
uint8_t read_only : 1; /**< Mount the partition as read-only. */
uint8_t dont_mount:1; /**< Don't attempt to mount.*/
uint8_t grow_on_mount:1; /**< Grow filesystem to match partition size on mount.*/
} esp_vfs_littlefs_conf_t;
/**
* Register and mount (if configured to) littlefs to VFS with given path prefix.
*
* @param conf Pointer to esp_vfs_littlefs_conf_t configuration structure
*
* @return
* - ESP_OK if success
* - ESP_ERR_NO_MEM if objects could not be allocated
* - ESP_ERR_INVALID_STATE if already mounted or partition is encrypted
* - ESP_ERR_NOT_FOUND if partition for littlefs was not found
* - ESP_FAIL if mount or format fails
*/
esp_err_t esp_vfs_littlefs_register(const esp_vfs_littlefs_conf_t * conf);
/**
* Unregister and unmount littlefs from VFS
*
* @param partition_label Label of the partition to unregister.
*
* @return
* - ESP_OK if successful
* - ESP_ERR_INVALID_STATE already unregistered
*/
esp_err_t esp_vfs_littlefs_unregister(const char* partition_label);
#ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT
/**
* Unregister and unmount LittleFS from VFS for SD card
*
* @param sdcard SD card to unregister.
*
* @return
* - ESP_OK if successful
* - ESP_ERR_INVALID_STATE already unregistered
*/
esp_err_t esp_vfs_littlefs_unregister_sdmmc(sdmmc_card_t *sdcard);
#endif
/**
* Unregister and unmount littlefs from VFS
*
* @param partition partition to unregister.
*
* @return
* - ESP_OK if successful
* - ESP_ERR_INVALID_STATE already unregistered
*/
esp_err_t esp_vfs_littlefs_unregister_partition(const esp_partition_t* partition);
/**
* Check if littlefs is mounted
*
* @param partition_label Label of the partition to check.
*
* @return
* - true if mounted
* - false if not mounted
*/
bool esp_littlefs_mounted(const char* partition_label);
/**
* Check if littlefs is mounted
*
* @param partition partition to check.
*
* @return
* - true if mounted
* - false if not mounted
*/
bool esp_littlefs_partition_mounted(const esp_partition_t* partition);
#ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT
/**
* Check if littlefs is mounted
*
* @param sdcard SD card to check.
*
* @return
* - true if mounted
* - false if not mounted
*/
bool esp_littlefs_sdmmc_mounted(sdmmc_card_t *sdcard);
#endif
/**
* Format the littlefs partition
*
* @param partition_label Label of the partition to format.
* @return
* - ESP_OK if successful
* - ESP_FAIL on error
*/
esp_err_t esp_littlefs_format(const char* partition_label);
/**
* Format the littlefs partition
*
* @param partition partition to format.
* @return
* - ESP_OK if successful
* - ESP_FAIL on error
*/
esp_err_t esp_littlefs_format_partition(const esp_partition_t* partition);
#ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT
/**
* Format the LittleFS on a SD card
*
* @param sdcard SD card to format
* @return
* - ESP_OK if successful
* - ESP_FAIL on error
*/
esp_err_t esp_littlefs_format_sdmmc(sdmmc_card_t *sdcard);
#endif
/**
* Get information for littlefs
*
* @param partition_label Optional, label of the partition to get info for.
* @param[out] total_bytes Size of the file system
* @param[out] used_bytes Current used bytes in the file system
*
* @return
* - ESP_OK if success
* - ESP_ERR_INVALID_STATE if not mounted
*/
esp_err_t esp_littlefs_info(const char* partition_label, size_t* total_bytes, size_t* used_bytes);
/**
* Get information for littlefs
*
* @param parition the partition to get info for.
* @param[out] total_bytes Size of the file system
* @param[out] used_bytes Current used bytes in the file system
*
* @return
* - ESP_OK if success
* - ESP_ERR_INVALID_STATE if not mounted
*/
esp_err_t esp_littlefs_partition_info(const esp_partition_t* partition, size_t *total_bytes, size_t *used_bytes);
#ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT
/**
* Get information for littlefs on SD card
*
* @param[in] sdcard the SD card to get info for.
* @param[out] total_bytes Size of the file system
* @param[out] used_bytes Current used bytes in the file system
*
* @return
* - ESP_OK if success
* - ESP_ERR_INVALID_STATE if not mounted
*/
esp_err_t esp_littlefs_sdmmc_info(sdmmc_card_t *sdcard, size_t *total_bytes, size_t *used_bytes);
#endif
#ifdef __cplusplus
} // extern "C"
#endif
#endif

Some files were not shown because too many files have changed in this diff Show More