aboutsummaryrefslogtreecommitdiff
path: root/accel.c
blob: 1bca794adee9aaf362eb9c4597e2e4e730c6c28e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#include "common.h"

#include <stdio.h>
#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/atomic.h>

#include "i2c.h"
#include "accel.h"

/* device address */
#define LIS302DL 0b00111000
/* registers */
#define LIS302DL_WHOAMI 0xf
#define LIS302DL_CTRLREG1 0x20
#define LIS302DL_UNUSED1 0x28
#define LIS302DL_OUTZ 0x2D

/* value for 1g with +-2g max; measured */
#define ACCEL_1G_POS (55)
#define ACCEL_1G_NEG (-55)
/* offset for horizon detection */
#define ACCEL_1G_OFF (5)
/* shake detection values; 2g for +-2g max */
#define ACCEL_SHAKE_POS (INT8_MAX)
#define ACCEL_SHAKE_NEG (INT8_MIN)
/* 250ms for 100Hz data rate */
#define ACCEL_SHAKE_TIMEOUT (25)

/* z value */
static volatile int8_t zval = 0;
/* number of times shaken (i.e. peak level measured) */
static uint8_t shakeCount = 0;
/* if max in one direction direction has been detected give it some time to
 * wait for max in the other direction */
static uint8_t shakeTimeout = 0;
/* sign of last shake peak */
static enum {SHAKE_NONE, SHAKE_POS, SHAKE_NEG} shakeSign = SHAKE_NONE;
/* horizon position */
/* current */
static horizon horizonSign = HORIZON_NONE;
/* how long has sign been stable? */
static uint8_t horizonStable = 0;
/* previous measurement */
static horizon horizonPrevSign = HORIZON_NONE;
/* currently reading from i2c */
static bool reading = false;

/* data ready interrupt
 */
ISR(PCINT1_vect) {
	/* empty */
}

void accelInit () {
	/* set interrupt lines to input with pull-up */
	DDRC = DDRC & ~((1 << DDC0) | (1 << DDC1));
	PORTC = PORTC | (1 << PORTC0) | (1 << PORTC1);
	/* enable interrupt PCI1 for PCINT8/9 */
	PCICR = PCICR | (1 << PCIE1);
	/* enable interrupts from port PC0/PC1 aka PCINT8/PCINT9 */
	PCMSK1 = (1 << PCINT9) | (1 << PCINT8);
}

/* XXX: make nonblocking */
void accelStart () {
	/* configuration:
	 * disable power-down-mode, enable z-axis
	 * defaults
	 * low active, data ready interrupt on int2
	 */
	uint8_t data[] = {0b01000100, 0b0, 0b10100000};

	if (!twRequest (TWM_WRITE, LIS302DL, LIS302DL_CTRLREG1, data,
			sizeof (data)/sizeof (*data))) {
		printf ("cannot start write\n");
	}
	sleepwhile (twr.status == TWST_WAIT);
	printf ("final twi status was %i\n", twr.status);
}

/*	register shake gesture
 *
 *	“shake” means a peak in one direction followed by another one in the other
 *	direction. called for every data set pulled.
 */
static void accelProcessShake () {
	/* detect shake if:
	 * a) horizon is positive and accel z value is >= ACCEL_SHAKE_POS
	 * b) horizon is negative and accel z value is >= ACCEL_SHAKE_POS offset by
	 *    the value for 1g (negative)
	 * (same for negative horizon)
	 */
	if (((zval >= ACCEL_SHAKE_POS &&
			horizonSign == HORIZON_POS) ||
			(zval >= (ACCEL_SHAKE_POS + ACCEL_1G_NEG) &&
			horizonSign == HORIZON_NEG)) &&
			shakeSign != SHAKE_POS) {
		/* if we did not time out (i.e. max in other direction has been
		 * detected) register shake */
		if (shakeTimeout > 0) {
			++shakeCount;
			/* correctly detect double/triple/… shakes; setting this to
			 * ACCEL_SHAKE_TIMEOUT yields wrong results */
			shakeTimeout = 0;
		} else {
			shakeTimeout = ACCEL_SHAKE_TIMEOUT;
		}
		shakeSign = SHAKE_POS;
	} else if (((zval <= ACCEL_SHAKE_NEG &&
			horizonSign == HORIZON_NEG) ||
			(zval <= (ACCEL_SHAKE_NEG + ACCEL_1G_POS) &&
			horizonSign == HORIZON_POS)) &&
			shakeSign != SHAKE_NEG) {
		if (shakeTimeout > 0) {
			++shakeCount;
			shakeTimeout = 0;
		} else {
			shakeTimeout = ACCEL_SHAKE_TIMEOUT;
		}
		shakeSign = SHAKE_NEG;
	} else {
		if (shakeTimeout > 0) {
			--shakeTimeout;
		}
	}
}

/*	register horizon change
 *
 *	i.e. have we been turned upside down?
 */
static void accelProcessHorizon () {
	/* measuring approximately 1g */
	if (zval > (ACCEL_1G_POS - ACCEL_1G_OFF) &&
			zval < (ACCEL_1G_POS + ACCEL_1G_OFF) &&
			horizonPrevSign == HORIZON_POS && horizonSign != HORIZON_POS) {
		++horizonStable;
	} else if (zval < (ACCEL_1G_NEG + ACCEL_1G_OFF)
			&& zval > (ACCEL_1G_NEG - ACCEL_1G_OFF) &&
			horizonPrevSign == HORIZON_NEG && horizonSign != HORIZON_NEG) {
		++horizonStable;
	} else {
		horizonStable = 0;
	}
	/* make sure its not just shaking */
	if (horizonStable > 5) {
		horizonSign = horizonPrevSign;
		horizonStable = 0;
	}
	horizonPrevSign = zval >= 0 ? HORIZON_POS : HORIZON_NEG;
}

bool accelProcess () {
	if (reading) {
		if (twr.status == TWST_OK) {
			accelProcessHorizon ();
			accelProcessShake ();
			/* new data transfered */
			reading = false;
			return true;
		} else if (twr.status == TWST_ERR) {
			printf ("accel i2c error %x at step %i\n", twr.error, twr.step);
			reading = false;
		}
	} else {
		if (!((PINC >> PINC1) & 0x1) && twr.status != TWST_WAIT) {
			/* new data available in device buffer and bus is free */
			if (!twRequest (TWM_READ, LIS302DL, LIS302DL_OUTZ,
					(uint8_t *) &zval, sizeof (zval))) {
				printf ("cannot start read\n");
			} else {
				reading = true;
			}
		}
	}

	return false;
}

int8_t accelGetZ () {
	return zval;
}

uint8_t accelGetShakeCount () {
	return shakeCount;
}

void accelResetShakeCount () {
	shakeCount = 0;
}

horizon accelGetHorizon () {
	return horizonSign;
}