aboutsummaryrefslogtreecommitdiff
path: root/accel.c
blob: 6dcb5c9dbd5aed19d6d3c35b558d2a0d990820d1 (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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#include "common.h"

#include <stdio.h>
#include <stdlib.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_CTRLREG2 0x21
#define LIS302DL_UNUSED1 0x28
#define LIS302DL_OUTZ 0x2D

/* value for 1g with +-2g max; measured */
#define ACCEL_1G (55)
/* offset for horizon detection */
#define ACCEL_1G_OFF (5)
/* threshold starting shake detection */
#define ACCEL_SHAKE_START (90)
/* difference prev and current value to get the current one registered as peak */
#define ACCEL_SHAKE_REGISTER (120)
/* 100ms for 100Hz data rate */
#define ACCEL_SHAKE_TIMEOUT (10)

/* z value */
static int8_t zval = 0;
static int16_t zvalnormal = 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;
static int16_t prevShakeVal = 0;
static uint8_t peakCount = 0;
static horizon shakeHorizon = HORIZON_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;

/* driver state */
#define STOPPED 0
#define START_REQUEST 1
#define STARTING 2
#define STOP_REQUEST 3
#define STOPPING 4
#define READING 5
#define IDLE 6
static uint8_t state = STOPPED;

/* data ready interrupt
 */
ISR(PCINT1_vect) {
	const bool interrupt = (PINC >> PINC1) & 0x1;
	/* high-active */
	if (interrupt) {
		enableWakeup (WAKE_ACCEL);
	} else {
		disableWakeup (WAKE_ACCEL);
	}
}

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

void accelStart () {
	assert (state == STOPPED);
	state = START_REQUEST;

}

/*	register shake gesture
 *
 *	“shake” means three peaks in directions 1, -1, 1
 */
static void accelProcessShake () {
	if (shakeTimeout > 0) {
		--shakeTimeout;
		if (horizonSign != shakeHorizon) {
			/* ignore if horizon changed */
			shakeTimeout = 0;
			prevShakeVal = 0;
			peakCount = 0;
		} else if (sign (prevShakeVal) != sign (zvalnormal) &&
				abs (prevShakeVal - zvalnormal) >= ACCEL_SHAKE_REGISTER) {
			++peakCount;
			shakeTimeout = ACCEL_SHAKE_TIMEOUT;
			prevShakeVal = zvalnormal;
		} else if (sign (prevShakeVal) == sign (zvalnormal) &&
				abs (zvalnormal) >= abs (prevShakeVal)) {
			/* actually we did not measure the peak yet/are still experiencing it */
			prevShakeVal = zvalnormal;
			shakeTimeout = ACCEL_SHAKE_TIMEOUT;
		}
		if (shakeTimeout == 0) {
			/* just timed out, can register gesture now */
			shakeCount += peakCount/2;
			prevShakeVal = 0;
			peakCount = 0;
		}
	}

	/* start shake detection */
	if (shakeTimeout == 0 && abs (zvalnormal) >= ACCEL_SHAKE_START) {
		shakeTimeout = ACCEL_SHAKE_TIMEOUT;
		peakCount = 1;
		prevShakeVal = zvalnormal;
		shakeHorizon = horizonSign;
	}
}

/*	register horizon change
 *
 *	i.e. have we been turned upside down?
 */
static void accelProcessHorizon () {
	/* measuring approximately 1g */
	if (zval > (ACCEL_1G - ACCEL_1G_OFF) &&
			zval < (ACCEL_1G + ACCEL_1G_OFF) &&
			horizonPrevSign == HORIZON_POS && horizonSign != HORIZON_POS) {
		++horizonStable;
	} else if (zval < (-ACCEL_1G + ACCEL_1G_OFF)
			&& zval > (-ACCEL_1G - 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;
}

void accelProcess () {
	switch (state) {
		case START_REQUEST: {
			/* configuration:
			 * disable power-down-mode, enable z-axis
			 * defaults
			 * push-pull, high-active, data ready interrupt on int2
			 */
			static uint8_t data[] = {0b01000100, 0b0, 0b00100000};

			const bool ret = twRequest (TWM_WRITE, LIS302DL, LIS302DL_CTRLREG1, data,
					sizeof (data)/sizeof (*data));
			if (ret) {
				state = STARTING;
			}
			break;
		}

		case STARTING:
			if (shouldWakeup (WAKE_I2C)) {
				disableWakeup (WAKE_I2C);
				state = IDLE;
			}
			break;

		case IDLE:
			/* new data available in device buffer and bus is free */
			if (shouldWakeup (WAKE_ACCEL) && twRequest (TWM_READ, LIS302DL,
						LIS302DL_OUTZ, (uint8_t *) &zval, sizeof (zval))) {
				state = READING;
			}
			break;

		case READING:
			if (shouldWakeup (WAKE_I2C)) {
				disableWakeup (WAKE_I2C);
				state = IDLE;
				/* the bus might be in use again already */
				//assert (twr.status == TWST_OK);
				accelProcessHorizon ();

				/* calculate normalized z (i.e. without earth gravity component) */
				if (horizonSign == HORIZON_NEG) {
					zvalnormal = zval - (-ACCEL_1G);
				} else if (horizonSign == HORIZON_POS) {
					zvalnormal = zval - ACCEL_1G;
				}

				accelProcessShake ();
			}
			break;

		default:
			assert (0);
			break;
	}
}

int8_t accelGetZ () {
	return zval;
}

int8_t accelGetNormalizedZ () {
	return zvalnormal;
}

uint8_t accelGetShakeCount () {
	return shakeCount;
}

void accelResetShakeCount () {
	shakeCount = 0;
}

horizon accelGetHorizon () {
	return horizonSign;
}