Создание собственной системы стабилизации

oleg70
alexeykozin:

дельта инс не имеет интегратора,

Т.е. фактически у Вас не дельта скорости на выходе а дельта ускорения… а прибавляете вы ее, если не ошибаюсь, к расстоянию по жпс… как то не клеится (??)…
Ну да ладно, а результат проверки этого алгоритма в полете можете прокоментировать ? лучше стало ? (насколько)…

alexeykozin
oleg70:

Т.е. фактически у Вас не дельта скорости на выходе а дельта ускорения…

в настоящее время беру и скорость и текущую позицию прямо из расчетов инерциалки
сохряняю текущую позицию и скрость в буффер “снизу” это проталкивает данные наверх, сэмплов 5, шестой затирается.
github.com/kozinalexey/…/AP_InertialNav.cpp#L255

_hp_x.push_back(_position.x); //save current inav position to buffer back point for calculate position change during gps delay
_hp_y.push_back(_position.y);

а тут я считаю разницу между тем что было по состоянию на момент актуальности текущих но запаздывающих жпс данных и тем что сейчас

github.com/kozinalexey/…/AP_InertialNav.cpp#L268

_gps_position_lag_x = _position.x - _hp_x.peek(_gps_sample_number); //peek 0 400ms peek 1 300ms etc
_gps_position_lag_y = _position.y - _hp_y.peek(_gps_sample_number);	

но на быстрых процах можно было бы из ускорений все считать

oleg70:

Ну да ладно, а результат проверки этого алгоритма в полете можете прокоментировать ? лучше стало ? (насколько)…

визуально сильно снизилась тенденция к раскрутке по спирали при ошибке компаса, при анализе логов уменьшилось расхождение между координатами ГНСС и INAV. я вывел в лог в пакете INAV параметры LA LN с тем чтобы можно было сравнивать инерциальные координаты с данными навигационного приемника gps LAT LNG прямо в анализаторе логов мишен планера

rual
oleg70:

Т.е. фактически у Вас не дельта скорости на выходе а дельта ускорения… а прибавляете вы ее, если не ошибаюсь, к расстоянию по жпс… как то не клеится (??)…

да нет, всё клеится РастИНС1-РастИНС0 = дельтаРастИНС, это как раз и есть расчет ИНСки между отсчетами ГНСС, т.е. актуализация положения между отсчетами ГНСС. дельтаРастИНС/dt = СкоростьИНС (за период dt); где dt - период получения данных ГНСС. Алексей, коэффициент притяжки ИНС и период обновления ИНС какие?

alexeykozin
rual:

Алексей, коэффициент притяжки ИНС и период обновления ИНС какие?

_gps_k_gps_spd =0.003 к 0.997 (подтяг скорости )
_gps_k_gps_pos =0.008 к 0.992 (подтяг позиции )

осуществляется с частотой около 100 гц

_position.x = _position.x * _gps_k_inav_pos +  (_gps_position.x + _gps_position_lag_x) * _gps_k_gps_pos; //pool inav position to gps position
_position.y = _position.y * _gps_k_inav_pos +  (_gps_position.y + _gps_position_lag_y) * _gps_k_gps_pos;


_velocity.x = _velocity.x * _gps_k_inav_spd + (_gps_velocity_x + _gps_velocity_lag_x) * _gps_k_gps_spd ; //pool inav velocity to gps velocity
_velocity.y = _velocity.y * _gps_k_inav_spd + (_gps_velocity_y + _gps_velocity_lag_y) * _gps_k_gps_spd ;

но имеет смысл после внедрения компенсациипопробовать подтяг скорости усилить до 0.005 к 0.995
параметры подтяга и компенсации жпс лага - ругулируемые из парметров, привел значения которые были использованы в полете на видео

oleg70
alexeykozin:

к раскрутке по спирали при ошибке компаса,

компас, я так понял, смешивается с гирой и акселем в общий алгоритм ориентации ИНС (типа алго магвика и махони)??

alexeykozin:

визуально сильно снизилась тенденция к раскрутке

тогда - это результат (!), поздравляю…

oleg70

Я тут был воодушевлён применением RPi в качестве контроллера полета… Так вот последние изыскания показали, что при работе с SPI многозадачный линукс становится фактически однозадачным… Иными словами - запустив в одном терминале процесс обмена данными по SPI и их выводом на экран, другие задачи запускаться не собираются до тех пор пока мы не убьём этот процесс…
Вывод: нафига тогда всё это (?), и вопрос по передаче видео по WiFi и тому подобное отпадает сам собой… Не понятно как люди, делающие платы типа “NAVIO” надеются на развитие этого направления (?).
Там правда у них какойто “патч” линукса… и похоже I2C… - но это, по моему, только усугубляет проблему…

rual
oleg70:

Так вот последние изыскания показали, что при работе с SPI многозадачный линукс становится фактически однозадачным… Иными словами - запустив в одном терминале процесс обмена данными по SPI и их выводом на экран, другие задачи запускаться не собираются до тех пор пока мы не убьём этот процесс…

Дык там похоже внутри процесса бесконечный опрос битов в регистре СПИ, надо код драйвера прошерстить, в момент ожидания должна вызываться спецфункция передачи управления ОС.

oleg70
rual:

надо код драйвера прошерстить,

Да, тут без “хирургического вмешательства” не обойтись, только для такого меня этот процесс может надолго затянуться… проще наверно дождаться когда “гурманы-операционьщики” сделают нормальную РТОС для этих целей.

alexeykozin

имхо напрасное это дело возлагать на бортовой компьютер функции по стабилизации полета и управлении моторами.
куда более было бы просто оставить стабилизацию на хорошем проверенном апм а функции навигации реализовать на бортовом компе
втч визуальную ориентацию. требуемые для полета угловые поправки можно передавать по мавлинку через сериал

oleg70
alexeykozin:

а функции навигации реализовать на бортовом компе
втч визуальную ориентацию.

Всю навигацию, любого уровня сложности, stm обсчитывает не напрягаясь… Я у себя ему и RTOS и OSD навесил + 500 гц реалтайм, а ему (stmF4) всё ни по чём, только ОЗУ уже впритык… А визуальная ориентация - больше мечты, чем реальность, пока даже прототипов не видно на просторах инета.
От RPi хотелось только одного - чтоб весь софт был на языке высокого уровня, чтоб без всякой компиляции/перепрошивки и прочей колготы с программаторами и средами разработки, чуть ли не в поле, испытывать те или иные идеи алгоритмов управления, навешивать новые сенсоры, и т.д … пока что глухо с этой затеей (на полку, до лучших времен)

alexeykozin
oleg70:

Всю навигацию, любого уровня сложности, stm обсчитывает не напрягаясь…

за исключением визуального ориентирования, именно его я и имел ввиду под внешней навигационной системой.

rual

Привет Друзья и Коллеги по хобби! Пользуясь случаем поздравляю всех с наступающим Новым годом! Здоровья крепкого Вам и родным, удачи во всех начинаниях, мирного неба! Высоких и красивых полётов!

11 days later
SergDoc

Всем здравия в новом году!
Шавет нужен:
Короче писал-писал драйвер да не выписал, точнее драйвер то есть, но случилось беда - при экспериментах с мелкой уложил 6000 и лапу проца - но не суть, куплю - приедут. Как говорится “плохой результат-тоже результат” )))
Суть в другом, пока это всё ехать собирается, на мелкой накопилось куча косяков и вопросов которые надо решать:

  1. и самое основное - что делать? (риторический вопрос) т.к. я-мы убедились, что компас в 9250 - нехорошее железо и связываться с ним не хочется - есть 2 решения данной беды:
    а) вешать компас через 9250 или оставить через 6000 (это всё не проверено и недоказуемо)
    б) вешать компас на spi (HMC5983) забирать обе лапы с 6000 - тем самым забыть о 2- imu 😦
  2. сделать плату чуть шире скажем не 28Х28 а 28Х32 - будет место поставить MINI-USB рядом с выходами на моторы, а не микро как сейчас…
alexeykozin

6000 есть с донора, если нужно для опытов не вопрос.
компас надо либо вешать на спи либо понижать частоту айтуси
второй иму будет интересен, надо поискать другие свободные лапы.
юсб можно вообще убрать с мини платы. все вывести на мкроплощадки для распайки на тонкие проводки,
на микроаппарататах каждый грамм на счету кому нужен юсб припаяются
миниюсб вообще лошадь и не вариант

SergDoc
alexeykozin:

6000 есть с донора, если нужно для опытов не вопрос.

“Золушка в 12 часов превратилась в тыкву, но принца уже было не остановить” ))) я уже с ф4бы выдрал - всё пучком…
лап нет сапсем ))) - экспериментирую дальше )))

rual
SergDoc:

Всем здравия в новом году!

Привет! С Наступившим!

SergDoc:

что компас в 9250 - нехорошее железо и связываться с ним не хочется

Делись, чем оно плохо? А то у меня есть платка с ней, но пока не разбирался.

SergDoc:

а) вешать компас через 9250 или оставить через 6000 (это всё не проверено и недоказуемо)
б) вешать компас на spi (HMC5983) забирать обе лапы с 6000 - тем самым забыть о 2- imu

Вешать лучше отдельно (не через МПУ), ибо ты сам написал почему. хотя… надо бы проверить сквозное чтение.
2 датчика нужны? а зачем? если только в плане отказоустойчивости, но прецедентов в полёте ведь не было? если смысл удорожания и нагромождения?

SergDoc:
  1. сделать плату чуть шире скажем не 28Х28 а 28Х32 - будет место поставить MINI-USB рядом с выходами на моторы, а не микро как сейчас…

вот мне понравилась компоновка 20х40 banggood.com/Mini-NAZE32-SP-Racing-F3-Flight-Contr…
но правда это Ф3, там назе32шная дурь с внешним юсб-юсарт, можно поставить Ф4 и убрать мостик, и ещё флэху спиашную поставить

SergDoc
rual:

Делись, чем оно плохо? А то у меня есть платка с ней, но пока не разбирался.

грубо: если данные с hmc мне надо делить на 1095, то АК надо умножать на 1.5 ( вчера всё-таки заставил 6000 -ю упираться в аукс и2ц - надо осцилл на работе забрать теперь…

rual:

и ещё флэху спиашную поставить

мы с Максом думали на воткнуть что-нибудь вместо sd, но чёт ничего серьёзного (в плане объёма) и дешевого нет - легче sd припаять))) в мелкой пока связка фрам+сд не мешают)))

alexeykozin

недостатки сд карт.
-кардхолдер занимает много места
-содержит намагничиваемый металл и если рядом с компасом то после касания его отверткой может отравлять компасу жизнь
-содержит котнтакты нужно осторожничать с промывкой, может насосать в себя раствора промывочной жидкости и тогда привет
в логике арду заложено что если карта не вставлена при запуске то только консоль - значит нужен будет джампер при отказе от сд

SergDoc
alexeykozin:

в логике арду заложено что если карта не вставлена при запуске то только консоль - значит нужен будет джампер при отказе от сд

ну его - эту логику:

  1. если что-то не так вываливаться в консоль
  2. желательно при перепрошивке стирать фрам (задать буту такую возможность) что на мой взгляд лучше всего - никаких проблем с параметрами после прошивки…
  3. из станции стирать фрам с ребутом
    например в таулабсе в отличии от опенпилота можно флеш стирать из станции, а не заливать отдельную прошивку для этого…
    а так да особенно на мелкой - кусок железа на всё брюхо…
SergDoc

И так, HMC5883 пишется и читается через MPU6000, одна проблема - потерял ось X компаса (

/**
 ******************************************************************************
 * @addtogroup PIOS PIOS Core hardware abstraction layer
 * @{
 * @addtogroup PIOS_MPU6000M MPU6000M Functions
 * @brief Deals with the hardware interface to the 3-axis gyro
 * @{
 *
 * @file       pios_mpu6000m.c
 * @author     Swift-Flyer, , Copyright (C) 2015
 * @brief      MPU6000M 9-axis gyro accel and mag chip
 * @see        The GNU Public License (GPL) Version 3
 *
 ******************************************************************************
 */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/* Project Includes */
#include "pios.h"

#if defined(PIOS_INCLUDE_MPU6000M)

#include "physical_constants.h"
#include "pios_mpu6000m.h"
#include "pios_semaphore.h"
#include "pios_thread.h"
#include "pios_queue.h"

/* Private constants */
#define MPU6000M_TASK_PRIORITY    PIOS_THREAD_PRIO_HIGHEST
#define MPU6000M_TASK_STACK_BYTES 512
#define PIOS_MPU6000M_MAX_DOWNSAMPLE 2

#define MPU6000M_WHOAMI_ID       0x68

#ifdef PIOS_MPU6000M_SPI_HIGH_SPEED
#define MPU6000M_SPI_HIGH_SPEED              PIOS_MPU6000M_SPI_HIGH_SPEED
#else
#define MPU6000M_SPI_HIGH_SPEED              20000000    // should result in 10.5MHz clock on F4 targets like Sparky2
#endif
#define MPU6000M_SPI_LOW_SPEED               1000000



/* HMC5883 Addresses */
#define PIOS_MPU6000M_HMC5883_I2C_ADDR            0x1E
#define PIOS_MPU6000M_HMC5883_I2C_READ_ADDR      0x3D
#define PIOS_MPU6000M_HMC5883_I2C_WRITE_ADDR     0x3C
#define PIOS_MPU6000M_HMC5883_CONFIG_REG_A        (uint8_t)0x00
#define PIOS_MPU6000M_HMC5883_CONFIG_REG_B        (uint8_t)0x01
#define PIOS_MPU6000M_HMC5883_MODE_REG            (uint8_t)0x02
#define PIOS_MPU6000M_HMC5883_DATAOUT_XMSB_REG        0x03
#define PIOS_MPU6000M_HMC5883_DATAOUT_XLSB_REG        0x04
#define PIOS_MPU6000M_HMC5883_DATAOUT_ZMSB_REG        0x05
#define PIOS_MPU6000M_HMC5883_DATAOUT_ZLSB_REG        0x06
#define PIOS_MPU6000M_HMC5883_DATAOUT_YMSB_REG        0x07
#define PIOS_MPU6000M_HMC5883_DATAOUT_YLSB_REG        0x08
#define PIOS_MPU6000M_HMC5883_DATAOUT_STATUS_REG        0x09
#define PIOS_MPU6000M_HMC5883_DATAOUT_IDA_REG        0x0A
#define PIOS_MPU6000M_HMC5883_DATAOUT_IDB_REG        0x0B
#define PIOS_MPU6000M_HMC5883_DATAOUT_IDC_REG        0x0C

/* Output Data Rate */
#define PIOS_MPU6000M_HMC5883_ODR_0_75        0x00
#define PIOS_MPU6000M_HMC5883_ODR_1_5        0x04
#define PIOS_MPU6000M_HMC5883_ODR_3            0x08
#define PIOS_MPU6000M_HMC5883_ODR_7_5        0x0C
#define PIOS_MPU6000M_HMC5883_ODR_15            0x10
#define PIOS_MPU6000M_HMC5883_ODR_30            0x14
#define PIOS_MPU6000M_HMC5883_ODR_75            0x18

/* Measure configuration */
#define PIOS_MPU6000M_HMC5883_MEASCONF_NORMAL        0x00
#define PIOS_MPU6000M_HMC5883_MEASCONF_BIAS_POS        0x01
#define PIOS_MPU6000M_HMC5883_MEASCONF_BIAS_NEG        0x02

/* Gain settings */
#define PIOS_MPU6000M_HMC5883_GAIN_0_88            0x00
#define PIOS_MPU6000M_HMC5883_GAIN_1_3            0x20
#define PIOS_MPU6000M_HMC5883_GAIN_1_9            0x40
#define PIOS_MPU6000M_HMC5883_GAIN_2_5            0x60
#define PIOS_MPU6000M_HMC5883_GAIN_4_0            0x80
#define PIOS_MPU6000M_HMC5883_GAIN_4_7            0xA0
#define PIOS_MPU6000M_HMC5883_GAIN_5_6            0xC0
#define PIOS_MPU6000M_HMC5883_GAIN_8_1            0xE0

/* Modes */
#define PIOS_MPU6000M_HMC5883_MODE_CONTINUOUS    0x00
#define PIOS_MPU6000M_HMC5883_MODE_SINGLE        0x01
#define PIOS_MPU6000M_HMC5883_MODE_IDLE            0x02
#define PIOS_MPU6000M_HMC5883_MODE_SLEEP            0x03
/* Global Variables */

enum pios_mpu6000m_dev_magic {
    PIOS_MPU6000M_DEV_MAGIC = 0x9da9b3ed,
};

struct mpu6000m_dev {
    uint32_t spi_id;
    uint32_t slave_num;
    enum pios_mpu60x0_accel_range accel_range;
    enum pios_mpu60x0_range gyro_range;
    struct pios_queue *gyro_queue;
    struct pios_queue *accel_queue;
    struct pios_queue *mag_queue;
    struct pios_thread *TaskHandle;
    struct pios_semaphore *data_ready_sema;
    const struct pios_mpu6000m_cfg *cfg;
    enum pios_mpu60x0_filter filter;
    enum pios_mpu6000m_dev_magic magic;
};

//! Global structure for this device device
static struct mpu6000m_dev *dev;

//! Private functions
static struct mpu6000m_dev *PIOS_MPU6000M_alloc(const struct pios_mpu6000m_cfg *cfg);
static int32_t PIOS_MPU6000M_Validate(struct mpu6000m_dev *dev);
static void PIOS_MPU6000M_Task(void *parameters);
static uint8_t PIOS_MPU6000M_ReadReg(uint8_t reg);
static int32_t PIOS_MPU6000M_WriteReg(uint8_t reg, uint8_t data);
static int32_t PIOS_MPU6000M_ClaimBus(bool lowspeed);
static int32_t PIOS_MPU6000M_ReleaseBus(bool lowspeed);

/**
 * @brief Allocate a new device
 */
static struct mpu6000m_dev *PIOS_MPU6000M_alloc(const struct pios_mpu6000m_cfg *cfg)
{
    struct mpu6000m_dev *mpu6000m_dev;

    mpu6000m_dev = (struct mpu6000m_dev *)PIOS_malloc(sizeof(*mpu6000m_dev));
    if (!mpu6000m_dev)
        return NULL;

    mpu6000m_dev->magic = PIOS_MPU6000M_DEV_MAGIC;

    mpu6000m_dev->accel_queue = PIOS_Queue_Create(PIOS_MPU6000M_MAX_DOWNSAMPLE, sizeof(struct pios_sensor_accel_data));
    if (mpu6000m_dev->accel_queue == NULL) {
        PIOS_free(mpu6000m_dev);
        return NULL;
    }

    mpu6000m_dev->gyro_queue = PIOS_Queue_Create(PIOS_MPU6000M_MAX_DOWNSAMPLE, sizeof(struct pios_sensor_gyro_data));
    if (mpu6000m_dev->gyro_queue == NULL) {
        PIOS_Queue_Delete(dev->accel_queue);
        PIOS_free(mpu6000m_dev);
        return NULL;
    }

    if (cfg->use_magnetometer) {
        mpu6000m_dev->mag_queue = PIOS_Queue_Create(PIOS_MPU6000M_MAX_DOWNSAMPLE, sizeof(struct pios_sensor_mag_data));
        if (mpu6000m_dev->mag_queue == NULL) {
            PIOS_Queue_Delete(dev->accel_queue);
            PIOS_Queue_Delete(dev->gyro_queue);
            PIOS_free(mpu6000m_dev);
            return NULL;
        }
    }

    mpu6000m_dev->data_ready_sema = PIOS_Semaphore_Create();
    if (mpu6000m_dev->data_ready_sema == NULL) {
        PIOS_Queue_Delete(dev->accel_queue);
        PIOS_Queue_Delete(dev->gyro_queue);
        if (cfg->use_magnetometer)
            PIOS_Queue_Delete(dev->mag_queue);
        PIOS_free(mpu6000m_dev);
        return NULL;
    }

    return mpu6000m_dev;
}

/**
 * @brief Validate the handle to the device
 * @returns 0 for valid device or -1 otherwise
 */
static int32_t PIOS_MPU6000M_Validate(struct mpu6000m_dev *dev)
{
    if (dev == NULL)
        return -1;
    if (dev->magic != PIOS_MPU6000M_DEV_MAGIC)
        return -2;
    if (dev->spi_id == 0)
        return -3;
    return 0;
}

/**
 * @brief Claim the SPI bus for the communications and select this chip
 * \param[in] flag controls if low speed access for control registers should be used
 * @return 0 if successful, -1 for invalid device, -2 if unable to claim bus
 */
static int32_t PIOS_MPU6000M_ClaimBus(bool lowspeed)
{
    if (PIOS_MPU6000M_Validate(dev) != 0)
        return -1;

    if (PIOS_SPI_ClaimBus(dev->spi_id) != 0)
        return -2;

    if (lowspeed)
        PIOS_SPI_SetClockSpeed(dev->spi_id, MPU6000M_SPI_LOW_SPEED);

    PIOS_SPI_RC_PinSet(dev->spi_id, dev->slave_num, 0);

    return 0;
}

/**
 * @brief Release the SPI bus for the communications and end the transaction
 * \param[in] must be true when bus was claimed in lowspeed mode
 * @return 0 if successful
 */
static int32_t PIOS_MPU6000M_ReleaseBus(bool lowspeed)
{
    if (PIOS_MPU6000M_Validate(dev) != 0)
        return -1;

    PIOS_SPI_RC_PinSet(dev->spi_id, dev->slave_num, 1);

    if (lowspeed)
        PIOS_SPI_SetClockSpeed(dev->spi_id, MPU6000M_SPI_HIGH_SPEED);

    PIOS_SPI_ReleaseBus(dev->spi_id);

    return 0;
}

/**
 * @brief Read a register from MPU6000M
 * @returns The register value
 * @param reg[in] Register address to be read
 */
static uint8_t PIOS_MPU6000M_ReadReg(uint8_t reg)
{
    uint8_t data;

    PIOS_MPU6000M_ClaimBus(true);

    PIOS_SPI_TransferByte(dev->spi_id, 0x80 | reg); // request byte
    data = PIOS_SPI_TransferByte(dev->spi_id, 0);   // receive response

    PIOS_MPU6000M_ReleaseBus(true);

    return data;
}

/**
 * @brief Writes one byte to the MPU6000M register
 * \param[in] reg Register address
 * \param[in] data Byte to write
 * @returns 0 when success
 */
static int32_t PIOS_MPU6000M_WriteReg(uint8_t reg, uint8_t data)
{
    if (PIOS_MPU6000M_ClaimBus(true) != 0)
        return -1;

    PIOS_SPI_TransferByte(dev->spi_id, 0x7f & reg);
    PIOS_SPI_TransferByte(dev->spi_id, data);

    PIOS_MPU6000M_ReleaseBus(true);

    return 0;
}

/**
 * @brief Writes one byte to the HMC5883 register using MPU6000M I2C master
 * \param[in] reg Register address
 * \param[in] data Byte to write
 * @returns 0 when success
 */
static int32_t PIOS_MPU6000M_Mag_WriteReg(uint8_t reg, uint8_t data)
{
    // we will use I2C SLV4 to manipulate with HMC5883 control registers
    if (PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_SLV4_REG_REG, reg) != 0)
        return -1;
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_SLV4_ADDR_REG, PIOS_MPU6000M_HMC5883_I2C_ADDR);
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_SLV4_DO_REG, data);
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_SLV4_CTRL_REG, PIOS_MPU60X0_I2CSLV_EN);
    uint32_t timeout = 0;

    // wait for I2C transaction done, use simple safety
    // escape counter to prevent endless loop in case
    // MPU6000M is broken
    uint8_t status = 0;
    do {
        if (timeout++ > 50)
            return -2;

        status = PIOS_MPU6000M_ReadReg(PIOS_MPU60X0_I2C_MST_STATUS_REG);
    } while ((status & PIOS_MPU60X0_I2C_MST_SLV4_DONE) == 0);

    if (status & PIOS_MPU60X0_I2C_MST_SLV4_NACK)
        return -3;

    return 0;
}

/**
 * @brief Reads one byte from the HMC5883 register using MPU6000M I2C master
 * \param[in] reg Register address
 * \param[in] data Byte to write
 **/
static uint8_t PIOS_MPU6000M_Mag_ReadReg(uint8_t reg)
{
    // we will use I2C SLV4 to manipulate with HMC5883 control registers
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_SLV4_REG_REG, reg);
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_SLV4_ADDR_REG, PIOS_MPU6000M_HMC5883_I2C_ADDR | 0x80);
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_SLV4_CTRL_REG, PIOS_MPU60X0_I2CSLV_EN);
    uint32_t timeout = 0;

    // wait for I2C transaction done, use simple safety
    // escape counter to prevent endless loop in case
    // MPU6000M is broken
    uint8_t status = 0;
    do {
        if (timeout++ > 50)
            return 0;

        status = PIOS_MPU6000M_ReadReg(PIOS_MPU60X0_I2C_MST_STATUS_REG);
    } while ((status & PIOS_MPU60X0_I2C_MST_SLV4_DONE) == 0);

    return PIOS_MPU6000M_ReadReg(PIOS_MPU60X0_SLV4_DI_REG);
}

/**
 * @brief Initialize the HMC5883 magnetometer inside MPU6000M
 * \return 0 if success
 *
 */
static int32_t PIOS_MPU6000M_Mag_Config(void)
{
    char idm = PIOS_MPU6000M_Mag_ReadReg(PIOS_MPU6000M_HMC5883_DATAOUT_IDA_REG);

    if(idm != 'H') // Expect H

        return -2;
    // CRTL_REGA
    PIOS_MPU6000M_Mag_WriteReg(PIOS_MPU6000M_HMC5883_CONFIG_REG_A, 0x70);

    PIOS_DELAY_WaitmS(2);

    // CRTL_REGB
    PIOS_MPU6000M_Mag_WriteReg(PIOS_MPU6000M_HMC5883_CONFIG_REG_B, 0x20);

    PIOS_DELAY_WaitmS(2);

    // Mode register
    PIOS_MPU6000M_Mag_WriteReg(PIOS_MPU6000M_HMC5883_MODE_REG, 0x00);

    // give chip some time to initialize
    PIOS_DELAY_WaitmS(2);

    // configure mpu6000m to read hmc5x83 data range from register
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_SLV0_REG_REG, PIOS_MPU6000M_HMC5883_DATAOUT_XMSB_REG);
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_SLV0_ADDR_REG, PIOS_MPU6000M_HMC5883_I2C_ADDR | 0x80);
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_SLV0_CTRL_REG, PIOS_MPU60X0_I2CSLV_EN | 6);

    return 0;
}
/**
 * @brief Read the identification bytes from the HMC5883 sensor
 * \param[out] uint8_t array of size 4 to store HMC5883 ID.
 * \return 0 if successful, -1 if not

static uint8_t PIOS_MPU6000M_HMC5883_ReadID(uint8_t out)
{
    uint8_t retval = PIOS_MPU6000M_Mag_ReadReg(PIOS_MPU6000M_HMC5883_DATAOUT_IDA_REG, 0);
    out = '\0';
    return retval;
} */
/**
 * @brief Initialize the MPU6000M gyro & accel registers
 * \return 0 if successful
 * \param[in] pios_mpu6000m_cfg struct to be used to configure sensor.
 *
 */
static int32_t PIOS_MPU6000M_Config(struct pios_mpu6000m_cfg const *cfg)
{
    // reset chip
    if (PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_PWR_MGMT_REG, PIOS_MPU60X0_PWRMGMT_IMU_RST) != 0)
        return -1;

    // give chip some time to initialize
    PIOS_DELAY_WaitmS(50);

    uint8_t id = PIOS_MPU6000M_ReadReg(PIOS_MPU60X0_WHOAMI);
    if (id != MPU6000M_WHOAMI_ID)
        return -2;


    // power management config
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_PWR_MGMT_REG, PIOS_MPU60X0_PWRMGMT_PLL_X_CLK);

    // user control
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_USER_CTRL_REG, PIOS_MPU60X0_USERCTL_DIS_I2C | PIOS_MPU60X0_USERCTL_I2C_MST_EN);

    if (dev->cfg->use_magnetometer)
        if (PIOS_MPU6000M_Mag_Config() != 0)
            return -3;




    // Digital low-pass filter and scale
    // set this before sample rate else sample rate calculation will fail

     PIOS_MPU6000M_SetLPF(cfg->default_filter);

    // Sample rate
    PIOS_MPU6000M_SetSampleRate(cfg->default_samplerate);

    // Set the gyro scale
    PIOS_MPU6000M_SetGyroRange(PIOS_MPU60X0_SCALE_500_DEG);

    // Set the accel scale
    PIOS_MPU6000M_SetAccelRange(PIOS_MPU60X0_ACCEL_8G);

    // Interrupt configuration
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_INT_CFG_REG, cfg->interrupt_cfg);

    // Interrupt enable
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_INT_EN_REG, PIOS_MPU60X0_INTEN_DATA_RDY);

    return 0;
}

/**
 * @brief Initialize the MPU6000M 9-axis sensor.
 * @return 0 for success, -1 for failure to allocate, -10 for failure to get irq
 */
int32_t PIOS_MPU6000M_Init(uint32_t spi_id, uint32_t slave_num, const struct pios_mpu6000m_cfg *cfg)
{
    dev = PIOS_MPU6000M_alloc(cfg);
    if (dev == NULL)
        return -1;

    dev->spi_id = spi_id;
    dev->slave_num = slave_num;
    dev->cfg = cfg;

    /* Configure the MPU6000M Sensor */
    if (PIOS_MPU6000M_Config(cfg) != 0)
        return -2;

    /* Set up EXTI line */
    PIOS_EXTI_Init(cfg->exti_cfg);

    // Wait 20 ms for data ready interrupt and make sure it happens
    // twice
    if ((PIOS_Semaphore_Take(dev->data_ready_sema, 20) != true) ||
        (PIOS_Semaphore_Take(dev->data_ready_sema, 20) != true)) {
        return -10;
    }

    dev->TaskHandle = PIOS_Thread_Create(
            PIOS_MPU6000M_Task, "pios_mpu6000m", MPU6000M_TASK_STACK_BYTES, NULL, MPU6000M_TASK_PRIORITY);
    PIOS_Assert(dev->TaskHandle != NULL);

    PIOS_SENSORS_Register(PIOS_SENSOR_ACCEL, dev->accel_queue);
    PIOS_SENSORS_Register(PIOS_SENSOR_GYRO, dev->gyro_queue);

    if (dev->cfg->use_magnetometer)
        PIOS_SENSORS_Register(PIOS_SENSOR_MAG, dev->mag_queue);

    return 0;
}

/**
 * @brief Test MPU6000M presence on the bus
 * @returns 0 if success
 */
int32_t PIOS_MPU6000M_Test(void)
{
    uint8_t id = PIOS_MPU6000M_ReadReg(PIOS_MPU60X0_WHOAMI);
    if (id != 0x68)
        return 1;
    char idm = PIOS_MPU6000M_Mag_ReadReg(PIOS_MPU6000M_HMC5883_DATAOUT_IDA_REG);

    if(idm != 'H') // Expect H

        return 1;

    return 0;
}

/**
 * @brief Set gyroscope range
 * @returns 0 if successful
 * @param range[in] gyroscope range
 */
void PIOS_MPU6000M_SetGyroRange(enum pios_mpu60x0_range gyro_range)
{
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_GYRO_CFG_REG, gyro_range);

    switch (gyro_range) {
    case PIOS_MPU60X0_SCALE_250_DEG:
        PIOS_SENSORS_SetMaxGyro(250);
        break;
    case PIOS_MPU60X0_SCALE_500_DEG:
        PIOS_SENSORS_SetMaxGyro(500);
        break;
    case PIOS_MPU60X0_SCALE_1000_DEG:
        PIOS_SENSORS_SetMaxGyro(1000);
        break;
    case PIOS_MPU60X0_SCALE_2000_DEG:
        PIOS_SENSORS_SetMaxGyro(2000);
        break;
    }

    dev->gyro_range = gyro_range;

}

/**
 * @brief Set accelerometer range
 * @returns 0 if success
 * @param range[in] accelerometer range
 */
void PIOS_MPU6000M_SetAccelRange(enum pios_mpu60x0_accel_range accel_range)
{
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_ACCEL_CFG_REG, accel_range);

    dev->accel_range = accel_range;


}

/**
 * @brief Set sampling frequency of accels and gyros axes
 * @returns 0 if successful
 * @param samplerate_hz[in] Sampling frequency in Hz
 */
void PIOS_MPU6000M_SetSampleRate(uint16_t samplerate_hz)
{
    // mpu6000m ODR divider is unable to run from 8kHz clock like mpu60x0 :(
    // check if someone want to use 250Hz DLPF and don't want 8kHz sampling
    // and politely refuse him
    uint16_t filter_frequency = 8000;

    if (dev->filter != PIOS_MPU60X0_LOWPASS_256_HZ)
        filter_frequency = 1000;




    // limit samplerate to filter frequency
    if (samplerate_hz > filter_frequency)
        samplerate_hz = filter_frequency;

    // calculate divisor, round to nearest integeter
    int32_t divisor = (int32_t)(((float)filter_frequency / samplerate_hz) + 0.5f) - 1;

    // limit resulting divisor to register value range
    if (divisor < 0)
        divisor = 0;

    if (divisor > 0xff)
        divisor = 0xff;

     PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_SMPLRT_DIV_REG, (uint8_t)divisor);
}

/**
 * Configure the digital low-pass filter
 */
void PIOS_MPU6000M_SetLPF(enum pios_mpu60x0_filter filter)
{
    PIOS_MPU6000M_WriteReg(PIOS_MPU60X0_DLPF_CFG_REG, filter);

    dev->filter = filter;
}

/**
 * @brief Get current gyro scale for deg/s
 * @returns scale
 */
static float PIOS_MPU6000M_GetGyroScale(void)
{
    switch (dev->gyro_range) {
    case PIOS_MPU60X0_SCALE_250_DEG:
        return 1.0f / 131.0f;
    case PIOS_MPU60X0_SCALE_500_DEG:
        return 1.0f / 65.5f;
    case PIOS_MPU60X0_SCALE_1000_DEG:
        return 1.0f / 32.8f;
    case PIOS_MPU60X0_SCALE_2000_DEG:
        return 1.0f / 16.4f;
    }
    return 0;
}

/**
 * @brief Get current gyro scale for ms^-2
 * @returns scale
 */
static float PIOS_MPU6000M_GetAccelScale(void)
{
    switch (dev->accel_range) {
    case PIOS_MPU60X0_ACCEL_2G:
        return GRAVITY / 16384.0f;
    case PIOS_MPU60X0_ACCEL_4G:
        return GRAVITY / 8192.0f;
    case PIOS_MPU60X0_ACCEL_8G:
        return GRAVITY / 4096.0f;
    case PIOS_MPU60X0_ACCEL_16G:
        return GRAVITY / 2048.0f;
    }
    return 0;
}

/**
* @brief IRQ Handler.  Notice MPU6000M task to read all sensors data.
*/
bool PIOS_MPU6000M_IRQHandler(void)
{
    if (PIOS_MPU6000M_Validate(dev) != 0)
        return false;

    bool need_yield = false;

    PIOS_Semaphore_Give_FromISR(dev->data_ready_sema, &need_yield);

    return need_yield;
}

static void PIOS_MPU6000M_Task(void *parameters)
{
    while (1) {
        //Wait for data ready interrupt
        if (PIOS_Semaphore_Take(dev->data_ready_sema, PIOS_SEMAPHORE_TIMEOUT_MAX) != true)
            continue;

        enum {
            IDX_REG = 0,
            IDX_ACCEL_XOUT_H,
            IDX_ACCEL_XOUT_L,
            IDX_ACCEL_YOUT_H,
            IDX_ACCEL_YOUT_L,
            IDX_ACCEL_ZOUT_H,
            IDX_ACCEL_ZOUT_L,
            IDX_TEMP_OUT_H,
            IDX_TEMP_OUT_L,
            IDX_GYRO_XOUT_H,
            IDX_GYRO_XOUT_L,
            IDX_GYRO_YOUT_H,
            IDX_GYRO_YOUT_L,
            IDX_GYRO_ZOUT_H,
            IDX_GYRO_ZOUT_L,
            IDX_MAG_XOUT_L,
            IDX_MAG_XOUT_H,
            IDX_MAG_YOUT_L,
            IDX_MAG_YOUT_H,
            IDX_MAG_ZOUT_L,
            IDX_MAG_ZOUT_H,
            BUFFER_SIZE,
        };

        uint8_t mpu6000m_rec_buf[BUFFER_SIZE];
        uint8_t mpu6000m_tx_buf[BUFFER_SIZE] = {PIOS_MPU60X0_ACCEL_X_OUT_MSB | 0x80, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

        uint8_t transfer_size = (dev->cfg->use_magnetometer) ? BUFFER_SIZE : BUFFER_SIZE - 6;
        // claim bus in high speed mode
        if (PIOS_MPU6000M_ClaimBus(false) != 0)
            continue;

        if (PIOS_SPI_TransferBlock(dev->spi_id, mpu6000m_tx_buf, mpu6000m_rec_buf, transfer_size, 0) < 0) {
            PIOS_MPU6000M_ReleaseBus(false);
            continue;
        }

        PIOS_MPU6000M_ReleaseBus(false);

        struct pios_sensor_accel_data accel_data;
        struct pios_sensor_gyro_data gyro_data;
        struct pios_sensor_mag_data mag_data;

        float accel_x = (int16_t)(mpu6000m_rec_buf[IDX_ACCEL_XOUT_H] << 8 | mpu6000m_rec_buf[IDX_ACCEL_XOUT_L]);
        float accel_y = (int16_t)(mpu6000m_rec_buf[IDX_ACCEL_YOUT_H] << 8 | mpu6000m_rec_buf[IDX_ACCEL_YOUT_L]);
        float accel_z = (int16_t)(mpu6000m_rec_buf[IDX_ACCEL_ZOUT_H] << 8 | mpu6000m_rec_buf[IDX_ACCEL_ZOUT_L]);
        float gyro_x = (int16_t)(mpu6000m_rec_buf[IDX_GYRO_XOUT_H] << 8 | mpu6000m_rec_buf[IDX_GYRO_XOUT_L]);
        float gyro_y = (int16_t)(mpu6000m_rec_buf[IDX_GYRO_YOUT_H] << 8 | mpu6000m_rec_buf[IDX_GYRO_YOUT_L]);
        float gyro_z = (int16_t)(mpu6000m_rec_buf[IDX_GYRO_ZOUT_H] << 8 | mpu6000m_rec_buf[IDX_GYRO_ZOUT_L]);
        float mag_x = (int16_t)(mpu6000m_rec_buf[IDX_MAG_XOUT_H] << 8 | mpu6000m_rec_buf[IDX_MAG_XOUT_L]);
        float mag_y = (int16_t)(mpu6000m_rec_buf[IDX_MAG_YOUT_H] << 8 | mpu6000m_rec_buf[IDX_MAG_YOUT_L]);
        float mag_z = (int16_t)(mpu6000m_rec_buf[IDX_MAG_ZOUT_H] << 8 | mpu6000m_rec_buf[IDX_MAG_ZOUT_L]);

        // Rotate the sensor to TL convention.  The datasheet defines X as towards the right
        // and Y as forward. TL convention transposes this.  Also the Z is defined negatively
        // to our convention. This is true for accels and gyros. Magnetometer corresponds TL convention.
        switch (dev->cfg->orientation) {
        case PIOS_MPU6000M_TOP_0DEG:
            accel_data.y = accel_x;
            accel_data.x = accel_y;
            accel_data.z = -accel_z;
            gyro_data.y  = gyro_x;
            gyro_data.x  = gyro_y;
            gyro_data.z  = -gyro_z;
            mag_data.x   = mag_y;
            mag_data.y   = mag_x;
            mag_data.z   = -mag_z;
            break;
        case PIOS_MPU6000M_TOP_90DEG:
            accel_data.y = -accel_y;
            accel_data.x = accel_x;
            accel_data.z = -accel_z;
            gyro_data.y  = -gyro_y;
            gyro_data.x  = gyro_x;
            gyro_data.z  = -gyro_z;
            mag_data.x   = -mag_x;
            mag_data.y   = mag_y;
            mag_data.z   = -mag_z;
            break;
        case PIOS_MPU6000M_TOP_180DEG:
            accel_data.y = -accel_x;
            accel_data.x = -accel_y;
            accel_data.z = -accel_z;
            gyro_data.y  = -gyro_x;
            gyro_data.x  = -gyro_y;
            gyro_data.z  = -gyro_z;
            mag_data.x   = -mag_y;
            mag_data.y   = -mag_x;
            mag_data.z   = -mag_z;

            break;
        case PIOS_MPU6000M_TOP_270DEG:
            accel_data.y = accel_y;
            accel_data.x = -accel_x;
            accel_data.z = -accel_z;
            gyro_data.y  = gyro_y;
            gyro_data.x  = -gyro_x;
            gyro_data.z  = -gyro_z;
            mag_data.x   = mag_x;
            mag_data.y   = -mag_y;
            mag_data.z   = -mag_z;
            break;
        case PIOS_MPU6000M_BOTTOM_0DEG:
            accel_data.y = -accel_x;
            accel_data.x = accel_y;
            accel_data.z = accel_z;
            gyro_data.y  = -gyro_x;
            gyro_data.x  = gyro_y;
            gyro_data.z  = gyro_z;
            mag_data.x   = mag_y;
            mag_data.y   = -mag_x;
            mag_data.z   = mag_z;
            break;

        case PIOS_MPU6000M_BOTTOM_90DEG:
            accel_data.y = -accel_y;
            accel_data.x = -accel_x;
            accel_data.z = accel_z;
            gyro_data.y  = -gyro_y;
            gyro_data.x  = -gyro_x;
            gyro_data.z  = gyro_z;
            mag_data.x   = -mag_x;
            mag_data.y   = -mag_y;
            mag_data.z   = mag_z;
            break;

        case PIOS_MPU6000M_BOTTOM_180DEG:
            accel_data.y = accel_x;
            accel_data.x = -accel_y;
            accel_data.z = accel_z;
            gyro_data.y  = gyro_x;
            gyro_data.x  = -gyro_y;
            gyro_data.z  = gyro_z;
            mag_data.x   = -mag_y;
            mag_data.y   = mag_x;
            mag_data.z   = mag_z;
            break;

        case PIOS_MPU6000M_BOTTOM_270DEG:
            accel_data.y = accel_y;
            accel_data.x = accel_x;
            gyro_data.y  = gyro_y;
            gyro_data.x  = gyro_x;
            gyro_data.z  = gyro_z;
            accel_data.z = accel_z;
            mag_data.x   = mag_x;
            mag_data.y   = mag_y;
            mag_data.z   = mag_z;
            break;

        }

        int16_t raw_temp = (int16_t)(mpu6000m_rec_buf[IDX_TEMP_OUT_H] << 8 | mpu6000m_rec_buf[IDX_TEMP_OUT_L]);
        float temperature = 21.0f + ((float)raw_temp) / 333.87f;

        // Apply sensor scaling
        float accel_scale = PIOS_MPU6000M_GetAccelScale();
        accel_data.x *= accel_scale;
        accel_data.y *= accel_scale;
        accel_data.z *= accel_scale;
        accel_data.temperature = temperature;

        float gyro_scale = PIOS_MPU6000M_GetGyroScale();
        gyro_data.x *= gyro_scale;
        gyro_data.y *= gyro_scale;
        gyro_data.z *= gyro_scale;
        gyro_data.temperature = temperature;

        PIOS_Queue_Send(dev->accel_queue, &accel_data, 0);
        PIOS_Queue_Send(dev->gyro_queue, &gyro_data, 0);

        if (dev->cfg->use_magnetometer) {

                mag_data.x /= 1090.0f;
                mag_data.y /= 1090.0f;
                mag_data.z /= 1090.0f;
                PIOS_Queue_Send(dev->mag_queue, &mag_data, 0);

        }
    }
}

#endif

/**
 * @}
 * @}
 */