From 68b9c4778faed6f0b1a589d42bb932e0464019d2 Mon Sep 17 00:00:00 2001 From: R Date: Wed, 6 Aug 2025 02:39:13 +0100 Subject: [PATCH 01/10] pbio/drv/rproc/rproc_ev3_pru1.h: Share with PRU project This makes it much easier to keep the projects in sync. --- lib/pbio/drv/pwm/pwm_ev3.c | 2 +- lib/pbio/drv/rproc/rproc_ev3.h | 9 +-------- lib/pbio/drv/rproc/rproc_ev3_pru1.h | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 lib/pbio/drv/rproc/rproc_ev3_pru1.h diff --git a/lib/pbio/drv/pwm/pwm_ev3.c b/lib/pbio/drv/pwm/pwm_ev3.c index 0783d6a8d..3a9d99a56 100644 --- a/lib/pbio/drv/pwm/pwm_ev3.c +++ b/lib/pbio/drv/pwm/pwm_ev3.c @@ -20,7 +20,7 @@ static pbio_error_t pbdrv_pwm_tiam1808_set_duty(pbdrv_pwm_dev_t *dev, uint32_t c return PBIO_SUCCESS; } - pbdrv_rproc_ev3_pru1_shared_ram.pwms[ch] = value; + pbdrv_rproc_ev3_pru1_shared_ram.pwm_duty_cycle[ch] = value; return PBIO_SUCCESS; } diff --git a/lib/pbio/drv/rproc/rproc_ev3.h b/lib/pbio/drv/rproc/rproc_ev3.h index d8c51716a..7012af570 100644 --- a/lib/pbio/drv/rproc/rproc_ev3.h +++ b/lib/pbio/drv/rproc/rproc_ev3.h @@ -4,18 +4,11 @@ #ifndef _INTERNAL_PBDRV_RPROC_EV3_H_ #define _INTERNAL_PBDRV_RPROC_EV3_H_ -#include - // EV3 PRU interfaces and ABI // These need to match the interface expected by the PRU firmware // PRU1 interface - -#define PBDRV_RPROC_EV3_PRU1_NUM_PWM_CHANNELS 4 - -typedef struct { - uint8_t pwms[PBDRV_RPROC_EV3_PRU1_NUM_PWM_CHANNELS]; -} pbdrv_rproc_ev3_pru1_shared_ram_t; +#include "rproc_ev3_pru1.h" extern volatile pbdrv_rproc_ev3_pru1_shared_ram_t pbdrv_rproc_ev3_pru1_shared_ram; diff --git a/lib/pbio/drv/rproc/rproc_ev3_pru1.h b/lib/pbio/drv/rproc/rproc_ev3_pru1.h new file mode 100644 index 000000000..4402c0d82 --- /dev/null +++ b/lib/pbio/drv/rproc/rproc_ev3_pru1.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 The Pybricks Authors + +#ifndef _INTERNAL_PBDRV_RPROC_EV3_PRU1_H_ +#define _INTERNAL_PBDRV_RPROC_EV3_PRU1_H_ + +#include + +// This file is shared between the PRU1 source code +// and the Pybricks source code. It defines the ABI +// between the two codebases and must be kept in sync. + +#define PBDRV_RPROC_EV3_PRU1_NUM_PWM_CHANNELS 4 + +typedef struct { + union { + // u32 used by the PRU codebase to get more-efficient codegen + uint32_t pwms; + // PWM duty cycle for each channel, range [0-255] + uint8_t pwm_duty_cycle[PBDRV_RPROC_EV3_PRU1_NUM_PWM_CHANNELS]; + }; +} pbdrv_rproc_ev3_pru1_shared_ram_t; + +#endif // _INTERNAL_PBDRV_RPROC_EV3_PRU1_H_ From 413e42eaf051f1b807928ef4e8466b0c8afc952d Mon Sep 17 00:00:00 2001 From: R Date: Wed, 6 Aug 2025 02:46:07 +0100 Subject: [PATCH 02/10] pbio/drv/rproc/rproc_ev3.c: Clear PRU1 data RAM The firmware expects .bss variables to be zeroed by the loader, which is this code here. --- lib/pbio/drv/rproc/rproc_ev3.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/pbio/drv/rproc/rproc_ev3.c b/lib/pbio/drv/rproc/rproc_ev3.c index 96c4e6dbf..79aa9d5fe 100644 --- a/lib/pbio/drv/rproc/rproc_ev3.c +++ b/lib/pbio/drv/rproc/rproc_ev3.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -56,6 +57,10 @@ void pbdrv_rproc_init(void) { unsigned int *fw_start = (unsigned int *)&pbdrv_rproc_ev3_pru1_fw_start; uint32_t fw_sz = &pbdrv_rproc_ev3_pru1_fw_end - &pbdrv_rproc_ev3_pru1_fw_start; PRUSSDRVPruWriteMemory(PRUSS0_PRU1_IRAM, 0, fw_start, fw_sz); + // Clear data RAM + for (int i = 0; i < PRUSS_DATARAM_SIZE; i += 4) { + HWREG(DATARAM1_PHYS_BASE + i) = 0; + } // Set constant table C30 to point to shared memory PRUSSDRVPruSetCTable(1, 30, (((uint32_t)&pbdrv_rproc_ev3_pru1_shared_ram) >> 8) & 0xffff); PRUSSDRVPruEnable(1); From c46a978def03fead4eec309b968a700a9855983a Mon Sep 17 00:00:00 2001 From: R Date: Thu, 7 Aug 2025 18:06:23 +0100 Subject: [PATCH 03/10] bricks/_common/arm_none_eabi.mk: Update PRU1 firmware This is the released firmware binary which implements I2C. --- bricks/_common/arm_none_eabi.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bricks/_common/arm_none_eabi.mk b/bricks/_common/arm_none_eabi.mk index ac207b950..764e55fff 100644 --- a/bricks/_common/arm_none_eabi.mk +++ b/bricks/_common/arm_none_eabi.mk @@ -624,8 +624,8 @@ $(BUILD)/u-boot.bin: $(BUILD)/pru_ledpwm.bin: $(ECHO) "Downloading pru_ledpwm.bin" $(Q)mkdir -p $(dir $@) - $(Q)curl -sL -o $@ https://github.com/pybricks/pybricks-pru/releases/download/v0.0.1/pru_ledpwm.bin - $(Q)echo "c0138addb8ebb3d0f531499b6f45ccc71f524afbb6ce55ca3ab462a001ec28d2 $@" | sha256sum -c --strict + $(Q)curl -sL -o $@ https://github.com/pybricks/pybricks-pru/releases/download/v1.0.0/pru_ledpwm.bin + $(Q)echo "b4f1225e277bb22efa5394ce782cc19a3e2fdd54367e40b9d09e9ca99c6ef6d0 $@" | sha256sum -c --strict MAKE_BOOTABLE_IMAGE = $(PBTOP)/bricks/ev3/make_bootable_image.py From b0004ca4ea6ddef61a96c32c0790e2aa7967a574 Mon Sep 17 00:00:00 2001 From: R Date: Wed, 6 Aug 2025 03:03:08 +0100 Subject: [PATCH 04/10] pbio/drv/gpio/gpio_ev3.c: Route via PRU Give the PRU complete control over GPIO banks 0/1. The PRU needs to be able to change GPIO pin directions for I2C. However, if the ARM and the PRU both attempt read-modify-write operations on this register, they can corrupt each other's access. Solve this by giving the PRU complete control over these banks once the PRU has booted. --- lib/pbio/drv/gpio/gpio_ev3.c | 34 +++++++++++++++++++++++++++-- lib/pbio/drv/rproc/rproc_ev3_pru1.h | 6 +++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/pbio/drv/gpio/gpio_ev3.c b/lib/pbio/drv/gpio/gpio_ev3.c index 6a794f3cf..d2c98e962 100644 --- a/lib/pbio/drv/gpio/gpio_ev3.c +++ b/lib/pbio/drv/gpio/gpio_ev3.c @@ -17,6 +17,9 @@ #include #include +#include "../rproc/rproc.h" +#include "../rproc/rproc_ev3.h" + static uint32_t get_pin_index(const pbdrv_gpio_t *gpio) { // TI API indexes pins from 1 to 144, so need to add 1. pbdrv_gpio_ev3_mux_t *mux_info = gpio->bank; @@ -31,13 +34,32 @@ static void pbdrv_gpio_alt_gpio(const pbdrv_gpio_t *gpio) { pbdrv_gpio_alt(gpio, mux_info->gpio_mode); } +// If the pin is not in banks 0/1, the PRU is not involved. +// Otherwise, if the PRU is initialized, do direction changes +// via the PRU in order to prevent race conditions. +// If the PRU is not initialized (i.e. doing direction changes +// during early boot), also set the direction directly via +// the ARM. The PRU code polls these direction change registers +// continuously and is not expected to block for too long, +// only on the order of microseconds. +#define PBDRV_GPIO_EV3_ARM_OWNS_GPIO_BANK(pin_index) \ + ((pin_index) > 32 || !pbdrv_rproc_is_ready()) + static void gpio_write(const pbdrv_gpio_t *gpio, uint8_t value) { if (!gpio) { return; } pbdrv_gpio_alt_gpio(gpio); uint32_t pin_index = get_pin_index(gpio); - GPIODirModeSet(SOC_GPIO_0_REGS, pin_index, GPIO_DIR_OUTPUT); + if (PBDRV_GPIO_EV3_ARM_OWNS_GPIO_BANK(pin_index)) { + GPIODirModeSet(SOC_GPIO_0_REGS, pin_index, GPIO_DIR_OUTPUT); + } else if (GPIODirModeGet(SOC_GPIO_0_REGS, pin_index) != GPIO_DIR_OUTPUT) { + uint32_t val = 1 << (pin_index - 1); + pbdrv_rproc_ev3_pru1_shared_ram.gpio_bank_01_dir_clr = val; + while (pbdrv_rproc_ev3_pru1_shared_ram.gpio_bank_01_dir_clr) { + // Wait for the PRU to process the command + } + } GPIOPinWrite(SOC_GPIO_0_REGS, pin_index, value); } @@ -55,7 +77,15 @@ uint8_t pbdrv_gpio_input(const pbdrv_gpio_t *gpio) { } pbdrv_gpio_alt_gpio(gpio); uint32_t pin_index = get_pin_index(gpio); - GPIODirModeSet(SOC_GPIO_0_REGS, pin_index, GPIO_DIR_INPUT); + if (PBDRV_GPIO_EV3_ARM_OWNS_GPIO_BANK(pin_index)) { + GPIODirModeSet(SOC_GPIO_0_REGS, pin_index, GPIO_DIR_INPUT); + } else if (GPIODirModeGet(SOC_GPIO_0_REGS, pin_index) != GPIO_DIR_INPUT) { + uint32_t val = 1 << (pin_index - 1); + pbdrv_rproc_ev3_pru1_shared_ram.gpio_bank_01_dir_set = val; + while (pbdrv_rproc_ev3_pru1_shared_ram.gpio_bank_01_dir_set) { + // Wait for the PRU to process the command + } + } return GPIOPinRead(SOC_GPIO_0_REGS, pin_index) == GPIO_PIN_HIGH; } diff --git a/lib/pbio/drv/rproc/rproc_ev3_pru1.h b/lib/pbio/drv/rproc/rproc_ev3_pru1.h index 4402c0d82..1f0c93c62 100644 --- a/lib/pbio/drv/rproc/rproc_ev3_pru1.h +++ b/lib/pbio/drv/rproc/rproc_ev3_pru1.h @@ -19,6 +19,12 @@ typedef struct { // PWM duty cycle for each channel, range [0-255] uint8_t pwm_duty_cycle[PBDRV_RPROC_EV3_PRU1_NUM_PWM_CHANNELS]; }; + // Because the PRU needs to manipulate the GPIO direction + // for doing I2C, and because these registers do *not* + // support atomic bit access, we give the PRU full ownership + // of them and route ARM accesses through the PRU. + uint32_t gpio_bank_01_dir_set; + uint32_t gpio_bank_01_dir_clr; } pbdrv_rproc_ev3_pru1_shared_ram_t; #endif // _INTERNAL_PBDRV_RPROC_EV3_PRU1_H_ From e137d53a64ca8abac15aa4f5950c495c31ab9478 Mon Sep 17 00:00:00 2001 From: R Date: Wed, 6 Aug 2025 05:02:43 +0100 Subject: [PATCH 05/10] pbio/drv/rproc/rproc_ev3: Add I2C interface for PRU1 This defines the communications structure and sets up the resources for PRU1's software I2C controller. --- lib/pbio/drv/i2c/i2c_ev3.c | 8 +++--- lib/pbio/drv/i2c/i2c_ev3.h | 2 +- lib/pbio/drv/rproc/rproc_ev3.c | 6 +++++ lib/pbio/drv/rproc/rproc_ev3_pru1.h | 40 +++++++++++++++++++++++++++++ lib/pbio/platform/ev3/pbdrvconfig.h | 1 - 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/lib/pbio/drv/i2c/i2c_ev3.c b/lib/pbio/drv/i2c/i2c_ev3.c index 9a8648abe..3b72593ac 100644 --- a/lib/pbio/drv/i2c/i2c_ev3.c +++ b/lib/pbio/drv/i2c/i2c_ev3.c @@ -18,6 +18,8 @@ #include #include "i2c_ev3.h" +#include "../rproc/rproc_ev3.h" + #define DEBUG 1 #if DEBUG #include @@ -38,10 +40,10 @@ struct _pbdrv_i2c_dev_t { // }; -static pbdrv_i2c_dev_t i2c_devs[PBDRV_CONFIG_I2C_EV3_NUM_DEV]; +static pbdrv_i2c_dev_t i2c_devs[PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES]; pbio_error_t pbdrv_i2c_get_instance(uint8_t id, pbdrv_i2c_dev_t **i2c_dev) { - if (id >= PBDRV_CONFIG_I2C_EV3_NUM_DEV) { + if (id >= PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES) { return PBIO_ERROR_INVALID_ARG; } pbdrv_i2c_dev_t *dev = &i2c_devs[id]; @@ -59,7 +61,7 @@ pbio_error_t pbdrv_i2c_placeholder_operation(pbdrv_i2c_dev_t *i2c_dev, const cha } void pbdrv_i2c_init(void) { - for (int i = 0; i < PBDRV_CONFIG_I2C_EV3_NUM_DEV; i++) { + for (int i = 0; i < PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES; i++) { const pbdrv_i2c_ev3_platform_data_t *pdata = &pbdrv_i2c_ev3_platform_data[i]; pbdrv_i2c_dev_t *i2c = &i2c_devs[i]; i2c->pdata = pdata; diff --git a/lib/pbio/drv/i2c/i2c_ev3.h b/lib/pbio/drv/i2c/i2c_ev3.h index b7b47dba8..5795407a3 100644 --- a/lib/pbio/drv/i2c/i2c_ev3.h +++ b/lib/pbio/drv/i2c/i2c_ev3.h @@ -22,6 +22,6 @@ typedef struct { * Array of I2C platform data to be defined in platform.c. */ extern const pbdrv_i2c_ev3_platform_data_t - pbdrv_i2c_ev3_platform_data[PBDRV_CONFIG_I2C_EV3_NUM_DEV]; + pbdrv_i2c_ev3_platform_data[1]; #endif // _INTERNAL_PBDRV_I2C_EV3_H_ diff --git a/lib/pbio/drv/rproc/rproc_ev3.c b/lib/pbio/drv/rproc/rproc_ev3.c index 79aa9d5fe..bbed910e0 100644 --- a/lib/pbio/drv/rproc/rproc_ev3.c +++ b/lib/pbio/drv/rproc/rproc_ev3.c @@ -45,6 +45,12 @@ void pbdrv_rproc_init(void) { TimerPeriodSet(SOC_TMR_0_REGS, TMR_TIMER34, 256 * 256 - 1); TimerEnable(SOC_TMR_0_REGS, TMR_TIMER34, TMR_ENABLE_CONT); + // Enable Timer2 "12" half for 20 kHz = 2 * 10 kHz + // This is used by the PRU to time I2C bits + TimerConfigure(SOC_TMR_2_REGS, TMR_CFG_32BIT_UNCH_CLK_BOTH_INT); + TimerPeriodSet(SOC_TMR_2_REGS, TMR_TIMER12, SOC_SYSCLK_2_FREQ / (2 * PBDRV_RPROC_EV3_PRU1_I2C_CLK_SPEED_HZ) - 1); + TimerEnable(SOC_TMR_2_REGS, TMR_TIMER12, TMR_ENABLE_CONT); + // Clear shared command memory memset((void *)&pbdrv_rproc_ev3_pru1_shared_ram, 0, sizeof(pbdrv_rproc_ev3_pru1_shared_ram)); diff --git a/lib/pbio/drv/rproc/rproc_ev3_pru1.h b/lib/pbio/drv/rproc/rproc_ev3_pru1.h index 1f0c93c62..b9b1e5fb3 100644 --- a/lib/pbio/drv/rproc/rproc_ev3_pru1.h +++ b/lib/pbio/drv/rproc/rproc_ev3_pru1.h @@ -11,6 +11,45 @@ // between the two codebases and must be kept in sync. #define PBDRV_RPROC_EV3_PRU1_NUM_PWM_CHANNELS 4 +#define PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES 4 + +#define PBDRV_RPROC_EV3_PRU1_I2C_CLK_SPEED_HZ 10000 + +// I2C command bits, valid when bit7 == 0 +// Start an I2C transaction +#define PBDRV_RPROC_EV3_PRU1_I2C_CMD_START (1 << 0) +// Generate a stop, clock pulse, and start instead of a repeated start +// Used for the NXT ultrasonic sensor. +#define PBDRV_RPROC_EV3_PRU1_I2C_CMD_NXT_QUIRK (1 << 1) + +// I2C status bits, valid when bit7 == 1 +// Indicates transaction is complete +#define PBDRV_RPROC_EV3_PRU1_I2C_STAT_DONE (1 << 7) +// Mask for the status code +#define PBDRV_RPROC_EV3_PRU1_I2C_STAT_MASK 0x7f + +// I2C transaction status codes +enum { + PBDRV_RPROC_EV3_PRU1_I2C_STAT_OK, + PBDRV_RPROC_EV3_PRU1_I2C_STAT_TIMEOUT, + PBDRV_RPROC_EV3_PRU1_I2C_STAT_NAK, +}; + +#define PBDRV_RPROC_EV3_PRU1_I2C_PACK_FLAGS(daddr, rlen, wlen, flags) \ + ((((daddr) & 0xff) << 24) | \ + (((rlen) & 0xff) << 16) | \ + (((wlen) & 0xff) << 8) | \ + ((flags) & 0xff)) + +typedef struct { + // bit[7:0] status or flags + // bit[15:8] write length + // bit[23:16] read length + // bit[31:24] device address (unshifted) + uint32_t flags; + // Physical address of a transaction buffer + uintptr_t buffer; +} pbdrv_rproc_ev3_pru1_i2c_command_t; typedef struct { union { @@ -25,6 +64,7 @@ typedef struct { // of them and route ARM accesses through the PRU. uint32_t gpio_bank_01_dir_set; uint32_t gpio_bank_01_dir_clr; + pbdrv_rproc_ev3_pru1_i2c_command_t i2c[PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES]; } pbdrv_rproc_ev3_pru1_shared_ram_t; #endif // _INTERNAL_PBDRV_RPROC_EV3_PRU1_H_ diff --git a/lib/pbio/platform/ev3/pbdrvconfig.h b/lib/pbio/platform/ev3/pbdrvconfig.h index ce160970b..c1c5aea14 100644 --- a/lib/pbio/platform/ev3/pbdrvconfig.h +++ b/lib/pbio/platform/ev3/pbdrvconfig.h @@ -41,7 +41,6 @@ #define PBDRV_CONFIG_I2C (1) #define PBDRV_CONFIG_I2C_EV3 (1) -#define PBDRV_CONFIG_I2C_EV3_NUM_DEV (4) #define PBDRV_CONFIG_BUTTON (1) #define PBDRV_CONFIG_BUTTON_GPIO (1) From 917975180f142db7c405d1887d083e0a5bdc63d1 Mon Sep 17 00:00:00 2001 From: R Date: Wed, 6 Aug 2025 06:24:15 +0100 Subject: [PATCH 06/10] pbio/drv/i2c/i2c_ev3.c: Actually initialize I2C This calls the I2C init function from core.c and removes the platform data which is unneeded with how we're using the PRU. --- lib/pbio/drv/core.c | 2 ++ lib/pbio/drv/i2c/i2c_ev3.c | 14 +++++--------- lib/pbio/drv/i2c/i2c_ev3.h | 27 --------------------------- 3 files changed, 7 insertions(+), 36 deletions(-) delete mode 100644 lib/pbio/drv/i2c/i2c_ev3.h diff --git a/lib/pbio/drv/core.c b/lib/pbio/drv/core.c index 69dfee4a9..b7cf834ef 100644 --- a/lib/pbio/drv/core.c +++ b/lib/pbio/drv/core.c @@ -18,6 +18,7 @@ #include "clock/clock.h" #include "counter/counter.h" #include "display/display.h" +#include "i2c/i2c.h" #include "imu/imu.h" #include "led/led_array.h" #include "led/led.h" @@ -49,6 +50,7 @@ void pbdrv_init(void) { pbdrv_charger_init(); pbdrv_counter_init(); pbdrv_display_init(); + pbdrv_i2c_init(); pbdrv_imu_init(); pbdrv_led_array_init(); pbdrv_led_init(); diff --git a/lib/pbio/drv/i2c/i2c_ev3.c b/lib/pbio/drv/i2c/i2c_ev3.c index 3b72593ac..43c97ed64 100644 --- a/lib/pbio/drv/i2c/i2c_ev3.c +++ b/lib/pbio/drv/i2c/i2c_ev3.c @@ -16,7 +16,6 @@ #include #include -#include "i2c_ev3.h" #include "../rproc/rproc_ev3.h" @@ -33,11 +32,8 @@ #endif struct _pbdrv_i2c_dev_t { - /** Platform-specific data */ - const pbdrv_i2c_ev3_platform_data_t *pdata; - // - // TODO: i2c state goes here. - // + bool is_initialized; + uint8_t pru_i2c_idx; }; static pbdrv_i2c_dev_t i2c_devs[PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES]; @@ -47,7 +43,7 @@ pbio_error_t pbdrv_i2c_get_instance(uint8_t id, pbdrv_i2c_dev_t **i2c_dev) { return PBIO_ERROR_INVALID_ARG; } pbdrv_i2c_dev_t *dev = &i2c_devs[id]; - if (!dev->pdata) { + if (!dev->is_initialized) { // has not been initialized yet return PBIO_ERROR_AGAIN; } @@ -62,9 +58,9 @@ pbio_error_t pbdrv_i2c_placeholder_operation(pbdrv_i2c_dev_t *i2c_dev, const cha void pbdrv_i2c_init(void) { for (int i = 0; i < PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES; i++) { - const pbdrv_i2c_ev3_platform_data_t *pdata = &pbdrv_i2c_ev3_platform_data[i]; pbdrv_i2c_dev_t *i2c = &i2c_devs[i]; - i2c->pdata = pdata; + i2c->pru_i2c_idx = i; + i2c->is_initialized = true; } } diff --git a/lib/pbio/drv/i2c/i2c_ev3.h b/lib/pbio/drv/i2c/i2c_ev3.h deleted file mode 100644 index 5795407a3..000000000 --- a/lib/pbio/drv/i2c/i2c_ev3.h +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2025 The Pybricks Authors - -#ifndef _INTERNAL_PBDRV_I2C_EV3_H_ -#define _INTERNAL_PBDRV_I2C_EV3_H_ - -#include - -#include "pbdrvconfig.h" - -#include - -/** Platform-specific information for I2C peripheral. */ -typedef struct { - /** GPIO pin to provide software clock. */ - pbdrv_gpio_t clk; - /** GPIO pin to provide software data. */ - pbdrv_gpio_t sda; -} pbdrv_i2c_ev3_platform_data_t; - -/** - * Array of I2C platform data to be defined in platform.c. - */ -extern const pbdrv_i2c_ev3_platform_data_t - pbdrv_i2c_ev3_platform_data[1]; - -#endif // _INTERNAL_PBDRV_I2C_EV3_H_ From efcf8b590ba7a40eb0731f7bb7241624abce42c6 Mon Sep 17 00:00:00 2001 From: R Date: Wed, 6 Aug 2025 15:59:29 +0100 Subject: [PATCH 07/10] pbio/drv/i2c/i2c_ev3.c: Add interrupt handlers The PRU uses these events to inform the ARM of completion (both success and failure). --- lib/pbio/drv/i2c/i2c_ev3.c | 80 +++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/lib/pbio/drv/i2c/i2c_ev3.c b/lib/pbio/drv/i2c/i2c_ev3.c index 43c97ed64..5b2dcc18f 100644 --- a/lib/pbio/drv/i2c/i2c_ev3.c +++ b/lib/pbio/drv/i2c/i2c_ev3.c @@ -10,13 +10,20 @@ #include #include #include +#include -#include +#include +#include +#include + +#include #include +#include #include #include +#include "../drv/rproc/rproc.h" #include "../rproc/rproc_ev3.h" #define DEBUG 1 @@ -32,12 +39,20 @@ #endif struct _pbdrv_i2c_dev_t { + volatile bool is_busy; bool is_initialized; uint8_t pru_i2c_idx; }; static pbdrv_i2c_dev_t i2c_devs[PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES]; +enum { + PRU_I2C_PORT1_EVT = 42, + PRU_I2C_PORT2_EVT = 44, + PRU_I2C_PORT3_EVT = 46, + PRU_I2C_PORT4_EVT = 48, +}; + pbio_error_t pbdrv_i2c_get_instance(uint8_t id, pbdrv_i2c_dev_t **i2c_dev) { if (id >= PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES) { return PBIO_ERROR_INVALID_ARG; @@ -51,17 +66,80 @@ pbio_error_t pbdrv_i2c_get_instance(uint8_t id, pbdrv_i2c_dev_t **i2c_dev) { return PBIO_SUCCESS; } +static void pbdrv_i2c_irq_0(void) { + IntSystemStatusClear(SYS_INT_EVTOUT4); + HWREG(INTC_PHYS_BASE + PRU_INTC_SICR_REG) = PRU_I2C_PORT1_EVT; + i2c_devs[0].is_busy = false; + pbio_os_request_poll(); +} +static void pbdrv_i2c_irq_1(void) { + IntSystemStatusClear(SYS_INT_EVTOUT5); + HWREG(INTC_PHYS_BASE + PRU_INTC_SICR_REG) = PRU_I2C_PORT2_EVT; + i2c_devs[1].is_busy = false; + pbio_os_request_poll(); +} +static void pbdrv_i2c_irq_2(void) { + IntSystemStatusClear(SYS_INT_EVTOUT6); + HWREG(INTC_PHYS_BASE + PRU_INTC_SICR_REG) = PRU_I2C_PORT3_EVT; + i2c_devs[2].is_busy = false; + pbio_os_request_poll(); +} +static void pbdrv_i2c_irq_3(void) { + IntSystemStatusClear(SYS_INT_EVTOUT7); + HWREG(INTC_PHYS_BASE + PRU_INTC_SICR_REG) = PRU_I2C_PORT4_EVT; + i2c_devs[3].is_busy = false; + pbio_os_request_poll(); +} + pbio_error_t pbdrv_i2c_placeholder_operation(pbdrv_i2c_dev_t *i2c_dev, const char *operation) { debug_pr("I2C placeholder operation %s\n", operation); return PBIO_SUCCESS; } +static pbio_os_process_t ev3_i2c_init_process; + +pbio_error_t ev3_i2c_init_process_thread(pbio_os_state_t *state, void *context) { + PBIO_OS_ASYNC_BEGIN(state); + + // Need rproc to be initialized, because it sets up the PRU INTC + PBIO_OS_AWAIT_UNTIL(state, pbdrv_rproc_is_ready()); + + // REVISIT: These event numbers get set up by the SUART library. + // We should separate them cleanly in the future. + IntRegister(SYS_INT_EVTOUT4, pbdrv_i2c_irq_0); + IntChannelSet(SYS_INT_EVTOUT4, 2); + IntSystemEnable(SYS_INT_EVTOUT4); + HWREG(INTC_PHYS_BASE + PRU_INTC_EISR_REG) = PRU_I2C_PORT1_EVT; + + IntRegister(SYS_INT_EVTOUT5, pbdrv_i2c_irq_1); + IntChannelSet(SYS_INT_EVTOUT5, 2); + IntSystemEnable(SYS_INT_EVTOUT5); + HWREG(INTC_PHYS_BASE + PRU_INTC_EISR_REG) = PRU_I2C_PORT2_EVT; + + IntRegister(SYS_INT_EVTOUT6, pbdrv_i2c_irq_2); + IntChannelSet(SYS_INT_EVTOUT6, 2); + IntSystemEnable(SYS_INT_EVTOUT6); + HWREG(INTC_PHYS_BASE + PRU_INTC_EISR_REG) = PRU_I2C_PORT3_EVT; + + IntRegister(SYS_INT_EVTOUT7, pbdrv_i2c_irq_3); + IntChannelSet(SYS_INT_EVTOUT7, 2); + IntSystemEnable(SYS_INT_EVTOUT7); + HWREG(INTC_PHYS_BASE + PRU_INTC_EISR_REG) = PRU_I2C_PORT4_EVT; + + pbio_busy_count_down(); + + PBIO_OS_ASYNC_END(PBIO_SUCCESS); +} + void pbdrv_i2c_init(void) { for (int i = 0; i < PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES; i++) { pbdrv_i2c_dev_t *i2c = &i2c_devs[i]; i2c->pru_i2c_idx = i; i2c->is_initialized = true; } + + pbio_busy_count_up(); + pbio_os_process_start(&ev3_i2c_init_process, ev3_i2c_init_process_thread, NULL); } #endif // PBDRV_CONFIG_I2C_EV3 From dc37fb6e913162a29cca88692580c03b91c62269 Mon Sep 17 00:00:00 2001 From: R Date: Wed, 6 Aug 2025 16:47:30 +0100 Subject: [PATCH 08/10] pbio/drv/i2c/i2c_ev3.c: Set up transaction buffers These buffers are sized for a maximum-length transaction and are DMA-aligned. This API currently does not / will not support zero-copy operation, and user buffers will be copied to/from these DMA buffers. --- lib/pbio/drv/i2c/i2c_ev3.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/pbio/drv/i2c/i2c_ev3.c b/lib/pbio/drv/i2c/i2c_ev3.c index 5b2dcc18f..eec4a9190 100644 --- a/lib/pbio/drv/i2c/i2c_ev3.c +++ b/lib/pbio/drv/i2c/i2c_ev3.c @@ -21,6 +21,7 @@ #include #include +#include #include #include "../drv/rproc/rproc.h" @@ -38,7 +39,14 @@ #define DBG_ERR(expr) #endif +// Max 255 bytes write, 255 bytes read +// Rounded up to a nice power of 2 and multiple of cache lines +#define PRU_I2C_MAX_BYTES_PER_TXN 512 + +static uint8_t pbdrv_i2c_buffers[PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES][PRU_I2C_MAX_BYTES_PER_TXN] PBDRV_DMA_BUF; + struct _pbdrv_i2c_dev_t { + uint8_t *buffer; volatile bool is_busy; bool is_initialized; uint8_t pru_i2c_idx; @@ -104,6 +112,12 @@ pbio_error_t ev3_i2c_init_process_thread(pbio_os_state_t *state, void *context) // Need rproc to be initialized, because it sets up the PRU INTC PBIO_OS_AWAIT_UNTIL(state, pbdrv_rproc_is_ready()); + // Set up the buffer pointers + for (int i = 0; i < PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES; i++) { + pbdrv_i2c_dev_t *i2c = &i2c_devs[i]; + pbdrv_rproc_ev3_pru1_shared_ram.i2c[i].buffer = (uintptr_t)i2c->buffer; + } + // REVISIT: These event numbers get set up by the SUART library. // We should separate them cleanly in the future. IntRegister(SYS_INT_EVTOUT4, pbdrv_i2c_irq_0); @@ -135,6 +149,7 @@ void pbdrv_i2c_init(void) { for (int i = 0; i < PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES; i++) { pbdrv_i2c_dev_t *i2c = &i2c_devs[i]; i2c->pru_i2c_idx = i; + i2c->buffer = pbdrv_i2c_buffers[i]; i2c->is_initialized = true; } From bb2f7166b7fc3531cf362ea0cd999d23ecd8ae65 Mon Sep 17 00:00:00 2001 From: R Date: Wed, 6 Aug 2025 22:07:27 +0100 Subject: [PATCH 09/10] pbio/drv/i2c/i2c_ev3.c: Implement I2C operation This implements performing an I2C operation asynchronously. It does not hook this up to any higher-level functionality. --- lib/pbio/drv/i2c/i2c_ev3.c | 72 ++++++++++++++++++++++++++++++++++-- lib/pbio/include/pbdrv/i2c.h | 50 +++++++++++++++++++------ 2 files changed, 108 insertions(+), 14 deletions(-) diff --git a/lib/pbio/drv/i2c/i2c_ev3.c b/lib/pbio/drv/i2c/i2c_ev3.c index eec4a9190..409a65d70 100644 --- a/lib/pbio/drv/i2c/i2c_ev3.c +++ b/lib/pbio/drv/i2c/i2c_ev3.c @@ -99,9 +99,75 @@ static void pbdrv_i2c_irq_3(void) { pbio_os_request_poll(); } -pbio_error_t pbdrv_i2c_placeholder_operation(pbdrv_i2c_dev_t *i2c_dev, const char *operation) { - debug_pr("I2C placeholder operation %s\n", operation); - return PBIO_SUCCESS; +pbio_error_t pbdrv_i2c_write_then_read( + pbio_os_state_t *state, + pbdrv_i2c_dev_t *i2c_dev, + uint8_t dev_addr, + const uint8_t *wdata, + size_t wlen, + uint8_t *rdata, + size_t rlen, + bool nxt_quirk) { + + PBIO_OS_ASYNC_BEGIN(state); + + if (wlen && !wdata) { + return PBIO_ERROR_INVALID_ARG; + } + if (rlen && !rdata) { + return PBIO_ERROR_INVALID_ARG; + } + if (wlen > 0xff || rlen > 0xff) { + return PBIO_ERROR_INVALID_ARG; + } + + if (i2c_dev->is_busy) { + return PBIO_ERROR_BUSY; + } + + // Prepare TX data + if (wlen) { + memcpy(i2c_dev->buffer, wdata, wlen); + } + i2c_dev->is_busy = true; + pbdrv_cache_prepare_before_dma(i2c_dev->buffer, PRU_I2C_MAX_BYTES_PER_TXN); + + // Kick off transfer + pbdrv_rproc_ev3_pru1_shared_ram.i2c[i2c_dev->pru_i2c_idx].flags = PBDRV_RPROC_EV3_PRU1_I2C_PACK_FLAGS( + dev_addr, + rlen, + wlen, + PBDRV_RPROC_EV3_PRU1_I2C_CMD_START | (nxt_quirk ? PBDRV_RPROC_EV3_PRU1_I2C_CMD_NXT_QUIRK : 0) + ); + + // Wait for transfer to finish + PBIO_OS_AWAIT_WHILE(state, i2c_dev->is_busy); + + uint32_t flags = pbdrv_rproc_ev3_pru1_shared_ram.i2c[i2c_dev->pru_i2c_idx].flags; + debug_pr("i2c %d done flags %08x\r\n", i2c_dev->pru_i2c_idx, flags); + if (!(flags & PBDRV_RPROC_EV3_PRU1_I2C_STAT_DONE)) { + debug_pr("i2c %d not actually done???\r\n", i2c_dev->pru_i2c_idx); + return PBIO_ERROR_FAILED; + } + switch (flags & PBDRV_RPROC_EV3_PRU1_I2C_STAT_MASK) { + case PBDRV_RPROC_EV3_PRU1_I2C_STAT_OK: + break; + case PBDRV_RPROC_EV3_PRU1_I2C_STAT_TIMEOUT: + return PBIO_ERROR_TIMEDOUT; + case PBDRV_RPROC_EV3_PRU1_I2C_STAT_NAK: + return PBIO_ERROR_IO; + default: + debug_pr("i2c %d unknown error occurred???\r\n", i2c_dev->pru_i2c_idx); + return PBIO_ERROR_FAILED; + } + + // If we got here, there's no error. Copy RX data. + pbdrv_cache_prepare_after_dma(i2c_dev->buffer, PRU_I2C_MAX_BYTES_PER_TXN); + if (rlen) { + memcpy(rdata, &i2c_dev->buffer[wlen], rlen); + } + + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } static pbio_os_process_t ev3_i2c_init_process; diff --git a/lib/pbio/include/pbdrv/i2c.h b/lib/pbio/include/pbdrv/i2c.h index b9d478704..c14a93267 100644 --- a/lib/pbio/include/pbdrv/i2c.h +++ b/lib/pbio/include/pbdrv/i2c.h @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -30,18 +31,37 @@ typedef struct _pbdrv_i2c_dev_t pbdrv_i2c_dev_t; pbio_error_t pbdrv_i2c_get_instance(uint8_t id, pbdrv_i2c_dev_t **i2c_dev); /** - * Does an I2C operation. To be replaced. + * Does an I2C operation. To be integrated with higher-level code. * - * Doesn't do anything yet. This is a placeholder so someone knowledgable with - * EV3 I2C has a starting point without setting up all the boilerplate. - * - * @param [in] i2c_dev The I2C device. - * @param [in] operation Dummy parameter. - * @return ::PBIO_SUCCESS on success. + * @param [in] state Protothread state for async operation. + * @param [in] i2c_dev The I2C device. + * @param [in] dev_addr I2C device address (unshifted). + * @param [in] wdata Data to be sent to the device. + * Can be null if \p wlen is 0. + * @param [in] wlen Length of \p wdata. + * If this is 0 and \p rlen is also 0, + * an empty "ping" will be sent, consisting of + * only a device address (with R/nW = 0). + * If this is 0 but \p rlen is not 0, + * a read transaction will be sent. + * If this is not 0 and \p rlen is not 0, + * a write will be sent followed by a read in + * a single transaction. + * @param [out] rdata Buffer for data read from the device. + * Can be null if \p rlen is 0. + * @param [in] rlen Size of \p rdata. + * @param [in] nxt_quirk Whether to use NXT I2C transaction quirk. + * @return ::PBIO_SUCCESS on success. */ -pbio_error_t pbdrv_i2c_placeholder_operation(pbdrv_i2c_dev_t *i2c_dev, const char *operation); - -// Delete above and add read/write functions here. See I2C driver for examples, also protothreads. +pbio_error_t pbdrv_i2c_write_then_read( + pbio_os_state_t *state, + pbdrv_i2c_dev_t *i2c_dev, + uint8_t dev_addr, + const uint8_t *wdata, + size_t wlen, + uint8_t *rdata, + size_t rlen, + bool nxt_quirk); #else // PBDRV_CONFIG_I2C @@ -50,7 +70,15 @@ static inline pbio_error_t pbdrv_i2c_get_instance(uint8_t id, pbdrv_i2c_dev_t ** return PBIO_ERROR_NOT_SUPPORTED; } -static inline pbio_error_t pbdrv_i2c_placeholder_operation(pbdrv_i2c_dev_t *i2c_dev, const char *operation) { +static inline pbio_error_t pbdrv_i2c_write_then_read( + pbio_os_state_t *state, + pbdrv_i2c_dev_t *i2c_dev, + uint8_t dev_addr, + const uint8_t *wdata, + size_t wlen, + uint8_t *rdata, + size_t rlen, + bool nxt_quirk) { return PBIO_ERROR_NOT_SUPPORTED; } From 7dc9c3d349b348cf1590d86c685400efbe0311a0 Mon Sep 17 00:00:00 2001 From: R Date: Thu, 7 Aug 2025 16:52:06 +0100 Subject: [PATCH 10/10] pbio/drv/gpio/gpio_ev3.c: Rename config TIAM1808 -> EV3 The only platform we have with this SoC is the EV3, so rename this configuration parameter to match. --- lib/pbio/drv/gpio/gpio_ev3.c | 4 ++-- lib/pbio/platform/ev3/pbdrvconfig.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pbio/drv/gpio/gpio_ev3.c b/lib/pbio/drv/gpio/gpio_ev3.c index d2c98e962..590022f9c 100644 --- a/lib/pbio/drv/gpio/gpio_ev3.c +++ b/lib/pbio/drv/gpio/gpio_ev3.c @@ -3,7 +3,7 @@ #include "pbdrv/config.h" -#if PBDRV_CONFIG_GPIO_TIAM1808 +#if PBDRV_CONFIG_GPIO_EV3 #include @@ -103,4 +103,4 @@ void pbdrv_gpio_set_pull(const pbdrv_gpio_t *gpio, pbdrv_gpio_pull_t pull) { // Not implemented for TI AM1808 since EV3 does not use software pull-up/pull-down. } -#endif // PBDRV_CONFIG_GPIO_TIAM1808 +#endif // PBDRV_CONFIG_GPIO_EV3 diff --git a/lib/pbio/platform/ev3/pbdrvconfig.h b/lib/pbio/platform/ev3/pbdrvconfig.h index c1c5aea14..7f523a53a 100644 --- a/lib/pbio/platform/ev3/pbdrvconfig.h +++ b/lib/pbio/platform/ev3/pbdrvconfig.h @@ -52,7 +52,7 @@ #define PBDRV_CONFIG_DISPLAY_NUM_ROWS (128) #define PBDRV_CONFIG_GPIO (1) -#define PBDRV_CONFIG_GPIO_TIAM1808 (1) +#define PBDRV_CONFIG_GPIO_EV3 (1) #define PBDRV_CONFIG_HAS_PORT_A (1) #define PBDRV_CONFIG_HAS_PORT_B (1)