/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <stdlib.h> #include <string.h> #include <timer.h> #include <heap.h> #include <plat/rtc.h> #include <plat/syscfg.h> #include <hostIntf.h> #include <nanohubPacket.h> #include <floatRt.h> #include <seos.h> #include <nanohub_math.h> #include <algos/fusion.h> #include <sensors.h> #include <variant/sensType.h> #include <limits.h> #include <slab.h> #define ORIENTATION_APP_VERSION 1 #define MAX_NUM_COMMS_EVENT_SAMPLES 15 // at most 15 samples can fit in one comms_event #define NUM_COMMS_EVENTS_IN_FIFO 2 // This controls how often the hub needs to wake up // in batching // needs to be greater than max raw sensor rate ratio #define FIFO_DEPTH (NUM_COMMS_EVENTS_IN_FIFO * MAX_NUM_COMMS_EVENT_SAMPLES) /* * FIFO_MARGIN: max raw sensor rate ratio is 8:1. * If 2 batchs of high rate data comes before 1 low rate data, there can be at max 15 samples left * in the FIFO */ #define FIFO_MARGIN 15 #define MAX_NUM_SAMPLES (FIFO_MARGIN + FIFO_DEPTH) // actual input sample fifo depth #define EVT_SENSOR_ACC_DATA_RDY sensorGetMyEventType(SENS_TYPE_ACCEL) #define EVT_SENSOR_GYR_DATA_RDY sensorGetMyEventType(SENS_TYPE_GYRO) #define EVT_SENSOR_MAG_DATA_RDY sensorGetMyEventType(SENS_TYPE_MAG) #define EVT_SENSOR_MAG_BIAS sensorGetMyEventType(SENS_TYPE_MAG_BIAS) #define kGravityEarth 9.80665f #define kRad2deg (180.0f / M_PI) #define MIN_GYRO_RATE_HZ SENSOR_HZ(100.0f) #define MAX_MAG_RATE_HZ SENSOR_HZ(50.0f) enum { FUSION_FLAG_ENABLED = 0x01, FUSION_FLAG_INITIALIZED = 0x08, FUSION_FLAG_GAME_ENABLED = 0x10, FUSION_FLAG_GAME_INITIALIZED = 0x20 }; enum RawSensorType { ACC, GYR, MAG, NUM_OF_RAW_SENSOR }; enum FusionSensorType { ORIENT, GRAVITY, GEOMAG, LINEAR, GAME, ROTAT, NUM_OF_FUSION_SENSOR }; struct FusionSensorSample { uint64_t time; float x, y, z; }; struct FusionSensor { uint32_t handle; struct TripleAxisDataEvent *ev; uint64_t prev_time; uint64_t latency; uint32_t rate; bool active; bool use_gyro_data; bool use_mag_data; uint8_t idx; }; struct FusionTask { uint32_t tid; uint32_t accelHandle; uint32_t gyroHandle; uint32_t magHandle; struct Fusion fusion; struct Fusion game; struct FusionSensor sensors[NUM_OF_FUSION_SENSOR]; struct FusionSensorSample samples[NUM_OF_RAW_SENSOR][MAX_NUM_SAMPLES]; size_t sample_indices[NUM_OF_RAW_SENSOR]; size_t sample_counts[NUM_OF_RAW_SENSOR]; uint32_t counters[NUM_OF_RAW_SENSOR]; uint64_t ResamplePeriodNs[NUM_OF_RAW_SENSOR]; uint64_t last_time[NUM_OF_RAW_SENSOR]; struct TripleAxisDataPoint last_sample[NUM_OF_RAW_SENSOR]; uint32_t flags; uint32_t raw_sensor_rate[NUM_OF_RAW_SENSOR]; uint64_t raw_sensor_latency; uint8_t accel_client_cnt; uint8_t gyro_client_cnt; uint8_t mag_client_cnt; }; static uint32_t FusionRates[] = { SENSOR_HZ(12.5f), SENSOR_HZ(25.0f), SENSOR_HZ(50.0f), SENSOR_HZ(100.0f), SENSOR_HZ(200.0f), 0, }; //should match "supported rates in length" and be the timer length for that rate in nanosecs static const uint64_t rateTimerVals[] = { 1000000000ULL / 12.5f, 1000000000ULL / 25, 1000000000ULL / 50, 1000000000ULL / 100, 1000000000ULL / 200, }; static struct FusionTask mTask; #define DEC_INFO_RATE(name, rates, type, axis, inter, samples) \ .sensorName = name, \ .supportedRates = rates, \ .sensorType = type, \ .numAxis = axis, \ .interrupt = inter, \ .minSamples = samples static const struct SensorInfo mSi[NUM_OF_FUSION_SENSOR] = { { DEC_INFO_RATE("Orientation", FusionRates, SENS_TYPE_ORIENTATION, NUM_AXIS_THREE, NANOHUB_INT_NONWAKEUP, 20) }, { DEC_INFO_RATE("Gravity", FusionRates, SENS_TYPE_GRAVITY, NUM_AXIS_THREE, NANOHUB_INT_NONWAKEUP, 20) }, { DEC_INFO_RATE("Geomagnetic Rotation Vector", FusionRates, SENS_TYPE_GEO_MAG_ROT_VEC, NUM_AXIS_THREE, NANOHUB_INT_NONWAKEUP, 20) }, { DEC_INFO_RATE("Linear Acceleration", FusionRates, SENS_TYPE_LINEAR_ACCEL, NUM_AXIS_THREE, NANOHUB_INT_NONWAKEUP, 20) }, { DEC_INFO_RATE("Game Rotation Vector", FusionRates, SENS_TYPE_GAME_ROT_VECTOR, NUM_AXIS_THREE, NANOHUB_INT_NONWAKEUP, 300) }, { DEC_INFO_RATE("Rotation Vector", FusionRates, SENS_TYPE_ROTATION_VECTOR, NUM_AXIS_THREE, NANOHUB_INT_NONWAKEUP, 20) }, }; static struct SlabAllocator *mDataSlab; static void dataEvtFree(void *ptr) { slabAllocatorFree(mDataSlab, ptr); } static void fillSamples(struct TripleAxisDataEvent *ev, enum RawSensorType index) { bool bad_timestamp; size_t i, w, n, num_samples; struct TripleAxisDataPoint *curr_sample, *next_sample; uint32_t counter; uint64_t ResamplePeriodNs, curr_time, next_time; uint64_t sample_spacing_ns; float weight_next; if (index == GYR && mTask.gyro_client_cnt == 0) { return; } if (index == MAG && mTask.mag_client_cnt == 0) { return; } n = mTask.sample_counts[index]; i = mTask.sample_indices[index]; counter = mTask.counters[index]; ResamplePeriodNs = mTask.ResamplePeriodNs[index]; w = (mTask.sample_indices[index] + n) % MAX_NUM_SAMPLES; // check if this sensor was used before if (mTask.last_time[index] == ULONG_LONG_MAX) { curr_sample = ev->samples; next_sample = curr_sample + 1; num_samples = ev->samples[0].firstSample.numSamples; curr_time = ev->referenceTime; } else { curr_sample = &mTask.last_sample[index]; next_sample = ev->samples; num_samples = ev->samples[0].firstSample.numSamples + 1; curr_time = mTask.last_time[index]; } while (num_samples > 1) { if (next_sample == ev->samples) next_time = ev->referenceTime; else next_time = curr_time + next_sample->deltaTime; // error handling for non-chronological accel timestamps sample_spacing_ns = (next_time > curr_time) ? (next_time - curr_time) : 0; // This can happen during sensor config changes bad_timestamp = (sample_spacing_ns > 10 * ResamplePeriodNs); // Check to see if we need to move the interpolation window or // interpolate if ((counter >= sample_spacing_ns) || bad_timestamp) { num_samples--; counter -= (bad_timestamp ? counter : sample_spacing_ns); curr_sample = next_sample; next_sample++; curr_time = next_time; } else { weight_next = (float)counter / floatFromUint64(sample_spacing_ns); mTask.samples[index][w].x = curr_sample->x + weight_next * (next_sample->x - curr_sample->x); mTask.samples[index][w].y = curr_sample->y + weight_next * (next_sample->y - curr_sample->y); mTask.samples[index][w].z = curr_sample->z + weight_next * (next_sample->z - curr_sample->z); mTask.samples[index][w].time = curr_time + counter; // Move the read index when buffer is full if (++n > MAX_NUM_SAMPLES) { n = MAX_NUM_SAMPLES; if (++i == MAX_NUM_SAMPLES) { i = 0; } } // Reset the write index if (++w == MAX_NUM_SAMPLES) { w = 0; } // Move to the next resample counter += ResamplePeriodNs; } } mTask.sample_counts[index] = n; mTask.sample_indices[index] = i; mTask.counters[index] = counter; mTask.last_sample[index] = *curr_sample; mTask.last_time[index] = curr_time; } static bool allocateDataEvt(struct FusionSensor *mSensor, uint64_t time) { mSensor->ev = slabAllocatorAlloc(mDataSlab); if (mSensor->ev == NULL) { // slab allocation failed, need to stop draining raw samples for now. osLog(LOG_INFO, "ORIENTATION: slabAllocatorAlloc() Failed\n"); return false; } // delta time for the first sample is sample count memset(&mSensor->ev->samples[0].firstSample, 0x00, sizeof(struct SensorFirstSample)); mSensor->ev->referenceTime = time; mSensor->prev_time = time; return true; } // returns false if addSample() fails static bool addSample(struct FusionSensor *mSensor, uint64_t time, float x, float y, float z) { struct TripleAxisDataPoint *sample; // Bypass processing this accel sample. // This is needed after recovering from a slab shortage. if (mSensor->prev_time == time) { osLog(LOG_INFO, "Accel sample has been processed by fusion sensor %d\n", mSensor->idx); return true; } if (mSensor->ev == NULL) { if (!allocateDataEvt(mSensor, time)) return false; } if (mSensor->ev->samples[0].firstSample.numSamples >= MAX_NUM_COMMS_EVENT_SAMPLES) { osLog(LOG_ERROR, "ORIENTATION: BAD_INDEX\n"); return false; } sample = &mSensor->ev->samples[mSensor->ev->samples[0].firstSample.numSamples++]; if (mSensor->ev->samples[0].firstSample.numSamples > 1) { sample->deltaTime = time > mSensor->prev_time ? (time - mSensor->prev_time) : 0; mSensor->prev_time = time; } sample->x = x; sample->y = y; sample->z = z; if (mSensor->ev->samples[0].firstSample.numSamples == MAX_NUM_COMMS_EVENT_SAMPLES) { osEnqueueEvtOrFree( EVENT_TYPE_BIT_DISCARDABLE | sensorGetMyEventType(mSi[mSensor->idx].sensorType), mSensor->ev, dataEvtFree); mSensor->ev = NULL; } return true; } // returns false if addSample fails for any fusion sensor // (most likely due to slab allocation failure) static bool updateOutput(ssize_t last_accel_sample_index, uint64_t last_sensor_time) { struct Vec4 attitude; struct Vec3 g, a; struct Mat33 R; // direction-cosine/rotation matrix, inertial -> device bool rInited; // indicates if matrix R has been initialzed. for avoiding repeated computation bool ret = true; if (fusionHasEstimate(&mTask.game)) { rInited = false; if (mTask.sensors[GAME].active) { fusionGetAttitude(&mTask.game, &attitude); if (!addSample(&mTask.sensors[GAME], last_sensor_time, attitude.x, attitude.y, attitude.z)) { ret = false; } } if (mTask.sensors[GRAVITY].active) { fusionGetRotationMatrix(&mTask.game, &R); rInited = true; initVec3(&g, R.elem[0][2], R.elem[1][2], R.elem[2][2]); vec3ScalarMul(&g, kGravityEarth); if (!addSample(&mTask.sensors[GRAVITY], last_sensor_time, g.x, g.y, g.z)) { ret = false; } } if (last_accel_sample_index >= 0 && mTask.sensors[LINEAR].active) { if (!rInited) { fusionGetRotationMatrix(&mTask.game, &R); } initVec3(&g, R.elem[0][2], R.elem[1][2], R.elem[2][2]); vec3ScalarMul(&g, kGravityEarth); initVec3(&a, mTask.samples[0][last_accel_sample_index].x, mTask.samples[0][last_accel_sample_index].y, mTask.samples[0][last_accel_sample_index].z); if (!addSample(&mTask.sensors[LINEAR], mTask.samples[0][last_accel_sample_index].time, a.x - g.x, a.y - g.y, a.z - g.z)) { ret = false; } } } if (fusionHasEstimate(&mTask.fusion)) { fusionGetAttitude(&mTask.fusion, &attitude); if (mTask.sensors[ORIENT].active) { fusionGetRotationMatrix(&mTask.fusion, &R); // x, y, z = yaw, pitch, roll float x = atan2f(-R.elem[0][1], R.elem[0][0]) * kRad2deg; float y = atan2f(-R.elem[1][2], R.elem[2][2]) * kRad2deg; float z = asinf(R.elem[0][2]) * kRad2deg; if (x < 0.0f) { x += 360.0f; } if (!addSample(&mTask.sensors[ORIENT], last_sensor_time, x, y, z)) { ret = false; } } if (mTask.sensors[GEOMAG].active) { if (!addSample(&mTask.sensors[GEOMAG], last_sensor_time, attitude.x, attitude.y, attitude.z)) { ret = false; } } if (mTask.sensors[ROTAT].active) { if (!addSample(&mTask.sensors[ROTAT], last_sensor_time, attitude.x, attitude.y, attitude.z)) { ret = false; } } } return ret; } static void drainSamples() { struct Vec3 a, w, m; uint64_t a_time, g_time, m_time; size_t i = mTask.sample_indices[ACC]; size_t j = 0; size_t k = 0; size_t which; float dT; bool success = true; if (mTask.gyro_client_cnt > 0) j = mTask.sample_indices[GYR]; if (mTask.mag_client_cnt > 0) k = mTask.sample_indices[MAG]; // Keep draining raw samples and producing fusion samples only if // 1) all raw sensors needed are present (to compare timestamp) and // 2) updateOutput() succeeded (no slab shortage) // Otherwise, wait till next raw sample event. while (mTask.sample_counts[ACC] > 0 && (!(mTask.gyro_client_cnt > 0) || mTask.sample_counts[GYR] > 0) && (!(mTask.mag_client_cnt > 0) || mTask.sample_counts[MAG] > 0) && success) { a_time = mTask.samples[ACC][i].time; g_time = mTask.gyro_client_cnt > 0 ? mTask.samples[GYR][j].time : ULONG_LONG_MAX; m_time = mTask.mag_client_cnt > 0 ? mTask.samples[MAG][k].time : ULONG_LONG_MAX; // priority with same timestamp: gyro > acc > mag if (g_time <= a_time && g_time <= m_time) { which = GYR; } else if (a_time <= m_time) { which = ACC; } else { which = MAG; } dT = floatFromUint64(mTask.ResamplePeriodNs[which]) * 1e-9f; switch (which) { case ACC: initVec3(&a, mTask.samples[ACC][i].x, mTask.samples[ACC][i].y, mTask.samples[ACC][i].z); if (mTask.flags & FUSION_FLAG_ENABLED) fusionHandleAcc(&mTask.fusion, &a, dT); if (mTask.flags & FUSION_FLAG_GAME_ENABLED) fusionHandleAcc(&mTask.game, &a, dT); success = updateOutput(i, mTask.samples[ACC][i].time); // Do not remove the accel sample until all active fusion sesnsors // successfully updated the output. // Fusion sensors that have processed this accel sample will bypass // it in addSample(). if (success) { --mTask.sample_counts[ACC]; if (++i == MAX_NUM_SAMPLES) { i = 0; } } break; case GYR: initVec3(&w, mTask.samples[GYR][j].x, mTask.samples[GYR][j].y, mTask.samples[GYR][j].z); if (mTask.flags & FUSION_FLAG_ENABLED) fusionHandleGyro(&mTask.fusion, &w, dT); if (mTask.flags & FUSION_FLAG_GAME_ENABLED) fusionHandleGyro(&mTask.game, &w, dT); --mTask.sample_counts[GYR]; if (++j == MAX_NUM_SAMPLES) j = 0; break; case MAG: initVec3(&m, mTask.samples[MAG][k].x, mTask.samples[MAG][k].y, mTask.samples[MAG][k].z); fusionHandleMag(&mTask.fusion, &m, dT); --mTask.sample_counts[MAG]; if (++k == MAX_NUM_SAMPLES) k = 0; break; } } mTask.sample_indices[ACC] = i; if (mTask.gyro_client_cnt > 0) mTask.sample_indices[GYR] = j; if (mTask.mag_client_cnt > 0) mTask.sample_indices[MAG] = k; for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++) { if (mTask.sensors[i].ev != NULL) { osEnqueueEvtOrFree(EVENT_TYPE_BIT_DISCARDABLE | sensorGetMyEventType(mSi[i].sensorType), mTask.sensors[i].ev, dataEvtFree); mTask.sensors[i].ev = NULL; } } } static void configureFusion() { if (mTask.sensors[ORIENT].active || mTask.sensors[ROTAT].active || mTask.sensors[GEOMAG].active) { mTask.flags |= FUSION_FLAG_ENABLED; initFusion(&mTask.fusion, (mTask.mag_client_cnt > 0 ? FUSION_USE_MAG : 0) | (mTask.gyro_client_cnt > 0 ? FUSION_USE_GYRO : 0) | ((mTask.flags & FUSION_FLAG_INITIALIZED) ? 0 : FUSION_REINITIALIZE)); mTask.flags |= FUSION_FLAG_INITIALIZED; } else { mTask.flags &= ~FUSION_FLAG_ENABLED; mTask.flags &= ~FUSION_FLAG_INITIALIZED; } } static void configureGame() { if (mTask.sensors[GAME].active || mTask.sensors[GRAVITY].active || mTask.sensors[LINEAR].active) { mTask.flags |= FUSION_FLAG_GAME_ENABLED; initFusion(&mTask.game, FUSION_USE_GYRO | ((mTask.flags & FUSION_FLAG_INITIALIZED) ? 0 : FUSION_REINITIALIZE)); mTask.flags |= FUSION_FLAG_GAME_INITIALIZED; } else { mTask.flags &= ~FUSION_FLAG_GAME_ENABLED; mTask.flags &= ~FUSION_FLAG_GAME_INITIALIZED; } } static void fusionSetRateAcc(void) { int i; if (mTask.accelHandle == 0) { mTask.sample_counts[ACC] = 0; mTask.sample_indices[ACC] = 0; mTask.counters[ACC] = 0; mTask.last_time[ACC] = ULONG_LONG_MAX; for (i = 0; sensorFind(SENS_TYPE_ACCEL, i, &mTask.accelHandle) != NULL; i++) { if (sensorRequest(mTask.tid, mTask.accelHandle, mTask.raw_sensor_rate[ACC], mTask.raw_sensor_latency)) { osEventSubscribe(mTask.tid, EVT_SENSOR_ACC_DATA_RDY); break; } } } else { sensorRequestRateChange(mTask.tid, mTask.accelHandle, mTask.raw_sensor_rate[ACC], mTask.raw_sensor_latency); } } static void fusionSetRateGyr(void) { int i; if (mTask.gyroHandle == 0) { mTask.sample_counts[GYR] = 0; mTask.sample_indices[GYR] = 0; mTask.counters[GYR] = 0; mTask.last_time[GYR] = ULONG_LONG_MAX; for (i = 0; sensorFind(SENS_TYPE_GYRO, i, &mTask.gyroHandle) != NULL; i++) { if (sensorRequest(mTask.tid, mTask.gyroHandle, mTask.raw_sensor_rate[GYR], mTask.raw_sensor_latency)) { osEventSubscribe(mTask.tid, EVT_SENSOR_GYR_DATA_RDY); break; } } } else { sensorRequestRateChange(mTask.tid, mTask.gyroHandle, mTask.raw_sensor_rate[GYR], mTask.raw_sensor_latency); } } static void fusionSetRateMag(void) { int i; if (mTask.magHandle == 0) { mTask.sample_counts[MAG] = 0; mTask.sample_indices[MAG] = 0; mTask.counters[MAG] = 0; mTask.last_time[MAG] = ULONG_LONG_MAX; for (i = 0; sensorFind(SENS_TYPE_MAG, i, &mTask.magHandle) != NULL; i++) { if (sensorRequest(mTask.tid, mTask.magHandle, mTask.raw_sensor_rate[MAG], mTask.raw_sensor_latency)) { osEventSubscribe(mTask.tid, EVT_SENSOR_MAG_DATA_RDY); osEventSubscribe(mTask.tid, EVT_SENSOR_MAG_BIAS); break; } } } else { sensorRequestRateChange(mTask.tid, mTask.magHandle, mTask.raw_sensor_rate[MAG], mTask.raw_sensor_latency); } } static bool fusionSetRate(uint32_t rate, uint64_t latency, void *cookie) { struct FusionSensor *mSensor = &mTask.sensors[(int)cookie]; int i; uint32_t max_rate = 0; uint32_t gyr_rate, mag_rate; uint64_t min_resample_period = ULONG_LONG_MAX; mSensor->rate = rate; mSensor->latency = latency; for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++) { if (mTask.sensors[i].active) { max_rate = max_rate > mTask.sensors[i].rate ? max_rate : mTask.sensors[i].rate; } } if (mTask.accel_client_cnt > 0) { mTask.raw_sensor_rate[ACC] = max_rate; mTask.ResamplePeriodNs[ACC] = sensorTimerLookupCommon(FusionRates, rateTimerVals, max_rate); min_resample_period = mTask.ResamplePeriodNs[ACC] < min_resample_period ? mTask.ResamplePeriodNs[ACC] : min_resample_period; } if (mTask.gyro_client_cnt > 0) { gyr_rate = max_rate > MIN_GYRO_RATE_HZ ? max_rate : MIN_GYRO_RATE_HZ; mTask.raw_sensor_rate[GYR] = gyr_rate; mTask.ResamplePeriodNs[GYR] = sensorTimerLookupCommon(FusionRates, rateTimerVals, gyr_rate); min_resample_period = mTask.ResamplePeriodNs[GYR] < min_resample_period ? mTask.ResamplePeriodNs[GYR] : min_resample_period; } if (mTask.mag_client_cnt > 0) { mag_rate = max_rate < MAX_MAG_RATE_HZ ? max_rate : MAX_MAG_RATE_HZ; mTask.raw_sensor_rate[MAG] = mag_rate; mTask.ResamplePeriodNs[MAG] = sensorTimerLookupCommon(FusionRates, rateTimerVals, mag_rate); min_resample_period = mTask.ResamplePeriodNs[MAG] < min_resample_period ? mTask.ResamplePeriodNs[MAG] : min_resample_period; } // This guarantees that local raw sensor FIFOs won't overflow. mTask.raw_sensor_latency = min_resample_period * (FIFO_DEPTH - 1); for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++) { if (mTask.sensors[i].active) { mTask.raw_sensor_latency = mTask.sensors[i].latency < mTask.raw_sensor_latency ? mTask.sensors[i].latency : mTask.raw_sensor_latency; } } if (mTask.accel_client_cnt > 0) fusionSetRateAcc(); if (mTask.gyro_client_cnt > 0) fusionSetRateGyr(); if (mTask.mag_client_cnt > 0) fusionSetRateMag(); if (mSensor->rate > 0) sensorSignalInternalEvt(mSensor->handle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency); return true; } static bool fusionPower(bool on, void *cookie) { struct FusionSensor *mSensor = &mTask.sensors[(int)cookie]; int idx; mSensor->active = on; if (on == false) { mTask.accel_client_cnt--; if (mSensor->use_gyro_data) mTask.gyro_client_cnt--; if (mSensor->use_mag_data) mTask.mag_client_cnt--; // if client_cnt == 0 and Handle == 0, nothing need to be done. // if client_cnt > 0 and Handle == 0, something else is turning it on, all will be done. if (mTask.accel_client_cnt == 0 && mTask.accelHandle != 0) { sensorRelease(mTask.tid, mTask.accelHandle); mTask.accelHandle = 0; osEventUnsubscribe(mTask.tid, EVT_SENSOR_ACC_DATA_RDY); } if (mTask.gyro_client_cnt == 0 && mTask.gyroHandle != 0) { sensorRelease(mTask.tid, mTask.gyroHandle); mTask.gyroHandle = 0; osEventUnsubscribe(mTask.tid, EVT_SENSOR_GYR_DATA_RDY); } if (mTask.mag_client_cnt == 0 && mTask.magHandle != 0) { sensorRelease(mTask.tid, mTask.magHandle); mTask.magHandle = 0; osEventUnsubscribe(mTask.tid, EVT_SENSOR_MAG_DATA_RDY); } idx = mSensor->idx; (void) fusionSetRate(0, ULONG_LONG_MAX, (void *)idx); } else { mTask.accel_client_cnt++; if (mSensor->use_gyro_data) mTask.gyro_client_cnt++; if (mSensor->use_mag_data) mTask.mag_client_cnt++; } configureFusion(); configureGame(); sensorSignalInternalEvt(mSensor->handle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, on, 0); return true; } static bool fusionFirmwareUpload(void *cookie) { struct FusionSensor *mSensor = &mTask.sensors[(int)cookie]; sensorSignalInternalEvt(mSensor->handle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0); return true; } static bool fusionFlush(void *cookie) { struct FusionSensor *mSensor = &mTask.sensors[(int)cookie]; uint32_t evtType = sensorGetMyEventType(mSi[mSensor->idx].sensorType); osEnqueueEvt(evtType, SENSOR_DATA_EVENT_FLUSH, NULL); return true; } static void fusionHandleEvent(uint32_t evtType, const void* evtData) { struct TripleAxisDataEvent *ev; int i; if (evtData == SENSOR_DATA_EVENT_FLUSH) return; switch (evtType) { case EVT_APP_START: // check for gyro and mag osEventUnsubscribe(mTask.tid, EVT_APP_START); if (!sensorFind(SENS_TYPE_GYRO, 0, &mTask.gyroHandle)) { for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++) mTask.sensors[i].use_gyro_data = false; } mTask.gyroHandle = 0; if (!sensorFind(SENS_TYPE_MAG, 0, &mTask.magHandle)) { for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++) mTask.sensors[i].use_mag_data = false; } mTask.magHandle = 0; break; case EVT_SENSOR_ACC_DATA_RDY: ev = (struct TripleAxisDataEvent *)evtData; fillSamples(ev, ACC); drainSamples(); break; case EVT_SENSOR_GYR_DATA_RDY: ev = (struct TripleAxisDataEvent *)evtData; fillSamples(ev, GYR); drainSamples(); break; case EVT_SENSOR_MAG_BIAS: ev = (struct TripleAxisDataEvent *)evtData; if (ev->samples[0].firstSample.biasPresent && mTask.flags & FUSION_FLAG_ENABLED) { //it is a user initiated mag cal event fusionSetMagTrust(&mTask.fusion, MANUAL_MAG_CAL); } break; case EVT_SENSOR_MAG_DATA_RDY: ev = (struct TripleAxisDataEvent *)evtData; fillSamples(ev, MAG); drainSamples(); break; } } static const struct SensorOps mSops = { .sensorPower = fusionPower, .sensorFirmwareUpload = fusionFirmwareUpload, .sensorSetRate = fusionSetRate, .sensorFlush = fusionFlush, }; static bool fusionStart(uint32_t tid) { size_t i, slabSize; mTask.tid = tid; mTask.flags = 0; for (i = 0; i < NUM_OF_RAW_SENSOR; i++) { mTask.sample_counts[i] = 0; mTask.sample_indices[i] = 0; } for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++) { mTask.sensors[i].handle = sensorRegister(&mSi[i], &mSops, (void *)i, true); mTask.sensors[i].idx = i; mTask.sensors[i].use_gyro_data = true; mTask.sensors[i].use_mag_data = true; } mTask.sensors[GEOMAG].use_gyro_data = false; mTask.sensors[GAME].use_mag_data = false; mTask.sensors[GRAVITY].use_mag_data = false; mTask.sensors[LINEAR].use_mag_data = false; mTask.accel_client_cnt = 0; mTask.gyro_client_cnt = 0; mTask.mag_client_cnt = 0; slabSize = sizeof(struct TripleAxisDataEvent) + MAX_NUM_COMMS_EVENT_SAMPLES * sizeof(struct TripleAxisDataPoint); // worst case 6 output sensors * (N + 1) comms_events mDataSlab = slabAllocatorNew(slabSize, 4, 6 * (NUM_COMMS_EVENTS_IN_FIFO + 1)); if (!mDataSlab) { osLog(LOG_ERROR, "ORIENTATION: slabAllocatorNew() FAILED\n"); return false; } osEventSubscribe(mTask.tid, EVT_APP_START); return true; } static void fusionEnd() { mTask.flags &= ~FUSION_FLAG_INITIALIZED; mTask.flags &= ~FUSION_FLAG_GAME_INITIALIZED; slabAllocatorDestroy(mDataSlab); } INTERNAL_APP_INIT( APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 4), ORIENTATION_APP_VERSION, fusionStart, fusionEnd, fusionHandleEvent);