diff --git a/.github/workflows/validate-component.yml b/.github/workflows/validate-component.yml index c705637..77ca7b7 100644 --- a/.github/workflows/validate-component.yml +++ b/.github/workflows/validate-component.yml @@ -16,10 +16,10 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version: '3.11' - name: Install IDF Component Manager - run: pip install idf-component-manager + run: pip install 'idf-component-manager==1.5.2' - name: Validate and Pack Component run: compote component pack --name Notecard diff --git a/Kconfig b/Kconfig index 41f6100..9e6d23d 100644 --- a/Kconfig +++ b/Kconfig @@ -71,6 +71,14 @@ menu "Notecard Configuration" help Enable internal pull-up resistors for I2C lines. Disable this if you have external pull-up resistors. + + config NOTECARD_I2C_MUTEX + bool "Enable I2C mutex" + default y + help + Enable I2C mutex callback to prevent concurrent access to the I2C bus. + Disable if you intend to define your own I2C bus mutex. + endmenu menu "Default UART Configuration" diff --git a/README.md b/README.md index 0caa154..394baeb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # ESP-IDF Notecard Component An ESP-IDF component for integrating Espressif devices with the Blues [Notecard](https://blues.com/products/notecard/). +This component provides a thread-safe interface to the Notecard using the [note-c](https://github.com/blues/note-c) library. ## Installation @@ -44,6 +45,36 @@ idf.py menuconfig Component config ---> Notecard Configuration ``` +## Thread Safety + +The component automatically provides thread-safe access to the Notecard in multi-threaded FreeRTOS applications. +The underlying [note-c](https://github.com/blues/note-c) library protects the Notecard from concurrent access using internal mutexes, so no additional locking is required for normal use. + +### I2C Bus Sharing + +If you have other I2C peripherals on the same bus as the Notecard, register your I2C mutex with `note-c` using `NoteSetFnI2CMutex()` to minimize the time spent under lock. + +For your convenience, we have provided a default implementation of I2C mutex APIs to coordinate access (example shown below): + +```c +#include "notecard.h" + +// Access your I2C peripherals +notecard_i2c_lock(); +i2c_master_transmit(my_peripheral_handle, data, len, timeout); +notecard_i2c_unlock(); +``` + +This ensures the `note-c` won't attempt I2C communication while you're accessing your other peripherals. + +In order to enable/disable the provided I2C bus mutex (e.g. when using your own mutex), use `menuconfig`: + +``` +Component config ---> Notecard Configuration ---> Default I2C Configuration ---> [ ] Enable I2C mutex +``` + +> Note: The I2C mutex is enabled by default. + ## Examples For examples, see the [examples](examples) directory. diff --git a/examples/basic_usage_i2c/dependencies.lock b/examples/basic_usage_i2c/dependencies.lock index d2df1b0..5e9340d 100644 --- a/examples/basic_usage_i2c/dependencies.lock +++ b/examples/basic_usage_i2c/dependencies.lock @@ -5,6 +5,6 @@ dependencies: version: 5.5.1 direct_dependencies: - idf -manifest_hash: 08ac1ee392ce13ac722569147b417c0484712da30ecb148c8d70f9d19fd69d84 +manifest_hash: cbc3626899740395aaaf5addbe70903d6950040e23cbea45719d4e50ea14d749 target: esp32 version: 2.0.0 diff --git a/examples/basic_usage_uart/dependencies.lock b/examples/basic_usage_uart/dependencies.lock index d2df1b0..5e9340d 100644 --- a/examples/basic_usage_uart/dependencies.lock +++ b/examples/basic_usage_uart/dependencies.lock @@ -5,6 +5,6 @@ dependencies: version: 5.5.1 direct_dependencies: - idf -manifest_hash: 08ac1ee392ce13ac722569147b417c0484712da30ecb148c8d70f9d19fd69d84 +manifest_hash: cbc3626899740395aaaf5addbe70903d6950040e23cbea45719d4e50ea14d749 target: esp32 version: 2.0.0 diff --git a/idf_component.yml b/idf_component.yml index d57973e..32e0834 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -29,8 +29,16 @@ maintainers: - "Support " files: - use_gitignore: true - -examples: - - path: examples/basic_usage_i2c - - path: examples/basic_usage_uart + exclude: + - "build/**/*" + - "dist/**/*" + - ".vscode/**/*" + - ".idea/**/*" + - "*.swp" + - "*.swo" + - ".DS_Store" + - "Thumbs.db" + - "sdkconfig" + - "sdkconfig.old" + - "CMakeCache.txt" + - "CMakeFiles/**/*" diff --git a/include/notecard.h b/include/notecard.h index b76c1b8..83d20ba 100644 --- a/include/notecard.h +++ b/include/notecard.h @@ -159,6 +159,35 @@ bool notecard_is_initialized(void); */ void notecard_set_logging(bool enable); +#ifdef CONFIG_NOTECARD_I2C_MUTEX +/** + * @brief Lock the I2C bus mutex + * + * Use this when accessing other I2C peripherals on the same bus to prevent + * conflicts with Notecard I2C operations. Always pair with notecard_i2c_unlock(). + * + * @note Only available if CONFIG_NOTECARD_I2C_MUTEX is enabled in Kconfig + * + * Example: + * @code + * notecard_i2c_lock(); + * // Access your I2C sensor here + * i2c_master_transmit(my_sensor_handle, data, len, timeout); + * notecard_i2c_unlock(); + * @endcode + */ +void notecard_i2c_lock(void); + +/** + * @brief Unlock the I2C bus mutex + * + * Must be called after notecard_i2c_lock() to release the I2C bus. + * + * @note Only available if CONFIG_NOTECARD_I2C_MUTEX is enabled in Kconfig + */ +void notecard_i2c_unlock(void); +#endif + #ifdef __cplusplus } #endif diff --git a/src/notecard.c b/src/notecard.c index 81fad2a..391eeff 100644 --- a/src/notecard.c +++ b/src/notecard.c @@ -71,6 +71,9 @@ esp_err_t notecard_init(const notecard_config_t *config) notecard_platform_delay, notecard_platform_millis); + // Register mutex hooks for thread safety + notecard_platform_register_mutex_hooks(); + // Set up logging output if (g_logging_enabled) { NoteSetFnDebugOutput(notecard_logging_output); diff --git a/src/notecard_platform.c b/src/notecard_platform.c index 79ab73a..d10723a 100644 --- a/src/notecard_platform.c +++ b/src/notecard_platform.c @@ -3,6 +3,7 @@ #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/semphr.h" #include "driver/i2c_master.h" #include "driver/uart.h" #include @@ -22,6 +23,46 @@ static bool g_i2c_initialized = false; static bool g_uart_initialized = false; static i2c_master_bus_handle_t g_i2c_bus_handle = NULL; +// FreeRTOS mutex handles for thread-safe access +#ifdef CONFIG_NOTECARD_I2C_MUTEX +static SemaphoreHandle_t g_i2c_mutex = NULL; +#endif +static SemaphoreHandle_t g_notecard_mutex = NULL; + +//============================================================================= +// Mutex Platform Implementation +//============================================================================= + +#ifdef CONFIG_NOTECARD_I2C_MUTEX +static void notecard_platform_i2c_lock(void) +{ + if (g_i2c_mutex != NULL) { + for (;xSemaphoreTake(g_i2c_mutex, portMAX_DELAY) != pdTRUE;); + } +} + +static void notecard_platform_i2c_unlock(void) +{ + if (g_i2c_mutex != NULL) { + xSemaphoreGive(g_i2c_mutex); + } +} +#endif + +static void notecard_platform_note_lock(void) +{ + if (g_notecard_mutex != NULL) { + for (;xSemaphoreTake(g_notecard_mutex, portMAX_DELAY) != pdTRUE;); + } +} + +static void notecard_platform_note_unlock(void) +{ + if (g_notecard_mutex != NULL) { + xSemaphoreGive(g_notecard_mutex); + } +} + //============================================================================= // I2C Platform Implementation //============================================================================= @@ -37,6 +78,27 @@ esp_err_t notecard_platform_i2c_init(const notecard_i2c_config_t *config) return ESP_ERR_INVALID_ARG; } + // Create mutexes + if (g_notecard_mutex == NULL) { + g_notecard_mutex = xSemaphoreCreateMutex(); + if (g_notecard_mutex == NULL) { + ESP_LOGE(TAG, "Failed to create Notecard mutex"); + return ESP_ERR_NO_MEM; + } + } + +#ifdef CONFIG_NOTECARD_I2C_MUTEX + if (g_i2c_mutex == NULL) { + g_i2c_mutex = xSemaphoreCreateMutex(); + if (g_i2c_mutex == NULL) { + ESP_LOGE(TAG, "Failed to create I2C mutex"); + vSemaphoreDelete(g_notecard_mutex); + g_notecard_mutex = NULL; + return ESP_ERR_NO_MEM; + } + } +#endif + // Store configuration memcpy(&g_i2c_config, config, sizeof(notecard_i2c_config_t)); @@ -55,6 +117,12 @@ esp_err_t notecard_platform_i2c_init(const notecard_i2c_config_t *config) esp_err_t ret = i2c_new_master_bus(&bus_config, &g_i2c_bus_handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "I2C master bus creation failed: %s", esp_err_to_name(ret)); +#ifdef CONFIG_NOTECARD_I2C_MUTEX + vSemaphoreDelete(g_i2c_mutex); + g_i2c_mutex = NULL; +#endif + vSemaphoreDelete(g_notecard_mutex); + g_notecard_mutex = NULL; return ret; } @@ -79,6 +147,20 @@ esp_err_t notecard_platform_i2c_deinit(void) g_i2c_bus_handle = NULL; g_i2c_initialized = false; + + // Clean up mutexes +#ifdef CONFIG_NOTECARD_I2C_MUTEX + if (g_i2c_mutex != NULL) { + vSemaphoreDelete(g_i2c_mutex); + g_i2c_mutex = NULL; + } +#endif + + if (g_notecard_mutex != NULL) { + vSemaphoreDelete(g_notecard_mutex); + g_notecard_mutex = NULL; + } + ESP_LOGI(TAG, "I2C deinitialized"); return ESP_OK; } @@ -228,6 +310,15 @@ esp_err_t notecard_platform_uart_init(const notecard_uart_config_t *config) return ESP_ERR_INVALID_ARG; } + // Create Notecard mutex + if (g_notecard_mutex == NULL) { + g_notecard_mutex = xSemaphoreCreateMutex(); + if (g_notecard_mutex == NULL) { + ESP_LOGE(TAG, "Failed to create Notecard mutex"); + return ESP_ERR_NO_MEM; + } + } + memcpy(&g_uart_config, config, sizeof(notecard_uart_config_t)); uart_config_t uart_conf = { @@ -257,6 +348,8 @@ esp_err_t notecard_platform_uart_init(const notecard_uart_config_t *config) config->tx_buffer_size, 0, NULL, 0); if (ret != ESP_OK) { ESP_LOGE(TAG, "UART driver install failed: %s", esp_err_to_name(ret)); + vSemaphoreDelete(g_notecard_mutex); + g_notecard_mutex = NULL; return ret; } @@ -280,6 +373,13 @@ esp_err_t notecard_platform_uart_deinit(void) } g_uart_initialized = false; + + // Clean up Notecard mutex + if (g_notecard_mutex != NULL) { + vSemaphoreDelete(g_notecard_mutex); + g_notecard_mutex = NULL; + } + ESP_LOGI(TAG, "UART deinitialized"); return ESP_OK; } @@ -385,3 +485,37 @@ void notecard_platform_free(void *ptr) { free(ptr); } + +//============================================================================= +// Mutex Hook Registration +//============================================================================= + +void notecard_platform_register_mutex_hooks(void) +{ + // Register mutex hooks with note-c based on Kconfig settings + // Enable Notecard mutexes (I2C mutex is optional) + NoteSetFnNoteMutex(notecard_platform_note_lock, + notecard_platform_note_unlock); + +#ifdef CONFIG_NOTECARD_I2C_MUTEX + // Enable I2C mutexes + NoteSetFnI2CMutex(notecard_platform_i2c_lock, + notecard_platform_i2c_unlock); +#endif +} + +//============================================================================= +// Public I2C Mutex API +//============================================================================= + +#ifdef CONFIG_NOTECARD_I2C_MUTEX +void notecard_i2c_lock(void) +{ + notecard_platform_i2c_lock(); +} + +void notecard_i2c_unlock(void) +{ + notecard_platform_i2c_unlock(); +} +#endif diff --git a/src/notecard_platform.h b/src/notecard_platform.h index 63b1f35..355fb83 100644 --- a/src/notecard_platform.h +++ b/src/notecard_platform.h @@ -40,6 +40,15 @@ void notecard_platform_delay(uint32_t ms); void *notecard_platform_malloc(size_t size); void notecard_platform_free(void *ptr); +// Mutex registration function +void notecard_platform_register_mutex_hooks(void); + +#ifdef CONFIG_NOTECARD_I2C_MUTEX +// Public I2C mutex functions +void notecard_i2c_lock(void); +void notecard_i2c_unlock(void); +#endif + #ifdef __cplusplus } #endif