Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/index.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/rp2040/hardware_regs/include/hardware/platform_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#endif

#define FIRST_USER_IRQ (NUM_IRQS - NUM_USER_IRQS)
#define VTABLE_FIRST_IRQ 16

#endif

2 changes: 2 additions & 0 deletions src/rp2_common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 37 additions & 0 deletions src/rp2_common/hardware_i2c/include/hardware/i2c.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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
*
Expand Down
4 changes: 2 additions & 2 deletions src/rp2_common/hardware_irq/irq.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/rp2_common/hardware_timer/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions src/rp2_common/pico_i2c_slave/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
103 changes: 103 additions & 0 deletions src/rp2_common/pico_i2c_slave/i2c_slave.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2021 Valentin Milea <valentin.milea@gmail.com>
* 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);
}
66 changes: 66 additions & 0 deletions src/rp2_common/pico_i2c_slave/include/pico/i2c_slave.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2021 Valentin Milea <valentin.milea@gmail.com>
* 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_
1 change: 1 addition & 0 deletions test/kitchen_sink/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test/kitchen_sink/kitchen_sink.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,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"
Expand Down