microbit: Added basic gesture recognition into MicroBitAccelerometer
The following postures of the device are now detected: - TILT_UP - TILT_DOWN - TILT_LEFT - TILT_RIGHT - FACE_UP - FACE_DOWN In addition, the following gestures are inferred: - NONE - SHAKE - FREEFALL - WHEEE (>=3g) - SICK (>=5g) - UNCONSCIOUS (>=8g) Events are now triggered on the MessageBus upon the transition from one posture/gesture to another, and a synchronous getGesture() method is now also provided to interogate the last gesture recognised. I should be noted that the default accelerator range of +/-2g will be insufficient to detect some of the events noted above, and MicroBitAccelerometer::setRange() should be used to increase the range if required.master
parent
2327ad44ff
commit
1b18ac8641
|
@ -39,7 +39,42 @@
|
|||
/*
|
||||
* Accelerometer events
|
||||
*/
|
||||
#define MICROBIT_ACCELEROMETER_EVT_DATA_UPDATE 1
|
||||
#define MICROBIT_ACCELEROMETER_EVT_DATA_UPDATE 1
|
||||
|
||||
/*
|
||||
* Gesture events
|
||||
*/
|
||||
#define MICROBIT_ACCELEROMETER_EVT_TILT_UP 1
|
||||
#define MICROBIT_ACCELEROMETER_EVT_TILT_DOWN 2
|
||||
#define MICROBIT_ACCELEROMETER_EVT_TILT_LEFT 3
|
||||
#define MICROBIT_ACCELEROMETER_EVT_TILT_RIGHT 4
|
||||
#define MICROBIT_ACCELEROMETER_EVT_FACE_UP 5
|
||||
#define MICROBIT_ACCELEROMETER_EVT_FACE_DOWN 6
|
||||
#define MICROBIT_ACCELEROMETER_EVT_FREEFALL 7
|
||||
#define MICROBIT_ACCELEROMETER_EVT_WHEEE 8
|
||||
#define MICROBIT_ACCELEROMETER_EVT_SICK 9
|
||||
#define MICROBIT_ACCELEROMETER_EVT_UNCONSCIOUS 10
|
||||
#define MICROBIT_ACCELEROMETER_EVT_SHAKE 11
|
||||
|
||||
/*
|
||||
* Gesture recogniser constants
|
||||
*/
|
||||
#define MICROBIT_ACCELEROMETER_REST_TOLERANCE 200
|
||||
#define MICROBIT_ACCELEROMETER_TILT_TOLERANCE 200
|
||||
#define MICROBIT_ACCELEROMETER_FREEFALL_TOLERANCE 200
|
||||
#define MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE 1000
|
||||
#define MICROBIT_ACCELEROMETER_WHEEE_TOLERANCE 3000
|
||||
#define MICROBIT_ACCELEROMETER_SICK_TOLERANCE 5000
|
||||
#define MICROBIT_ACCELEROMETER_UNCONSCIOUS_TOLERANCE 8000
|
||||
#define MICROBIT_ACCELEROMETER_GESTURE_DAMPING 10
|
||||
#define MICROBIT_ACCELEROMETER_SHAKE_DAMPING 10
|
||||
|
||||
#define MICROBIT_ACCELEROMETER_REST_THRESHOLD (MICROBIT_ACCELEROMETER_REST_TOLERANCE * MICROBIT_ACCELEROMETER_REST_TOLERANCE)
|
||||
#define MICROBIT_ACCELEROMETER_FREEFALL_THRESHOLD (MICROBIT_ACCELEROMETER_FREEFALL_TOLERANCE * MICROBIT_ACCELEROMETER_FREEFALL_TOLERANCE)
|
||||
#define MICROBIT_ACCELEROMETER_WHEEE_THRESHOLD (MICROBIT_ACCELEROMETER_WHEEE_TOLERANCE * MICROBIT_ACCELEROMETER_WHEEE_TOLERANCE)
|
||||
#define MICROBIT_ACCELEROMETER_SICK_THRESHOLD (MICROBIT_ACCELEROMETER_SICK_TOLERANCE * MICROBIT_ACCELEROMETER_SICK_TOLERANCE)
|
||||
#define MICROBIT_ACCELEROMETER_UNCONSCIOUS_THRESHOLD (MICROBIT_ACCELEROMETER_UNCONSCIOUS_TOLERANCE * MICROBIT_ACCELEROMETER_UNCONSCIOUS_TOLERANCE)
|
||||
#define MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD 4
|
||||
|
||||
struct MMA8653Sample
|
||||
{
|
||||
|
@ -63,6 +98,32 @@ struct MMA8653SampleRangeConfig
|
|||
extern const MMA8653SampleRangeConfig MMA8653SampleRange[];
|
||||
extern const MMA8653SampleRateConfig MMA8653SampleRate[];
|
||||
|
||||
enum BasicGesture
|
||||
{
|
||||
NONE,
|
||||
UP,
|
||||
DOWN,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
FACE_UP,
|
||||
FACE_DOWN,
|
||||
FREEFALL,
|
||||
WHEEE,
|
||||
SICK,
|
||||
UNCONSCIOUS,
|
||||
SHAKE
|
||||
};
|
||||
|
||||
struct ShakeHistory
|
||||
{
|
||||
uint16_t shaken:1,
|
||||
x:1,
|
||||
y:1,
|
||||
z:1,
|
||||
count:4,
|
||||
timer:8;
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition for MicroBit Accelerometer.
|
||||
*
|
||||
|
@ -76,11 +137,15 @@ class MicroBitAccelerometer : public MicroBitComponent
|
|||
* Used to track asynchronous events in the event bus.
|
||||
*/
|
||||
|
||||
MMA8653Sample sample; // The last sample read.
|
||||
DigitalIn int1; // Data ready interrupt.
|
||||
uint16_t address; // I2C address of this accelerometer.
|
||||
uint16_t samplePeriod; // The time between samples, in milliseconds.
|
||||
uint8_t sampleRange; // The sample range of the accelerometer in g.
|
||||
MMA8653Sample sample; // The last sample read.
|
||||
DigitalIn int1; // Data ready interrupt.
|
||||
uint8_t sigma; // the number of ticks that the instantaneous gesture has been stable.
|
||||
BasicGesture gesture; // the current, filtered orientation of the device.
|
||||
BasicGesture iGesture; // the last instantaneous orientation recorded.
|
||||
ShakeHistory shake; // State information needed to detect shake events.
|
||||
|
||||
public:
|
||||
|
||||
|
@ -192,6 +257,17 @@ class MicroBitAccelerometer : public MicroBitComponent
|
|||
*/
|
||||
int getZ();
|
||||
|
||||
/**
|
||||
* Reads the last recorded gesture detected.
|
||||
* @return The last gesture detected.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* if (uBit.accelerometer.getGesture() == SHAKE)
|
||||
* @endcode
|
||||
*/
|
||||
BasicGesture getGesture();
|
||||
|
||||
/**
|
||||
* periodic callback from MicroBit idle thread.
|
||||
* Check if any data is ready for reading by checking the interrupt flag on the accelerometer
|
||||
|
@ -224,6 +300,27 @@ class MicroBitAccelerometer : public MicroBitComponent
|
|||
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER or MICROBIT_I2C_ERROR if the the read request failed.
|
||||
*/
|
||||
int readCommand(uint8_t reg, uint8_t* buffer, int length);
|
||||
|
||||
/**
|
||||
* Updates the basic gesture recognizer. This performs instantaneous pose recognition, and also some low pass filtering to promote
|
||||
* stability.
|
||||
*/
|
||||
void updateGesture();
|
||||
|
||||
/**
|
||||
* Service function. Calculates the current scalar acceleration of the device (x^2 + y^2 + z^2).
|
||||
* It does not, however, square root the result, as this is a relatively high cost operation.
|
||||
* This is left to application code should it be needed.
|
||||
* @return the sum of the square of the acceleration of the device across all axes.
|
||||
*/
|
||||
int instantaneousAcceleration2();
|
||||
|
||||
/**
|
||||
* Service function. Determines the best guess posture of the device based on instantaneous data.
|
||||
* This makes no use of historic data, and forms this input to th filter implemented in updateGesture().
|
||||
* @return A best guess of the curret posture of the device, based on instanataneous data.
|
||||
*/
|
||||
BasicGesture instantaneousPosture();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#define MICROBIT_ID_IO_P20 25 //SDA
|
||||
|
||||
#define MICROBIT_ID_BUTTON_AB 26 // Button A+B multibutton
|
||||
#define MICROBIT_ID_GESTURE 27 // Gesture events
|
||||
|
||||
#define MICROBIT_ID_NOTIFY 1023 // Notfication channel, for general purpose synchronisation
|
||||
#define MICROBIT_ID_NOTIFY_ONE 1022 // Notfication channel, for general purpose synchronisation
|
||||
|
|
|
@ -144,6 +144,16 @@ MicroBitAccelerometer::MicroBitAccelerometer(uint16_t id, uint16_t address) : sa
|
|||
this->samplePeriod = 20;
|
||||
this->sampleRange = 2;
|
||||
|
||||
// Initialise gesture history
|
||||
this->sigma = 0;
|
||||
this->gesture = NONE;
|
||||
this->iGesture = NONE;
|
||||
this->shake.x = 0;
|
||||
this->shake.y = 0;
|
||||
this->shake.z = 0;
|
||||
this->shake.count = 0;
|
||||
this->shake.timer = 0;
|
||||
|
||||
// Configure and enable the accelerometer.
|
||||
if (this->configure() == MICROBIT_OK)
|
||||
uBit.flags |= MICROBIT_FLAG_ACCELEROMETER_RUNNING;
|
||||
|
@ -211,12 +221,136 @@ int MicroBitAccelerometer::update()
|
|||
sample.y *= this->sampleRange;
|
||||
sample.z *= this->sampleRange;
|
||||
|
||||
// Update gesture tracking
|
||||
updateGesture();
|
||||
|
||||
// Indicate that a new sample is available
|
||||
MicroBitEvent e(id, MICROBIT_ACCELEROMETER_EVT_DATA_UPDATE);
|
||||
|
||||
return MICROBIT_OK;
|
||||
};
|
||||
|
||||
/**
|
||||
* Service function. Calculates the current scalar acceleration of the device (x^2 + y^2 + z^2).
|
||||
* It does not, however, square root the result, as this is a relatively high cost operation.
|
||||
* This is left to application code should it be needed.
|
||||
*
|
||||
* @return the sum of the square of the acceleration of the device across all axes.
|
||||
*/
|
||||
int
|
||||
MicroBitAccelerometer::instantaneousAcceleration2()
|
||||
{
|
||||
// Use pythagoras theorem to determine the combined force acting on the device.
|
||||
return (int)sample.x*(int)sample.x + (int)sample.y*(int)sample.y + (int)sample.z*(int)sample.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service function. Determines the best guess posture of the device based on instantaneous data.
|
||||
* This makes no use of historic data (except for shake), and forms this input to the filter implemented in updateGesture().
|
||||
*
|
||||
* @return A best guess of the current posture of the device, based on instantaneous data.
|
||||
*/
|
||||
BasicGesture
|
||||
MicroBitAccelerometer::instantaneousPosture()
|
||||
{
|
||||
int force = instantaneousAcceleration2();
|
||||
bool shakeDetected = false;
|
||||
|
||||
// Test for shake events.
|
||||
if ((sample.x < -MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && shake.x) || (sample.x > MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !shake.x))
|
||||
{
|
||||
shakeDetected = true;
|
||||
shake.x = !shake.x;
|
||||
}
|
||||
|
||||
if ((sample.y < -MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && shake.y) || (sample.y > MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !shake.y))
|
||||
{
|
||||
shakeDetected = true;
|
||||
shake.y = !shake.y;
|
||||
}
|
||||
|
||||
if ((sample.z < -MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && shake.z) || (sample.z > MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !shake.z))
|
||||
{
|
||||
shakeDetected = true;
|
||||
shake.z = !shake.z;
|
||||
}
|
||||
|
||||
if (shakeDetected && shake.count < MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD && ++shake.count == MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD)
|
||||
shake.shaken = 1;
|
||||
|
||||
if (++shake.timer >= MICROBIT_ACCELEROMETER_SHAKE_DAMPING)
|
||||
{
|
||||
shake.timer = 0;
|
||||
if (shake.count > 0)
|
||||
{
|
||||
if(--shake.count == 0)
|
||||
shake.shaken = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (shake.shaken)
|
||||
return SHAKE;
|
||||
|
||||
if (force < MICROBIT_ACCELEROMETER_FREEFALL_THRESHOLD)
|
||||
return FREEFALL;
|
||||
|
||||
if (force > MICROBIT_ACCELEROMETER_UNCONSCIOUS_THRESHOLD)
|
||||
return UNCONSCIOUS;
|
||||
|
||||
if (force > MICROBIT_ACCELEROMETER_SICK_THRESHOLD)
|
||||
return SICK;
|
||||
|
||||
if (force > MICROBIT_ACCELEROMETER_WHEEE_THRESHOLD)
|
||||
return WHEEE;
|
||||
|
||||
// Determine our posture.
|
||||
if (sample.x < (-1000 + MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return LEFT;
|
||||
|
||||
if (sample.x > (1000 - MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return RIGHT;
|
||||
|
||||
if (sample.y < (-1000 + MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DOWN;
|
||||
|
||||
if (sample.y > (1000 - MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return UP;
|
||||
|
||||
if (sample.z < (-1000 + MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return FACE_UP;
|
||||
|
||||
if (sample.z > (1000 - MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return FACE_DOWN;
|
||||
|
||||
return NONE;
|
||||
}
|
||||
|
||||
void
|
||||
MicroBitAccelerometer::updateGesture()
|
||||
{
|
||||
// Determine what it looks like we're doing based on the latest sample...
|
||||
BasicGesture g = instantaneousPosture();
|
||||
|
||||
// Perform some low pass filtering to remove hysteresis (data flapping) effects
|
||||
if (g == iGesture)
|
||||
{
|
||||
if (sigma < MICROBIT_ACCELEROMETER_GESTURE_DAMPING)
|
||||
sigma++;
|
||||
}
|
||||
else
|
||||
{
|
||||
iGesture = g;
|
||||
sigma = 0;
|
||||
}
|
||||
|
||||
// If we've reached threshold, update our record and raise the relevant event...
|
||||
if (iGesture != gesture && sigma >= MICROBIT_ACCELEROMETER_GESTURE_DAMPING)
|
||||
{
|
||||
gesture = iGesture;
|
||||
MicroBitEvent e(MICROBIT_ID_GESTURE, gesture);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to set the sample rate of the accelerometer to the specified value (in ms).
|
||||
* n.b. the requested rate may not be possible on the hardware. In this case, the
|
||||
|
@ -306,6 +440,19 @@ int MicroBitAccelerometer::getZ()
|
|||
return sample.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the last recorded gesture detected.
|
||||
* @return The last gesture detected.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* if (uBit.accelerometer.getGesture() == SHAKE)
|
||||
* @endcode
|
||||
*/
|
||||
BasicGesture MicroBitAccelerometer::getGesture()
|
||||
{
|
||||
return gesture;
|
||||
}
|
||||
|
||||
/**
|
||||
* periodic callback from MicroBit clock.
|
||||
|
|
Loading…
Reference in New Issue