Skip to content

Commit 18b97fc

Browse files
authored
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
1 parent 66bd4d8 commit 18b97fc

File tree

12 files changed

+226
-4
lines changed

12 files changed

+226
-4
lines changed

docs/index.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* @{
4444
* \defgroup pico_async_context pico_async_context
4545
* \defgroup pico_multicore pico_multicore
46+
* \defgroup pico_i2c_slave pico_i2c_slave
4647
* \defgroup pico_rand pico_rand
4748
* \defgroup pico_stdlib pico_stdlib
4849
* \defgroup pico_sync pico_sync

src/rp2040/hardware_regs/include/hardware/platform_defs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#endif
4444

4545
#define FIRST_USER_IRQ (NUM_IRQS - NUM_USER_IRQS)
46+
#define VTABLE_FIRST_IRQ 16
4647

4748
#endif
4849

src/rp2_common/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ if (NOT PICO_BARE_METAL)
5959
pico_add_subdirectory(tinyusb)
6060
pico_add_subdirectory(pico_stdio_usb)
6161

62+
pico_add_subdirectory(pico_i2c_slave)
63+
6264
pico_add_subdirectory(pico_async_context)
6365
pico_add_subdirectory(pico_cyw43_driver)
6466
pico_add_subdirectory(pico_lwip)

src/rp2_common/hardware_i2c/include/hardware/i2c.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ static inline i2c_hw_t *i2c_get_hw(i2c_inst_t *i2c) {
153153
return i2c->hw;
154154
}
155155

156+
static inline i2c_inst_t *i2c_get_instance(uint instance) {
157+
static_assert(NUM_I2CS == 2, "");
158+
invalid_params_if(I2C, instance >= NUM_I2CS);
159+
return instance ? i2c1 : i2c0;
160+
}
161+
156162
/*! \brief Attempt to write specified number of bytes to address, blocking until the specified absolute time is reached.
157163
* \ingroup hardware_i2c
158164
*
@@ -312,6 +318,37 @@ static inline void i2c_read_raw_blocking(i2c_inst_t *i2c, uint8_t *dst, size_t l
312318
}
313319
}
314320

321+
/**
322+
* \brief Pop a byte from I2C Rx FIFO.
323+
* \ingroup hardware_i2c
324+
*
325+
* This function is non-blocking and assumes the Rx FIFO isn't empty.
326+
*
327+
* \param i2c I2C instance.
328+
* \return uint8_t Byte value.
329+
*/
330+
static inline uint8_t i2c_read_byte_raw(i2c_inst_t *i2c) {
331+
i2c_hw_t *hw = i2c_get_hw(i2c);
332+
assert(hw->status & I2C_IC_STATUS_RFNE_BITS); // Rx FIFO must not be empty
333+
return (uint8_t)hw->data_cmd;
334+
}
335+
336+
/**
337+
* \brief Push a byte into I2C Tx FIFO.
338+
* \ingroup hardware_i2c
339+
*
340+
* This function is non-blocking and assumes the Tx FIFO isn't full.
341+
*
342+
* \param i2c I2C instance.
343+
* \param value Byte value.
344+
*/
345+
static inline void i2c_write_byte_raw(i2c_inst_t *i2c, uint8_t value) {
346+
i2c_hw_t *hw = i2c_get_hw(i2c);
347+
assert(hw->status & I2C_IC_STATUS_TFNF_BITS); // Tx FIFO must not be full
348+
hw->data_cmd = value;
349+
}
350+
351+
315352
/*! \brief Return the DREQ to use for pacing transfers to/from a particular I2C instance
316353
* \ingroup hardware_i2c
317354
*

src/rp2_common/hardware_irq/irq.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ static inline void *remove_thumb_bit(void *addr) {
4141

4242
static void set_raw_irq_handler_and_unlock(uint num, irq_handler_t handler, uint32_t save) {
4343
// update vtable (vtable_handler may be same or updated depending on cases, but we do it anyway for compactness)
44-
get_vtable()[16 + num] = handler;
44+
get_vtable()[VTABLE_FIRST_IRQ + num] = handler;
4545
__dmb();
4646
spin_unlock(spin_lock_instance(PICO_SPINLOCK_ID_IRQ), save);
4747
}
@@ -306,7 +306,7 @@ void irq_remove_handler(uint num, irq_handler_t handler) {
306306
// Sadly this is not something we can detect.
307307

308308
uint exception = __get_current_exception();
309-
hard_assert(!exception || exception == num + 16);
309+
hard_assert(!exception || exception == num + VTABLE_FIRST_IRQ);
310310

311311
struct irq_handler_chain_slot *prev_slot = NULL;
312312
struct irq_handler_chain_slot *existing_vtable_slot = remove_thumb_bit(vtable_handler);

src/rp2_common/hardware_timer/timer.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ static inline uint harware_alarm_irq_number(uint alarm_num) {
112112

113113
static void hardware_alarm_irq_handler(void) {
114114
// Determine which timer this IRQ is for
115-
uint alarm_num = __get_current_exception() - 16 - TIMER_IRQ_0;
115+
uint alarm_num = __get_current_exception() - VTABLE_FIRST_IRQ - TIMER_IRQ_0;
116116
check_hardware_alarm_num_param(alarm_num);
117117

118118
hardware_alarm_callback_t callback = NULL;

src/rp2_common/pico_async_context/async_context_threadsafe_background.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ static void process_under_lock(async_context_threadsafe_background_t *self) {
280280

281281
// Low priority interrupt handler to perform background processing
282282
static void low_priority_irq_handler(void) {
283-
uint index = __get_current_exception() - 16 - FIRST_USER_IRQ;
283+
uint index = __get_current_exception() - VTABLE_FIRST_IRQ - FIRST_USER_IRQ;
284284
assert(index < count_of(async_contexts_by_user_irq));
285285
async_context_threadsafe_background_t *self = async_contexts_by_user_irq[index];
286286
if (!self) return;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
if (NOT TARGET pico_i2c_slave)
2+
pico_add_library(pico_i2c_slave)
3+
4+
target_sources(pico_i2c_slave INTERFACE
5+
${CMAKE_CURRENT_LIST_DIR}/i2c_slave.c)
6+
7+
target_include_directories(pico_i2c_slave_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
8+
9+
pico_mirrored_target_link_libraries(pico_i2c_slave INTERFACE hardware_i2c hardware_irq)
10+
endif()
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright (c) 2021 Valentin Milea <[email protected]>
3+
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
4+
*
5+
* SPDX-License-Identifier: BSD-3-Clause
6+
*/
7+
8+
#include "pico/i2c_slave.h"
9+
#include "hardware/irq.h"
10+
11+
typedef struct i2c_slave {
12+
i2c_slave_handler_t handler;
13+
bool transfer_in_progress;
14+
} i2c_slave_t;
15+
16+
static i2c_slave_t i2c_slaves[2];
17+
18+
static inline i2c_inst_t *get_hw_instance(const i2c_slave_t *slave) {
19+
return i2c_get_instance(slave - i2c_slaves);
20+
}
21+
22+
static void __isr __not_in_flash_func(i2c_slave_irq_handler)(void) {
23+
uint i2c_index = __get_current_exception() - VTABLE_FIRST_IRQ - I2C0_IRQ;
24+
i2c_slave_t *slave = &i2c_slaves[i2c_index];
25+
i2c_inst_t *i2c = i2c_get_instance(i2c_index);
26+
i2c_hw_t *hw = i2c_get_hw(i2c);
27+
28+
uint32_t intr_stat = hw->intr_stat;
29+
if (intr_stat == 0) {
30+
return;
31+
}
32+
bool do_finish_transfer = false;
33+
if (intr_stat & I2C_IC_INTR_STAT_R_TX_ABRT_BITS) {
34+
hw->clr_tx_abrt;
35+
do_finish_transfer = true;
36+
}
37+
if (intr_stat & I2C_IC_INTR_STAT_R_START_DET_BITS) {
38+
hw->clr_start_det;
39+
do_finish_transfer = true;
40+
}
41+
if (intr_stat & I2C_IC_INTR_STAT_R_STOP_DET_BITS) {
42+
hw->clr_stop_det;
43+
do_finish_transfer = true;
44+
}
45+
if (do_finish_transfer && slave->transfer_in_progress) {
46+
slave->handler(i2c, I2C_SLAVE_FINISH);
47+
slave->transfer_in_progress = false;
48+
}
49+
if (intr_stat & I2C_IC_INTR_STAT_R_RX_FULL_BITS) {
50+
slave->transfer_in_progress = true;
51+
slave->handler(i2c, I2C_SLAVE_RECEIVE);
52+
}
53+
if (intr_stat & I2C_IC_INTR_STAT_R_RD_REQ_BITS) {
54+
hw->clr_rd_req;
55+
slave->transfer_in_progress = true;
56+
slave->handler(i2c, I2C_SLAVE_REQUEST);
57+
}
58+
}
59+
60+
void i2c_slave_init(i2c_inst_t *i2c, uint8_t address, i2c_slave_handler_t handler) {
61+
assert(i2c == i2c0 || i2c == i2c1);
62+
assert(handler != NULL);
63+
64+
uint i2c_index = i2c_hw_index(i2c);
65+
i2c_slave_t *slave = &i2c_slaves[i2c_index];
66+
slave->handler = handler;
67+
68+
// Note: The I2C slave does clock stretching implicitly after a RD_REQ, while the Tx FIFO is empty.
69+
// There is also an option to enable clock stretching while the Rx FIFO is full, but we leave it
70+
// disabled since the Rx FIFO should never fill up (unless slave->handler() is way too slow).
71+
i2c_set_slave_mode(i2c, true, address);
72+
73+
i2c_hw_t *hw = i2c_get_hw(i2c);
74+
// unmask necessary interrupts
75+
hw->intr_mask =
76+
I2C_IC_INTR_MASK_M_RX_FULL_BITS | I2C_IC_INTR_MASK_M_RD_REQ_BITS | I2C_IC_RAW_INTR_STAT_TX_ABRT_BITS |
77+
I2C_IC_INTR_MASK_M_STOP_DET_BITS | I2C_IC_INTR_MASK_M_START_DET_BITS;
78+
79+
// enable interrupt for current core
80+
uint num = I2C0_IRQ + i2c_index;
81+
irq_set_exclusive_handler(num, i2c_slave_irq_handler);
82+
irq_set_enabled(num, true);
83+
}
84+
85+
void i2c_slave_deinit(i2c_inst_t *i2c) {
86+
assert(i2c == i2c0 || i2c == i2c1);
87+
88+
uint i2c_index = i2c_hw_index(i2c);
89+
i2c_slave_t *slave = &i2c_slaves[i2c_index];
90+
assert(slave->handler); // should be called after i2c_slave_init()
91+
92+
slave->handler = NULL;
93+
slave->transfer_in_progress = false;
94+
95+
uint num = I2C0_IRQ + i2c_index;
96+
irq_set_enabled(num, false);
97+
irq_remove_handler(num, i2c_slave_irq_handler);
98+
99+
i2c_hw_t *hw = i2c_get_hw(i2c);
100+
hw->intr_mask = I2C_IC_INTR_MASK_RESET;
101+
102+
i2c_set_slave_mode(i2c, false, 0);
103+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2021 Valentin Milea <[email protected]>
3+
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
4+
*
5+
* SPDX-License-Identifier: BSD-3-Clause
6+
*/
7+
8+
#ifndef _PICO_I2C_SLAVE_H_
9+
#define _PICO_I2C_SLAVE_H_
10+
11+
#include "hardware/i2c.h"
12+
13+
#ifdef __cplusplus
14+
extern "C" {
15+
#endif
16+
17+
/** \file pico/i2c_slave.h
18+
* \defgrup pico_i2c_slave pico_i2c_slave
19+
* \brief I2C slave helper library, which takes care of hooking the I2C IRQ and calling back the user with I2C events.
20+
*/
21+
22+
/**
23+
* \brief I2C slave event types.
24+
*/
25+
typedef enum i2c_slave_event_t
26+
{
27+
I2C_SLAVE_RECEIVE, /**< Data from master is available for reading. Slave must read from Rx FIFO. */
28+
I2C_SLAVE_REQUEST, /**< Master is requesting data. Slave must write into Tx FIFO. */
29+
I2C_SLAVE_FINISH, /**< Master has sent a Stop or Restart signal. Slave may prepare for the next transfer. */
30+
} i2c_slave_event_t;
31+
32+
/**
33+
* \brief I2C slave event handler
34+
*
35+
* The event handler will run from the I2C ISR, so it should return quickly (under 25 us at 400 kb/s).
36+
* Avoid blocking inside the handler and split large data transfers across multiple calls for best results.
37+
* When sending data to master, up to `i2c_get_write_available()` bytes can be written without blocking.
38+
* When receiving data from master, up to `i2c_get_read_available()` bytes can be read without blocking.
39+
*
40+
* \param i2c Slave I2C instance.
41+
* \param event Event type.
42+
*/
43+
typedef void (*i2c_slave_handler_t)(i2c_inst_t *i2c, i2c_slave_event_t event);
44+
45+
/**
46+
* \brief Configure I2C instance for slave mode.
47+
*
48+
* \param i2c I2C instance.
49+
* \param address 7-bit slave address.
50+
* \param handler Called on events from I2C master. It will run from the I2C ISR, on the CPU core
51+
* where the slave was initialized.
52+
*/
53+
void i2c_slave_init(i2c_inst_t *i2c, uint8_t address, i2c_slave_handler_t handler);
54+
55+
/**
56+
* \brief Restore I2C instance to master mode.
57+
*
58+
* \param i2c I2C instance.
59+
*/
60+
void i2c_slave_deinit(i2c_inst_t *i2c);
61+
62+
#ifdef __cplusplus
63+
}
64+
#endif
65+
66+
#endif // _PICO_I2C_SLAVE_H_

0 commit comments

Comments
 (0)