Пример #1
0
static void mix_4WS(u8 action) {
    u8 val;

    if (action == MLA_CHG) {
	// change value
	switch (menu_set) {
	    case 0:
		// channel number/off
		val = cm.channel_4WS;
		if (!val)  val = 2;
		val = (u8)menu_change_val(val, 2, channels, 1, 1);
		if (val == 2)   cm.channel_4WS = 0;
		else		cm.channel_4WS = val;
		break;
	    case 1:
		// mix value
		menu_4WS_mix = (s8)menu_change_val(menu_4WS_mix, -100, 100,
						   MIX_FAST, 0);
		break;
	    case 2:
		// crab/no-crab
		menu_4WS_crab ^= 1;
	}
    }
    else if (action == MLA_NEXT) {
	// select next value
	if (++menu_set > 2)   menu_set = 0;
	if (!cm.channel_4WS)  menu_set = 0;
    }

    // show value
    lcd_7seg(4);
    switch (menu_set) {
	case 0:
	    // channel number/OFF
	    if (!cm.channel_4WS)  lcd_chars("OFF");
	    else		  lcd_char_num3(cm.channel_4WS);
	    lcd_segment(LS_SYM_CHANNEL, LS_ON);
	    break;
	case 1:
	    // mix value
	    lcd_char_num3(menu_4WS_mix);
	    lcd_segment(LS_SYM_PERCENT, LS_ON);
	    break;
	case 2:
	    // crab/no-crab
	    lcd_chars("CR");
	    lcd_char(LCHR3, (u8)(menu_4WS_crab + '0'));
	    menu_blink &= (u8)~(MCB_CHR1 | MCB_CHR2);
    }
}
Пример #2
0
// set dualrate
static void sf_dualrate(u8 channel, u8 change) {
    u8 *addr = &cm.dualrate[channel];
    if (channel == 1 && menu_adc_direction)  addr = &cm.dualrate[2];
    if (change)  *addr = (u8)menu_change_val(*addr, 0, 100, DUALRATE_FAST, 0);
    lcd_segment(LS_SYM_PERCENT, LS_ON);
    lcd_char_num3(*addr);
}
Пример #3
0
// set endpoints
void sf_endpoint(u8 channel, u8 change) {
    u8 *addr = &cm.endpoint[channel][menu_adc_direction];
    if (change)  *addr = (u8)menu_change_val(*addr, 0, cg.endpoint_max,
                                             ENDPOINT_FAST, 0);
    lcd_segment(LS_SYM_PERCENT, LS_ON);
    lcd_char_num3(*addr);
}
Пример #4
0
// set subtrims
static void sf_subtrim(u8 channel, u8 change) {
    s8 *addr = &cm.subtrim[channel];
    if (change)
	*addr = (s8)menu_change_val(*addr, -SUBTRIM_MAX, SUBTRIM_MAX,
	                            SUBTRIM_FAST, 0);
    lcd_char_num3(*addr);
}
Пример #5
0
// show main screen (model number and name/battery/...)
static void main_screen(u8 item) {
    menu_adc_wakeup = 0;

    // chars is item dependent
    if (item == MS_NAME) {
	// model name
	lcd_segment(LS_SYM_CHANNEL, LS_OFF);
	lcd_segment(LS_SYM_DOT, LS_OFF);
	lcd_segment(LS_SYM_VOLTS, LS_OFF);
	show_model_number(cg.model);
	lcd_chars(cm.name);
    }
    else if (item == MS_BATTERY) {
	static u16 bat_val;
	static u16 bat_time;

	// battery voltage
	lcd_segment(LS_SYM_CHANNEL, LS_OFF);
	lcd_segment(LS_SYM_DOT, LS_ON);
	lcd_segment(LS_SYM_VOLTS, LS_ON);
	show_model_number(cg.model);
	// calculate voltage from current raw value and calib value
	if (time_sec >= bat_time) {
	    bat_time = time_sec + 2;
	    bat_val = (u16)(((u32)adc_battery * 100 + 300) / cg.battery_calib);
	}
	lcd_char_num3(bat_val);
	menu_adc_wakeup = 1;
    }
    else {
	// timers
	menu_timer_show((u8)(item - MS_TIMER0));
    }
    lcd_update();
}
Пример #6
0
// set expos
static void sf_expo(u8 channel, u8 change) {
    s8 *addr = &cm.expo[channel];
    if (channel == 1 && menu_adc_direction)  addr = &cm.expo[2];
    if (change)  *addr = (s8)menu_change_val(*addr, -EXPO_MAX, EXPO_MAX,
                                             EXPO_FAST, 0);
    lcd_segment(LS_SYM_PERCENT, LS_ON);
    lcd_char_num3(*addr);
}
Пример #7
0
// set channel value, exclude 4WS and DIG channels
//   for channels 3..8 so add 2 to channel number
static void sf_channel_val(u8 channel, u8 change) {
    s8 *addr = &menu_channel3_8[channel];
    if (change && !(menu_channels_mixed & (u8)(1 << (channel + 2))))
	*addr = (s8)menu_change_val(*addr, -100, 100, CHANNEL_FAST, 0);

    // show value
    lcd_7seg((u8)(channel + 2 + 1));
    lcd_char_num3(*addr);
}
Пример #8
0
static void gs_long_press_delay(u8 change) {
    u8 *addr = &cg.long_press_delay;
    if (change == 0xff) {
	lcd_set(L7SEG, LB_EMPTY);
	return;
    }
    if (change)  *addr = (u8)menu_change_val(*addr, 20, 200, 5, 0);
    lcd_7seg(L7_D);
    lcd_char_num3(*addr * 5);
}
Пример #9
0
static void mix_DIG(u8 action) {
    u8 val;

    if (action == MLA_CHG) {
	// change value
	switch (menu_set) {
	    case 0:
		// channel number/off
		val = cm.channel_DIG;
		if (!val)  val = 2;
		val = (u8)menu_change_val(val, 1, channels, 1, 1);
		if (val == 2)   cm.channel_DIG = 0;
		else		cm.channel_DIG = val;
		break;
	    case 1:
		// mix value
		menu_DIG_mix = (s8)menu_change_val(menu_DIG_mix, -100, 100,
						   MIX_FAST, 0);
		break;
	}
    }
    else if (action == MLA_NEXT) {
	// select next value
	if (++menu_set > 1)   menu_set = 0;
	if (!cm.channel_DIG)  menu_set = 0;
    }

    // show value
    lcd_7seg(L7_D);
    switch (menu_set) {
	case 0:
	    // channel number/OFF
	    if (!cm.channel_DIG)  lcd_chars("OFF");
	    else		  lcd_char_num3(cm.channel_DIG);
	    lcd_segment(LS_SYM_CHANNEL, LS_ON);
	    break;
	case 1:
	    // mix value
	    lcd_char_num3(menu_DIG_mix);
	    lcd_segment(LS_SYM_PERCENT, LS_ON);
	    break;
    }
}
Пример #10
0
static void gs_throttle_dead(u8 change) {
    u8 *addr = &cg.throttle_dead_zone;
    if (change == 0xff) {
	lcd_set(L7SEG, LB_EMPTY);
	return;
    }
    if (change)  *addr = (u8)menu_change_val(*addr, 0, 50, 2, 0);
    lcd_7seg(2);
    lcd_char_num3(*addr);
}
Пример #11
0
// set number of model channels
static void menu_channels(u8 action) {
    // change value
    if (action == MLA_CHG)
	cm.channels = (u8)(menu_change_val(cm.channels + 1, 2,
					   MAX_CHANNELS, 1, 0) - 1);

    // show value
    lcd_7seg(L7_C);
    lcd_segment(LS_SYM_CHANNEL, LS_ON);
    lcd_char_num3(cm.channels + 1);
}
Пример #12
0
static void gs_battery_low(u8 change) {
    u8 *addr = &cg.battery_low;
    if (change == 0xff) {
	lcd_segment(LS_SYM_LOWPWR, LS_OFF);
	lcd_segment(LS_SYM_DOT, LS_OFF);
	lcd_segment(LS_SYM_VOLTS, LS_OFF);
	return;
    }
    if (change)  *addr = (u8)menu_change_val(*addr, 60, 105, 2, 0);
    lcd_segment(LS_SYM_LOWPWR, LS_ON);
    lcd_segment(LS_SYM_DOT, LS_ON);
    lcd_segment(LS_SYM_VOLTS, LS_ON);
    lcd_char_num3(cg.battery_low);
}
Пример #13
0
// reset value to reset_value
static void kf_reset(u8 *id, u8 *param, u8 flags, s16 *prev_val) {
    u8 *name = param ? param : id;
    et_functions_s *etf = menu_et_function_find_name(name);
    s16 val;

    if (!etf)  return;
    val = etf->reset;
    AVAL(val);

    if (flags & FF_SHOW) {
	BEEP_RESET;
	menu_et_function_show_id(etf);
	lcd_char_num3(val);
    }
}
Пример #14
0
// set servo speed
static void sf_speed(u8 channel, u8 change) {
    u8 *addr = &cm.speed[channel];
    u8 thfwdonly = (u8)(channel == 1 && menu_adc_direction ? 1 : 0);
    if (channel == 0 && menu_adc_direction)  addr = &cm.stspd_return;
    if (change) {
	if (thfwdonly)
	    // throttle forward only setting
	    cm.thspd_onlyfwd ^= 1;
	else *addr = (u8)menu_change_val(*addr, 1, 100, SPEED_FAST, 0);
    }
    if (thfwdonly)
	lcd_chars(cm.thspd_onlyfwd ? "OFF" : "ON ");
    else {
	lcd_char_num3(*addr);
	lcd_segment(LS_SYM_PERCENT, LS_ON);
    }
}
Пример #15
0
static void gs_endpoint_max(u8 change) {
    u8 *addr = &cg.endpoint_max;
    if (change == 0xff) {
	lcd_segment(LS_MENU_EPO, LS_OFF);
	lcd_segment(LS_SYM_PERCENT, LS_OFF);
	lcd_set(L7SEG, LB_EMPTY);
	return;
    }
    if (change)  *addr = (u8)menu_change_val(*addr, 100, 150, 5, 0);
    lcd_segment(LS_SYM_PERCENT, LS_ON);
    lcd_segment(LS_MENU_EPO, LS_ON);
    lcd_char_num3(*addr);
    if (*addr > 120) {
	lcd_7seg(L7_D);
	lcd_set_blink(L7SEG, LB_SPC);
    }
    else lcd_set(L7SEG, LB_EMPTY);
}
Пример #16
0
// multi-position show and set value
static void show_MP(u8 *name, s16 val) {
    u8 mp_id = (u8)(name[2] - '1');
    s8 *multi_position;
    u8 channel_MP;
    u8 num_MP = config_get_MP(mp_id, &channel_MP, &multi_position);

    // show also selected channel/DIG
    if (channel_MP == MP_DIG) {
	lcd_7seg(L7_D);
	lcd_menu(LM_EPO);
	lcd_set_blink(LMENU, LB_SPC);
	lcd_segment(LS_SYM_CHANNEL, LS_OFF);
	lcd_segment(LS_SYM_PERCENT, LS_ON);
    }
    else {
	lcd_7seg(channel_MP);
	lcd_segment(LS_SYM_CHANNEL, LS_ON);
    }
    lcd_char_num3(multi_position[menu_MP_index[mp_id]]);
}
Пример #17
0
// set channel value to one endpoint (also to middle with 3-pos CH3)
static void kf_set_switch(u8 *id, u8 *param, u8 flags, s16 *prev_val) {
    u8 *name = param ? param : id;
    et_functions_s *etf = menu_et_function_find_name(name);
    s16 val;

    if (!etf)  return;
    RVAL(val);

    if (flags & FF_MID) {
	// middle
	if (flags & FF_PREVIOUS)  val = *prev_val;
	else			  val = etf->reset;
    }
    else if (flags & FF_ON) {
	// ON
	*prev_val = val;  // always save previous val
	val = (s8)(flags & FF_REVERSE ? etf->min : etf->max);
    }
    else {
	// OFF
	if ((flags & FF_PREVIOUS) && !(flags & FF_HAS_MID))
	    // use previous only when there is no middle state
	    val = *prev_val;
	else {
	    // save previous only when there is middle state
	    if (flags & FF_HAS_MID)  *prev_val = val;
	    val = (s8)(flags & FF_REVERSE ? etf->max : etf->min);
	}
    }
    AVAL(val);

    if (flags & FF_SHOW) {
	menu_et_function_show_id(etf);
	lcd_char_num3(val);
    }
}
Пример #18
0
static void mix_MultiPosition(u8 action) {
    s8 val;

    if (action == MLA_CHG) {
	// change value
	if (menu_set == 0) {
	    // channel number/off
	    val = cm.channel_MP;
	    if (!val)  val = 2;
	    else if (val == MP_DIG)  val = (s8)(channels + 1);
	    val = (u8)menu_change_val(val, 2, channels + 1, 1, 1);
	    if (val == 2)   			cm.channel_MP = 0;
	    else if (val == (s8)(channels + 1))	cm.channel_MP = MP_DIG;
	    else	    			cm.channel_MP = val;
	}
	else {
	    // position value + END state (END not for first position)
	    val = cm.multi_position[menu_set - 1];
	    if (val == MULTI_POSITION_END)  val = -101;
	    val = (s8)menu_change_val(val, menu_set == 1 ? -100 : -101, 100,
				      CHANNEL_FAST, 0);
	    if (val == -101) {
		// set all from this to END value
		memset(&cm.multi_position[menu_set - 1], (u8)MULTI_POSITION_END,
		       NUM_MULTI_POSITION + 1 - menu_set);
	    }
	    else cm.multi_position[menu_set - 1] = val;
	}
    }
    else if (action == MLA_NEXT) {
	// select next value
	if (cm.channel_MP) {
	    if (menu_set == 0)  menu_set = 1;
	    else if (cm.multi_position[menu_set - 1] == MULTI_POSITION_END
		    || ++menu_set > NUM_MULTI_POSITION)  menu_set = 0;
	}
	// allow forcing channel value
	if (menu_set && cm.channel_MP && cm.channel_MP <= channels) {
	    menu_force_value_channel = cm.channel_MP;
	}
	else menu_force_value_channel = 0;
    }

    // show value
    lcd_7seg(L7_P);
    if (menu_set == 0) {
	// channel number/OFF
	if (!cm.channel_MP)	lcd_chars("OFF");
	else if (cm.channel_MP == MP_DIG)
				lcd_chars("DIG");
	else			lcd_char_num3(cm.channel_MP);
	lcd_segment(LS_SYM_CHANNEL, LS_ON);
    }
    else {
	// position value
	val = cm.multi_position[menu_set - 1];
	if (val == MULTI_POSITION_END) {
	    lcd_chars("END");
	    val = -100;
	}
	else  lcd_char_num3(val);
	if (cm.channel_MP == MP_DIG)	menu_DIG_mix = val;
	else				menu_force_value = val * PPM(5);
    }
}
Пример #19
0
// calibrate menu
void menu_calibrate(u8 at_poweron) {
    u8 channel = 1;
    u16 last_val = 0xffff;
    u16 val;
    u8 seg;
    u8 bat_volts;
    u16 update_time = 0;
    u16 update_val = 0;

    menu_adc_wakeup = 1;

    // cleanup screen and disable possible low bat warning
    buzzer_off();
    if (at_poweron)	buzzer_on(30, 30, 2);
    else		key_beep();
    menu_battery_low = 0;	// it will be set automatically again
    battery_low_shutup = 0;
    backlight_set_default(BACKLIGHT_MAX);
    backlight_on();
    lcd_clear();

    btnra();

    // show intro text
    lcd_chars("CAL");
    lcd_update();
    delay_menu_always(2);

    // show channel number and not-yet calibrated values
    lcd_segment(LS_SYM_CHANNEL, LS_ON);
    lcd_7seg(channel);
    lcd_menu(LM_MODEL | LM_NAME | LM_REV | LM_TRIM | LM_DR | LM_EXP);
    lcd_set_blink(LMENU, LB_SPC);

    while (1) {
	// check keys
	if (btnl(BTN_BACK | BTN_ENTER))  break;

	if (btn(BTN_END | BTN_ROT_ALL)) {
	    if (btn(BTN_END))  key_beep();
	    // change channel number
	    channel = (u8)menu_change_val(channel, 1, 4, 1, 1);
	    lcd_7seg(channel);
	    lcd_update();
	    update_time = 0;
	}

	else if (btn(BTN_ENTER)) {
	    // save calibrate value for channels 1 and 2
	    // select actual voltage for channel 4
	    if (channel == 1) {
		key_beep();
		val = ADC_OVS(steering);
		if (val < CALIB_ST_LOW_MID) {
		    cg.calib_steering_left = val;
		    seg = LS_MENU_MODEL;
		}
		else if (val <= CALIB_ST_MID_HIGH) {
		    cg.calib_steering_mid = val;
		    seg = LS_MENU_NAME;
		}
		else {
		    cg.calib_steering_right = val;
		    seg = LS_MENU_REV;
		}
		lcd_segment(seg, LS_OFF);
		lcd_update();
	    }
	    else if (channel == 2) {
		key_beep();
		val = ADC_OVS(throttle);
		if (val < CALIB_TH_LOW_MID) {
		    cg.calib_throttle_fwd = val;
		    seg = LS_MENU_TRIM;
		}
		else if (val <= CALIB_TH_MID_HIGH) {
		    cg.calib_throttle_mid = val;
		    seg = LS_MENU_DR;
		}
		else {
		    cg.calib_throttle_bck = val;
		    seg = LS_MENU_EXP;
		}
		lcd_segment(seg, LS_OFF);  // set corresponding LCD off
		lcd_update();
	    }
	    else if (channel == 4) {
		key_beep();
		// allow to set actual battery voltage
		lcd_segment(LS_SYM_DOT, LS_ON);
		lcd_segment(LS_SYM_VOLTS, LS_ON);
		bat_volts = (u8)(((u32)adc_battery * 100 + 300) / cg.battery_calib);
		lcd_char_num3(bat_volts);
		lcd_update();

		while (1) {
		    btnra();
		    stop();

		    if (btnl(BTN_BACK) || btn(BTN_ENTER | BTN_END))  break;
		    if (btn(BTN_ROT_ALL)) {
			if (btn(BTN_ROT_L))  bat_volts--;
			else                 bat_volts++;
			lcd_char_num3(bat_volts);
			lcd_update();
		    }
		}

		key_beep();
		lcd_segment(LS_SYM_DOT, LS_OFF);
		lcd_segment(LS_SYM_VOLTS, LS_OFF);
		last_val = 0xffff;	// show ADC value
		if (!btn(BTN_END)) {
		    // recalculate calibrate value for 10V
		    cg.battery_calib = (u16)(((u32)adc_battery * 100 + 40) / bat_volts);
		    if (btnl(BTN_BACK | BTN_ENTER))  break;
		}
	    }
	}

	// show ADC value if other than last val
	switch (channel) {
	    case 1:
		val = ADC_OVS(steering);
		break;
	    case 2:
		val = ADC_OVS(throttle);
		break;
	    case 3:
		val = ADC_OVS(ch3);
		break;
	    case 4:
		val = adc_battery;
		break;
	    default:		// to eliminate compiler warning
		val = 0;
	}
	// only update display every 1s
	if (time_sec >= update_time) {
	    update_time = time_sec + 1;
	    update_val = val;
	}
	if (update_val != last_val) {
	    last_val = update_val;
	    lcd_char_num3(val);
	    lcd_update();
	}

	btnra();
	stop();
    }

    menu_adc_wakeup = 0;
    beep(60);
    lcd_menu(0);
    lcd_update();
    config_global_save();
    apply_global_config();
}
Пример #20
0
// 7seg:  1 2 3 d
// chars:
// function
//   OFF
//   other -> buttons
//              MO	 -> 	    reverse              -> prev_val
//           NL/RP/RE/EN -> step -> reverse -> opp_reset             -> rotate
// id:                       V
static void km_trim(u8 action) {
    config_et_map_s *etm = &ck.et_map[menu_id];
    u8 idx, btn, new_idx = 0;

    if (action == 1) {
	// change value
	switch (menu_set) {
	    case 0:
		// function
		// select new function, map through trim_functions
		if (!etm->is_trim)  etm->function = 0;
		idx = menu_et_function_idx(etm->function);
		while (1) {
		    idx = (u8)menu_change_val(idx, 0, trim_functions_max, 1, 1);
		    new_idx = trim_functions[idx];
		    if (!new_idx)  continue;				// empty slot
		    new_idx--;  // was one more
		    if (menu_et_function_is_allowed(new_idx))  break;	// we have it
		}
		// set values to defaults
		((u16 *)etm)[0] = 0;
		((u16 *)etm)[1] = 0;
		etm->function = new_idx;
		if (etm->function)
		    etm->is_trim = etm->is_trim2 = 1;
		break;
	    case 1:
		// buttons
		// show special ("SP") only when selected function has it
		if (menu_et_function_long_special(etm->function))
			idx = 1;
		else	idx = 2;
		btn = etm->buttons;
		btn = (u8)menu_change_val(btn, 0, TRIM_BUTTONS_SIZE - idx,
					  1, 1);
		// skip MOMentary for list functions
		if (btn == ETB_MOMENTARY &&
		    menu_et_function_is_list(etm->function)) {
		    if (etm->buttons < ETB_MOMENTARY)  btn++;
		    else  btn--;
		}
		etm->buttons = btn;
		break;
	    case 2:
		// step
		etm->step = (u8)menu_change_val(etm->step, 0,
						STEPS_MAP_SIZE - 1, 1, 0);
		break;
	    case 3:
		// reverse
		etm->reverse ^= 1;
		break;
	    case 4:
		// opposite reset
		etm->opposite_reset ^= 1;
		break;
	    case 5:
		// return to previous value
		etm->previous_val ^= 1;
		break;
	    case 6:
		etm->rotate ^= 1;
		break;
	}
    }

    else if (action == 2) {
	// switch to next setting
	if (menu_set || etm->is_trim) {
	    if (etm->buttons == ETB_MOMENTARY) {
		if (++menu_set > 5)       menu_set = 0;
		else if (menu_set == 2)   menu_set = 3;  // skip "step" for momentary
		else if (menu_set == 4)   menu_set = 5;  // skip "opposite_reset"
	    }
	    else {
		if (++menu_set > 4)  menu_set = 0;
		else if (menu_et_function_is_list(etm->function)) {
		    if (menu_set == 2) {
			// skip "step"
			menu_set++;
			etm->step = 0;
		    }
		    else if (menu_set == 4) {
			// skip "opposite reset"
			menu_set = 6;
			etm->opposite_reset = 0;
		    }
		}
	    }
	}
    }

    // show value of menu_set
    switch (menu_set) {
	case 0:
	    // function
	    if (!etm->is_trim)  lcd_chars("OFF");
	    else  lcd_chars(menu_et_function_name(etm->function));
	    break;
	case 1:
	    // buttons
	    lcd_char(LCHR1, 'B');
	    lcd_chars2(trim_buttons[etm->buttons]);
	    menu_blink &= (u8)~MCB_CHR1;
	    break;
	case 2:
	    // step
	    lcd_char_num3(steps_map[etm->step]);
	    lcd_segment(LS_SYM_VOLTS, LS_ON);
	    break;
	case 3:
	    // reverse
	    lcd_chars("RE");
	    lcd_char(LCHR3, (u8)(etm->reverse + '0'));
	    menu_blink &= (u8)~(MCB_CHR1 | MCB_CHR2);
	    break;
	case 4:
	    // opposite reset
	    lcd_chars("OR");
	    lcd_char(LCHR3, (u8)(etm->opposite_reset + '0'));
	    menu_blink &= (u8)~(MCB_CHR1 | MCB_CHR2);
	    break;
	case 5:
	    // previous val
	    lcd_chars("PV");
	    lcd_char(LCHR3, (u8)(etm->previous_val + '0'));
	    menu_blink &= (u8)~(MCB_CHR1 | MCB_CHR2);
	    break;
	case 6:
	    // rotate
	    lcd_chars("RO");
	    lcd_char(LCHR3, (u8)(etm->rotate + '0'));
	    menu_blink &= (u8)~(MCB_CHR1 | MCB_CHR2);
	    break;
    }
}
Пример #21
0
static u8 menu_popup_et(u8 trim_id) {
    u16 delay_time;
    s16 val;
    u8  step;
    u16 buttons_state_last;
    u16 btn_l = ETB_L(trim_id);
    u16 btn_r = ETB_R(trim_id);
    u16 btn_lr = btn_l | btn_r;
    config_et_map_s *etm = &ck.et_map[trim_id];
#define SF_ROTATE  etm->rotate
    et_functions_s *etf = &et_functions[etm->function];
    u8 *mbs = &menu_buttons_state[NUM_KEYS + 2 * trim_id];

    // read value
    RVAL(val);

    // remember buttons state
    buttons_state_last = buttons_state & ~btn_lr;

    // handle momentary keys
    //   when something changed, show value for 5s while checking buttons
    //   during initialize show nothing
    if (etm->buttons == ETB_MOMENTARY) {
	s16 *pv = &menu_buttons_previous_values[NUM_KEYS + 2 * trim_id];
	u8 value_showed = 0;
	u8 state;

	while (1) {
	    // set actual state of btn_lr to buttons_state_last
	    buttons_state_last &= ~btn_lr;
	    buttons_state_last |= buttons_state & btn_lr;

	    // check buttons
	    if (btns(btn_l)) {
		// left
		if (*mbs == MBS_LEFT)  break;	// already was left
		state = MBS_LEFT;
		*pv = val;			// save previous value
		val = etm->reverse ? etf->max : etf->min;
	    }
	    else if (btns(btn_r)) {
		// right
		if (*mbs == MBS_RIGHT)  break;	// already was right
		state = MBS_RIGHT;
		*pv = val;			// save previous value
		val = etm->reverse ? etf->min : etf->max;
	    }
	    else {
		// center
		if (*mbs == MBS_RELEASED)  break;  // already was center
		state = MBS_RELEASED;
		if (etm->previous_val) {
		    if (*mbs != MBS_INITIALIZE)  val = *pv;
		}
		else  val = etf->reset;
	    }
	    AVAL(val);
	    if (*mbs == MBS_INITIALIZE) {
		// show nothing when doing initialize
		*mbs = state;
		break;
	    }
	    *mbs = state;
	    btnr(btn_lr);
	    key_beep();

	    // if another button was pressed, leave this screen
	    if (buttons)  break;
	    if (buttons_state != buttons_state_last) break;

	    // show function id first time
	    if (!value_showed) {
		value_showed = 1;
		menu_et_function_show_id(etf);
	    }

	    // show current value
	    if (etf->show_func)  etf->show_func(etf->name, val);
	    else		 lcd_char_num3(val);
	    lcd_update();

	    // sleep 5s, and if no button was changed during, end this screen
	    delay_time = POPUP_DELAY * 200;
	    while (delay_time && !(buttons & ~btn_lr) &&
		   (buttons_state == buttons_state_last))
		delay_time = delay_menu(delay_time);

	    if ((buttons_state & btn_lr) == (buttons_state_last & btn_lr))
		break;
	}

	btnr(btn_lr);
	if (value_showed)  lcd_menu(0);		// set MENU off
	return value_showed;
    }

    // if button is not initialized, do it
    if (*mbs == MBS_INITIALIZE) {
	// set value to default only for non-config values (mixes, channels...)
	if (etf->flags & EF_NOCONFIG) {
	    val = etf->reset;
	    AVAL(val);
	}
	*mbs = MBS_RELEASED;
    }

    // return when key was not pressed
    if (!btn(btn_lr))	 return 0;

    // convert steps
    step = steps_map[etm->step];

    // show MENU and CHANNEL
    menu_et_function_show_id(etf);

    while (1) {
	u8  val_set_to_reset = 0;

	// check value left/right
	if (btnl_all(btn_lr)) {
	    // both long keys together
	    key_beep();
	    if (etf->long_func && etm->buttons == ETB_SPECIAL)
		// special handling
		etf->long_func(etf->name, &val, btn_l, btn_r);
	    else {
		// reset to given reset value
		val = etf->reset;
	    }
	    AVAL(val);
	    if (val == etf->reset)  val_set_to_reset = 1;
	    btnr(btn_lr);
	}
	else if (btn(btn_lr)) {
	    if (!btns_all(btn_lr)) {
		// only one key is currently pressed
		key_beep();
		if (etf->long_func && etm->buttons == ETB_SPECIAL &&
		    btnl(btn_lr))
		    // special handling
		    etf->long_func(etf->name, &val, btn_l, btn_r);
		else if ((etm->buttons == ETB_LONG_RESET ||
			  etm->buttons == ETB_LONG_ENDVAL) && btnl(btn_lr)) {
		    // handle long key press
		    if (etm->buttons == ETB_LONG_RESET) {
			val = etf->reset;
		    }
		    else {
			// set side value
			if ((u8)(btn(btn_l) ? 1 : 0) ^ etm->reverse)
			    val = etf->min;
			else
			    val = etf->max;
		    }
		}
		else {
		    // handle short key press
		    if ((u8)(btn(btn_l) ? 1 : 0) ^ etm->reverse) {
			val -= step;
			if (val < etf->min) {
			    if (etm->rotate)  val = etf->max;
			    else	      val = etf->min;
			}
			if (etm->opposite_reset &&
			    val > etf->reset)  val = etf->reset;
		    }
		    else {
			val += step;
			if (val > etf->max) {
			    if (etm->rotate)  val = etf->min;
			    else	      val = etf->max;
			}
			if (etm->opposite_reset &&
			    val < etf->reset)  val = etf->reset;
		    }
		}
		AVAL(val);
		if (val == etf->reset)  val_set_to_reset = 1;
		btnr(btn_lr);
	    }
	    else btnr_nolong(btn_lr);  // keep long-presses for testing-both
	}
	else if (btn(BTN_ROT_ALL)) {
	    s16 val2 = val;
	    val = menu_change_val(val, etf->min, etf->max, etf->rot_fast_step,
	                          etm->rotate);
	    // if encoder skipped reset value, set it to reset value
	    if (val2 < etf->reset && val > etf->reset ||
	        val2 > etf->reset && val < etf->reset)  val = etf->reset;
	    AVAL(val);
	    if (val == etf->reset)  val_set_to_reset = 1;
	}
	btnr(BTN_ROT_ALL);

	// longer beep at value reset value
	if (val_set_to_reset)  BEEP_RESET;

	// if another button was pressed, leave this screen
	if (buttons)  break;
	if ((buttons_state & ~btn_lr) != buttons_state_last)  break;

	// show current value
	if (etf->show_func)  etf->show_func(etf->name, val);
	else		 lcd_char_num3(val);
	lcd_update();

	// if reset value was reached, ignore rotate/btn_lr for some time
	delay_time = POPUP_DELAY * 200;
	if (val_set_to_reset) {
	    u8 delay = RESET_VALUE_DELAY;
	    while (delay && !(buttons & ~(btn_lr | BTN_ROT_ALL)) &&
		   ((buttons_state & ~btn_lr) == buttons_state_last))
		delay = (u8)delay_menu(delay);
	    btnr(BTN_ROT_ALL | btn_lr);
	    delay_time -= RESET_VALUE_DELAY;
	}

	// sleep 5s, and if no button was changed during, end this screen
	while (delay_time && !buttons &&
	       ((buttons_state & ~btn_lr) == buttons_state_last))
	    delay_time = delay_menu(delay_time);

	if (!buttons)  break;  // timeouted without button press
    }

    btnr(btn_lr);  // reset also long values

    // set MENU off
    lcd_menu(0);

    // save model config
    config_model_save();
    return 1;
}