Skip to content

Commit

Permalink
Merge branch 'gpiod_aux_handlers'
Browse files Browse the repository at this point in the history
  • Loading branch information
nettings committed Mar 8, 2019
2 parents f1a49ae + 7e19f29 commit 2da0078
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 29 deletions.
124 changes: 98 additions & 26 deletions gpiod_process.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
#define MAXNAME 64
// debounce time windows, in us:
#define GPI_DEBOUNCE_SWITCH 50
#define GPI_DEBOUNCE_ROTARY 10
#define GPI_DEBOUNCE_ROTARY 0

typedef enum {
GPI_NOTSET,
Expand All @@ -42,11 +42,58 @@ typedef enum {

typedef struct {
line_type_t type;
int aux;
unsigned int aux;
unsigned long ts_last;
int ts_delta;
} line_t;

/* Yay. A bit-wise state machine :)
* State:
* nnnn
* |||+----> clk state
* ||+-----> dt state
* |+------> clockwise bit
* +-------> outer ring bit
* Two rings:
* Outer 1100*___ ____1000*
* / \/ \
* Inner / 0x00 \
* | / \ |
* | 0010 0101 |
* 1101 | | 1010
* \ 0011* 0111* /
* \ \ / /
* ----1x11----
*
* The starred states will fire a callback.
* Between unstarred states, we tolerate bouncing.
* A starred state cannot bounce back, because we
* change rings immediately, so that it cannot
* trigger again.
*/

#define CLK 0x1
#define DT 0x2
#define CLOCKWISE 0x4
#define OUTER 0x8

#define UPDATE_CLK(state,clk) (state = ((clk == 0) ? (state & ~CLK) : (state | CLK)))
#define UPDATE_DT(state,dt) (state = ((dt == 0) ? (state & ~DT) : (state | DT)))
#define GET_CLK(state) (state & CLK)
#define GET_DT(state) ((state & DT) >> 1)

#define IS_CLOCKWISE(state) ((state & CLOCKWISE) && 1)
#define SET_CLOCKWISE(state) (state |= CLOCKWISE)
#define SET_CCW(state) (state &= ~CLOCKWISE)

#define IS_OUTER(state) ((state & OUTER) && 1)
#define SET_OUTER(state) (state |= OUTER)
#define SET_INNER(state) (state &= ~OUTER)

#define FIRE_INNER(state) ((state & CLK) && (state & DT) && !IS_OUTER(state))
#define FIRE_OUTER(state) (!(state & CLK) && !(state &DT) && IS_OUTER(state))


static line_t *gpi[MAXGPIO] = { 0 };
static void (*user_callback)();

Expand All @@ -62,51 +109,73 @@ static unsigned long long usec_stamp(struct timespec t)
return (unsigned long long)((t.tv_sec * 1000000ULL) + (t.tv_nsec / 1000ULL));
}

static char* uint_pp(unsigned int bitfield, int nbits) {
char* output = calloc(sizeof(char), nbits);
for (int i=0; i<nbits; i++) {
int shift = nbits - i - 1;
output[i] = (char)(((bitfield & (1U << shift)) >> shift ) + '0');
}
return output;
}

static int handle_event(int event, unsigned int line, const struct timespec *timestamp,
void *data)
{
int pthis, pprev;
unsigned long long now;
int value;
unsigned int* state;

if (shutdown)
return GPIOD_CTXLESS_EVENT_CB_RET_STOP;

now = usec_stamp(*timestamp);
DBG("GPIOD handler at time %lld", now);
value = (event == GPIOD_CTXLESS_EVENT_CB_RISING_EDGE) ? 1 : 0;
if ((now - gpi[line]->ts_last) > gpi[line]->ts_delta) {
// we're not bouncing:
gpi[line]->ts_last = now;
switch (gpi[line]->type) {
case GPI_ROTARY:
// smart debouncing algo from https://www.technoblogy.com/show?1YHJ
// While clk may be bouncing, we store the current state of dt
// (which is assumed has settled by now). No matter how often we
// bounce, the stored state will be the same - basically a clean
// version of clk, so we use it instead!
pthis = gpiod_ctxless_get_value(device, gpi[line]->aux,
ACTIVE_HIGH, consumer);
// We compare it with the previous value (we stored it in the
// unused aux field of our aux line), to see how it changed:
pprev = gpi[gpi[line]->aux]->aux;
if (pthis == pprev) break; // nothing to do
if (pthis == (event == GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE) ? 1 : 0) {
user_callback(line, 1); // falling edge, clockwise
} else {
user_callback(line, -1); // rising edge, counterclockwise
}
gpi[gpi[line]->aux]->aux = pthis;
// lazy hack:
// store current value in aux field,
// because an aux can't have an aux.
state = &(gpi[gpi[line]->aux]->aux);
UPDATE_CLK(*state, value);
break;
case GPI_AUX:
state = &(gpi[line]->aux);
UPDATE_DT(*state, value);
break;
case GPI_SWITCH:
pthis = (event ==
GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE) ? 1 : 0;
user_callback(line, pthis);
user_callback(line, 1 - value); // look for falling edge
return GPIOD_CTXLESS_EVENT_CB_RET_OK; // skip state machine
break;
default:
ERR("No handler for type %d. THIS SHOULD NEVER HAPPEN.",
gpi[line]->type);
return GPIOD_CTXLESS_EVENT_CB_RET_ERR;
break;
}
gpi[line]->ts_last = now;
DBG("state before: %s", uint_pp(*state, 4));
// which direction?
if (IS_OUTER(*state)) {
if (GET_CLK(*state) < GET_DT(*state))
SET_CLOCKWISE(*state);
else if (GET_CLK(*state) > GET_DT(*state))
SET_CCW(*state);
} else if (GET_CLK(*state) > GET_DT(*state))
SET_CLOCKWISE(*state);
else if (GET_CLK(*state) < GET_DT(*state))
SET_CCW(*state);

if (FIRE_INNER(*state)) {
user_callback(line, -1 + 2 * IS_CLOCKWISE(*state));
SET_OUTER(*state);
} else if (FIRE_OUTER(*state)) {
user_callback(line, -1 + 2 * IS_CLOCKWISE(*state));
SET_INNER(*state);
}
DBG("state after: %s", uint_pp(*state, 4));
}
return GPIOD_CTXLESS_EVENT_CB_RET_OK;
}
Expand Down Expand Up @@ -137,10 +206,13 @@ int setup_GPIOD_rotary(int line, int aux)
return -ENOMEM;
}
gpi[line]->type = GPI_ROTARY;
gpi[aux]->type = GPI_AUX;
gpi[line]->aux = aux;
gpi[line]->ts_last = NEVER;
gpi[line]->ts_delta = GPI_DEBOUNCE_ROTARY;
gpi[aux]->type = GPI_AUX;
gpi[aux]->aux = 0;
gpi[aux]->ts_last = NEVER;
gpi[line]->ts_delta = GPI_DEBOUNCE_ROTARY;
return 0;
}

Expand Down Expand Up @@ -188,7 +260,7 @@ int start_GPIOD()
int err = 0;
DBG("Starting GPIOD handler.");
for (int line = 0; line < MAXGPIO; line++) {
if (gpi[line] != NULL && gpi[line]->type != GPI_AUX) {
if (gpi[line] != NULL) {
offsets[num_lines++] = line;
}
}
Expand Down
6 changes: 6 additions & 0 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ void update(control_t* c, int delta)
{
switch (c->type) {
case ROTARY:
case AUX:
if (c->target == MASTER) {
// only send relative changes to slaves
c->value = delta * c->step;
Expand Down Expand Up @@ -242,9 +243,11 @@ int main(int argc, char *argv[])
continue;
c = controller[i];
switch (c->target) {
/* // can't happen since we gave auxes their own gpio handler
case NOTGT:
// this line is a dt pin for a rotary, without its own handler
continue;
*/
#ifdef HAVE_ALSA
case ALSA:
c->param1 = setup_ALSA_elem(c->param1);
Expand All @@ -261,6 +264,9 @@ int main(int argc, char *argv[])
case SWITCH:
setup_GPIOD_switch(c->pin1);
break;
case AUX:
// do nothing
break;
default:
ERR("c->type %d can't happen here. BUG?", c->type);
}
Expand Down
27 changes: 24 additions & 3 deletions parse_cmdline.c
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,6 @@ int parse_cmdline(int argc, char *argv[])
ERR("calloc() failed.");
goto error;
}
controller[c->pin2] = d;
d->type = AUX;
#ifdef HAVE_JACK
if (match(config[2], "jack")) {
if (parse_cmdline_rotary_JACK(c, config))
Expand Down Expand Up @@ -244,6 +242,24 @@ int parse_cmdline(int argc, char *argv[])
ERR("Unknown type '%s'.", config[2]);
goto error;
}
controller[c->pin2] = d;
d->type = AUX;
d->target = c->target;
d->min = c->min;
d->max = c->max;
d->step = c->step;
d->toggle = c->toggle;
d->midi_ch = c->midi_ch;
d->midi_cc = c->midi_cc;
d->param1 = calloc(sizeof(char), MAXNAME);
d->param2 = calloc(sizeof(char), MAXNAME);
if (c->param1 == NULL || c->param2 == NULL) {
ERR("calloc() failed.");
goto error;
}
strncpy(d->param1, c->param1, MAXNAME);
strncpy(d->param2, c->param2, MAXNAME);
d->value = c->value;
break;

case 's':
Expand Down Expand Up @@ -384,8 +400,13 @@ int parse_cmdline(int argc, char *argv[])
free(c->param2);
free(c);
}
if (d != NULL)
if (d != NULL) {
if (d->param1 != NULL)
free(d->param1);
if (d->param2 != NULL)
free(d->param2);
free(d);
}
printf("Use -h for help.\n");
return EXIT_ERR;
}
Expand Down

0 comments on commit 2da0078

Please sign in to comment.