Modularization, stage 1
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "unit_base.h"
|
||||
#include "unit_touch.h"
|
||||
|
||||
#define TOUCH_INTERNAL
|
||||
#include "_touch_internal.h"
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
//
|
||||
// Created by MightyPork on 2018/02/25.
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "unit_base.h"
|
||||
#include "unit_touch.h"
|
||||
|
||||
#define TOUCH_INTERNAL
|
||||
|
||||
#include "_touch_internal.h"
|
||||
|
||||
// discharge time in ms
|
||||
#define DIS_TIME 1
|
||||
|
||||
static void startNextPhase(Unit *unit);
|
||||
|
||||
static void UTOUCH_EventReportJob(Job *job)
|
||||
{
|
||||
Unit *unit = job->unit;
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
uint8_t buf[8];
|
||||
PayloadBuilder pb = pb_start(buf, 8, NULL);
|
||||
pb_u32(&pb, pinmask_pack_32(~job->data1, priv->all_channels_mask)); // inverted and packed - all pins (pressed state)
|
||||
pb_u32(&pb, pinmask_pack_32(job->data2, priv->all_channels_mask)); // trigger generating pins
|
||||
assert_param(pb.ok);
|
||||
|
||||
EventReport er = {
|
||||
.unit = unit,
|
||||
.type = 0x00,
|
||||
.length = 8,
|
||||
.data = buf,
|
||||
.timestamp = job->timestamp,
|
||||
};
|
||||
|
||||
EventReport_Send(&er);
|
||||
}
|
||||
|
||||
static void UTOUCH_CheckForBinaryEvents(Unit *const unit)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
const uint32_t time_ms = PTIM_GetTime();
|
||||
|
||||
if (priv->last_done_ms == 0) {
|
||||
// avoid bug with trigger on first capture
|
||||
priv->last_done_ms = time_ms;
|
||||
}
|
||||
|
||||
const uint64_t ts = PTIM_GetMicrotime();
|
||||
uint32_t eventpins = 0;
|
||||
|
||||
const uint16_t ms_elapsed = (uint16_t) (time_ms - priv->last_done_ms);
|
||||
|
||||
for (uint16_t i = 0; i < 32; i++) {
|
||||
const uint32_t poke = (uint32_t) (1 << i);
|
||||
if (0 == (priv->all_channels_mask & poke)) continue;
|
||||
if (priv->binary_thr[i] == 0) continue; // skip disabled channels
|
||||
|
||||
const bool isactive = (bool) (priv->binary_active_bits & poke);
|
||||
const bool can_go_up = !isactive && (priv->readouts[i] > (priv->binary_thr[i] + priv->binary_hysteresis));
|
||||
const bool can_go_down = isactive && (priv->readouts[i] < priv->binary_thr[i]);
|
||||
|
||||
if (can_go_up) {
|
||||
priv->bin_trig_cnt[i] += ms_elapsed;
|
||||
if (priv->bin_trig_cnt[i] >= priv->binary_debounce_ms) {
|
||||
priv->binary_active_bits |= poke;
|
||||
priv->bin_trig_cnt[i] = 0; // reset for the other direction of the switch
|
||||
|
||||
eventpins |= poke;
|
||||
}
|
||||
}
|
||||
else if (priv->bin_trig_cnt[i] > 0) {
|
||||
priv->bin_trig_cnt[i] = 0;
|
||||
}
|
||||
|
||||
if (can_go_down) {
|
||||
priv->bin_trig_cnt[i] -= ms_elapsed;
|
||||
if (priv->bin_trig_cnt[i] <= -priv->binary_debounce_ms) {
|
||||
priv->binary_active_bits &= ~poke;
|
||||
priv->bin_trig_cnt[i] = 0; // reset for the other direction of the switch
|
||||
|
||||
eventpins |= poke;
|
||||
}
|
||||
}
|
||||
else if (priv->bin_trig_cnt[i] < 0) {
|
||||
priv->bin_trig_cnt[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (eventpins != 0) {
|
||||
Job j = {
|
||||
.timestamp = ts,
|
||||
.data1 = priv->binary_active_bits,
|
||||
.data2 = eventpins,
|
||||
.unit = unit,
|
||||
.cb = UTOUCH_EventReportJob,
|
||||
};
|
||||
|
||||
scheduleJob(&j);
|
||||
}
|
||||
|
||||
priv->last_done_ms = time_ms;
|
||||
}
|
||||
|
||||
void UTOUCH_HandleIrq(void *arg)
|
||||
{
|
||||
Unit *unit = arg;
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
if (TSC->ISR & TSC_ISR_MCEF) {
|
||||
priv->status = UTSC_STATUS_FAIL;
|
||||
dbg_touch("TSC Failure.");
|
||||
TSC->ICR = TSC_ICR_EOAIC | TSC_ICR_MCEIC;
|
||||
}
|
||||
|
||||
if (TSC->ISR & TSC_ISR_EOAF) {
|
||||
TSC->ICR = TSC_ICR_EOAIC;
|
||||
// assert_param((TSC->IOGCSR>>16) == priv->groups_phase[priv->next_phase]);
|
||||
|
||||
// Store captured data
|
||||
const uint32_t chmask = TSC->IOCCR;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (chmask & (1<<i)) {
|
||||
priv->readouts[i] = (uint16_t) (TSC->IOGXCR[i >> 2] & 0x3FFF);
|
||||
}
|
||||
}
|
||||
|
||||
priv->next_phase++;
|
||||
|
||||
if (!priv->cfg.interlaced) {
|
||||
// check if we've run out of existing or populated groups
|
||||
if (priv->next_phase == 3 || priv->groups_phase[priv->next_phase] == 0) {
|
||||
priv->next_phase = 0;
|
||||
priv->status = UTSC_STATUS_READY;
|
||||
UTOUCH_CheckForBinaryEvents(unit);
|
||||
}
|
||||
}
|
||||
|
||||
TSC->CR &= ~TSC_CR_IODEF; // pull low - discharge
|
||||
}
|
||||
|
||||
priv->ongoing = false;
|
||||
priv->discharge_delay = DIS_TIME;
|
||||
}
|
||||
|
||||
#if TSC_DEBUG
|
||||
static volatile uint32_t xcnt=0;
|
||||
#endif
|
||||
|
||||
void UTOUCH_updateTick(Unit *unit)
|
||||
{
|
||||
#if TSC_DEBUG
|
||||
xcnt++;
|
||||
#endif
|
||||
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
if (priv->ongoing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (priv->discharge_delay > 0) {
|
||||
priv->discharge_delay--;
|
||||
} else {
|
||||
startNextPhase(unit);
|
||||
}
|
||||
|
||||
#if TSC_DEBUG
|
||||
if(xcnt >= 250) {
|
||||
xcnt=0;
|
||||
PRINTF("> ");
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (priv->all_channels_mask & (1<<i)) {
|
||||
PRINTF("%d ", (int)priv->readouts[i]);
|
||||
}
|
||||
}
|
||||
PRINTF("\r\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void startNextPhase(Unit *unit)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
if (priv->all_channels_mask == 0) return;
|
||||
|
||||
if (priv->cfg.interlaced) {
|
||||
// Find the next non-zero bit, wrap around if needed
|
||||
while ((priv->all_channels_mask & (1<<priv->next_phase))==0) {
|
||||
priv->next_phase++;
|
||||
if (priv->next_phase == 32) {
|
||||
priv->next_phase = 0;
|
||||
priv->status = UTSC_STATUS_READY;
|
||||
UTOUCH_CheckForBinaryEvents(unit);
|
||||
}
|
||||
}
|
||||
TSC->IOGCSR = (uint32_t) (1 << (priv->next_phase >> 2)); // phase divided by 4
|
||||
TSC->IOCCR = (uint32_t) (1 << priv->next_phase);
|
||||
|
||||
// interlaced - float neighbouring electrodes
|
||||
TSC->CR |= TSC_CR_IODEF;
|
||||
} else {
|
||||
TSC->IOGCSR = priv->groups_phase[priv->next_phase];
|
||||
TSC->IOCCR = priv->channels_phase[priv->next_phase];
|
||||
|
||||
// separate - keep neighbouring electrodes at GND
|
||||
}
|
||||
|
||||
TSC->ICR = TSC_ICR_EOAIC | TSC_ICR_MCEIC;
|
||||
|
||||
// Go!
|
||||
priv->ongoing = true;
|
||||
TSC->CR |= TSC_CR_START;
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "unit_base.h"
|
||||
|
||||
#define TOUCH_INTERNAL
|
||||
|
||||
#include "_touch_internal.h"
|
||||
|
||||
/** Allocate data structure and set defaults */
|
||||
error_t UTOUCH_preInit(Unit *unit)
|
||||
{
|
||||
struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv));
|
||||
if (priv == NULL) return E_OUT_OF_MEM;
|
||||
|
||||
priv->cfg.charge_time = 2;
|
||||
priv->cfg.drain_time = 2;
|
||||
priv->cfg.spread_deviation = 0;
|
||||
priv->cfg.ss_presc = 1;
|
||||
priv->cfg.pg_presc = 32;
|
||||
priv->cfg.sense_timeout = 7;
|
||||
memset(priv->cfg.group_scaps, 0, 8);
|
||||
memset(priv->cfg.group_channels, 0, 8);
|
||||
priv->cfg.binary_hysteresis = 10;
|
||||
priv->cfg.binary_debounce_ms = 20;
|
||||
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
/** Finalize unit set-up */
|
||||
error_t UTOUCH_init(Unit *unit)
|
||||
{
|
||||
bool suc = true;
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
unit->tick_interval = 1; // sample every 1 ms
|
||||
|
||||
// copy from conf
|
||||
priv->binary_debounce_ms = priv->cfg.binary_debounce_ms;
|
||||
priv->binary_hysteresis = priv->cfg.binary_hysteresis;
|
||||
|
||||
TRY(rsc_claim(unit, R_TSC));
|
||||
|
||||
// simple bound checks, just clamp without error
|
||||
if (priv->cfg.charge_time > 16) priv->cfg.charge_time = 16;
|
||||
if (priv->cfg.charge_time < 1) priv->cfg.charge_time = 1;
|
||||
|
||||
if (priv->cfg.drain_time > 16) priv->cfg.drain_time = 16;
|
||||
if (priv->cfg.drain_time < 1) priv->cfg.drain_time = 1;
|
||||
|
||||
if (priv->cfg.spread_deviation > 128) priv->cfg.drain_time = 128;
|
||||
|
||||
if (priv->cfg.ss_presc > 2) priv->cfg.ss_presc = 2;
|
||||
if (priv->cfg.ss_presc < 1) priv->cfg.ss_presc = 1;
|
||||
|
||||
if (priv->cfg.sense_timeout > 7) priv->cfg.sense_timeout = 7;
|
||||
if (priv->cfg.sense_timeout < 1) priv->cfg.sense_timeout = 1;
|
||||
|
||||
uint8_t tmppgpresc = priv->cfg.pg_presc;
|
||||
if (tmppgpresc == 0) return E_BAD_CONFIG;
|
||||
uint8_t pgpresc_reg = 0;
|
||||
while ((tmppgpresc & 1) == 0 && tmppgpresc != 0) {
|
||||
pgpresc_reg++;
|
||||
tmppgpresc >>= 1;
|
||||
}
|
||||
if (tmppgpresc != 1 || pgpresc_reg > 7) {
|
||||
dbg("Bad pgpresc");
|
||||
return E_BAD_CONFIG; // TODO better reporting
|
||||
}
|
||||
|
||||
if ((pgpresc_reg==0 && priv->cfg.drain_time<=2) || (pgpresc_reg==1 && priv->cfg.drain_time==0)) {
|
||||
dbg("Illegal PGPSC vs CTPL");
|
||||
return E_BAD_CONFIG;
|
||||
}
|
||||
|
||||
// enable clock
|
||||
hw_periph_clock_enable(TSC);
|
||||
// reset
|
||||
__HAL_RCC_TSC_FORCE_RESET();
|
||||
__HAL_RCC_TSC_RELEASE_RESET();
|
||||
|
||||
priv->all_channels_mask = 0;
|
||||
|
||||
for (int gi = 0; gi < 8; gi++) {
|
||||
const uint8_t cap = priv->cfg.group_scaps[gi];
|
||||
const uint8_t ch = priv->cfg.group_channels[gi];
|
||||
|
||||
if (cap == 0) {
|
||||
if (ch != 0) {
|
||||
dbg_touch("TSC group %d has no cap!", (int) (gi + 1));
|
||||
return E_BAD_CONFIG;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == 0) continue; // if no channels, don't bother setting up anything
|
||||
|
||||
if (cap != 2 && cap != 4 && cap != 8 && cap != 16) {
|
||||
dbg_touch("TSC group %d has more than 1 cap!", (int) (gi + 1));
|
||||
return E_BAD_CONFIG;
|
||||
}
|
||||
|
||||
if (cap & ch) {
|
||||
dbg_touch("TSC pin can't be both channel and cap! (gpr %d)", (int) (gi + 1));
|
||||
return E_BAD_CONFIG;
|
||||
}
|
||||
|
||||
// This is a loop through the pins in a group gi
|
||||
int phasenum = 0;
|
||||
for (int pi = 0; pi < 4; pi++) {
|
||||
// pin numbers are 1-based in the config
|
||||
const bool iscap = 0 != (cap & (2 << pi));
|
||||
const bool isch = 0 != (ch & (2 << pi));
|
||||
|
||||
if (!iscap && !isch) continue;
|
||||
|
||||
Resource r = utouch_group_rscs[gi][pi];
|
||||
TRY(rsc_claim(unit, r));
|
||||
|
||||
GPIO_TypeDef *port;
|
||||
uint32_t ll;
|
||||
assert_param(hw_pinrsc2ll(r, &port, &ll));
|
||||
LL_GPIO_SetPinOutputType(port, ll, isch ? LL_GPIO_OUTPUT_PUSHPULL : LL_GPIO_OUTPUT_OPENDRAIN);
|
||||
|
||||
// 7 and 8 (1-based) use AF1, else AF3
|
||||
TRY(hw_configure_gpiorsc_af(r, gi >= 6 ? LL_GPIO_AF_1 : LL_GPIO_AF_3));
|
||||
|
||||
uint32_t bit = (uint32_t) (1 << (gi * 4 + pi));
|
||||
|
||||
if (iscap) {
|
||||
dbg_touch("TSC cap @ %s", rsc_get_name(r));
|
||||
// Sampling cap
|
||||
TSC->IOSCR |= bit;
|
||||
|
||||
// Disable pin hysteresis (causes noise)
|
||||
TSC->IOHCR ^= bit;
|
||||
}
|
||||
else {
|
||||
dbg_touch("TSC ch @ %s", rsc_get_name(r));
|
||||
|
||||
if (priv->cfg.interlaced) {
|
||||
// interlaced - only update the mask beforehand
|
||||
priv->all_channels_mask |= bit;
|
||||
} else {
|
||||
// channels are configured individually when read.
|
||||
// we prepare bitmaps to use for the read groups (all can be read in at most 3 steps)
|
||||
priv->channels_phase[phasenum] |= bit; // this is used for the channel selection register
|
||||
priv->groups_phase[phasenum] |= 1 << gi; // this will be used for the group enable register, if all 0, this and any following phases are unused.
|
||||
phasenum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// common TSC config
|
||||
TSC->CR =
|
||||
((priv->cfg.charge_time - 1) << TSC_CR_CTPH_Pos) |
|
||||
((priv->cfg.drain_time - 1) << TSC_CR_CTPL_Pos) |
|
||||
((priv->cfg.ss_presc - 1) << TSC_CR_SSPSC_Pos) |
|
||||
(pgpresc_reg << TSC_CR_PGPSC_Pos) |
|
||||
((priv->cfg.sense_timeout - 1) << TSC_CR_MCV_Pos) |
|
||||
TSC_CR_TSCE;
|
||||
|
||||
if (priv->cfg.spread_deviation > 0) {
|
||||
TSC->CR |= ((priv->cfg.spread_deviation - 1) << TSC_CR_SSD_Pos) | TSC_CR_SSE;
|
||||
}
|
||||
|
||||
dbg_touch("CR = %08x, ht is %d, lt is %d", (int)TSC->CR,
|
||||
(int)priv->cfg.charge_time,
|
||||
(int)priv->cfg.drain_time);
|
||||
|
||||
// iofloat is used for discharging
|
||||
|
||||
// Enable the interrupts
|
||||
TSC->IER = TSC_IER_EOAIE | TSC_IER_MCEIE;
|
||||
irqd_attach(TSC, UTOUCH_HandleIrq, unit);
|
||||
|
||||
if (!priv->cfg.interlaced) {
|
||||
dbg_touch("TSC phases:");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
priv->all_channels_mask |= priv->channels_phase[i];
|
||||
|
||||
dbg_touch(" %d: ch %08"PRIx32", g %02"PRIx32,
|
||||
i + 1,
|
||||
priv->channels_phase[i],
|
||||
(uint32_t) priv->groups_phase[i]);
|
||||
}
|
||||
}
|
||||
|
||||
priv->status = UTSC_STATUS_BUSY; // first loop ...
|
||||
priv->next_phase = 0;
|
||||
|
||||
// starts in the tick callback
|
||||
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/** Tear down the unit */
|
||||
void UTOUCH_deInit(Unit *unit)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
// de-init peripherals
|
||||
if (unit->status == E_SUCCESS) {
|
||||
hw_periph_clock_disable(TSC);
|
||||
// clear all registers to their default values
|
||||
__HAL_RCC_TSC_FORCE_RESET();
|
||||
__HAL_RCC_TSC_RELEASE_RESET();
|
||||
|
||||
irqd_detach(TSC, UTOUCH_HandleIrq);
|
||||
}
|
||||
|
||||
// Release all resources, deinit pins
|
||||
rsc_teardown(unit);
|
||||
|
||||
// Free memory
|
||||
free_ck(unit->data);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
|
||||
#ifndef GEX_F072_TOUCH_INTERNAL_H
|
||||
#define GEX_F072_TOUCH_INTERNAL_H
|
||||
|
||||
#ifndef TOUCH_INTERNAL
|
||||
#error bad include!
|
||||
#endif
|
||||
|
||||
#include "unit_base.h"
|
||||
|
||||
#define TSC_DEBUG 0
|
||||
|
||||
#if TSC_DEBUG
|
||||
#define dbg_touch(f,...) dbg(f,##__VA_ARGS__)
|
||||
#else
|
||||
#define dbg_touch(f,...) do{}while(0)
|
||||
#endif
|
||||
|
||||
enum utsc_status {
|
||||
UTSC_STATUS_BUSY = 0,
|
||||
UTSC_STATUS_READY = 1,
|
||||
UTSC_STATUS_FAIL = 2
|
||||
};
|
||||
|
||||
/** Private data structure */
|
||||
struct priv {
|
||||
// settings
|
||||
struct {
|
||||
uint8_t charge_time; // 1-16 -> 0..15
|
||||
uint8_t drain_time; // 1-16 -> 0..15
|
||||
uint8_t spread_deviation; // 1-128, 0=off ... 0-127, 0 sets 0 to SSE
|
||||
uint8_t ss_presc; // 1-2 -> 0..1
|
||||
uint8_t pg_presc; // 1,2,4,8,16,32,64,128 -> 0..7 when writing to the periph
|
||||
uint8_t sense_timeout; // 1-7 -> 0..6 hex when writing to the periph
|
||||
// the schmitts must be disabled on all used channels, restored to 0xFFFF on deinit
|
||||
uint8_t group_scaps[8];
|
||||
uint8_t group_channels[8];
|
||||
bool interlaced;
|
||||
uint16_t binary_debounce_ms;
|
||||
uint16_t binary_hysteresis;
|
||||
} cfg;
|
||||
|
||||
uint8_t next_phase;
|
||||
uint8_t discharge_delay;
|
||||
uint32_t channels_phase[3];
|
||||
uint8_t groups_phase[3];
|
||||
uint16_t readouts[32];
|
||||
int16_t bin_trig_cnt[32];
|
||||
uint16_t binary_debounce_ms;
|
||||
uint16_t binary_hysteresis;
|
||||
uint16_t binary_thr[32];
|
||||
uint32_t binary_active_bits;
|
||||
uint32_t all_channels_mask;
|
||||
uint32_t last_done_ms;
|
||||
bool ongoing;
|
||||
|
||||
enum utsc_status status;
|
||||
} __attribute__((packed));
|
||||
|
||||
extern const char *utouch_group_labels[8];
|
||||
extern const Resource utouch_group_rscs[8][4];
|
||||
|
||||
/** Allocate data structure and set defaults */
|
||||
error_t UTOUCH_preInit(Unit *unit);
|
||||
|
||||
/** Load from a binary buffer stored in Flash */
|
||||
void UTOUCH_loadBinary(Unit *unit, PayloadParser *pp);
|
||||
|
||||
/** Write to a binary buffer for storing in Flash */
|
||||
void UTOUCH_writeBinary(Unit *unit, PayloadBuilder *pb);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Parse a key-value pair from the INI file */
|
||||
error_t UTOUCH_loadIni(Unit *unit, const char *key, const char *value);
|
||||
|
||||
/** Generate INI file section for the unit */
|
||||
void UTOUCH_writeIni(Unit *unit, IniWriter *iw);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Finalize unit set-up */
|
||||
error_t UTOUCH_init(Unit *unit);
|
||||
|
||||
/** Tear down the unit */
|
||||
void UTOUCH_deInit(Unit *unit);
|
||||
|
||||
void UTOUCH_updateTick(Unit *unit);
|
||||
|
||||
void UTOUCH_HandleIrq(void *arg);
|
||||
|
||||
#endif //GEX_F072_TOUCH_INTERNAL_H
|
||||
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "unit_base.h"
|
||||
|
||||
#define TOUCH_INTERNAL
|
||||
#include "_touch_internal.h"
|
||||
|
||||
const char *utouch_group_labels[8] = {
|
||||
"1:A0, 2:A1, 3:A2, 4:A3",
|
||||
"1:A4, 2:A5, 3:A6, 4:A7",
|
||||
"1:C5, 2:B0, 3:B1, 4:B2",
|
||||
"1:A9, 2:A10, 3:A11, 4:A12",
|
||||
"1:B3, 2:B4, 3:B6, 4:B7",
|
||||
"1:B11, 2:B12, 3:B13, 4:B14",
|
||||
"1:E2, 2:E3, 3:E4, 4:E5",
|
||||
"1:D12, 2:D13, 3:D14, 4:D15",
|
||||
};
|
||||
|
||||
const Resource utouch_group_rscs[8][4] = {
|
||||
{R_PA0, R_PA1, R_PA2, R_PA3},
|
||||
{R_PA4, R_PA5, R_PA6, R_PA7},
|
||||
{R_PC5, R_PB0, R_PB1, R_PB2},
|
||||
{R_PA9, R_PA10, R_PA11, R_PA12},
|
||||
{R_PB3, R_PB4, R_PB6, R_PB7},
|
||||
{R_PB11, R_PB12, R_PB13, R_PB14},
|
||||
{R_PE2, R_PE3, R_PE4, R_PE5},
|
||||
{R_PD12, R_PD13, R_PD14, R_PD15},
|
||||
};
|
||||
|
||||
/** Load from a binary buffer stored in Flash */
|
||||
void UTOUCH_loadBinary(Unit *unit, PayloadParser *pp)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
uint8_t version = pp_u8(pp);
|
||||
(void)version;
|
||||
|
||||
priv->cfg.charge_time = pp_u8(pp);
|
||||
priv->cfg.drain_time = pp_u8(pp);
|
||||
priv->cfg.spread_deviation = pp_u8(pp);
|
||||
priv->cfg.ss_presc = pp_u8(pp);
|
||||
priv->cfg.pg_presc = pp_u8(pp);
|
||||
priv->cfg.sense_timeout = pp_u8(pp);
|
||||
pp_buf(pp, priv->cfg.group_scaps, 8);
|
||||
pp_buf(pp, priv->cfg.group_channels, 8);
|
||||
|
||||
if (version >= 1) {
|
||||
priv->cfg.interlaced = pp_bool(pp);
|
||||
}
|
||||
|
||||
if (version >= 2) {
|
||||
priv->cfg.binary_debounce_ms = pp_u16(pp);
|
||||
priv->cfg.binary_hysteresis = pp_u16(pp);
|
||||
}
|
||||
}
|
||||
|
||||
/** Write to a binary buffer for storing in Flash */
|
||||
void UTOUCH_writeBinary(Unit *unit, PayloadBuilder *pb)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
pb_u8(pb, 2); // version
|
||||
|
||||
pb_u8(pb, priv->cfg.charge_time);
|
||||
pb_u8(pb, priv->cfg.drain_time);
|
||||
pb_u8(pb, priv->cfg.spread_deviation);
|
||||
pb_u8(pb, priv->cfg.ss_presc);
|
||||
pb_u8(pb, priv->cfg.pg_presc);
|
||||
pb_u8(pb, priv->cfg.sense_timeout);
|
||||
pb_buf(pb, priv->cfg.group_scaps, 8);
|
||||
pb_buf(pb, priv->cfg.group_channels, 8);
|
||||
pb_bool(pb, priv->cfg.interlaced);
|
||||
pb_u16(pb, priv->cfg.binary_debounce_ms);
|
||||
pb_u16(pb, priv->cfg.binary_hysteresis);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Parse a key-value pair from the INI file */
|
||||
error_t UTOUCH_loadIni(Unit *unit, const char *key, const char *value)
|
||||
{
|
||||
bool suc = true;
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
if (streq(key, "charge-time")) {
|
||||
priv->cfg.charge_time = cfg_u8_parse(value, &suc);
|
||||
}
|
||||
else if (streq(key, "drain-time")) {
|
||||
priv->cfg.drain_time = cfg_u8_parse(value, &suc);
|
||||
}
|
||||
else if (streq(key, "ss-deviation")) {
|
||||
priv->cfg.spread_deviation = cfg_u8_parse(value, &suc);
|
||||
}
|
||||
else if (streq(key, "ss-clock-prediv")) {
|
||||
priv->cfg.ss_presc = cfg_u8_parse(value, &suc);
|
||||
}
|
||||
else if (streq(key, "pg-clock-prediv")) {
|
||||
priv->cfg.pg_presc = cfg_u8_parse(value, &suc);
|
||||
}
|
||||
else if (streq(key, "sense-timeout")) {
|
||||
priv->cfg.sense_timeout = cfg_u8_parse(value, &suc);
|
||||
}
|
||||
else if (streq(key, "interlaced-pads")) {
|
||||
priv->cfg.interlaced = cfg_bool_parse(value, &suc);
|
||||
}
|
||||
else if (streq(key, "btn-debounce")) {
|
||||
priv->cfg.binary_debounce_ms = cfg_u16_parse(value, &suc);
|
||||
}
|
||||
else if (streq(key, "btn-hysteresis")) {
|
||||
priv->cfg.binary_hysteresis = cfg_u16_parse(value, &suc);
|
||||
}
|
||||
else {
|
||||
volatile char namebuf[10]; // must be volatile or gcc optimizes out the second compare and fucks it up
|
||||
|
||||
for (int i = 0; i < 6; i++) { // skip 7,8
|
||||
SPRINTF(namebuf, "g%d_cap", i+1);
|
||||
if (streq(key, namebuf)) {
|
||||
priv->cfg.group_scaps[i] = (uint8_t) cfg_pinmask_parse(value, &suc);
|
||||
goto matched;
|
||||
}
|
||||
|
||||
SPRINTF(namebuf, "g%d_ch", i+1);
|
||||
if (streq(key, namebuf)) {
|
||||
priv->cfg.group_channels[i] = (uint8_t) cfg_pinmask_parse(value, &suc);
|
||||
goto matched;
|
||||
}
|
||||
}
|
||||
|
||||
return E_BAD_KEY;
|
||||
}
|
||||
|
||||
matched:
|
||||
if (!suc) return E_BAD_VALUE;
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
/** Generate INI file section for the unit */
|
||||
void UTOUCH_writeIni(Unit *unit, IniWriter *iw)
|
||||
{
|
||||
struct priv *priv = unit->data;
|
||||
|
||||
iw_comment(iw, "This unit utilizes the touch sensing controller.");
|
||||
iw_comment(iw, "See the reference manual for details about its function.");
|
||||
iw_cmt_newline(iw);
|
||||
|
||||
iw_comment(iw, "Pulse generator clock prescaller (1,2,4,...,128)");
|
||||
iw_entry_d(iw, "pg-clock-prediv", priv->cfg.pg_presc);
|
||||
iw_comment(iw, "Sense pad charging time (1-16)");
|
||||
iw_entry_d(iw, "charge-time", priv->cfg.charge_time);
|
||||
iw_comment(iw, "Charge transfer time (1-16)");
|
||||
iw_entry_d(iw, "drain-time", priv->cfg.drain_time);
|
||||
iw_comment(iw, "Measurement timeout (1-7)");
|
||||
iw_entry_d(iw, "sense-timeout", priv->cfg.sense_timeout);
|
||||
|
||||
iw_cmt_newline(iw);
|
||||
iw_comment(iw, "Spread spectrum max deviation (0-128,0=off)");
|
||||
iw_entry_d(iw, "ss-deviation", priv->cfg.spread_deviation);
|
||||
iw_comment(iw, "Spreading clock prescaller (1,2)");
|
||||
iw_entry_d(iw, "ss-clock-prediv", priv->cfg.ss_presc);
|
||||
|
||||
iw_cmt_newline(iw);
|
||||
iw_comment(iw, "Optimize for interlaced pads (individual sampling with others floating)");
|
||||
iw_entry_s(iw, "interlaced-pads", str_yn(priv->cfg.interlaced));
|
||||
|
||||
iw_cmt_newline(iw);
|
||||
iw_comment(iw, "Button mode debounce (ms) and release hysteresis (lsb)");
|
||||
iw_entry_d(iw, "btn-debounce", priv->cfg.binary_debounce_ms);
|
||||
iw_entry_d(iw, "btn-hysteresis", priv->cfg.binary_hysteresis);
|
||||
|
||||
iw_cmt_newline(iw);
|
||||
iw_comment(iw, "Each used group must have 1 sampling capacitor and 1-3 channels.");
|
||||
iw_comment(iw, "Channels are numbered 1,2,3,4");
|
||||
iw_cmt_newline(iw);
|
||||
|
||||
char namebuf[10];
|
||||
for (int i = 0; i < 6; i++) { // skip 7,8
|
||||
iw_commentf(iw, "Group%d - %s", i+1, utouch_group_labels[i]);
|
||||
SPRINTF(namebuf, "g%d_cap", i+1);
|
||||
iw_entry_s(iw, namebuf, cfg_pinmask_encode(priv->cfg.group_scaps[i], unit_tmp512, true));
|
||||
SPRINTF(namebuf, "g%d_ch", i+1);
|
||||
iw_entry_s(iw, namebuf, cfg_pinmask_encode(priv->cfg.group_channels[i], unit_tmp512, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/25.
|
||||
//
|
||||
|
||||
#include "unit_base.h"
|
||||
#include "unit_touch.h"
|
||||
|
||||
#define TOUCH_INTERNAL
|
||||
#include "_touch_internal.h"
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
enum TouchCmd_ {
|
||||
CMD_READ=0,
|
||||
CMD_SET_BIN_THR=1,
|
||||
CMD_DISABLE_ALL_REPORTS=2,
|
||||
CMD_SET_DEBOUNCE_TIME=3,
|
||||
CMD_SET_HYSTERESIS=4,
|
||||
CMD_GET_CH_COUNT=10,
|
||||
};
|
||||
|
||||
/** Handle a request message */
|
||||
static error_t UTOUCH_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp)
|
||||
{
|
||||
struct priv* priv = unit->data;
|
||||
PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL);
|
||||
|
||||
switch (command) {
|
||||
/**
|
||||
* read the current touch pad values (smaller = higher capacity)
|
||||
*
|
||||
* resp: a list of u16 (order: group and pin, ascending)
|
||||
*/
|
||||
case CMD_READ:
|
||||
if (priv->status == UTSC_STATUS_BUSY) return E_BUSY;
|
||||
if (priv->status == UTSC_STATUS_FAIL) return E_HW_TIMEOUT;
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (priv->all_channels_mask & (1<<i)) {
|
||||
pb_u16(&pb, priv->readouts[i]);
|
||||
}
|
||||
}
|
||||
com_respond_pb(frame_id, MSG_SUCCESS, &pb);
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Set thresholds for the button mode.
|
||||
*
|
||||
* pld: a list of u16 for the enabled channels (order: group and pin, ascending)
|
||||
*/
|
||||
case CMD_SET_BIN_THR:
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (priv->all_channels_mask & (1<<i)) {
|
||||
priv->bin_trig_cnt[i] = 0;
|
||||
priv->binary_thr[i] = pp_u16(pp);
|
||||
if (priv->readouts[i] >= (priv->binary_thr[i] + priv->binary_hysteresis)) {
|
||||
priv->binary_active_bits |= 1<<i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Set the debounce time in ms (replaces the default value from settings)
|
||||
*
|
||||
* pld: ms:u16
|
||||
*/
|
||||
case CMD_SET_DEBOUNCE_TIME:
|
||||
priv->binary_debounce_ms = pp_u16(pp);
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Set hysteresis (replaces the default value from settings)
|
||||
*
|
||||
* Hysteresis is added to the threshold value for the switch-off level
|
||||
* (switch-off happens when the measured value is exceeded - capacity of the pad drops)
|
||||
*
|
||||
* pld: hyst:u16
|
||||
*/
|
||||
case CMD_SET_HYSTERESIS:
|
||||
priv->binary_hysteresis = pp_u16(pp);
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Disable button mode reports. This effectively sets all thresholds to 0, disabling checking.
|
||||
*/
|
||||
case CMD_DISABLE_ALL_REPORTS:
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (priv->all_channels_mask & (1<<i)) {
|
||||
priv->binary_thr[i] = 0;
|
||||
priv->bin_trig_cnt[i] = 0;
|
||||
}
|
||||
}
|
||||
priv->binary_active_bits = 0;
|
||||
return E_SUCCESS;
|
||||
|
||||
/**
|
||||
* Get the number of configured touch pad channels
|
||||
*
|
||||
* resp: count:u8
|
||||
*/
|
||||
case CMD_GET_CH_COUNT:;
|
||||
uint8_t nb = 0;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (priv->all_channels_mask & (1<<i)) {
|
||||
nb++;
|
||||
}
|
||||
}
|
||||
pb_u8(&pb, nb);
|
||||
com_respond_pb(frame_id, MSG_SUCCESS, &pb);
|
||||
return E_SUCCESS;
|
||||
|
||||
default:
|
||||
return E_UNKNOWN_COMMAND;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Unit template */
|
||||
const UnitDriver UNIT_TOUCH = {
|
||||
.name = "TOUCH",
|
||||
.description = "Capacitive touch sensing",
|
||||
// Settings
|
||||
.preInit = UTOUCH_preInit,
|
||||
.cfgLoadBinary = UTOUCH_loadBinary,
|
||||
.cfgWriteBinary = UTOUCH_writeBinary,
|
||||
.cfgLoadIni = UTOUCH_loadIni,
|
||||
.cfgWriteIni = UTOUCH_writeIni,
|
||||
// Init
|
||||
.init = UTOUCH_init,
|
||||
.deInit = UTOUCH_deInit,
|
||||
// Function
|
||||
.handleRequest = UTOUCH_handleRequest,
|
||||
.updateTick = UTOUCH_updateTick,
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/25.
|
||||
//
|
||||
// Digital input unit; single or multiple pin read access on one port (A-F)
|
||||
//
|
||||
|
||||
#ifndef U_TOUCH_H
|
||||
#define U_TOUCH_H
|
||||
|
||||
#include "unit.h"
|
||||
|
||||
extern const UnitDriver UNIT_TOUCH;
|
||||
|
||||
// UU_ prototypes
|
||||
|
||||
#endif //U_TOUCH_H
|
||||
Reference in New Issue
Block a user