From 18b97fcba0d601774e40301f6786ebe5eb3260d6 Mon Sep 17 00:00:00 2001 From: Graham Sanderson Date: Tue, 7 Feb 2023 07:47:01 -0600 Subject: [PATCH] add new pcio_I2c_slave library (#1205) * add (slightly modified) pico_i2c_slave library from https://github.com/vmilea/pico_i2c_slave * introduce VTABLE_FIRST_IRQ constant --- docs/index.h | 1 + .../include/hardware/platform_defs.h | 1 + src/rp2_common/CMakeLists.txt | 2 + .../hardware_i2c/include/hardware/i2c.h | 37 +++++++ src/rp2_common/hardware_irq/irq.c | 4 +- src/rp2_common/hardware_timer/timer.c | 2 +- .../async_context_threadsafe_background.c | 2 +- src/rp2_common/pico_i2c_slave/CMakeLists.txt | 10 ++ src/rp2_common/pico_i2c_slave/i2c_slave.c | 103 ++++++++++++++++++ .../pico_i2c_slave/include/pico/i2c_slave.h | 66 +++++++++++ test/kitchen_sink/CMakeLists.txt | 1 + test/kitchen_sink/kitchen_sink.c | 1 + 12 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 src/rp2_common/pico_i2c_slave/CMakeLists.txt create mode 100644 src/rp2_common/pico_i2c_slave/i2c_slave.c create mode 100644 src/rp2_common/pico_i2c_slave/include/pico/i2c_slave.h diff --git a/docs/index.h b/docs/index.h index b09521f..209a28a 100644 --- a/docs/index.h +++ b/docs/index.h @@ -43,6 +43,7 @@ * @{ * \defgroup pico_async_context pico_async_context * \defgroup pico_multicore pico_multicore + * \defgroup pico_i2c_slave pico_i2c_slave * \defgroup pico_rand pico_rand * \defgroup pico_stdlib pico_stdlib * \defgroup pico_sync pico_sync diff --git a/src/rp2040/hardware_regs/include/hardware/platform_defs.h b/src/rp2040/hardware_regs/include/hardware/platform_defs.h index 2f51642..c28bb90 100644 --- a/src/rp2040/hardware_regs/include/hardware/platform_defs.h +++ b/src/rp2040/hardware_regs/include/hardware/platform_defs.h @@ -43,6 +43,7 @@ #endif #define FIRST_USER_IRQ (NUM_IRQS - NUM_USER_IRQS) +#define VTABLE_FIRST_IRQ 16 #endif diff --git a/src/rp2_common/CMakeLists.txt b/src/rp2_common/CMakeLists.txt index fd1260a..0f7eb7e 100644 --- a/src/rp2_common/CMakeLists.txt +++ b/src/rp2_common/CMakeLists.txt @@ -59,6 +59,8 @@ if (NOT PICO_BARE_METAL) pico_add_subdirectory(tinyusb) pico_add_subdirectory(pico_stdio_usb) + pico_add_subdirectory(pico_i2c_slave) + pico_add_subdirectory(pico_async_context) pico_add_subdirectory(pico_cyw43_driver) pico_add_subdirectory(pico_lwip) diff --git a/src/rp2_common/hardware_i2c/include/hardware/i2c.h b/src/rp2_common/hardware_i2c/include/hardware/i2c.h index 202ec02..33454f0 100644 --- a/src/rp2_common/hardware_i2c/include/hardware/i2c.h +++ b/src/rp2_common/hardware_i2c/include/hardware/i2c.h @@ -153,6 +153,12 @@ static inline i2c_hw_t *i2c_get_hw(i2c_inst_t *i2c) { return i2c->hw; } +static inline i2c_inst_t *i2c_get_instance(uint instance) { + static_assert(NUM_I2CS == 2, ""); + invalid_params_if(I2C, instance >= NUM_I2CS); + return instance ? i2c1 : i2c0; +} + /*! \brief Attempt to write specified number of bytes to address, blocking until the specified absolute time is reached. * \ingroup hardware_i2c * @@ -312,6 +318,37 @@ static inline void i2c_read_raw_blocking(i2c_inst_t *i2c, uint8_t *dst, size_t l } } +/** + * \brief Pop a byte from I2C Rx FIFO. + * \ingroup hardware_i2c + * + * This function is non-blocking and assumes the Rx FIFO isn't empty. + * + * \param i2c I2C instance. + * \return uint8_t Byte value. + */ +static inline uint8_t i2c_read_byte_raw(i2c_inst_t *i2c) { + i2c_hw_t *hw = i2c_get_hw(i2c); + assert(hw->status & I2C_IC_STATUS_RFNE_BITS); // Rx FIFO must not be empty + return (uint8_t)hw->data_cmd; +} + +/** + * \brief Push a byte into I2C Tx FIFO. + * \ingroup hardware_i2c + * + * This function is non-blocking and assumes the Tx FIFO isn't full. + * + * \param i2c I2C instance. + * \param value Byte value. + */ +static inline void i2c_write_byte_raw(i2c_inst_t *i2c, uint8_t value) { + i2c_hw_t *hw = i2c_get_hw(i2c); + assert(hw->status & I2C_IC_STATUS_TFNF_BITS); // Tx FIFO must not be full + hw->data_cmd = value; +} + + /*! \brief Return the DREQ to use for pacing transfers to/from a particular I2C instance * \ingroup hardware_i2c * diff --git a/src/rp2_common/hardware_irq/irq.c b/src/rp2_common/hardware_irq/irq.c index 616ea49..1ca799c 100644 --- a/src/rp2_common/hardware_irq/irq.c +++ b/src/rp2_common/hardware_irq/irq.c @@ -41,7 +41,7 @@ static inline void *remove_thumb_bit(void *addr) { static void set_raw_irq_handler_and_unlock(uint num, irq_handler_t handler, uint32_t save) { // update vtable (vtable_handler may be same or updated depending on cases, but we do it anyway for compactness) - get_vtable()[16 + num] = handler; + get_vtable()[VTABLE_FIRST_IRQ + num] = handler; __dmb(); spin_unlock(spin_lock_instance(PICO_SPINLOCK_ID_IRQ), save); } @@ -306,7 +306,7 @@ void irq_remove_handler(uint num, irq_handler_t handler) { // Sadly this is not something we can detect. uint exception = __get_current_exception(); - hard_assert(!exception || exception == num + 16); + hard_assert(!exception || exception == num + VTABLE_FIRST_IRQ); struct irq_handler_chain_slot *prev_slot = NULL; struct irq_handler_chain_slot *existing_vtable_slot = remove_thumb_bit(vtable_handler); diff --git a/src/rp2_common/hardware_timer/timer.c b/src/rp2_common/hardware_timer/timer.c index 3ac5c8c..a2bfe89 100644 --- a/src/rp2_common/hardware_timer/timer.c +++ b/src/rp2_common/hardware_timer/timer.c @@ -112,7 +112,7 @@ static inline uint harware_alarm_irq_number(uint alarm_num) { static void hardware_alarm_irq_handler(void) { // Determine which timer this IRQ is for - uint alarm_num = __get_current_exception() - 16 - TIMER_IRQ_0; + uint alarm_num = __get_current_exception() - VTABLE_FIRST_IRQ - TIMER_IRQ_0; check_hardware_alarm_num_param(alarm_num); hardware_alarm_callback_t callback = NULL; diff --git a/src/rp2_common/pico_async_context/async_context_threadsafe_background.c b/src/rp2_common/pico_async_context/async_context_threadsafe_background.c index 444d8c2..7890fe2 100644 --- a/src/rp2_common/pico_async_context/async_context_threadsafe_background.c +++ b/src/rp2_common/pico_async_context/async_context_threadsafe_background.c @@ -280,7 +280,7 @@ static void process_under_lock(async_context_threadsafe_background_t *self) { // Low priority interrupt handler to perform background processing static void low_priority_irq_handler(void) { - uint index = __get_current_exception() - 16 - FIRST_USER_IRQ; + uint index = __get_current_exception() - VTABLE_FIRST_IRQ - FIRST_USER_IRQ; assert(index < count_of(async_contexts_by_user_irq)); async_context_threadsafe_background_t *self = async_contexts_by_user_irq[index]; if (!self) return; diff --git a/src/rp2_common/pico_i2c_slave/CMakeLists.txt b/src/rp2_common/pico_i2c_slave/CMakeLists.txt new file mode 100644 index 0000000..f0f246d --- /dev/null +++ b/src/rp2_common/pico_i2c_slave/CMakeLists.txt @@ -0,0 +1,10 @@ +if (NOT TARGET pico_i2c_slave) + pico_add_library(pico_i2c_slave) + + target_sources(pico_i2c_slave INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/i2c_slave.c) + + target_include_directories(pico_i2c_slave_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) + + pico_mirrored_target_link_libraries(pico_i2c_slave INTERFACE hardware_i2c hardware_irq) +endif() diff --git a/src/rp2_common/pico_i2c_slave/i2c_slave.c b/src/rp2_common/pico_i2c_slave/i2c_slave.c new file mode 100644 index 0000000..74a988e --- /dev/null +++ b/src/rp2_common/pico_i2c_slave/i2c_slave.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 Valentin Milea + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico/i2c_slave.h" +#include "hardware/irq.h" + +typedef struct i2c_slave { + i2c_slave_handler_t handler; + bool transfer_in_progress; +} i2c_slave_t; + +static i2c_slave_t i2c_slaves[2]; + +static inline i2c_inst_t *get_hw_instance(const i2c_slave_t *slave) { + return i2c_get_instance(slave - i2c_slaves); +} + +static void __isr __not_in_flash_func(i2c_slave_irq_handler)(void) { + uint i2c_index = __get_current_exception() - VTABLE_FIRST_IRQ - I2C0_IRQ; + i2c_slave_t *slave = &i2c_slaves[i2c_index]; + i2c_inst_t *i2c = i2c_get_instance(i2c_index); + i2c_hw_t *hw = i2c_get_hw(i2c); + + uint32_t intr_stat = hw->intr_stat; + if (intr_stat == 0) { + return; + } + bool do_finish_transfer = false; + if (intr_stat & I2C_IC_INTR_STAT_R_TX_ABRT_BITS) { + hw->clr_tx_abrt; + do_finish_transfer = true; + } + if (intr_stat & I2C_IC_INTR_STAT_R_START_DET_BITS) { + hw->clr_start_det; + do_finish_transfer = true; + } + if (intr_stat & I2C_IC_INTR_STAT_R_STOP_DET_BITS) { + hw->clr_stop_det; + do_finish_transfer = true; + } + if (do_finish_transfer && slave->transfer_in_progress) { + slave->handler(i2c, I2C_SLAVE_FINISH); + slave->transfer_in_progress = false; + } + if (intr_stat & I2C_IC_INTR_STAT_R_RX_FULL_BITS) { + slave->transfer_in_progress = true; + slave->handler(i2c, I2C_SLAVE_RECEIVE); + } + if (intr_stat & I2C_IC_INTR_STAT_R_RD_REQ_BITS) { + hw->clr_rd_req; + slave->transfer_in_progress = true; + slave->handler(i2c, I2C_SLAVE_REQUEST); + } +} + +void i2c_slave_init(i2c_inst_t *i2c, uint8_t address, i2c_slave_handler_t handler) { + assert(i2c == i2c0 || i2c == i2c1); + assert(handler != NULL); + + uint i2c_index = i2c_hw_index(i2c); + i2c_slave_t *slave = &i2c_slaves[i2c_index]; + slave->handler = handler; + + // Note: The I2C slave does clock stretching implicitly after a RD_REQ, while the Tx FIFO is empty. + // There is also an option to enable clock stretching while the Rx FIFO is full, but we leave it + // disabled since the Rx FIFO should never fill up (unless slave->handler() is way too slow). + i2c_set_slave_mode(i2c, true, address); + + i2c_hw_t *hw = i2c_get_hw(i2c); + // unmask necessary interrupts + hw->intr_mask = + I2C_IC_INTR_MASK_M_RX_FULL_BITS | I2C_IC_INTR_MASK_M_RD_REQ_BITS | I2C_IC_RAW_INTR_STAT_TX_ABRT_BITS | + I2C_IC_INTR_MASK_M_STOP_DET_BITS | I2C_IC_INTR_MASK_M_START_DET_BITS; + + // enable interrupt for current core + uint num = I2C0_IRQ + i2c_index; + irq_set_exclusive_handler(num, i2c_slave_irq_handler); + irq_set_enabled(num, true); +} + +void i2c_slave_deinit(i2c_inst_t *i2c) { + assert(i2c == i2c0 || i2c == i2c1); + + uint i2c_index = i2c_hw_index(i2c); + i2c_slave_t *slave = &i2c_slaves[i2c_index]; + assert(slave->handler); // should be called after i2c_slave_init() + + slave->handler = NULL; + slave->transfer_in_progress = false; + + uint num = I2C0_IRQ + i2c_index; + irq_set_enabled(num, false); + irq_remove_handler(num, i2c_slave_irq_handler); + + i2c_hw_t *hw = i2c_get_hw(i2c); + hw->intr_mask = I2C_IC_INTR_MASK_RESET; + + i2c_set_slave_mode(i2c, false, 0); +} diff --git a/src/rp2_common/pico_i2c_slave/include/pico/i2c_slave.h b/src/rp2_common/pico_i2c_slave/include/pico/i2c_slave.h new file mode 100644 index 0000000..f16bafc --- /dev/null +++ b/src/rp2_common/pico_i2c_slave/include/pico/i2c_slave.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Valentin Milea + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_I2C_SLAVE_H_ +#define _PICO_I2C_SLAVE_H_ + +#include "hardware/i2c.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file pico/i2c_slave.h + * \defgrup pico_i2c_slave pico_i2c_slave + * \brief I2C slave helper library, which takes care of hooking the I2C IRQ and calling back the user with I2C events. + */ + +/** + * \brief I2C slave event types. + */ +typedef enum i2c_slave_event_t +{ + I2C_SLAVE_RECEIVE, /**< Data from master is available for reading. Slave must read from Rx FIFO. */ + I2C_SLAVE_REQUEST, /**< Master is requesting data. Slave must write into Tx FIFO. */ + I2C_SLAVE_FINISH, /**< Master has sent a Stop or Restart signal. Slave may prepare for the next transfer. */ +} i2c_slave_event_t; + +/** + * \brief I2C slave event handler + * + * The event handler will run from the I2C ISR, so it should return quickly (under 25 us at 400 kb/s). + * Avoid blocking inside the handler and split large data transfers across multiple calls for best results. + * When sending data to master, up to `i2c_get_write_available()` bytes can be written without blocking. + * When receiving data from master, up to `i2c_get_read_available()` bytes can be read without blocking. + * + * \param i2c Slave I2C instance. + * \param event Event type. + */ +typedef void (*i2c_slave_handler_t)(i2c_inst_t *i2c, i2c_slave_event_t event); + +/** + * \brief Configure I2C instance for slave mode. + * + * \param i2c I2C instance. + * \param address 7-bit slave address. + * \param handler Called on events from I2C master. It will run from the I2C ISR, on the CPU core + * where the slave was initialized. + */ +void i2c_slave_init(i2c_inst_t *i2c, uint8_t address, i2c_slave_handler_t handler); + +/** + * \brief Restore I2C instance to master mode. + * + * \param i2c I2C instance. + */ +void i2c_slave_deinit(i2c_inst_t *i2c); + +#ifdef __cplusplus +} +#endif + +#endif // _PICO_I2C_SLAVE_H_ diff --git a/test/kitchen_sink/CMakeLists.txt b/test/kitchen_sink/CMakeLists.txt index 0ab07aa..2068a1a 100644 --- a/test/kitchen_sink/CMakeLists.txt +++ b/test/kitchen_sink/CMakeLists.txt @@ -30,6 +30,7 @@ target_link_libraries(kitchen_sink_libs INTERFACE pico_double pico_fix_rp2040_usb_device_enumeration pico_float + pico_i2c_slave pico_int64_ops pico_malloc pico_mem_ops diff --git a/test/kitchen_sink/kitchen_sink.c b/test/kitchen_sink/kitchen_sink.c index 03ec8dd..c030a76 100644 --- a/test/kitchen_sink/kitchen_sink.c +++ b/test/kitchen_sink/kitchen_sink.c @@ -41,6 +41,7 @@ #include "pico/fix/rp2040_usb_device_enumeration.h" #include "pico/float.h" #include "pico/int64_ops.h" +#include "pico/i2c_slave.h" #include "pico/malloc.h" #include "pico/multicore.h" #include "pico/printf.h"