Esempio n. 1
0
TEST(Trims, invertedThrottlePlusThrottleTrim)
{
  MODEL_RESET();
  modelDefault(0);
  g_model.throttleReversed = 1;
  g_model.thrTrim = 1;
  // stick max + trim max
  anaInValues[THR_STICK] = +1024;
  setTrimValue(0, THR_STICK, TRIM_MAX);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], -1024);
  // stick max + trim mid
  anaInValues[THR_STICK] = +1024;
  setTrimValue(0, THR_STICK, 0);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], -1024+250);
  // stick max + trim min
  anaInValues[THR_STICK] = +1024;
  setTrimValue(0, THR_STICK, TRIM_MIN);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], -1024+500);
  // stick min + trim max
  anaInValues[THR_STICK] = -1024;
  setTrimValue(0, THR_STICK, TRIM_MAX);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], +1024);
  // stick min + trim min
  anaInValues[THR_STICK] = -1024;
  setTrimValue(0, THR_STICK, TRIM_MIN);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], +1024);

  // now the same tests with extended Trims
  g_model.extendedTrims = 1;
  // stick max + trim max
  anaInValues[THR_STICK] = +1024;
  setTrimValue(0, THR_STICK, TRIM_EXTENDED_MAX);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], -1024);
  // stick max + trim mid
  anaInValues[THR_STICK] = +1024;
  setTrimValue(0, THR_STICK, 0);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], -1024+1000);
  // stick max + trim min
  anaInValues[THR_STICK] = +1024;
  setTrimValue(0, THR_STICK, TRIM_EXTENDED_MIN);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], -1024+2000);
  // stick min + trim max
  anaInValues[THR_STICK] = -1024;
  setTrimValue(0, THR_STICK, TRIM_EXTENDED_MAX);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], +1024);
  // stick min + trim min
  anaInValues[THR_STICK] = -1024;
  setTrimValue(0, THR_STICK, TRIM_EXTENDED_MIN);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], +1024);
}
Esempio n. 2
0
TEST(Trims, greaterTrimLink)
{
  MODEL_RESET();
  setTrimValue(1, RUD_STICK, TRIM_EXTENDED_MAX+3); // link to FP3 trim
  setTrimValue(3, RUD_STICK, 32);
  EXPECT_EQ(getRawTrimValue(getTrimFlightPhase(1, RUD_STICK), RUD_STICK), 32);
}
Esempio n. 3
0
TEST(Trims, chainedTrims)
{
  MODEL_RESET();
  setTrimValue(0, RUD_STICK, 32);
  setTrimValue(1, RUD_STICK, TRIM_EXTENDED_MAX+1); // link to FP0 trim
  setTrimValue(2, RUD_STICK, TRIM_EXTENDED_MAX+2); // link to FP1 trim
  EXPECT_EQ(getRawTrimValue(getTrimFlightPhase(0, RUD_STICK), RUD_STICK), 32);
}
Esempio n. 4
0
TEST(Trims, infiniteChainedTrims)
{
  MODEL_RESET();
  setTrimValue(0, 0, 32);
  setTrimValue(1, 0, TRIM_EXTENDED_MAX+3); // link to FP3 trim
  setTrimValue(2, 0, TRIM_EXTENDED_MAX+2); // link to FP1 trim
  setTrimValue(3, 0, TRIM_EXTENDED_MAX+3); // link to FP2 trim
  EXPECT_EQ(getRawTrimValue(getTrimFlightPhase(0, 2), 0), 32);
}
Esempio n. 5
0
TEST(Trims, CopyTrimsToOffset)
{
  MODEL_RESET();
  modelDefault(0);
  setTrimValue(0, ELE_STICK, -100); // -100 on elevator
  evalFunctions(); // it disables all safety channels
  copyTrimsToOffset(1);
  EXPECT_EQ(getTrimValue(0, ELE_STICK), -100); // unchanged
#if defined(CPUARM)
  EXPECT_EQ(g_model.limitData[1].offset, -195);
#else
  EXPECT_EQ(g_model.limitData[1].offset, -200);
#endif
}
Esempio n. 6
0
TEST(getSwitch, inputWithTrim)
{
  MODEL_RESET();
  modelDefault(0);
  MIXER_RESET();

  g_model.logicalSw[0] = { LS_FUNC_VPOS, MIXSRC_FIRST_INPUT, 0, 0 };

  doMixerCalculations();
  evalLogicalSwitches();
  EXPECT_EQ(getSwitch(SWSRC_SW1), false);

  setTrimValue(0, 0, 32);
  doMixerCalculations();
  evalLogicalSwitches();
  EXPECT_EQ(getSwitch(SWSRC_SW1), true);
}
Esempio n. 7
0
TEST(Mixer, NoTrimOnInactiveMix)
{
  MODEL_RESET();
  MIXER_RESET();
  g_model.mixData[0].destCh = 0;
  g_model.mixData[0].mltpx = MLTPX_ADD;
  g_model.mixData[0].srcRaw = MIXSRC_Thr;
  g_model.mixData[0].weight = 100;
  g_model.mixData[0].swtch = SWSRC_THR;
  g_model.mixData[0].speedUp = SLOW_STEP*5;
  g_model.mixData[0].speedDown = SLOW_STEP*5;
  setTrimValue(0, 2, 256);

  s_mixer_first_run_done = true;

  simuSetSwitch(0, 1);
  CHECK_SLOW_MOVEMENT(0, 1, 100);

  simuSetSwitch(0, -1);
  CHECK_SLOW_MOVEMENT(0, -1, 100);
}
Esempio n. 8
0
TEST(Trims, invertedThrottlePlusthrottleTrimWithZeroWeightOnThrottle)
{
  MODEL_RESET();
  modelDefault(0);
  g_model.throttleReversed = 1;
  g_model.thrTrim = 1;
#if defined(PCBTARANIS)
  // the input already exists
  ExpoData *expo = expoAddress(THR_STICK);
#else
  ExpoData *expo = expoAddress(0);
  expo->mode = 3;
  expo->chn = THR_STICK;
#endif
  expo->weight = 0;
  // stick max + trim max
  anaInValues[THR_STICK] = +1024;
  setTrimValue(0, THR_STICK, TRIM_MAX);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 0);
  // stick max + trim mid
  anaInValues[THR_STICK] = +1024;
  setTrimValue(0, THR_STICK, 0);
  evalMixes(1);
  EXPECT_LE(abs(channelOutputs[2] - 125), 1);
  // stick max + trim min
  anaInValues[THR_STICK] = +1024;
  setTrimValue(0, THR_STICK, TRIM_MIN);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 250);
  // stick min + trim max
  anaInValues[THR_STICK] = -1024;
  setTrimValue(0, THR_STICK, TRIM_MAX);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 0);
  // stick min + trim mid
  anaInValues[THR_STICK] = -1024;
  setTrimValue(0, THR_STICK, 0);
  evalMixes(1);
  EXPECT_LE(abs(channelOutputs[2] - 125), 1);
  // stick min + trim min
  anaInValues[THR_STICK] = -1024;
  setTrimValue(0, THR_STICK, TRIM_MIN);
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 250);

  // now some tests with extended Trims
  g_model.extendedTrims = 1;
  // trim min + various stick positions = should always be same value
  setTrimValue(0, THR_STICK, TRIM_EXTENDED_MIN);
  anaInValues[THR_STICK] = -1024;
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 1000);
  anaInValues[THR_STICK] = -300;
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 1000);
  anaInValues[THR_STICK] = +300;
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 1000);
  anaInValues[THR_STICK] = +1024;
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 1000);

  // trim max + various stick positions = should always be same value
  setTrimValue(0, THR_STICK, TRIM_EXTENDED_MAX);
  anaInValues[THR_STICK] = -1024;
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 0);
  anaInValues[THR_STICK] = -300;
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 0);
  anaInValues[THR_STICK] = +300;
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 0);
  anaInValues[THR_STICK] = +1024;
  evalMixes(1);
  EXPECT_EQ(channelOutputs[2], 0);
}
void menuModelPhaseOne(uint8_t event)
{
  FlightModeData *fm = flightModeAddress(s_currIdx);
  putsFlightMode(13*FW, 0, s_currIdx+1, (getFlightMode()==s_currIdx ? BOLD : 0));

#if defined(GVARS) && !defined(PCBSTD)
  static const pm_uint8_t mstate_tab_fm1[] PROGMEM = {0, 0, 0, (uint8_t)-1, 1, 1, 1, 1, 1};
  static const pm_uint8_t mstate_tab_others[] PROGMEM = {0, 0, 3, IF_ROTARY_ENCODERS(NUM_ROTARY_ENCODERS-1) 0, 0, (uint8_t)-1, 2, 2, 2, 2, 2};

  check(event, 0, NULL, 0, (s_currIdx == 0) ? mstate_tab_fm1 : mstate_tab_others, DIM(mstate_tab_others)-1, ITEM_MODEL_PHASE_MAX - 1 - (s_currIdx==0 ? (ITEM_MODEL_PHASE_FADE_IN-ITEM_MODEL_PHASE_SWITCH) : 0));

  TITLE(STR_MENUFLIGHTPHASE);

  #define PHASE_ONE_FIRST_LINE (1+1*FH)
#else
  SUBMENU(STR_MENUFLIGHTPHASE, 3 + (s_currIdx==0 ? 0 : 2 + (bool)NUM_ROTARY_ENCODERS), {0, 0, 3, IF_ROTARY_ENCODERS(NUM_ROTARY_ENCODERS-1) 0/*, 0*/});
  #define PHASE_ONE_FIRST_LINE (1+1*FH)
#endif

  int8_t sub = m_posVert;
  int8_t editMode = s_editMode;

#if defined(GVARS) && !defined(PCBSTD)
  if (s_currIdx == 0 && sub>=ITEM_MODEL_PHASE_SWITCH) sub += ITEM_MODEL_PHASE_FADE_IN-ITEM_MODEL_PHASE_SWITCH;

  for (uint8_t k=0; k<LCD_LINES-1; k++) {
    coord_t y = MENU_HEADER_HEIGHT + 1 + k*FH;
    int8_t i = k + s_pgOfs;
    if (s_currIdx == 0 && i>=ITEM_MODEL_PHASE_SWITCH) i += ITEM_MODEL_PHASE_FADE_IN-ITEM_MODEL_PHASE_SWITCH;
    uint8_t attr = (sub==i ? (editMode>0 ? BLINK|INVERS : INVERS) : 0);
#else
  for (uint8_t i=0, k=0, y=PHASE_ONE_FIRST_LINE; i<ITEM_MODEL_PHASE_MAX; i++, k++, y+=FH) {
    if (s_currIdx == 0 && i==ITEM_MODEL_PHASE_SWITCH) i = ITEM_MODEL_PHASE_FADE_IN;
    uint8_t attr = (sub==k ? (editMode>0 ? BLINK|INVERS : INVERS) : 0);
#endif
    switch(i) {
      case ITEM_MODEL_PHASE_NAME:
        editSingleName(MIXES_2ND_COLUMN, y, STR_PHASENAME, fm->name, sizeof(fm->name), event, attr);
        break;
      case ITEM_MODEL_PHASE_SWITCH:
        fm->swtch = switchMenuItem(MIXES_2ND_COLUMN, y, fm->swtch, attr, event);
        break;
      case ITEM_MODEL_PHASE_TRIMS:
        lcd_putsLeft(y, STR_TRIMS);
        for (uint8_t t=0; t<NUM_STICKS; t++) {
          putsTrimMode(MIXES_2ND_COLUMN+(t*FW), y, s_currIdx, t, m_posHorz==t ? attr : 0);
          if (attr && m_posHorz==t && ((editMode>0) || p1valdiff)) {
            int16_t v = getRawTrimValue(s_currIdx, t);
            if (v < TRIM_EXTENDED_MAX) v = TRIM_EXTENDED_MAX;
            v = checkIncDec(event, v, TRIM_EXTENDED_MAX, TRIM_EXTENDED_MAX+MAX_FLIGHT_MODES-1, EE_MODEL);
            if (checkIncDec_Ret) {
              if (v == TRIM_EXTENDED_MAX) v = 0;
              setTrimValue(s_currIdx, t, v);
            }
          }
        }
        break;

#if ROTARY_ENCODERS > 0
      case ITEM_MODEL_PHASE_ROTARY_ENCODERS:
        lcd_putsLeft(y, STR_ROTARY_ENCODER);
        for (uint8_t t=0; t<NUM_ROTARY_ENCODERS; t++) {
          putsRotaryEncoderMode(MIXES_2ND_COLUMN+(t*FW), y, s_currIdx, t, m_posHorz==t ? attr : 0);
          if (attr && m_posHorz==t && ((editMode>0) || p1valdiff)) {
            int16_t v = flightModeAddress(s_currIdx)->rotaryEncoders[t];
            if (v < ROTARY_ENCODER_MAX) v = ROTARY_ENCODER_MAX;
            v = checkIncDec(event, v, ROTARY_ENCODER_MAX, ROTARY_ENCODER_MAX+MAX_FLIGHT_MODES-1, EE_MODEL);
            if (checkIncDec_Ret) {
              if (v == ROTARY_ENCODER_MAX) v = 0;
              flightModeAddress(s_currIdx)->rotaryEncoders[t] = v;
            }
          }
        }
        break;
#endif

      case ITEM_MODEL_PHASE_FADE_IN:
        fm->fadeIn = EDIT_DELAY(0, y, event, attr, STR_FADEIN, fm->fadeIn);
        break;

      case ITEM_MODEL_PHASE_FADE_OUT:
        fm->fadeOut = EDIT_DELAY(0, y, event, attr, STR_FADEOUT, fm->fadeOut);
        break;

#if defined(GVARS) && !defined(PCBSTD)
      case ITEM_MODEL_PHASE_GVARS_LABEL:
        lcd_putsLeft(y, STR_GLOBAL_VARS);
        break;

      default:
      {
        uint8_t idx = i-ITEM_MODEL_PHASE_GV1;
        uint8_t posHorz = m_posHorz;
        if (attr && posHorz > 0 && s_currIdx==0) posHorz++;

        putsStrIdx(INDENT_WIDTH, y, STR_GV, idx+1);

        editName(4*FW, y, g_model.gvars[idx].name, LEN_GVAR_NAME, event, posHorz==0 ? attr : 0);

        int16_t v = fm->gvars[idx];
        if (v > GVAR_MAX) {
          uint8_t p = v - GVAR_MAX - 1;
          if (p >= s_currIdx) p++;
          putsFlightMode(11*FW, y, p+1, posHorz==1 ? attr : 0);
        }
        else {
          lcd_putsAtt(11*FW, y, STR_OWN, posHorz==1 ? attr : 0);
        }
        if (attr && s_currIdx>0 && posHorz==1 && (editMode>0 || p1valdiff)) {
          if (v < GVAR_MAX) v = GVAR_MAX;
          v = checkIncDec(event, v, GVAR_MAX, GVAR_MAX+MAX_FLIGHT_MODES-1, EE_MODEL);
          if (checkIncDec_Ret) {
            if (v == GVAR_MAX) v = 0;
            fm->gvars[idx] = v;
          }
        }

        uint8_t p = getGVarFlightPhase(s_currIdx, idx);
        lcd_outdezAtt(21*FW, y, GVAR_VALUE(idx, p), posHorz==2 ? attr : 0);
        if (attr && posHorz==2 && ((editMode>0) || p1valdiff)) {
          GVAR_VALUE(idx, p) = checkIncDec(event, GVAR_VALUE(idx, p), -GVAR_LIMIT, GVAR_LIMIT, EE_MODEL);
        }

        break;
      }
#endif
    }
  }
}

#if defined(ROTARY_ENCODERS)
  #if ROTARY_ENCODERS > 2
    #define NAME_OFS (-4-12)
    #define SWITCH_OFS (-FW/2-2-13)
    #define TRIMS_OFS  (-FW/2-4-15)
    #define ROTARY_ENC_OFS (0)
  #else
    #define NAME_OFS (-4)
    #define SWITCH_OFS (-FW/2-2)
    #define TRIMS_OFS  (-FW/2-4)
    #define ROTARY_ENC_OFS (2)
  #endif
#else
  #define NAME_OFS 0
  #define SWITCH_OFS (FW/2)
  #define TRIMS_OFS  (FW/2)
#endif

void menuModelFlightModesAll(uint8_t event)
{
  SIMPLE_MENU(STR_MENUFLIGHTPHASES, menuTabModel, e_FlightModesAll, 1+MAX_FLIGHT_MODES+1);

  int8_t sub = m_posVert - 1;

  switch (event) {
    CASE_EVT_ROTARY_BREAK
    case EVT_KEY_FIRST(KEY_ENTER):
      if (sub == MAX_FLIGHT_MODES) {
        s_editMode = 0;
        trimsCheckTimer = 200; // 2 seconds
      }
      // no break
    case EVT_KEY_FIRST(KEY_RIGHT):
      if (sub >= 0 && sub < MAX_FLIGHT_MODES) {
        s_currIdx = sub;
        pushMenu(menuModelPhaseOne);
      }
      break;
  }

  uint8_t att;
  for (uint8_t i=0; i<MAX_FLIGHT_MODES; i++) {
#if defined(CPUARM)
    int8_t y = 1 + (1+i-s_pgOfs)*FH;
    if (y<1*FH+1 || y>(LCD_LINES-1)*FH+1) continue;
#else
    uint8_t y = 1 + (i+1)*FH;
#endif
    att = (i==sub ? INVERS : 0);
    FlightModeData *p = flightModeAddress(i);
    putsFlightMode(0, y, i+1, att|(getFlightMode()==i ? BOLD : 0));

    lcd_putsnAtt(4*FW+NAME_OFS, y, p->name, sizeof(p->name), ZCHAR);
    if (i == 0) {
      lcd_puts((5+LEN_FLIGHT_MODE_NAME)*FW+SWITCH_OFS, y, STR_DEFAULT);
    }
    else {
      putsSwitches((5+LEN_FLIGHT_MODE_NAME)*FW+SWITCH_OFS, y, p->swtch, 0);
      for (uint8_t t=0; t<NUM_STICKS; t++) {
        putsTrimMode((9+LEN_FLIGHT_MODE_NAME+t)*FW+TRIMS_OFS, y, i, t, 0);
      }
#if defined(CPUM2560)
      for (uint8_t t=0; t<NUM_ROTARY_ENCODERS; t++) {
        putsRotaryEncoderMode((13+LEN_FLIGHT_MODE_NAME+t)*FW+TRIMS_OFS+ROTARY_ENC_OFS, y, i, t, 0);
      }
#endif
    }

    if (p->fadeIn || p->fadeOut) {
      lcd_putc(LCD_W-FW-MENUS_SCROLLBAR_WIDTH, y, (p->fadeIn && p->fadeOut) ? '*' : (p->fadeIn ? 'I' : 'O'));
    }
  }

#if defined(CPUARM)
  if (s_pgOfs != MAX_FLIGHT_MODES-(LCD_LINES-2)) return;
#endif

  lcd_putsLeft((LCD_LINES-1)*FH+1, STR_CHECKTRIMS);
  putsFlightMode(OFS_CHECKTRIMS, (LCD_LINES-1)*FH+1, mixerCurrentFlightMode+1);
  if (sub==MAX_FLIGHT_MODES && !trimsCheckTimer) {
    lcd_status_line();
  }
}
Esempio n. 10
0
void OpenTxSimulator::setTrim(unsigned int idx, int value)
{
  idx = NAMESPACE::modn12x3[4*getStickMode() + idx];
  ::uint8_t phase = getTrimFlightPhase(getFlightMode(), idx);
  setTrimValue(phase, idx, value);
}
Esempio n. 11
0
uint8_t Translate()
{
  if (g_eeGeneral.myVers == 0) {
    if (theFile.readRlc1((uint8_t*)&g_eeGeneral, 1) != 1)
      return 0;
    theFile.openRlc(FILE_GENERAL);
  }

  if (g_eeGeneral.myVers == EEPROM_VER_r584 || (g_eeGeneral.myVers >= EEPROM_ER9X_MIN && g_eeGeneral.myVers <= EEPROM_ER9X_MAX)) {
    alert(g_eeGeneral.myVers == EEPROM_VER_r584 ? PSTR("EEprom Data v3") : PSTR("EEprom Data Er9x"), true);
    message(PSTR("EEPROM Converting"));
    theFile.readRlc1((uint8_t*)&g_eeGeneral, sizeof(g_eeGeneral));
    memset(&g_eeGeneral.frskyRssiAlarms, 0, sizeof(g_eeGeneral.frskyRssiAlarms));
    if (g_eeGeneral.myVers == EEPROM_VER_r584) {
      // previous version had only 6 custom switches, OFF and ON values have to be shifted 6
      if (g_eeGeneral.lightSw == MAX_SWITCH-6)
        g_eeGeneral.lightSw += 6;
      if (g_eeGeneral.lightSw == -MAX_SWITCH+6)
        g_eeGeneral.lightSw -= 6;
    }
    else {
      g_eeGeneral.inactivityTimer += 10;
    }
    g_eeGeneral.view = 0; // will not translate the view index
    EEPROM_V3::EEGeneral *old = (EEPROM_V3::EEGeneral *)&g_eeGeneral;
    g_eeGeneral.disableMemoryWarning = old->disableMemoryWarning;
    g_eeGeneral.switchWarning = old->disableSwitchWarning ? 0 : -1;
    for (uint8_t i=0; i<4; i++) {
      g_eeGeneral.trainer.mix[i].srcChn = old->trainer.mix[i].srcChn;
      g_eeGeneral.trainer.mix[i].mode = old->trainer.mix[i].mode;
      g_eeGeneral.trainer.mix[i].studWeight = old->trainer.mix[i].studWeight * 13 / 4;
    }
    for (uint8_t id=0; id<MAX_MODELS; id++) {
      theFile.openRlc(FILE_MODEL(id));
      uint16_t sz = theFile.readRlc1((uint8_t*)&g_model, sizeof(EEPROM_V4::ModelData));
      if(sz > 0) {
        EEPROM_V4::ModelData *v4 = (EEPROM_V4::ModelData *)&g_model;
        EEPROM_V3::ModelData *v3 = (EEPROM_V3::ModelData *)&g_model;
        SwashRingData swashR;
        swashR.invertELE = v4->swashInvertELE;
        swashR.invertAIL = v4->swashInvertAIL;
        swashR.invertCOL = v4->swashInvertCOL;
        swashR.type = v4->swashType;
        swashR.collectiveSource = v4->swashCollectiveSource;
        swashR.value = v4->swashRingValue;
        int8_t trims[4];
        memcpy(&trims[0], &v3->trim[0], 4);
        int8_t trimSw = v3->trimSw;
        for (uint8_t i=0; i<10; i++)
          g_model.name[i] = char2idx(g_model.name[i]);
        g_model.timer1.mode = v3->tmrMode;
        g_model.timer1.dir = v3->tmrDir;
        g_model.timer1.val = v3->tmrVal;
        g_model.protocol = v3->protocol;
        g_model.ppmNCH = v3->ppmNCH;
        g_model.thrTrim = v3->thrTrim;
        g_model.trimInc = v3->trimInc;
        g_model.pulsePol = v3->pulsePol;
        if (g_eeGeneral.myVers == EEPROM_VER_r584)
          g_model.extendedLimits = 0;
        else
          g_model.extendedLimits = v4->extendedLimits;
        g_model.extendedTrims = 0;
        g_model.spare2 = 0;
        g_model.ppmDelay = v3->ppmDelay;
        g_model.beepANACenter = v3->beepANACenter;
        g_model.timer2.mode = 0;
        g_model.timer2.dir = 0;
        g_model.timer2.val = 0;
        for (uint8_t i=0; i<MAX_MIXERS; i++) {
          memmove(&g_model.mixData[i], &v3->mixData[i], sizeof(MixData)); // MixData size changed!
          g_model.mixData[i].mixWarn = g_model.mixData[i].phase;
          g_model.mixData[i].phase = 0;
          if (g_eeGeneral.myVers == EEPROM_VER_r584 && g_model.mixData[i].srcRaw > MIX_FULL) {
            g_model.mixData[i].srcRaw += 3; /* because of [CYC1:CYC3] inserted after MIX_FULL */
          }
        }
        assert((char *)&g_model.limitData[0] < (char *)&v3->limitData[0]);
        memmove(&g_model.limitData[0], &v3->limitData[0], sizeof(LimitData)*NUM_CHNOUT);
        assert((char *)&g_model.expoData[0] < (char *)v3->expoData);
        EEPROM_V4::ExpoData expo4[4];
        memcpy(&expo4[0], &v4->expoData[0], sizeof(expo4));
        memset(&g_model.expoData[0], 0, sizeof(expo4));
        // expos conversion
        uint8_t e = 0;
        for (uint8_t ch = 0; ch < 4 && e < MAX_EXPOS; ch++) {
          for (uint8_t dr = 0, pos = 0; dr < 3 && e < MAX_EXPOS; dr++, pos++) {
            if ((dr == 0 && !expo4[ch].drSw1) || (dr == 1 && !expo4[ch].drSw2))
              dr = 2;
            if (dr == 2 && !expo4[ch].expo[0][0][0] && !expo4[ch].expo[0][0][1] && !expo4[ch].expo[0][1][0] && !expo4[ch].expo[0][1][1])
              break;
            g_model.expoData[e].swtch = (dr == 0 ? -expo4[ch].drSw1 : (dr == 1 ? -expo4[ch].drSw2 : 0));
            g_model.expoData[e].chn = ch;
            g_model.expoData[e].expo = expo4[ch].expo[pos][0][0];
            g_model.expoData[e].weight = 100 + expo4[ch].expo[pos][1][0];
            if (expo4[ch].expo[pos][0][0] == expo4[ch].expo[pos][0][1] && expo4[ch].expo[pos][1][0] == expo4[ch].expo[pos][1][1]) {
              g_model.expoData[e++].mode = 3;
            }
            else {
              g_model.expoData[e].mode = 2;
              if (e < MAX_EXPOS - 1) {
                g_model.expoData[e + 1].swtch = g_model.expoData[e].swtch;
                g_model.expoData[++e].chn = ch;
                g_model.expoData[e].mode = 1;
                g_model.expoData[e].expo = expo4[ch].expo[pos][0][1];
                g_model.expoData[e++].weight = 100 + expo4[ch].expo[pos][1][1];
              }
            }
          }
        }

        assert((char *)&g_model.curves5[0][0] < (char *)&v3->curves5[0][0]);
        memmove(&g_model.curves5[0][0], &v3->curves5[0][0], 5*MAX_CURVE5);
        assert((char *)&g_model.curves9[0][0] < (char *)&v3->curves9[0][0]);
        memmove(&g_model.curves9[0][0], &v3->curves9[0][0], 9*MAX_CURVE9);
        if (g_eeGeneral.myVers == EEPROM_VER_r584) {
          memmove(&g_model.customSw[0], &v3->customSw[0], sizeof(CustomSwData)*6);
          memset(&g_model.customSw[6], 0, sizeof(CustomSwData)*6);
          memset(&g_model.safetySw[0], 0, sizeof(SafetySwData)*NUM_CHNOUT + sizeof(SwashRingData) + sizeof(FrSkyData));
        }
        else {
          assert((char *)&g_model.customSw[0] < (char *)&v4->customSw[0]);
          memmove(&g_model.customSw[0], &v4->customSw[0], sizeof(CustomSwData)*12);
          assert((char *)&g_model.safetySw[0] < (char *)&v4->safetySw[0]);
          memmove(&g_model.safetySw[0], &v4->safetySw[0], sizeof(SafetySwData)*NUM_CHNOUT);
          memcpy(&g_model.swashR, &swashR, sizeof(SwashRingData));
          for (uint8_t i=0; i<2; i++) {
            // TODO this conversion is bad
            // assert(&g_model.frsky.channels[i].ratio < &v4->frsky.channels[i].ratio);
            g_model.frsky.channels[i].ratio = v4->frsky.channels[i].ratio;
            g_model.frsky.channels[i].type = v4->frsky.channels[i].type;
            // g_model.frsky.channels[i].offset = 0;
            g_model.frsky.channels[i].alarms_value[0] = v4->frsky.channels[i].alarms_value[0];
            g_model.frsky.channels[i].alarms_value[1] = v4->frsky.channels[i].alarms_value[1];
            g_model.frsky.channels[i].alarms_level = v4->frsky.channels[i].alarms_level;
            g_model.frsky.channels[i].alarms_greater = v4->frsky.channels[i].alarms_greater;
            g_model.frsky.channels[i].barMin = 0;
            g_model.frsky.channels[i].barMax = 0;
          }
        }
        memset(&g_model.phaseData[0], 0, sizeof(g_model.phaseData));
        memset(&g_model.funcSw[0], 0, sizeof(g_model.funcSw));
        if (trimSw) {
          g_model.funcSw[0].swtch = trimSw;
          g_model.funcSw[0].func = FUNC_INSTANT_TRIM;
        }
        for (uint8_t i=0; i<NUM_STICKS; i++)
          setTrimValue(0, i, trims[i]);
        g_model.ppmFrameLength = 0;
        theFile.writeRlc(FILE_MODEL(id), FILE_TYP_MODEL, (uint8_t*)&g_model, sizeof(g_model), 200);
      }
    }
    g_eeGeneral.myVers = EEPROM_VER;
    theFile.writeRlc(FILE_GENERAL, FILE_TYP_GENERAL, (uint8_t*)&g_eeGeneral, sizeof(EEGeneral), 200);
    return sizeof(EEGeneral);
  }

  return 0;
}