diff --git a/components/badge23/captouch.c b/components/badge23/captouch.c
index ae3da7c263b7a590ed38f37f57984b6e7c34fb7f..ffff6f214aedecb26e633f07c0f1140ea6eb7af0 100644
--- a/components/badge23/captouch.c
+++ b/components/badge23/captouch.c
@@ -4,6 +4,7 @@
 #include "esp_log.h"
 
 #include "flow3r_bsp_captouch.h"
+#include "sdkconfig.h"
 
 #include "freertos/FreeRTOS.h"
 #include "freertos/semphr.h"
@@ -77,7 +78,11 @@ typedef struct {
 } st3m_captouch_petal_pad_t;
 
 typedef struct {
+#if defined(CONFIG_FLOW3R_HW_GEN_P3)
+    st3m_captouch_petal_pad_t tip;
+#else
     st3m_captouch_petal_pad_t base;
+#endif
     st3m_captouch_petal_pad_t cw;
     st3m_captouch_petal_pad_t ccw;
     bool pressed;
@@ -112,12 +117,20 @@ static void _on_data(const flow3r_bsp_captouch_state_t *st) {
     xSemaphoreTake(_mu, portMAX_DELAY);
     memcpy(&_state, st, sizeof(flow3r_bsp_captouch_state_t));
     for (size_t i = 0; i < 5; i++) {
+#if defined(CONFIG_FLOW3R_HW_GEN_P3)
+        _pad_feed(&_state.top[i].tip, _state.raw.petals[i * 2].tip.raw, true);
+#else
         _pad_feed(&_state.top[i].base, _state.raw.petals[i * 2].base.raw, true);
+#endif
         _pad_feed(&_state.top[i].cw, _state.raw.petals[i * 2].cw.raw, true);
         _pad_feed(&_state.top[i].ccw, _state.raw.petals[i * 2].ccw.raw, true);
-        _state.top[i].pressed = _state.top[i].base.pressed ||
-                                _state.top[i].cw.pressed ||
-                                _state.top[i].ccw.pressed;
+        _state.top[i].pressed =
+#if defined(CONFIG_FLOW3R_HW_GEN_P3)
+            _state.top[i].tip.pressed ||
+#else
+            _state.top[i].base.pressed ||
+#endif
+            _state.top[i].cw.pressed || _state.top[i].ccw.pressed;
     }
     for (size_t i = 0; i < 5; i++) {
         _pad_feed(&_state.bottom[i].base, _state.raw.petals[i * 2 + 1].base.raw,
@@ -176,10 +189,18 @@ void read_captouch_ex(captouch_state_t *state) {
     memset(state, 0, sizeof(captouch_state_t));
     xSemaphoreTake(_mu, portMAX_DELAY);
     for (size_t i = 0; i < 5; i++) {
+#if defined(CONFIG_FLOW3R_HW_GEN_P3)
+        bool base = _state.top[i].tip.pressed;
+#else
         bool base = _state.top[i].base.pressed;
+#endif
         bool cw = _state.top[i].cw.pressed;
         bool ccw = _state.top[i].ccw.pressed;
+#if defined(CONFIG_FLOW3R_HW_GEN_P3)
+        state->petals[i * 2].pads.tip_pressed = base;
+#else
         state->petals[i * 2].pads.base_pressed = base;
+#endif
         state->petals[i * 2].pads.cw_pressed = cw;
         state->petals[i * 2].pads.ccw_pressed = ccw;
         state->petals[i * 2].pressed = base || cw || ccw;
@@ -244,6 +265,15 @@ int32_t captouch_get_petal_phi(uint8_t petal) {
 int32_t captouch_get_petal_rad(uint8_t petal) {
     bool top = (petal % 2) == 0;
     if (top) {
+#if defined(CONFIG_FLOW3R_HW_GEN_P3)
+        size_t ix = petal / 2;
+        xSemaphoreTake(_mu, portMAX_DELAY);
+        int32_t left = ringbuffer_avg(&_state.top[ix].ccw.rb);
+        int32_t right = ringbuffer_avg(&_state.top[ix].cw.rb);
+        int32_t tip = ringbuffer_avg(&_state.top[ix].tip.rb);
+        xSemaphoreGive(_mu);
+        return tip - (left + right) / 2;
+#else
         size_t ix = petal / 2;
         xSemaphoreTake(_mu, portMAX_DELAY);
         int32_t left = ringbuffer_avg(&_state.top[ix].ccw.rb);
@@ -251,6 +281,7 @@ int32_t captouch_get_petal_rad(uint8_t petal) {
         int32_t base = ringbuffer_avg(&_state.top[ix].base.rb);
         xSemaphoreGive(_mu);
         return (left + right) / 2 - base;
+#endif
     } else {
         size_t ix = (petal - 1) / 2;
         xSemaphoreTake(_mu, portMAX_DELAY);
diff --git a/components/flow3r_bsp/flow3r_bsp_captouch.c b/components/flow3r_bsp/flow3r_bsp_captouch.c
index 1246bebbb27d5766c8136caead1d2af024b76797..146b66e84c6f7983e4d8f5a9fac03f0913158506 100644
--- a/components/flow3r_bsp/flow3r_bsp_captouch.c
+++ b/components/flow3r_bsp/flow3r_bsp_captouch.c
@@ -17,6 +17,45 @@ typedef struct {
     petal_kind_t pad_kind;
 } pad_mapping_t;
 
+#if defined(CONFIG_FLOW3R_HW_GEN_P3)
+static const pad_mapping_t _map_top[12] = {
+    { 0, petal_pad_tip },  // 0
+    { 0, petal_pad_ccw },  // 1
+    { 0, petal_pad_cw },   // 2
+    { 2, petal_pad_cw },   // 3
+    { 2, petal_pad_ccw },  // 4
+    { 2, petal_pad_tip },  // 5
+    { 6, petal_pad_tip },  // 6
+    { 6, petal_pad_ccw },  // 7
+    { 6, petal_pad_cw },   // 8
+    { 4, petal_pad_cw },   // 9
+    { 4, petal_pad_ccw },  // 10
+    { 4, petal_pad_tip },  // 11
+};
+static const pad_mapping_t _map_bot[13] = {
+    { 1, petal_pad_base },  // 0
+    { 1, petal_pad_tip },   // 1
+
+    { 3, petal_pad_base },  // 2
+    { 3, petal_pad_tip },   // 3
+
+    { 5, petal_pad_base },  // 4
+    { 5, petal_pad_tip },   // 5
+
+    { 7, petal_pad_tip },   // 6
+    { 7, petal_pad_base },  // 7
+
+    { 9, petal_pad_tip },   // 8
+    { 9, petal_pad_base },  // 9
+
+    { 8, petal_pad_tip },  // 10
+    { 8, petal_pad_cw },   // 11
+    { 8, petal_pad_ccw },  // 12
+};
+static gpio_num_t _interrupt_gpio_top = GPIO_NUM_15;
+static gpio_num_t _interrupt_gpio_bot = GPIO_NUM_15;
+static bool _interrupt_shared = true;
+#elif defined(CONFIG_FLOW3R_HW_GEN_P4) || defined(CONFIG_FLOW3R_HW_GEN_P6)
 static const pad_mapping_t _map_top[12] = {
     { 0, petal_pad_ccw },   // 0
     { 0, petal_pad_base },  // 1
@@ -31,21 +70,6 @@ static const pad_mapping_t _map_top[12] = {
     { 4, petal_pad_base },  // 10
     { 4, petal_pad_cw },    // 11
 };
-
-static ad7147_chip_t _top = {
-    .name = "top",
-    .nchannels = 12,
-    .sequences = {
-        {
-            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
-        },
-        {
-            -1, -1, -1, -1, -1, -1,
-            -1, -1, -1, -1, -1, -1,
-        },
-    },
-};
-
 static const pad_mapping_t _map_bot[13] = {
     { 1, petal_pad_base },  // 0
     { 1, petal_pad_tip },   // 1
@@ -66,6 +90,33 @@ static const pad_mapping_t _map_bot[13] = {
     { 8, petal_pad_cw },    // 11
     { 8, petal_pad_base },  // 12
 };
+#if defined(CONFIG_FLOW3R_HW_GEN_P4)
+static gpio_num_t _interrupt_gpio_top = GPIO_NUM_15;
+static gpio_num_t _interrupt_gpio_bot = GPIO_NUM_15;
+static bool _interrupt_shared = true;
+#else
+static gpio_num_t _interrupt_gpio_top = GPIO_NUM_15;
+static gpio_num_t _interrupt_gpio_bot = GPIO_NUM_16;
+static bool _interrupt_shared = false;
+#endif
+
+#else
+#error "captouch not implemented for this badge generation"
+#endif
+
+static ad7147_chip_t _top = {
+    .name = "top",
+    .nchannels = 12,
+    .sequences = {
+        {
+            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+        },
+        {
+            -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1,
+        },
+    },
+};
 
 static ad7147_chip_t _bot = {
     .name = "bot",
@@ -109,9 +160,6 @@ static ad7147_chip_t _bot = {
     },
 };
 
-static gpio_num_t _interrupt_gpio_top = GPIO_NUM_15;
-static gpio_num_t _interrupt_gpio_bot = GPIO_NUM_16;
-
 static flow3r_bsp_captouch_callback_t _callback = NULL;
 
 static flow3r_bsp_captouch_state_t _state = {};
@@ -168,6 +216,14 @@ static void _task(void *data) {
             ESP_LOGE(TAG, "Queue receive failed");
             return;
         }
+        bool top = !bot;
+
+        if (_interrupt_shared) {
+            // No way to know which captouch chip triggered the interrupt, so
+            // process both.
+            top = true;
+            bot = true;
+        }
 
         if (bot) {
             if ((ret = flow3r_bsp_ad7147_chip_process(&_bot)) != ESP_OK) {
@@ -181,7 +237,8 @@ static void _task(void *data) {
             } else {
                 bot_ok = true;
             }
-        } else {
+        }
+        if (top) {
             if ((ret = flow3r_bsp_ad7147_chip_process(&_top)) != ESP_OK) {
                 if (top_ok) {
                     ESP_LOGE(TAG,
@@ -265,10 +322,14 @@ esp_err_t flow3r_bsp_captouch_init(flow3r_bsp_captouch_callback_t callback) {
         ESP_LOGE(TAG, "Failed to add bottom captouch ISR");
         return ret;
     }
-    if ((ret = _gpio_interrupt_setup(_interrupt_gpio_top, _top_isr)) !=
-        ESP_OK) {
-        ESP_LOGE(TAG, "Failed to add top captouch ISR");
-        return ret;
+    if (!_interrupt_shared) {
+        // On badges with shared interrupts, only install the 'bot' ISR as a
+        // shared ISR.
+        if ((ret = _gpio_interrupt_setup(_interrupt_gpio_top, _top_isr)) !=
+            ESP_OK) {
+            ESP_LOGE(TAG, "Failed to add top captouch ISR");
+            return ret;
+        }
     }
 
     xTaskCreate(&_task, "captouch", 4096, NULL, configMAX_PRIORITIES - 1, NULL);