Skip to content
Merged
4 changes: 2 additions & 2 deletions bricks/_common/arm_none_eabi.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions lib/pbio/drv/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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();
Expand Down
38 changes: 34 additions & 4 deletions lib/pbio/drv/gpio/gpio_ev3.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#include "pbdrv/config.h"

#if PBDRV_CONFIG_GPIO_TIAM1808
#if PBDRV_CONFIG_GPIO_EV3

#include <stdint.h>

Expand All @@ -17,6 +17,9 @@
#include <tiam1808/gpio.h>
#include <tiam1808/psc.h>

#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;
Expand All @@ -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) {
Copy link
Member

@laurensvalk laurensvalk Aug 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this check go as the first if, so we don't re-set it for pins owned by the ARM either?

(I know it used to double-set it before as well, but now that we have the test we might as well cover both cases.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured that code path was cheap enough (a single register write) that it wasn't worth bothering

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);
}

Expand All @@ -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;
}

Expand All @@ -73,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
189 changes: 173 additions & 16 deletions lib/pbio/drv/i2c/i2c_ev3.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,22 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <pbdrv/gpio.h>
#include <tiam1808/armv5/am1808/interrupt.h>
#include <tiam1808/hw/hw_types.h>
#include <tiam1808/pruss.h>

#include <pbio/busy_count.h>
#include <pbio/error.h>
#include <pbio/os.h>
#include <pbio/util.h>

#include <pbdrv/cache.h>
#include <pbdrv/i2c.h>
#include "i2c_ev3.h"

#include "../drv/rproc/rproc.h"
#include "../rproc/rproc_ev3.h"

#define DEBUG 1
#if DEBUG
Expand All @@ -30,40 +39,188 @@
#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 {
/** Platform-specific data */
const pbdrv_i2c_ev3_platform_data_t *pdata;
//
// TODO: i2c state goes here.
//
uint8_t *buffer;
volatile bool is_busy;
bool is_initialized;
uint8_t pru_i2c_idx;
};

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];

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_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];
if (!dev->pdata) {
if (!dev->is_initialized) {
// has not been initialized yet
return PBIO_ERROR_AGAIN;
}
*i2c_dev = dev;
return PBIO_SUCCESS;
}

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 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_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;

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());

// 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);
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_CONFIG_I2C_EV3_NUM_DEV; i++) {
const pbdrv_i2c_ev3_platform_data_t *pdata = &pbdrv_i2c_ev3_platform_data[i];
for (int i = 0; i < PBDRV_RPROC_EV3_PRU1_NUM_I2C_BUSES; i++) {
pbdrv_i2c_dev_t *i2c = &i2c_devs[i];
i2c->pdata = pdata;
i2c->pru_i2c_idx = i;
i2c->buffer = pbdrv_i2c_buffers[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
27 changes: 0 additions & 27 deletions lib/pbio/drv/i2c/i2c_ev3.h

This file was deleted.

2 changes: 1 addition & 1 deletion lib/pbio/drv/pwm/pwm_ev3.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
11 changes: 11 additions & 0 deletions lib/pbio/drv/rproc/rproc_ev3.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <string.h>

#include <tiam1808/hw/hw_syscfg0_AM1808.h>
#include <tiam1808/hw/hw_types.h>
#include <tiam1808/hw/soc_AM1808.h>
#include <tiam1808/pruss.h>
#include <tiam1808/psc.h>
Expand Down Expand Up @@ -44,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));

Expand All @@ -56,6 +63,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);
Expand Down
Loading