diff --git a/components/badge23/captouch.c b/components/badge23/captouch.c index a2b32f423c0ca69f72c6236cb874eb89940dd81e..ae3da7c263b7a590ed38f37f57984b6e7c34fb7f 100644 --- a/components/badge23/captouch.c +++ b/components/badge23/captouch.c @@ -1,613 +1,262 @@ -#include "include/badge23/captouch.h" -#include <freertos/FreeRTOS.h> -#include <freertos/atomic.h> -#include <stdint.h> +#include "badge23/captouch.h" + +#include "esp_err.h" #include "esp_log.h" -#include "flow3r_bsp_i2c.h" - -#define PETAL_PAD_TIP 0 -#define PETAL_PAD_CCW 1 -#define PETAL_PAD_CW 2 -#define PETAL_PAD_BASE 3 - -#define CIN CDC_NONE 0 -#define CIN_CDC_NEG 1 -#define CIN_CDC_POS 2 -#define CIN_BIAS 3 - -#define AFE_INCR_CAP 1000 - -static const uint8_t top_map[] = { 0, 0, 0, 2, 2, 2, 6, 6, 6, 4, 4, 4 }; -static const uint8_t top_stages = 12; -static const uint8_t bot_map[] = { 1, 1, 3, 3, 5, 7, 7, 9, 9, 8, 8, 8 }; -static const uint8_t bot_stages = 12; -static const uint8_t bot_stage_config[] = { 0, 1, 2, 3, 5, 6, - 7, 8, 9, 10, 11, 12 }; -#define DEFAULT_THRES_TOP 8000 -#define DEFAULT_THRES_BOT 12000 - -#if defined(CONFIG_FLOW3R_HW_GEN_P4) -static const uint8_t top_segment_map[] = { - 1, 3, 2, 2, 3, 1, 1, 3, 2, 1, 3, 2 -}; // PETAL_PAD_* -static const uint8_t bot_segment_map[] = { - 3, 0, 3, 0, 0, 0, 3, 0, 3, 1, 2, 3 -}; // PETAL_PAD_* -#elif defined(CONFIG_FLOW3R_HW_GEN_P6) -static const uint8_t top_segment_map[] = { - 1, 3, 2, 2, 3, 1, 1, 3, 2, 1, 3, 2 -}; // PETAL_PAD_* -static const uint8_t bot_segment_map[] = { - 3, 0, 3, 0, 0, 0, 3, 0, 3, 1, 2, 3 -}; // PETAL_PAD_* -#elif defined(CONFIG_FLOW3R_HW_GEN_P3) -static const uint8_t top_segment_map[] = { - 0, 1, 2, 2, 1, 0, 0, 1, 2, 2, 1, 0 -}; // PETAL_PAD_* -static const uint8_t bot_segment_map[] = { - 3, 0, 3, 0, 0, 0, 3, 0, 3, 0, 2, 1 -}; // PETAL_PAD_* -#endif - -static const char *TAG = "captouch"; - -#define AD7147_REG_PWR_CONTROL 0x00 -#define AD7147_REG_STAGE_CAL_EN 0x01 -#define AD7147_REG_STAGE_HIGH_INT_ENABLE 0x06 -#define AD7147_REG_DEVICE_ID 0x17 - -#define TIMEOUT_MS 1000 - -#define PETAL_PRESSED_DEBOUNCE 2 - -static struct ad714x_chip *chip_top; -static struct ad714x_chip *chip_bot; -typedef struct { - uint16_t amb; - uint16_t cdc; - uint16_t thres; - uint8_t pressed; -} petal_pad_t; +#include "flow3r_bsp_captouch.h" -typedef struct { - uint8_t config_mask; - petal_pad_t pads[4]; // ordered according to PETAL_PAD_* - uint8_t pressed; -} petal_t; - -static petal_t petals[10]; - -struct ad714x_chip { - const flow3r_i2c_address *addr; - uint8_t gpio; - int pos_afe_offsets[13]; - int neg_afe_offsets[13]; - int neg_afe_offset_swap; - int stages; -}; - -static struct ad714x_chip chip_top_rev5 = { - .addr = &flow3r_i2c_addresses.touch_top, - .gpio = 15, - .pos_afe_offsets = { 4, 2, 2, 2, 2, 3, 4, 2, 2, 2, 2, 0 }, - .stages = top_stages, -}; - -static struct ad714x_chip chip_bot_rev5 = { - .addr = &flow3r_i2c_addresses.touch_bottom, - .gpio = 15, - .pos_afe_offsets = { 3, 2, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3 }, - .stages = bot_stages, -}; - -static esp_err_t ad714x_i2c_write(const struct ad714x_chip *chip, - const uint16_t reg, const uint16_t data) { - const uint8_t tx[] = { reg >> 8, reg & 0xFF, data >> 8, data & 0xFF }; - ESP_LOGD(TAG, "AD7147 write reg %X-> %X", reg, data); - return flow3r_bsp_i2c_write_to_device(*chip->addr, tx, sizeof(tx), - TIMEOUT_MS / portTICK_PERIOD_MS); -} +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" -static esp_err_t ad714x_i2c_read(const struct ad714x_chip *chip, - const uint16_t reg, uint16_t *data, - const size_t len) { - const uint8_t tx[] = { reg >> 8, reg & 0xFF }; - uint8_t rx[len * 2]; - esp_err_t ret = flow3r_bsp_i2c_write_read_device( - *chip->addr, tx, sizeof(tx), rx, sizeof(rx), - TIMEOUT_MS / portTICK_PERIOD_MS); - for (int i = 0; i < len; i++) { - data[i] = (rx[i * 2] << 8) | rx[i * 2 + 1]; - } - return ret; -} +#include <string.h> -struct ad7147_stage_config { - unsigned int cinX_connection_setup[13]; - unsigned int se_connection_setup : 2; - unsigned int neg_afe_offset_disable : 1; - unsigned int pos_afe_offset_disable : 1; - unsigned int neg_afe_offset : 6; - unsigned int neg_afe_offset_swap : 1; - unsigned int pos_afe_offset : 6; - unsigned int pos_afe_offset_swap : 1; - unsigned int neg_threshold_sensitivity : 4; - unsigned int neg_peak_detect : 3; - unsigned int pos_threshold_sensitivity : 4; - unsigned int pos_peak_detect : 3; -}; - -static const uint16_t bank2 = 0x80; - -static void ad714x_set_stage_config(const struct ad714x_chip *chip, - const uint8_t stage, - const struct ad7147_stage_config *config) { - const uint16_t connection_6_0 = (config->cinX_connection_setup[6] << 12) | - (config->cinX_connection_setup[5] << 10) | - (config->cinX_connection_setup[4] << 8) | - (config->cinX_connection_setup[3] << 6) | - (config->cinX_connection_setup[2] << 4) | - (config->cinX_connection_setup[1] << 2) | - (config->cinX_connection_setup[0] << 0); - const uint16_t connection_12_7 = (config->pos_afe_offset_disable << 15) | - (config->neg_afe_offset_disable << 14) | - (config->se_connection_setup << 12) | - (config->cinX_connection_setup[12] << 10) | - (config->cinX_connection_setup[11] << 8) | - (config->cinX_connection_setup[10] << 6) | - (config->cinX_connection_setup[9] << 4) | - (config->cinX_connection_setup[8] << 2) | - (config->cinX_connection_setup[7] << 0); - const uint16_t afe_offset = - (config->pos_afe_offset_swap << 15) | (config->pos_afe_offset << 8) | - (config->neg_afe_offset_swap << 7) | (config->neg_afe_offset << 0); - const uint16_t sensitivity = (config->pos_peak_detect << 12) | - (config->pos_threshold_sensitivity << 8) | - (config->neg_peak_detect << 4) | - (config->neg_threshold_sensitivity << 0); - - // ESP_LOGI(TAG, "Stage %d config-> %X %X %X %X", stage, connection_6_0, - // connection_12_7, afe_offset, sensitivity); ESP_LOGI(TAG, "Config: %X %X - // %X %X %X %X %X %X %X", config->pos_afe_offset_disable, - // config->pos_afe_offset_disable, config->se_connection_setup, - // config->cinX_connection_setup[12], config->cinX_connection_setup[11], - // config->cinX_connection_setup[10], config->cinX_connection_setup[9], - // config->cinX_connection_setup[8], config->cinX_connection_setup[7]); - - ad714x_i2c_write(chip, bank2 + stage * 8, connection_6_0); - ad714x_i2c_write(chip, bank2 + stage * 8 + 1, connection_12_7); - ad714x_i2c_write(chip, bank2 + stage * 8 + 2, afe_offset); - ad714x_i2c_write(chip, bank2 + stage * 8 + 3, sensitivity); -} +static const char *TAG = "st3m-captouch"; -struct ad7147_device_config { - unsigned int power_mode : 2; - unsigned int lp_conv_delay : 2; - unsigned int sequence_stage_num : 4; - unsigned int decimation : 2; - unsigned int sw_reset : 1; - unsigned int int_pol : 1; - unsigned int ext_source : 1; - unsigned int cdc_bias : 2; - - unsigned int stage0_cal_en : 1; - unsigned int stage1_cal_en : 1; - unsigned int stage2_cal_en : 1; - unsigned int stage3_cal_en : 1; - unsigned int stage4_cal_en : 1; - unsigned int stage5_cal_en : 1; - unsigned int stage6_cal_en : 1; - unsigned int stage7_cal_en : 1; - unsigned int stage8_cal_en : 1; - unsigned int stage9_cal_en : 1; - unsigned int stage10_cal_en : 1; - unsigned int stage11_cal_en : 1; - unsigned int avg_fp_skip : 2; - unsigned int avg_lp_skip : 2; - - unsigned int stage0_high_int_enable : 1; - unsigned int stage1_high_int_enable : 1; - unsigned int stage2_high_int_enable : 1; - unsigned int stage3_high_int_enable : 1; - unsigned int stage4_high_int_enable : 1; - unsigned int stage5_high_int_enable : 1; - unsigned int stage6_high_int_enable : 1; - unsigned int stage7_high_int_enable : 1; - unsigned int stage8_high_int_enable : 1; - unsigned int stage9_high_int_enable : 1; - unsigned int stage10_high_int_enable : 1; - unsigned int stage11_high_int_enable : 1; -}; - -static void ad714x_set_device_config( - const struct ad714x_chip *chip, const struct ad7147_device_config *config) { - const uint16_t pwr_control = - (config->cdc_bias << 14) | (config->ext_source << 12) | - (config->int_pol << 11) | (config->sw_reset << 10) | - (config->decimation << 8) | (config->sequence_stage_num << 4) | - (config->lp_conv_delay << 2) | (config->power_mode << 0); - const uint16_t stage_cal_en = - (config->avg_lp_skip << 14) | (config->avg_fp_skip << 12) | - (config->stage11_cal_en << 11) | (config->stage10_cal_en << 10) | - (config->stage9_cal_en << 9) | (config->stage8_cal_en << 8) | - (config->stage7_cal_en << 7) | (config->stage6_cal_en << 6) | - (config->stage5_cal_en << 5) | (config->stage4_cal_en << 4) | - (config->stage3_cal_en << 3) | (config->stage2_cal_en << 2) | - (config->stage1_cal_en << 1) | (config->stage0_cal_en << 0); - const uint16_t stage_high_int_enable = - (config->stage11_high_int_enable << 11) | - (config->stage10_high_int_enable << 10) | - (config->stage9_high_int_enable << 9) | - (config->stage8_high_int_enable << 8) | - (config->stage7_high_int_enable << 7) | - (config->stage6_high_int_enable << 6) | - (config->stage5_high_int_enable << 5) | - (config->stage4_high_int_enable << 4) | - (config->stage3_high_int_enable << 3) | - (config->stage2_high_int_enable << 2) | - (config->stage1_high_int_enable << 1) | - (config->stage0_high_int_enable << 0); - - ad714x_i2c_write(chip, AD7147_REG_PWR_CONTROL, pwr_control); - ad714x_i2c_write(chip, AD7147_REG_STAGE_CAL_EN, stage_cal_en); - ad714x_i2c_write(chip, AD7147_REG_STAGE_HIGH_INT_ENABLE, - stage_high_int_enable); +// A simple, non-concurrent ringbuffer. I feel like I've already implemented +// this once in this codebase. +// +// TODO(q3k): unify/expose as common st3m API. +typedef struct { + size_t write_ix; + bool wrapped; + uint16_t buf[4]; +} ringbuffer_t; + +// Size of ringbuffer, in elements. +static inline size_t ringbuffer_size(const ringbuffer_t *rb) { + return sizeof(rb->buf) / sizeof(uint16_t); } -static struct ad7147_stage_config ad714x_default_config(void) { - return (struct ad7147_stage_config){ - .cinX_connection_setup = { CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS, - CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS, - CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS }, - .se_connection_setup = 0b01, - .pos_afe_offset = 0, - }; +// Write to ringbuffer. +static void ringbuffer_write(ringbuffer_t *rb, uint16_t data) { + rb->buf[rb->write_ix] = data; + rb->write_ix++; + if (rb->write_ix >= ringbuffer_size(rb)) { + rb->write_ix = 0; + rb->wrapped = true; + } } -static void captouch_configure_stage(struct ad714x_chip *chip, uint8_t stage) { - struct ad7147_stage_config stage_config; - stage_config = ad714x_default_config(); - if (chip == chip_bot) { - stage_config.cinX_connection_setup[bot_stage_config[stage]] = - CIN_CDC_POS; - } else { - stage_config.cinX_connection_setup[stage] = CIN_CDC_POS; +// Get ringbuffer average (mean), or 0 if no values have yet been inserted. +static uint16_t ringbuffer_avg(const ringbuffer_t *rb) { + int32_t res = 0; + if (rb->wrapped) { + for (size_t i = 0; i < ringbuffer_size(rb); i++) { + res += rb->buf[i]; + } + res /= ringbuffer_size(rb); + return res; } - stage_config.pos_afe_offset = chip->pos_afe_offsets[stage]; - ad714x_set_stage_config(chip, stage, &stage_config); + if (rb->write_ix == 0) { + return 0; + } + for (size_t i = 0; i < rb->write_ix; i++) { + res += rb->buf[i]; + } + res /= rb->write_ix; + return res; } -static int8_t captouch_configure_stage_afe_offset(uint8_t top, uint8_t stage, - int8_t delta_afe) { - int8_t sat = 0; - struct ad714x_chip *chip = chip_bot; - if (top) chip = chip_top; - int8_t afe = chip->pos_afe_offsets[stage] - chip->neg_afe_offsets[stage]; - if ((afe >= 63) && (delta_afe > 0)) sat = 1; - if ((afe <= 63) && (delta_afe < 0)) sat = -1; - afe += delta_afe; - if (afe >= 63) afe = 63; - if (afe <= -63) afe = -63; - - if (afe > 0) { - chip->pos_afe_offsets[stage] = afe; - chip->neg_afe_offsets[stage] = 0; - } else { - chip->pos_afe_offsets[stage] = 0; - chip->neg_afe_offsets[stage] = -afe; +// Get last inserted value, or 0 if no value have yet been inserted. +static uint16_t ringbuffer_last(const ringbuffer_t *rb) { + if (rb->write_ix == 0) { + if (rb->wrapped) { + return rb->buf[ringbuffer_size(rb) - 1]; + } + return 0; } - captouch_configure_stage(chip, stage); - return sat; + return rb->buf[rb->write_ix - 1]; } -static void captouch_init_chip( - struct ad714x_chip *chip, const struct ad7147_device_config device_config) { - uint16_t data; - ad714x_i2c_read(chip, AD7147_REG_DEVICE_ID, &data, 1); - ESP_LOGI(TAG, "DEVICE ID = %X", data); +// TODO(q3k): expose these as user structs? - ad714x_set_device_config(chip, &device_config); +typedef struct { + ringbuffer_t rb; + bool pressed; +} st3m_captouch_petal_pad_t; - for (int i = 0; i < chip->stages; i++) { - captouch_configure_stage(chip, i); - } -} +typedef struct { + st3m_captouch_petal_pad_t base; + st3m_captouch_petal_pad_t cw; + st3m_captouch_petal_pad_t ccw; + bool pressed; +} st3m_captouch_petal_top_t; -static void captouch_init_petals() { - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 4; j++) { - petals[i].pads[j].amb = 0; - petals[i].pads[j].cdc = 0; - if (i % 2) { - petals[i].pads[j].thres = DEFAULT_THRES_BOT; - } else { - petals[i].pads[j].thres = DEFAULT_THRES_TOP; - } - } - petals[i].config_mask = 0; - } - for (int i = 0; i < bot_stages; i++) { - petals[bot_map[i]].config_mask |= 1 << bot_segment_map[i]; - } - for (int i = 0; i < top_stages; i++) { - petals[top_map[i]].config_mask |= 1 << top_segment_map[i]; - } +typedef struct { + st3m_captouch_petal_pad_t base; + st3m_captouch_petal_pad_t tip; + bool pressed; +} st3m_captouch_petal_bottom_t; + +typedef struct { + flow3r_bsp_captouch_state_t raw; + + st3m_captouch_petal_top_t top[5]; + st3m_captouch_petal_bottom_t bottom[5]; +} st3m_captouch_state_t; + +static SemaphoreHandle_t _mu = NULL; +static st3m_captouch_state_t _state = {}; +static bool _request_calibration = false; +static bool _calibrating = false; + +static inline void _pad_feed(st3m_captouch_petal_pad_t *pad, uint16_t data, + bool top) { + ringbuffer_write(&pad->rb, data); + int32_t thres = top ? 8000 : 12000; + pad->pressed = data > thres; } -int32_t captouch_get_petal_rad(uint8_t petal) { - if (petal > 9) petal = 9; - uint8_t cf = petals[petal].config_mask; - if (cf == 0b1110) { // CCW, CW, BASE - int32_t left = petals[petal].pads[PETAL_PAD_CCW].cdc; - left -= petals[petal].pads[PETAL_PAD_CCW].amb; - int32_t right = petals[petal].pads[PETAL_PAD_CW].cdc; - right -= petals[petal].pads[PETAL_PAD_CW].amb; - int32_t base = petals[petal].pads[PETAL_PAD_BASE].cdc; - base -= petals[petal].pads[PETAL_PAD_BASE].amb; - return (left + right) / 2 - base; +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++) { + _pad_feed(&_state.top[i].base, _state.raw.petals[i * 2].base.raw, true); + _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; } - if (cf == 0b111) { // CCW, CW, TIP - int32_t left = petals[petal].pads[PETAL_PAD_CCW].cdc; - left -= petals[petal].pads[PETAL_PAD_CCW].amb; - int32_t right = petals[petal].pads[PETAL_PAD_CW].cdc; - right -= petals[petal].pads[PETAL_PAD_CW].amb; - int32_t tip = petals[petal].pads[PETAL_PAD_TIP].cdc; - tip -= petals[petal].pads[PETAL_PAD_TIP].amb; - return (-left - right) / 2 + tip; + for (size_t i = 0; i < 5; i++) { + _pad_feed(&_state.bottom[i].base, _state.raw.petals[i * 2 + 1].base.raw, + false); + _pad_feed(&_state.bottom[i].tip, _state.raw.petals[i * 2 + 1].tip.raw, + false); + _state.bottom[i].pressed = + _state.bottom[i].base.pressed || _state.bottom[i].tip.pressed; } - if (cf == 0b1001) { // TIP, BASE - int32_t tip = petals[petal].pads[PETAL_PAD_TIP].cdc; - tip -= petals[petal].pads[PETAL_PAD_TIP].amb; - int32_t base = petals[petal].pads[PETAL_PAD_BASE].cdc; - base -= petals[petal].pads[PETAL_PAD_BASE].amb; - return tip - base; - } - if (cf == 0b1) { // TIP - int32_t tip = petals[petal].pads[PETAL_PAD_TIP].cdc; - tip -= petals[petal].pads[PETAL_PAD_TIP].amb; - return tip; + if (_request_calibration) { + _request_calibration = false; + flow3r_bsp_captouch_calibrate(); } - return 0; -} - -int32_t captouch_get_petal_phi(uint8_t petal) { - if (petal > 9) petal = 9; - uint8_t cf = petals[petal].config_mask; - if ((cf == 0b1110) || (cf == 0b110) || (cf == 0b111)) { // CCW, CW, (BASE) - int32_t left = petals[petal].pads[PETAL_PAD_CCW].cdc; - left -= petals[petal].pads[PETAL_PAD_CCW].amb; - int32_t right = petals[petal].pads[PETAL_PAD_CW].cdc; - right -= petals[petal].pads[PETAL_PAD_CW].amb; - return left - right; - } - return 0; + _calibrating = flow3r_bsp_captouch_calibrating(); + xSemaphoreGive(_mu); } void captouch_init(void) { - captouch_init_petals(); - chip_top = &chip_top_rev5; - chip_bot = &chip_bot_rev5; - - captouch_init_chip(chip_top, (struct ad7147_device_config){ - .sequence_stage_num = 11, - .decimation = 1, - }); - - captouch_init_chip(chip_bot, (struct ad7147_device_config){ - .sequence_stage_num = 11, - .decimation = 1, - }); -} + assert(_mu == NULL); + _mu = xSemaphoreCreateMutex(); + assert(_mu != NULL); -uint16_t read_captouch() { - uint16_t bin_petals = 0; - for (int i = 0; i < 10; i++) { - if (petals[i].pressed) { - bin_petals |= (1 << i); - } + esp_err_t ret = flow3r_bsp_captouch_init(_on_data); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Captouch init failed: %s", esp_err_to_name(ret)); } - return bin_petals; } -void read_captouch_ex(captouch_state_t *state) { - for (int i = 0; i < 10; i++) { - state->petals[i].pressed = petals[i].pressed > 0; - state->petals[i].pads.base_pressed = - petals[i].pads[PETAL_PAD_BASE].pressed > 0; - state->petals[i].pads.tip_pressed = - petals[i].pads[PETAL_PAD_TIP].pressed > 0; - state->petals[i].pads.cw_pressed = - petals[i].pads[PETAL_PAD_CW].pressed > 0; - state->petals[i].pads.ccw_pressed = - petals[i].pads[PETAL_PAD_CCW].pressed > 0; - } +void captouch_print_debug_info(void) { + // Deprecated no-op, will be removed. } -uint16_t cdc_data[2][12] = { - 0, -}; -uint16_t cdc_ambient[2][12] = { - 0, -}; +void captouch_read_cycle(void) { + // Deprecated no-op, will be removed. + // (now handled by interrupt) +} -static volatile uint32_t calib_active = 0; +void captouch_set_calibration_afe_target(uint16_t target) { + // Deprecated no-op, will be removed. +} -static uint8_t calib_cycles = 0; void captouch_force_calibration() { - if (!calib_cycles) { // last calib has finished - calib_cycles = 16; // goal cycles, can be argument someday - Atomic_Increment_u32(&calib_active); - } + xSemaphoreTake(_mu, portMAX_DELAY); + _request_calibration = true; + xSemaphoreGive(_mu); } uint8_t captouch_calibration_active() { - return Atomic_CompareAndSwap_u32(&calib_active, 0, 0) == - ATOMIC_COMPARE_AND_SWAP_FAILURE; + xSemaphoreTake(_mu, portMAX_DELAY); + bool res = _calibrating || _request_calibration; + xSemaphoreGive(_mu); + return res; } -void check_petals_pressed() { - for (int i = 0; i < 10; i++) { - bool petal_pressed = false; - for (int j = 0; j < 4; j++) { - bool pad_pressed = false; - if ((petals[i].pads[j].amb + petals[i].pads[j].thres) < - petals[i].pads[j].cdc) { - petal_pressed = true; - pad_pressed = true; - } - - if (pad_pressed) { - petals[i].pads[j].pressed = PETAL_PRESSED_DEBOUNCE; - } else if (petals[i].pads[j].pressed) { - petals[i].pads[j].pressed--; - } - } - if (petal_pressed) { - petals[i].pressed = PETAL_PRESSED_DEBOUNCE; - } else if (petals[i].pressed) { - petals[i].pressed--; - } +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++) { + bool base = _state.top[i].base.pressed; + bool cw = _state.top[i].cw.pressed; + bool ccw = _state.top[i].ccw.pressed; + state->petals[i * 2].pads.base_pressed = base; + state->petals[i * 2].pads.cw_pressed = cw; + state->petals[i * 2].pads.ccw_pressed = ccw; + state->petals[i * 2].pressed = base || cw || ccw; } -} - -void cdc_to_petal(bool bot, bool amb, uint16_t cdc_data[], - uint8_t cdc_data_length) { - for (int i = 0; i < cdc_data_length; i++) { - size_t petal_index = bot ? bot_map[i] : top_map[i]; - size_t pad_index = bot ? bot_segment_map[i] : top_segment_map[i]; - petal_pad_t *pad = &petals[petal_index].pads[pad_index]; - uint16_t *target = amb ? &pad->amb : &pad->cdc; - *target = cdc_data[i]; + for (size_t i = 0; i < 5; i++) { + bool base = _state.bottom[i].base.pressed; + bool tip = _state.bottom[i].tip.pressed; + state->petals[i * 2 + 1].pads.base_pressed = base; + state->petals[i * 2 + 1].pads.tip_pressed = tip; + state->petals[i * 2 + 1].pressed = base || tip; } + xSemaphoreGive(_mu); } -uint16_t captouch_get_petal_pad_raw(uint8_t petal, uint8_t pad) { - if (petal > 9) petal = 9; - if (pad > 3) pad = 3; - return petals[petal].pads[pad].cdc; -} -uint16_t captouch_get_petal_pad_calib_ref(uint8_t petal, uint8_t pad) { - if (petal > 9) petal = 9; - if (pad > 3) pad = 3; - return petals[petal].pads[pad].amb; -} -uint16_t captouch_get_petal_pad(uint8_t petal, uint8_t pad) { - if (petal > 9) petal = 9; - if (pad > 3) pad = 3; - if (petals[petal].pads[pad].amb < petals[petal].pads[pad].cdc) { - return petals[petal].pads[pad].cdc - petals[petal].pads[pad].amb; +uint16_t read_captouch(void) { + xSemaphoreTake(_mu, portMAX_DELAY); + uint16_t res = 0; + for (size_t i = 0; i < 5; i++) { + if (_state.top[i].pressed) res |= (1 << (i * 2)); } - return 0; + for (size_t i = 0; i < 5; i++) { + if (_state.bottom[i].pressed) res |= (1 << (i * 2 + 1)); + } + xSemaphoreGive(_mu); + return res; } void captouch_set_petal_pad_threshold(uint8_t petal, uint8_t pad, uint16_t thres) { - if (petal > 9) petal = 9; - if (pad > 3) pad = 3; - petals[petal].pads[pad].thres = thres; + // Deprecated no-op, will be removed. } -static int32_t calib_target = 6000; - -void captouch_set_calibration_afe_target(uint16_t target) { - calib_target = target; +uint16_t captouch_get_petal_pad_raw(uint8_t petal, uint8_t pad) { + // Deprecated no-op, will be removed. + return 0; } -void captouch_read_cycle() { - static uint8_t calib_cycle = 0; - static uint8_t calib_div = 1; - static uint32_t ambient_acc[2][12] = { { - 0, - }, - { - 0, - } }; - if (calib_cycles) { - if (calib_cycle == 0) { // last cycle has finished, setup new - calib_cycle = calib_cycles; - calib_div = calib_cycles; - for (int j = 0; j < 12; j++) { - ambient_acc[0][j] = 0; - ambient_acc[1][j] = 0; - } - } +uint16_t captouch_get_petal_pad_calib_ref(uint8_t petal, uint8_t pad) { + // Deprecated no-op, will be removed. + return 0; +} - ad714x_i2c_read(chip_top, 0xB, cdc_ambient[0], chip_top->stages); - ad714x_i2c_read(chip_bot, 0xB, cdc_ambient[1], chip_bot->stages); - for (int j = 0; j < 12; j++) { - ambient_acc[0][j] += cdc_ambient[0][j]; - ambient_acc[1][j] += cdc_ambient[1][j]; - } +uint16_t captouch_get_petal_pad(uint8_t petal, uint8_t pad) { + // Deprecated no-op, will be removed. + return 0; +} - // TODO: use median instead of average - calib_cycle--; - if (!calib_cycle) { // calib cycle is complete - for (int i = 0; i < 12; i++) { - cdc_ambient[0][i] = ambient_acc[0][i] / calib_div; - cdc_ambient[1][i] = ambient_acc[1][i] / calib_div; - } - cdc_to_petal(0, 1, cdc_ambient[0], 12); - cdc_to_petal(1, 1, cdc_ambient[1], 12); - calib_cycles = 0; - - uint8_t recalib = 0; - for (int i = 0; i < 12; i++) { - for (int j = 0; j < 2; j++) { - int32_t diff = ((int32_t)cdc_ambient[j][i]) - calib_target; - int8_t steps = diff / (AFE_INCR_CAP); - if ((steps > 1) || (steps < -1)) { - if (!captouch_configure_stage_afe_offset(1 - j, i, - steps)) { - recalib = 1; - } - } - } - } - if (recalib) { - calib_cycles = 16; // do another round - } else { - Atomic_Decrement_u32(&calib_active); - } - } +int32_t captouch_get_petal_phi(uint8_t petal) { + bool top = (petal % 2) == 0; + if (top) { + 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); + xSemaphoreGive(_mu); + return left - right; } else { - ad714x_i2c_read(chip_top, 0xB, cdc_data[0], chip_top->stages); - cdc_to_petal(0, 0, cdc_data[0], 12); - - ad714x_i2c_read(chip_bot, 0xB, cdc_data[1], chip_bot->stages); - cdc_to_petal(1, 0, cdc_data[1], 12); - - check_petals_pressed(); + return 0; } } -static void captouch_print_debug_info_chip(const struct ad714x_chip *chip) { - uint16_t *data; - uint16_t *ambient; - const int stages = chip->stages; - - if (chip == chip_top) { - data = cdc_data[0]; - ambient = cdc_ambient[0]; +int32_t captouch_get_petal_rad(uint8_t petal) { + bool top = (petal % 2) == 0; + if (top) { + 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 base = ringbuffer_avg(&_state.top[ix].base.rb); + xSemaphoreGive(_mu); + return (left + right) / 2 - base; } else { - data = cdc_data[1]; - ambient = cdc_ambient[1]; - } - - // Appease clang-tidy. - (void)data; - (void)ambient; - ESP_LOGI(TAG, "CDC results: %X %X %X %X %X %X %X %X %X %X %X %X", data[0], - data[1], data[2], data[3], data[4], data[5], data[6], data[7], - data[8], data[9], data[10], data[11]); - - for (int stage = 0; stage < stages; stage++) { - ESP_LOGI(TAG, "stage %d ambient: %X diff: %d", stage, ambient[stage], - data[stage] - ambient[stage]); + size_t ix = (petal - 1) / 2; + xSemaphoreTake(_mu, portMAX_DELAY); + int32_t tip = ringbuffer_avg(&_state.bottom[ix].tip.rb); + int32_t base = ringbuffer_avg(&_state.bottom[ix].base.rb); + xSemaphoreGive(_mu); + return tip - base; } } - -void captouch_print_debug_info(void) { - captouch_print_debug_info_chip(chip_top); - captouch_print_debug_info_chip(chip_bot); -} diff --git a/components/flow3r_bsp/CMakeLists.txt b/components/flow3r_bsp/CMakeLists.txt index 60ecf0f064c22c517352f00e55fc2630226a3e40..3c16ce246b6c2846cd2068ec35b6f2a0f8e3394e 100644 --- a/components/flow3r_bsp/CMakeLists.txt +++ b/components/flow3r_bsp/CMakeLists.txt @@ -10,6 +10,9 @@ idf_component_register( flow3r_bsp_leds.c flow3r_bsp_rmtled.c flow3r_bsp_spio.c + flow3r_bsp_captouch.c + flow3r_bsp_ad7147.c + flow3r_bsp_ad7147_hw.c INCLUDE_DIRS . REQUIRES diff --git a/components/flow3r_bsp/flow3r_bsp_ad7147.c b/components/flow3r_bsp/flow3r_bsp_ad7147.c new file mode 100644 index 0000000000000000000000000000000000000000..8bae4631a1e3a78469b9e508812096b6590ec44e --- /dev/null +++ b/components/flow3r_bsp/flow3r_bsp_ad7147.c @@ -0,0 +1,259 @@ +#include "flow3r_bsp_ad7147.h" +#include "flow3r_bsp_ad7147_hw.h" +#include "flow3r_bsp_captouch.h" + +#include "esp_err.h" +#include "esp_log.h" + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +static const char *TAG = "flow3r-bsp-ad7147"; +static const int32_t _calib_target = 6000; +static const int32_t _calib_incr_cap = 1000; + +#define COMPLAIN(c, ...) \ + do { \ + if (!c->failed) { \ + ESP_LOGE(TAG, __VA_ARGS__); \ + c->failed = true; \ + } \ + } while (0) + +// Length of sequence, assuming sequence is right-padded with -1. +static size_t _captouch_sequence_length(int8_t *sequence) { + for (size_t i = 0; i < 12; i++) { + if (sequence[i] == -1) { + return i; + } + } + return 12; +} + +// Request current sequence from captouch chip. +static esp_err_t _sequence_request(ad7147_chip_t *chip, bool reprogram) { + int8_t *seq = chip->sequences[chip->seq_position]; + ad7147_sequence_t seq_out = { + .len = _captouch_sequence_length(seq), + }; + for (size_t i = 0; i < seq_out.len; i++) { + int8_t channel = seq[i]; + int8_t offset = chip->channels[channel].afe_offset; + seq_out.channels[i] = channel; + seq_out.pos_afe_offsets[i] = offset; + } + + esp_err_t ret; + if ((ret = ad7147_hw_configure_stages(&chip->dev, &seq_out, reprogram)) != + ESP_OK) { + return ret; + } + return ESP_OK; +} + +// Advance internal sequence pointer to next sequence. Returns true if advance +// occurred (in other words, false when there is only one sequence). +static bool _sequence_advance(ad7147_chip_t *chip) { + // Advance to next sequence. + size_t start = chip->seq_position; + chip->seq_position++; + if (chip->seq_position >= _AD7147_SEQ_MAX) { + chip->seq_position = 0; + } else { + if (_captouch_sequence_length(chip->sequences[chip->seq_position]) == + 0) { + chip->seq_position = 0; + } + } + return start != chip->seq_position; +} + +// Queue a calibration request until the next _chip_process call picks it up. +static void _calibration_request(ad7147_chip_t *chip) { + chip->calibration_pending = true; +} + +static int _uint16_sort(const void *va, const void *vb) { + uint16_t a = *((uint16_t *)va); + uint16_t b = *((uint16_t *)vb); + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; +} + +// Calculate median of 16 measurements. +static uint16_t _average_calib_measurements(uint16_t *measurements) { + qsort(measurements, _AD7147_CALIB_CYCLES, sizeof(uint16_t), _uint16_sort); + size_t ix = _AD7147_CALIB_CYCLES / 2; + return measurements[ix]; +} + +static size_t _channel_from_readout(const ad7147_chip_t *chip, size_t ix) { + size_t six = chip->seq_position; + const int8_t *seq = chip->sequences[six]; + assert(seq[ix] >= 0); + return seq[ix]; +} + +// Check if a channel's AFE offset can be tweaked to reach a wanted amb value. +// True is returned if a tweak was performed, false otherwise. +static bool _channel_afe_tweak(ad7147_chip_t *chip, size_t cix) { + int32_t cur = chip->channels[cix].amb; + int32_t target = _calib_target; + int32_t diff = (cur - target) / _calib_incr_cap; + if (diff < 1 && diff > -1) { + // Close enough. + return false; + } + int32_t offset = chip->channels[cix].afe_offset; + if (offset <= 0 && diff < 0) { + // Saturated, can't do anything. + return false; + } + if (offset >= 63 && diff > 0) { + // Saturated, can't do anything. + return false; + } + + offset += diff; + if (offset < 0) { + offset = 0; + } + if (offset > 63) { + offset = 63; + } + chip->channels[cix].afe_offset = offset; + return true; +} + +// Called when a sequence is completed by the low-level layer. +static void _on_data(void *user, uint16_t *data, size_t len) { + ad7147_chip_t *chip = (ad7147_chip_t *)user; + + if (chip->calibration_cycles > 0) { + // We're doing a calibration cycle on our channels. Instead of writing + // the data to channel->cdc, write it to channel->amb_meas. + size_t j = chip->calibration_cycles - 1; + for (size_t i = 0; i < len; i++) { + chip->channels[_channel_from_readout(chip, i)].amb_meas[j] = + data[i]; + } + } else { + // Normal measurement, apply to channel->cdc. + for (size_t i = 0; i < len; i++) { + chip->channels[_channel_from_readout(chip, i)].cdc = data[i]; + } + } + + bool reprogram = _sequence_advance(chip); + + // Synchronize on beginning of sequence for calibration logic. + if (chip->seq_position == 0) { + // Deal with calibration pending flag, possibly starting calibration. + if (chip->calibration_pending) { + if (chip->calibration_cycles == 0) { + ESP_LOGI(TAG, "%s: calibration starting...", chip->name); + chip->calibration_cycles = _AD7147_CALIB_CYCLES; + } + chip->calibration_pending = false; + } + + if (chip->calibration_cycles > 0) { + // Deal with active calibration. + chip->calibration_cycles--; + if (chip->calibration_cycles == 0) { + // Calibration measurements done. Calculate average amb data for + // each channel. + for (size_t i = 0; i < chip->nchannels; i++) { + uint16_t avg = + _average_calib_measurements(chip->channels[i].amb_meas); + chip->channels[i].amb = avg; + } + + char msg[256]; + char *wr = msg; + for (size_t i = 0; i < chip->nchannels; i++) { + if (wr != msg) { + wr += snprintf(wr, 256 - (wr - msg), ", "); + } + wr += snprintf(wr, 256 - (wr - msg), "%04d/%02d", + chip->channels[i].amb, + chip->channels[i].afe_offset); + } + ESP_LOGD(TAG, "%s: calibration: %s.", chip->name, msg); + + // Can we tweak the AFE to get a better measurement? + uint16_t rerun = 0; + for (size_t i = 0; i < chip->nchannels; i++) { + bool tweak = _channel_afe_tweak(chip, i); + if (tweak) { + rerun |= (1 << i); + } + } + + if (rerun != 0) { + // Rerun calibration again, + ESP_LOGI(TAG, + "%s: calibration done, but can do better (%04x). " + "Retrying.", + chip->name, rerun); + chip->calibration_cycles = _AD7147_CALIB_CYCLES; + } else { + ESP_LOGI(TAG, "%s: calibration done.", chip->name); + } + } + } else { + // Submit data to higher level for processing. + if (chip->callback != NULL) { + uint16_t val[13]; + for (size_t i = 0; i < chip->nchannels; i++) { + int32_t cdc = chip->channels[i].cdc; + int32_t amb = chip->channels[i].amb; + int32_t diff = cdc - amb; + if (diff < 0) { + val[i] = 0; + } else if (diff > 65535) { + val[i] = 65535; + } else { + val[i] = diff; + } + } + chip->callback(chip->user, val, chip->nchannels); + } + } + } + + // BUG(q3k): we shouldn't need to do this every cycle, but otherwise we get + // some weird results on positional output. Needs to be investigated. + esp_err_t ret; + if ((ret = _sequence_request(chip, reprogram)) != ESP_OK) { + COMPLAIN(chip, "%s: requesting next sequence failed: %s", chip->name, + esp_err_to_name(ret)); + } +} + +esp_err_t flow3r_bsp_ad7147_chip_init(ad7147_chip_t *chip, + flow3r_i2c_address address) { + esp_err_t ret; + for (size_t i = 0; i < chip->nchannels; i++) { + chip->channels[i].amb = 0; + } + if ((ret = ad7147_hw_init(&chip->dev, address, _on_data, chip)) != ESP_OK) { + return ret; + } + _calibration_request(chip); + if ((ret = _sequence_request(chip, false)) != ESP_OK) { + return ret; + } + return ESP_OK; +} + +esp_err_t flow3r_bsp_ad7147_chip_process(ad7147_chip_t *chip) { + return ad7147_hw_process(&chip->dev); +} \ No newline at end of file diff --git a/components/flow3r_bsp/flow3r_bsp_ad7147.h b/components/flow3r_bsp/flow3r_bsp_ad7147.h new file mode 100644 index 0000000000000000000000000000000000000000..5082a87d066d51f87bb3a2541958c5330ed79b96 --- /dev/null +++ b/components/flow3r_bsp/flow3r_bsp_ad7147.h @@ -0,0 +1,78 @@ +#pragma once + +// High-level AD7147 captouch functions. Includes support for switching +// sequences and has basic software calibration routines. +// +// Only takes care of one captouch controller at once. Both captouch controllers +// are put together into a single view in flow3r_bsp_captouch. + +// The AD7147 captouch chip is weird. +// +// It has 13 input channels, and can perform arbitrarily sequenced reads from +// these channels (then report the results of that sequence at once), but the +// maximum sequence length is 12 stages. +// +// That means getting 13 independent captouch channel readouts is tricky, as you +// need to change these sequences around to get all channels. + +#include "flow3r_bsp_ad7147_hw.h" + +#define _AD7147_SEQ_MAX 2 +#define _AD7147_CALIB_CYCLES 16 + +// State of an AD7147 channel. Each AD7147 has 13 channels, but can only access +// 12 of them at once in a single sequence. +typedef struct { + // Positive AFE offset currently programmed. [0,64). + int8_t afe_offset; + // Last measurement. + uint16_t cdc; + + // Ambient value used for offset when checking for touch presence. Written + // by calibration, and attempts to reach a preset calibration setpoint. + uint16_t amb; + // Calibration samples gathered during the calibraiton process. + uint16_t amb_meas[_AD7147_CALIB_CYCLES]; +} ad7147_channel_t; + +// State and configuration of an AD7147 chip. Wraps the low-level structure in +// everything required to manage multiple sequences and perform calibration. +typedef struct { + // Opaque name used to prefix log messages. + const char *name; + + // [0, n_channels) are the expected connected channels to the inputs of the + // chip. + size_t nchannels; + ad7147_channel_t channels[13]; + + // Sequences to be handled by this chip. Each sequence is a -1 right-padded + // list of channel numbers that the sequence will be programmed to. If a + // sequence is all -1, it will be skipped. + int8_t sequences[_AD7147_SEQ_MAX][12]; + + // Current position within the sequences list. + size_t seq_position; + + // Called when all sequences have scanned through. + ad7147_data_callback_t callback; + void *user; + + ad7147_hw_t dev; + bool failed; + + bool calibration_pending; + size_t calibration_cycles; +} ad7147_chip_t; + +// Call to initialize the chip at a given address. Structure must be zeroed, and +// callback must be configured. +// +// The chip will be configured to pull its interrupt line low when a sequence +// has finished and thus when _chip_process should be called. +esp_err_t flow3r_bsp_ad7147_chip_init(ad7147_chip_t *chip, + flow3r_i2c_address address); + +// Call to poll the chip and perform any necessary actions. Can be called from +// an interrupt. +esp_err_t flow3r_bsp_ad7147_chip_process(ad7147_chip_t *chip); \ No newline at end of file diff --git a/components/flow3r_bsp/flow3r_bsp_ad7147_hw.c b/components/flow3r_bsp/flow3r_bsp_ad7147_hw.c new file mode 100644 index 0000000000000000000000000000000000000000..b7237c5e17b4718ae607d9360a0abb9b9616b246 --- /dev/null +++ b/components/flow3r_bsp/flow3r_bsp_ad7147_hw.c @@ -0,0 +1,313 @@ +#include "flow3r_bsp_ad7147_hw.h" + +#include "esp_err.h" +#include "esp_log.h" + +#include <string.h> + +#define TIMEOUT_MS 1000 + +#define CIN CDC_NONE 0 +#define CIN_CDC_NEG 1 +#define CIN_CDC_POS 2 +#define CIN_BIAS 3 + +#define AD7147_REG_PWR_CONTROL 0x00 +#define AD7147_REG_STAGE_CAL_EN 0x01 +#define AD7147_REG_AMB_COMP_CTRL0 0x02 +#define AD7147_REG_STAGE_HIGH_INT_ENABLE 0x06 +#define AD7147_REG_STAGE_COMPLETE_INT_ENABLE 0x07 +#define AD7147_REG_STAGE_COMPLETE_INT_STATUS 0x0A +#define AD7147_REG_CDC_RESULT_S0 0x0B +#define AD7147_REG_DEVICE_ID 0x17 +#define AD7147_REG_STAGE0_CONNECTION 0x80 + +// Write single register at `reg`. +static esp_err_t _i2c_write(const ad7147_hw_t *dev, uint16_t reg, + uint16_t data) { + const uint8_t tx[] = { reg >> 8, reg & 0xFF, data >> 8, data & 0xFF }; + return flow3r_bsp_i2c_write_to_device(dev->addr, tx, sizeof(tx), + TIMEOUT_MS / portTICK_PERIOD_MS); +} + +// Write continuous `len`-long register range starting at `reg`. +static esp_err_t _i2c_write_multiple(const ad7147_hw_t *dev, uint16_t reg, + const uint16_t *data, size_t len) { + uint8_t *tx = malloc(len * 2 + 2); + assert(tx != NULL); + tx[0] = reg >> 8; + tx[1] = reg & 0xff; + for (size_t i = 0; i < len; i++) { + tx[2 + i * 2] = data[i] >> 8; + tx[2 + i * 2 + 1] = data[i] & 0xff; + } + esp_err_t ret = flow3r_bsp_i2c_write_to_device( + dev->addr, tx, len * 2 + 2, TIMEOUT_MS / portTICK_PERIOD_MS); + free(tx); + return ret; +} + +// Read continuous `len`-long register range starting at `reg`. +static esp_err_t _i2c_read(const ad7147_hw_t *dev, const uint16_t reg, + uint16_t *data, const size_t len) { + const uint8_t tx[] = { reg >> 8, reg & 0xFF }; + uint8_t rx[len * 2]; + esp_err_t ret = flow3r_bsp_i2c_write_read_device( + dev->addr, tx, sizeof(tx), rx, sizeof(rx), + TIMEOUT_MS / portTICK_PERIOD_MS); + for (int i = 0; i < len; i++) { + data[i] = (rx[i * 2] << 8) | rx[i * 2 + 1]; + } + return ret; +} + +// Configure device's PWR_CONTROL register based on dev_config. +static esp_err_t _configure_pwr_control(const ad7147_hw_t *dev) { + const ad7147_device_config_t *config = &dev->dev_config; + const uint16_t val = + (config->cdc_bias << 14) | (config->ext_source << 12) | + (config->int_pol << 11) | (config->sw_reset << 10) | + (config->decimation << 8) | (config->sequence_stage_num << 4) | + (config->lp_conv_delay << 2) | (config->power_mode << 0); + return _i2c_write(dev, AD7147_REG_PWR_CONTROL, val); +} + +// Configure device's STAGE_CAL_EN register based on dev_config. +static esp_err_t _configure_stage_cal_en(const ad7147_hw_t *dev) { + const ad7147_device_config_t *config = &dev->dev_config; + const uint16_t val = + (config->avg_lp_skip << 14) | (config->avg_fp_skip << 12) | + (config->stage11_cal_en << 11) | (config->stage10_cal_en << 10) | + (config->stage9_cal_en << 9) | (config->stage8_cal_en << 8) | + (config->stage7_cal_en << 7) | (config->stage6_cal_en << 6) | + (config->stage5_cal_en << 5) | (config->stage4_cal_en << 4) | + (config->stage3_cal_en << 3) | (config->stage2_cal_en << 2) | + (config->stage1_cal_en << 1) | (config->stage0_cal_en << 0); + + return _i2c_write(dev, AD7147_REG_STAGE_CAL_EN, val); +} + +// Configure device's STAGE_HIGH_INT_ENANBLE register based on dev_config. +static esp_err_t _configure_high_int_en(const ad7147_hw_t *dev) { + const ad7147_device_config_t *config = &dev->dev_config; + const uint16_t val = (config->stage11_high_int_enable << 11) | + (config->stage10_high_int_enable << 10) | + (config->stage9_high_int_enable << 9) | + (config->stage8_high_int_enable << 8) | + (config->stage7_high_int_enable << 7) | + (config->stage6_high_int_enable << 6) | + (config->stage5_high_int_enable << 5) | + (config->stage4_high_int_enable << 4) | + (config->stage3_high_int_enable << 3) | + (config->stage2_high_int_enable << 2) | + (config->stage1_high_int_enable << 1) | + (config->stage0_high_int_enable << 0); + + return _i2c_write(dev, AD7147_REG_STAGE_HIGH_INT_ENABLE, val); +} + +// Configure device's STAGE_COMPLETE_INT_ENABLE register based on dev_config. +static esp_err_t _configure_complete_int_en(const ad7147_hw_t *dev) { + const ad7147_device_config_t *config = &dev->dev_config; + const uint16_t val = + ((config->stageX_complete_int_enable[0] ? 1 : 0) << 11) | + ((config->stageX_complete_int_enable[1] ? 1 : 0) << 10) | + ((config->stageX_complete_int_enable[2] ? 1 : 0) << 9) | + ((config->stageX_complete_int_enable[3] ? 1 : 0) << 8) | + ((config->stageX_complete_int_enable[4] ? 1 : 0) << 7) | + ((config->stageX_complete_int_enable[5] ? 1 : 0) << 6) | + ((config->stageX_complete_int_enable[6] ? 1 : 0) << 5) | + ((config->stageX_complete_int_enable[6] ? 1 : 0) << 4) | + ((config->stageX_complete_int_enable[8] ? 1 : 0) << 3) | + ((config->stageX_complete_int_enable[9] ? 1 : 0) << 2) | + ((config->stageX_complete_int_enable[10] ? 1 : 0) << 1) | + ((config->stageX_complete_int_enable[11] ? 1 : 0) << 0); + + return _i2c_write(dev, AD7147_REG_STAGE_COMPLETE_INT_ENABLE, val); +} + +// Configure stage per stage_config. +static esp_err_t _configure_stage(const ad7147_hw_t *dev, uint8_t stage) { + const ad7147_stage_config_t *config = &dev->stage_config[stage]; + + const uint16_t connection_6_0 = (config->cinX_connection_setup[6] << 12) | + (config->cinX_connection_setup[5] << 10) | + (config->cinX_connection_setup[4] << 8) | + (config->cinX_connection_setup[3] << 6) | + (config->cinX_connection_setup[2] << 4) | + (config->cinX_connection_setup[1] << 2) | + (config->cinX_connection_setup[0] << 0); + const uint16_t connection_12_7 = (config->pos_afe_offset_disable << 15) | + (config->neg_afe_offset_disable << 14) | + (config->se_connection_setup << 12) | + (config->cinX_connection_setup[12] << 10) | + (config->cinX_connection_setup[11] << 8) | + (config->cinX_connection_setup[10] << 6) | + (config->cinX_connection_setup[9] << 4) | + (config->cinX_connection_setup[8] << 2) | + (config->cinX_connection_setup[7] << 0); + const uint16_t afe_offset = + (config->pos_afe_offset_swap << 15) | (config->pos_afe_offset << 8) | + (config->neg_afe_offset_swap << 7) | (config->neg_afe_offset << 0); + const uint16_t sensitivity = (config->pos_peak_detect << 12) | + (config->pos_threshold_sensitivity << 8) | + (config->neg_peak_detect << 4) | + (config->neg_threshold_sensitivity << 0); + + const uint8_t reg = AD7147_REG_STAGE0_CONNECTION + stage * 8; + uint16_t tx[4] = { + connection_6_0, + connection_12_7, + afe_offset, + sensitivity, + }; + return _i2c_write_multiple(dev, reg, tx, 4); +} + +// Configure entire device per stage_config and dev_config. +static esp_err_t _configure_full(const ad7147_hw_t *dev) { + esp_err_t ret; + if ((ret = _configure_pwr_control(dev)) != ESP_OK) { + return ret; + } + if ((ret = _configure_stage_cal_en(dev)) != ESP_OK) { + return ret; + } + if ((ret = _configure_high_int_en(dev)) != ESP_OK) { + return ret; + } + if ((ret = _configure_complete_int_en(dev)) != ESP_OK) { + return ret; + } + for (uint8_t i = 0; i < 12; i++) { + if ((ret = _configure_stage(dev, i)) != ESP_OK) { + return ret; + } + } + return ESP_OK; +} + +// Force stage sequencer to reset. Glitchy. +static esp_err_t _reset_sequencer(const ad7147_hw_t *dev) { + uint16_t val = (0x0 << 0) | // FF_SKIP_CNT + (0xf << 4) | // FP_PROXIMITY_CNT + (0xf << 8) | // LP_PROXIMITY_CNT + (0x0 << 12) | // PWR_DOWN_TIMEOUT + (0x0 << 14) | // FORCED_CAL + (0x1 << 15); // CONV_RESET + esp_err_t res; + if ((res = _i2c_write(dev, AD7147_REG_AMB_COMP_CTRL0, val)) != ESP_OK) { + return res; + } + return ESP_OK; +} + +// Read completed conversion data and call user callback on it. +static esp_err_t _process_complete(ad7147_hw_t *device) { + uint16_t data[12]; + size_t count = device->num_stages; + esp_err_t res; + if ((res = _i2c_read(device, AD7147_REG_CDC_RESULT_S0, data, count)) != + ESP_OK) { + return res; + } + if (device->callback != NULL) { + device->callback(device->user, data, count); + } + + return ESP_OK; +} + +esp_err_t ad7147_hw_process(ad7147_hw_t *device) { + // Read complete status register. This acknowledges interrupts. + uint16_t st = 0; + esp_err_t res = + _i2c_read(device, AD7147_REG_STAGE_COMPLETE_INT_STATUS, &st, 1); + if (res != ESP_OK) { + return res; + } + + // Nothing to do if no stages are expected to be read. + if (device->num_stages < 1) { + return ESP_OK; + } + + // Bit indicating the conversion has been complete for the requested number + // of stages. + uint16_t complete_bit = (1 << (device->num_stages - 1)); + if (st & complete_bit) { + res = _process_complete(device); + if (res != ESP_OK) { + return res; + } + } else { + // Spurious hw_process call, nothing to do... + } + + return ESP_OK; +} + +esp_err_t ad7147_hw_init(ad7147_hw_t *device, flow3r_i2c_address addr, + ad7147_data_callback_t callback, void *user) { + memset(device, 0, sizeof(ad7147_hw_t)); + device->addr = addr; + device->callback = callback; + device->user = user; + + // device->dev_config.decimation = 0b10; // Decimation: 64, lowest possible. + device->dev_config.decimation = 0b01; + + for (size_t i = 0; i < 12; i++) { + for (size_t j = 0; j < 13; j++) { + device->stage_config[i].cinX_connection_setup[j] = CIN_BIAS; + } + device->stage_config[i].se_connection_setup = 0b01; + } + return _configure_full(device); +} + +esp_err_t ad7147_hw_configure_stages(ad7147_hw_t *device, + const ad7147_sequence_t *seq, + bool reprogram) { + // Reset all stage/channel configuration. + for (size_t i = 0; i < 12; i++) { + for (int8_t j = 0; j < 13; j++) { + device->stage_config[i].cinX_connection_setup[j] = CIN_BIAS; + } + device->dev_config.stageX_complete_int_enable[i] = false; + } + + // Configure stages as requested. + for (size_t i = 0; i < seq->len; i++) { + int8_t channel = seq->channels[i]; + int8_t offset = seq->pos_afe_offsets[i]; + device->stage_config[i].cinX_connection_setup[channel] = CIN_CDC_POS; + unsigned int pos_offset = offset < 0 ? 0 : (offset > 63 ? 63 : offset); + device->stage_config[i].pos_afe_offset = pos_offset; + } + device->dev_config.sequence_stage_num = seq->len - 1; + device->dev_config.stageX_complete_int_enable[seq->len - 1] = true; + + // For our own record (more precisely, for the interrupt handler). + device->num_stages = seq->len; + + // Submit changes over I2C. + esp_err_t ret; + if ((ret = _configure_pwr_control(device)) != ESP_OK) { + return ret; + } + for (uint8_t i = 0; i < 12; i++) { + if ((ret = _configure_stage(device, i)) != ESP_OK) { + return ret; + } + } + if ((ret = _configure_complete_int_en(device)) != ESP_OK) { + return ret; + } + if (reprogram) { + if ((ret = _reset_sequencer(device)) != ESP_OK) { + return ret; + } + } + return ESP_OK; +} diff --git a/components/flow3r_bsp/flow3r_bsp_ad7147_hw.h b/components/flow3r_bsp/flow3r_bsp_ad7147_hw.h new file mode 100644 index 0000000000000000000000000000000000000000..5cdfd63b34142dba4000d08a47245d0b37af5536 --- /dev/null +++ b/components/flow3r_bsp/flow3r_bsp_ad7147_hw.h @@ -0,0 +1,115 @@ +#pragma once + +// Low-level AD7147 (captouch controller) interfacing functions. Currently only +// implements sequences where each channel is connected to the positive CDC +// input, and only the raw data is read out. + +#include "esp_err.h" +#include "flow3r_bsp_i2c.h" + +#include <stdint.h> + +// 'Global' configuration for the AD7147 captouch controller. +typedef struct ad7147_device_config { + unsigned int power_mode : 2; + unsigned int lp_conv_delay : 2; + unsigned int sequence_stage_num : 4; + unsigned int decimation : 2; + unsigned int sw_reset : 1; + unsigned int int_pol : 1; + unsigned int ext_source : 1; + unsigned int cdc_bias : 2; + + unsigned int stage0_cal_en : 1; + unsigned int stage1_cal_en : 1; + unsigned int stage2_cal_en : 1; + unsigned int stage3_cal_en : 1; + unsigned int stage4_cal_en : 1; + unsigned int stage5_cal_en : 1; + unsigned int stage6_cal_en : 1; + unsigned int stage7_cal_en : 1; + unsigned int stage8_cal_en : 1; + unsigned int stage9_cal_en : 1; + unsigned int stage10_cal_en : 1; + unsigned int stage11_cal_en : 1; + unsigned int avg_fp_skip : 2; + unsigned int avg_lp_skip : 2; + + unsigned int stage0_high_int_enable : 1; + unsigned int stage1_high_int_enable : 1; + unsigned int stage2_high_int_enable : 1; + unsigned int stage3_high_int_enable : 1; + unsigned int stage4_high_int_enable : 1; + unsigned int stage5_high_int_enable : 1; + unsigned int stage6_high_int_enable : 1; + unsigned int stage7_high_int_enable : 1; + unsigned int stage8_high_int_enable : 1; + unsigned int stage9_high_int_enable : 1; + unsigned int stage10_high_int_enable : 1; + unsigned int stage11_high_int_enable : 1; + + bool stageX_complete_int_enable[12]; +} ad7147_device_config_t; + +// Per sequencer stage configuration. +typedef struct { + unsigned int cinX_connection_setup[13]; + unsigned int se_connection_setup : 2; + unsigned int neg_afe_offset_disable : 1; + unsigned int pos_afe_offset_disable : 1; + unsigned int neg_afe_offset : 6; + unsigned int neg_afe_offset_swap : 1; + unsigned int pos_afe_offset : 6; + unsigned int pos_afe_offset_swap : 1; + unsigned int neg_threshold_sensitivity : 4; + unsigned int neg_peak_detect : 3; + unsigned int pos_threshold_sensitivity : 4; + unsigned int pos_peak_detect : 3; +} ad7147_stage_config_t; + +typedef void (*ad7147_data_callback_t)(void *user, uint16_t *data, size_t len); + +// AD7147 low level configuration/access structure. Doesn't know anything about +// calibration or high-level sequencing, just talks to a chip to configure +// stages and can be called to poll the chip for new CDC data. +typedef struct { + // I2C address of chip. + flow3r_i2c_address addr; + + // Function and user-controlled argument that will be called when the + // sequence has finished and new data is available. + ad7147_data_callback_t callback; + void *user; + + ad7147_stage_config_t stage_config[12]; + ad7147_device_config_t dev_config; + uint8_t num_stages; +} ad7147_hw_t; + +// Initialize the AD7147 captouch controller with a given callback. This +// callback will be called when new data has been acquired. +esp_err_t ad7147_hw_init(ad7147_hw_t *device, flow3r_i2c_address addr, + ad7147_data_callback_t callback, void *user); + +// Sequencer configuration, high-level. +typedef struct { + // Number of sequencer stages, [1, 12]. + size_t len; + // For each sequencer stage, channel number that this stage should sample. + int8_t channels[12]; + // For each sequencer stage, AFE offset that this stage should use when + // sampling the configured channel. + int8_t pos_afe_offsets[12]; +} ad7147_sequence_t; + +// Configure sequencer stages. +// +// If reprogram is true, the sequencer will be restarted. This should be true if +// the sequence channels/lengths changed from the previous call. +esp_err_t ad7147_hw_configure_stages(ad7147_hw_t *device, + const ad7147_sequence_t *seq, + bool reprogram); + +// Polls sequencer status from the chip and calls the user callback if new data +// is available / the sequence finished. +esp_err_t ad7147_hw_process(ad7147_hw_t *device); \ No newline at end of file diff --git a/components/flow3r_bsp/flow3r_bsp_captouch.c b/components/flow3r_bsp/flow3r_bsp_captouch.c new file mode 100644 index 0000000000000000000000000000000000000000..1246bebbb27d5766c8136caead1d2af024b76797 --- /dev/null +++ b/components/flow3r_bsp/flow3r_bsp_captouch.c @@ -0,0 +1,319 @@ +#include "flow3r_bsp_captouch.h" +#include "flow3r_bsp_ad7147.h" +#include "flow3r_bsp_i2c.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" + +#include "driver/gpio.h" + +#include "esp_log.h" + +static const char *TAG = "flow3r-bsp-captouch"; + +typedef struct { + size_t petal_number; + petal_kind_t pad_kind; +} pad_mapping_t; + +static const pad_mapping_t _map_top[12] = { + { 0, petal_pad_ccw }, // 0 + { 0, petal_pad_base }, // 1 + { 0, petal_pad_cw }, // 2 + { 2, petal_pad_cw }, // 3 + { 2, petal_pad_base }, // 4 + { 2, petal_pad_ccw }, // 5 + { 6, petal_pad_ccw }, // 6 + { 6, petal_pad_base }, // 7 + { 6, petal_pad_cw }, // 8 + { 4, petal_pad_ccw }, // 9 + { 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 + + { 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_ccw }, // 10 + { 8, petal_pad_cw }, // 11 + { 8, petal_pad_base }, // 12 +}; + +static ad7147_chip_t _bot = { + .name = "bot", + .nchannels = 13, + .sequences = { + /// This is the ideal sequence we want. First, all the bottom sensors. + /// Then the top petal. + + //{ + // 0, 1, 2, 3, 4, 5, + // 6, 7, 8, 9, -1, -1, + //}, + //{ + // 10, 11, 12, -1, -1, -1, + // -1, -1, -1, -1, -1, -1 + //}, + + /// However, that causes extreme glitches. This seems to make it + /// slightly better: + + //{ + // 0, 1, 2, 3, 4, 5, + // 9, -1, -1, -1, -1, -1, + //}, + //{ + // 10, 11, 12, 6, 7, 8, + // 9, -1, -1, -1, -1, -1 + //}, + + /// However again, that's still too glitchy for my taste. So we end up + /// just ignoring one of the bottom petal pads and hope to figure this + /// out later (tm). + /// BUG(q3k): whyyyyyyyyyyyyyyy + { + 0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12 + }, + { + -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, + }, + }, +}; + +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 = {}; + +static bool _processed = false; + +static void _on_chip_data(void *user, uint16_t *data, size_t len) { + ad7147_chip_t *chip = (ad7147_chip_t *)user; + assert(chip == &_top || chip == &_bot); + bool top = chip == &_top; + const pad_mapping_t *map = top ? _map_top : _map_bot; + + for (size_t i = 0; i < len; i++) { + flow3r_bsp_captouch_petal_state_t *petal = + &_state.petals[map[i].petal_number]; + flow3r_bsp_captouch_petal_pad_state_t *pad = + flow3r_bsp_captouch_pad_for_petal(petal, map[i].pad_kind); + pad->raw = data[i]; + } + + _processed = true; +} + +static QueueHandle_t _q = NULL; + +static void _bot_isr(void *data) { + (void)data; + bool bot = true; + xQueueSendFromISR(_q, &bot, NULL); +} + +static void _top_isr(void *data) { + (void)data; + bool bot = false; + xQueueSendFromISR(_q, &bot, NULL); +} + +static void _kickstart(void) { + bool bot = false; + xQueueSend(_q, &bot, portMAX_DELAY); + bot = true; + xQueueSend(_q, &bot, portMAX_DELAY); +} + +static void _task(void *data) { + (void)data; + + bool top_ok = true; + bool bot_ok = true; + esp_err_t ret; + for (;;) { + bool bot = false; + if (xQueueReceive(_q, &bot, portMAX_DELAY) == pdFALSE) { + ESP_LOGE(TAG, "Queue receive failed"); + return; + } + + if (bot) { + if ((ret = flow3r_bsp_ad7147_chip_process(&_bot)) != ESP_OK) { + if (bot_ok) { + ESP_LOGE(TAG, + "Bottom captouch processing failed: %s (silencing " + "future warnings)", + esp_err_to_name(ret)); + bot_ok = false; + } + } else { + bot_ok = true; + } + } else { + if ((ret = flow3r_bsp_ad7147_chip_process(&_top)) != ESP_OK) { + if (top_ok) { + ESP_LOGE(TAG, + "Top captouch processing failed: %s (silencing " + "future warnings)", + esp_err_to_name(ret)); + top_ok = false; + } + } else { + top_ok = true; + } + } + + _callback(&_state); + } +} + +esp_err_t _gpio_interrupt_setup(gpio_num_t num, gpio_isr_t isr) { + esp_err_t ret; + + gpio_config_t io_conf = { + .intr_type = GPIO_INTR_NEGEDGE, + .mode = GPIO_MODE_INPUT, + .pull_up_en = true, + .pin_bit_mask = (1 << num), + }; + if ((ret = gpio_config(&io_conf)) != ESP_OK) { + return ret; + } + if ((ret = gpio_isr_handler_add(num, isr, NULL)) != ESP_OK) { + return ret; + } + return ESP_OK; +} + +esp_err_t flow3r_bsp_captouch_init(flow3r_bsp_captouch_callback_t callback) { + assert(_callback == NULL); + assert(callback != NULL); + _callback = callback; + + _q = xQueueCreate(2, sizeof(bool)); + assert(_q != NULL); + + esp_err_t ret; + + for (size_t i = 0; i < 10; i++) { + bool top = (i % 2) == 0; + _state.petals[i].kind = top ? petal_top : petal_bottom; + _state.petals[i].ccw.kind = petal_pad_ccw; + _state.petals[i].ccw.threshold = top ? 8000 : 12000; + _state.petals[i].cw.kind = petal_pad_cw; + _state.petals[i].cw.threshold = top ? 8000 : 12000; + _state.petals[i].tip.kind = petal_pad_tip; + _state.petals[i].tip.threshold = top ? 8000 : 12000; + _state.petals[i].base.kind = petal_pad_base; + _state.petals[i].base.threshold = top ? 8000 : 12000; + } + + _top.callback = _on_chip_data; + _top.user = &_top; + _bot.callback = _on_chip_data; + _bot.user = &_bot; + + if ((ret = flow3r_bsp_ad7147_chip_init( + &_top, flow3r_i2c_addresses.touch_top)) != ESP_OK) { + return ret; + } + if ((ret = flow3r_bsp_ad7147_chip_init( + &_bot, flow3r_i2c_addresses.touch_bottom)) != ESP_OK) { + return ret; + } + ESP_LOGI(TAG, "Captouch initialized"); + + if ((ret = gpio_install_isr_service(ESP_INTR_FLAG_SHARED | + ESP_INTR_FLAG_LOWMED)) != ESP_OK) { + ESP_LOGE(TAG, "Failed to install GPIO ISR service"); + return ret; + } + if ((ret = _gpio_interrupt_setup(_interrupt_gpio_bot, _bot_isr)) != + ESP_OK) { + 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; + } + + xTaskCreate(&_task, "captouch", 4096, NULL, configMAX_PRIORITIES - 1, NULL); + _kickstart(); + return ESP_OK; +} + +const flow3r_bsp_captouch_petal_pad_state_t * +flow3r_bsp_captouch_pad_for_petal_const( + const flow3r_bsp_captouch_petal_state_t *petal, petal_pad_kind_t kind) { + switch (kind) { + case petal_pad_tip: + return &petal->tip; + case petal_pad_base: + return &petal->base; + case petal_pad_cw: + return &petal->cw; + case petal_pad_ccw: + return &petal->ccw; + } + assert(0); +} + +flow3r_bsp_captouch_petal_pad_state_t *flow3r_bsp_captouch_pad_for_petal( + flow3r_bsp_captouch_petal_state_t *petal, petal_pad_kind_t kind) { + switch (kind) { + case petal_pad_tip: + return &petal->tip; + case petal_pad_base: + return &petal->base; + case petal_pad_cw: + return &petal->cw; + case petal_pad_ccw: + return &petal->ccw; + } + assert(0); +} + +void flow3r_bsp_captouch_calibrate() { + _bot.calibration_pending = true; + _top.calibration_pending = true; +} + +bool flow3r_bsp_captouch_calibrating() { + bool bot = _bot.calibration_pending || _bot.calibration_cycles > 0; + bool top = _top.calibration_pending || _top.calibration_cycles > 0; + return bot || top; +} \ No newline at end of file diff --git a/components/flow3r_bsp/flow3r_bsp_captouch.h b/components/flow3r_bsp/flow3r_bsp_captouch.h new file mode 100644 index 0000000000000000000000000000000000000000..65a752c30c92d4222dd569ee0a7cedbccc44e449 --- /dev/null +++ b/components/flow3r_bsp/flow3r_bsp_captouch.h @@ -0,0 +1,88 @@ +#pragma once + +// Highest-level driver for captouch on the flow3r badge. Uses 2x AD7147. + +// The flow3r has 10 touch petals. 5 petals on the top layer, 5 petals on the +// bottom layer. +// +// Top petals have three capacitive pads. Bottom petals have two capacitive +// pads. +// +// The petals are numbered from 0 to 9 (inclusive). Petal 0 is next to the USB +// port, and is a top petal. Petal 1 is a bottom petal to its left. Petal 2 is a +// top petal to its left, and the rest continue counter-clockwise accordingly. + +#include "esp_err.h" + +#include <stdbool.h> + +// One of the four possible touch points (pads) on a petal. Top petals have +// base/cw/ccw. Bottom petals have base/tip. +typedef enum { + // Pad away from centre of badge. + petal_pad_tip = 0, + // Pad going counter-clockwise around the badge. + petal_pad_ccw = 1, + // Pad going clockwise around the badge. + petal_pad_cw = 2, + // Pad going towards the centre of the badge. + petal_pad_base = 3, +} petal_pad_kind_t; + +// Each petal can be either top or bottom. +typedef enum { + // Petal on the top layer. Has base, cw, ccw pads. + petal_top = 0, + // petal on the bottom layer. Has base and tip fields. + petal_bottom = 1, +} petal_kind_t; + +// State of a petal's pad. +typedef struct { + // Is it a top or bottom petal? + petal_pad_kind_t kind; + // Raw value, compensated for ambient value. + uint16_t raw; + // Configured threshold for touch detection. + uint16_t threshold; +} flow3r_bsp_captouch_petal_pad_state_t; + +// State of a petal. Only the fields relevant to the petal kind (tip/base or +// base/cw/ccw) are present. +typedef struct { + petal_kind_t kind; + flow3r_bsp_captouch_petal_pad_state_t tip; + flow3r_bsp_captouch_petal_pad_state_t ccw; + flow3r_bsp_captouch_petal_pad_state_t cw; + flow3r_bsp_captouch_petal_pad_state_t base; +} flow3r_bsp_captouch_petal_state_t; + +// State of all petals of the badge. +typedef struct { + flow3r_bsp_captouch_petal_state_t petals[10]; +} flow3r_bsp_captouch_state_t; + +typedef void (*flow3r_bsp_captouch_callback_t)( + const flow3r_bsp_captouch_state_t *state); + +// Initialize captouch subsystem with a given callback. This callback will be +// called any time new captouch data is available. The given data will be valid +// for the lifetime of the function, so should be copied by users. +// +// An interrupt and task will be set up to handle the data processing. +esp_err_t flow3r_bsp_captouch_init(flow3r_bsp_captouch_callback_t callback); + +// Get a given petal's pad data for a given petal kind. +const flow3r_bsp_captouch_petal_pad_state_t * +flow3r_bsp_captouch_pad_for_petal_const( + const flow3r_bsp_captouch_petal_state_t *petal, petal_pad_kind_t kind); +flow3r_bsp_captouch_petal_pad_state_t *flow3r_bsp_captouch_pad_for_petal( + flow3r_bsp_captouch_petal_state_t *petal, petal_pad_kind_t kind); + +// Request captouch calibration. +void flow3r_bsp_captouch_calibrate(); + +// Returns true if captouch is currently calibrating. +// +// TODO(q3k): this seems glitchy, investigate. +bool flow3r_bsp_captouch_calibrating(); \ No newline at end of file